diff --git a/.gitattributes b/.gitattributes index a06f6efa295ac8d7ab21bacf3999b28079efbca5..c633db7b9f3d1313572d0a0e47f5bc8c69a3e5e0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,3 +27,9 @@ phivenv/Lib/site-packages/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 filter phivenv/Lib/site-packages/numpy/fft/_pocketfft_umath.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text phivenv/Lib/site-packages/numpy/lib/tests/__pycache__/test_function_base.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text phivenv/Lib/site-packages/numpy/lib/__pycache__/_function_base_impl.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text +phivenv/Lib/site-packages/numpy/linalg/_umath_linalg.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text +phivenv/Lib/site-packages/numpy/linalg/__pycache__/_linalg.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text +phivenv/Lib/site-packages/numpy/ma/tests/__pycache__/test_core.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text +phivenv/Lib/site-packages/numpy/ma/__pycache__/core.cpython-39.pyc filter=lfs diff=lfs merge=lfs -text +phivenv/Lib/site-packages/numpy/random/bit_generator.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text +phivenv/Lib/site-packages/numpy/random/mtrand.cp39-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text diff --git a/phivenv/Lib/site-packages/numpy/linalg/__pycache__/_linalg.cpython-39.pyc b/phivenv/Lib/site-packages/numpy/linalg/__pycache__/_linalg.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..080e645edcfeed97c405223eea7240449392315a --- /dev/null +++ b/phivenv/Lib/site-packages/numpy/linalg/__pycache__/_linalg.cpython-39.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52844dee0838dc44a68055ecb0d2cdebec4a2b210dbd133de29b9b049c1745c7 +size 100370 diff --git a/phivenv/Lib/site-packages/numpy/linalg/_umath_linalg.cp39-win_amd64.pyd b/phivenv/Lib/site-packages/numpy/linalg/_umath_linalg.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..ad23a75bca19fc1e210d5a526b996ea8e4cb2b26 --- /dev/null +++ b/phivenv/Lib/site-packages/numpy/linalg/_umath_linalg.cp39-win_amd64.pyd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6b68375b64499649bb6463e9bdfbc0f9ce2f452f802f0ca03d25c1117bab512 +size 108544 diff --git a/phivenv/Lib/site-packages/numpy/ma/__pycache__/core.cpython-39.pyc b/phivenv/Lib/site-packages/numpy/ma/__pycache__/core.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c41400eece64e465691597ac2eb8eacf8f4e982 --- /dev/null +++ b/phivenv/Lib/site-packages/numpy/ma/__pycache__/core.cpython-39.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4edfcc64904822f100b97598aa44eaebf1d5a60aacda9a655253c40564c5eb7f +size 229071 diff --git a/phivenv/Lib/site-packages/numpy/ma/tests/__pycache__/test_core.cpython-39.pyc b/phivenv/Lib/site-packages/numpy/ma/tests/__pycache__/test_core.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..591b520dc3c42f2b8c43379ef0b4b6cce0cad623 --- /dev/null +++ b/phivenv/Lib/site-packages/numpy/ma/tests/__pycache__/test_core.cpython-39.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c5850c6af5b2ad19bbc0c2dee5fc585ed2be4d2d5cf07df7b068a00a107e944 +size 171203 diff --git a/phivenv/Lib/site-packages/numpy/random/bit_generator.cp39-win_amd64.pyd b/phivenv/Lib/site-packages/numpy/random/bit_generator.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..d265396dc67c1a9b7f12774336916d87328d4cfc --- /dev/null +++ b/phivenv/Lib/site-packages/numpy/random/bit_generator.cp39-win_amd64.pyd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58420f10150e71e4cfa1b02863292ce76f48d3b3874e18826d523f208d8a70bb +size 175616 diff --git a/phivenv/Lib/site-packages/numpy/random/mtrand.cp39-win_amd64.pyd b/phivenv/Lib/site-packages/numpy/random/mtrand.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..1feaa2d248260d8aeff292d96906dc00c87fa7f1 --- /dev/null +++ b/phivenv/Lib/site-packages/numpy/random/mtrand.cp39-win_amd64.pyd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:475a979aca48a1132ec8ac7cc87ec5dab85fde5085bde1febdf54266c03d0bea +size 644608 diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/INSTALLER b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/LICENSE b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..353924be0e59b9ad7e6c22848c2189398481821d --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/LICENSE @@ -0,0 +1,19 @@ +Copyright Jason R. Coombs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/METADATA b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..fe760b18d9d02c9d223026265832a387a284ee0b --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/METADATA @@ -0,0 +1,119 @@ +Metadata-Version: 2.1 +Name: setuptools +Version: 58.1.0 +Summary: Easily download, build, install, upgrade, and uninstall Python packages +Home-page: https://github.com/pypa/setuptools +Author: Python Packaging Authority +Author-email: distutils-sig@python.org +License: UNKNOWN +Project-URL: Documentation, https://setuptools.readthedocs.io/ +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires-Python: >=3.6 +License-File: LICENSE +Provides-Extra: certs +Provides-Extra: docs +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: jaraco.packaging (>=8.2) ; extra == 'docs' +Requires-Dist: rst.linker (>=1.9) ; extra == 'docs' +Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs' +Requires-Dist: pygments-github-lexers (==0.0.5) ; extra == 'docs' +Requires-Dist: sphinx-inline-tabs ; extra == 'docs' +Requires-Dist: sphinxcontrib-towncrier ; extra == 'docs' +Requires-Dist: furo ; extra == 'docs' +Provides-Extra: ssl +Provides-Extra: testing +Requires-Dist: pytest (>=4.6) ; extra == 'testing' +Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing' +Requires-Dist: pytest-flake8 ; extra == 'testing' +Requires-Dist: pytest-cov ; extra == 'testing' +Requires-Dist: pytest-enabler (>=1.0.1) ; extra == 'testing' +Requires-Dist: mock ; extra == 'testing' +Requires-Dist: flake8-2020 ; extra == 'testing' +Requires-Dist: virtualenv (>=13.0.0) ; extra == 'testing' +Requires-Dist: pytest-virtualenv (>=1.2.7) ; extra == 'testing' +Requires-Dist: wheel ; extra == 'testing' +Requires-Dist: paver ; extra == 'testing' +Requires-Dist: pip (>=19.1) ; extra == 'testing' +Requires-Dist: jaraco.envs ; extra == 'testing' +Requires-Dist: pytest-xdist ; extra == 'testing' +Requires-Dist: sphinx ; extra == 'testing' +Requires-Dist: jaraco.path (>=3.2.0) ; extra == 'testing' +Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing' +Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing' + +.. image:: https://img.shields.io/pypi/v/setuptools.svg + :target: `PyPI link`_ + +.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/setuptools + +.. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg + :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg + :target: https://setuptools.readthedocs.io + +.. image:: https://img.shields.io/badge/skeleton-2021-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white + :target: https://codecov.io/gh/pypa/setuptools + +.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme + +See the `Installation Instructions +`_ in the Python Packaging +User's Guide for instructions on installing, upgrading, and uninstalling +Setuptools. + +Questions and comments should be directed to the `distutils-sig +mailing list `_. +Bug reports and especially tested patches may be +submitted directly to the `bug tracker +`_. + + +Code of Conduct +=============== + +Everyone interacting in the setuptools project's codebases, issue trackers, +chat rooms, and mailing lists is expected to follow the +`PSF Code of Conduct `_. + + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + + +Security Contact +================ + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. + + diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/RECORD b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..254299940088671e8cb9b3682610d60af071ddae --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/RECORD @@ -0,0 +1,296 @@ +_distutils_hack/__init__.py,sha256=X3RUiA6KBPoEmco_CjACyltyQbFRGVUpZRAbSkPGwMs,3688 +_distutils_hack/__pycache__/__init__.cpython-39.pyc,, +_distutils_hack/__pycache__/override.cpython-39.pyc,, +_distutils_hack/override.py,sha256=Eu_s-NF6VIZ4Cqd0tbbA5wtWky2IZPNd8et6GLt1mzo,44 +distutils-precedence.pth,sha256=fqf_7z_ioRfuEsaO1lU2F_DX_S8FkCV8JcSElZo7c3M,152 +pkg_resources/__init__.py,sha256=P3PNN3_m8JJrYMp-i-Sq-3rhK5vuViqqjn1UXKHfe7Q,108202 +pkg_resources/__pycache__/__init__.cpython-39.pyc,, +pkg_resources/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pkg_resources/_vendor/__pycache__/__init__.cpython-39.pyc,, +pkg_resources/_vendor/__pycache__/appdirs.cpython-39.pyc,, +pkg_resources/_vendor/__pycache__/pyparsing.cpython-39.pyc,, +pkg_resources/_vendor/appdirs.py,sha256=MievUEuv3l_mQISH5SF0shDk_BNhHHzYiAPrT3ITN4I,24701 +pkg_resources/_vendor/packaging/__about__.py,sha256=PNMsaZn4UcCHyubgROH1bl6CluduPjI5kFrSp_Zgklo,736 +pkg_resources/_vendor/packaging/__init__.py,sha256=6enbp5XgRfjBjsI9-bn00HjHf5TH21PDMOKkJW8xw-w,562 +pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/_typing.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/markers.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/tags.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/utils.cpython-39.pyc,, +pkg_resources/_vendor/packaging/__pycache__/version.cpython-39.pyc,, +pkg_resources/_vendor/packaging/_compat.py,sha256=MXdsGpSE_W-ZrHoC87andI4LV2FAwU7HLL-eHe_CjhU,1128 +pkg_resources/_vendor/packaging/_structures.py,sha256=ozkCX8Q8f2qE1Eic3YiQ4buDVfgz2iYevY9e7R2y3iY,2022 +pkg_resources/_vendor/packaging/_typing.py,sha256=x59EhQ57TMT-kTRyLZV25HZvYGGwbucTo6iKh_O0tMw,1812 +pkg_resources/_vendor/packaging/markers.py,sha256=YSntQkMnKyw1_FG6oRNNnGxLL6bAxcGXOtuFE-YTS3k,9518 +pkg_resources/_vendor/packaging/requirements.py,sha256=R8K4H4xX_iD4LvpGw1U3ouuPbGN-wzsFgD7brhAM71Y,4929 +pkg_resources/_vendor/packaging/specifiers.py,sha256=uYp9l13F0LcknS6d4N60ytiBgFmIhKideOq9AnsxTco,31944 +pkg_resources/_vendor/packaging/tags.py,sha256=NKMS37Zo_nWrZxgsD6zbXsXgc9edn9m160cBiLmHJdE,24067 +pkg_resources/_vendor/packaging/utils.py,sha256=RShlvnjO2CtYSD8uri32frMMFMTmB-3ihsq1-ghzLEw,1811 +pkg_resources/_vendor/packaging/version.py,sha256=Cnbm-OO9D_qd8ZTFxzFcjSavexSYFZmyeaoPvMsjgPc,15470 +pkg_resources/_vendor/pyparsing.py,sha256=mahtkgcp3grNAD0re_9R0DLvBnvjzpeLwgJqT-3H1CE,232056 +pkg_resources/extern/__init__.py,sha256=3PixaT9Tzzd4NoyV6CVhGd7S_9Z-U5yvMWAftZKvC6k,2362 +pkg_resources/extern/__pycache__/__init__.cpython-39.pyc,, +pkg_resources/tests/data/my-test-package-source/__pycache__/setup.cpython-39.pyc,, +pkg_resources/tests/data/my-test-package-source/setup.py,sha256=Mrezl3nqxkYkjCYpIxmjhhg4AR8hgi4QZdEYmk-I7R8,104 +setuptools-58.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +setuptools-58.1.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050 +setuptools-58.1.0.dist-info/METADATA,sha256=jjOLGyArpWjlz4JTmU_TEhFruOOTABRjZYqBzJXus5A,4852 +setuptools-58.1.0.dist-info/RECORD,, +setuptools-58.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +setuptools-58.1.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +setuptools-58.1.0.dist-info/entry_points.txt,sha256=wpnhLrbtyk4hZ1qCCw48cCSxoQPzULMhIuaFqsB7GxQ,2636 +setuptools-58.1.0.dist-info/top_level.txt,sha256=d9yL39v_W7qmKDDSH6sT4bE0j_Ls1M3P161OGgdsm4g,41 +setuptools/__init__.py,sha256=l7ULo8jGk-4-8jbacmJ58cYpSRX4swS1ccbJaJVAGdM,7448 +setuptools/__pycache__/__init__.cpython-39.pyc,, +setuptools/__pycache__/_deprecation_warning.cpython-39.pyc,, +setuptools/__pycache__/_imp.cpython-39.pyc,, +setuptools/__pycache__/archive_util.cpython-39.pyc,, +setuptools/__pycache__/build_meta.cpython-39.pyc,, +setuptools/__pycache__/config.cpython-39.pyc,, +setuptools/__pycache__/dep_util.cpython-39.pyc,, +setuptools/__pycache__/depends.cpython-39.pyc,, +setuptools/__pycache__/dist.cpython-39.pyc,, +setuptools/__pycache__/errors.cpython-39.pyc,, +setuptools/__pycache__/extension.cpython-39.pyc,, +setuptools/__pycache__/glob.cpython-39.pyc,, +setuptools/__pycache__/installer.cpython-39.pyc,, +setuptools/__pycache__/launch.cpython-39.pyc,, +setuptools/__pycache__/monkey.cpython-39.pyc,, +setuptools/__pycache__/msvc.cpython-39.pyc,, +setuptools/__pycache__/namespaces.cpython-39.pyc,, +setuptools/__pycache__/package_index.cpython-39.pyc,, +setuptools/__pycache__/py34compat.cpython-39.pyc,, +setuptools/__pycache__/sandbox.cpython-39.pyc,, +setuptools/__pycache__/unicode_utils.cpython-39.pyc,, +setuptools/__pycache__/version.cpython-39.pyc,, +setuptools/__pycache__/wheel.cpython-39.pyc,, +setuptools/__pycache__/windows_support.cpython-39.pyc,, +setuptools/_deprecation_warning.py,sha256=jU9-dtfv6cKmtQJOXN8nP1mm7gONw5kKEtiPtbwnZyI,218 +setuptools/_distutils/__init__.py,sha256=lpQAphR_7uhWC2fbSEps4Ja9W4YwezN_IX_LJEt3khU,250 +setuptools/_distutils/__pycache__/__init__.cpython-39.pyc,, +setuptools/_distutils/__pycache__/_msvccompiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/archive_util.cpython-39.pyc,, +setuptools/_distutils/__pycache__/bcppcompiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/ccompiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/cmd.cpython-39.pyc,, +setuptools/_distutils/__pycache__/config.cpython-39.pyc,, +setuptools/_distutils/__pycache__/core.cpython-39.pyc,, +setuptools/_distutils/__pycache__/cygwinccompiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/debug.cpython-39.pyc,, +setuptools/_distutils/__pycache__/dep_util.cpython-39.pyc,, +setuptools/_distutils/__pycache__/dir_util.cpython-39.pyc,, +setuptools/_distutils/__pycache__/dist.cpython-39.pyc,, +setuptools/_distutils/__pycache__/errors.cpython-39.pyc,, +setuptools/_distutils/__pycache__/extension.cpython-39.pyc,, +setuptools/_distutils/__pycache__/fancy_getopt.cpython-39.pyc,, +setuptools/_distutils/__pycache__/file_util.cpython-39.pyc,, +setuptools/_distutils/__pycache__/filelist.cpython-39.pyc,, +setuptools/_distutils/__pycache__/log.cpython-39.pyc,, +setuptools/_distutils/__pycache__/msvc9compiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/msvccompiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/py35compat.cpython-39.pyc,, +setuptools/_distutils/__pycache__/py38compat.cpython-39.pyc,, +setuptools/_distutils/__pycache__/spawn.cpython-39.pyc,, +setuptools/_distutils/__pycache__/sysconfig.cpython-39.pyc,, +setuptools/_distutils/__pycache__/text_file.cpython-39.pyc,, +setuptools/_distutils/__pycache__/unixccompiler.cpython-39.pyc,, +setuptools/_distutils/__pycache__/util.cpython-39.pyc,, +setuptools/_distutils/__pycache__/version.cpython-39.pyc,, +setuptools/_distutils/__pycache__/versionpredicate.cpython-39.pyc,, +setuptools/_distutils/_msvccompiler.py,sha256=jR0JM5A1JMnZ6xMDicQzhXWgXTVXs1lWAeUexC1z198,20813 +setuptools/_distutils/archive_util.py,sha256=qW-uiGwYexTvK5e-iSel_31Dshx-CqTanNPK6snwf98,8572 +setuptools/_distutils/bcppcompiler.py,sha256=OJDVpCUmX6H8v_7lV1zifV1fcx92Cr2dhiUh6989UJI,14894 +setuptools/_distutils/ccompiler.py,sha256=G2tn9Q3zQ0VUNfW1LM-nrnLt_6OhtiUunugCv85D1PQ,47607 +setuptools/_distutils/cmd.py,sha256=eco6LAGUtobLuPafuhmgKgkwRRL_WY8KJ4YeDCHpcls,18079 +setuptools/_distutils/command/__init__.py,sha256=2TA-rlNDlzeI-csbWHXFjGD8uOYqALMfyWOhT49nC6g,799 +setuptools/_distutils/command/__pycache__/__init__.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/bdist.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/bdist_dumb.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/bdist_msi.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/bdist_rpm.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/bdist_wininst.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/build.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/build_clib.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/build_ext.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/build_py.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/build_scripts.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/check.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/clean.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/config.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/install.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/install_data.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/install_egg_info.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/install_headers.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/install_lib.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/install_scripts.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/py37compat.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/register.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/sdist.cpython-39.pyc,, +setuptools/_distutils/command/__pycache__/upload.cpython-39.pyc,, +setuptools/_distutils/command/bdist.py,sha256=2z4eudRl_n7m3lG9leL0IYqes4bsm8c0fxfZuiafjMg,5562 +setuptools/_distutils/command/bdist_dumb.py,sha256=BTur9jcIppyP7Piavjfsk7YjElqvxeYO2npUyPPOekc,4913 +setuptools/_distutils/command/bdist_msi.py,sha256=EVFQYN_X-ExeeP8gmdV9JcINsuUGsLJUz9afMU0Rt8c,35579 +setuptools/_distutils/command/bdist_rpm.py,sha256=gjOw22GhDSbcq0bdq25cTb-n6HWWm0bShLQad_mkJ4k,21537 +setuptools/_distutils/command/bdist_wininst.py,sha256=iGlaI-VfElHOneeczKHWnSN5a10-7IMcJaXuR1mdS3c,16030 +setuptools/_distutils/command/build.py,sha256=1AF-dxN_NlOEyoydBz19AwpeWYPSYCZvOLJSN_PdatY,5773 +setuptools/_distutils/command/build_clib.py,sha256=bgVTHh28eLQA2Gkw68amApd_j7qQBX4MTI-zTvAK_J4,8022 +setuptools/_distutils/command/build_ext.py,sha256=hX4ksYXRC-Q3fEvLrANIG97mq3twO6ZwkMhfANNK3Yg,31683 +setuptools/_distutils/command/build_py.py,sha256=hXesMrH_epNj6K8SUtJdipgEis3EdICKeZ8VWe_ndck,16495 +setuptools/_distutils/command/build_scripts.py,sha256=urdn6wPxPMW5dLqpqFkZ8dqaFG1tf9TiAao6U9LCoEI,5963 +setuptools/_distutils/command/check.py,sha256=5qDtI75ccZg3sAItQWeaIu8y3FR314O4rr9Smz4HsEo,5637 +setuptools/_distutils/command/clean.py,sha256=2TCt47ru4hZZM0RfVfUYj5bbpicpGLP4Qhw5jBtvp9k,2776 +setuptools/_distutils/command/config.py,sha256=2aTjww3PwjMB8-ZibCe4P7B-qG1hM1gn_rJXYyxRz6c,13117 +setuptools/_distutils/command/install.py,sha256=oaYyzj2vAGb_HKqdFts7rY0gx80W9MrqPQCZpfvGj2k,27534 +setuptools/_distutils/command/install_data.py,sha256=YhGOAwh3gJPqF7em5XA0rmpR42z1bLh80ooElzDyUvk,2822 +setuptools/_distutils/command/install_egg_info.py,sha256=0kW0liVMeadkjX0ZcRfMptKFen07Gw6gyw1VHT5KIwc,2603 +setuptools/_distutils/command/install_headers.py,sha256=XQ6idkbIDfr1ljXCOznuVUMvOFpHBn6cK0Wz9gIM2b4,1298 +setuptools/_distutils/command/install_lib.py,sha256=9AofR-MO9lAtjwwuukCptepOaJEKMZW2VHiyR5hU7HA,8397 +setuptools/_distutils/command/install_scripts.py,sha256=_CLUeQwGJRcY2kik7azPMn5IdtDCrjWdUvZ1khlG6ck,2017 +setuptools/_distutils/command/py37compat.py,sha256=qzRhhvTihqx_PZZt2ZYECxh1X3Oj255VqatzelYFAKw,671 +setuptools/_distutils/command/register.py,sha256=2jaq9968rt2puRVDBx1HbNiXv27uOk8idE_4lPf_3VM,11712 +setuptools/_distutils/command/sdist.py,sha256=qotJjAOzyhJjq2-oDImjNFrOtaSneEFDJTB-sEk1wnU,19005 +setuptools/_distutils/command/upload.py,sha256=BLO1w7eSAqsCjCLXtf_CRVSjwF1WmyOByGVGNdcQ8oY,7597 +setuptools/_distutils/config.py,sha256=dtHgblx9JhfyrKx1-J7Jlxw_f7s8ZbPFQii2UWMTZpY,4827 +setuptools/_distutils/core.py,sha256=jbdOkpOK09xi-56vhhwvn3fYdhLb5DJO8q3K1fnQz0Q,8876 +setuptools/_distutils/cygwinccompiler.py,sha256=QpmRAopZOYEKww_iCWTu3KLjs9gggyl90E0fagAxqCM,16938 +setuptools/_distutils/debug.py,sha256=N6MrTAqK6l9SVk6tWweR108PM8Ol7qNlfyV-nHcLhsY,139 +setuptools/_distutils/dep_util.py,sha256=GuR9Iw_jzZRkyemJ5HX8rB_wRGxkIBcBm1qh54r7zhk,3491 +setuptools/_distutils/dir_util.py,sha256=UwhBOUTcV65GTwce4SPuTXR8Z8q3LYEcmttqcGb0bYo,7778 +setuptools/_distutils/dist.py,sha256=Biuf6ca8uiFfMScRFsYUKtb5neMPtxKxRtXn50_1f3U,50421 +setuptools/_distutils/errors.py,sha256=Yr6tKZGdzBoNi53vBtiq0UJ__X05CmxSdQJqOWaw6SY,3577 +setuptools/_distutils/extension.py,sha256=bTb3Q0CoevGKYv5dX1ls--Ln8tlB0-UEOsi9BwzlZ-s,10515 +setuptools/_distutils/fancy_getopt.py,sha256=OPxp2CxHi1Yp_d1D8JxW4Ueq9fC71tegQFaafh58GGU,17784 +setuptools/_distutils/file_util.py,sha256=0hUqfItN_x2DVihR0MHdA4KCMVCOO8VoByaFp_a6MDg,8148 +setuptools/_distutils/filelist.py,sha256=Z9f5hvepZnpniZ2IFmCnWIjdviWozs8sbARBhWajwoM,13407 +setuptools/_distutils/log.py,sha256=hWBmdUC2K927QcVv3REMW3HMPclxccPQngxLSuUXQl0,1969 +setuptools/_distutils/msvc9compiler.py,sha256=X623B92g0v8A3BEM9qpRf396AEd_hfjkfDUVTKu0hcE,30453 +setuptools/_distutils/msvccompiler.py,sha256=qruALeGRq8-CjtjE2tLQ8W26QnchcYedWzFme8AxZ4Q,23540 +setuptools/_distutils/py35compat.py,sha256=-sk1vBIsOgH-AobjIYbK_OEjdJF_54Ul_D1EiE9XM_c,455 +setuptools/_distutils/py38compat.py,sha256=II7ddBxOijC7uNN4z_46HYUjwYTJYMNiLJoGTormZm0,212 +setuptools/_distutils/spawn.py,sha256=4uE9k3VZWijxy7E_Rlcmh1MoamaPJ8rajdNBagKxjgU,3498 +setuptools/_distutils/sysconfig.py,sha256=mrtbAa9QXYXrNEe2HGKFyes2oJTNqImcgJGWiXnxOtQ,21630 +setuptools/_distutils/text_file.py,sha256=PsuAJeWdKJoLSV_6N6IpB5-0Pa84KzLUucJMFRazw3I,12483 +setuptools/_distutils/unixccompiler.py,sha256=VmkjwPXyVI8_nbHLqrGgS7xgf12JNeWXkWHcx1iRIj0,14980 +setuptools/_distutils/util.py,sha256=6UsgxxG3pzfimk2JHa5LBIHHddmBT-YdxoocXLlL6g4,20373 +setuptools/_distutils/version.py,sha256=8NogP6NPPQpp3EUMZcT9czEHia-ehqPo8spo_e7AgUU,12514 +setuptools/_distutils/versionpredicate.py,sha256=ZxpEA-TQv88mUWc6hetUO4qSqA2sa7ipjZ3QEK5evDk,5133 +setuptools/_imp.py,sha256=HmF91IbitRfsD5z-g4_wmcuH-RahyIONbPgiCOFgtzA,2392 +setuptools/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +setuptools/_vendor/__pycache__/__init__.cpython-39.pyc,, +setuptools/_vendor/__pycache__/ordered_set.cpython-39.pyc,, +setuptools/_vendor/__pycache__/pyparsing.cpython-39.pyc,, +setuptools/_vendor/more_itertools/__init__.py,sha256=C7sXffHTXM3P-iaLPPfqfmDoxOflQMJLcM7ed9p3jak,82 +setuptools/_vendor/more_itertools/__pycache__/__init__.cpython-39.pyc,, +setuptools/_vendor/more_itertools/__pycache__/more.cpython-39.pyc,, +setuptools/_vendor/more_itertools/__pycache__/recipes.cpython-39.pyc,, +setuptools/_vendor/more_itertools/more.py,sha256=DlZa8v6JihVwfQ5zHidOA-xDE0orcQIUyxVnCaUoDKE,117968 +setuptools/_vendor/more_itertools/recipes.py,sha256=UkNkrsZyqiwgLHANBTmvMhCvaNSvSNYhyOpz_Jc55DY,16256 +setuptools/_vendor/ordered_set.py,sha256=dbaCcs27dyN9gnMWGF5nA_BrVn6Q-NrjKYJpV9_fgBs,15130 +setuptools/_vendor/packaging/__about__.py,sha256=PNMsaZn4UcCHyubgROH1bl6CluduPjI5kFrSp_Zgklo,736 +setuptools/_vendor/packaging/__init__.py,sha256=6enbp5XgRfjBjsI9-bn00HjHf5TH21PDMOKkJW8xw-w,562 +setuptools/_vendor/packaging/__pycache__/__about__.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/__init__.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/_compat.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/_structures.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/_typing.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/markers.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/requirements.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/specifiers.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/tags.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/utils.cpython-39.pyc,, +setuptools/_vendor/packaging/__pycache__/version.cpython-39.pyc,, +setuptools/_vendor/packaging/_compat.py,sha256=MXdsGpSE_W-ZrHoC87andI4LV2FAwU7HLL-eHe_CjhU,1128 +setuptools/_vendor/packaging/_structures.py,sha256=ozkCX8Q8f2qE1Eic3YiQ4buDVfgz2iYevY9e7R2y3iY,2022 +setuptools/_vendor/packaging/_typing.py,sha256=x59EhQ57TMT-kTRyLZV25HZvYGGwbucTo6iKh_O0tMw,1812 +setuptools/_vendor/packaging/markers.py,sha256=BCCxZbt8xgysH8v5pqbLkdtQnRZHIGkJQqlNBGek4nQ,9509 +setuptools/_vendor/packaging/requirements.py,sha256=VHydZdk8m3qFxReomNwKr71cmpjantEV_xOhkEyyINI,4917 +setuptools/_vendor/packaging/specifiers.py,sha256=uYp9l13F0LcknS6d4N60ytiBgFmIhKideOq9AnsxTco,31944 +setuptools/_vendor/packaging/tags.py,sha256=NKMS37Zo_nWrZxgsD6zbXsXgc9edn9m160cBiLmHJdE,24067 +setuptools/_vendor/packaging/utils.py,sha256=RShlvnjO2CtYSD8uri32frMMFMTmB-3ihsq1-ghzLEw,1811 +setuptools/_vendor/packaging/version.py,sha256=Cnbm-OO9D_qd8ZTFxzFcjSavexSYFZmyeaoPvMsjgPc,15470 +setuptools/_vendor/pyparsing.py,sha256=mahtkgcp3grNAD0re_9R0DLvBnvjzpeLwgJqT-3H1CE,232056 +setuptools/archive_util.py,sha256=maJDbozRbDeSPw53VT0cb_IS3W0Ap73lJR8tX8RZDx0,7077 +setuptools/build_meta.py,sha256=x7FI1UPKCKxBBSopXocfGDnJa98rQO8atKXSwJtdid8,10280 +setuptools/cli-32.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536 +setuptools/cli-64.exe,sha256=KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo,74752 +setuptools/cli.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536 +setuptools/command/__init__.py,sha256=e-8TJOikUe3St0fw2b2p9u5EDdSxl5zHUBJJKifbcQ8,217 +setuptools/command/__pycache__/__init__.cpython-39.pyc,, +setuptools/command/__pycache__/alias.cpython-39.pyc,, +setuptools/command/__pycache__/bdist_egg.cpython-39.pyc,, +setuptools/command/__pycache__/bdist_rpm.cpython-39.pyc,, +setuptools/command/__pycache__/build_clib.cpython-39.pyc,, +setuptools/command/__pycache__/build_ext.cpython-39.pyc,, +setuptools/command/__pycache__/build_py.cpython-39.pyc,, +setuptools/command/__pycache__/develop.cpython-39.pyc,, +setuptools/command/__pycache__/dist_info.cpython-39.pyc,, +setuptools/command/__pycache__/easy_install.cpython-39.pyc,, +setuptools/command/__pycache__/egg_info.cpython-39.pyc,, +setuptools/command/__pycache__/install.cpython-39.pyc,, +setuptools/command/__pycache__/install_egg_info.cpython-39.pyc,, +setuptools/command/__pycache__/install_lib.cpython-39.pyc,, +setuptools/command/__pycache__/install_scripts.cpython-39.pyc,, +setuptools/command/__pycache__/py36compat.cpython-39.pyc,, +setuptools/command/__pycache__/register.cpython-39.pyc,, +setuptools/command/__pycache__/rotate.cpython-39.pyc,, +setuptools/command/__pycache__/saveopts.cpython-39.pyc,, +setuptools/command/__pycache__/sdist.cpython-39.pyc,, +setuptools/command/__pycache__/setopt.cpython-39.pyc,, +setuptools/command/__pycache__/test.cpython-39.pyc,, +setuptools/command/__pycache__/upload.cpython-39.pyc,, +setuptools/command/__pycache__/upload_docs.cpython-39.pyc,, +setuptools/command/alias.py,sha256=1sLQxZcNh6dDQpDmm4G7UGGTol83nY1NTPmNBbm2siI,2381 +setuptools/command/bdist_egg.py,sha256=-upiB6fFtm8cQSQj1LRDVpG1-T143DsXCvV0fh03u7U,16604 +setuptools/command/bdist_rpm.py,sha256=PxrgoHPNaw2Pw2qNjjHDPC-Ay_IaDbCqP3d_5N-cj2A,1182 +setuptools/command/build_clib.py,sha256=fWHSFGkk10VCddBWCszvNhowbG9Z9CZXVjQ2uSInoOs,4415 +setuptools/command/build_ext.py,sha256=SNK042HfB2ezlDQbSVRGFqI1IM5A4AsjU1wpV3fgskE,13212 +setuptools/command/build_py.py,sha256=UydjclXl6FSyrPjXOOwZD-gHby0tIKoP-qu5itvyP0g,8276 +setuptools/command/develop.py,sha256=5_Ss7ENd1_B_jVMY1tF5UV_y1Xu6jbVzAPG8oKeluGA,7012 +setuptools/command/dist_info.py,sha256=5t6kOfrdgALT-P3ogss6PF9k-Leyesueycuk3dUyZnI,960 +setuptools/command/easy_install.py,sha256=uK0hIXMflGuefmQuA8c6lwwT7Np0uVXRWhxPiextgb4,85344 +setuptools/command/egg_info.py,sha256=se-FhYI1sZMzKd6lndV_-vNkJ31hX4HY4ZcMUu71l9k,25335 +setuptools/command/install.py,sha256=8doMxeQEDoK4Eco0mO2WlXXzzp9QnsGJQ7Z7yWkZPG8,4705 +setuptools/command/install_egg_info.py,sha256=bMgeIeRiXzQ4DAGPV1328kcjwQjHjOWU4FngAWLV78Q,2203 +setuptools/command/install_lib.py,sha256=Uz42McsyHZAjrB6cw9E7Bz0xsaTbzxnM1PI9CBhiPtE,3875 +setuptools/command/install_scripts.py,sha256=o0jN_ex7yYYk8W5clymTFOXwkFMKzW9q_zd9Npcex7M,2593 +setuptools/command/launcher manifest.xml,sha256=xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE,628 +setuptools/command/py36compat.py,sha256=7yLWzQj179Enx3pJ8V1cDDCzeLMFMd9XJXlK-iZTq5Y,4946 +setuptools/command/register.py,sha256=kk3DxXCb5lXTvqnhfwx2g6q7iwbUmgTyXUCaBooBOUk,468 +setuptools/command/rotate.py,sha256=SvsQPasezIojPjvMnfkqzh8P0U0tCj0daczF8uc3NQM,2128 +setuptools/command/saveopts.py,sha256=za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4,658 +setuptools/command/sdist.py,sha256=pEMF0GMVuaznNK6GFamK4GSXG9_qef0ic8z7jEsPmKo,5967 +setuptools/command/setopt.py,sha256=okxhqD1NM1nQlbSVDCNv6P7Y7g680sc2r-tUW7wPH1Y,5086 +setuptools/command/test.py,sha256=qGY-Hx1RPCndlVh2rsrEs5479CgmxRsrEflVLr98jVA,8088 +setuptools/command/upload.py,sha256=XT3YFVfYPAmA5qhGg0euluU98ftxRUW-PzKcODMLxUs,462 +setuptools/command/upload_docs.py,sha256=ba5kOyedD_u62weinrxqqnvpuQvBIuamXehJG6tAvO0,7218 +setuptools/config.py,sha256=sm9ZbziX9DlOugcVlIbhqttMJwxwznGEsk82D8MVaDM,23123 +setuptools/dep_util.py,sha256=BDx1BkzNQntvAB4alypHbW5UVBzjqths000PrUL4Zqc,949 +setuptools/depends.py,sha256=iHfZdLdlCu2BllSF9bRg7NU0oqbPWMH8ljm4BuwQDY0,5474 +setuptools/dist.py,sha256=cZtPPzGEhSomPH_vXH_DeCFetjJ9B8Hv8VUCG0KbZh8,43087 +setuptools/errors.py,sha256=MVOcv381HNSajDgEUWzOQ4J6B5BHCBMSjHfaWcEwA1o,524 +setuptools/extension.py,sha256=NMM46XjNdVelWemc0x8CyVKA5Ks6Zm3xTWSA2SS6xZM,1684 +setuptools/extern/__init__.py,sha256=Hhf9W73WAitw9TdRJfDIb6YFjmK56CF61afds1Mg0HY,2407 +setuptools/extern/__pycache__/__init__.cpython-39.pyc,, +setuptools/glob.py,sha256=1oZjbfjAHSXbgdhSuR6YGU8jKob9L8NtEmBYqcPTLYk,4873 +setuptools/gui-32.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536 +setuptools/gui-64.exe,sha256=aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE,75264 +setuptools/gui.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536 +setuptools/installer.py,sha256=jbhb7ZVkNV_bSUMgfnLcZw0IHr6REFnKF4o7_1Jqxm0,3567 +setuptools/launch.py,sha256=TyPT-Ic1T2EnYvGO26gfNRP4ysBlrhpbRjQxWsiO414,812 +setuptools/monkey.py,sha256=0e3HdVKXHL415O7np-AUqhEFXPPuDdJKbI47chQ_DE4,5217 +setuptools/msvc.py,sha256=3LLt938e6OR7wWPzIvCQu7LCWZSIKqoKV6w3r8jV3kY,50561 +setuptools/namespaces.py,sha256=PMqGVPXPYQgjUTvEg9bGccRAkIODrQ6NmsDg_fwErwI,3093 +setuptools/package_index.py,sha256=2A1O7fpTXcfeD5IV4HWrIoEXXkgq5k8t9aWrjx90Vnw,39886 +setuptools/py34compat.py,sha256=KYOd6ybRxjBW8NJmYD8t_UyyVmysppFXqHpFLdslGXU,245 +setuptools/sandbox.py,sha256=mR83i-mu-ZUU_7TaMgYCeRSyzkqv8loJ_GR9xhS2DDw,14348 +setuptools/script (dev).tmpl,sha256=RUzQzCQUaXtwdLtYHWYbIQmOaES5Brqq1FvUA_tu-5I,218 +setuptools/script.tmpl,sha256=WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY,138 +setuptools/unicode_utils.py,sha256=aOOFo4JGwAsiBttGYDsqFS7YqWQeZ2j6DWiCuctR_00,941 +setuptools/version.py,sha256=og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE,144 +setuptools/wheel.py,sha256=0P8tSk105uF_Ub-30N2HU2X2v7MKDSdjpeQlRRW3SkI,8288 +setuptools/windows_support.py,sha256=5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE,714 diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/REQUESTED b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/WHEEL b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..5bad85fdc1cd08553756d0fb2c7be8b5ad6af7fb --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/entry_points.txt b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..9466bf6320157d79e69d6940e2b09fb08f64da51 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/entry_points.txt @@ -0,0 +1,56 @@ +[distutils.commands] +alias = setuptools.command.alias:alias +bdist_egg = setuptools.command.bdist_egg:bdist_egg +bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm +build_clib = setuptools.command.build_clib:build_clib +build_ext = setuptools.command.build_ext:build_ext +build_py = setuptools.command.build_py:build_py +develop = setuptools.command.develop:develop +dist_info = setuptools.command.dist_info:dist_info +easy_install = setuptools.command.easy_install:easy_install +egg_info = setuptools.command.egg_info:egg_info +install = setuptools.command.install:install +install_egg_info = setuptools.command.install_egg_info:install_egg_info +install_lib = setuptools.command.install_lib:install_lib +install_scripts = setuptools.command.install_scripts:install_scripts +rotate = setuptools.command.rotate:rotate +saveopts = setuptools.command.saveopts:saveopts +sdist = setuptools.command.sdist:sdist +setopt = setuptools.command.setopt:setopt +test = setuptools.command.test:test +upload_docs = setuptools.command.upload_docs:upload_docs + +[distutils.setup_keywords] +dependency_links = setuptools.dist:assert_string_list +eager_resources = setuptools.dist:assert_string_list +entry_points = setuptools.dist:check_entry_points +exclude_package_data = setuptools.dist:check_package_data +extras_require = setuptools.dist:check_extras +include_package_data = setuptools.dist:assert_bool +install_requires = setuptools.dist:check_requirements +namespace_packages = setuptools.dist:check_nsp +package_data = setuptools.dist:check_package_data +packages = setuptools.dist:check_packages +python_requires = setuptools.dist:check_specifier +setup_requires = setuptools.dist:check_requirements +test_loader = setuptools.dist:check_importable +test_runner = setuptools.dist:check_importable +test_suite = setuptools.dist:check_test_suite +tests_require = setuptools.dist:check_requirements +use_2to3 = setuptools.dist:invalid_unless_false +zip_safe = setuptools.dist:assert_bool + +[egg_info.writers] +PKG-INFO = setuptools.command.egg_info:write_pkg_info +dependency_links.txt = setuptools.command.egg_info:overwrite_arg +depends.txt = setuptools.command.egg_info:warn_depends_obsolete +eager_resources.txt = setuptools.command.egg_info:overwrite_arg +entry_points.txt = setuptools.command.egg_info:write_entries +namespace_packages.txt = setuptools.command.egg_info:overwrite_arg +requires.txt = setuptools.command.egg_info:write_requirements +top_level.txt = setuptools.command.egg_info:write_toplevel_names + +[setuptools.finalize_distribution_options] +keywords = setuptools.dist:Distribution._finalize_setup_keywords +parent_finalize = setuptools.dist:_Distribution.finalize_options + diff --git a/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/top_level.txt b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..b5ac1070294b478b7cc2ce677207ee08813bfa37 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools-58.1.0.dist-info/top_level.txt @@ -0,0 +1,3 @@ +_distutils_hack +pkg_resources +setuptools diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1cae5fa02f0c91698eb4839268dd2bd70815872 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/_deprecation_warning.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/_deprecation_warning.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..233ac14546a7e8b46b39bbc9889bbb1711410d6f Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/_deprecation_warning.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/_imp.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/_imp.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0fceabc13d3b158a4b10c2ce75614922ab5e35d Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/_imp.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/archive_util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/archive_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c9a57dfc8bffa1e2c24ce818ee816eba14bb24b Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/archive_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/build_meta.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/build_meta.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cac4dd088c329d62ad6a72c7dec37cd115204e85 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/build_meta.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/config.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ccc88640e22e4d371874b0961aae4a726e76fda Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/config.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/dep_util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/dep_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db99bf1cf79c0f61afdbd95b98ad5b91324f6034 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/dep_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/depends.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/depends.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53394f97ebd50a5a607d1a61177364d9255ada05 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/depends.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/dist.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/dist.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ed83c0e6b5311f8644f279452b340727e8df2c8 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/dist.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/errors.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/errors.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78db537e8c3a0e6cbfa31968fa833a5dbb3159c5 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/errors.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/extension.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/extension.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6cabe91cf6cd800cc5c5bec14bbbe498404f061 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/extension.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/glob.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/glob.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72c53bf39cc8dfb61f7d538193b724289341dab0 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/glob.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/installer.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/installer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a44a9adeb6b3f327647eb0c1bc6a52a6d76c3a2 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/installer.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/launch.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/launch.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33541e14cbba363f5aab8a1736ef796ac22eac0f Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/launch.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/monkey.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/monkey.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91bab4bbc99bf59885ee5f261ca7f3d0fae40a70 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/monkey.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/msvc.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/msvc.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..900120691cf64d6b9e235bd57be51dcfd4c985f9 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/msvc.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/namespaces.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/namespaces.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35ba367fb26be3ca0b988b41a47112f1266e32b9 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/namespaces.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/package_index.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/package_index.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f03eaef6f84e05fa568c23452201bb9d2db58b6b Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/package_index.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/py34compat.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/py34compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..910b28cf74c4da71de3758bb86f25230edd42c52 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/py34compat.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/sandbox.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/sandbox.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79abcffc10e0cb4de04266b0131b254eed7c4ee2 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/sandbox.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/unicode_utils.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/unicode_utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea021d81fc8cedaccddc2a1a2d00259e7f5cd229 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/unicode_utils.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/version.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e97077db80768a722ddcc2e45b6b0def63266d71 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/version.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/wheel.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/wheel.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0fe2966ca2ee0c5d6ae8f8bac00d4fd0e41b48c0 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/wheel.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/__pycache__/windows_support.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/__pycache__/windows_support.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea53ef03b8da4b0b22ad6ccde916918276f5de39 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/__pycache__/windows_support.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1d71201e82fda6b216507b2462bec4f42cb7a82 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/_msvccompiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/_msvccompiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfc9cc610a262d8c1d5f4214d4f0fee4eca37e16 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/_msvccompiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/archive_util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/archive_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d919ab69eb9133c14f10284cb70e7d7eb721173f Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/archive_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/bcppcompiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/bcppcompiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fdecc75ced1a5f42708789ce11180d6e5237cf9 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/bcppcompiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/ccompiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/ccompiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..caaf004ef5938ff99653c2b4f050cf07f5376d0b Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/ccompiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/cmd.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/cmd.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..559bc03f9a03ca6fa5f7843948a68891a4b27957 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/cmd.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/config.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b72e91d9db973b59e72507f260798f88088e7bf Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/config.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/core.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/core.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ca7da4b5d0d86cdd9929cd88a93bc3c56d48400 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/core.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/cygwinccompiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/cygwinccompiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37d8dce940cf23092ff108428172b00a50345a41 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/cygwinccompiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/debug.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/debug.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba9b642809b262cf991d292e10b2e07ffda0a055 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/debug.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dep_util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dep_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46464cb9edfa8f176efa7a323724608d557d5d67 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dep_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dir_util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dir_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af29b8bac6fb81f927d607c65a8a888cd9b9fd03 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dir_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dist.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dist.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b500844df37b96c48f51f1c4bd35a5d6118248e7 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/dist.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/errors.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/errors.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..447aaddfc456345081ff91ba75bd72059ec6fddb Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/errors.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/extension.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/extension.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ebc462e25f30f4f4bcb86b4e4707f8bd0085d9b Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/extension.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/fancy_getopt.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/fancy_getopt.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a47d2fd66a00a00bc3b329845627ca67239ce00 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/fancy_getopt.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/file_util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/file_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df0404f99d301c88b68a3932c4536113ee09d727 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/file_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/filelist.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/filelist.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd05ac7185045493d71cd9eec4fc3caef88bc2bd Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/filelist.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/log.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/log.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c7b0caeff587872994d3005fa41aab840fb5dc6 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/log.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/msvc9compiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/msvc9compiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3ab5d5be6a3dfaf26de886fa9280bf43a82b746 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/msvc9compiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/msvccompiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/msvccompiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a708e6d38f0e5222d9c2dce378164b45be329fe Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/msvccompiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/py35compat.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/py35compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c261515219a687d707c14277fdd6b826a697895e Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/py35compat.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/py38compat.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/py38compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a6a4b0850127f5b423cc76882061c54bf958ce1 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/py38compat.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/spawn.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/spawn.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..809f702a2934fd7f644acaba85c6a2aa9b9be384 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/spawn.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/sysconfig.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/sysconfig.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74e910535b380845c8bfe8b81594ad8a76664ae2 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/sysconfig.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/text_file.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/text_file.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f4faf3cdb67dc12095aae7207d8b87fd3027079 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/text_file.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/unixccompiler.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/unixccompiler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64621a6f759eb6166eb611e04755dce33cf86e36 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/unixccompiler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/util.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b0af3058939c043e10bdb0d21abb1a7d9f4707a Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/version.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e332b79ea2a65fc12585d3d83e272501fc722b37 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/version.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/versionpredicate.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/versionpredicate.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b282d2758c871712aaf73b266cb24296f05894e Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/__pycache__/versionpredicate.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e36454e1a2b11aa6b2b84cc0b2c8eea6f25c7d02 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82b86fe05b203f60fb70846f888222b2d54d994c Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_clib.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_clib.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd86e25f2a050b8da44fd872b36b222033b2a47c Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_clib.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_ext.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_ext.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efaf2a9daefb26adc66cdbafcc1200e48a716989 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_ext.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_py.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_py.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3b7ddad7b9f0581f68279d547ab3e52d594267b Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_py.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_scripts.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_scripts.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da2a621ace2eab2deba404657c34c718bbaa930b Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/build_scripts.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/check.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/check.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dea4d999e1763ceb7ffaf9551146f698ab958282 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/check.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/clean.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/clean.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00ccf4e49120822295f7c3d55361d4b411de4d2d Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/clean.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/config.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eb53901ac9a0abf1b5b1b6f117e3eaadd017d88 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/config.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e718f64822c064cb688c316a9d1c3e4e38b9bcd Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_data.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_data.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3de525cedc961848ec8893669a1b111669121dbd Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_data.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_egg_info.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_egg_info.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5080285dd1fabbba99132f346c2f1c6b3ced1426 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_egg_info.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_headers.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_headers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cdf47683a2702818b19624dc44068bde97070c6 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_headers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_lib.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_lib.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5ea48b75f89c231637f689cd6fa1fe68e6381b6 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_lib.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_scripts.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_scripts.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01872626ffa10914bcefe8e0f158a50ab0255c8f Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/install_scripts.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/py37compat.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/py37compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79d159dc35bc0ff8e2389e7249c98fe7c1efd535 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/py37compat.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/register.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/register.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..558985f9e2668db9ab44dfe7918a7c7ca9bab815 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/register.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/sdist.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/sdist.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e613196c4334bff7f81aaf50dbe60cb3d6f3ace Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/sdist.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/upload.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/upload.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdf9938ac3a3ce08e67e379813351b1b1710912a Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_distutils/command/__pycache__/upload.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/__init__.py b/phivenv/Lib/site-packages/setuptools/_vendor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4b11f30d7f10884f838b279896cfc27bf302d89 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/__pycache__/ordered_set.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/__pycache__/ordered_set.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f195aa8fea649fa5632e64460cbf005808ba28cb Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/__pycache__/ordered_set.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__init__.py b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..19a169fc30183db91f931ad6ad04fbc0e16559b3 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__init__.py @@ -0,0 +1,4 @@ +from .more import * # noqa +from .recipes import * # noqa + +__version__ = '8.8.0' diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8fcf55a45e7c80f859121852887503c58f4fcac Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__pycache__/recipes.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__pycache__/recipes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab1d8e6eec93fb72ddb207501743b04d8f917dd8 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/__pycache__/recipes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/more.py b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/more.py new file mode 100644 index 0000000000000000000000000000000000000000..0f7d282aa5df08f3e2692bf1e51dfaaea60ae4ea --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/more.py @@ -0,0 +1,3825 @@ +import warnings + +from collections import Counter, defaultdict, deque, abc +from collections.abc import Sequence +from concurrent.futures import ThreadPoolExecutor +from functools import partial, reduce, wraps +from heapq import merge, heapify, heapreplace, heappop +from itertools import ( + chain, + compress, + count, + cycle, + dropwhile, + groupby, + islice, + repeat, + starmap, + takewhile, + tee, + zip_longest, +) +from math import exp, factorial, floor, log +from queue import Empty, Queue +from random import random, randrange, uniform +from operator import itemgetter, mul, sub, gt, lt +from sys import hexversion, maxsize +from time import monotonic + +from .recipes import ( + consume, + flatten, + pairwise, + powerset, + take, + unique_everseen, +) + +__all__ = [ + 'AbortThread', + 'adjacent', + 'always_iterable', + 'always_reversible', + 'bucket', + 'callback_iter', + 'chunked', + 'circular_shifts', + 'collapse', + 'collate', + 'consecutive_groups', + 'consumer', + 'countable', + 'count_cycle', + 'mark_ends', + 'difference', + 'distinct_combinations', + 'distinct_permutations', + 'distribute', + 'divide', + 'exactly_n', + 'filter_except', + 'first', + 'groupby_transform', + 'ilen', + 'interleave_longest', + 'interleave', + 'intersperse', + 'islice_extended', + 'iterate', + 'ichunked', + 'is_sorted', + 'last', + 'locate', + 'lstrip', + 'make_decorator', + 'map_except', + 'map_reduce', + 'nth_or_last', + 'nth_permutation', + 'nth_product', + 'numeric_range', + 'one', + 'only', + 'padded', + 'partitions', + 'set_partitions', + 'peekable', + 'repeat_last', + 'replace', + 'rlocate', + 'rstrip', + 'run_length', + 'sample', + 'seekable', + 'SequenceView', + 'side_effect', + 'sliced', + 'sort_together', + 'split_at', + 'split_after', + 'split_before', + 'split_when', + 'split_into', + 'spy', + 'stagger', + 'strip', + 'substrings', + 'substrings_indexes', + 'time_limited', + 'unique_to_each', + 'unzip', + 'windowed', + 'with_iter', + 'UnequalIterablesError', + 'zip_equal', + 'zip_offset', + 'windowed_complete', + 'all_unique', + 'value_chain', + 'product_index', + 'combination_index', + 'permutation_index', +] + +_marker = object() + + +def chunked(iterable, n, strict=False): + """Break *iterable* into lists of length *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6], 3)) + [[1, 2, 3], [4, 5, 6]] + + By the default, the last yielded list will have fewer than *n* elements + if the length of *iterable* is not divisible by *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6, 7, 8], 3)) + [[1, 2, 3], [4, 5, 6], [7, 8]] + + To use a fill-in value instead, see the :func:`grouper` recipe. + + If the length of *iterable* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + list is yielded. + + """ + iterator = iter(partial(take, n, iter(iterable)), []) + if strict: + + def ret(): + for chunk in iterator: + if len(chunk) != n: + raise ValueError('iterable is not divisible by n.') + yield chunk + + return iter(ret()) + else: + return iterator + + +def first(iterable, default=_marker): + """Return the first item of *iterable*, or *default* if *iterable* is + empty. + + >>> first([0, 1, 2, 3]) + 0 + >>> first([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + + :func:`first` is useful when you have a generator of expensive-to-retrieve + values and want any arbitrary one. It is marginally shorter than + ``next(iter(iterable), default)``. + + """ + try: + return next(iter(iterable)) + except StopIteration as e: + if default is _marker: + raise ValueError( + 'first() was called on an empty iterable, and no ' + 'default value was provided.' + ) from e + return default + + +def last(iterable, default=_marker): + """Return the last item of *iterable*, or *default* if *iterable* is + empty. + + >>> last([0, 1, 2, 3]) + 3 + >>> last([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + try: + if isinstance(iterable, Sequence): + return iterable[-1] + # Work around https://bugs.python.org/issue38525 + elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0): + return next(reversed(iterable)) + else: + return deque(iterable, maxlen=1)[-1] + except (IndexError, TypeError, StopIteration): + if default is _marker: + raise ValueError( + 'last() was called on an empty iterable, and no default was ' + 'provided.' + ) + return default + + +def nth_or_last(iterable, n, default=_marker): + """Return the nth or the last item of *iterable*, + or *default* if *iterable* is empty. + + >>> nth_or_last([0, 1, 2, 3], 2) + 2 + >>> nth_or_last([0, 1], 2) + 1 + >>> nth_or_last([], 0, 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + return last(islice(iterable, n + 1), default=default) + + +class peekable: + """Wrap an iterator to allow lookahead and prepending elements. + + Call :meth:`peek` on the result to get the value that will be returned + by :func:`next`. This won't advance the iterator: + + >>> p = peekable(['a', 'b']) + >>> p.peek() + 'a' + >>> next(p) + 'a' + + Pass :meth:`peek` a default value to return that instead of raising + ``StopIteration`` when the iterator is exhausted. + + >>> p = peekable([]) + >>> p.peek('hi') + 'hi' + + peekables also offer a :meth:`prepend` method, which "inserts" items + at the head of the iterable: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> p.peek() + 11 + >>> list(p) + [11, 12, 1, 2, 3] + + peekables can be indexed. Index 0 is the item that will be returned by + :func:`next`, index 1 is the item after that, and so on: + The values up to the given index will be cached. + + >>> p = peekable(['a', 'b', 'c', 'd']) + >>> p[0] + 'a' + >>> p[1] + 'b' + >>> next(p) + 'a' + + Negative indexes are supported, but be aware that they will cache the + remaining items in the source iterator, which may require significant + storage. + + To check whether a peekable is exhausted, check its truth value: + + >>> p = peekable(['a', 'b']) + >>> if p: # peekable has items + ... list(p) + ['a', 'b'] + >>> if not p: # peekable is exhausted + ... list(p) + [] + + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self._cache = deque() + + def __iter__(self): + return self + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + """Return the item that will be next returned from ``next()``. + + Return ``default`` if there are no items left. If ``default`` is not + provided, raise ``StopIteration``. + + """ + if not self._cache: + try: + self._cache.append(next(self._it)) + except StopIteration: + if default is _marker: + raise + return default + return self._cache[0] + + def prepend(self, *items): + """Stack up items to be the next ones returned from ``next()`` or + ``self.peek()``. The items will be returned in + first in, first out order:: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> list(p) + [11, 12, 1, 2, 3] + + It is possible, by prepending items, to "resurrect" a peekable that + previously raised ``StopIteration``. + + >>> p = peekable([]) + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + >>> p.prepend(1) + >>> next(p) + 1 + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + + """ + self._cache.extendleft(reversed(items)) + + def __next__(self): + if self._cache: + return self._cache.popleft() + + return next(self._it) + + def _get_slice(self, index): + # Normalize the slice's arguments + step = 1 if (index.step is None) else index.step + if step > 0: + start = 0 if (index.start is None) else index.start + stop = maxsize if (index.stop is None) else index.stop + elif step < 0: + start = -1 if (index.start is None) else index.start + stop = (-maxsize - 1) if (index.stop is None) else index.stop + else: + raise ValueError('slice step cannot be zero') + + # If either the start or stop index is negative, we'll need to cache + # the rest of the iterable in order to slice from the right side. + if (start < 0) or (stop < 0): + self._cache.extend(self._it) + # Otherwise we'll need to find the rightmost index and cache to that + # point. + else: + n = min(max(start, stop) + 1, maxsize) + cache_len = len(self._cache) + if n >= cache_len: + self._cache.extend(islice(self._it, n - cache_len)) + + return list(self._cache)[index] + + def __getitem__(self, index): + if isinstance(index, slice): + return self._get_slice(index) + + cache_len = len(self._cache) + if index < 0: + self._cache.extend(self._it) + elif index >= cache_len: + self._cache.extend(islice(self._it, index + 1 - cache_len)) + + return self._cache[index] + + +def collate(*iterables, **kwargs): + """Return a sorted merge of the items from each of several already-sorted + *iterables*. + + >>> list(collate('ACDZ', 'AZ', 'JKL')) + ['A', 'A', 'C', 'D', 'J', 'K', 'L', 'Z', 'Z'] + + Works lazily, keeping only the next value from each iterable in memory. Use + :func:`collate` to, for example, perform a n-way mergesort of items that + don't fit in memory. + + If a *key* function is specified, the iterables will be sorted according + to its result: + + >>> key = lambda s: int(s) # Sort by numeric value, not by string + >>> list(collate(['1', '10'], ['2', '11'], key=key)) + ['1', '2', '10', '11'] + + + If the *iterables* are sorted in descending order, set *reverse* to + ``True``: + + >>> list(collate([5, 3, 1], [4, 2, 0], reverse=True)) + [5, 4, 3, 2, 1, 0] + + If the elements of the passed-in iterables are out of order, you might get + unexpected results. + + On Python 3.5+, this function is an alias for :func:`heapq.merge`. + + """ + warnings.warn( + "collate is no longer part of more_itertools, use heapq.merge", + DeprecationWarning, + ) + return merge(*iterables, **kwargs) + + +def consumer(func): + """Decorator that automatically advances a PEP-342-style "reverse iterator" + to its first yield point so you don't have to call ``next()`` on it + manually. + + >>> @consumer + ... def tally(): + ... i = 0 + ... while True: + ... print('Thing number %s is %s.' % (i, (yield))) + ... i += 1 + ... + >>> t = tally() + >>> t.send('red') + Thing number 0 is red. + >>> t.send('fish') + Thing number 1 is fish. + + Without the decorator, you would have to call ``next(t)`` before + ``t.send()`` could be used. + + """ + + @wraps(func) + def wrapper(*args, **kwargs): + gen = func(*args, **kwargs) + next(gen) + return gen + + return wrapper + + +def ilen(iterable): + """Return the number of items in *iterable*. + + >>> ilen(x for x in range(1000000) if x % 3 == 0) + 333334 + + This consumes the iterable, so handle with care. + + """ + # This approach was selected because benchmarks showed it's likely the + # fastest of the known implementations at the time of writing. + # See GitHub tracker: #236, #230. + counter = count() + deque(zip(iterable, counter), maxlen=0) + return next(counter) + + +def iterate(func, start): + """Return ``start``, ``func(start)``, ``func(func(start))``, ... + + >>> from itertools import islice + >>> list(islice(iterate(lambda x: 2*x, 1), 10)) + [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + + """ + while True: + yield start + start = func(start) + + +def with_iter(context_manager): + """Wrap an iterable in a ``with`` statement, so it closes once exhausted. + + For example, this will close the file when the iterator is exhausted:: + + upper_lines = (line.upper() for line in with_iter(open('foo'))) + + Any context manager which returns an iterable is a candidate for + ``with_iter``. + + """ + with context_manager as iterable: + yield from iterable + + +def one(iterable, too_short=None, too_long=None): + """Return the first item from *iterable*, which is expected to contain only + that item. Raise an exception if *iterable* is empty or has more than one + item. + + :func:`one` is useful for ensuring that an iterable contains only one item. + For example, it can be used to retrieve the result of a database query + that is expected to return a single row. + + If *iterable* is empty, ``ValueError`` will be raised. You may specify a + different exception with the *too_short* keyword: + + >>> it = [] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (expected 1)' + >>> too_short = IndexError('too few items') + >>> one(it, too_short=too_short) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + IndexError: too few items + + Similarly, if *iterable* contains more than one item, ``ValueError`` will + be raised. You may specify a different exception with the *too_long* + keyword: + + >>> it = ['too', 'many'] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 'too', + 'many', and perhaps more. + >>> too_long = RuntimeError + >>> one(it, too_long=too_long) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + Note that :func:`one` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check iterable + contents less destructively. + + """ + it = iter(iterable) + + try: + first_value = next(it) + except StopIteration as e: + raise ( + too_short or ValueError('too few items in iterable (expected 1)') + ) from e + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def distinct_permutations(iterable, r=None): + """Yield successive distinct permutations of the elements in *iterable*. + + >>> sorted(distinct_permutations([1, 0, 1])) + [(0, 1, 1), (1, 0, 1), (1, 1, 0)] + + Equivalent to ``set(permutations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + Duplicate permutations arise when there are duplicated elements in the + input iterable. The number of items returned is + `n! / (x_1! * x_2! * ... * x_n!)`, where `n` is the total number of + items input, and each `x_i` is the count of a distinct item in the input + sequence. + + If *r* is given, only the *r*-length permutations are yielded. + + >>> sorted(distinct_permutations([1, 0, 1], r=2)) + [(0, 1), (1, 0), (1, 1)] + >>> sorted(distinct_permutations(range(3), r=2)) + [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] + + """ + # Algorithm: https://w.wiki/Qai + def _full(A): + while True: + # Yield the permutation we have + yield tuple(A) + + # Find the largest index i such that A[i] < A[i + 1] + for i in range(size - 2, -1, -1): + if A[i] < A[i + 1]: + break + # If no such index exists, this permutation is the last one + else: + return + + # Find the largest index j greater than j such that A[i] < A[j] + for j in range(size - 1, i, -1): + if A[i] < A[j]: + break + + # Swap the value of A[i] with that of A[j], then reverse the + # sequence from A[i + 1] to form the new permutation + A[i], A[j] = A[j], A[i] + A[i + 1 :] = A[: i - size : -1] # A[i + 1:][::-1] + + # Algorithm: modified from the above + def _partial(A, r): + # Split A into the first r items and the last r items + head, tail = A[:r], A[r:] + right_head_indexes = range(r - 1, -1, -1) + left_tail_indexes = range(len(tail)) + + while True: + # Yield the permutation we have + yield tuple(head) + + # Starting from the right, find the first index of the head with + # value smaller than the maximum value of the tail - call it i. + pivot = tail[-1] + for i in right_head_indexes: + if head[i] < pivot: + break + pivot = head[i] + else: + return + + # Starting from the left, find the first value of the tail + # with a value greater than head[i] and swap. + for j in left_tail_indexes: + if tail[j] > head[i]: + head[i], tail[j] = tail[j], head[i] + break + # If we didn't find one, start from the right and find the first + # index of the head with a value greater than head[i] and swap. + else: + for j in right_head_indexes: + if head[j] > head[i]: + head[i], head[j] = head[j], head[i] + break + + # Reverse head[i + 1:] and swap it with tail[:r - (i + 1)] + tail += head[: i - r : -1] # head[i + 1:][::-1] + i += 1 + head[i:], tail[:] = tail[: r - i], tail[r - i :] + + items = sorted(iterable) + + size = len(items) + if r is None: + r = size + + if 0 < r <= size: + return _full(items) if (r == size) else _partial(items, r) + + return iter(() if r else ((),)) + + +def intersperse(e, iterable, n=1): + """Intersperse filler element *e* among the items in *iterable*, leaving + *n* items between each filler element. + + >>> list(intersperse('!', [1, 2, 3, 4, 5])) + [1, '!', 2, '!', 3, '!', 4, '!', 5] + + >>> list(intersperse(None, [1, 2, 3, 4, 5], n=2)) + [1, 2, None, 3, 4, None, 5] + + """ + if n == 0: + raise ValueError('n must be > 0') + elif n == 1: + # interleave(repeat(e), iterable) -> e, x_0, e, e, x_1, e, x_2... + # islice(..., 1, None) -> x_0, e, e, x_1, e, x_2... + return islice(interleave(repeat(e), iterable), 1, None) + else: + # interleave(filler, chunks) -> [e], [x_0, x_1], [e], [x_2, x_3]... + # islice(..., 1, None) -> [x_0, x_1], [e], [x_2, x_3]... + # flatten(...) -> x_0, x_1, e, x_2, x_3... + filler = repeat([e]) + chunks = chunked(iterable, n) + return flatten(islice(interleave(filler, chunks), 1, None)) + + +def unique_to_each(*iterables): + """Return the elements from each of the input iterables that aren't in the + other input iterables. + + For example, suppose you have a set of packages, each with a set of + dependencies:: + + {'pkg_1': {'A', 'B'}, 'pkg_2': {'B', 'C'}, 'pkg_3': {'B', 'D'}} + + If you remove one package, which dependencies can also be removed? + + If ``pkg_1`` is removed, then ``A`` is no longer necessary - it is not + associated with ``pkg_2`` or ``pkg_3``. Similarly, ``C`` is only needed for + ``pkg_2``, and ``D`` is only needed for ``pkg_3``:: + + >>> unique_to_each({'A', 'B'}, {'B', 'C'}, {'B', 'D'}) + [['A'], ['C'], ['D']] + + If there are duplicates in one input iterable that aren't in the others + they will be duplicated in the output. Input order is preserved:: + + >>> unique_to_each("mississippi", "missouri") + [['p', 'p'], ['o', 'u', 'r']] + + It is assumed that the elements of each iterable are hashable. + + """ + pool = [list(it) for it in iterables] + counts = Counter(chain.from_iterable(map(set, pool))) + uniques = {element for element in counts if counts[element] == 1} + return [list(filter(uniques.__contains__, it)) for it in pool] + + +def windowed(seq, n, fillvalue=None, step=1): + """Return a sliding window of width *n* over the given iterable. + + >>> all_windows = windowed([1, 2, 3, 4, 5], 3) + >>> list(all_windows) + [(1, 2, 3), (2, 3, 4), (3, 4, 5)] + + When the window is larger than the iterable, *fillvalue* is used in place + of missing values: + + >>> list(windowed([1, 2, 3], 4)) + [(1, 2, 3, None)] + + Each window will advance in increments of *step*: + + >>> list(windowed([1, 2, 3, 4, 5, 6], 3, fillvalue='!', step=2)) + [(1, 2, 3), (3, 4, 5), (5, 6, '!')] + + To slide into the iterable's items, use :func:`chain` to add filler items + to the left: + + >>> iterable = [1, 2, 3, 4] + >>> n = 3 + >>> padding = [None] * (n - 1) + >>> list(windowed(chain(padding, iterable), 3)) + [(None, None, 1), (None, 1, 2), (1, 2, 3), (2, 3, 4)] + """ + if n < 0: + raise ValueError('n must be >= 0') + if n == 0: + yield tuple() + return + if step < 1: + raise ValueError('step must be >= 1') + + window = deque(maxlen=n) + i = n + for _ in map(window.append, seq): + i -= 1 + if not i: + i = step + yield tuple(window) + + size = len(window) + if size < n: + yield tuple(chain(window, repeat(fillvalue, n - size))) + elif 0 < i < min(step, n): + window += (fillvalue,) * i + yield tuple(window) + + +def substrings(iterable): + """Yield all of the substrings of *iterable*. + + >>> [''.join(s) for s in substrings('more')] + ['m', 'o', 'r', 'e', 'mo', 'or', 're', 'mor', 'ore', 'more'] + + Note that non-string iterables can also be subdivided. + + >>> list(substrings([0, 1, 2])) + [(0,), (1,), (2,), (0, 1), (1, 2), (0, 1, 2)] + + """ + # The length-1 substrings + seq = [] + for item in iter(iterable): + seq.append(item) + yield (item,) + seq = tuple(seq) + item_count = len(seq) + + # And the rest + for n in range(2, item_count + 1): + for i in range(item_count - n + 1): + yield seq[i : i + n] + + +def substrings_indexes(seq, reverse=False): + """Yield all substrings and their positions in *seq* + + The items yielded will be a tuple of the form ``(substr, i, j)``, where + ``substr == seq[i:j]``. + + This function only works for iterables that support slicing, such as + ``str`` objects. + + >>> for item in substrings_indexes('more'): + ... print(item) + ('m', 0, 1) + ('o', 1, 2) + ('r', 2, 3) + ('e', 3, 4) + ('mo', 0, 2) + ('or', 1, 3) + ('re', 2, 4) + ('mor', 0, 3) + ('ore', 1, 4) + ('more', 0, 4) + + Set *reverse* to ``True`` to yield the same items in the opposite order. + + + """ + r = range(1, len(seq) + 1) + if reverse: + r = reversed(r) + return ( + (seq[i : i + L], i, i + L) for L in r for i in range(len(seq) - L + 1) + ) + + +class bucket: + """Wrap *iterable* and return an object that buckets it iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) + + +def spy(iterable, n=1): + """Return a 2-tuple with a list containing the first *n* elements of + *iterable*, and an iterator with the same items as *iterable*. + This allows you to "look ahead" at the items in the iterable without + advancing it. + + There is one item in the list by default: + + >>> iterable = 'abcdefg' + >>> head, iterable = spy(iterable) + >>> head + ['a'] + >>> list(iterable) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + You may use unpacking to retrieve items instead of lists: + + >>> (head,), iterable = spy('abcdefg') + >>> head + 'a' + >>> (first, second), iterable = spy('abcdefg', 2) + >>> first + 'a' + >>> second + 'b' + + The number of items requested can be larger than the number of items in + the iterable: + + >>> iterable = [1, 2, 3, 4, 5] + >>> head, iterable = spy(iterable, 10) + >>> head + [1, 2, 3, 4, 5] + >>> list(iterable) + [1, 2, 3, 4, 5] + + """ + it = iter(iterable) + head = take(n, it) + + return head.copy(), chain(head, it) + + +def interleave(*iterables): + """Return a new iterable yielding from each iterable in turn, + until the shortest is exhausted. + + >>> list(interleave([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7] + + For a version that doesn't terminate after the shortest iterable is + exhausted, see :func:`interleave_longest`. + + """ + return chain.from_iterable(zip(*iterables)) + + +def interleave_longest(*iterables): + """Return a new iterable yielding from each iterable in turn, + skipping any that are exhausted. + + >>> list(interleave_longest([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7, 3, 8] + + This function produces the same output as :func:`roundrobin`, but may + perform better for some inputs (in particular when the number of iterables + is large). + + """ + i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker)) + return (x for x in i if x is not _marker) + + +def collapse(iterable, base_type=None, levels=None): + """Flatten an iterable with multiple levels of nesting (e.g., a list of + lists of tuples) into non-iterable types. + + >>> iterable = [(1, 2), ([3, 4], [[5], [6]])] + >>> list(collapse(iterable)) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and + will not be collapsed. + + To avoid collapsing other types, specify *base_type*: + + >>> iterable = ['ab', ('cd', 'ef'), ['gh', 'ij']] + >>> list(collapse(iterable, base_type=tuple)) + ['ab', ('cd', 'ef'), 'gh', 'ij'] + + Specify *levels* to stop flattening after a certain level: + + >>> iterable = [('a', ['b']), ('c', ['d'])] + >>> list(collapse(iterable)) # Fully flattened + ['a', 'b', 'c', 'd'] + >>> list(collapse(iterable, levels=1)) # Only one level flattened + ['a', ['b'], 'c', ['d']] + + """ + + def walk(node, level): + if ( + ((levels is not None) and (level > levels)) + or isinstance(node, (str, bytes)) + or ((base_type is not None) and isinstance(node, base_type)) + ): + yield node + return + + try: + tree = iter(node) + except TypeError: + yield node + return + else: + for child in tree: + yield from walk(child, level + 1) + + yield from walk(iterable, 0) + + +def side_effect(func, iterable, chunk_size=None, before=None, after=None): + """Invoke *func* on each item in *iterable* (or on each *chunk_size* group + of items) before yielding the item. + + `func` must be a function that takes a single argument. Its return value + will be discarded. + + *before* and *after* are optional functions that take no arguments. They + will be executed before iteration starts and after it ends, respectively. + + `side_effect` can be used for logging, updating progress bars, or anything + that is not functionally "pure." + + Emitting a status message: + + >>> from more_itertools import consume + >>> func = lambda item: print('Received {}'.format(item)) + >>> consume(side_effect(func, range(2))) + Received 0 + Received 1 + + Operating on chunks of items: + + >>> pair_sums = [] + >>> func = lambda chunk: pair_sums.append(sum(chunk)) + >>> list(side_effect(func, [0, 1, 2, 3, 4, 5], 2)) + [0, 1, 2, 3, 4, 5] + >>> list(pair_sums) + [1, 5, 9] + + Writing to a file-like object: + + >>> from io import StringIO + >>> from more_itertools import consume + >>> f = StringIO() + >>> func = lambda x: print(x, file=f) + >>> before = lambda: print(u'HEADER', file=f) + >>> after = f.close + >>> it = [u'a', u'b', u'c'] + >>> consume(side_effect(func, it, before=before, after=after)) + >>> f.closed + True + + """ + try: + if before is not None: + before() + + if chunk_size is None: + for item in iterable: + func(item) + yield item + else: + for chunk in chunked(iterable, chunk_size): + func(chunk) + yield from chunk + finally: + if after is not None: + after() + + +def sliced(seq, n, strict=False): + """Yield slices of length *n* from the sequence *seq*. + + >>> list(sliced((1, 2, 3, 4, 5, 6), 3)) + [(1, 2, 3), (4, 5, 6)] + + By the default, the last yielded slice will have fewer than *n* elements + if the length of *seq* is not divisible by *n*: + + >>> list(sliced((1, 2, 3, 4, 5, 6, 7, 8), 3)) + [(1, 2, 3), (4, 5, 6), (7, 8)] + + If the length of *seq* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + slice is yielded. + + This function will only work for iterables that support slicing. + For non-sliceable iterables, see :func:`chunked`. + + """ + iterator = takewhile(len, (seq[i : i + n] for i in count(0, n))) + if strict: + + def ret(): + for _slice in iterator: + if len(_slice) != n: + raise ValueError("seq is not divisible by n.") + yield _slice + + return iter(ret()) + else: + return iterator + + +def split_at(iterable, pred, maxsplit=-1, keep_separator=False): + """Yield lists of items from *iterable*, where each list is delimited by + an item where callable *pred* returns ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b')) + [['a'], ['c', 'd', 'c'], ['a']] + + >>> list(split_at(range(10), lambda n: n % 2 == 1)) + [[0], [2], [4], [6], [8], []] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_at(range(10), lambda n: n % 2 == 1, maxsplit=2)) + [[0], [2], [4, 5, 6, 7, 8, 9]] + + By default, the delimiting items are not included in the output. + The include them, set *keep_separator* to ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b', keep_separator=True)) + [['a'], ['b'], ['c', 'd', 'c'], ['b'], ['a']] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item): + yield buf + if keep_separator: + yield [item] + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + else: + buf.append(item) + yield buf + + +def split_before(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends just before + an item for which callable *pred* returns ``True``: + + >>> list(split_before('OneTwo', lambda s: s.isupper())) + [['O', 'n', 'e'], ['T', 'w', 'o']] + + >>> list(split_before(range(10), lambda n: n % 3 == 0)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_before(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]] + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield [item] + list(it) + return + buf = [] + maxsplit -= 1 + buf.append(item) + if buf: + yield buf + + +def split_after(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends with an + item where callable *pred* returns ``True``: + + >>> list(split_after('one1two2', lambda s: s.isdigit())) + [['o', 'n', 'e', '1'], ['t', 'w', 'o', '2']] + + >>> list(split_after(range(10), lambda n: n % 3 == 0)) + [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_after(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0], [1, 2, 3], [4, 5, 6, 7, 8, 9]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + buf.append(item) + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + if buf: + yield buf + + +def split_when(iterable, pred, maxsplit=-1): + """Split *iterable* into pieces based on the output of *pred*. + *pred* should be a function that takes successive pairs of items and + returns ``True`` if the iterable should be split in between them. + + For example, to find runs of increasing numbers, split the iterable when + element ``i`` is larger than element ``i + 1``: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], lambda x, y: x > y)) + [[1, 2, 3, 3], [2, 5], [2, 4], [2]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], + ... lambda x, y: x > y, maxsplit=2)) + [[1, 2, 3, 3], [2, 5], [2, 4, 2]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + it = iter(iterable) + try: + cur_item = next(it) + except StopIteration: + return + + buf = [cur_item] + for next_item in it: + if pred(cur_item, next_item): + yield buf + if maxsplit == 1: + yield [next_item] + list(it) + return + buf = [] + maxsplit -= 1 + + buf.append(next_item) + cur_item = next_item + + yield buf + + +def split_into(iterable, sizes): + """Yield a list of sequential items from *iterable* of length 'n' for each + integer 'n' in *sizes*. + + >>> list(split_into([1,2,3,4,5,6], [1,2,3])) + [[1], [2, 3], [4, 5, 6]] + + If the sum of *sizes* is smaller than the length of *iterable*, then the + remaining items of *iterable* will not be returned. + + >>> list(split_into([1,2,3,4,5,6], [2,3])) + [[1, 2], [3, 4, 5]] + + If the sum of *sizes* is larger than the length of *iterable*, fewer items + will be returned in the iteration that overruns *iterable* and further + lists will be empty: + + >>> list(split_into([1,2,3,4], [1,2,3,4])) + [[1], [2, 3], [4], []] + + When a ``None`` object is encountered in *sizes*, the returned list will + contain items up to the end of *iterable* the same way that itertools.slice + does: + + >>> list(split_into([1,2,3,4,5,6,7,8,9,0], [2,3,None])) + [[1, 2], [3, 4, 5], [6, 7, 8, 9, 0]] + + :func:`split_into` can be useful for grouping a series of items where the + sizes of the groups are not uniform. An example would be where in a row + from a table, multiple columns represent elements of the same feature + (e.g. a point represented by x,y,z) but, the format is not the same for + all columns. + """ + # convert the iterable argument into an iterator so its contents can + # be consumed by islice in case it is a generator + it = iter(iterable) + + for size in sizes: + if size is None: + yield list(it) + return + else: + yield list(islice(it, size)) + + +def padded(iterable, fillvalue=None, n=None, next_multiple=False): + """Yield the elements from *iterable*, followed by *fillvalue*, such that + at least *n* items are emitted. + + >>> list(padded([1, 2, 3], '?', 5)) + [1, 2, 3, '?', '?'] + + If *next_multiple* is ``True``, *fillvalue* will be emitted until the + number of items emitted is a multiple of *n*:: + + >>> list(padded([1, 2, 3, 4], n=3, next_multiple=True)) + [1, 2, 3, 4, None, None] + + If *n* is ``None``, *fillvalue* will be emitted indefinitely. + + """ + it = iter(iterable) + if n is None: + yield from chain(it, repeat(fillvalue)) + elif n < 1: + raise ValueError('n must be at least 1') + else: + item_count = 0 + for item in it: + yield item + item_count += 1 + + remaining = (n - item_count) % n if next_multiple else n - item_count + for _ in range(remaining): + yield fillvalue + + +def repeat_last(iterable, default=None): + """After the *iterable* is exhausted, keep yielding its last element. + + >>> list(islice(repeat_last(range(3)), 5)) + [0, 1, 2, 2, 2] + + If the iterable is empty, yield *default* forever:: + + >>> list(islice(repeat_last(range(0), 42), 5)) + [42, 42, 42, 42, 42] + + """ + item = _marker + for item in iterable: + yield item + final = default if item is _marker else item + yield from repeat(final) + + +def distribute(n, iterable): + """Distribute the items from *iterable* among *n* smaller iterables. + + >>> group_1, group_2 = distribute(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 3, 5] + >>> list(group_2) + [2, 4, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = distribute(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 4, 7], [2, 5], [3, 6]] + + If the length of *iterable* is smaller than *n*, then the last returned + iterables will be empty: + + >>> children = distribute(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function uses :func:`itertools.tee` and may require significant + storage. If you need the order items in the smaller iterables to match the + original iterable, see :func:`divide`. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + children = tee(iterable, n) + return [islice(it, index, None, n) for index, it in enumerate(children)] + + +def stagger(iterable, offsets=(-1, 0, 1), longest=False, fillvalue=None): + """Yield tuples whose elements are offset from *iterable*. + The amount by which the `i`-th item in each tuple is offset is given by + the `i`-th item in *offsets*. + + >>> list(stagger([0, 1, 2, 3])) + [(None, 0, 1), (0, 1, 2), (1, 2, 3)] + >>> list(stagger(range(8), offsets=(0, 2, 4))) + [(0, 2, 4), (1, 3, 5), (2, 4, 6), (3, 5, 7)] + + By default, the sequence will end when the final element of a tuple is the + last item in the iterable. To continue until the first element of a tuple + is the last item in the iterable, set *longest* to ``True``:: + + >>> list(stagger([0, 1, 2, 3], longest=True)) + [(None, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, None), (3, None, None)] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + children = tee(iterable, len(offsets)) + + return zip_offset( + *children, offsets=offsets, longest=longest, fillvalue=fillvalue + ) + + +class UnequalIterablesError(ValueError): + def __init__(self, details=None): + msg = 'Iterables have different lengths' + if details is not None: + msg += (': index 0 has length {}; index {} has length {}').format( + *details + ) + + super().__init__(msg) + + +def _zip_equal_generator(iterables): + for combo in zip_longest(*iterables, fillvalue=_marker): + for val in combo: + if val is _marker: + raise UnequalIterablesError() + yield combo + + +def zip_equal(*iterables): + """``zip`` the input *iterables* together, but raise + ``UnequalIterablesError`` if they aren't all the same length. + + >>> it_1 = range(3) + >>> it_2 = iter('abc') + >>> list(zip_equal(it_1, it_2)) + [(0, 'a'), (1, 'b'), (2, 'c')] + + >>> it_1 = range(3) + >>> it_2 = iter('abcd') + >>> list(zip_equal(it_1, it_2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + more_itertools.more.UnequalIterablesError: Iterables have different + lengths + + """ + if hexversion >= 0x30A00A6: + warnings.warn( + ( + 'zip_equal will be removed in a future version of ' + 'more-itertools. Use the builtin zip function with ' + 'strict=True instead.' + ), + DeprecationWarning, + ) + # Check whether the iterables are all the same size. + try: + first_size = len(iterables[0]) + for i, it in enumerate(iterables[1:], 1): + size = len(it) + if size != first_size: + break + else: + # If we didn't break out, we can use the built-in zip. + return zip(*iterables) + + # If we did break out, there was a mismatch. + raise UnequalIterablesError(details=(first_size, i, size)) + # If any one of the iterables didn't have a length, start reading + # them until one runs out. + except TypeError: + return _zip_equal_generator(iterables) + + +def zip_offset(*iterables, offsets, longest=False, fillvalue=None): + """``zip`` the input *iterables* together, but offset the `i`-th iterable + by the `i`-th item in *offsets*. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1))) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')] + + This can be used as a lightweight alternative to SciPy or pandas to analyze + data sets in which some series have a lead or lag relationship. + + By default, the sequence will end when the shortest iterable is exhausted. + To continue until the longest iterable is exhausted, set *longest* to + ``True``. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1), longest=True)) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e'), (None, 'f')] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + if len(iterables) != len(offsets): + raise ValueError("Number of iterables and offsets didn't match") + + staggered = [] + for it, n in zip(iterables, offsets): + if n < 0: + staggered.append(chain(repeat(fillvalue, -n), it)) + elif n > 0: + staggered.append(islice(it, n, None)) + else: + staggered.append(it) + + if longest: + return zip_longest(*staggered, fillvalue=fillvalue) + + return zip(*staggered) + + +def sort_together(iterables, key_list=(0,), key=None, reverse=False): + """Return the input iterables sorted together, with *key_list* as the + priority for sorting. All iterables are trimmed to the length of the + shortest one. + + This can be used like the sorting function in a spreadsheet. If each + iterable represents a column of data, the key list determines which + columns are used for sorting. + + By default, all iterables are sorted using the ``0``-th iterable:: + + >>> iterables = [(4, 3, 2, 1), ('a', 'b', 'c', 'd')] + >>> sort_together(iterables) + [(1, 2, 3, 4), ('d', 'c', 'b', 'a')] + + Set a different key list to sort according to another iterable. + Specifying multiple keys dictates how ties are broken:: + + >>> iterables = [(3, 1, 2), (0, 1, 0), ('c', 'b', 'a')] + >>> sort_together(iterables, key_list=(1, 2)) + [(2, 3, 1), (0, 0, 1), ('a', 'c', 'b')] + + To sort by a function of the elements of the iterable, pass a *key* + function. Its arguments are the elements of the iterables corresponding to + the key list:: + + >>> names = ('a', 'b', 'c') + >>> lengths = (1, 2, 3) + >>> widths = (5, 2, 1) + >>> def area(length, width): + ... return length * width + >>> sort_together([names, lengths, widths], key_list=(1, 2), key=area) + [('c', 'b', 'a'), (3, 2, 1), (1, 2, 5)] + + Set *reverse* to ``True`` to sort in descending order. + + >>> sort_together([(1, 2, 3), ('c', 'b', 'a')], reverse=True) + [(3, 2, 1), ('a', 'b', 'c')] + + """ + if key is None: + # if there is no key function, the key argument to sorted is an + # itemgetter + key_argument = itemgetter(*key_list) + else: + # if there is a key function, call it with the items at the offsets + # specified by the key function as arguments + key_list = list(key_list) + if len(key_list) == 1: + # if key_list contains a single item, pass the item at that offset + # as the only argument to the key function + key_offset = key_list[0] + key_argument = lambda zipped_items: key(zipped_items[key_offset]) + else: + # if key_list contains multiple items, use itemgetter to return a + # tuple of items, which we pass as *args to the key function + get_key_items = itemgetter(*key_list) + key_argument = lambda zipped_items: key( + *get_key_items(zipped_items) + ) + + return list( + zip(*sorted(zip(*iterables), key=key_argument, reverse=reverse)) + ) + + +def unzip(iterable): + """The inverse of :func:`zip`, this function disaggregates the elements + of the zipped *iterable*. + + The ``i``-th iterable contains the ``i``-th element from each element + of the zipped iterable. The first element is used to to determine the + length of the remaining elements. + + >>> iterable = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> letters, numbers = unzip(iterable) + >>> list(letters) + ['a', 'b', 'c', 'd'] + >>> list(numbers) + [1, 2, 3, 4] + + This is similar to using ``zip(*iterable)``, but it avoids reading + *iterable* into memory. Note, however, that this function uses + :func:`itertools.tee` and thus may require significant storage. + + """ + head, iterable = spy(iter(iterable)) + if not head: + # empty iterable, e.g. zip([], [], []) + return () + # spy returns a one-length iterable as head + head = head[0] + iterables = tee(iterable, len(head)) + + def itemgetter(i): + def getter(obj): + try: + return obj[i] + except IndexError: + # basically if we have an iterable like + # iter([(1, 2, 3), (4, 5), (6,)]) + # the second unzipped iterable would fail at the third tuple + # since it would try to access tup[1] + # same with the third unzipped iterable and the second tuple + # to support these "improperly zipped" iterables, + # we create a custom itemgetter + # which just stops the unzipped iterables + # at first length mismatch + raise StopIteration + + return getter + + return tuple(map(itemgetter(i), it) for i, it in enumerate(iterables)) + + +def divide(n, iterable): + """Divide the elements from *iterable* into *n* parts, maintaining + order. + + >>> group_1, group_2 = divide(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 2, 3] + >>> list(group_2) + [4, 5, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = divide(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 2, 3], [4, 5], [6, 7]] + + If the length of the iterable is smaller than n, then the last returned + iterables will be empty: + + >>> children = divide(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function will exhaust the iterable before returning and may require + significant storage. If order is not important, see :func:`distribute`, + which does not first pull the iterable into memory. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + try: + iterable[:0] + except TypeError: + seq = tuple(iterable) + else: + seq = iterable + + q, r = divmod(len(seq), n) + + ret = [] + stop = 0 + for i in range(1, n + 1): + start = stop + stop += q + 1 if i <= r else q + ret.append(iter(seq[start:stop])) + + return ret + + +def always_iterable(obj, base_type=(str, bytes)): + """If *obj* is iterable, return an iterator over its items:: + + >>> obj = (1, 2, 3) + >>> list(always_iterable(obj)) + [1, 2, 3] + + If *obj* is not iterable, return a one-item iterable containing *obj*:: + + >>> obj = 1 + >>> list(always_iterable(obj)) + [1] + + If *obj* is ``None``, return an empty iterable: + + >>> obj = None + >>> list(always_iterable(None)) + [] + + By default, binary and text strings are not considered iterable:: + + >>> obj = 'foo' + >>> list(always_iterable(obj)) + ['foo'] + + If *base_type* is set, objects for which ``isinstance(obj, base_type)`` + returns ``True`` won't be considered iterable. + + >>> obj = {'a': 1} + >>> list(always_iterable(obj)) # Iterate over the dict's keys + ['a'] + >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit + [{'a': 1}] + + Set *base_type* to ``None`` to avoid any special handling and treat objects + Python considers iterable as iterable: + + >>> obj = 'foo' + >>> list(always_iterable(obj, base_type=None)) + ['f', 'o', 'o'] + """ + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +def adjacent(predicate, iterable, distance=1): + """Return an iterable over `(bool, item)` tuples where the `item` is + drawn from *iterable* and the `bool` indicates whether + that item satisfies the *predicate* or is adjacent to an item that does. + + For example, to find whether items are adjacent to a ``3``:: + + >>> list(adjacent(lambda x: x == 3, range(6))) + [(False, 0), (False, 1), (True, 2), (True, 3), (True, 4), (False, 5)] + + Set *distance* to change what counts as adjacent. For example, to find + whether items are two places away from a ``3``: + + >>> list(adjacent(lambda x: x == 3, range(6), distance=2)) + [(False, 0), (True, 1), (True, 2), (True, 3), (True, 4), (True, 5)] + + This is useful for contextualizing the results of a search function. + For example, a code comparison tool might want to identify lines that + have changed, but also surrounding lines to give the viewer of the diff + context. + + The predicate function will only be called once for each item in the + iterable. + + See also :func:`groupby_transform`, which can be used with this function + to group ranges of items with the same `bool` value. + + """ + # Allow distance=0 mainly for testing that it reproduces results with map() + if distance < 0: + raise ValueError('distance must be at least 0') + + i1, i2 = tee(iterable) + padding = [False] * distance + selected = chain(padding, map(predicate, i1), padding) + adjacent_to_selected = map(any, windowed(selected, 2 * distance + 1)) + return zip(adjacent_to_selected, i2) + + +def groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None): + """An extension of :func:`itertools.groupby` that can apply transformations + to the grouped data. + + * *keyfunc* is a function computing a key value for each item in *iterable* + * *valuefunc* is a function that transforms the individual items from + *iterable* after grouping + * *reducefunc* is a function that transforms each group of items + + >>> iterable = 'aAAbBBcCC' + >>> keyfunc = lambda k: k.upper() + >>> valuefunc = lambda v: v.lower() + >>> reducefunc = lambda g: ''.join(g) + >>> list(groupby_transform(iterable, keyfunc, valuefunc, reducefunc)) + [('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')] + + Each optional argument defaults to an identity function if not specified. + + :func:`groupby_transform` is useful when grouping elements of an iterable + using a separate iterable as the key. To do this, :func:`zip` the iterables + and pass a *keyfunc* that extracts the first element and a *valuefunc* + that extracts the second element:: + + >>> from operator import itemgetter + >>> keys = [0, 0, 1, 1, 1, 2, 2, 2, 3] + >>> values = 'abcdefghi' + >>> iterable = zip(keys, values) + >>> grouper = groupby_transform(iterable, itemgetter(0), itemgetter(1)) + >>> [(k, ''.join(g)) for k, g in grouper] + [(0, 'ab'), (1, 'cde'), (2, 'fgh'), (3, 'i')] + + Note that the order of items in the iterable is significant. + Only adjacent items are grouped together, so if you don't want any + duplicate groups, you should sort the iterable by the key function. + + """ + ret = groupby(iterable, keyfunc) + if valuefunc: + ret = ((k, map(valuefunc, g)) for k, g in ret) + if reducefunc: + ret = ((k, reducefunc(g)) for k, g in ret) + + return ret + + +class numeric_range(abc.Sequence, abc.Hashable): + """An extension of the built-in ``range()`` function whose arguments can + be any orderable numeric type. + + With only *stop* specified, *start* defaults to ``0`` and *step* + defaults to ``1``. The output items will match the type of *stop*: + + >>> list(numeric_range(3.5)) + [0.0, 1.0, 2.0, 3.0] + + With only *start* and *stop* specified, *step* defaults to ``1``. The + output items will match the type of *start*: + + >>> from decimal import Decimal + >>> start = Decimal('2.1') + >>> stop = Decimal('5.1') + >>> list(numeric_range(start, stop)) + [Decimal('2.1'), Decimal('3.1'), Decimal('4.1')] + + With *start*, *stop*, and *step* specified the output items will match + the type of ``start + step``: + + >>> from fractions import Fraction + >>> start = Fraction(1, 2) # Start at 1/2 + >>> stop = Fraction(5, 2) # End at 5/2 + >>> step = Fraction(1, 2) # Count by 1/2 + >>> list(numeric_range(start, stop, step)) + [Fraction(1, 2), Fraction(1, 1), Fraction(3, 2), Fraction(2, 1)] + + If *step* is zero, ``ValueError`` is raised. Negative steps are supported: + + >>> list(numeric_range(3, -1, -1.0)) + [3.0, 2.0, 1.0, 0.0] + + Be aware of the limitations of floating point numbers; the representation + of the yielded numbers may be surprising. + + ``datetime.datetime`` objects can be used for *start* and *stop*, if *step* + is a ``datetime.timedelta`` object: + + >>> import datetime + >>> start = datetime.datetime(2019, 1, 1) + >>> stop = datetime.datetime(2019, 1, 3) + >>> step = datetime.timedelta(days=1) + >>> items = iter(numeric_range(start, stop, step)) + >>> next(items) + datetime.datetime(2019, 1, 1, 0, 0) + >>> next(items) + datetime.datetime(2019, 1, 2, 0, 0) + + """ + + _EMPTY_HASH = hash(range(0, 0)) + + def __init__(self, *args): + argc = len(args) + if argc == 1: + (self._stop,) = args + self._start = type(self._stop)(0) + self._step = type(self._stop - self._start)(1) + elif argc == 2: + self._start, self._stop = args + self._step = type(self._stop - self._start)(1) + elif argc == 3: + self._start, self._stop, self._step = args + elif argc == 0: + raise TypeError( + 'numeric_range expected at least ' + '1 argument, got {}'.format(argc) + ) + else: + raise TypeError( + 'numeric_range expected at most ' + '3 arguments, got {}'.format(argc) + ) + + self._zero = type(self._step)(0) + if self._step == self._zero: + raise ValueError('numeric_range() arg 3 must not be zero') + self._growing = self._step > self._zero + self._init_len() + + def __bool__(self): + if self._growing: + return self._start < self._stop + else: + return self._start > self._stop + + def __contains__(self, elem): + if self._growing: + if self._start <= elem < self._stop: + return (elem - self._start) % self._step == self._zero + else: + if self._start >= elem > self._stop: + return (self._start - elem) % (-self._step) == self._zero + + return False + + def __eq__(self, other): + if isinstance(other, numeric_range): + empty_self = not bool(self) + empty_other = not bool(other) + if empty_self or empty_other: + return empty_self and empty_other # True if both empty + else: + return ( + self._start == other._start + and self._step == other._step + and self._get_by_index(-1) == other._get_by_index(-1) + ) + else: + return False + + def __getitem__(self, key): + if isinstance(key, int): + return self._get_by_index(key) + elif isinstance(key, slice): + step = self._step if key.step is None else key.step * self._step + + if key.start is None or key.start <= -self._len: + start = self._start + elif key.start >= self._len: + start = self._stop + else: # -self._len < key.start < self._len + start = self._get_by_index(key.start) + + if key.stop is None or key.stop >= self._len: + stop = self._stop + elif key.stop <= -self._len: + stop = self._start + else: # -self._len < key.stop < self._len + stop = self._get_by_index(key.stop) + + return numeric_range(start, stop, step) + else: + raise TypeError( + 'numeric range indices must be ' + 'integers or slices, not {}'.format(type(key).__name__) + ) + + def __hash__(self): + if self: + return hash((self._start, self._get_by_index(-1), self._step)) + else: + return self._EMPTY_HASH + + def __iter__(self): + values = (self._start + (n * self._step) for n in count()) + if self._growing: + return takewhile(partial(gt, self._stop), values) + else: + return takewhile(partial(lt, self._stop), values) + + def __len__(self): + return self._len + + def _init_len(self): + if self._growing: + start = self._start + stop = self._stop + step = self._step + else: + start = self._stop + stop = self._start + step = -self._step + distance = stop - start + if distance <= self._zero: + self._len = 0 + else: # distance > 0 and step > 0: regular euclidean division + q, r = divmod(distance, step) + self._len = int(q) + int(r != self._zero) + + def __reduce__(self): + return numeric_range, (self._start, self._stop, self._step) + + def __repr__(self): + if self._step == 1: + return "numeric_range({}, {})".format( + repr(self._start), repr(self._stop) + ) + else: + return "numeric_range({}, {}, {})".format( + repr(self._start), repr(self._stop), repr(self._step) + ) + + def __reversed__(self): + return iter( + numeric_range( + self._get_by_index(-1), self._start - self._step, -self._step + ) + ) + + def count(self, value): + return int(value in self) + + def index(self, value): + if self._growing: + if self._start <= value < self._stop: + q, r = divmod(value - self._start, self._step) + if r == self._zero: + return int(q) + else: + if self._start >= value > self._stop: + q, r = divmod(self._start - value, -self._step) + if r == self._zero: + return int(q) + + raise ValueError("{} is not in numeric range".format(value)) + + def _get_by_index(self, i): + if i < 0: + i += self._len + if i < 0 or i >= self._len: + raise IndexError("numeric range object index out of range") + return self._start + i * self._step + + +def count_cycle(iterable, n=None): + """Cycle through the items from *iterable* up to *n* times, yielding + the number of completed cycles along with each item. If *n* is omitted the + process repeats indefinitely. + + >>> list(count_cycle('AB', 3)) + [(0, 'A'), (0, 'B'), (1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')] + + """ + iterable = tuple(iterable) + if not iterable: + return iter(()) + counter = count() if n is None else range(n) + return ((i, item) for i in counter for item in iterable) + + +def mark_ends(iterable): + """Yield 3-tuples of the form ``(is_first, is_last, item)``. + + >>> list(mark_ends('ABC')) + [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')] + + Use this when looping over an iterable to take special action on its first + and/or last items: + + >>> iterable = ['Header', 100, 200, 'Footer'] + >>> total = 0 + >>> for is_first, is_last, item in mark_ends(iterable): + ... if is_first: + ... continue # Skip the header + ... if is_last: + ... continue # Skip the footer + ... total += item + >>> print(total) + 300 + """ + it = iter(iterable) + + try: + b = next(it) + except StopIteration: + return + + try: + for i in count(): + a = b + b = next(it) + yield i == 0, False, a + + except StopIteration: + yield i == 0, True, a + + +def locate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(locate([0, 1, 1, 0, 1, 0, 0])) + [1, 2, 4] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item. + + >>> list(locate(['a', 'b', 'c', 'b'], lambda x: x == 'b')) + [1, 3] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(locate(iterable, pred=pred, window_size=3)) + [1, 5, 9] + + Use with :func:`seekable` to find indexes and then retrieve the associated + items: + + >>> from itertools import count + >>> from more_itertools import seekable + >>> source = (3 * n + 1 if (n % 2) else n // 2 for n in count()) + >>> it = seekable(source) + >>> pred = lambda x: x > 100 + >>> indexes = locate(it, pred=pred) + >>> i = next(indexes) + >>> it.seek(i) + >>> next(it) + 106 + + """ + if window_size is None: + return compress(count(), map(pred, iterable)) + + if window_size < 1: + raise ValueError('window size must be at least 1') + + it = windowed(iterable, window_size, fillvalue=_marker) + return compress(count(), starmap(pred, it)) + + +def lstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the beginning + for which *pred* returns ``True``. + + For example, to remove a set of items from the start of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(lstrip(iterable, pred)) + [1, 2, None, 3, False, None] + + This function is analogous to to :func:`str.lstrip`, and is essentially + an wrapper for :func:`itertools.dropwhile`. + + """ + return dropwhile(pred, iterable) + + +def rstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the end + for which *pred* returns ``True``. + + For example, to remove a set of items from the end of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(rstrip(iterable, pred)) + [None, False, None, 1, 2, None, 3] + + This function is analogous to :func:`str.rstrip`. + + """ + cache = [] + cache_append = cache.append + cache_clear = cache.clear + for x in iterable: + if pred(x): + cache_append(x) + else: + yield from cache + cache_clear() + yield x + + +def strip(iterable, pred): + """Yield the items from *iterable*, but strip any from the + beginning and end for which *pred* returns ``True``. + + For example, to remove a set of items from both ends of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(strip(iterable, pred)) + [1, 2, None, 3] + + This function is analogous to :func:`str.strip`. + + """ + return rstrip(lstrip(iterable, pred), pred) + + +class islice_extended: + """An extension of :func:`itertools.islice` that supports negative values + for *stop*, *start*, and *step*. + + >>> iterable = iter('abcdefgh') + >>> list(islice_extended(iterable, -4, -1)) + ['e', 'f', 'g'] + + Slices with negative values require some caching of *iterable*, but this + function takes care to minimize the amount of memory required. + + For example, you can use a negative step with an infinite iterator: + + >>> from itertools import count + >>> list(islice_extended(count(), 110, 99, -2)) + [110, 108, 106, 104, 102, 100] + + You can also use slice notation directly: + + >>> iterable = map(str, count()) + >>> it = islice_extended(iterable)[10:20:2] + >>> list(it) + ['10', '12', '14', '16', '18'] + + """ + + def __init__(self, iterable, *args): + it = iter(iterable) + if args: + self._iterable = _islice_helper(it, slice(*args)) + else: + self._iterable = it + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterable) + + def __getitem__(self, key): + if isinstance(key, slice): + return islice_extended(_islice_helper(self._iterable, key)) + + raise TypeError('islice_extended.__getitem__ argument must be a slice') + + +def _islice_helper(it, s): + start = s.start + stop = s.stop + if s.step == 0: + raise ValueError('step argument must be a non-zero integer or None.') + step = s.step or 1 + + if step > 0: + start = 0 if (start is None) else start + + if start < 0: + # Consume all but the last -start items + cache = deque(enumerate(it, 1), maxlen=-start) + len_iter = cache[-1][0] if cache else 0 + + # Adjust start to be positive + i = max(len_iter + start, 0) + + # Adjust stop to be positive + if stop is None: + j = len_iter + elif stop >= 0: + j = min(stop, len_iter) + else: + j = max(len_iter + stop, 0) + + # Slice the cache + n = j - i + if n <= 0: + return + + for index, item in islice(cache, 0, n, step): + yield item + elif (stop is not None) and (stop < 0): + # Advance to the start position + next(islice(it, start, start), None) + + # When stop is negative, we have to carry -stop items while + # iterating + cache = deque(islice(it, -stop), maxlen=-stop) + + for index, item in enumerate(it): + cached_item = cache.popleft() + if index % step == 0: + yield cached_item + cache.append(item) + else: + # When both start and stop are positive we have the normal case + yield from islice(it, start, stop, step) + else: + start = -1 if (start is None) else start + + if (stop is not None) and (stop < 0): + # Consume all but the last items + n = -stop - 1 + cache = deque(enumerate(it, 1), maxlen=n) + len_iter = cache[-1][0] if cache else 0 + + # If start and stop are both negative they are comparable and + # we can just slice. Otherwise we can adjust start to be negative + # and then slice. + if start < 0: + i, j = start, stop + else: + i, j = min(start - len_iter, -1), None + + for index, item in list(cache)[i:j:step]: + yield item + else: + # Advance to the stop position + if stop is not None: + m = stop + 1 + next(islice(it, m, m), None) + + # stop is positive, so if start is negative they are not comparable + # and we need the rest of the items. + if start < 0: + i = start + n = None + # stop is None and start is positive, so we just need items up to + # the start index. + elif stop is None: + i = None + n = start + 1 + # Both stop and start are positive, so they are comparable. + else: + i = None + n = start - stop + if n <= 0: + return + + cache = list(islice(it, n)) + + yield from cache[i::step] + + +def always_reversible(iterable): + """An extension of :func:`reversed` that supports all iterables, not + just those which implement the ``Reversible`` or ``Sequence`` protocols. + + >>> print(*always_reversible(x for x in range(3))) + 2 1 0 + + If the iterable is already reversible, this function returns the + result of :func:`reversed()`. If the iterable is not reversible, + this function will cache the remaining items in the iterable and + yield them in reverse order, which may require significant storage. + """ + try: + return reversed(iterable) + except TypeError: + return reversed(list(iterable)) + + +def consecutive_groups(iterable, ordering=lambda x: x): + """Yield groups of consecutive items using :func:`itertools.groupby`. + The *ordering* function determines whether two items are adjacent by + returning their position. + + By default, the ordering function is the identity function. This is + suitable for finding runs of numbers: + + >>> iterable = [1, 10, 11, 12, 20, 30, 31, 32, 33, 40] + >>> for group in consecutive_groups(iterable): + ... print(list(group)) + [1] + [10, 11, 12] + [20] + [30, 31, 32, 33] + [40] + + For finding runs of adjacent letters, try using the :meth:`index` method + of a string of letters: + + >>> from string import ascii_lowercase + >>> iterable = 'abcdfgilmnop' + >>> ordering = ascii_lowercase.index + >>> for group in consecutive_groups(iterable, ordering): + ... print(list(group)) + ['a', 'b', 'c', 'd'] + ['f', 'g'] + ['i'] + ['l', 'm', 'n', 'o', 'p'] + + Each group of consecutive items is an iterator that shares it source with + *iterable*. When an an output group is advanced, the previous group is + no longer available unless its elements are copied (e.g., into a ``list``). + + >>> iterable = [1, 2, 11, 12, 21, 22] + >>> saved_groups = [] + >>> for group in consecutive_groups(iterable): + ... saved_groups.append(list(group)) # Copy group elements + >>> saved_groups + [[1, 2], [11, 12], [21, 22]] + + """ + for k, g in groupby( + enumerate(iterable), key=lambda x: x[0] - ordering(x[1]) + ): + yield map(itemgetter(1), g) + + +def difference(iterable, func=sub, *, initial=None): + """This function is the inverse of :func:`itertools.accumulate`. By default + it will compute the first difference of *iterable* using + :func:`operator.sub`: + + >>> from itertools import accumulate + >>> iterable = accumulate([0, 1, 2, 3, 4]) # produces 0, 1, 3, 6, 10 + >>> list(difference(iterable)) + [0, 1, 2, 3, 4] + + *func* defaults to :func:`operator.sub`, but other functions can be + specified. They will be applied as follows:: + + A, B, C, D, ... --> A, func(B, A), func(C, B), func(D, C), ... + + For example, to do progressive division: + + >>> iterable = [1, 2, 6, 24, 120] + >>> func = lambda x, y: x // y + >>> list(difference(iterable, func)) + [1, 2, 3, 4, 5] + + If the *initial* keyword is set, the first element will be skipped when + computing successive differences. + + >>> it = [10, 11, 13, 16] # from accumulate([1, 2, 3], initial=10) + >>> list(difference(it, initial=10)) + [1, 2, 3] + + """ + a, b = tee(iterable) + try: + first = [next(b)] + except StopIteration: + return iter([]) + + if initial is not None: + first = [] + + return chain(first, starmap(func, zip(b, a))) + + +class SequenceView(Sequence): + """Return a read-only view of the sequence object *target*. + + :class:`SequenceView` objects are analogous to Python's built-in + "dictionary view" types. They provide a dynamic view of a sequence's items, + meaning that when the sequence updates, so does the view. + + >>> seq = ['0', '1', '2'] + >>> view = SequenceView(seq) + >>> view + SequenceView(['0', '1', '2']) + >>> seq.append('3') + >>> view + SequenceView(['0', '1', '2', '3']) + + Sequence views support indexing, slicing, and length queries. They act + like the underlying sequence, except they don't allow assignment: + + >>> view[1] + '1' + >>> view[1:-1] + ['1', '2'] + >>> len(view) + 4 + + Sequence views are useful as an alternative to copying, as they don't + require (much) extra storage. + + """ + + def __init__(self, target): + if not isinstance(target, Sequence): + raise TypeError + self._target = target + + def __getitem__(self, index): + return self._target[index] + + def __len__(self): + return len(self._target) + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, repr(self._target)) + + +class seekable: + """Wrap an iterator to allow for seeking backward and forward. This + progressively caches the items in the source iterable so they can be + re-visited. + + Call :meth:`seek` with an index to seek to that position in the source + iterable. + + To "reset" an iterator, seek to ``0``: + + >>> from itertools import count + >>> it = seekable((str(n) for n in count())) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> it.seek(0) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> next(it) + '3' + + You can also seek forward: + + >>> it = seekable((str(n) for n in range(20))) + >>> it.seek(10) + >>> next(it) + '10' + >>> it.seek(20) # Seeking past the end of the source isn't a problem + >>> list(it) + [] + >>> it.seek(0) # Resetting works even after hitting the end + >>> next(it), next(it), next(it) + ('0', '1', '2') + + Call :meth:`peek` to look ahead one item without advancing the iterator: + + >>> it = seekable('1234') + >>> it.peek() + '1' + >>> list(it) + ['1', '2', '3', '4'] + >>> it.peek(default='empty') + 'empty' + + Before the iterator is at its end, calling :func:`bool` on it will return + ``True``. After it will return ``False``: + + >>> it = seekable('5678') + >>> bool(it) + True + >>> list(it) + ['5', '6', '7', '8'] + >>> bool(it) + False + + You may view the contents of the cache with the :meth:`elements` method. + That returns a :class:`SequenceView`, a view that updates automatically: + + >>> it = seekable((str(n) for n in range(10))) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> elements = it.elements() + >>> elements + SequenceView(['0', '1', '2']) + >>> next(it) + '3' + >>> elements + SequenceView(['0', '1', '2', '3']) + + By default, the cache grows as the source iterable progresses, so beware of + wrapping very large or infinite iterables. Supply *maxlen* to limit the + size of the cache (this of course limits how far back you can seek). + + >>> from itertools import count + >>> it = seekable((str(n) for n in count()), maxlen=2) + >>> next(it), next(it), next(it), next(it) + ('0', '1', '2', '3') + >>> list(it.elements()) + ['2', '3'] + >>> it.seek(0) + >>> next(it), next(it), next(it), next(it) + ('2', '3', '4', '5') + >>> next(it) + '6' + + """ + + def __init__(self, iterable, maxlen=None): + self._source = iter(iterable) + if maxlen is None: + self._cache = [] + else: + self._cache = deque([], maxlen) + self._index = None + + def __iter__(self): + return self + + def __next__(self): + if self._index is not None: + try: + item = self._cache[self._index] + except IndexError: + self._index = None + else: + self._index += 1 + return item + + item = next(self._source) + self._cache.append(item) + return item + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + try: + peeked = next(self) + except StopIteration: + if default is _marker: + raise + return default + if self._index is None: + self._index = len(self._cache) + self._index -= 1 + return peeked + + def elements(self): + return SequenceView(self._cache) + + def seek(self, index): + self._index = index + remainder = index - len(self._cache) + if remainder > 0: + consume(self, remainder) + + +class run_length: + """ + :func:`run_length.encode` compresses an iterable with run-length encoding. + It yields groups of repeated items with the count of how many times they + were repeated: + + >>> uncompressed = 'abbcccdddd' + >>> list(run_length.encode(uncompressed)) + [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + + :func:`run_length.decode` decompresses an iterable that was previously + compressed with run-length encoding. It yields the items of the + decompressed iterable: + + >>> compressed = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> list(run_length.decode(compressed)) + ['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'] + + """ + + @staticmethod + def encode(iterable): + return ((k, ilen(g)) for k, g in groupby(iterable)) + + @staticmethod + def decode(iterable): + return chain.from_iterable(repeat(k, n) for k, n in iterable) + + +def exactly_n(iterable, n, predicate=bool): + """Return ``True`` if exactly ``n`` items in the iterable are ``True`` + according to the *predicate* function. + + >>> exactly_n([True, True, False], 2) + True + >>> exactly_n([True, True, False], 1) + False + >>> exactly_n([0, 1, 2, 3, 4, 5], 3, lambda x: x < 3) + True + + The iterable will be advanced until ``n + 1`` truthy items are encountered, + so avoid calling it on infinite iterables. + + """ + return len(take(n + 1, filter(predicate, iterable))) == n + + +def circular_shifts(iterable): + """Return a list of circular shifts of *iterable*. + + >>> circular_shifts(range(4)) + [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)] + """ + lst = list(iterable) + return take(len(lst), windowed(cycle(lst), len(lst))) + + +def make_decorator(wrapping_func, result_index=0): + """Return a decorator version of *wrapping_func*, which is a function that + modifies an iterable. *result_index* is the position in that function's + signature where the iterable goes. + + This lets you use itertools on the "production end," i.e. at function + definition. This can augment what the function returns without changing the + function's code. + + For example, to produce a decorator version of :func:`chunked`: + + >>> from more_itertools import chunked + >>> chunker = make_decorator(chunked, result_index=0) + >>> @chunker(3) + ... def iter_range(n): + ... return iter(range(n)) + ... + >>> list(iter_range(9)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + + To only allow truthy items to be returned: + + >>> truth_serum = make_decorator(filter, result_index=1) + >>> @truth_serum(bool) + ... def boolean_test(): + ... return [0, 1, '', ' ', False, True] + ... + >>> list(boolean_test()) + [1, ' ', True] + + The :func:`peekable` and :func:`seekable` wrappers make for practical + decorators: + + >>> from more_itertools import peekable + >>> peekable_function = make_decorator(peekable) + >>> @peekable_function() + ... def str_range(*args): + ... return (str(x) for x in range(*args)) + ... + >>> it = str_range(1, 20, 2) + >>> next(it), next(it), next(it) + ('1', '3', '5') + >>> it.peek() + '7' + >>> next(it) + '7' + + """ + # See https://sites.google.com/site/bbayles/index/decorator_factory for + # notes on how this works. + def decorator(*wrapping_args, **wrapping_kwargs): + def outer_wrapper(f): + def inner_wrapper(*args, **kwargs): + result = f(*args, **kwargs) + wrapping_args_ = list(wrapping_args) + wrapping_args_.insert(result_index, result) + return wrapping_func(*wrapping_args_, **wrapping_kwargs) + + return inner_wrapper + + return outer_wrapper + + return decorator + + +def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None): + """Return a dictionary that maps the items in *iterable* to categories + defined by *keyfunc*, transforms them with *valuefunc*, and + then summarizes them by category with *reducefunc*. + + *valuefunc* defaults to the identity function if it is unspecified. + If *reducefunc* is unspecified, no summarization takes place: + + >>> keyfunc = lambda x: x.upper() + >>> result = map_reduce('abbccc', keyfunc) + >>> sorted(result.items()) + [('A', ['a']), ('B', ['b', 'b']), ('C', ['c', 'c', 'c'])] + + Specifying *valuefunc* transforms the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> result = map_reduce('abbccc', keyfunc, valuefunc) + >>> sorted(result.items()) + [('A', [1]), ('B', [1, 1]), ('C', [1, 1, 1])] + + Specifying *reducefunc* summarizes the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> reducefunc = sum + >>> result = map_reduce('abbccc', keyfunc, valuefunc, reducefunc) + >>> sorted(result.items()) + [('A', 1), ('B', 2), ('C', 3)] + + You may want to filter the input iterable before applying the map/reduce + procedure: + + >>> all_items = range(30) + >>> items = [x for x in all_items if 10 <= x <= 20] # Filter + >>> keyfunc = lambda x: x % 2 # Evens map to 0; odds to 1 + >>> categories = map_reduce(items, keyfunc=keyfunc) + >>> sorted(categories.items()) + [(0, [10, 12, 14, 16, 18, 20]), (1, [11, 13, 15, 17, 19])] + >>> summaries = map_reduce(items, keyfunc=keyfunc, reducefunc=sum) + >>> sorted(summaries.items()) + [(0, 90), (1, 75)] + + Note that all items in the iterable are gathered into a list before the + summarization step, which may require significant storage. + + The returned object is a :obj:`collections.defaultdict` with the + ``default_factory`` set to ``None``, such that it behaves like a normal + dictionary. + + """ + valuefunc = (lambda x: x) if (valuefunc is None) else valuefunc + + ret = defaultdict(list) + for item in iterable: + key = keyfunc(item) + value = valuefunc(item) + ret[key].append(value) + + if reducefunc is not None: + for key, value_list in ret.items(): + ret[key] = reducefunc(value_list) + + ret.default_factory = None + return ret + + +def rlocate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``, starting from the right and moving left. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(rlocate([0, 1, 1, 0, 1, 0, 0])) # Truthy at 1, 2, and 4 + [4, 2, 1] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item: + + >>> iterable = iter('abcb') + >>> pred = lambda x: x == 'b' + >>> list(rlocate(iterable, pred)) + [3, 1] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(rlocate(iterable, pred=pred, window_size=3)) + [9, 5, 1] + + Beware, this function won't return anything for infinite iterables. + If *iterable* is reversible, ``rlocate`` will reverse it and search from + the right. Otherwise, it will search from the left and return the results + in reverse order. + + See :func:`locate` to for other example applications. + + """ + if window_size is None: + try: + len_iter = len(iterable) + return (len_iter - i - 1 for i in locate(reversed(iterable), pred)) + except TypeError: + pass + + return reversed(list(locate(iterable, pred, window_size))) + + +def replace(iterable, pred, substitutes, count=None, window_size=1): + """Yield the items from *iterable*, replacing the items for which *pred* + returns ``True`` with the items from the iterable *substitutes*. + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1] + >>> pred = lambda x: x == 0 + >>> substitutes = (2, 3) + >>> list(replace(iterable, pred, substitutes)) + [1, 1, 2, 3, 1, 1, 2, 3, 1, 1] + + If *count* is given, the number of replacements will be limited: + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1, 0] + >>> pred = lambda x: x == 0 + >>> substitutes = [None] + >>> list(replace(iterable, pred, substitutes, count=2)) + [1, 1, None, 1, 1, None, 1, 1, 0] + + Use *window_size* to control the number of items passed as arguments to + *pred*. This allows for locating and replacing subsequences. + + >>> iterable = [0, 1, 2, 5, 0, 1, 2, 5] + >>> window_size = 3 + >>> pred = lambda *args: args == (0, 1, 2) # 3 items passed to pred + >>> substitutes = [3, 4] # Splice in these items + >>> list(replace(iterable, pred, substitutes, window_size=window_size)) + [3, 4, 5, 3, 4, 5] + + """ + if window_size < 1: + raise ValueError('window_size must be at least 1') + + # Save the substitutes iterable, since it's used more than once + substitutes = tuple(substitutes) + + # Add padding such that the number of windows matches the length of the + # iterable + it = chain(iterable, [_marker] * (window_size - 1)) + windows = windowed(it, window_size) + + n = 0 + for w in windows: + # If the current window matches our predicate (and we haven't hit + # our maximum number of replacements), splice in the substitutes + # and then consume the following windows that overlap with this one. + # For example, if the iterable is (0, 1, 2, 3, 4...) + # and the window size is 2, we have (0, 1), (1, 2), (2, 3)... + # If the predicate matches on (0, 1), we need to zap (0, 1) and (1, 2) + if pred(*w): + if (count is None) or (n < count): + n += 1 + yield from substitutes + consume(windows, window_size - 1) + continue + + # If there was no match (or we've reached the replacement limit), + # yield the first item from the window. + if w and (w[0] is not _marker): + yield w[0] + + +def partitions(iterable): + """Yield all possible order-preserving partitions of *iterable*. + + >>> iterable = 'abc' + >>> for part in partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['a', 'b', 'c'] + + This is unrelated to :func:`partition`. + + """ + sequence = list(iterable) + n = len(sequence) + for i in powerset(range(1, n)): + yield [sequence[i:j] for i, j in zip((0,) + i, i + (n,))] + + +def set_partitions(iterable, k=None): + """ + Yield the set partitions of *iterable* into *k* parts. Set partitions are + not order-preserving. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable, 2): + ... print([''.join(p) for p in part]) + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + + + If *k* is not given, every set partition is generated. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + ['a', 'b', 'c'] + + """ + L = list(iterable) + n = len(L) + if k is not None: + if k < 1: + raise ValueError( + "Can't partition in a negative or zero number of groups" + ) + elif k > n: + return + + def set_partitions_helper(L, k): + n = len(L) + if k == 1: + yield [L] + elif n == k: + yield [[s] for s in L] + else: + e, *M = L + for p in set_partitions_helper(M, k - 1): + yield [[e], *p] + for p in set_partitions_helper(M, k): + for i in range(len(p)): + yield p[:i] + [[e] + p[i]] + p[i + 1 :] + + if k is None: + for k in range(1, n + 1): + yield from set_partitions_helper(L, k) + else: + yield from set_partitions_helper(L, k) + + +class time_limited: + """ + Yield items from *iterable* until *limit_seconds* have passed. + If the time limit expires before all items have been yielded, the + ``timed_out`` parameter will be set to ``True``. + + >>> from time import sleep + >>> def generator(): + ... yield 1 + ... yield 2 + ... sleep(0.2) + ... yield 3 + >>> iterable = time_limited(0.1, generator()) + >>> list(iterable) + [1, 2] + >>> iterable.timed_out + True + + Note that the time is checked before each item is yielded, and iteration + stops if the time elapsed is greater than *limit_seconds*. If your time + limit is 1 second, but it takes 2 seconds to generate the first item from + the iterable, the function will run for 2 seconds and not yield anything. + + """ + + def __init__(self, limit_seconds, iterable): + if limit_seconds < 0: + raise ValueError('limit_seconds must be positive') + self.limit_seconds = limit_seconds + self._iterable = iter(iterable) + self._start_time = monotonic() + self.timed_out = False + + def __iter__(self): + return self + + def __next__(self): + item = next(self._iterable) + if monotonic() - self._start_time > self.limit_seconds: + self.timed_out = True + raise StopIteration + + return item + + +def only(iterable, default=None, too_long=None): + """If *iterable* has only one item, return it. + If it has zero items, return *default*. + If it has more than one item, raise the exception given by *too_long*, + which is ``ValueError`` by default. + + >>> only([], default='missing') + 'missing' + >>> only([1]) + 1 + >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 1, 2, + and perhaps more.' + >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError + + Note that :func:`only` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check + iterable contents less destructively. + """ + it = iter(iterable) + first_value = next(it, default) + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def ichunked(iterable, n): + """Break *iterable* into sub-iterables with *n* elements each. + :func:`ichunked` is like :func:`chunked`, but it yields iterables + instead of lists. + + If the sub-iterables are read in order, the elements of *iterable* + won't be stored in memory. + If they are read out of order, :func:`itertools.tee` is used to cache + elements as necessary. + + >>> from itertools import count + >>> all_chunks = ichunked(count(), 4) + >>> c_1, c_2, c_3 = next(all_chunks), next(all_chunks), next(all_chunks) + >>> list(c_2) # c_1's elements have been cached; c_3's haven't been + [4, 5, 6, 7] + >>> list(c_1) + [0, 1, 2, 3] + >>> list(c_3) + [8, 9, 10, 11] + + """ + source = iter(iterable) + + while True: + # Check to see whether we're at the end of the source iterable + item = next(source, _marker) + if item is _marker: + return + + # Clone the source and yield an n-length slice + source, it = tee(chain([item], source)) + yield islice(it, n) + + # Advance the source iterable + consume(source, n) + + +def distinct_combinations(iterable, r): + """Yield the distinct combinations of *r* items taken from *iterable*. + + >>> list(distinct_combinations([0, 0, 1], 2)) + [(0, 0), (0, 1)] + + Equivalent to ``set(combinations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + """ + if r < 0: + raise ValueError('r must be non-negative') + elif r == 0: + yield () + return + pool = tuple(iterable) + generators = [unique_everseen(enumerate(pool), key=itemgetter(1))] + current_combo = [None] * r + level = 0 + while generators: + try: + cur_idx, p = next(generators[-1]) + except StopIteration: + generators.pop() + level -= 1 + continue + current_combo[level] = p + if level + 1 == r: + yield tuple(current_combo) + else: + generators.append( + unique_everseen( + enumerate(pool[cur_idx + 1 :], cur_idx + 1), + key=itemgetter(1), + ) + ) + level += 1 + + +def filter_except(validator, iterable, *exceptions): + """Yield the items from *iterable* for which the *validator* function does + not raise one of the specified *exceptions*. + + *validator* is called for each item in *iterable*. + It should be a function that accepts one argument and raises an exception + if that item is not valid. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(filter_except(int, iterable, ValueError, TypeError)) + ['1', '2', '4'] + + If an exception other than one given by *exceptions* is raised by + *validator*, it is raised like normal. + """ + for item in iterable: + try: + validator(item) + except exceptions: + pass + else: + yield item + + +def map_except(function, iterable, *exceptions): + """Transform each item from *iterable* with *function* and yield the + result, unless *function* raises one of the specified *exceptions*. + + *function* is called to transform each item in *iterable*. + It should be a accept one argument. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(map_except(int, iterable, ValueError, TypeError)) + [1, 2, 4] + + If an exception other than one given by *exceptions* is raised by + *function*, it is raised like normal. + """ + for item in iterable: + try: + yield function(item) + except exceptions: + pass + + +def _sample_unweighted(iterable, k): + # Implementation of "Algorithm L" from the 1994 paper by Kim-Hung Li: + # "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))". + + # Fill up the reservoir (collection of samples) with the first `k` samples + reservoir = take(k, iterable) + + # Generate random number that's the largest in a sample of k U(0,1) numbers + # Largest order statistic: https://en.wikipedia.org/wiki/Order_statistic + W = exp(log(random()) / k) + + # The number of elements to skip before changing the reservoir is a random + # number with a geometric distribution. Sample it using random() and logs. + next_index = k + floor(log(random()) / log(1 - W)) + + for index, element in enumerate(iterable, k): + + if index == next_index: + reservoir[randrange(k)] = element + # The new W is the largest in a sample of k U(0, `old_W`) numbers + W *= exp(log(random()) / k) + next_index += floor(log(random()) / log(1 - W)) + 1 + + return reservoir + + +def _sample_weighted(iterable, k, weights): + # Implementation of "A-ExpJ" from the 2006 paper by Efraimidis et al. : + # "Weighted random sampling with a reservoir". + + # Log-transform for numerical stability for weights that are small/large + weight_keys = (log(random()) / weight for weight in weights) + + # Fill up the reservoir (collection of samples) with the first `k` + # weight-keys and elements, then heapify the list. + reservoir = take(k, zip(weight_keys, iterable)) + heapify(reservoir) + + # The number of jumps before changing the reservoir is a random variable + # with an exponential distribution. Sample it using random() and logs. + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + + for weight, element in zip(weights, iterable): + if weight >= weights_to_skip: + # The notation here is consistent with the paper, but we store + # the weight-keys in log-space for better numerical stability. + smallest_weight_key, _ = reservoir[0] + t_w = exp(weight * smallest_weight_key) + r_2 = uniform(t_w, 1) # generate U(t_w, 1) + weight_key = log(r_2) / weight + heapreplace(reservoir, (weight_key, element)) + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + else: + weights_to_skip -= weight + + # Equivalent to [element for weight_key, element in sorted(reservoir)] + return [heappop(reservoir)[1] for _ in range(k)] + + +def sample(iterable, k, weights=None): + """Return a *k*-length list of elements chosen (without replacement) + from the *iterable*. Like :func:`random.sample`, but works on iterables + of unknown length. + + >>> iterable = range(100) + >>> sample(iterable, 5) # doctest: +SKIP + [81, 60, 96, 16, 4] + + An iterable with *weights* may also be given: + + >>> iterable = range(100) + >>> weights = (i * i + 1 for i in range(100)) + >>> sampled = sample(iterable, 5, weights=weights) # doctest: +SKIP + [79, 67, 74, 66, 78] + + The algorithm can also be used to generate weighted random permutations. + The relative weight of each item determines the probability that it + appears late in the permutation. + + >>> data = "abcdefgh" + >>> weights = range(1, len(data) + 1) + >>> sample(data, k=len(data), weights=weights) # doctest: +SKIP + ['c', 'a', 'b', 'e', 'g', 'd', 'h', 'f'] + """ + if k == 0: + return [] + + iterable = iter(iterable) + if weights is None: + return _sample_unweighted(iterable, k) + else: + weights = iter(weights) + return _sample_weighted(iterable, k, weights) + + +def is_sorted(iterable, key=None, reverse=False): + """Returns ``True`` if the items of iterable are in sorted order, and + ``False`` otherwise. *key* and *reverse* have the same meaning that they do + in the built-in :func:`sorted` function. + + >>> is_sorted(['1', '2', '3', '4', '5'], key=int) + True + >>> is_sorted([5, 4, 3, 1, 2], reverse=True) + False + + The function returns ``False`` after encountering the first out-of-order + item. If there are no out-of-order items, the iterable is exhausted. + """ + + compare = lt if reverse else gt + it = iterable if (key is None) else map(key, iterable) + return not any(starmap(compare, pairwise(it))) + + +class AbortThread(BaseException): + pass + + +class callback_iter: + """Convert a function that uses callbacks to an iterator. + + Let *func* be a function that takes a `callback` keyword argument. + For example: + + >>> def func(callback=None): + ... for i, c in [(1, 'a'), (2, 'b'), (3, 'c')]: + ... if callback: + ... callback(i, c) + ... return 4 + + + Use ``with callback_iter(func)`` to get an iterator over the parameters + that are delivered to the callback. + + >>> with callback_iter(func) as it: + ... for args, kwargs in it: + ... print(args) + (1, 'a') + (2, 'b') + (3, 'c') + + The function will be called in a background thread. The ``done`` property + indicates whether it has completed execution. + + >>> it.done + True + + If it completes successfully, its return value will be available + in the ``result`` property. + + >>> it.result + 4 + + Notes: + + * If the function uses some keyword argument besides ``callback``, supply + *callback_kwd*. + * If it finished executing, but raised an exception, accessing the + ``result`` property will raise the same exception. + * If it hasn't finished executing, accessing the ``result`` + property from within the ``with`` block will raise ``RuntimeError``. + * If it hasn't finished executing, accessing the ``result`` property from + outside the ``with`` block will raise a + ``more_itertools.AbortThread`` exception. + * Provide *wait_seconds* to adjust how frequently the it is polled for + output. + + """ + + def __init__(self, func, callback_kwd='callback', wait_seconds=0.1): + self._func = func + self._callback_kwd = callback_kwd + self._aborted = False + self._future = None + self._wait_seconds = wait_seconds + self._executor = ThreadPoolExecutor(max_workers=1) + self._iterator = self._reader() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._aborted = True + self._executor.shutdown() + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterator) + + @property + def done(self): + if self._future is None: + return False + return self._future.done() + + @property + def result(self): + if not self.done: + raise RuntimeError('Function has not yet completed') + + return self._future.result() + + def _reader(self): + q = Queue() + + def callback(*args, **kwargs): + if self._aborted: + raise AbortThread('canceled by user') + + q.put((args, kwargs)) + + self._future = self._executor.submit( + self._func, **{self._callback_kwd: callback} + ) + + while True: + try: + item = q.get(timeout=self._wait_seconds) + except Empty: + pass + else: + q.task_done() + yield item + + if self._future.done(): + break + + remaining = [] + while True: + try: + item = q.get_nowait() + except Empty: + break + else: + q.task_done() + remaining.append(item) + q.join() + yield from remaining + + +def windowed_complete(iterable, n): + """ + Yield ``(beginning, middle, end)`` tuples, where: + + * Each ``middle`` has *n* items from *iterable* + * Each ``beginning`` has the items before the ones in ``middle`` + * Each ``end`` has the items after the ones in ``middle`` + + >>> iterable = range(7) + >>> n = 3 + >>> for beginning, middle, end in windowed_complete(iterable, n): + ... print(beginning, middle, end) + () (0, 1, 2) (3, 4, 5, 6) + (0,) (1, 2, 3) (4, 5, 6) + (0, 1) (2, 3, 4) (5, 6) + (0, 1, 2) (3, 4, 5) (6,) + (0, 1, 2, 3) (4, 5, 6) () + + Note that *n* must be at least 0 and most equal to the length of + *iterable*. + + This function will exhaust the iterable and may require significant + storage. + """ + if n < 0: + raise ValueError('n must be >= 0') + + seq = tuple(iterable) + size = len(seq) + + if n > size: + raise ValueError('n must be <= len(seq)') + + for i in range(size - n + 1): + beginning = seq[:i] + middle = seq[i : i + n] + end = seq[i + n :] + yield beginning, middle, end + + +def all_unique(iterable, key=None): + """ + Returns ``True`` if all the elements of *iterable* are unique (no two + elements are equal). + + >>> all_unique('ABCB') + False + + If a *key* function is specified, it will be used to make comparisons. + + >>> all_unique('ABCb') + True + >>> all_unique('ABCb', str.lower) + False + + The function returns as soon as the first non-unique element is + encountered. Iterables with a mix of hashable and unhashable items can + be used, but the function will be slower for unhashable items. + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + for element in map(key, iterable) if key else iterable: + try: + if element in seenset: + return False + seenset_add(element) + except TypeError: + if element in seenlist: + return False + seenlist_add(element) + return True + + +def nth_product(index, *args): + """Equivalent to ``list(product(*args))[index]``. + + The products of *args* can be ordered lexicographically. + :func:`nth_product` computes the product at sort position *index* without + computing the previous products. + + >>> nth_product(8, range(2), range(2), range(2), range(2)) + (1, 0, 0, 0) + + ``IndexError`` will be raised if the given *index* is invalid. + """ + pools = list(map(tuple, reversed(args))) + ns = list(map(len, pools)) + + c = reduce(mul, ns) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + result = [] + for pool, n in zip(pools, ns): + result.append(pool[index % n]) + index //= n + + return tuple(reversed(result)) + + +def nth_permutation(iterable, r, index): + """Equivalent to ``list(permutations(iterable, r))[index]``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`nth_permutation` + computes the subsequence at sort position *index* directly, without + computing the previous subsequences. + + >>> nth_permutation('ghijk', 2, 5) + ('h', 'i') + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = list(iterable) + n = len(pool) + + if r is None or r == n: + r, c = n, factorial(n) + elif not 0 <= r < n: + raise ValueError + else: + c = factorial(n) // factorial(n - r) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + if c == 0: + return tuple() + + result = [0] * r + q = index * factorial(n) // c if r < n else index + for d in range(1, n + 1): + q, i = divmod(q, d) + if 0 <= n - d < r: + result[n - d] = i + if q == 0: + break + + return tuple(map(pool.pop, result)) + + +def value_chain(*args): + """Yield all arguments passed to the function in the same order in which + they were passed. If an argument itself is iterable then iterate over its + values. + + >>> list(value_chain(1, 2, 3, [4, 5, 6])) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and are emitted + as-is: + + >>> list(value_chain('12', '34', ['56', '78'])) + ['12', '34', '56', '78'] + + + Multiple levels of nesting are not flattened. + + """ + for value in args: + if isinstance(value, (str, bytes)): + yield value + continue + try: + yield from value + except TypeError: + yield value + + +def product_index(element, *args): + """Equivalent to ``list(product(*args)).index(element)`` + + The products of *args* can be ordered lexicographically. + :func:`product_index` computes the first index of *element* without + computing the previous products. + + >>> product_index([8, 2], range(10), range(5)) + 42 + + ``ValueError`` will be raised if the given *element* isn't in the product + of *args*. + """ + index = 0 + + for x, pool in zip_longest(element, args, fillvalue=_marker): + if x is _marker or pool is _marker: + raise ValueError('element is not a product of args') + + pool = tuple(pool) + index = index * len(pool) + pool.index(x) + + return index + + +def combination_index(element, iterable): + """Equivalent to ``list(combinations(iterable, r)).index(element)`` + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`combination_index` computes the index of the + first *element*, without computing the previous combinations. + + >>> combination_index('adf', 'abcdefg') + 10 + + ``ValueError`` will be raised if the given *element* isn't one of the + combinations of *iterable*. + """ + element = enumerate(element) + k, y = next(element, (None, None)) + if k is None: + return 0 + + indexes = [] + pool = enumerate(iterable) + for n, x in pool: + if x == y: + indexes.append(n) + tmp, y = next(element, (None, None)) + if tmp is None: + break + else: + k = tmp + else: + raise ValueError('element is not a combination of iterable') + + n, _ = last(pool, default=(n, None)) + + # Python versiosn below 3.8 don't have math.comb + index = 1 + for i, j in enumerate(reversed(indexes), start=1): + j = n - j + if i <= j: + index += factorial(j) // (factorial(i) * factorial(j - i)) + + return factorial(n + 1) // (factorial(k + 1) * factorial(n - k)) - index + + +def permutation_index(element, iterable): + """Equivalent to ``list(permutations(iterable, r)).index(element)``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`permutation_index` + computes the index of the first *element* directly, without computing + the previous permutations. + + >>> permutation_index([1, 3, 2], range(5)) + 19 + + ``ValueError`` will be raised if the given *element* isn't one of the + permutations of *iterable*. + """ + index = 0 + pool = list(iterable) + for i, x in zip(range(len(pool), -1, -1), element): + r = pool.index(x) + index = index * i + r + del pool[r] + + return index + + +class countable: + """Wrap *iterable* and keep a count of how many items have been consumed. + + The ``items_seen`` attribute starts at ``0`` and increments as the iterable + is consumed: + + >>> iterable = map(str, range(10)) + >>> it = countable(iterable) + >>> it.items_seen + 0 + >>> next(it), next(it) + ('0', '1') + >>> list(it) + ['2', '3', '4', '5', '6', '7', '8', '9'] + >>> it.items_seen + 10 + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self.items_seen = 0 + + def __iter__(self): + return self + + def __next__(self): + item = next(self._it) + self.items_seen += 1 + + return item diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/recipes.py b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/recipes.py new file mode 100644 index 0000000000000000000000000000000000000000..521abd7c2ca633f90a5ba13a8060c5c3d0c32205 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/more_itertools/recipes.py @@ -0,0 +1,620 @@ +"""Imported from the recipes section of the itertools documentation. + +All functions taken from the recipes section of the itertools library docs +[1]_. +Some backward-compatible usability improvements have been made. + +.. [1] http://docs.python.org/library/itertools.html#recipes + +""" +import warnings +from collections import deque +from itertools import ( + chain, + combinations, + count, + cycle, + groupby, + islice, + repeat, + starmap, + tee, + zip_longest, +) +import operator +from random import randrange, sample, choice + +__all__ = [ + 'all_equal', + 'consume', + 'convolve', + 'dotproduct', + 'first_true', + 'flatten', + 'grouper', + 'iter_except', + 'ncycles', + 'nth', + 'nth_combination', + 'padnone', + 'pad_none', + 'pairwise', + 'partition', + 'powerset', + 'prepend', + 'quantify', + 'random_combination_with_replacement', + 'random_combination', + 'random_permutation', + 'random_product', + 'repeatfunc', + 'roundrobin', + 'tabulate', + 'tail', + 'take', + 'unique_everseen', + 'unique_justseen', +] + + +def take(n, iterable): + """Return first *n* items of the iterable as a list. + + >>> take(3, range(10)) + [0, 1, 2] + + If there are fewer than *n* items in the iterable, all of them are + returned. + + >>> take(10, range(3)) + [0, 1, 2] + + """ + return list(islice(iterable, n)) + + +def tabulate(function, start=0): + """Return an iterator over the results of ``func(start)``, + ``func(start + 1)``, ``func(start + 2)``... + + *func* should be a function that accepts one integer argument. + + If *start* is not specified it defaults to 0. It will be incremented each + time the iterator is advanced. + + >>> square = lambda x: x ** 2 + >>> iterator = tabulate(square, -3) + >>> take(4, iterator) + [9, 4, 1, 0] + + """ + return map(function, count(start)) + + +def tail(n, iterable): + """Return an iterator over the last *n* items of *iterable*. + + >>> t = tail(3, 'ABCDEFG') + >>> list(t) + ['E', 'F', 'G'] + + """ + return iter(deque(iterable, maxlen=n)) + + +def consume(iterator, n=None): + """Advance *iterable* by *n* steps. If *n* is ``None``, consume it + entirely. + + Efficiently exhausts an iterator without returning values. Defaults to + consuming the whole iterator, but an optional second argument may be + provided to limit consumption. + + >>> i = (x for x in range(10)) + >>> next(i) + 0 + >>> consume(i, 3) + >>> next(i) + 4 + >>> consume(i) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + If the iterator has fewer items remaining than the provided limit, the + whole iterator will be consumed. + + >>> i = (x for x in range(3)) + >>> consume(i, 5) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + """ + # Use functions that consume iterators at C speed. + if n is None: + # feed the entire iterator into a zero-length deque + deque(iterator, maxlen=0) + else: + # advance to the empty slice starting at position n + next(islice(iterator, n, n), None) + + +def nth(iterable, n, default=None): + """Returns the nth item or a default value. + + >>> l = range(10) + >>> nth(l, 3) + 3 + >>> nth(l, 20, "zebra") + 'zebra' + + """ + return next(islice(iterable, n, None), default) + + +def all_equal(iterable): + """ + Returns ``True`` if all the elements are equal to each other. + + >>> all_equal('aaaa') + True + >>> all_equal('aaab') + False + + """ + g = groupby(iterable) + return next(g, True) and not next(g, False) + + +def quantify(iterable, pred=bool): + """Return the how many times the predicate is true. + + >>> quantify([True, False, True]) + 2 + + """ + return sum(map(pred, iterable)) + + +def pad_none(iterable): + """Returns the sequence of elements and then returns ``None`` indefinitely. + + >>> take(5, pad_none(range(3))) + [0, 1, 2, None, None] + + Useful for emulating the behavior of the built-in :func:`map` function. + + See also :func:`padded`. + + """ + return chain(iterable, repeat(None)) + + +padnone = pad_none + + +def ncycles(iterable, n): + """Returns the sequence elements *n* times + + >>> list(ncycles(["a", "b"], 3)) + ['a', 'b', 'a', 'b', 'a', 'b'] + + """ + return chain.from_iterable(repeat(tuple(iterable), n)) + + +def dotproduct(vec1, vec2): + """Returns the dot product of the two iterables. + + >>> dotproduct([10, 10], [20, 20]) + 400 + + """ + return sum(map(operator.mul, vec1, vec2)) + + +def flatten(listOfLists): + """Return an iterator flattening one level of nesting in a list of lists. + + >>> list(flatten([[0, 1], [2, 3]])) + [0, 1, 2, 3] + + See also :func:`collapse`, which can flatten multiple levels of nesting. + + """ + return chain.from_iterable(listOfLists) + + +def repeatfunc(func, times=None, *args): + """Call *func* with *args* repeatedly, returning an iterable over the + results. + + If *times* is specified, the iterable will terminate after that many + repetitions: + + >>> from operator import add + >>> times = 4 + >>> args = 3, 5 + >>> list(repeatfunc(add, times, *args)) + [8, 8, 8, 8] + + If *times* is ``None`` the iterable will not terminate: + + >>> from random import randrange + >>> times = None + >>> args = 1, 11 + >>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP + [2, 4, 8, 1, 8, 4] + + """ + if times is None: + return starmap(func, repeat(args)) + return starmap(func, repeat(args, times)) + + +def _pairwise(iterable): + """Returns an iterator of paired items, overlapping, from the original + + >>> take(4, pairwise(count())) + [(0, 1), (1, 2), (2, 3), (3, 4)] + + On Python 3.10 and above, this is an alias for :func:`itertools.pairwise`. + + """ + a, b = tee(iterable) + next(b, None) + yield from zip(a, b) + + +try: + from itertools import pairwise as itertools_pairwise +except ImportError: + pairwise = _pairwise +else: + + def pairwise(iterable): + yield from itertools_pairwise(iterable) + + pairwise.__doc__ = _pairwise.__doc__ + + +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks. + + >>> list(grouper('ABCDEFG', 3, 'x')) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')] + + """ + if isinstance(iterable, int): + warnings.warn( + "grouper expects iterable as first parameter", DeprecationWarning + ) + n, iterable = iterable, n + args = [iter(iterable)] * n + return zip_longest(fillvalue=fillvalue, *args) + + +def roundrobin(*iterables): + """Yields an item from each iterable, alternating between them. + + >>> list(roundrobin('ABC', 'D', 'EF')) + ['A', 'D', 'E', 'B', 'F', 'C'] + + This function produces the same output as :func:`interleave_longest`, but + may perform better for some inputs (in particular when the number of + iterables is small). + + """ + # Recipe credited to George Sakkis + pending = len(iterables) + nexts = cycle(iter(it).__next__ for it in iterables) + while pending: + try: + for next in nexts: + yield next() + except StopIteration: + pending -= 1 + nexts = cycle(islice(nexts, pending)) + + +def partition(pred, iterable): + """ + Returns a 2-tuple of iterables derived from the input iterable. + The first yields the items that have ``pred(item) == False``. + The second yields the items that have ``pred(item) == True``. + + >>> is_odd = lambda x: x % 2 != 0 + >>> iterable = range(10) + >>> even_items, odd_items = partition(is_odd, iterable) + >>> list(even_items), list(odd_items) + ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9]) + + If *pred* is None, :func:`bool` is used. + + >>> iterable = [0, 1, False, True, '', ' '] + >>> false_items, true_items = partition(None, iterable) + >>> list(false_items), list(true_items) + ([0, False, ''], [1, True, ' ']) + + """ + if pred is None: + pred = bool + + evaluations = ((pred(x), x) for x in iterable) + t1, t2 = tee(evaluations) + return ( + (x for (cond, x) in t1 if not cond), + (x for (cond, x) in t2 if cond), + ) + + +def powerset(iterable): + """Yields all possible subsets of the iterable. + + >>> list(powerset([1, 2, 3])) + [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] + + :func:`powerset` will operate on iterables that aren't :class:`set` + instances, so repeated elements in the input will produce repeated elements + in the output. Use :func:`unique_everseen` on the input to avoid generating + duplicates: + + >>> seq = [1, 1, 0] + >>> list(powerset(seq)) + [(), (1,), (1,), (0,), (1, 1), (1, 0), (1, 0), (1, 1, 0)] + >>> from more_itertools import unique_everseen + >>> list(powerset(unique_everseen(seq))) + [(), (1,), (0,), (1, 0)] + + """ + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def unique_everseen(iterable, key=None): + """ + Yield unique elements, preserving order. + + >>> list(unique_everseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D'] + >>> list(unique_everseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'D'] + + Sequences with a mix of hashable and unhashable items can be used. + The function will be slower (i.e., `O(n^2)`) for unhashable items. + + Remember that ``list`` objects are unhashable - you can use the *key* + parameter to transform the list to a tuple (which is hashable) to + avoid a slowdown. + + >>> iterable = ([1, 2], [2, 3], [1, 2]) + >>> list(unique_everseen(iterable)) # Slow + [[1, 2], [2, 3]] + >>> list(unique_everseen(iterable, key=tuple)) # Faster + [[1, 2], [2, 3]] + + Similary, you may want to convert unhashable ``set`` objects with + ``key=frozenset``. For ``dict`` objects, + ``key=lambda x: frozenset(x.items())`` can be used. + + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seenset: + seenset_add(k) + yield element + except TypeError: + if k not in seenlist: + seenlist_add(k) + yield element + + +def unique_justseen(iterable, key=None): + """Yields elements in order, ignoring serial duplicates + + >>> list(unique_justseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D', 'A', 'B'] + >>> list(unique_justseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'A', 'D'] + + """ + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + + +def iter_except(func, exception, first=None): + """Yields results from a function repeatedly until an exception is raised. + + Converts a call-until-exception interface to an iterator interface. + Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel + to end the loop. + + >>> l = [0, 1, 2] + >>> list(iter_except(l.pop, IndexError)) + [2, 1, 0] + + """ + try: + if first is not None: + yield first() + while 1: + yield func() + except exception: + pass + + +def first_true(iterable, default=None, pred=None): + """ + Returns the first true value in the iterable. + + If no true value is found, returns *default* + + If *pred* is not None, returns the first item for which + ``pred(item) == True`` . + + >>> first_true(range(10)) + 1 + >>> first_true(range(10), pred=lambda x: x > 5) + 6 + >>> first_true(range(10), default='missing', pred=lambda x: x > 9) + 'missing' + + """ + return next(filter(pred, iterable), default) + + +def random_product(*args, repeat=1): + """Draw an item at random from each of the input iterables. + + >>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP + ('c', 3, 'Z') + + If *repeat* is provided as a keyword argument, that many items will be + drawn from each iterable. + + >>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP + ('a', 2, 'd', 3) + + This equivalent to taking a random selection from + ``itertools.product(*args, **kwarg)``. + + """ + pools = [tuple(pool) for pool in args] * repeat + return tuple(choice(pool) for pool in pools) + + +def random_permutation(iterable, r=None): + """Return a random *r* length permutation of the elements in *iterable*. + + If *r* is not specified or is ``None``, then *r* defaults to the length of + *iterable*. + + >>> random_permutation(range(5)) # doctest:+SKIP + (3, 4, 0, 1, 2) + + This equivalent to taking a random selection from + ``itertools.permutations(iterable, r)``. + + """ + pool = tuple(iterable) + r = len(pool) if r is None else r + return tuple(sample(pool, r)) + + +def random_combination(iterable, r): + """Return a random *r* length subsequence of the elements in *iterable*. + + >>> random_combination(range(5), 3) # doctest:+SKIP + (2, 3, 4) + + This equivalent to taking a random selection from + ``itertools.combinations(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(sample(range(n), r)) + return tuple(pool[i] for i in indices) + + +def random_combination_with_replacement(iterable, r): + """Return a random *r* length subsequence of elements in *iterable*, + allowing individual elements to be repeated. + + >>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP + (0, 0, 1, 2, 2) + + This equivalent to taking a random selection from + ``itertools.combinations_with_replacement(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(randrange(n) for i in range(r)) + return tuple(pool[i] for i in indices) + + +def nth_combination(iterable, r, index): + """Equivalent to ``list(combinations(iterable, r))[index]``. + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`nth_combination` computes the subsequence at + sort position *index* directly, without computing the previous + subsequences. + + >>> nth_combination(range(5), 3, 5) + (0, 3, 4) + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = tuple(iterable) + n = len(pool) + if (r < 0) or (r > n): + raise ValueError + + c = 1 + k = min(r, n - r) + for i in range(1, k + 1): + c = c * (n - k + i) // i + + if index < 0: + index += c + + if (index < 0) or (index >= c): + raise IndexError + + result = [] + while r: + c, n, r = c * r // n, n - 1, r - 1 + while index >= c: + index -= c + c, n = c * (n - r) // n, n - 1 + result.append(pool[-1 - n]) + + return tuple(result) + + +def prepend(value, iterator): + """Yield *value*, followed by the elements in *iterator*. + + >>> value = '0' + >>> iterator = ['1', '2', '3'] + >>> list(prepend(value, iterator)) + ['0', '1', '2', '3'] + + To prepend multiple values, see :func:`itertools.chain` + or :func:`value_chain`. + + """ + return chain([value], iterator) + + +def convolve(signal, kernel): + """Convolve the iterable *signal* with the iterable *kernel*. + + >>> signal = (1, 2, 3, 4, 5) + >>> kernel = [3, 2, 1] + >>> list(convolve(signal, kernel)) + [3, 8, 14, 20, 26, 14, 5] + + Note: the input arguments are not interchangeable, as the *kernel* + is immediately consumed and stored. + + """ + kernel = tuple(kernel)[::-1] + n = len(kernel) + window = deque([0], maxlen=n) * n + for x in chain(signal, repeat(0, n - 1)): + window.append(x) + yield sum(map(operator.mul, kernel, window)) diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/ordered_set.py b/phivenv/Lib/site-packages/setuptools/_vendor/ordered_set.py new file mode 100644 index 0000000000000000000000000000000000000000..14876000de895a609d5b9f3de39c3c8fc44ef1fc --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/ordered_set.py @@ -0,0 +1,488 @@ +""" +An OrderedSet is a custom MutableSet that remembers its order, so that every +entry has an index that can be looked up. + +Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, +and released under the MIT license. +""" +import itertools as it +from collections import deque + +try: + # Python 3 + from collections.abc import MutableSet, Sequence +except ImportError: + # Python 2.7 + from collections import MutableSet, Sequence + +SLICE_ALL = slice(None) +__version__ = "3.1" + + +def is_iterable(obj): + """ + Are we being asked to look up a list of things, instead of a single thing? + We check for the `__iter__` attribute so that this can cover types that + don't have to be known by this module, such as NumPy arrays. + + Strings, however, should be considered as atomic values to look up, not + iterables. The same goes for tuples, since they are immutable and therefore + valid entries. + + We don't need to check for the Python 2 `unicode` type, because it doesn't + have an `__iter__` attribute anyway. + """ + return ( + hasattr(obj, "__iter__") + and not isinstance(obj, str) + and not isinstance(obj, tuple) + ) + + +class OrderedSet(MutableSet, Sequence): + """ + An OrderedSet is a custom MutableSet that remembers its order, so that + every entry has an index that can be looked up. + + Example: + >>> OrderedSet([1, 1, 2, 3, 2]) + OrderedSet([1, 2, 3]) + """ + + def __init__(self, iterable=None): + self.items = [] + self.map = {} + if iterable is not None: + self |= iterable + + def __len__(self): + """ + Returns the number of unique elements in the ordered set + + Example: + >>> len(OrderedSet([])) + 0 + >>> len(OrderedSet([1, 2])) + 2 + """ + return len(self.items) + + def __getitem__(self, index): + """ + Get the item at a given index. + + If `index` is a slice, you will get back that slice of items, as a + new OrderedSet. + + If `index` is a list or a similar iterable, you'll get a list of + items corresponding to those indices. This is similar to NumPy's + "fancy indexing". The result is not an OrderedSet because you may ask + for duplicate indices, and the number of elements returned should be + the number of elements asked for. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset[1] + 2 + """ + if isinstance(index, slice) and index == SLICE_ALL: + return self.copy() + elif is_iterable(index): + return [self.items[i] for i in index] + elif hasattr(index, "__index__") or isinstance(index, slice): + result = self.items[index] + if isinstance(result, list): + return self.__class__(result) + else: + return result + else: + raise TypeError("Don't know how to index an OrderedSet by %r" % index) + + def copy(self): + """ + Return a shallow copy of this object. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> other = this.copy() + >>> this == other + True + >>> this is other + False + """ + return self.__class__(self) + + def __getstate__(self): + if len(self) == 0: + # The state can't be an empty list. + # We need to return a truthy value, or else __setstate__ won't be run. + # + # This could have been done more gracefully by always putting the state + # in a tuple, but this way is backwards- and forwards- compatible with + # previous versions of OrderedSet. + return (None,) + else: + return list(self) + + def __setstate__(self, state): + if state == (None,): + self.__init__([]) + else: + self.__init__(state) + + def __contains__(self, key): + """ + Test if the item is in this ordered set + + Example: + >>> 1 in OrderedSet([1, 3, 2]) + True + >>> 5 in OrderedSet([1, 3, 2]) + False + """ + return key in self.map + + def add(self, key): + """ + Add `key` as an item to this OrderedSet, then return its index. + + If `key` is already in the OrderedSet, return the index it already + had. + + Example: + >>> oset = OrderedSet() + >>> oset.append(3) + 0 + >>> print(oset) + OrderedSet([3]) + """ + if key not in self.map: + self.map[key] = len(self.items) + self.items.append(key) + return self.map[key] + + append = add + + def update(self, sequence): + """ + Update the set with the given iterable sequence, then return the index + of the last element inserted. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.update([3, 1, 5, 1, 4]) + 4 + >>> print(oset) + OrderedSet([1, 2, 3, 5, 4]) + """ + item_index = None + try: + for item in sequence: + item_index = self.add(item) + except TypeError: + raise ValueError( + "Argument needs to be an iterable, got %s" % type(sequence) + ) + return item_index + + def index(self, key): + """ + Get the index of a given entry, raising an IndexError if it's not + present. + + `key` can be an iterable of entries that is not a string, in which case + this returns a list of indices. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.index(2) + 1 + """ + if is_iterable(key): + return [self.index(subkey) for subkey in key] + return self.map[key] + + # Provide some compatibility with pd.Index + get_loc = index + get_indexer = index + + def pop(self): + """ + Remove and return the last element from the set. + + Raises KeyError if the set is empty. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.pop() + 3 + """ + if not self.items: + raise KeyError("Set is empty") + + elem = self.items[-1] + del self.items[-1] + del self.map[elem] + return elem + + def discard(self, key): + """ + Remove an element. Do not raise an exception if absent. + + The MutableSet mixin uses this to implement the .remove() method, which + *does* raise an error when asked to remove a non-existent item. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + """ + if key in self: + i = self.map[key] + del self.items[i] + del self.map[key] + for k, v in self.map.items(): + if v >= i: + self.map[k] = v - 1 + + def clear(self): + """ + Remove all items from this OrderedSet. + """ + del self.items[:] + self.map.clear() + + def __iter__(self): + """ + Example: + >>> list(iter(OrderedSet([1, 2, 3]))) + [1, 2, 3] + """ + return iter(self.items) + + def __reversed__(self): + """ + Example: + >>> list(reversed(OrderedSet([1, 2, 3]))) + [3, 2, 1] + """ + return reversed(self.items) + + def __repr__(self): + if not self: + return "%s()" % (self.__class__.__name__,) + return "%s(%r)" % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + """ + Returns true if the containers have the same items. If `other` is a + Sequence, then order is checked, otherwise it is ignored. + + Example: + >>> oset = OrderedSet([1, 3, 2]) + >>> oset == [1, 3, 2] + True + >>> oset == [1, 2, 3] + False + >>> oset == [2, 3] + False + >>> oset == OrderedSet([3, 2, 1]) + False + """ + # In Python 2 deque is not a Sequence, so treat it as one for + # consistent behavior with Python 3. + if isinstance(other, (Sequence, deque)): + # Check that this OrderedSet contains the same elements, in the + # same order, as the other object. + return list(self) == list(other) + try: + other_as_set = set(other) + except TypeError: + # If `other` can't be converted into a set, it's not equal. + return False + else: + return set(self) == other_as_set + + def union(self, *sets): + """ + Combines all unique items. + Each items order is defined by its first appearance. + + Example: + >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) + >>> print(oset) + OrderedSet([3, 1, 4, 5, 2, 0]) + >>> oset.union([8, 9]) + OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) + >>> oset | {10} + OrderedSet([3, 1, 4, 5, 2, 0, 10]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + containers = map(list, it.chain([self], sets)) + items = it.chain.from_iterable(containers) + return cls(items) + + def __and__(self, other): + # the parent implementation of this is backwards + return self.intersection(other) + + def intersection(self, *sets): + """ + Returns elements in common between all sets. Order is defined only + by the first set. + + Example: + >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) + >>> print(oset) + OrderedSet([1, 2, 3]) + >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) + OrderedSet([2]) + >>> oset.intersection() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + if sets: + common = set.intersection(*map(set, sets)) + items = (item for item in self if item in common) + else: + items = self + return cls(items) + + def difference(self, *sets): + """ + Returns all elements that are in this set but not the others. + + Example: + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) + OrderedSet([1]) + >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ + if sets: + other = set.union(*map(set, sets)) + items = (item for item in self if item not in other) + else: + items = self + return cls(items) + + def issubset(self, other): + """ + Report whether another set contains this set. + + Example: + >>> OrderedSet([1, 2, 3]).issubset({1, 2}) + False + >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) + True + >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) + False + """ + if len(self) > len(other): # Fast check for obvious cases + return False + return all(item in other for item in self) + + def issuperset(self, other): + """ + Report whether this set contains another set. + + Example: + >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) + False + >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) + True + >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) + False + """ + if len(self) < len(other): # Fast check for obvious cases + return False + return all(item in self for item in other) + + def symmetric_difference(self, other): + """ + Return the symmetric difference of two OrderedSets as a new set. + That is, the new set will contain all elements that are in exactly + one of the sets. + + Their order will be preserved, with elements from `self` preceding + elements from `other`. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference(other) + OrderedSet([4, 5, 9, 2]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + diff1 = cls(self).difference(other) + diff2 = cls(other).difference(self) + return diff1.union(diff2) + + def _update_items(self, items): + """ + Replace the 'items' list of this OrderedSet with a new one, updating + self.map accordingly. + """ + self.items = items + self.map = {item: idx for (idx, item) in enumerate(items)} + + def difference_update(self, *sets): + """ + Update this OrderedSet to remove items from one or more other sets. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> this.difference_update(OrderedSet([2, 4])) + >>> print(this) + OrderedSet([1, 3]) + + >>> this = OrderedSet([1, 2, 3, 4, 5]) + >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) + >>> print(this) + OrderedSet([3, 5]) + """ + items_to_remove = set() + for other in sets: + items_to_remove |= set(other) + self._update_items([item for item in self.items if item not in items_to_remove]) + + def intersection_update(self, other): + """ + Update this OrderedSet to keep only items in another set, preserving + their order in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.intersection_update(other) + >>> print(this) + OrderedSet([1, 3, 7]) + """ + other = set(other) + self._update_items([item for item in self.items if item in other]) + + def symmetric_difference_update(self, other): + """ + Update this OrderedSet to remove items from another set, then + add items from the other set that were not present in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference_update(other) + >>> print(this) + OrderedSet([4, 5, 9, 2]) + """ + items_to_add = [item for item in other if item not in self] + items_to_remove = set(other) + self._update_items( + [item for item in self.items if item not in items_to_remove] + items_to_add + ) diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__about__.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__about__.py new file mode 100644 index 0000000000000000000000000000000000000000..4d998578d7b5d39ae1cd5ce8832e7cd85ed2a1d1 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__about__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "20.4" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "Copyright 2014-2019 %s" % __author__ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__init__.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a0cf67df5245be16a020ca048832e180f7ce8661 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__init__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/__about__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/__about__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2b1fcfef19b411f7698667c2e0b705149081a83 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/__about__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c7b681a4ce87e14769f59be093ca987ef73842a Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_compat.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d21305273d41b56c26e9a2e95b244a84f9eef3fd Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_compat.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_structures.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_structures.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a656f355de8fa4fb889d7379e2062923c3511eaf Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_structures.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_typing.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_typing.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70cac692e153c499722b003cab7c1aeb66359908 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/_typing.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/markers.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/markers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fc7456008482cfa949529ab873971d4b4f1ab5c Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/markers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/requirements.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/requirements.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0f593de3f95d2b5cab2d6dfca86557beec84e76 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/requirements.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/specifiers.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/specifiers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43e5fdbdde0ffbec85c2f40229c91993956118b3 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/specifiers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/tags.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/tags.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e3eae27a437154a63fb45feb426e9e8b1271eed Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/tags.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/utils.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed30d0d2fbac3f1657a76858121ea9162469c1e0 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/utils.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/version.cpython-39.pyc b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf73a54665261d828a839beb0eec9d74810aaeb7 Binary files /dev/null and b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/__pycache__/version.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_compat.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..e54bd4ede8761df5882a3354bc22bdee7a5e8a8b --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_compat.py @@ -0,0 +1,38 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import sys + +from ._typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from typing import Any, Dict, Tuple, Type + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +# flake8: noqa + +if PY3: + string_types = (str,) +else: + string_types = (basestring,) + + +def with_metaclass(meta, *bases): + # type: (Type[Any], Tuple[Type[Any], ...]) -> Any + """ + Create a base class with a metaclass. + """ + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): # type: ignore + def __new__(cls, name, this_bases, d): + # type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any + return meta(name, bases, d) + + return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_structures.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_structures.py new file mode 100644 index 0000000000000000000000000000000000000000..800d5c5588c99dc216cdea5084da440efb641945 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_structures.py @@ -0,0 +1,86 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + + +class InfinityType(object): + def __repr__(self): + # type: () -> str + return "Infinity" + + def __hash__(self): + # type: () -> int + return hash(repr(self)) + + def __lt__(self, other): + # type: (object) -> bool + return False + + def __le__(self, other): + # type: (object) -> bool + return False + + def __eq__(self, other): + # type: (object) -> bool + return isinstance(other, self.__class__) + + def __ne__(self, other): + # type: (object) -> bool + return not isinstance(other, self.__class__) + + def __gt__(self, other): + # type: (object) -> bool + return True + + def __ge__(self, other): + # type: (object) -> bool + return True + + def __neg__(self): + # type: (object) -> NegativeInfinityType + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType(object): + def __repr__(self): + # type: () -> str + return "-Infinity" + + def __hash__(self): + # type: () -> int + return hash(repr(self)) + + def __lt__(self, other): + # type: (object) -> bool + return True + + def __le__(self, other): + # type: (object) -> bool + return True + + def __eq__(self, other): + # type: (object) -> bool + return isinstance(other, self.__class__) + + def __ne__(self, other): + # type: (object) -> bool + return not isinstance(other, self.__class__) + + def __gt__(self, other): + # type: (object) -> bool + return False + + def __ge__(self, other): + # type: (object) -> bool + return False + + def __neg__(self): + # type: (object) -> InfinityType + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_typing.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_typing.py new file mode 100644 index 0000000000000000000000000000000000000000..77a8b9185a07d0338e652810f48757dfe9e0c90c --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/_typing.py @@ -0,0 +1,48 @@ +"""For neatly implementing static typing in packaging. + +`mypy` - the static type analysis tool we use - uses the `typing` module, which +provides core functionality fundamental to mypy's functioning. + +Generally, `typing` would be imported at runtime and used in that fashion - +it acts as a no-op at runtime and does not have any run-time overhead by +design. + +As it turns out, `typing` is not vendorable - it uses separate sources for +Python 2/Python 3. Thus, this codebase can not expect it to be present. +To work around this, mypy allows the typing import to be behind a False-y +optional to prevent it from running at runtime and type-comments can be used +to remove the need for the types to be accessible directly during runtime. + +This module provides the False-y guard in a nicely named fashion so that a +curious maintainer can reach here to read this. + +In packaging, all static-typing related imports should be guarded as follows: + + from packaging._typing import TYPE_CHECKING + + if TYPE_CHECKING: + from typing import ... + +Ref: https://github.com/python/mypy/issues/3216 +""" + +__all__ = ["TYPE_CHECKING", "cast"] + +# The TYPE_CHECKING constant defined by the typing module is False at runtime +# but True while type checking. +if False: # pragma: no cover + from typing import TYPE_CHECKING +else: + TYPE_CHECKING = False + +# typing's cast syntax requires calling typing.cast at runtime, but we don't +# want to import typing at runtime. Here, we inform the type checkers that +# we're importing `typing.cast` as `cast` and re-implement typing.cast's +# runtime behavior in a block that is ignored by type checkers. +if TYPE_CHECKING: # pragma: no cover + # not executed at runtime + from typing import cast +else: + # executed at runtime + def cast(type_, value): # noqa + return value diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/markers.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/markers.py new file mode 100644 index 0000000000000000000000000000000000000000..03fbdfcc944ef6f969744134957a428d19c743ca --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/markers.py @@ -0,0 +1,328 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import operator +import os +import platform +import sys + +from setuptools.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd +from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString +from setuptools.extern.pyparsing import Literal as L # noqa + +from ._compat import string_types +from ._typing import TYPE_CHECKING +from .specifiers import Specifier, InvalidSpecifier + +if TYPE_CHECKING: # pragma: no cover + from typing import Any, Callable, Dict, List, Optional, Tuple, Union + + Operator = Callable[[str, str], bool] + + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node(object): + def __init__(self, value): + # type: (Any) -> None + self.value = value + + def __str__(self): + # type: () -> str + return str(self.value) + + def __repr__(self): + # type: () -> str + return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + + def serialize(self): + # type: () -> str + raise NotImplementedError + + +class Variable(Node): + def serialize(self): + # type: () -> str + return str(self) + + +class Value(Node): + def serialize(self): + # type: () -> str + return '"{0}"'.format(self) + + +class Op(Node): + def serialize(self): + # type: () -> str + return str(self) + + +VARIABLE = ( + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") # PEP-345 + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # undocumented setuptools legacy + | L("extra") # PEP-508 +) +ALIASES = { + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results): + # type: (Union[ParseResults, List[Any]]) -> List[Any] + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker(marker, first=True): + # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str + + assert isinstance(marker, (list, tuple, string_types)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} # type: Dict[str, Operator] + + +def _eval_op(lhs, op, rhs): + # type: (str, Op, str) -> bool + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper = _operators.get(op.serialize()) # type: Optional[Operator] + if oper is None: + raise UndefinedComparison( + "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) + ) + + return oper(lhs, rhs) + + +class Undefined(object): + pass + + +_undefined = Undefined() + + +def _get_env(environment, name): + # type: (Dict[str, str], str) -> str + value = environment.get(name, _undefined) # type: Union[str, Undefined] + + if isinstance(value, Undefined): + raise UndefinedEnvironmentName( + "{0!r} does not exist in evaluation environment.".format(name) + ) + + return value + + +def _evaluate_markers(markers, environment): + # type: (List[Any], Dict[str, str]) -> bool + groups = [[]] # type: List[List[bool]] + + for marker in markers: + assert isinstance(marker, (list, tuple, string_types)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info): + # type: (sys._version_info) -> str + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment(): + # type: () -> Dict[str, str] + if hasattr(sys, "implementation"): + # Ignoring the `sys.implementation` reference for type checking due to + # mypy not liking that the attribute doesn't exist in Python 2.7 when + # run with the `--py27` flag. + iver = format_full_version(sys.implementation.version) # type: ignore + implementation_name = sys.implementation.name # type: ignore + else: + iver = "0" + implementation_name = "" + + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker(object): + def __init__(self, marker): + # type: (str) -> None + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( + marker, marker[e.loc : e.loc + 8] + ) + raise InvalidMarker(err_str) + + def __str__(self): + # type: () -> str + return _format_marker(self._markers) + + def __repr__(self): + # type: () -> str + return "".format(str(self)) + + def evaluate(self, environment=None): + # type: (Optional[Dict[str, str]]) -> bool + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/requirements.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/requirements.py new file mode 100644 index 0000000000000000000000000000000000000000..5d50c7d7e20c8b390edc0e6a2c362161641117d4 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/requirements.py @@ -0,0 +1,145 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import string +import re + +from setuptools.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException +from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine +from setuptools.extern.pyparsing import Literal as L # noqa +from urllib import parse as urlparse + +from ._typing import TYPE_CHECKING +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + +if TYPE_CHECKING: # pragma: no cover + from typing import List + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r"[^ ]+")("url") +URL = AT + URI + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") +_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start : t._original_end]) +) +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") + + +class Requirement(object): + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string): + # type: (str) -> None + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + 'Parse error at "{0!r}": {1}'.format( + requirement_string[e.loc : e.loc + 8], e.msg + ) + ) + + self.name = req.name + if req.url: + parsed_url = urlparse.urlparse(req.url) + if parsed_url.scheme == "file": + if urlparse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement("Invalid URL: {0}".format(req.url)) + self.url = req.url + else: + self.url = None + self.extras = set(req.extras.asList() if req.extras else []) + self.specifier = SpecifierSet(req.specifier) + self.marker = req.marker if req.marker else None + + def __str__(self): + # type: () -> str + parts = [self.name] # type: List[str] + + if self.extras: + parts.append("[{0}]".format(",".join(sorted(self.extras)))) + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append("@ {0}".format(self.url)) + if self.marker: + parts.append(" ") + + if self.marker: + parts.append("; {0}".format(self.marker)) + + return "".join(parts) + + def __repr__(self): + # type: () -> str + return "".format(str(self)) diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/specifiers.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/specifiers.py new file mode 100644 index 0000000000000000000000000000000000000000..fe09bb1dbb22f7670d33fe4b86ac45e207cc7eb1 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/specifiers.py @@ -0,0 +1,863 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import abc +import functools +import itertools +import re + +from ._compat import string_types, with_metaclass +from ._typing import TYPE_CHECKING +from .utils import canonicalize_version +from .version import Version, LegacyVersion, parse + +if TYPE_CHECKING: # pragma: no cover + from typing import ( + List, + Dict, + Union, + Iterable, + Iterator, + Optional, + Callable, + Tuple, + FrozenSet, + ) + + ParsedVersion = Union[Version, LegacyVersion] + UnparsedVersion = Union[Version, LegacyVersion, str] + CallableOperator = Callable[[ParsedVersion, str], bool] + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore + @abc.abstractmethod + def __str__(self): + # type: () -> str + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self): + # type: () -> int + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other): + # type: (object) -> bool + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other): + # type: (object) -> bool + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self): + # type: () -> Optional[bool] + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item, prereleases=None): + # type: (str, Optional[bool]) -> bool + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter(self, iterable, prereleases=None): + # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators = {} # type: Dict[str, str] + + def __init__(self, spec="", prereleases=None): + # type: (str, Optional[bool]) -> None + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + + self._spec = ( + match.group("operator").strip(), + match.group("version").strip(), + ) # type: Tuple[str, str] + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self): + # type: () -> str + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + + def __str__(self): + # type: () -> str + return "{0}{1}".format(*self._spec) + + @property + def _canonical_spec(self): + # type: () -> Tuple[str, Union[Version, str]] + return self._spec[0], canonicalize_version(self._spec[1]) + + def __hash__(self): + # type: () -> int + return hash(self._canonical_spec) + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, string_types): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def __ne__(self, other): + # type: (object) -> bool + if isinstance(other, string_types): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op): + # type: (str) -> CallableOperator + operator_callable = getattr( + self, "_compare_{0}".format(self._operators[op]) + ) # type: CallableOperator + return operator_callable + + def _coerce_version(self, version): + # type: (UnparsedVersion) -> ParsedVersion + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self): + # type: () -> str + return self._spec[0] + + @property + def version(self): + # type: () -> str + return self._spec[1] + + @property + def prereleases(self): + # type: () -> Optional[bool] + return self._prereleases + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + self._prereleases = value + + def __contains__(self, item): + # type: (str) -> bool + return self.contains(item) + + def contains(self, item, prereleases=None): + # type: (UnparsedVersion, Optional[bool]) -> bool + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + normalized_item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable = self._get_operator(self.operator) # type: CallableOperator + return operator_callable(normalized_item, self.version) + + def filter(self, iterable, prereleases=None): + # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later incase nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(==|!=|<=|>=|<|>)) + \s* + (?P + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def _coerce_version(self, version): + # type: (Union[ParsedVersion, str]) -> LegacyVersion + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective, spec): + # type: (LegacyVersion, str) -> bool + return prospective > self._coerce_version(spec) + + +def _require_version_compare( + fn # type: (Callable[[Specifier, ParsedVersion, str], bool]) +): + # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool] + @functools.wraps(fn) + def wrapped(self, prospective, spec): + # type: (Specifier, ParsedVersion, str) -> bool + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore post and dev releases and we want to treat the pre-release as + # it's own separate segment. + prefix = ".".join( + list( + itertools.takewhile( + lambda x: (not x.startswith("post") and not x.startswith("dev")), + _version_split(spec), + ) + )[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + @_require_version_compare + def _compare_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + split_spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + split_prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = split_prospective[: len(split_spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + padded_spec, padded_prospective = _pad_version( + split_spec, shortened_prospective + ) + + return padded_prospective == padded_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + @_require_version_compare + def _compare_not_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal(self, prospective, spec): + # type: (ParsedVersion, str) -> bool + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective, spec_str): + # type: (ParsedVersion, str) -> bool + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective, spec_str): + # type: (ParsedVersion, str) -> bool + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective, spec): + # type: (Version, str) -> bool + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self): + # type: () -> bool + + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version): + # type: (str) -> List[str] + result = [] # type: List[str] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _pad_version(left, right): + # type: (List[str], List[str]) -> Tuple[List[str], List[str]] + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + + +class SpecifierSet(BaseSpecifier): + def __init__(self, specifiers="", prereleases=None): + # type: (str, Optional[bool]) -> None + + # Split on , to break each individual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed = set() + for specifier in split_specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self): + # type: () -> str + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "".format(str(self), pre) + + def __str__(self): + # type: () -> str + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self): + # type: () -> int + return hash(self._specs) + + def __and__(self, other): + # type: (Union[SpecifierSet, str]) -> SpecifierSet + if isinstance(other, string_types): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, (string_types, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other): + # type: (object) -> bool + if isinstance(other, (string_types, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self): + # type: () -> int + return len(self._specs) + + def __iter__(self): + # type: () -> Iterator[FrozenSet[_IndividualSpecifier]] + return iter(self._specs) + + @property + def prereleases(self): + # type: () -> Optional[bool] + + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value): + # type: (bool) -> None + self._prereleases = value + + def __contains__(self, item): + # type: (Union[ParsedVersion, str]) -> bool + return self.contains(item) + + def contains(self, item, prereleases=None): + # type: (Union[ParsedVersion, str], Optional[bool]) -> bool + + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, + iterable, # type: Iterable[Union[ParsedVersion, str]] + prereleases=None, # type: Optional[bool] + ): + # type: (...) -> Iterable[Union[ParsedVersion, str]] + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered = [] # type: List[Union[ParsedVersion, str]] + found_prereleases = [] # type: List[Union[ParsedVersion, str]] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/tags.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..9064910b8bafe2d60ce5fca8897226f5e0fb8f8f --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/tags.py @@ -0,0 +1,751 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import + +import distutils.util + +try: + from importlib.machinery import EXTENSION_SUFFIXES +except ImportError: # pragma: no cover + import imp + + EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] + del imp +import logging +import os +import platform +import re +import struct +import sys +import sysconfig +import warnings + +from ._typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: # pragma: no cover + from typing import ( + Dict, + FrozenSet, + IO, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + ) + + PythonVersion = Sequence[int] + MacVersion = Tuple[int, int] + GlibcVersion = Tuple[int, int] + + +logger = logging.getLogger(__name__) + +INTERPRETER_SHORT_NAMES = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} # type: Dict[str, str] + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag(object): + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform"] + + def __init__(self, interpreter, abi, platform): + # type: (str, str, str) -> None + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + + @property + def interpreter(self): + # type: () -> str + return self._interpreter + + @property + def abi(self): + # type: () -> str + return self._abi + + @property + def platform(self): + # type: () -> str + return self._platform + + def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self.platform == other.platform) + and (self.abi == other.abi) + and (self.interpreter == other.interpreter) + ) + + def __hash__(self): + # type: () -> int + return hash((self._interpreter, self._abi, self._platform)) + + def __str__(self): + # type: () -> str + return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + + def __repr__(self): + # type: () -> str + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag): + # type: (str) -> FrozenSet[Tag] + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _warn_keyword_parameter(func_name, kwargs): + # type: (str, Dict[str, bool]) -> bool + """ + Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. + """ + if not kwargs: + return False + elif len(kwargs) > 1 or "warn" not in kwargs: + kwargs.pop("warn", None) + arg = next(iter(kwargs.keys())) + raise TypeError( + "{}() got an unexpected keyword argument {!r}".format(func_name, arg) + ) + return kwargs["warn"] + + +def _get_config_var(name, warn=False): + # type: (str, bool) -> Union[int, str, None] + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string): + # type: (str) -> str + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version): + # type: (PythonVersion) -> bool + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version, warn=False): + # type: (PythonVersion, bool) -> List[str] + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append("cp{version}".format(version=version)) + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version=None, # type: Optional[PythonVersion] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + warn = _warn_keyword_parameter("cpython_tags", kwargs) + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or _platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi(): + # type: () -> Iterator[str] + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter=None, # type: Optional[str] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + warn = _warn_keyword_parameter("generic_tags", kwargs) + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or _platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version): + # type: (PythonVersion) -> Iterator[str] + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version=None, # type: Optional[PythonVersion] + interpreter=None, # type: Optional[str] + platforms=None, # type: Optional[Iterable[str]] +): + # type: (...) -> Iterator[Tag] + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or _platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): + # type: (str, bool) -> str + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version, cpu_arch): + # type: (MacVersion, str) -> List[str] + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + formats.append("universal") + return formats + + +def mac_platforms(version=None, arch=None): + # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() # type: ignore + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + for minor_version in range(version[1], -1, -1): + compat_version = version[0], minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +# From PEP 513. +def _is_manylinux_compatible(name, glibc_version): + # type: (str, GlibcVersion) -> bool + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + + return bool(getattr(_manylinux, name + "_compatible")) + except (ImportError, AttributeError): + # Fall through to heuristic check below. + pass + + return _have_compatible_glibc(*glibc_version) + + +def _glibc_version_string(): + # type: () -> Optional[str] + # Returns glibc version string, or None if not using glibc. + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _glibc_version_string_confstr(): + # type: () -> Optional[str] + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821 + "CS_GNU_LIBC_VERSION" + ) + assert version_string is not None + _, version = version_string.split() # type: Tuple[str, str] + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes(): + # type: () -> Optional[str] + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # Note: typeshed is wrong here so we are ignoring this line. + process_namespace = ctypes.CDLL(None) # type: ignore + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() # type: str + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing. +def _check_glibc_version(version_str, required_major, minimum_minor): + # type: (str, int, int) -> bool + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return False + return ( + int(m.group("major")) == required_major + and int(m.group("minor")) >= minimum_minor + ) + + +def _have_compatible_glibc(required_major, minimum_minor): + # type: (int, int) -> bool + version_str = _glibc_version_string() + if version_str is None: + return False + return _check_glibc_version(version_str, required_major, minimum_minor) + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader(object): + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file): + # type: (IO[bytes]) -> None + def unpack(fmt): + # type: (str) -> int + try: + (result,) = struct.unpack( + fmt, file.read(struct.calcsize(fmt)) + ) # type: (int, ) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "H" + format_i = "I" + format_q = "Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header(): + # type: () -> Optional[_ELFFileHeader] + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf(): + # type: () -> bool + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686(): + # type: () -> bool + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_manylinux_abi(arch): + # type: (str) -> bool + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return True + + +def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): + # type: (bool) -> Iterator[str] + linux = _normalize_string(distutils.util.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + manylinux_support = [] + _, arch = linux.split("_", 1) + if _have_compatible_manylinux_abi(arch): + if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"}: + manylinux_support.append( + ("manylinux2014", (2, 17)) + ) # CentOS 7 w/ glibc 2.17 (PEP 599) + if arch in {"x86_64", "i686"}: + manylinux_support.append( + ("manylinux2010", (2, 12)) + ) # CentOS 6 w/ glibc 2.12 (PEP 571) + manylinux_support.append( + ("manylinux1", (2, 5)) + ) # CentOS 5 w/ glibc 2.5 (PEP 513) + manylinux_support_iter = iter(manylinux_support) + for name, glibc_version in manylinux_support_iter: + if _is_manylinux_compatible(name, glibc_version): + yield linux.replace("linux", name) + break + # Support for a later manylinux implies support for an earlier version. + for name, _ in manylinux_support_iter: + yield linux.replace("linux", name) + yield linux + + +def _generic_platforms(): + # type: () -> Iterator[str] + yield _normalize_string(distutils.util.get_platform()) + + +def _platform_tags(): + # type: () -> Iterator[str] + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name(): + # type: () -> str + """ + Returns the name of the running interpreter. + """ + try: + name = sys.implementation.name # type: ignore + except AttributeError: # pragma: no cover + # Python 2.7 compatibility. + name = platform.python_implementation().lower() + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(**kwargs): + # type: (bool) -> str + """ + Returns the version of the running interpreter. + """ + warn = _warn_keyword_parameter("interpreter_version", kwargs) + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version): + # type: (PythonVersion) -> str + if any(v >= 10 for v in version): + sep = "_" + else: + sep = "" + return sep.join(map(str, version)) + + +def sys_tags(**kwargs): + # type: (bool) -> Iterator[Tag] + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + warn = _warn_keyword_parameter("sys_tags", kwargs) + + interp_name = interpreter_name() + if interp_name == "cp": + for tag in cpython_tags(warn=warn): + yield tag + else: + for tag in generic_tags(): + yield tag + + for tag in compatible_tags(): + yield tag diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/utils.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..19579c1a0fa38c088a7cbb80950d0c85f5514cca --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/utils.py @@ -0,0 +1,65 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import re + +from ._typing import TYPE_CHECKING, cast +from .version import InvalidVersion, Version + +if TYPE_CHECKING: # pragma: no cover + from typing import NewType, Union + + NormalizedName = NewType("NormalizedName", str) + +_canonicalize_regex = re.compile(r"[-_.]+") + + +def canonicalize_name(name): + # type: (str) -> NormalizedName + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast("NormalizedName", value) + + +def canonicalize_version(_version): + # type: (str) -> Union[Version, str] + """ + This is very similar to Version.__str__, but has one subtle difference + with the way it handles the release segment. + """ + + try: + version = Version(_version) + except InvalidVersion: + # Legacy versions cannot be normalized + return _version + + parts = [] + + # Epoch + if version.epoch != 0: + parts.append("{0}!".format(version.epoch)) + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + + # Pre-release + if version.pre is not None: + parts.append("".join(str(x) for x in version.pre)) + + # Post-release + if version.post is not None: + parts.append(".post{0}".format(version.post)) + + # Development release + if version.dev is not None: + parts.append(".dev{0}".format(version.dev)) + + # Local version segment + if version.local is not None: + parts.append("+{0}".format(version.local)) + + return "".join(parts) diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/packaging/version.py b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/version.py new file mode 100644 index 0000000000000000000000000000000000000000..00371e86a87edfc5f8d1d1352360bfae0cce8e65 --- /dev/null +++ b/phivenv/Lib/site-packages/setuptools/_vendor/packaging/version.py @@ -0,0 +1,535 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import collections +import itertools +import re + +from ._structures import Infinity, NegativeInfinity +from ._typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union + + from ._structures import InfinityType, NegativeInfinityType + + InfiniteTypes = Union[InfinityType, NegativeInfinityType] + PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] + SubLocalType = Union[InfiniteTypes, int, str] + LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], + ] + CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType + ] + LegacyCmpKey = Tuple[int, Tuple[str, ...]] + VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool + ] + +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] + + +_Version = collections.namedtuple( + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] +) + + +def parse(version): + # type: (str) -> Union[LegacyVersion, Version] + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion(object): + _key = None # type: Union[CmpKey, LegacyCmpKey] + + def __hash__(self): + # type: () -> int + return hash(self._key) + + def __lt__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + # type: (object) -> bool + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + # type: (_BaseVersion) -> bool + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + # type: (object) -> bool + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + # type: (object, VersionComparisonMethod) -> Union[bool, NotImplemented] + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + def __init__(self, version): + # type: (str) -> None + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + # type: () -> str + return self._version + + def __repr__(self): + # type: () -> str + return "".format(repr(str(self))) + + @property + def public(self): + # type: () -> str + return self._version + + @property + def base_version(self): + # type: () -> str + return self._version + + @property + def epoch(self): + # type: () -> int + return -1 + + @property + def release(self): + # type: () -> None + return None + + @property + def pre(self): + # type: () -> None + return None + + @property + def post(self): + # type: () -> None + return None + + @property + def dev(self): + # type: () -> None + return None + + @property + def local(self): + # type: () -> None + return None + + @property + def is_prerelease(self): + # type: () -> bool + return False + + @property + def is_postrelease(self): + # type: () -> bool + return False + + @property + def is_devrelease(self): + # type: () -> bool + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s): + # type: (str) -> Iterator[str] + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # type: (str) -> LegacyCmpKey + + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts = [] # type: List[str] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + + return epoch, tuple(parts) + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+
+class Version(_BaseVersion):
+
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+
+    def __init__(self, version):
+        # type: (str) -> None
+
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion("Invalid version: '{0}'".format(version))
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=_parse_letter_version(
+                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+            ),
+            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self):
+        # type: () -> str
+        return "".format(repr(str(self)))
+
+    def __str__(self):
+        # type: () -> str
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append("{0}!".format(self.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(".post{0}".format(self.post))
+
+        # Development release
+        if self.dev is not None:
+            parts.append(".dev{0}".format(self.dev))
+
+        # Local version segment
+        if self.local is not None:
+            parts.append("+{0}".format(self.local))
+
+        return "".join(parts)
+
+    @property
+    def epoch(self):
+        # type: () -> int
+        _epoch = self._version.epoch  # type: int
+        return _epoch
+
+    @property
+    def release(self):
+        # type: () -> Tuple[int, ...]
+        _release = self._version.release  # type: Tuple[int, ...]
+        return _release
+
+    @property
+    def pre(self):
+        # type: () -> Optional[Tuple[str, int]]
+        _pre = self._version.pre  # type: Optional[Tuple[str, int]]
+        return _pre
+
+    @property
+    def post(self):
+        # type: () -> Optional[Tuple[str, int]]
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self):
+        # type: () -> Optional[Tuple[str, int]]
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self):
+        # type: () -> Optional[str]
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self):
+        # type: () -> str
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self):
+        # type: () -> str
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append("{0}!".format(self.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self):
+        # type: () -> bool
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self):
+        # type: () -> bool
+        return self.post is not None
+
+    @property
+    def is_devrelease(self):
+        # type: () -> bool
+        return self.dev is not None
+
+    @property
+    def major(self):
+        # type: () -> int
+        return self.release[0] if len(self.release) >= 1 else 0
+
+    @property
+    def minor(self):
+        # type: () -> int
+        return self.release[1] if len(self.release) >= 2 else 0
+
+    @property
+    def micro(self):
+        # type: () -> int
+        return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+    letter,  # type: str
+    number,  # type: Union[str, bytes, SupportsInt]
+):
+    # type: (...) -> Optional[Tuple[str, int]]
+
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+    if not letter and number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+    return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local):
+    # type: (str) -> Optional[LocalType]
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_separators.split(local)
+        )
+    return None
+
+
+def _cmpkey(
+    epoch,  # type: int
+    release,  # type: Tuple[int, ...]
+    pre,  # type: Optional[Tuple[str, int]]
+    post,  # type: Optional[Tuple[str, int]]
+    dev,  # type: Optional[Tuple[str, int]]
+    local,  # type: Optional[Tuple[SubLocalType]]
+):
+    # type: (...) -> CmpKey
+
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    _release = tuple(
+        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        _pre = NegativeInfinity  # type: PrePostDevType
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        _pre = Infinity
+    else:
+        _pre = pre
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        _post = NegativeInfinity  # type: PrePostDevType
+
+    else:
+        _post = post
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        _dev = Infinity  # type: PrePostDevType
+
+    else:
+        _dev = dev
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        _local = NegativeInfinity  # type: LocalType
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        _local = tuple(
+            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+        )
+
+    return epoch, _release, _pre, _post, _dev, _local
diff --git a/phivenv/Lib/site-packages/setuptools/_vendor/pyparsing.py b/phivenv/Lib/site-packages/setuptools/_vendor/pyparsing.py
new file mode 100644
index 0000000000000000000000000000000000000000..4cae78838708b07f76357f938289fce8a200e98e
--- /dev/null
+++ b/phivenv/Lib/site-packages/setuptools/_vendor/pyparsing.py
@@ -0,0 +1,5742 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2018  Paul T. McGuire
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__doc__ = \
+"""
+pyparsing module - Classes and methods to define and execute parsing grammars
+=============================================================================
+
+The pyparsing module is an alternative approach to creating and executing simple grammars,
+vs. the traditional lex/yacc approach, or the use of regular expressions.  With pyparsing, you
+don't need to learn a new syntax for defining grammars or matching expressions - the parsing module
+provides a library of classes that you use to construct the grammar directly in Python.
+
+Here is a program to parse "Hello, World!" (or any greeting of the form 
+C{", !"}), built up using L{Word}, L{Literal}, and L{And} elements 
+(L{'+'} operator gives L{And} expressions, strings are auto-converted to
+L{Literal} expressions)::
+
+    from pyparsing import Word, alphas
+
+    # define grammar of a greeting
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+
+    hello = "Hello, World!"
+    print (hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the self-explanatory
+class names, and the use of '+', '|' and '^' operators.
+
+The L{ParseResults} object returned from L{ParserElement.parseString} can be accessed as a nested list, a dictionary, or an
+object with named attributes.
+
+The pyparsing module handles some of the problems that are typically vexing when writing text parsers:
+ - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello  ,  World  !", etc.)
+ - quoted strings
+ - embedded comments
+
+
+Getting Started -
+-----------------
+Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
+classes inherit from. Use the docstrings for examples of how to:
+ - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
+ - construct character word-group expressions using the L{Word} class
+ - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
+ - use L{'+'}, L{'|'}, L{'^'}, and L{'&'} operators to combine simple expressions into more complex ones
+ - associate names with your parsed results using L{ParserElement.setResultsName}
+ - find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
+ - find more useful common expressions in the L{pyparsing_common} namespace class
+"""
+
+__version__ = "2.2.1"
+__versionTime__ = "18 Sep 2018 00:49 UTC"
+__author__ = "Paul McGuire "
+
+import string
+from weakref import ref as wkref
+import copy
+import sys
+import warnings
+import re
+import sre_constants
+import collections
+import pprint
+import traceback
+import types
+from datetime import datetime
+
+try:
+    from _thread import RLock
+except ImportError:
+    from threading import RLock
+
+try:
+    # Python 3
+    from collections.abc import Iterable
+    from collections.abc import MutableMapping
+except ImportError:
+    # Python 2.7
+    from collections import Iterable
+    from collections import MutableMapping
+
+try:
+    from collections import OrderedDict as _OrderedDict
+except ImportError:
+    try:
+        from ordereddict import OrderedDict as _OrderedDict
+    except ImportError:
+        _OrderedDict = None
+
+#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
+
+__all__ = [
+'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
+'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
+'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
+'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
+'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
+'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 
+'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore',
+'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
+'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
+'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
+'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
+'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
+'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
+'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 
+'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
+'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
+'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass',
+'CloseMatch', 'tokenMap', 'pyparsing_common',
+]
+
+system_version = tuple(sys.version_info)[:3]
+PY_3 = system_version[0] == 3
+if PY_3:
+    _MAX_INT = sys.maxsize
+    basestring = str
+    unichr = chr
+    _ustr = str
+
+    # build list of single arg builtins, that can be used as parse actions
+    singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
+
+else:
+    _MAX_INT = sys.maxint
+    range = xrange
+
+    def _ustr(obj):
+        """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries
+           str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It
+           then < returns the unicode object | encodes it with the default encoding | ... >.
+        """
+        if isinstance(obj,unicode):
+            return obj
+
+        try:
+            # If this works, then _ustr(obj) has the same behaviour as str(obj), so
+            # it won't break any existing code.
+            return str(obj)
+
+        except UnicodeEncodeError:
+            # Else encode it
+            ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
+            xmlcharref = Regex(r'&#\d+;')
+            xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
+            return xmlcharref.transformString(ret)
+
+    # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
+    singleArgBuiltins = []
+    import __builtin__
+    for fname in "sum len sorted reversed list tuple set any all min max".split():
+        try:
+            singleArgBuiltins.append(getattr(__builtin__,fname))
+        except AttributeError:
+            continue
+            
+_generatorType = type((y for y in range(1)))
+ 
+def _xml_escape(data):
+    """Escape &, <, >, ", ', etc. in a string of data."""
+
+    # ampersand must be replaced first
+    from_symbols = '&><"\''
+    to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split())
+    for from_,to_ in zip(from_symbols, to_symbols):
+        data = data.replace(from_, to_)
+    return data
+
+class _Constants(object):
+    pass
+
+alphas     = string.ascii_uppercase + string.ascii_lowercase
+nums       = "0123456789"
+hexnums    = nums + "ABCDEFabcdef"
+alphanums  = alphas + nums
+_bslash    = chr(92)
+printables = "".join(c for c in string.printable if c not in string.whitespace)
+
+class ParseBaseException(Exception):
+    """base exception class for all parsing runtime exceptions"""
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, pstr, loc=0, msg=None, elem=None ):
+        self.loc = loc
+        if msg is None:
+            self.msg = pstr
+            self.pstr = ""
+        else:
+            self.msg = msg
+            self.pstr = pstr
+        self.parserElement = elem
+        self.args = (pstr, loc, msg)
+
+    @classmethod
+    def _from_exception(cls, pe):
+        """
+        internal factory method to simplify creating one type of ParseException 
+        from another - avoids having __init__ signature conflicts among subclasses
+        """
+        return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
+
+    def __getattr__( self, aname ):
+        """supported attributes by name are:
+            - lineno - returns the line number of the exception text
+            - col - returns the column number of the exception text
+            - line - returns the line containing the exception text
+        """
+        if( aname == "lineno" ):
+            return lineno( self.loc, self.pstr )
+        elif( aname in ("col", "column") ):
+            return col( self.loc, self.pstr )
+        elif( aname == "line" ):
+            return line( self.loc, self.pstr )
+        else:
+            raise AttributeError(aname)
+
+    def __str__( self ):
+        return "%s (at char %d), (line:%d, col:%d)" % \
+                ( self.msg, self.loc, self.lineno, self.column )
+    def __repr__( self ):
+        return _ustr(self)
+    def markInputline( self, markerString = ">!<" ):
+        """Extracts the exception line from the input string, and marks
+           the location of the exception with a special symbol.
+        """
+        line_str = self.line
+        line_column = self.column - 1
+        if markerString:
+            line_str = "".join((line_str[:line_column],
+                                markerString, line_str[line_column:]))
+        return line_str.strip()
+    def __dir__(self):
+        return "lineno col line".split() + dir(type(self))
+
+class ParseException(ParseBaseException):
+    """
+    Exception thrown when parse expressions don't match class;
+    supported attributes by name are:
+     - lineno - returns the line number of the exception text
+     - col - returns the column number of the exception text
+     - line - returns the line containing the exception text
+        
+    Example::
+        try:
+            Word(nums).setName("integer").parseString("ABC")
+        except ParseException as pe:
+            print(pe)
+            print("column: {}".format(pe.col))
+            
+    prints::
+       Expected integer (at char 0), (line:1, col:1)
+        column: 1
+    """
+    pass
+
+class ParseFatalException(ParseBaseException):
+    """user-throwable exception thrown when inconsistent parse content
+       is found; stops all parsing immediately"""
+    pass
+
+class ParseSyntaxException(ParseFatalException):
+    """just like L{ParseFatalException}, but thrown internally when an
+       L{ErrorStop} ('-' operator) indicates that parsing is to stop 
+       immediately because an unbacktrackable syntax error has been found"""
+    pass
+
+#~ class ReparseException(ParseBaseException):
+    #~ """Experimental class - parse actions can raise this exception to cause
+       #~ pyparsing to reparse the input string:
+        #~ - with a modified input string, and/or
+        #~ - with a modified start location
+       #~ Set the values of the ReparseException in the constructor, and raise the
+       #~ exception in a parse action to cause pyparsing to use the new string/location.
+       #~ Setting the values as None causes no change to be made.
+       #~ """
+    #~ def __init_( self, newstring, restartLoc ):
+        #~ self.newParseText = newstring
+        #~ self.reparseLoc = restartLoc
+
+class RecursiveGrammarException(Exception):
+    """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive"""
+    def __init__( self, parseElementList ):
+        self.parseElementTrace = parseElementList
+
+    def __str__( self ):
+        return "RecursiveGrammarException: %s" % self.parseElementTrace
+
+class _ParseResultsWithOffset(object):
+    def __init__(self,p1,p2):
+        self.tup = (p1,p2)
+    def __getitem__(self,i):
+        return self.tup[i]
+    def __repr__(self):
+        return repr(self.tup[0])
+    def setOffset(self,i):
+        self.tup = (self.tup[0],i)
+
+class ParseResults(object):
+    """
+    Structured parse results, to provide multiple means of access to the parsed data:
+       - as a list (C{len(results)})
+       - by list index (C{results[0], results[1]}, etc.)
+       - by attribute (C{results.} - see L{ParserElement.setResultsName})
+
+    Example::
+        integer = Word(nums)
+        date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+        # equivalent form:
+        # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+        # parseString returns a ParseResults object
+        result = date_str.parseString("1999/12/31")
+
+        def test(s, fn=repr):
+            print("%s -> %s" % (s, fn(eval(s))))
+        test("list(result)")
+        test("result[0]")
+        test("result['month']")
+        test("result.day")
+        test("'month' in result")
+        test("'minutes' in result")
+        test("result.dump()", str)
+    prints::
+        list(result) -> ['1999', '/', '12', '/', '31']
+        result[0] -> '1999'
+        result['month'] -> '12'
+        result.day -> '31'
+        'month' in result -> True
+        'minutes' in result -> False
+        result.dump() -> ['1999', '/', '12', '/', '31']
+        - day: 31
+        - month: 12
+        - year: 1999
+    """
+    def __new__(cls, toklist=None, name=None, asList=True, modal=True ):
+        if isinstance(toklist, cls):
+            return toklist
+        retobj = object.__new__(cls)
+        retobj.__doinit = True
+        return retobj
+
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ):
+        if self.__doinit:
+            self.__doinit = False
+            self.__name = None
+            self.__parent = None
+            self.__accumNames = {}
+            self.__asList = asList
+            self.__modal = modal
+            if toklist is None:
+                toklist = []
+            if isinstance(toklist, list):
+                self.__toklist = toklist[:]
+            elif isinstance(toklist, _generatorType):
+                self.__toklist = list(toklist)
+            else:
+                self.__toklist = [toklist]
+            self.__tokdict = dict()
+
+        if name is not None and name:
+            if not modal:
+                self.__accumNames[name] = 0
+            if isinstance(name,int):
+                name = _ustr(name) # will always return a str, but use _ustr for consistency
+            self.__name = name
+            if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])):
+                if isinstance(toklist,basestring):
+                    toklist = [ toklist ]
+                if asList:
+                    if isinstance(toklist,ParseResults):
+                        self[name] = _ParseResultsWithOffset(toklist.copy(),0)
+                    else:
+                        self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0)
+                    self[name].__name = name
+                else:
+                    try:
+                        self[name] = toklist[0]
+                    except (KeyError,TypeError,IndexError):
+                        self[name] = toklist
+
+    def __getitem__( self, i ):
+        if isinstance( i, (int,slice) ):
+            return self.__toklist[i]
+        else:
+            if i not in self.__accumNames:
+                return self.__tokdict[i][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[i] ])
+
+    def __setitem__( self, k, v, isinstance=isinstance ):
+        if isinstance(v,_ParseResultsWithOffset):
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [v]
+            sub = v[0]
+        elif isinstance(k,(int,slice)):
+            self.__toklist[k] = v
+            sub = v
+        else:
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
+            sub = v
+        if isinstance(sub,ParseResults):
+            sub.__parent = wkref(self)
+
+    def __delitem__( self, i ):
+        if isinstance(i,(int,slice)):
+            mylen = len( self.__toklist )
+            del self.__toklist[i]
+
+            # convert int to slice
+            if isinstance(i, int):
+                if i < 0:
+                    i += mylen
+                i = slice(i, i+1)
+            # get removed indices
+            removed = list(range(*i.indices(mylen)))
+            removed.reverse()
+            # fixup indices in token dictionary
+            for name,occurrences in self.__tokdict.items():
+                for j in removed:
+                    for k, (value, position) in enumerate(occurrences):
+                        occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
+        else:
+            del self.__tokdict[i]
+
+    def __contains__( self, k ):
+        return k in self.__tokdict
+
+    def __len__( self ): return len( self.__toklist )
+    def __bool__(self): return ( not not self.__toklist )
+    __nonzero__ = __bool__
+    def __iter__( self ): return iter( self.__toklist )
+    def __reversed__( self ): return iter( self.__toklist[::-1] )
+    def _iterkeys( self ):
+        if hasattr(self.__tokdict, "iterkeys"):
+            return self.__tokdict.iterkeys()
+        else:
+            return iter(self.__tokdict)
+
+    def _itervalues( self ):
+        return (self[k] for k in self._iterkeys())
+            
+    def _iteritems( self ):
+        return ((k, self[k]) for k in self._iterkeys())
+
+    if PY_3:
+        keys = _iterkeys       
+        """Returns an iterator of all named result keys (Python 3.x only)."""
+
+        values = _itervalues
+        """Returns an iterator of all named result values (Python 3.x only)."""
+
+        items = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
+
+    else:
+        iterkeys = _iterkeys
+        """Returns an iterator of all named result keys (Python 2.x only)."""
+
+        itervalues = _itervalues
+        """Returns an iterator of all named result values (Python 2.x only)."""
+
+        iteritems = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
+
+        def keys( self ):
+            """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iterkeys())
+
+        def values( self ):
+            """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.itervalues())
+                
+        def items( self ):
+            """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iteritems())
+
+    def haskeys( self ):
+        """Since keys() returns an iterator, this method is helpful in bypassing
+           code that looks for the existence of any defined results names."""
+        return bool(self.__tokdict)
+        
+    def pop( self, *args, **kwargs):
+        """
+        Removes and returns item at specified index (default=C{last}).
+        Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
+        argument or an integer argument, it will use C{list} semantics
+        and pop tokens from the list of parsed tokens. If passed a 
+        non-integer argument (most likely a string), it will use C{dict}
+        semantics and pop the corresponding value from any defined 
+        results names. A second default return value argument is 
+        supported, just as in C{dict.pop()}.
+
+        Example::
+            def remove_first(tokens):
+                tokens.pop(0)
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
+
+            label = Word(alphas)
+            patt = label("LABEL") + OneOrMore(Word(nums))
+            print(patt.parseString("AAB 123 321").dump())
+
+            # Use pop() in a parse action to remove named result (note that corresponding value is not
+            # removed from list form of results)
+            def remove_LABEL(tokens):
+                tokens.pop("LABEL")
+                return tokens
+            patt.addParseAction(remove_LABEL)
+            print(patt.parseString("AAB 123 321").dump())
+        prints::
+            ['AAB', '123', '321']
+            - LABEL: AAB
+
+            ['AAB', '123', '321']
+        """
+        if not args:
+            args = [-1]
+        for k,v in kwargs.items():
+            if k == 'default':
+                args = (args[0], v)
+            else:
+                raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
+        if (isinstance(args[0], int) or 
+                        len(args) == 1 or 
+                        args[0] in self):
+            index = args[0]
+            ret = self[index]
+            del self[index]
+            return ret
+        else:
+            defaultvalue = args[1]
+            return defaultvalue
+
+    def get(self, key, defaultValue=None):
+        """
+        Returns named result matching the given key, or if there is no
+        such name, then returns the given C{defaultValue} or C{None} if no
+        C{defaultValue} is specified.
+
+        Similar to C{dict.get()}.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            result = date_str.parseString("1999/12/31")
+            print(result.get("year")) # -> '1999'
+            print(result.get("hour", "not specified")) # -> 'not specified'
+            print(result.get("hour")) # -> None
+        """
+        if key in self:
+            return self[key]
+        else:
+            return defaultValue
+
+    def insert( self, index, insStr ):
+        """
+        Inserts new element at location index in the list of parsed tokens.
+        
+        Similar to C{list.insert()}.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+            # use a parse action to insert the parse location in the front of the parsed results
+            def insert_locn(locn, tokens):
+                tokens.insert(0, locn)
+            print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
+        """
+        self.__toklist.insert(index, insStr)
+        # fixup indices in token dictionary
+        for name,occurrences in self.__tokdict.items():
+            for k, (value, position) in enumerate(occurrences):
+                occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
+
+    def append( self, item ):
+        """
+        Add single element to end of ParseResults list of elements.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            
+            # use a parse action to compute the sum of the parsed integers, and add it to the end
+            def append_sum(tokens):
+                tokens.append(sum(map(int, tokens)))
+            print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
+        """
+        self.__toklist.append(item)
+
+    def extend( self, itemseq ):
+        """
+        Add sequence of elements to end of ParseResults list of elements.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            
+            # use a parse action to append the reverse of the matched strings, to make a palindrome
+            def make_palindrome(tokens):
+                tokens.extend(reversed([t[::-1] for t in tokens]))
+                return ''.join(tokens)
+            print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+        """
+        if isinstance(itemseq, ParseResults):
+            self += itemseq
+        else:
+            self.__toklist.extend(itemseq)
+
+    def clear( self ):
+        """
+        Clear all elements and results names.
+        """
+        del self.__toklist[:]
+        self.__tokdict.clear()
+
+    def __getattr__( self, name ):
+        try:
+            return self[name]
+        except KeyError:
+            return ""
+            
+        if name in self.__tokdict:
+            if name not in self.__accumNames:
+                return self.__tokdict[name][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[name] ])
+        else:
+            return ""
+
+    def __add__( self, other ):
+        ret = self.copy()
+        ret += other
+        return ret
+
+    def __iadd__( self, other ):
+        if other.__tokdict:
+            offset = len(self.__toklist)
+            addoffset = lambda a: offset if a<0 else a+offset
+            otheritems = other.__tokdict.items()
+            otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) )
+                                for (k,vlist) in otheritems for v in vlist]
+            for k,v in otherdictitems:
+                self[k] = v
+                if isinstance(v[0],ParseResults):
+                    v[0].__parent = wkref(self)
+            
+        self.__toklist += other.__toklist
+        self.__accumNames.update( other.__accumNames )
+        return self
+
+    def __radd__(self, other):
+        if isinstance(other,int) and other == 0:
+            # useful for merging many ParseResults using sum() builtin
+            return self.copy()
+        else:
+            # this may raise a TypeError - so be it
+            return other + self
+        
+    def __repr__( self ):
+        return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
+
+    def __str__( self ):
+        return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
+
+    def _asStringList( self, sep='' ):
+        out = []
+        for item in self.__toklist:
+            if out and sep:
+                out.append(sep)
+            if isinstance( item, ParseResults ):
+                out += item._asStringList()
+            else:
+                out.append( _ustr(item) )
+        return out
+
+    def asList( self ):
+        """
+        Returns the parse results as a nested list of matching tokens, all converted to strings.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            result = patt.parseString("sldkj lsdkj sldkj")
+            # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+            print(type(result), result) # ->  ['sldkj', 'lsdkj', 'sldkj']
+            
+            # Use asList() to create an actual list
+            result_list = result.asList()
+            print(type(result_list), result_list) # ->  ['sldkj', 'lsdkj', 'sldkj']
+        """
+        return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
+
+    def asDict( self ):
+        """
+        Returns the named parse results as a nested dictionary.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(type(result), repr(result)) # ->  (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+            
+            result_dict = result.asDict()
+            print(type(result_dict), repr(result_dict)) # ->  {'day': '1999', 'year': '12', 'month': '31'}
+
+            # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+            import json
+            print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+            print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
+        """
+        if PY_3:
+            item_fn = self.items
+        else:
+            item_fn = self.iteritems
+            
+        def toItem(obj):
+            if isinstance(obj, ParseResults):
+                if obj.haskeys():
+                    return obj.asDict()
+                else:
+                    return [toItem(v) for v in obj]
+            else:
+                return obj
+                
+        return dict((k,toItem(v)) for k,v in item_fn())
+
+    def copy( self ):
+        """
+        Returns a new copy of a C{ParseResults} object.
+        """
+        ret = ParseResults( self.__toklist )
+        ret.__tokdict = self.__tokdict.copy()
+        ret.__parent = self.__parent
+        ret.__accumNames.update( self.__accumNames )
+        ret.__name = self.__name
+        return ret
+
+    def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ):
+        """
+        (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
+        """
+        nl = "\n"
+        out = []
+        namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items()
+                                                            for v in vlist)
+        nextLevelIndent = indent + "  "
+
+        # collapse out indents if formatting is not desired
+        if not formatted:
+            indent = ""
+            nextLevelIndent = ""
+            nl = ""
+
+        selfTag = None
+        if doctag is not None:
+            selfTag = doctag
+        else:
+            if self.__name:
+                selfTag = self.__name
+
+        if not selfTag:
+            if namedItemsOnly:
+                return ""
+            else:
+                selfTag = "ITEM"
+
+        out += [ nl, indent, "<", selfTag, ">" ]
+
+        for i,res in enumerate(self.__toklist):
+            if isinstance(res,ParseResults):
+                if i in namedItems:
+                    out += [ res.asXML(namedItems[i],
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+                else:
+                    out += [ res.asXML(None,
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+            else:
+                # individual token, see if there is a name for it
+                resTag = None
+                if i in namedItems:
+                    resTag = namedItems[i]
+                if not resTag:
+                    if namedItemsOnly:
+                        continue
+                    else:
+                        resTag = "ITEM"
+                xmlBodyText = _xml_escape(_ustr(res))
+                out += [ nl, nextLevelIndent, "<", resTag, ">",
+                                                xmlBodyText,
+                                                "" ]
+
+        out += [ nl, indent, "" ]
+        return "".join(out)
+
+    def __lookup(self,sub):
+        for k,vlist in self.__tokdict.items():
+            for v,loc in vlist:
+                if sub is v:
+                    return k
+        return None
+
+    def getName(self):
+        r"""
+        Returns the results name for this token expression. Useful when several 
+        different expressions might match at a particular location.
+
+        Example::
+            integer = Word(nums)
+            ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+            house_number_expr = Suppress('#') + Word(nums, alphanums)
+            user_data = (Group(house_number_expr)("house_number") 
+                        | Group(ssn_expr)("ssn")
+                        | Group(integer)("age"))
+            user_info = OneOrMore(user_data)
+            
+            result = user_info.parseString("22 111-22-3333 #221B")
+            for item in result:
+                print(item.getName(), ':', item[0])
+        prints::
+            age : 22
+            ssn : 111-22-3333
+            house_number : 221B
+        """
+        if self.__name:
+            return self.__name
+        elif self.__parent:
+            par = self.__parent()
+            if par:
+                return par.__lookup(self)
+            else:
+                return None
+        elif (len(self) == 1 and
+               len(self.__tokdict) == 1 and
+               next(iter(self.__tokdict.values()))[0][1] in (0,-1)):
+            return next(iter(self.__tokdict.keys()))
+        else:
+            return None
+
+    def dump(self, indent='', depth=0, full=True):
+        """
+        Diagnostic method for listing out the contents of a C{ParseResults}.
+        Accepts an optional C{indent} argument so that this string can be embedded
+        in a nested display of other data.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(result.dump())
+        prints::
+            ['12', '/', '31', '/', '1999']
+            - day: 1999
+            - month: 31
+            - year: 12
+        """
+        out = []
+        NL = '\n'
+        out.append( indent+_ustr(self.asList()) )
+        if full:
+            if self.haskeys():
+                items = sorted((str(k), v) for k,v in self.items())
+                for k,v in items:
+                    if out:
+                        out.append(NL)
+                    out.append( "%s%s- %s: " % (indent,('  '*depth), k) )
+                    if isinstance(v,ParseResults):
+                        if v:
+                            out.append( v.dump(indent,depth+1) )
+                        else:
+                            out.append(_ustr(v))
+                    else:
+                        out.append(repr(v))
+            elif any(isinstance(vv,ParseResults) for vv in self):
+                v = self
+                for i,vv in enumerate(v):
+                    if isinstance(vv,ParseResults):
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),vv.dump(indent,depth+1) ))
+                    else:
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),_ustr(vv)))
+            
+        return "".join(out)
+
+    def pprint(self, *args, **kwargs):
+        """
+        Pretty-printer for parsed results as a list, using the C{pprint} module.
+        Accepts additional positional or keyword args as defined for the 
+        C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
+
+        Example::
+            ident = Word(alphas, alphanums)
+            num = Word(nums)
+            func = Forward()
+            term = ident | num | Group('(' + func + ')')
+            func <<= ident + Group(Optional(delimitedList(term)))
+            result = func.parseString("fna a,b,(fnb c,d,200),100")
+            result.pprint(width=40)
+        prints::
+            ['fna',
+             ['a',
+              'b',
+              ['(', 'fnb', ['c', 'd', '200'], ')'],
+              '100']]
+        """
+        pprint.pprint(self.asList(), *args, **kwargs)
+
+    # add support for pickle protocol
+    def __getstate__(self):
+        return ( self.__toklist,
+                 ( self.__tokdict.copy(),
+                   self.__parent is not None and self.__parent() or None,
+                   self.__accumNames,
+                   self.__name ) )
+
+    def __setstate__(self,state):
+        self.__toklist = state[0]
+        (self.__tokdict,
+         par,
+         inAccumNames,
+         self.__name) = state[1]
+        self.__accumNames = {}
+        self.__accumNames.update(inAccumNames)
+        if par is not None:
+            self.__parent = wkref(par)
+        else:
+            self.__parent = None
+
+    def __getnewargs__(self):
+        return self.__toklist, self.__name, self.__asList, self.__modal
+
+    def __dir__(self):
+        return (dir(type(self)) + list(self.keys()))
+
+MutableMapping.register(ParseResults)
+
+def col (loc,strg):
+    """Returns current column within a string, counting newlines as line separators.
+   The first column is number 1.
+
+   Note: the default parsing behavior is to expand tabs in the input string
+   before starting the parsing process.  See L{I{ParserElement.parseString}} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    s = strg
+    return 1 if 0} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    return strg.count("\n",0,loc) + 1
+
+def line( loc, strg ):
+    """Returns the line of text containing loc within a string, counting newlines as line separators.
+       """
+    lastCR = strg.rfind("\n", 0, loc)
+    nextCR = strg.find("\n", loc)
+    if nextCR >= 0:
+        return strg[lastCR+1:nextCR]
+    else:
+        return strg[lastCR+1:]
+
+def _defaultStartDebugAction( instring, loc, expr ):
+    print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))
+
+def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
+    print ("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
+
+def _defaultExceptionDebugAction( instring, loc, expr, exc ):
+    print ("Exception raised:" + _ustr(exc))
+
+def nullDebugAction(*args):
+    """'Do-nothing' debug action, to suppress debugging output during parsing."""
+    pass
+
+# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
+#~ 'decorator to trim function calls to match the arity of the target'
+#~ def _trim_arity(func, maxargs=3):
+    #~ if func in singleArgBuiltins:
+        #~ return lambda s,l,t: func(t)
+    #~ limit = 0
+    #~ foundArity = False
+    #~ def wrapper(*args):
+        #~ nonlocal limit,foundArity
+        #~ while 1:
+            #~ try:
+                #~ ret = func(*args[limit:])
+                #~ foundArity = True
+                #~ return ret
+            #~ except TypeError:
+                #~ if limit == maxargs or foundArity:
+                    #~ raise
+                #~ limit += 1
+                #~ continue
+    #~ return wrapper
+
+# this version is Python 2.x-3.x cross-compatible
+'decorator to trim function calls to match the arity of the target'
+def _trim_arity(func, maxargs=2):
+    if func in singleArgBuiltins:
+        return lambda s,l,t: func(t)
+    limit = [0]
+    foundArity = [False]
+    
+    # traceback return data structure changed in Py3.5 - normalize back to plain tuples
+    if system_version[:2] >= (3,5):
+        def extract_stack(limit=0):
+            # special handling for Python 3.5.0 - extra deep call stack by 1
+            offset = -3 if system_version == (3,5,0) else -2
+            frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
+            return [frame_summary[:2]]
+        def extract_tb(tb, limit=0):
+            frames = traceback.extract_tb(tb, limit=limit)
+            frame_summary = frames[-1]
+            return [frame_summary[:2]]
+    else:
+        extract_stack = traceback.extract_stack
+        extract_tb = traceback.extract_tb
+    
+    # synthesize what would be returned by traceback.extract_stack at the call to 
+    # user's parse action 'func', so that we don't incur call penalty at parse time
+    
+    LINE_DIFF = 6
+    # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND 
+    # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+    this_line = extract_stack(limit=2)[-1]
+    pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
+
+    def wrapper(*args):
+        while 1:
+            try:
+                ret = func(*args[limit[0]:])
+                foundArity[0] = True
+                return ret
+            except TypeError:
+                # re-raise TypeErrors if they did not come from our arity testing
+                if foundArity[0]:
+                    raise
+                else:
+                    try:
+                        tb = sys.exc_info()[-1]
+                        if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
+                            raise
+                    finally:
+                        del tb
+
+                if limit[0] <= maxargs:
+                    limit[0] += 1
+                    continue
+                raise
+
+    # copy func name to wrapper for sensible debug output
+    func_name = ""
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    wrapper.__name__ = func_name
+
+    return wrapper
+
+class ParserElement(object):
+    """Abstract base level parser element class."""
+    DEFAULT_WHITE_CHARS = " \n\t\r"
+    verbose_stacktrace = False
+
+    @staticmethod
+    def setDefaultWhitespaceChars( chars ):
+        r"""
+        Overrides the default whitespace chars
+
+        Example::
+            # default whitespace chars are space,  and newline
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            
+            # change to just treat newline as significant
+            ParserElement.setDefaultWhitespaceChars(" \t")
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def']
+        """
+        ParserElement.DEFAULT_WHITE_CHARS = chars
+
+    @staticmethod
+    def inlineLiteralsUsing(cls):
+        """
+        Set class to be used for inclusion of string literals into a parser.
+        
+        Example::
+            # default literal class used is Literal
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+
+            # change to Suppress
+            ParserElement.inlineLiteralsUsing(Suppress)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '12', '31']
+        """
+        ParserElement._literalStringClass = cls
+
+    def __init__( self, savelist=False ):
+        self.parseAction = list()
+        self.failAction = None
+        #~ self.name = ""  # don't define self.name, let subclasses try/except upcall
+        self.strRepr = None
+        self.resultsName = None
+        self.saveAsList = savelist
+        self.skipWhitespace = True
+        self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        self.copyDefaultWhiteChars = True
+        self.mayReturnEmpty = False # used when checking for left-recursion
+        self.keepTabs = False
+        self.ignoreExprs = list()
+        self.debug = False
+        self.streamlined = False
+        self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
+        self.errmsg = ""
+        self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
+        self.debugActions = ( None, None, None ) #custom debug actions
+        self.re = None
+        self.callPreparse = True # used to avoid redundant calls to preParse
+        self.callDuringTry = False
+
+    def copy( self ):
+        """
+        Make a copy of this C{ParserElement}.  Useful for defining different parse actions
+        for the same parsing pattern, using copies of the original parse element.
+        
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
+            integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+            
+            print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
+        prints::
+            [5120, 100, 655360, 268435456]
+        Equivalent form of C{expr.copy()} is just C{expr()}::
+            integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+        """
+        cpy = copy.copy( self )
+        cpy.parseAction = self.parseAction[:]
+        cpy.ignoreExprs = self.ignoreExprs[:]
+        if self.copyDefaultWhiteChars:
+            cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        return cpy
+
+    def setName( self, name ):
+        """
+        Define name for this expression, makes debugging and exception messages clearer.
+        
+        Example::
+            Word(nums).parseString("ABC")  # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
+            Word(nums).setName("integer").parseString("ABC")  # -> Exception: Expected integer (at char 0), (line:1, col:1)
+        """
+        self.name = name
+        self.errmsg = "Expected " + self.name
+        if hasattr(self,"exception"):
+            self.exception.msg = self.errmsg
+        return self
+
+    def setResultsName( self, name, listAllMatches=False ):
+        """
+        Define name for referencing matching tokens as a nested attribute
+        of the returned parse results.
+        NOTE: this returns a *copy* of the original C{ParserElement} object;
+        this is so that the client can define a basic element, such as an
+        integer, and reference it in multiple places with different names.
+
+        You can also set results names using the abbreviated syntax,
+        C{expr("name")} in place of C{expr.setResultsName("name")} - 
+        see L{I{__call__}<__call__>}.
+
+        Example::
+            date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+
+            # equivalent form:
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+        """
+        newself = self.copy()
+        if name.endswith("*"):
+            name = name[:-1]
+            listAllMatches=True
+        newself.resultsName = name
+        newself.modalResults = not listAllMatches
+        return newself
+
+    def setBreak(self,breakFlag = True):
+        """Method to invoke the Python pdb debugger when this element is
+           about to be parsed. Set C{breakFlag} to True to enable, False to
+           disable.
+        """
+        if breakFlag:
+            _parseMethod = self._parse
+            def breaker(instring, loc, doActions=True, callPreParse=True):
+                import pdb
+                pdb.set_trace()
+                return _parseMethod( instring, loc, doActions, callPreParse )
+            breaker._originalParseMethod = _parseMethod
+            self._parse = breaker
+        else:
+            if hasattr(self._parse,"_originalParseMethod"):
+                self._parse = self._parse._originalParseMethod
+        return self
+
+    def setParseAction( self, *fns, **kwargs ):
+        """
+        Define one or more actions to perform when successfully matching parse element definition.
+        Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)},
+        C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where:
+         - s   = the original string being parsed (see note below)
+         - loc = the location of the matching substring
+         - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object
+        If the functions in fns modify the tokens, they can return them as the return
+        value from fn, and the modified list of tokens will replace the original.
+        Otherwise, fn does not need to return any value.
+
+        Optional keyword arguments:
+         - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
+
+        Note: the default parsing behavior is to expand tabs in the input string
+        before starting the parsing process.  See L{I{parseString}} for more information
+        on parsing strings containing C{}s, and suggested methods to maintain a
+        consistent view of the parsed string, the parse location, and line and column
+        positions within the parsed string.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer + '/' + integer + '/' + integer
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+            # use parse action to convert to ints at parse time
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            date_str = integer + '/' + integer + '/' + integer
+
+            # note that integer fields are now ints, not strings
+            date_str.parseString("1999/12/31")  # -> [1999, '/', 12, '/', 31]
+        """
+        self.parseAction = list(map(_trim_arity, list(fns)))
+        self.callDuringTry = kwargs.get("callDuringTry", False)
+        return self
+
+    def addParseAction( self, *fns, **kwargs ):
+        """
+        Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}.
+        
+        See examples in L{I{copy}}.
+        """
+        self.parseAction += list(map(_trim_arity, list(fns)))
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def addCondition(self, *fns, **kwargs):
+        """Add a boolean predicate function to expression's list of parse actions. See 
+        L{I{setParseAction}} for function call signatures. Unlike C{setParseAction}, 
+        functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+        Optional keyword arguments:
+         - message = define a custom message to be used in the raised exception
+         - fatal   = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+         
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            year_int = integer.copy()
+            year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+            date_str = year_int + '/' + integer + '/' + integer
+
+            result = date_str.parseString("1999/12/31")  # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
+        """
+        msg = kwargs.get("message", "failed user-defined condition")
+        exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
+        for fn in fns:
+            def pa(s,l,t):
+                if not bool(_trim_arity(fn)(s,l,t)):
+                    raise exc_type(s,l,msg)
+            self.parseAction.append(pa)
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def setFailAction( self, fn ):
+        """Define action to perform if parsing fails at this expression.
+           Fail acton fn is a callable function that takes the arguments
+           C{fn(s,loc,expr,err)} where:
+            - s = string being parsed
+            - loc = location where expression match was attempted and failed
+            - expr = the parse expression that failed
+            - err = the exception thrown
+           The function returns no value.  It may throw C{L{ParseFatalException}}
+           if it is desired to stop parsing immediately."""
+        self.failAction = fn
+        return self
+
+    def _skipIgnorables( self, instring, loc ):
+        exprsFound = True
+        while exprsFound:
+            exprsFound = False
+            for e in self.ignoreExprs:
+                try:
+                    while 1:
+                        loc,dummy = e._parse( instring, loc )
+                        exprsFound = True
+                except ParseException:
+                    pass
+        return loc
+
+    def preParse( self, instring, loc ):
+        if self.ignoreExprs:
+            loc = self._skipIgnorables( instring, loc )
+
+        if self.skipWhitespace:
+            wt = self.whiteChars
+            instrlen = len(instring)
+            while loc < instrlen and instring[loc] in wt:
+                loc += 1
+
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        return loc, []
+
+    def postParse( self, instring, loc, tokenlist ):
+        return tokenlist
+
+    #~ @profile
+    def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ):
+        debugging = ( self.debug ) #and doActions )
+
+        if debugging or self.failAction:
+            #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))
+            if (self.debugActions[0] ):
+                self.debugActions[0]( instring, loc, self )
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            try:
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            except ParseBaseException as err:
+                #~ print ("Exception raised:", err)
+                if self.debugActions[2]:
+                    self.debugActions[2]( instring, tokensStart, self, err )
+                if self.failAction:
+                    self.failAction( instring, tokensStart, self, err )
+                raise
+        else:
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            if self.mayIndexError or preloc >= len(instring):
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            else:
+                loc,tokens = self.parseImpl( instring, preloc, doActions )
+
+        tokens = self.postParse( instring, loc, tokens )
+
+        retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults )
+        if self.parseAction and (doActions or self.callDuringTry):
+            if debugging:
+                try:
+                    for fn in self.parseAction:
+                        tokens = fn( instring, tokensStart, retTokens )
+                        if tokens is not None:
+                            retTokens = ParseResults( tokens,
+                                                      self.resultsName,
+                                                      asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                      modal=self.modalResults )
+                except ParseBaseException as err:
+                    #~ print "Exception raised in user parse action:", err
+                    if (self.debugActions[2] ):
+                        self.debugActions[2]( instring, tokensStart, self, err )
+                    raise
+            else:
+                for fn in self.parseAction:
+                    tokens = fn( instring, tokensStart, retTokens )
+                    if tokens is not None:
+                        retTokens = ParseResults( tokens,
+                                                  self.resultsName,
+                                                  asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                  modal=self.modalResults )
+        if debugging:
+            #~ print ("Matched",self,"->",retTokens.asList())
+            if (self.debugActions[1] ):
+                self.debugActions[1]( instring, tokensStart, loc, self, retTokens )
+
+        return loc, retTokens
+
+    def tryParse( self, instring, loc ):
+        try:
+            return self._parse( instring, loc, doActions=False )[0]
+        except ParseFatalException:
+            raise ParseException( instring, loc, self.errmsg, self)
+    
+    def canParseNext(self, instring, loc):
+        try:
+            self.tryParse(instring, loc)
+        except (ParseException, IndexError):
+            return False
+        else:
+            return True
+
+    class _UnboundedCache(object):
+        def __init__(self):
+            cache = {}
+            self.not_in_cache = not_in_cache = object()
+
+            def get(self, key):
+                return cache.get(key, not_in_cache)
+
+            def set(self, key, value):
+                cache[key] = value
+
+            def clear(self):
+                cache.clear()
+                
+            def cache_len(self):
+                return len(cache)
+
+            self.get = types.MethodType(get, self)
+            self.set = types.MethodType(set, self)
+            self.clear = types.MethodType(clear, self)
+            self.__len__ = types.MethodType(cache_len, self)
+
+    if _OrderedDict is not None:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = _OrderedDict()
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(cache) > size:
+                        try:
+                            cache.popitem(False)
+                        except KeyError:
+                            pass
+
+                def clear(self):
+                    cache.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    else:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = {}
+                key_fifo = collections.deque([], size)
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(key_fifo) > size:
+                        cache.pop(key_fifo.popleft(), None)
+                    key_fifo.append(key)
+
+                def clear(self):
+                    cache.clear()
+                    key_fifo.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    # argument cache for optimizing repeated calls when backtracking through recursive expressions
+    packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
+    packrat_cache_lock = RLock()
+    packrat_cache_stats = [0, 0]
+
+    # this method gets repeatedly called during backtracking with the same arguments -
+    # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
+    def _parseCache( self, instring, loc, doActions=True, callPreParse=True ):
+        HIT, MISS = 0, 1
+        lookup = (self, instring, loc, callPreParse, doActions)
+        with ParserElement.packrat_cache_lock:
+            cache = ParserElement.packrat_cache
+            value = cache.get(lookup)
+            if value is cache.not_in_cache:
+                ParserElement.packrat_cache_stats[MISS] += 1
+                try:
+                    value = self._parseNoCache(instring, loc, doActions, callPreParse)
+                except ParseBaseException as pe:
+                    # cache a copy of the exception, without the traceback
+                    cache.set(lookup, pe.__class__(*pe.args))
+                    raise
+                else:
+                    cache.set(lookup, (value[0], value[1].copy()))
+                    return value
+            else:
+                ParserElement.packrat_cache_stats[HIT] += 1
+                if isinstance(value, Exception):
+                    raise value
+                return (value[0], value[1].copy())
+
+    _parse = _parseNoCache
+
+    @staticmethod
+    def resetCache():
+        ParserElement.packrat_cache.clear()
+        ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
+
+    _packratEnabled = False
+    @staticmethod
+    def enablePackrat(cache_size_limit=128):
+        """Enables "packrat" parsing, which adds memoizing to the parsing logic.
+           Repeated parse attempts at the same string location (which happens
+           often in many complex grammars) can immediately return a cached value,
+           instead of re-executing parsing/validating code.  Memoizing is done of
+           both valid results and parsing exceptions.
+           
+           Parameters:
+            - cache_size_limit - (default=C{128}) - if an integer value is provided
+              will limit the size of the packrat cache; if None is passed, then
+              the cache size will be unbounded; if 0 is passed, the cache will
+              be effectively disabled.
+            
+           This speedup may break existing programs that use parse actions that
+           have side-effects.  For this reason, packrat parsing is disabled when
+           you first import pyparsing.  To activate the packrat feature, your
+           program must call the class method C{ParserElement.enablePackrat()}.  If
+           your program uses C{psyco} to "compile as you go", you must call
+           C{enablePackrat} before calling C{psyco.full()}.  If you do not do this,
+           Python will crash.  For best results, call C{enablePackrat()} immediately
+           after importing pyparsing.
+           
+           Example::
+               import pyparsing
+               pyparsing.ParserElement.enablePackrat()
+        """
+        if not ParserElement._packratEnabled:
+            ParserElement._packratEnabled = True
+            if cache_size_limit is None:
+                ParserElement.packrat_cache = ParserElement._UnboundedCache()
+            else:
+                ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
+            ParserElement._parse = ParserElement._parseCache
+
+    def parseString( self, instring, parseAll=False ):
+        """
+        Execute the parse expression with the given string.
+        This is the main interface to the client code, once the complete
+        expression has been built.
+
+        If you want the grammar to require that the entire input string be
+        successfully parsed, then set C{parseAll} to True (equivalent to ending
+        the grammar with C{L{StringEnd()}}).
+
+        Note: C{parseString} implicitly calls C{expandtabs()} on the input string,
+        in order to report proper column numbers in parse actions.
+        If the input string contains tabs and
+        the grammar uses parse actions that use the C{loc} argument to index into the
+        string being parsed, you can ensure you have a consistent view of the input
+        string by:
+         - calling C{parseWithTabs} on your grammar before calling C{parseString}
+           (see L{I{parseWithTabs}})
+         - define your parse action using the full C{(s,loc,toks)} signature, and
+           reference the input string using the parse action's C{s} argument
+         - explicitly expand the tabs in your input string before calling
+           C{parseString}
+        
+        Example::
+            Word('a').parseString('aaaaabaaa')  # -> ['aaaaa']
+            Word('a').parseString('aaaaabaaa', parseAll=True)  # -> Exception: Expected end of text
+        """
+        ParserElement.resetCache()
+        if not self.streamlined:
+            self.streamline()
+            #~ self.saveAsList = True
+        for e in self.ignoreExprs:
+            e.streamline()
+        if not self.keepTabs:
+            instring = instring.expandtabs()
+        try:
+            loc, tokens = self._parse( instring, 0 )
+            if parseAll:
+                loc = self.preParse( instring, loc )
+                se = Empty() + StringEnd()
+                se._parse( instring, loc )
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+        else:
+            return tokens
+
+    def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ):
+        """
+        Scan the input string for expression matches.  Each match will return the
+        matching tokens, start location, and end location.  May be called with optional
+        C{maxMatches} argument, to clip scanning after 'n' matches are found.  If
+        C{overlap} is specified, then overlapping matches will be reported.
+
+        Note that the start and end locations are reported relative to the string
+        being parsed.  See L{I{parseString}} for more information on parsing
+        strings with embedded tabs.
+
+        Example::
+            source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+            print(source)
+            for tokens,start,end in Word(alphas).scanString(source):
+                print(' '*start + '^'*(end-start))
+                print(' '*start + tokens[0])
+        
+        prints::
+        
+            sldjf123lsdjjkf345sldkjf879lkjsfd987
+            ^^^^^
+            sldjf
+                    ^^^^^^^
+                    lsdjjkf
+                              ^^^^^^
+                              sldkjf
+                                       ^^^^^^
+                                       lkjsfd
+        """
+        if not self.streamlined:
+            self.streamline()
+        for e in self.ignoreExprs:
+            e.streamline()
+
+        if not self.keepTabs:
+            instring = _ustr(instring).expandtabs()
+        instrlen = len(instring)
+        loc = 0
+        preparseFn = self.preParse
+        parseFn = self._parse
+        ParserElement.resetCache()
+        matches = 0
+        try:
+            while loc <= instrlen and matches < maxMatches:
+                try:
+                    preloc = preparseFn( instring, loc )
+                    nextLoc,tokens = parseFn( instring, preloc, callPreParse=False )
+                except ParseException:
+                    loc = preloc+1
+                else:
+                    if nextLoc > loc:
+                        matches += 1
+                        yield tokens, preloc, nextLoc
+                        if overlap:
+                            nextloc = preparseFn( instring, loc )
+                            if nextloc > loc:
+                                loc = nextLoc
+                            else:
+                                loc += 1
+                        else:
+                            loc = nextLoc
+                    else:
+                        loc = preloc+1
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def transformString( self, instring ):
+        """
+        Extension to C{L{scanString}}, to modify matching text with modified tokens that may
+        be returned from a parse action.  To use C{transformString}, define a grammar and
+        attach a parse action to it that modifies the returned token list.
+        Invoking C{transformString()} on a target string will then scan for matches,
+        and replace the matched text patterns according to the logic in the parse
+        action.  C{transformString()} returns the resulting transformed string.
+        
+        Example::
+            wd = Word(alphas)
+            wd.setParseAction(lambda toks: toks[0].title())
+            
+            print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
+        Prints::
+            Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+        """
+        out = []
+        lastE = 0
+        # force preservation of s, to minimize unwanted transformation of string, and to
+        # keep string locs straight between transformString and scanString
+        self.keepTabs = True
+        try:
+            for t,s,e in self.scanString( instring ):
+                out.append( instring[lastE:s] )
+                if t:
+                    if isinstance(t,ParseResults):
+                        out += t.asList()
+                    elif isinstance(t,list):
+                        out += t
+                    else:
+                        out.append(t)
+                lastE = e
+            out.append(instring[lastE:])
+            out = [o for o in out if o]
+            return "".join(map(_ustr,_flatten(out)))
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def searchString( self, instring, maxMatches=_MAX_INT ):
+        """
+        Another extension to C{L{scanString}}, simplifying the access to the tokens found
+        to match the given parse expression.  May be called with optional
+        C{maxMatches} argument, to clip searching after 'n' matches are found.
+        
+        Example::
+            # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+            cap_word = Word(alphas.upper(), alphas.lower())
+            
+            print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
+
+            # the sum() builtin can be used to merge results into a single ParseResults object
+            print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
+        prints::
+            [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
+            ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
+        """
+        try:
+            return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ])
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
+        """
+        Generator method to split a string using the given expression as a separator.
+        May be called with optional C{maxsplit} argument, to limit the number of splits;
+        and the optional C{includeSeparators} argument (default=C{False}), if the separating
+        matching text should be included in the split results.
+        
+        Example::        
+            punc = oneOf(list(".,;:/-!?"))
+            print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+        prints::
+            ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+        """
+        splits = 0
+        last = 0
+        for t,s,e in self.scanString(instring, maxMatches=maxsplit):
+            yield instring[last:s]
+            if includeSeparators:
+                yield t[0]
+            last = e
+        yield instring[last:]
+
+    def __add__(self, other ):
+        """
+        Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement
+        converts them to L{Literal}s by default.
+        
+        Example::
+            greet = Word(alphas) + "," + Word(alphas) + "!"
+            hello = "Hello, World!"
+            print (hello, "->", greet.parseString(hello))
+        Prints::
+            Hello, World! -> ['Hello', ',', 'World', '!']
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return And( [ self, other ] )
+
+    def __radd__(self, other ):
+        """
+        Implementation of + operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other + self
+
+    def __sub__(self, other):
+        """
+        Implementation of - operator, returns C{L{And}} with error stop
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return self + And._ErrorStop() + other
+
+    def __rsub__(self, other ):
+        """
+        Implementation of - operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other - self
+
+    def __mul__(self,other):
+        """
+        Implementation of * operator, allows use of C{expr * 3} in place of
+        C{expr + expr + expr}.  Expressions may also me multiplied by a 2-integer
+        tuple, similar to C{{min,max}} multipliers in regular expressions.  Tuples
+        may also include C{None} as in:
+         - C{expr*(n,None)} or C{expr*(n,)} is equivalent
+              to C{expr*n + L{ZeroOrMore}(expr)}
+              (read as "at least n instances of C{expr}")
+         - C{expr*(None,n)} is equivalent to C{expr*(0,n)}
+              (read as "0 to n instances of C{expr}")
+         - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)}
+         - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)}
+
+        Note that C{expr*(None,n)} does not raise an exception if
+        more than n exprs exist in the input stream; that is,
+        C{expr*(None,n)} does not enforce a maximum number of expr
+        occurrences.  If this behavior is desired, then write
+        C{expr*(None,n) + ~expr}
+        """
+        if isinstance(other,int):
+            minElements, optElements = other,0
+        elif isinstance(other,tuple):
+            other = (other + (None, None))[:2]
+            if other[0] is None:
+                other = (0, other[1])
+            if isinstance(other[0],int) and other[1] is None:
+                if other[0] == 0:
+                    return ZeroOrMore(self)
+                if other[0] == 1:
+                    return OneOrMore(self)
+                else:
+                    return self*other[0] + ZeroOrMore(self)
+            elif isinstance(other[0],int) and isinstance(other[1],int):
+                minElements, optElements = other
+                optElements -= minElements
+            else:
+                raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1]))
+        else:
+            raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
+
+        if minElements < 0:
+            raise ValueError("cannot multiply ParserElement by negative value")
+        if optElements < 0:
+            raise ValueError("second tuple value must be greater or equal to first tuple value")
+        if minElements == optElements == 0:
+            raise ValueError("cannot multiply ParserElement by 0 or (0,0)")
+
+        if (optElements):
+            def makeOptionalList(n):
+                if n>1:
+                    return Optional(self + makeOptionalList(n-1))
+                else:
+                    return Optional(self)
+            if minElements:
+                if minElements == 1:
+                    ret = self + makeOptionalList(optElements)
+                else:
+                    ret = And([self]*minElements) + makeOptionalList(optElements)
+            else:
+                ret = makeOptionalList(optElements)
+        else:
+            if minElements == 1:
+                ret = self
+            else:
+                ret = And([self]*minElements)
+        return ret
+
+    def __rmul__(self, other):
+        return self.__mul__(other)
+
+    def __or__(self, other ):
+        """
+        Implementation of | operator - returns C{L{MatchFirst}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return MatchFirst( [ self, other ] )
+
+    def __ror__(self, other ):
+        """
+        Implementation of | operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other | self
+
+    def __xor__(self, other ):
+        """
+        Implementation of ^ operator - returns C{L{Or}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Or( [ self, other ] )
+
+    def __rxor__(self, other ):
+        """
+        Implementation of ^ operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other ^ self
+
+    def __and__(self, other ):
+        """
+        Implementation of & operator - returns C{L{Each}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Each( [ self, other ] )
+
+    def __rand__(self, other ):
+        """
+        Implementation of & operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other & self
+
+    def __invert__( self ):
+        """
+        Implementation of ~ operator - returns C{L{NotAny}}
+        """
+        return NotAny( self )
+
+    def __call__(self, name=None):
+        """
+        Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}.
+        
+        If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
+        passed as C{True}.
+           
+        If C{name} is omitted, same as calling C{L{copy}}.
+
+        Example::
+            # these are equivalent
+            userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
+            userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")             
+        """
+        if name is not None:
+            return self.setResultsName(name)
+        else:
+            return self.copy()
+
+    def suppress( self ):
+        """
+        Suppresses the output of this C{ParserElement}; useful to keep punctuation from
+        cluttering up returned output.
+        """
+        return Suppress( self )
+
+    def leaveWhitespace( self ):
+        """
+        Disables the skipping of whitespace before matching the characters in the
+        C{ParserElement}'s defined pattern.  This is normally only used internally by
+        the pyparsing module, but may be needed in some whitespace-sensitive grammars.
+        """
+        self.skipWhitespace = False
+        return self
+
+    def setWhitespaceChars( self, chars ):
+        """
+        Overrides the default whitespace chars
+        """
+        self.skipWhitespace = True
+        self.whiteChars = chars
+        self.copyDefaultWhiteChars = False
+        return self
+
+    def parseWithTabs( self ):
+        """
+        Overrides default behavior to expand C{}s to spaces before parsing the input string.
+        Must be called before C{parseString} when the input grammar contains elements that
+        match C{} characters.
+        """
+        self.keepTabs = True
+        return self
+
+    def ignore( self, other ):
+        """
+        Define expression to be ignored (e.g., comments) while doing pattern
+        matching; may be called repeatedly, to define multiple comment or other
+        ignorable patterns.
+        
+        Example::
+            patt = OneOrMore(Word(alphas))
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
+            
+            patt.ignore(cStyleComment)
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
+        """
+        if isinstance(other, basestring):
+            other = Suppress(other)
+
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                self.ignoreExprs.append(other)
+        else:
+            self.ignoreExprs.append( Suppress( other.copy() ) )
+        return self
+
+    def setDebugActions( self, startAction, successAction, exceptionAction ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        """
+        self.debugActions = (startAction or _defaultStartDebugAction,
+                             successAction or _defaultSuccessDebugAction,
+                             exceptionAction or _defaultExceptionDebugAction)
+        self.debug = True
+        return self
+
+    def setDebug( self, flag=True ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        Set C{flag} to True to enable, False to disable.
+
+        Example::
+            wd = Word(alphas).setName("alphaword")
+            integer = Word(nums).setName("numword")
+            term = wd | integer
+            
+            # turn on debugging for wd
+            wd.setDebug()
+
+            OneOrMore(term).parseString("abc 123 xyz 890")
+        
+        prints::
+            Match alphaword at loc 0(1,1)
+            Matched alphaword -> ['abc']
+            Match alphaword at loc 3(1,4)
+            Exception raised:Expected alphaword (at char 4), (line:1, col:5)
+            Match alphaword at loc 7(1,8)
+            Matched alphaword -> ['xyz']
+            Match alphaword at loc 11(1,12)
+            Exception raised:Expected alphaword (at char 12), (line:1, col:13)
+            Match alphaword at loc 15(1,16)
+            Exception raised:Expected alphaword (at char 15), (line:1, col:16)
+
+        The output shown is that produced by the default debug actions - custom debug actions can be
+        specified using L{setDebugActions}. Prior to attempting
+        to match the C{wd} expression, the debugging message C{"Match  at loc (,)"}
+        is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"}
+        message is shown. Also note the use of L{setName} to assign a human-readable name to the expression,
+        which makes debugging and exception messages easier to understand - for instance, the default
+        name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}.
+        """
+        if flag:
+            self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction )
+        else:
+            self.debug = False
+        return self
+
+    def __str__( self ):
+        return self.name
+
+    def __repr__( self ):
+        return _ustr(self)
+
+    def streamline( self ):
+        self.streamlined = True
+        self.strRepr = None
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        pass
+
+    def validate( self, validateTrace=[] ):
+        """
+        Check defined expressions for valid structure, check for infinite recursive definitions.
+        """
+        self.checkRecursion( [] )
+
+    def parseFile( self, file_or_filename, parseAll=False ):
+        """
+        Execute the parse expression on the given file or filename.
+        If a filename is specified (instead of a file object),
+        the entire file is opened, read, and closed before parsing.
+        """
+        try:
+            file_contents = file_or_filename.read()
+        except AttributeError:
+            with open(file_or_filename, "r") as f:
+                file_contents = f.read()
+        try:
+            return self.parseString(file_contents, parseAll)
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def __eq__(self,other):
+        if isinstance(other, ParserElement):
+            return self is other or vars(self) == vars(other)
+        elif isinstance(other, basestring):
+            return self.matches(other)
+        else:
+            return super(ParserElement,self)==other
+
+    def __ne__(self,other):
+        return not (self == other)
+
+    def __hash__(self):
+        return hash(id(self))
+
+    def __req__(self,other):
+        return self == other
+
+    def __rne__(self,other):
+        return not (self == other)
+
+    def matches(self, testString, parseAll=True):
+        """
+        Method for quick testing of a parser against a test string. Good for simple 
+        inline microtests of sub expressions while building up larger parser.
+           
+        Parameters:
+         - testString - to test against this expression for a match
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+            
+        Example::
+            expr = Word(nums)
+            assert expr.matches("100")
+        """
+        try:
+            self.parseString(_ustr(testString), parseAll=parseAll)
+            return True
+        except ParseBaseException:
+            return False
+                
+    def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
+        """
+        Execute the parse expression on a series of test strings, showing each
+        test, the parsed results or where the parse failed. Quick and easy way to
+        run a parse expression against a list of sample strings.
+           
+        Parameters:
+         - tests - a list of separate test strings, or a multiline string of test strings
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests           
+         - comment - (default=C{'#'}) - expression for indicating embedded comments in the test 
+              string; pass None to disable comment filtering
+         - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
+              if False, only dump nested list
+         - printResults - (default=C{True}) prints test output to stdout
+         - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
+
+        Returns: a (success, results) tuple, where success indicates that all tests succeeded
+        (or failed if C{failureTests} is True), and the results contain a list of lines of each 
+        test's output
+        
+        Example::
+            number_expr = pyparsing_common.number.copy()
+
+            result = number_expr.runTests('''
+                # unsigned integer
+                100
+                # negative integer
+                -100
+                # float with scientific notation
+                6.02e23
+                # integer with scientific notation
+                1e-12
+                ''')
+            print("Success" if result[0] else "Failed!")
+
+            result = number_expr.runTests('''
+                # stray character
+                100Z
+                # missing leading digit before '.'
+                -.100
+                # too many '.'
+                3.14.159
+                ''', failureTests=True)
+            print("Success" if result[0] else "Failed!")
+        prints::
+            # unsigned integer
+            100
+            [100]
+
+            # negative integer
+            -100
+            [-100]
+
+            # float with scientific notation
+            6.02e23
+            [6.02e+23]
+
+            # integer with scientific notation
+            1e-12
+            [1e-12]
+
+            Success
+            
+            # stray character
+            100Z
+               ^
+            FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+            # missing leading digit before '.'
+            -.100
+            ^
+            FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+            # too many '.'
+            3.14.159
+                ^
+            FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+            Success
+
+        Each test string must be on a single line. If you want to test a string that spans multiple
+        lines, create a test like this::
+
+            expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
+        
+        (Note that this is a raw string literal, you must include the leading 'r'.)
+        """
+        if isinstance(tests, basestring):
+            tests = list(map(str.strip, tests.rstrip().splitlines()))
+        if isinstance(comment, basestring):
+            comment = Literal(comment)
+        allResults = []
+        comments = []
+        success = True
+        for t in tests:
+            if comment is not None and comment.matches(t, False) or comments and not t:
+                comments.append(t)
+                continue
+            if not t:
+                continue
+            out = ['\n'.join(comments), t]
+            comments = []
+            try:
+                t = t.replace(r'\n','\n')
+                result = self.parseString(t, parseAll=parseAll)
+                out.append(result.dump(full=fullDump))
+                success = success and not failureTests
+            except ParseBaseException as pe:
+                fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+                if '\n' in t:
+                    out.append(line(pe.loc, t))
+                    out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
+                else:
+                    out.append(' '*pe.loc + '^' + fatal)
+                out.append("FAIL: " + str(pe))
+                success = success and failureTests
+                result = pe
+            except Exception as exc:
+                out.append("FAIL-EXCEPTION: " + str(exc))
+                success = success and failureTests
+                result = exc
+
+            if printResults:
+                if fullDump:
+                    out.append('')
+                print('\n'.join(out))
+
+            allResults.append((t, result))
+        
+        return success, allResults
+
+        
+class Token(ParserElement):
+    """
+    Abstract C{ParserElement} subclass, for defining atomic matching patterns.
+    """
+    def __init__( self ):
+        super(Token,self).__init__( savelist=False )
+
+
+class Empty(Token):
+    """
+    An empty token, will always match.
+    """
+    def __init__( self ):
+        super(Empty,self).__init__()
+        self.name = "Empty"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+
+class NoMatch(Token):
+    """
+    A token that will never match.
+    """
+    def __init__( self ):
+        super(NoMatch,self).__init__()
+        self.name = "NoMatch"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.errmsg = "Unmatchable token"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Literal(Token):
+    """
+    Token to exactly match a specified string.
+    
+    Example::
+        Literal('blah').parseString('blah')  # -> ['blah']
+        Literal('blah').parseString('blahfooblah')  # -> ['blah']
+        Literal('blah').parseString('bla')  # -> Exception: Expected "blah"
+    
+    For case-insensitive matching, use L{CaselessLiteral}.
+    
+    For keyword matching (force word break before and after the matched string),
+    use L{Keyword} or L{CaselessKeyword}.
+    """
+    def __init__( self, matchString ):
+        super(Literal,self).__init__()
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Literal; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+            self.__class__ = Empty
+        self.name = '"%s"' % _ustr(self.match)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+
+    # Performance tuning: this routine gets called a *lot*
+    # if this is a single character match string  and the first character matches,
+    # short-circuit as quickly as possible, and avoid calling startswith
+    #~ @profile
+    def parseImpl( self, instring, loc, doActions=True ):
+        if (instring[loc] == self.firstMatchChar and
+            (self.matchLen==1 or instring.startswith(self.match,loc)) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+_L = Literal
+ParserElement._literalStringClass = Literal
+
+class Keyword(Token):
+    """
+    Token to exactly match a specified string as a keyword, that is, it must be
+    immediately followed by a non-keyword character.  Compare with C{L{Literal}}:
+     - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
+     - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'}
+    Accepts two optional constructor arguments in addition to the keyword string:
+     - C{identChars} is a string of characters that would be valid identifier characters,
+          defaulting to all alphanumerics + "_" and "$"
+     - C{caseless} allows case-insensitive matching, default is C{False}.
+       
+    Example::
+        Keyword("start").parseString("start")  # -> ['start']
+        Keyword("start").parseString("starting")  # -> Exception
+
+    For case-insensitive matching, use L{CaselessKeyword}.
+    """
+    DEFAULT_KEYWORD_CHARS = alphanums+"_$"
+
+    def __init__( self, matchString, identChars=None, caseless=False ):
+        super(Keyword,self).__init__()
+        if identChars is None:
+            identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Keyword; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+        self.name = '"%s"' % self.match
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+        self.caseless = caseless
+        if caseless:
+            self.caselessmatch = matchString.upper()
+            identChars = identChars.upper()
+        self.identChars = set(identChars)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.caseless:
+            if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+                 (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and
+                 (loc == 0 or instring[loc-1].upper() not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        else:
+            if (instring[loc] == self.firstMatchChar and
+                (self.matchLen==1 or instring.startswith(self.match,loc)) and
+                (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and
+                (loc == 0 or instring[loc-1] not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+    def copy(self):
+        c = super(Keyword,self).copy()
+        c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        return c
+
+    @staticmethod
+    def setDefaultKeywordChars( chars ):
+        """Overrides the default Keyword chars
+        """
+        Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+class CaselessLiteral(Literal):
+    """
+    Token to match a specified string, ignoring case of letters.
+    Note: the matched results will always be in the case of the given
+    match string, NOT the case of the input text.
+
+    Example::
+        OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessKeyword}.)
+    """
+    def __init__( self, matchString ):
+        super(CaselessLiteral,self).__init__( matchString.upper() )
+        # Preserve the defining literal.
+        self.returnString = matchString
+        self.name = "'%s'" % self.returnString
+        self.errmsg = "Expected " + self.name
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[ loc:loc+self.matchLen ].upper() == self.match:
+            return loc+self.matchLen, self.returnString
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CaselessKeyword(Keyword):
+    """
+    Caseless version of L{Keyword}.
+
+    Example::
+        OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessLiteral}.)
+    """
+    def __init__( self, matchString, identChars=None ):
+        super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True )
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+             (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CloseMatch(Token):
+    """
+    A variation on L{Literal} which matches "close" matches, that is, 
+    strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters:
+     - C{match_string} - string to be matched
+     - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match
+    
+    The results from a successful parse will contain the matched text from the input string and the following named results:
+     - C{mismatches} - a list of the positions within the match_string where mismatches were found
+     - C{original} - the original match_string used to compare against the input string
+    
+    If C{mismatches} is an empty list, then the match was an exact match.
+    
+    Example::
+        patt = CloseMatch("ATCATCGAATGGA")
+        patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
+        patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
+
+        # exact match
+        patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
+
+        # close match allowing up to 2 mismatches
+        patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
+        patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
+    """
+    def __init__(self, match_string, maxMismatches=1):
+        super(CloseMatch,self).__init__()
+        self.name = match_string
+        self.match_string = match_string
+        self.maxMismatches = maxMismatches
+        self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
+        self.mayIndexError = False
+        self.mayReturnEmpty = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        start = loc
+        instrlen = len(instring)
+        maxloc = start + len(self.match_string)
+
+        if maxloc <= instrlen:
+            match_string = self.match_string
+            match_stringloc = 0
+            mismatches = []
+            maxMismatches = self.maxMismatches
+
+            for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)):
+                src,mat = s_m
+                if src != mat:
+                    mismatches.append(match_stringloc)
+                    if len(mismatches) > maxMismatches:
+                        break
+            else:
+                loc = match_stringloc + 1
+                results = ParseResults([instring[start:loc]])
+                results['original'] = self.match_string
+                results['mismatches'] = mismatches
+                return loc, results
+
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Word(Token):
+    """
+    Token for matching words composed of allowed character sets.
+    Defined with string containing all allowed initial characters,
+    an optional string containing allowed body characters (if omitted,
+    defaults to the initial character set), and an optional minimum,
+    maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction. An optional
+    C{excludeChars} parameter can list characters that might be found in 
+    the input C{bodyChars} string; useful to define a word of all printables
+    except for one or two characters, for instance.
+    
+    L{srange} is useful for defining custom character set strings for defining 
+    C{Word} expressions, using range notation from regular expression character sets.
+    
+    A common mistake is to use C{Word} to match a specific literal string, as in 
+    C{Word("Address")}. Remember that C{Word} uses the string argument to define
+    I{sets} of matchable characters. This expression would match "Add", "AAA",
+    "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
+    To match an exact literal string, use L{Literal} or L{Keyword}.
+
+    pyparsing includes helper strings for building Words:
+     - L{alphas}
+     - L{nums}
+     - L{alphanums}
+     - L{hexnums}
+     - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
+     - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+     - L{printables} (any non-whitespace character)
+
+    Example::
+        # a word composed of digits
+        integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+        
+        # a word with a leading capital, and zero or more lowercase
+        capital_word = Word(alphas.upper(), alphas.lower())
+
+        # hostnames are alphanumeric, with leading alpha, and '-'
+        hostname = Word(alphas, alphanums+'-')
+        
+        # roman numeral (not a strict parser, accepts invalid mix of characters)
+        roman = Word("IVXLCDM")
+        
+        # any string of non-whitespace characters, except for ','
+        csv_value = Word(printables, excludeChars=",")
+    """
+    def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ):
+        super(Word,self).__init__()
+        if excludeChars:
+            initChars = ''.join(c for c in initChars if c not in excludeChars)
+            if bodyChars:
+                bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
+        self.initCharsOrig = initChars
+        self.initChars = set(initChars)
+        if bodyChars :
+            self.bodyCharsOrig = bodyChars
+            self.bodyChars = set(bodyChars)
+        else:
+            self.bodyCharsOrig = initChars
+            self.bodyChars = set(initChars)
+
+        self.maxSpecified = max > 0
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.asKeyword = asKeyword
+
+        if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0):
+            if self.bodyCharsOrig == self.initCharsOrig:
+                self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
+            elif len(self.initCharsOrig) == 1:
+                self.reString = "%s[%s]*" % \
+                                      (re.escape(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            else:
+                self.reString = "[%s][%s]*" % \
+                                      (_escapeRegexRangeChars(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            if self.asKeyword:
+                self.reString = r"\b"+self.reString+r"\b"
+            try:
+                self.re = re.compile( self.reString )
+            except Exception:
+                self.re = None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.re:
+            result = self.re.match(instring,loc)
+            if not result:
+                raise ParseException(instring, loc, self.errmsg, self)
+
+            loc = result.end()
+            return loc, result.group()
+
+        if not(instring[ loc ] in self.initChars):
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        instrlen = len(instring)
+        bodychars = self.bodyChars
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, instrlen )
+        while loc < maxloc and instring[loc] in bodychars:
+            loc += 1
+
+        throwException = False
+        if loc - start < self.minLen:
+            throwException = True
+        if self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
+            throwException = True
+        if self.asKeyword:
+            if (start>0 and instring[start-1] in bodychars) or (loc4:
+                    return s[:4]+"..."
+                else:
+                    return s
+
+            if ( self.initCharsOrig != self.bodyCharsOrig ):
+                self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) )
+            else:
+                self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
+
+        return self.strRepr
+
+
+class Regex(Token):
+    r"""
+    Token for matching strings that match a given regular expression.
+    Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module.
+    If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as 
+    named parse results.
+
+    Example::
+        realnum = Regex(r"[+-]?\d+\.\d*")
+        date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)')
+        # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+        roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+    """
+    compiledREtype = type(re.compile("[A-Z]"))
+    def __init__( self, pattern, flags=0):
+        """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags."""
+        super(Regex,self).__init__()
+
+        if isinstance(pattern, basestring):
+            if not pattern:
+                warnings.warn("null string passed to Regex; use Empty() instead",
+                        SyntaxWarning, stacklevel=2)
+
+            self.pattern = pattern
+            self.flags = flags
+
+            try:
+                self.re = re.compile(self.pattern, self.flags)
+                self.reString = self.pattern
+            except sre_constants.error:
+                warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
+                    SyntaxWarning, stacklevel=2)
+                raise
+
+        elif isinstance(pattern, Regex.compiledREtype):
+            self.re = pattern
+            self.pattern = \
+            self.reString = str(pattern)
+            self.flags = flags
+            
+        else:
+            raise ValueError("Regex may only be constructed with a string or a compiled RE object")
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = self.re.match(instring,loc)
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        d = result.groupdict()
+        ret = ParseResults(result.group())
+        if d:
+            for k in d:
+                ret[k] = d[k]
+        return loc,ret
+
+    def __str__( self ):
+        try:
+            return super(Regex,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "Re:(%s)" % repr(self.pattern)
+
+        return self.strRepr
+
+
+class QuotedString(Token):
+    r"""
+    Token for matching strings that are delimited by quoting characters.
+    
+    Defined with the following parameters:
+        - quoteChar - string of one or more characters defining the quote delimiting string
+        - escChar - character to escape quotes, typically backslash (default=C{None})
+        - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None})
+        - multiline - boolean indicating whether quotes can span multiple lines (default=C{False})
+        - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True})
+        - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar)
+        - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
+
+    Example::
+        qs = QuotedString('"')
+        print(qs.searchString('lsjdf "This is the quote" sldjf'))
+        complex_qs = QuotedString('{{', endQuoteChar='}}')
+        print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
+        sql_qs = QuotedString('"', escQuote='""')
+        print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+    prints::
+        [['This is the quote']]
+        [['This is the "quote"']]
+        [['This is the quote with "embedded" quotes']]
+    """
+    def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
+        super(QuotedString,self).__init__()
+
+        # remove white space from quote chars - wont work anyway
+        quoteChar = quoteChar.strip()
+        if not quoteChar:
+            warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+            raise SyntaxError()
+
+        if endQuoteChar is None:
+            endQuoteChar = quoteChar
+        else:
+            endQuoteChar = endQuoteChar.strip()
+            if not endQuoteChar:
+                warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+                raise SyntaxError()
+
+        self.quoteChar = quoteChar
+        self.quoteCharLen = len(quoteChar)
+        self.firstQuoteChar = quoteChar[0]
+        self.endQuoteChar = endQuoteChar
+        self.endQuoteCharLen = len(endQuoteChar)
+        self.escChar = escChar
+        self.escQuote = escQuote
+        self.unquoteResults = unquoteResults
+        self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+        if multiline:
+            self.flags = re.MULTILINE | re.DOTALL
+            self.pattern = r'%s(?:[^%s%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        else:
+            self.flags = 0
+            self.pattern = r'%s(?:[^%s\n\r%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        if len(self.endQuoteChar) > 1:
+            self.pattern += (
+                '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
+                                               _escapeRegexRangeChars(self.endQuoteChar[i]))
+                                    for i in range(len(self.endQuoteChar)-1,0,-1)) + ')'
+                )
+        if escQuote:
+            self.pattern += (r'|(?:%s)' % re.escape(escQuote))
+        if escChar:
+            self.pattern += (r'|(?:%s.)' % re.escape(escChar))
+            self.escCharReplacePattern = re.escape(self.escChar)+"(.)"
+        self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
+
+        try:
+            self.re = re.compile(self.pattern, self.flags)
+            self.reString = self.pattern
+        except sre_constants.error:
+            warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
+                SyntaxWarning, stacklevel=2)
+            raise
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        ret = result.group()
+
+        if self.unquoteResults:
+
+            # strip off quotes
+            ret = ret[self.quoteCharLen:-self.endQuoteCharLen]
+
+            if isinstance(ret,basestring):
+                # replace escaped whitespace
+                if '\\' in ret and self.convertWhitespaceEscapes:
+                    ws_map = {
+                        r'\t' : '\t',
+                        r'\n' : '\n',
+                        r'\f' : '\f',
+                        r'\r' : '\r',
+                    }
+                    for wslit,wschar in ws_map.items():
+                        ret = ret.replace(wslit, wschar)
+
+                # replace escaped characters
+                if self.escChar:
+                    ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
+
+                # replace escaped quotes
+                if self.escQuote:
+                    ret = ret.replace(self.escQuote, self.endQuoteChar)
+
+        return loc, ret
+
+    def __str__( self ):
+        try:
+            return super(QuotedString,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
+
+        return self.strRepr
+
+
+class CharsNotIn(Token):
+    """
+    Token for matching words composed of characters I{not} in a given set (will
+    include whitespace in matched characters if not listed in the provided exclusion set - see example).
+    Defined with string containing all disallowed characters, and an optional
+    minimum, maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction.
+
+    Example::
+        # define a comma-separated-value as anything that is not a ','
+        csv_value = CharsNotIn(',')
+        print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
+    prints::
+        ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+    """
+    def __init__( self, notChars, min=1, max=0, exact=0 ):
+        super(CharsNotIn,self).__init__()
+        self.skipWhitespace = False
+        self.notChars = notChars
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = ( self.minLen == 0 )
+        self.mayIndexError = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[loc] in self.notChars:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        notchars = self.notChars
+        maxlen = min( start+self.maxLen, len(instring) )
+        while loc < maxlen and \
+              (instring[loc] not in notchars):
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+    def __str__( self ):
+        try:
+            return super(CharsNotIn, self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            if len(self.notChars) > 4:
+                self.strRepr = "!W:(%s...)" % self.notChars[:4]
+            else:
+                self.strRepr = "!W:(%s)" % self.notChars
+
+        return self.strRepr
+
+class White(Token):
+    """
+    Special matching class for matching whitespace.  Normally, whitespace is ignored
+    by pyparsing grammars.  This class is included when some whitespace structures
+    are significant.  Define with a string containing the whitespace characters to be
+    matched; default is C{" \\t\\r\\n"}.  Also takes optional C{min}, C{max}, and C{exact} arguments,
+    as defined for the C{L{Word}} class.
+    """
+    whiteStrs = {
+        " " : "",
+        "\t": "",
+        "\n": "",
+        "\r": "",
+        "\f": "",
+        }
+    def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
+        super(White,self).__init__()
+        self.matchWhite = ws
+        self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) )
+        #~ self.leaveWhitespace()
+        self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
+        self.mayReturnEmpty = True
+        self.errmsg = "Expected " + self.name
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if not(instring[ loc ] in self.matchWhite):
+            raise ParseException(instring, loc, self.errmsg, self)
+        start = loc
+        loc += 1
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, len(instring) )
+        while loc < maxloc and instring[loc] in self.matchWhite:
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+
+class _PositionToken(Token):
+    def __init__( self ):
+        super(_PositionToken,self).__init__()
+        self.name=self.__class__.__name__
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+class GoToColumn(_PositionToken):
+    """
+    Token to advance to a specific column of input text; useful for tabular report scraping.
+    """
+    def __init__( self, colno ):
+        super(GoToColumn,self).__init__()
+        self.col = colno
+
+    def preParse( self, instring, loc ):
+        if col(loc,instring) != self.col:
+            instrlen = len(instring)
+            if self.ignoreExprs:
+                loc = self._skipIgnorables( instring, loc )
+            while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col :
+                loc += 1
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        thiscol = col( loc, instring )
+        if thiscol > self.col:
+            raise ParseException( instring, loc, "Text not in expected column", self )
+        newloc = loc + self.col - thiscol
+        ret = instring[ loc: newloc ]
+        return newloc, ret
+
+
+class LineStart(_PositionToken):
+    """
+    Matches if current position is at the beginning of a line within the parse string
+    
+    Example::
+    
+        test = '''\
+        AAA this line
+        AAA and this line
+          AAA but not this one
+        B AAA and definitely not this one
+        '''
+
+        for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
+            print(t)
+    
+    Prints::
+        ['AAA', ' this line']
+        ['AAA', ' and this line']    
+
+    """
+    def __init__( self ):
+        super(LineStart,self).__init__()
+        self.errmsg = "Expected start of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if col(loc, instring) == 1:
+            return loc, []
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class LineEnd(_PositionToken):
+    """
+    Matches if current position is at the end of a line within the parse string
+    """
+    def __init__( self ):
+        super(LineEnd,self).__init__()
+        self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") )
+        self.errmsg = "Expected end of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if loc len(instring):
+            return loc, []
+        else:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+class WordStart(_PositionToken):
+    """
+    Matches if the current position is at the beginning of a Word, and
+    is not preceded by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of
+    the string being parsed, or at the beginning of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordStart,self).__init__()
+        self.wordChars = set(wordChars)
+        self.errmsg = "Not at the start of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        if loc != 0:
+            if (instring[loc-1] in self.wordChars or
+                instring[loc] not in self.wordChars):
+                raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+class WordEnd(_PositionToken):
+    """
+    Matches if the current position is at the end of a Word, and
+    is not followed by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of
+    the string being parsed, or at the end of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordEnd,self).__init__()
+        self.wordChars = set(wordChars)
+        self.skipWhitespace = False
+        self.errmsg = "Not at the end of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        instrlen = len(instring)
+        if instrlen>0 and loc maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+            else:
+                # save match among all matches, to retry longest to shortest
+                matches.append((loc2, e))
+
+        if matches:
+            matches.sort(key=lambda x: -x[0])
+            for _,e in matches:
+                try:
+                    return e._parse( instring, loc, doActions )
+                except ParseException as err:
+                    err.__traceback__ = None
+                    if err.loc > maxExcLoc:
+                        maxException = err
+                        maxExcLoc = err.loc
+
+        if maxException is not None:
+            maxException.msg = self.errmsg
+            raise maxException
+        else:
+            raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+
+    def __ixor__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #Or( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class MatchFirst(ParseExpression):
+    """
+    Requires that at least one C{ParseExpression} is found.
+    If two expressions match, the first one listed is the one that will match.
+    May be constructed using the C{'|'} operator.
+
+    Example::
+        # construct MatchFirst using '|' operator
+        
+        # watch the order of expressions to match
+        number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+        print(number.searchString("123 3.1416 789")) #  Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+        # put more selective expression first
+        number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+        print(number.searchString("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
+    """
+    def __init__( self, exprs, savelist = False ):
+        super(MatchFirst,self).__init__(exprs, savelist)
+        if self.exprs:
+            self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+        else:
+            self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        maxExcLoc = -1
+        maxException = None
+        for e in self.exprs:
+            try:
+                ret = e._parse( instring, loc, doActions )
+                return ret
+            except ParseException as err:
+                if err.loc > maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+
+        # only got here if no expression matched, raise exception for match that made it the furthest
+        else:
+            if maxException is not None:
+                maxException.msg = self.errmsg
+                raise maxException
+            else:
+                raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+    def __ior__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #MatchFirst( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class Each(ParseExpression):
+    """
+    Requires all given C{ParseExpression}s to be found, but in any order.
+    Expressions may be separated by whitespace.
+    May be constructed using the C{'&'} operator.
+
+    Example::
+        color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+        shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+        integer = Word(nums)
+        shape_attr = "shape:" + shape_type("shape")
+        posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+        color_attr = "color:" + color("color")
+        size_attr = "size:" + integer("size")
+
+        # use Each (using operator '&') to accept attributes in any order 
+        # (shape and posn are required, color and size are optional)
+        shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
+
+        shape_spec.runTests('''
+            shape: SQUARE color: BLACK posn: 100, 120
+            shape: CIRCLE size: 50 color: BLUE posn: 50,80
+            color:GREEN size:20 shape:TRIANGLE posn:20,40
+            '''
+            )
+    prints::
+        shape: SQUARE color: BLACK posn: 100, 120
+        ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+        - color: BLACK
+        - posn: ['100', ',', '120']
+          - x: 100
+          - y: 120
+        - shape: SQUARE
+
+
+        shape: CIRCLE size: 50 color: BLUE posn: 50,80
+        ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+        - color: BLUE
+        - posn: ['50', ',', '80']
+          - x: 50
+          - y: 80
+        - shape: CIRCLE
+        - size: 50
+
+
+        color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+        ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+        - color: GREEN
+        - posn: ['20', ',', '40']
+          - x: 20
+          - y: 40
+        - shape: TRIANGLE
+        - size: 20
+    """
+    def __init__( self, exprs, savelist = True ):
+        super(Each,self).__init__(exprs, savelist)
+        self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+        self.skipWhitespace = True
+        self.initExprGroups = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.initExprGroups:
+            self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
+            opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
+            opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
+            self.optionals = opt1 + opt2
+            self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ]
+            self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ]
+            self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ]
+            self.required += self.multirequired
+            self.initExprGroups = False
+        tmpLoc = loc
+        tmpReqd = self.required[:]
+        tmpOpt  = self.optionals[:]
+        matchOrder = []
+
+        keepMatching = True
+        while keepMatching:
+            tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
+            failed = []
+            for e in tmpExprs:
+                try:
+                    tmpLoc = e.tryParse( instring, tmpLoc )
+                except ParseException:
+                    failed.append(e)
+                else:
+                    matchOrder.append(self.opt1map.get(id(e),e))
+                    if e in tmpReqd:
+                        tmpReqd.remove(e)
+                    elif e in tmpOpt:
+                        tmpOpt.remove(e)
+            if len(failed) == len(tmpExprs):
+                keepMatching = False
+
+        if tmpReqd:
+            missing = ", ".join(_ustr(e) for e in tmpReqd)
+            raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing )
+
+        # add any unmatched Optionals, in case they have default values defined
+        matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt]
+
+        resultlist = []
+        for e in matchOrder:
+            loc,results = e._parse(instring,loc,doActions)
+            resultlist.append(results)
+
+        finalResults = sum(resultlist, ParseResults([]))
+        return loc, finalResults
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class ParseElementEnhance(ParserElement):
+    """
+    Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(ParseElementEnhance,self).__init__(savelist)
+        if isinstance( expr, basestring ):
+            if issubclass(ParserElement._literalStringClass, Token):
+                expr = ParserElement._literalStringClass(expr)
+            else:
+                expr = ParserElement._literalStringClass(Literal(expr))
+        self.expr = expr
+        self.strRepr = None
+        if expr is not None:
+            self.mayIndexError = expr.mayIndexError
+            self.mayReturnEmpty = expr.mayReturnEmpty
+            self.setWhitespaceChars( expr.whiteChars )
+            self.skipWhitespace = expr.skipWhitespace
+            self.saveAsList = expr.saveAsList
+            self.callPreparse = expr.callPreparse
+            self.ignoreExprs.extend(expr.ignoreExprs)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr is not None:
+            return self.expr._parse( instring, loc, doActions, callPreParse=False )
+        else:
+            raise ParseException("",loc,self.errmsg,self)
+
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        self.expr = self.expr.copy()
+        if self.expr is not None:
+            self.expr.leaveWhitespace()
+        return self
+
+    def ignore( self, other ):
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                super( ParseElementEnhance, self).ignore( other )
+                if self.expr is not None:
+                    self.expr.ignore( self.ignoreExprs[-1] )
+        else:
+            super( ParseElementEnhance, self).ignore( other )
+            if self.expr is not None:
+                self.expr.ignore( self.ignoreExprs[-1] )
+        return self
+
+    def streamline( self ):
+        super(ParseElementEnhance,self).streamline()
+        if self.expr is not None:
+            self.expr.streamline()
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        if self in parseElementList:
+            raise RecursiveGrammarException( parseElementList+[self] )
+        subRecCheckList = parseElementList[:] + [ self ]
+        if self.expr is not None:
+            self.expr.checkRecursion( subRecCheckList )
+
+    def validate( self, validateTrace=[] ):
+        tmp = validateTrace[:]+[self]
+        if self.expr is not None:
+            self.expr.validate(tmp)
+        self.checkRecursion( [] )
+
+    def __str__( self ):
+        try:
+            return super(ParseElementEnhance,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None and self.expr is not None:
+            self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) )
+        return self.strRepr
+
+
+class FollowedBy(ParseElementEnhance):
+    """
+    Lookahead matching of the given parse expression.  C{FollowedBy}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression matches at the current
+    position.  C{FollowedBy} always returns a null token list.
+
+    Example::
+        # use FollowedBy to match a label only if it is followed by a ':'
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
+    prints::
+        [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+    """
+    def __init__( self, expr ):
+        super(FollowedBy,self).__init__(expr)
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self.expr.tryParse( instring, loc )
+        return loc, []
+
+
+class NotAny(ParseElementEnhance):
+    """
+    Lookahead to disallow matching with the given parse expression.  C{NotAny}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression does I{not} match at the current
+    position.  Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny}
+    always returns a null token list.  May be constructed using the '~' operator.
+
+    Example::
+        
+    """
+    def __init__( self, expr ):
+        super(NotAny,self).__init__(expr)
+        #~ self.leaveWhitespace()
+        self.skipWhitespace = False  # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
+        self.mayReturnEmpty = True
+        self.errmsg = "Found unwanted token, "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr.canParseNext(instring, loc):
+            raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "~{" + _ustr(self.expr) + "}"
+
+        return self.strRepr
+
+class _MultipleMatch(ParseElementEnhance):
+    def __init__( self, expr, stopOn=None):
+        super(_MultipleMatch, self).__init__(expr)
+        self.saveAsList = True
+        ender = stopOn
+        if isinstance(ender, basestring):
+            ender = ParserElement._literalStringClass(ender)
+        self.not_ender = ~ender if ender is not None else None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self_expr_parse = self.expr._parse
+        self_skip_ignorables = self._skipIgnorables
+        check_ender = self.not_ender is not None
+        if check_ender:
+            try_not_ender = self.not_ender.tryParse
+        
+        # must be at least one (but first see if we are the stopOn sentinel;
+        # if so, fail)
+        if check_ender:
+            try_not_ender(instring, loc)
+        loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
+        try:
+            hasIgnoreExprs = (not not self.ignoreExprs)
+            while 1:
+                if check_ender:
+                    try_not_ender(instring, loc)
+                if hasIgnoreExprs:
+                    preloc = self_skip_ignorables( instring, loc )
+                else:
+                    preloc = loc
+                loc, tmptokens = self_expr_parse( instring, preloc, doActions )
+                if tmptokens or tmptokens.haskeys():
+                    tokens += tmptokens
+        except (ParseException,IndexError):
+            pass
+
+        return loc, tokens
+        
+class OneOrMore(_MultipleMatch):
+    """
+    Repetition of one or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match one or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: BLACK"
+        OneOrMore(attr_expr).parseString(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+        # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+        
+        # could also be written as
+        (attr_expr * (1,)).parseString(text).pprint()
+    """
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + _ustr(self.expr) + "}..."
+
+        return self.strRepr
+
+class ZeroOrMore(_MultipleMatch):
+    """
+    Optional repetition of zero or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match zero or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example: similar to L{OneOrMore}
+    """
+    def __init__( self, expr, stopOn=None):
+        super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
+        self.mayReturnEmpty = True
+        
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
+        except (ParseException,IndexError):
+            return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]..."
+
+        return self.strRepr
+
+class _NullToken(object):
+    def __bool__(self):
+        return False
+    __nonzero__ = __bool__
+    def __str__(self):
+        return ""
+
+_optionalNotMatched = _NullToken()
+class Optional(ParseElementEnhance):
+    """
+    Optional matching of the given expression.
+
+    Parameters:
+     - expr - expression that must match zero or more times
+     - default (optional) - value to be returned if the optional expression is not found.
+
+    Example::
+        # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+        zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
+        zip.runTests('''
+            # traditional ZIP code
+            12345
+            
+            # ZIP+4 form
+            12101-0001
+            
+            # invalid ZIP
+            98765-
+            ''')
+    prints::
+        # traditional ZIP code
+        12345
+        ['12345']
+
+        # ZIP+4 form
+        12101-0001
+        ['12101-0001']
+
+        # invalid ZIP
+        98765-
+             ^
+        FAIL: Expected end of text (at char 5), (line:1, col:6)
+    """
+    def __init__( self, expr, default=_optionalNotMatched ):
+        super(Optional,self).__init__( expr, savelist=False )
+        self.saveAsList = self.expr.saveAsList
+        self.defaultValue = default
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
+        except (ParseException,IndexError):
+            if self.defaultValue is not _optionalNotMatched:
+                if self.expr.resultsName:
+                    tokens = ParseResults([ self.defaultValue ])
+                    tokens[self.expr.resultsName] = self.defaultValue
+                else:
+                    tokens = [ self.defaultValue ]
+            else:
+                tokens = []
+        return loc, tokens
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]"
+
+        return self.strRepr
+
+class SkipTo(ParseElementEnhance):
+    """
+    Token for skipping over all undefined text until the matched expression is found.
+
+    Parameters:
+     - expr - target expression marking the end of the data to be skipped
+     - include - (default=C{False}) if True, the target expression is also parsed 
+          (the skipped text and target expression are returned as a 2-element list).
+     - ignore - (default=C{None}) used to define grammars (typically quoted strings and 
+          comments) that might contain false matches to the target expression
+     - failOn - (default=C{None}) define expressions that are not allowed to be 
+          included in the skipped test; if found before the target expression is found, 
+          the SkipTo is not a match
+
+    Example::
+        report = '''
+            Outstanding Issues Report - 1 Jan 2000
+
+               # | Severity | Description                               |  Days Open
+            -----+----------+-------------------------------------------+-----------
+             101 | Critical | Intermittent system crash                 |          6
+              94 | Cosmetic | Spelling error on Login ('log|n')         |         14
+              79 | Minor    | System slow when running too many reports |         47
+            '''
+        integer = Word(nums)
+        SEP = Suppress('|')
+        # use SkipTo to simply match everything up until the next SEP
+        # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+        # - parse action will call token.strip() for each matched token, i.e., the description body
+        string_data = SkipTo(SEP, ignore=quotedString)
+        string_data.setParseAction(tokenMap(str.strip))
+        ticket_expr = (integer("issue_num") + SEP 
+                      + string_data("sev") + SEP 
+                      + string_data("desc") + SEP 
+                      + integer("days_open"))
+        
+        for tkt in ticket_expr.searchString(report):
+            print tkt.dump()
+    prints::
+        ['101', 'Critical', 'Intermittent system crash', '6']
+        - days_open: 6
+        - desc: Intermittent system crash
+        - issue_num: 101
+        - sev: Critical
+        ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+        - days_open: 14
+        - desc: Spelling error on Login ('log|n')
+        - issue_num: 94
+        - sev: Cosmetic
+        ['79', 'Minor', 'System slow when running too many reports', '47']
+        - days_open: 47
+        - desc: System slow when running too many reports
+        - issue_num: 79
+        - sev: Minor
+    """
+    def __init__( self, other, include=False, ignore=None, failOn=None ):
+        super( SkipTo, self ).__init__( other )
+        self.ignoreExpr = ignore
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.includeMatch = include
+        self.asList = False
+        if isinstance(failOn, basestring):
+            self.failOn = ParserElement._literalStringClass(failOn)
+        else:
+            self.failOn = failOn
+        self.errmsg = "No match found for "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        startloc = loc
+        instrlen = len(instring)
+        expr = self.expr
+        expr_parse = self.expr._parse
+        self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
+        self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+        
+        tmploc = loc
+        while tmploc <= instrlen:
+            if self_failOn_canParseNext is not None:
+                # break if failOn expression matches
+                if self_failOn_canParseNext(instring, tmploc):
+                    break
+                    
+            if self_ignoreExpr_tryParse is not None:
+                # advance past ignore expressions
+                while 1:
+                    try:
+                        tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+                    except ParseBaseException:
+                        break
+            
+            try:
+                expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+            except (ParseException, IndexError):
+                # no match, advance loc in string
+                tmploc += 1
+            else:
+                # matched skipto expr, done
+                break
+
+        else:
+            # ran off the end of the input string without matching skipto expr, fail
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        # build up return values
+        loc = tmploc
+        skiptext = instring[startloc:loc]
+        skipresult = ParseResults(skiptext)
+        
+        if self.includeMatch:
+            loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
+            skipresult += mat
+
+        return loc, skipresult
+
+class Forward(ParseElementEnhance):
+    """
+    Forward declaration of an expression to be defined later -
+    used for recursive grammars, such as algebraic infix notation.
+    When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator.
+
+    Note: take care when assigning to C{Forward} not to overlook precedence of operators.
+    Specifically, '|' has a lower precedence than '<<', so that::
+        fwdExpr << a | b | c
+    will actually be evaluated as::
+        (fwdExpr << a) | b | c
+    thereby leaving b and c out as parseable alternatives.  It is recommended that you
+    explicitly group the values inserted into the C{Forward}::
+        fwdExpr << (a | b | c)
+    Converting to use the '<<=' operator instead will avoid this problem.
+
+    See L{ParseResults.pprint} for an example of a recursive parser created using
+    C{Forward}.
+    """
+    def __init__( self, other=None ):
+        super(Forward,self).__init__( other, savelist=False )
+
+    def __lshift__( self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass(other)
+        self.expr = other
+        self.strRepr = None
+        self.mayIndexError = self.expr.mayIndexError
+        self.mayReturnEmpty = self.expr.mayReturnEmpty
+        self.setWhitespaceChars( self.expr.whiteChars )
+        self.skipWhitespace = self.expr.skipWhitespace
+        self.saveAsList = self.expr.saveAsList
+        self.ignoreExprs.extend(self.expr.ignoreExprs)
+        return self
+        
+    def __ilshift__(self, other):
+        return self << other
+    
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        return self
+
+    def streamline( self ):
+        if not self.streamlined:
+            self.streamlined = True
+            if self.expr is not None:
+                self.expr.streamline()
+        return self
+
+    def validate( self, validateTrace=[] ):
+        if self not in validateTrace:
+            tmp = validateTrace[:]+[self]
+            if self.expr is not None:
+                self.expr.validate(tmp)
+        self.checkRecursion([])
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+        return self.__class__.__name__ + ": ..."
+
+        # stubbed out for now - creates awful memory and perf issues
+        self._revertClass = self.__class__
+        self.__class__ = _ForwardNoRecurse
+        try:
+            if self.expr is not None:
+                retString = _ustr(self.expr)
+            else:
+                retString = "None"
+        finally:
+            self.__class__ = self._revertClass
+        return self.__class__.__name__ + ": " + retString
+
+    def copy(self):
+        if self.expr is not None:
+            return super(Forward,self).copy()
+        else:
+            ret = Forward()
+            ret <<= self
+            return ret
+
+class _ForwardNoRecurse(Forward):
+    def __str__( self ):
+        return "..."
+
+class TokenConverter(ParseElementEnhance):
+    """
+    Abstract subclass of C{ParseExpression}, for converting parsed results.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(TokenConverter,self).__init__( expr )#, savelist )
+        self.saveAsList = False
+
+class Combine(TokenConverter):
+    """
+    Converter to concatenate all matching tokens to a single string.
+    By default, the matching patterns must also be contiguous in the input string;
+    this can be disabled by specifying C{'adjacent=False'} in the constructor.
+
+    Example::
+        real = Word(nums) + '.' + Word(nums)
+        print(real.parseString('3.1416')) # -> ['3', '.', '1416']
+        # will also erroneously match the following
+        print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
+
+        real = Combine(Word(nums) + '.' + Word(nums))
+        print(real.parseString('3.1416')) # -> ['3.1416']
+        # no match when there are internal spaces
+        print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
+    """
+    def __init__( self, expr, joinString="", adjacent=True ):
+        super(Combine,self).__init__( expr )
+        # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
+        if adjacent:
+            self.leaveWhitespace()
+        self.adjacent = adjacent
+        self.skipWhitespace = True
+        self.joinString = joinString
+        self.callPreparse = True
+
+    def ignore( self, other ):
+        if self.adjacent:
+            ParserElement.ignore(self, other)
+        else:
+            super( Combine, self).ignore( other )
+        return self
+
+    def postParse( self, instring, loc, tokenlist ):
+        retToks = tokenlist.copy()
+        del retToks[:]
+        retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults)
+
+        if self.resultsName and retToks.haskeys():
+            return [ retToks ]
+        else:
+            return retToks
+
+class Group(TokenConverter):
+    """
+    Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.
+
+    Example::
+        ident = Word(alphas)
+        num = Word(nums)
+        term = ident | num
+        func = ident + Optional(delimitedList(term))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', 'a', 'b', '100']
+
+        func = ident + Group(Optional(delimitedList(term)))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', ['a', 'b', '100']]
+    """
+    def __init__( self, expr ):
+        super(Group,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        return [ tokenlist ]
+
+class Dict(TokenConverter):
+    """
+    Converter to return a repetitive expression as a list, but also as a dictionary.
+    Each element can also be referenced using the first token in the expression as its key.
+    Useful for tabular report scraping when the first column can be used as a item key.
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        # print attributes as plain groups
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
+        result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
+        print(result.dump())
+        
+        # access named fields as dict entries, or output as dict
+        print(result['shape'])        
+        print(result.asDict())
+    prints::
+        ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+    See more examples at L{ParseResults} of accessing fields by results name.
+    """
+    def __init__( self, expr ):
+        super(Dict,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        for i,tok in enumerate(tokenlist):
+            if len(tok) == 0:
+                continue
+            ikey = tok[0]
+            if isinstance(ikey,int):
+                ikey = _ustr(tok[0]).strip()
+            if len(tok)==1:
+                tokenlist[ikey] = _ParseResultsWithOffset("",i)
+            elif len(tok)==2 and not isinstance(tok[1],ParseResults):
+                tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i)
+            else:
+                dictvalue = tok.copy() #ParseResults(i)
+                del dictvalue[0]
+                if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()):
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i)
+                else:
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i)
+
+        if self.resultsName:
+            return [ tokenlist ]
+        else:
+            return tokenlist
+
+
+class Suppress(TokenConverter):
+    """
+    Converter for ignoring the results of a parsed expression.
+
+    Example::
+        source = "a, b, c,d"
+        wd = Word(alphas)
+        wd_list1 = wd + ZeroOrMore(',' + wd)
+        print(wd_list1.parseString(source))
+
+        # often, delimiters that are useful during parsing are just in the
+        # way afterward - use Suppress to keep them out of the parsed output
+        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        print(wd_list2.parseString(source))
+    prints::
+        ['a', ',', 'b', ',', 'c', ',', 'd']
+        ['a', 'b', 'c', 'd']
+    (See also L{delimitedList}.)
+    """
+    def postParse( self, instring, loc, tokenlist ):
+        return []
+
+    def suppress( self ):
+        return self
+
+
+class OnlyOnce(object):
+    """
+    Wrapper for parse actions, to ensure they are only called once.
+    """
+    def __init__(self, methodCall):
+        self.callable = _trim_arity(methodCall)
+        self.called = False
+    def __call__(self,s,l,t):
+        if not self.called:
+            results = self.callable(s,l,t)
+            self.called = True
+            return results
+        raise ParseException(s,l,"")
+    def reset(self):
+        self.called = False
+
+def traceParseAction(f):
+    """
+    Decorator for debugging parse actions. 
+    
+    When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".}
+    When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised.
+
+    Example::
+        wd = Word(alphas)
+
+        @traceParseAction
+        def remove_duplicate_chars(tokens):
+            return ''.join(sorted(set(''.join(tokens))))
+
+        wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
+        print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
+    prints::
+        >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+        <3:
+            thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
+        sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) )
+        try:
+            ret = f(*paArgs)
+        except Exception as exc:
+            sys.stderr.write( "< ['aa', 'bb', 'cc']
+        delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+    """
+    dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..."
+    if combine:
+        return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName)
+    else:
+        return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName)
+
+def countedArray( expr, intExpr=None ):
+    """
+    Helper to define a counted list of expressions.
+    This helper defines a pattern of the form::
+        integer expr expr expr...
+    where the leading integer tells how many expr expressions follow.
+    The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed.
+    
+    If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value.
+
+    Example::
+        countedArray(Word(alphas)).parseString('2 ab cd ef')  # -> ['ab', 'cd']
+
+        # in this parser, the leading integer value is given in binary,
+        # '10' indicating that 2 values are in the array
+        binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
+        countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef')  # -> ['ab', 'cd']
+    """
+    arrayExpr = Forward()
+    def countFieldParseAction(s,l,t):
+        n = t[0]
+        arrayExpr << (n and Group(And([expr]*n)) or Group(empty))
+        return []
+    if intExpr is None:
+        intExpr = Word(nums).setParseAction(lambda t:int(t[0]))
+    else:
+        intExpr = intExpr.copy()
+    intExpr.setName("arrayLen")
+    intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
+    return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...')
+
+def _flatten(L):
+    ret = []
+    for i in L:
+        if isinstance(i,list):
+            ret.extend(_flatten(i))
+        else:
+            ret.append(i)
+    return ret
+
+def matchPreviousLiteral(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousLiteral(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches a
+    previous literal, will also match the leading C{"1:1"} in C{"1:10"}.
+    If this is not desired, use C{matchPreviousExpr}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    def copyTokenToRepeater(s,l,t):
+        if t:
+            if len(t) == 1:
+                rep << t[0]
+            else:
+                # flatten t tokens
+                tflat = _flatten(t.asList())
+                rep << And(Literal(tt) for tt in tflat)
+        else:
+            rep << Empty()
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def matchPreviousExpr(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousExpr(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches by
+    expressions, will I{not} match the leading C{"1:1"} in C{"1:10"};
+    the expressions are evaluated first, and then compared, so
+    C{"1"} is compared with C{"10"}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    e2 = expr.copy()
+    rep <<= e2
+    def copyTokenToRepeater(s,l,t):
+        matchTokens = _flatten(t.asList())
+        def mustMatchTheseTokens(s,l,t):
+            theseTokens = _flatten(t.asList())
+            if  theseTokens != matchTokens:
+                raise ParseException("",0,"")
+        rep.setParseAction( mustMatchTheseTokens, callDuringTry=True )
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def _escapeRegexRangeChars(s):
+    #~  escape these chars: ^-]
+    for c in r"\^-]":
+        s = s.replace(c,_bslash+c)
+    s = s.replace("\n",r"\n")
+    s = s.replace("\t",r"\t")
+    return _ustr(s)
+
+def oneOf( strs, caseless=False, useRegex=True ):
+    """
+    Helper to quickly define a set of alternative Literals, and makes sure to do
+    longest-first testing when there is a conflict, regardless of the input order,
+    but returns a C{L{MatchFirst}} for best performance.
+
+    Parameters:
+     - strs - a string of space-delimited literals, or a collection of string literals
+     - caseless - (default=C{False}) - treat all literals as caseless
+     - useRegex - (default=C{True}) - as an optimization, will generate a Regex
+          object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or
+          if creating a C{Regex} raises an exception)
+
+    Example::
+        comp_oper = oneOf("< = > <= >= !=")
+        var = Word(alphas)
+        number = Word(nums)
+        term = var | number
+        comparison_expr = term + comp_oper + term
+        print(comparison_expr.searchString("B = 12  AA=23 B<=AA AA>12"))
+    prints::
+        [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+    """
+    if caseless:
+        isequal = ( lambda a,b: a.upper() == b.upper() )
+        masks = ( lambda a,b: b.upper().startswith(a.upper()) )
+        parseElementClass = CaselessLiteral
+    else:
+        isequal = ( lambda a,b: a == b )
+        masks = ( lambda a,b: b.startswith(a) )
+        parseElementClass = Literal
+
+    symbols = []
+    if isinstance(strs,basestring):
+        symbols = strs.split()
+    elif isinstance(strs, Iterable):
+        symbols = list(strs)
+    else:
+        warnings.warn("Invalid argument to oneOf, expected string or iterable",
+                SyntaxWarning, stacklevel=2)
+    if not symbols:
+        return NoMatch()
+
+    i = 0
+    while i < len(symbols)-1:
+        cur = symbols[i]
+        for j,other in enumerate(symbols[i+1:]):
+            if ( isequal(other, cur) ):
+                del symbols[i+j+1]
+                break
+            elif ( masks(cur, other) ):
+                del symbols[i+j+1]
+                symbols.insert(i,other)
+                cur = other
+                break
+        else:
+            i += 1
+
+    if not caseless and useRegex:
+        #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] ))
+        try:
+            if len(symbols)==len("".join(symbols)):
+                return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols))
+            else:
+                return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols))
+        except Exception:
+            warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
+                    SyntaxWarning, stacklevel=2)
+
+
+    # last resort, just use MatchFirst
+    return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
+
+def dictOf( key, value ):
+    """
+    Helper to easily and clearly define a dictionary by specifying the respective patterns
+    for the key and value.  Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens
+    in the proper order.  The key pattern can include delimiting markers or punctuation,
+    as long as they are suppressed, thereby leaving the significant key text.  The value
+    pattern can include named results, so that the C{Dict} results can include named token
+    fields.
+
+    Example::
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        attr_label = label
+        attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
+
+        # similar to Dict, but simpler call format
+        result = dictOf(attr_label, attr_value).parseString(text)
+        print(result.dump())
+        print(result['shape'])
+        print(result.shape)  # object attribute access works too
+        print(result.asDict())
+    prints::
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        SQUARE
+        {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+    """
+    return Dict( ZeroOrMore( Group ( key + value ) ) )
+
+def originalTextFor(expr, asString=True):
+    """
+    Helper to return the original, untokenized text for a given expression.  Useful to
+    restore the parsed fields of an HTML start tag into the raw tag text itself, or to
+    revert separate tokens with intervening whitespace back to the original matching
+    input text. By default, returns astring containing the original parsed text.  
+       
+    If the optional C{asString} argument is passed as C{False}, then the return value is a 
+    C{L{ParseResults}} containing any results names that were originally matched, and a 
+    single token containing the original matched text from the input string.  So if 
+    the expression passed to C{L{originalTextFor}} contains expressions with defined
+    results names, you must set C{asString} to C{False} if you want to preserve those
+    results name values.
+
+    Example::
+        src = "this is test  bold text  normal text "
+        for tag in ("b","i"):
+            opener,closer = makeHTMLTags(tag)
+            patt = originalTextFor(opener + SkipTo(closer) + closer)
+            print(patt.searchString(src)[0])
+    prints::
+        [' bold text ']
+        ['text']
+    """
+    locMarker = Empty().setParseAction(lambda s,loc,t: loc)
+    endlocMarker = locMarker.copy()
+    endlocMarker.callPreparse = False
+    matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
+    if asString:
+        extractText = lambda s,l,t: s[t._original_start:t._original_end]
+    else:
+        def extractText(s,l,t):
+            t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
+    matchExpr.setParseAction(extractText)
+    matchExpr.ignoreExprs = expr.ignoreExprs
+    return matchExpr
+
+def ungroup(expr): 
+    """
+    Helper to undo pyparsing's default grouping of And expressions, even
+    if all but one are non-empty.
+    """
+    return TokenConverter(expr).setParseAction(lambda t:t[0])
+
+def locatedExpr(expr):
+    """
+    Helper to decorate a returned token with its starting and ending locations in the input string.
+    This helper adds the following results names:
+     - locn_start = location where matched expression begins
+     - locn_end = location where matched expression ends
+     - value = the actual parsed results
+
+    Be careful if the input text contains C{} characters, you may want to call
+    C{L{ParserElement.parseWithTabs}}
+
+    Example::
+        wd = Word(alphas)
+        for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+            print(match)
+    prints::
+        [[0, 'ljsdf', 5]]
+        [[8, 'lksdjjf', 15]]
+        [[18, 'lkkjj', 23]]
+    """
+    locator = Empty().setParseAction(lambda s,l,t: l)
+    return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
+
+
+# convenience constants for positional expressions
+empty       = Empty().setName("empty")
+lineStart   = LineStart().setName("lineStart")
+lineEnd     = LineEnd().setName("lineEnd")
+stringStart = StringStart().setName("stringStart")
+stringEnd   = StringEnd().setName("stringEnd")
+
+_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
+_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
+_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
+_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
+
+def srange(s):
+    r"""
+    Helper to easily define string ranges for use in Word construction.  Borrows
+    syntax from regexp '[]' string range definitions::
+        srange("[0-9]")   -> "0123456789"
+        srange("[a-z]")   -> "abcdefghijklmnopqrstuvwxyz"
+        srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
+    The input string must be enclosed in []'s, and the returned string is the expanded
+    character set joined into a single string.
+    The values enclosed in the []'s may be:
+     - a single character
+     - an escaped character with a leading backslash (such as C{\-} or C{\]})
+     - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) 
+         (C{\0x##} is also supported for backwards compatibility) 
+     - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
+     - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
+     - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
+    """
+    _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
+    try:
+        return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
+    except Exception:
+        return ""
+
+def matchOnlyAtCol(n):
+    """
+    Helper method for defining parse actions that require matching at a specific
+    column in the input text.
+    """
+    def verifyCol(strg,locn,toks):
+        if col(locn,strg) != n:
+            raise ParseException(strg,locn,"matched token not at column %d" % n)
+    return verifyCol
+
+def replaceWith(replStr):
+    """
+    Helper method for common parse actions that simply return a literal value.  Especially
+    useful when used with C{L{transformString}()}.
+
+    Example::
+        num = Word(nums).setParseAction(lambda toks: int(toks[0]))
+        na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
+        term = na | num
+        
+        OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
+    """
+    return lambda s,l,t: [replStr]
+
+def removeQuotes(s,l,t):
+    """
+    Helper parse action for removing quotation marks from parsed quoted strings.
+
+    Example::
+        # by default, quotation marks are included in parsed results
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+        # use removeQuotes to strip quotation marks from parsed results
+        quotedString.setParseAction(removeQuotes)
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+    """
+    return t[0][1:-1]
+
+def tokenMap(func, *args):
+    """
+    Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional 
+    args are passed, they are forwarded to the given function as additional arguments after
+    the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
+    parsed data to an integer using base 16.
+
+    Example (compare the last to example in L{ParserElement.transformString}::
+        hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+        hex_ints.runTests('''
+            00 11 22 aa FF 0a 0d 1a
+            ''')
+        
+        upperword = Word(alphas).setParseAction(tokenMap(str.upper))
+        OneOrMore(upperword).runTests('''
+            my kingdom for a horse
+            ''')
+
+        wd = Word(alphas).setParseAction(tokenMap(str.title))
+        OneOrMore(wd).setParseAction(' '.join).runTests('''
+            now is the winter of our discontent made glorious summer by this sun of york
+            ''')
+    prints::
+        00 11 22 aa FF 0a 0d 1a
+        [0, 17, 34, 170, 255, 10, 13, 26]
+
+        my kingdom for a horse
+        ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+        now is the winter of our discontent made glorious summer by this sun of york
+        ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+    """
+    def pa(s,l,t):
+        return [func(tokn, *args) for tokn in t]
+
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    pa.__name__ = func_name
+
+    return pa
+
+upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
+"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}"""
+
+downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
+"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}"""
+    
+def _makeTags(tagStr, xml):
+    """Internal helper to construct opening and closing tag expressions, given a tag name"""
+    if isinstance(tagStr,basestring):
+        resname = tagStr
+        tagStr = Keyword(tagStr, caseless=not xml)
+    else:
+        resname = tagStr.name
+
+    tagAttrName = Word(alphas,alphanums+"_-:")
+    if (xml):
+        tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes )
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    else:
+        printablesLessRAbrack = "".join(c for c in printables if c not in ">")
+        tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack)
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \
+                Optional( Suppress("=") + tagAttrValue ) ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    closeTag = Combine(_L("")
+
+    openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname)
+    closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % resname)
+    openTag.tag = resname
+    closeTag.tag = resname
+    return openTag, closeTag
+
+def makeHTMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches
+    tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
+
+    Example::
+        text = 'More info at the pyparsing wiki page'
+        # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
+        a,a_end = makeHTMLTags("A")
+        link_expr = a + SkipTo(a_end)("link_text") + a_end
+        
+        for link in link_expr.searchString(text):
+            # attributes in the  tag (like "href" shown here) are also accessible as named results
+            print(link.link_text, '->', link.href)
+    prints::
+        pyparsing -> http://pyparsing.wikispaces.com
+    """
+    return _makeTags( tagStr, False )
+
+def makeXMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
+    tags only in the given upper/lower case.
+
+    Example: similar to L{makeHTMLTags}
+    """
+    return _makeTags( tagStr, True )
+
+def withAttribute(*args,**attrDict):
+    """
+    Helper to create a validating parse action to be used with start tags created
+    with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag
+    with a required attribute value, to avoid false matches on common tags such as
+    C{} or C{
}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"").setName("HTML comment") +"Comment of the form C{}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers}, L{reals}, L{scientific notation}) + - common L{programming identifiers} + - network addresses (L{MAC}, L{IPv4}, L{IPv6}) + - ISO8601 L{dates} and L{datetime} + - L{UUID} + - L{comma-separated list} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = 'More info at the
pyparsing wiki page' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/phivenv/Lib/site-packages/sympy/__init__.py b/phivenv/Lib/site-packages/sympy/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c67ba49409169c49ac5d34f2be365def3c30369b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/__init__.py @@ -0,0 +1,545 @@ +""" +SymPy is a Python library for symbolic mathematics. It aims to become a +full-featured computer algebra system (CAS) while keeping the code as simple +as possible in order to be comprehensible and easily extensible. SymPy is +written entirely in Python. It depends on mpmath, and other external libraries +may be optionally for things like plotting support. + +See the webpage for more information and documentation: + + https://sympy.org + +""" + + +# Keep this in sync with setup.py/pyproject.toml +import sys +if sys.version_info < (3, 9): + raise ImportError("Python version 3.9 or above is required for SymPy.") +del sys + + +try: + import mpmath +except ImportError: + raise ImportError("SymPy now depends on mpmath as an external library. " + "See https://docs.sympy.org/latest/install.html#mpmath for more information.") + +del mpmath + +from sympy.release import __version__ +from sympy.core.cache import lazy_function + +if 'dev' in __version__: + def enable_warnings(): + import warnings + warnings.filterwarnings('default', '.*', DeprecationWarning, module='sympy.*') + del warnings + enable_warnings() + del enable_warnings + + +def __sympy_debug(): + # helper function so we don't import os globally + import os + debug_str = os.getenv('SYMPY_DEBUG', 'False') + if debug_str in ('True', 'False'): + return eval(debug_str) + else: + raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" % + debug_str) +# Fails py2 test if using type hinting +SYMPY_DEBUG = __sympy_debug() # type: bool + + +from .core import (sympify, SympifyError, cacheit, Basic, Atom, + preorder_traversal, S, Expr, AtomicExpr, UnevaluatedExpr, Symbol, + Wild, Dummy, symbols, var, Number, Float, Rational, Integer, + NumberSymbol, RealNumber, igcd, ilcm, seterr, E, I, nan, oo, pi, zoo, + AlgebraicNumber, comp, mod_inverse, Pow, integer_nthroot, integer_log, + trailing, Mul, prod, Add, Mod, Rel, Eq, Ne, Lt, Le, Gt, Ge, Equality, + GreaterThan, LessThan, Unequality, StrictGreaterThan, StrictLessThan, + vectorize, Lambda, WildFunction, Derivative, diff, FunctionClass, + Function, Subs, expand, PoleError, count_ops, expand_mul, expand_log, + expand_func, expand_trig, expand_complex, expand_multinomial, nfloat, + expand_power_base, expand_power_exp, arity, PrecisionExhausted, N, + evalf, Tuple, Dict, gcd_terms, factor_terms, factor_nc, evaluate, + Catalan, EulerGamma, GoldenRatio, TribonacciConstant, bottom_up, use, + postorder_traversal, default_sort_key, ordered, num_digits) + +from .logic import (to_cnf, to_dnf, to_nnf, And, Or, Not, Xor, Nand, Nor, + Implies, Equivalent, ITE, POSform, SOPform, simplify_logic, bool_map, + true, false, satisfiable) + +from .assumptions import (AppliedPredicate, Predicate, AssumptionsContext, + assuming, Q, ask, register_handler, remove_handler, refine) + +from .polys import (Poly, PurePoly, poly_from_expr, parallel_poly_from_expr, + degree, total_degree, degree_list, LC, LM, LT, pdiv, prem, pquo, + pexquo, div, rem, quo, exquo, half_gcdex, gcdex, invert, + subresultants, resultant, discriminant, cofactors, gcd_list, gcd, + lcm_list, lcm, terms_gcd, trunc, monic, content, primitive, compose, + decompose, sturm, gff_list, gff, sqf_norm, sqf_part, sqf_list, sqf, + factor_list, factor, intervals, refine_root, count_roots, all_roots, + real_roots, nroots, ground_roots, nth_power_roots_poly, cancel, + reduced, groebner, is_zero_dimensional, GroebnerBasis, poly, + symmetrize, horner, interpolate, rational_interpolate, viete, together, + BasePolynomialError, ExactQuotientFailed, PolynomialDivisionFailed, + OperationNotSupported, HeuristicGCDFailed, HomomorphismFailed, + IsomorphismFailed, ExtraneousFactors, EvaluationFailed, + RefinementFailed, CoercionFailed, NotInvertible, NotReversible, + NotAlgebraic, DomainError, PolynomialError, UnificationFailed, + GeneratorsError, GeneratorsNeeded, ComputationFailed, + UnivariatePolynomialError, MultivariatePolynomialError, + PolificationFailed, OptionError, FlagError, minpoly, + minimal_polynomial, primitive_element, field_isomorphism, + to_number_field, isolate, round_two, prime_decomp, prime_valuation, + galois_group, itermonomials, Monomial, lex, grlex, + grevlex, ilex, igrlex, igrevlex, CRootOf, rootof, RootOf, + ComplexRootOf, RootSum, roots, Domain, FiniteField, IntegerRing, + RationalField, RealField, ComplexField, PythonFiniteField, + GMPYFiniteField, PythonIntegerRing, GMPYIntegerRing, PythonRational, + GMPYRationalField, AlgebraicField, PolynomialRing, FractionField, + ExpressionDomain, FF_python, FF_gmpy, ZZ_python, ZZ_gmpy, QQ_python, + QQ_gmpy, GF, FF, ZZ, QQ, ZZ_I, QQ_I, RR, CC, EX, EXRAW, + construct_domain, swinnerton_dyer_poly, cyclotomic_poly, + symmetric_poly, random_poly, interpolating_poly, jacobi_poly, + chebyshevt_poly, chebyshevu_poly, hermite_poly, hermite_prob_poly, + legendre_poly, laguerre_poly, apart, apart_list, assemble_partfrac_list, + Options, ring, xring, vring, sring, field, xfield, vfield, sfield) + +from .series import (Order, O, limit, Limit, gruntz, series, approximants, + residue, EmptySequence, SeqPer, SeqFormula, sequence, SeqAdd, SeqMul, + fourier_series, fps, difference_delta, limit_seq) + +from .functions import (factorial, factorial2, rf, ff, binomial, + RisingFactorial, FallingFactorial, subfactorial, carmichael, + fibonacci, lucas, motzkin, tribonacci, harmonic, bernoulli, bell, euler, + catalan, genocchi, andre, partition, divisor_sigma, legendre_symbol, + jacobi_symbol, kronecker_symbol, mobius, primenu, primeomega, + totient, reduced_totient, primepi, sqrt, root, Min, Max, Id, + real_root, Rem, cbrt, re, im, sign, Abs, conjugate, arg, polar_lift, + periodic_argument, unbranched_argument, principal_branch, transpose, + adjoint, polarify, unpolarify, sin, cos, tan, sec, csc, cot, sinc, + asin, acos, atan, asec, acsc, acot, atan2, exp_polar, exp, ln, log, + LambertW, sinh, cosh, tanh, coth, sech, csch, asinh, acosh, atanh, + acoth, asech, acsch, floor, ceiling, frac, Piecewise, piecewise_fold, + piecewise_exclusive, erf, erfc, erfi, erf2, erfinv, erfcinv, erf2inv, + Ei, expint, E1, li, Li, Si, Ci, Shi, Chi, fresnels, fresnelc, gamma, + lowergamma, uppergamma, polygamma, loggamma, digamma, trigamma, + multigamma, dirichlet_eta, zeta, lerchphi, polylog, stieltjes, Eijk, + LeviCivita, KroneckerDelta, SingularityFunction, DiracDelta, Heaviside, + bspline_basis, bspline_basis_set, interpolating_spline, besselj, + bessely, besseli, besselk, hankel1, hankel2, jn, yn, jn_zeros, hn1, + hn2, airyai, airybi, airyaiprime, airybiprime, marcumq, hyper, + meijerg, appellf1, legendre, assoc_legendre, hermite, hermite_prob, + chebyshevt, chebyshevu, chebyshevu_root, chebyshevt_root, laguerre, + assoc_laguerre, gegenbauer, jacobi, jacobi_normalized, Ynm, Ynm_c, + Znm, elliptic_k, elliptic_f, elliptic_e, elliptic_pi, beta, mathieus, + mathieuc, mathieusprime, mathieucprime, riemann_xi, betainc, betainc_regularized) + +from .ntheory import (nextprime, prevprime, prime, primerange, + randprime, Sieve, sieve, primorial, cycle_length, composite, + compositepi, isprime, divisors, proper_divisors, factorint, + multiplicity, perfect_power, factor_cache, pollard_pm1, pollard_rho, primefactors, + divisor_count, proper_divisor_count, + factorrat, + mersenne_prime_exponent, is_perfect, is_mersenne_prime, is_abundant, + is_deficient, is_amicable, is_carmichael, abundance, npartitions, is_primitive_root, + is_quad_residue, n_order, sqrt_mod, + quadratic_residues, primitive_root, nthroot_mod, is_nthpow_residue, + sqrt_mod_iter, discrete_log, quadratic_congruence, + binomial_coefficients, binomial_coefficients_list, + multinomial_coefficients, continued_fraction_periodic, + continued_fraction_iterator, continued_fraction_reduce, + continued_fraction_convergents, continued_fraction, egyptian_fraction) + +from .concrete import product, Product, summation, Sum + +from .discrete import (fft, ifft, ntt, intt, fwht, ifwht, mobius_transform, + inverse_mobius_transform, convolution, covering_product, + intersecting_product) + +from .simplify import (simplify, hypersimp, hypersimilar, logcombine, + separatevars, posify, besselsimp, kroneckersimp, signsimp, + nsimplify, FU, fu, sqrtdenest, cse, epath, EPath, hyperexpand, + collect, rcollect, radsimp, collect_const, fraction, numer, denom, + trigsimp, exptrigsimp, powsimp, powdenest, combsimp, gammasimp, + ratsimp, ratsimpmodprime) + +from .sets import (Set, Interval, Union, EmptySet, FiniteSet, ProductSet, + Intersection, DisjointUnion, imageset, Complement, SymmetricDifference, ImageSet, + Range, ComplexRegion, Complexes, Reals, Contains, ConditionSet, Ordinal, + OmegaPower, ord0, PowerSet, Naturals, Naturals0, UniversalSet, + Integers, Rationals) + +from .solvers import (solve, solve_linear_system, solve_linear_system_LU, + solve_undetermined_coeffs, nsolve, solve_linear, checksol, det_quick, + inv_quick, check_assumptions, failing_assumptions, diophantine, + rsolve, rsolve_poly, rsolve_ratio, rsolve_hyper, checkodesol, + classify_ode, dsolve, homogeneous_order, solve_poly_system, factor_system, + solve_triangulated, pde_separate, pde_separate_add, pde_separate_mul, + pdsolve, classify_pde, checkpdesol, ode_order, reduce_inequalities, + reduce_abs_inequality, reduce_abs_inequalities, solve_poly_inequality, + solve_rational_inequalities, solve_univariate_inequality, decompogen, + solveset, linsolve, linear_eq_to_matrix, nonlinsolve, substitution) + +from .matrices import (ShapeError, NonSquareMatrixError, GramSchmidt, + casoratian, diag, eye, hessian, jordan_cell, list2numpy, matrix2numpy, + matrix_multiply_elementwise, ones, randMatrix, rot_axis1, rot_axis2, + rot_axis3, symarray, wronskian, zeros, MutableDenseMatrix, + DeferredVector, MatrixBase, Matrix, MutableMatrix, + MutableSparseMatrix, banded, ImmutableDenseMatrix, + ImmutableSparseMatrix, ImmutableMatrix, SparseMatrix, MatrixSlice, + BlockDiagMatrix, BlockMatrix, FunctionMatrix, Identity, Inverse, + MatAdd, MatMul, MatPow, MatrixExpr, MatrixSymbol, Trace, Transpose, + ZeroMatrix, OneMatrix, blockcut, block_collapse, matrix_symbols, + Adjoint, hadamard_product, HadamardProduct, HadamardPower, + Determinant, det, diagonalize_vector, DiagMatrix, DiagonalMatrix, + DiagonalOf, trace, DotProduct, kronecker_product, KroneckerProduct, + PermutationMatrix, MatrixPermute, Permanent, per, rot_ccw_axis1, + rot_ccw_axis2, rot_ccw_axis3, rot_givens) + +from .geometry import (Point, Point2D, Point3D, Line, Ray, Segment, Line2D, + Segment2D, Ray2D, Line3D, Segment3D, Ray3D, Plane, Ellipse, Circle, + Polygon, RegularPolygon, Triangle, rad, deg, are_similar, centroid, + convex_hull, idiff, intersection, closest_points, farthest_points, + GeometryError, Curve, Parabola) + +from .utilities import (flatten, group, take, subsets, variations, + numbered_symbols, cartes, capture, dict_merge, prefixes, postfixes, + sift, topological_sort, unflatten, has_dups, has_variety, reshape, + rotations, filldedent, lambdify, + threaded, xthreaded, public, memoize_property, timed) + +from .integrals import (integrate, Integral, line_integrate, mellin_transform, + inverse_mellin_transform, MellinTransform, InverseMellinTransform, + laplace_transform, laplace_correspondence, laplace_initial_conds, + inverse_laplace_transform, LaplaceTransform, + InverseLaplaceTransform, fourier_transform, inverse_fourier_transform, + FourierTransform, InverseFourierTransform, sine_transform, + inverse_sine_transform, SineTransform, InverseSineTransform, + cosine_transform, inverse_cosine_transform, CosineTransform, + InverseCosineTransform, hankel_transform, inverse_hankel_transform, + HankelTransform, InverseHankelTransform, singularityintegrate) + +from .tensor import (IndexedBase, Idx, Indexed, get_contraction_structure, + get_indices, shape, MutableDenseNDimArray, ImmutableDenseNDimArray, + MutableSparseNDimArray, ImmutableSparseNDimArray, NDimArray, + tensorproduct, tensorcontraction, tensordiagonal, derive_by_array, + permutedims, Array, DenseNDimArray, SparseNDimArray) + +from .parsing import parse_expr + +from .calculus import (euler_equations, singularities, is_increasing, + is_strictly_increasing, is_decreasing, is_strictly_decreasing, + is_monotonic, finite_diff_weights, apply_finite_diff, + differentiate_finite, periodicity, not_empty_in, AccumBounds, + is_convex, stationary_points, minimum, maximum) + +from .algebras import Quaternion + +from .printing import (pager_print, pretty, pretty_print, pprint, + pprint_use_unicode, pprint_try_use_unicode, latex, print_latex, + multiline_latex, mathml, print_mathml, python, print_python, pycode, + ccode, print_ccode, smtlib_code, glsl_code, print_glsl, cxxcode, fcode, + print_fcode, rcode, print_rcode, jscode, print_jscode, julia_code, + mathematica_code, octave_code, rust_code, print_gtk, preview, srepr, + print_tree, StrPrinter, sstr, sstrrepr, TableForm, dotprint, + maple_code, print_maple_code) + +test = lazy_function('sympy.testing.runtests_pytest', 'test') +doctest = lazy_function('sympy.testing.runtests', 'doctest') + +# This module causes conflicts with other modules: +# from .stats import * +# Adds about .04-.05 seconds of import time +# from combinatorics import * +# This module is slow to import: +#from physics import units +from .plotting import plot, textplot, plot_backends, plot_implicit, plot_parametric +from .interactive import init_session, init_printing, interactive_traversal + +evalf._create_evalf_table() + +__all__ = [ + '__version__', + + # sympy.core + 'sympify', 'SympifyError', 'cacheit', 'Basic', 'Atom', + 'preorder_traversal', 'S', 'Expr', 'AtomicExpr', 'UnevaluatedExpr', + 'Symbol', 'Wild', 'Dummy', 'symbols', 'var', 'Number', 'Float', + 'Rational', 'Integer', 'NumberSymbol', 'RealNumber', 'igcd', 'ilcm', + 'seterr', 'E', 'I', 'nan', 'oo', 'pi', 'zoo', 'AlgebraicNumber', 'comp', + 'mod_inverse', 'Pow', 'integer_nthroot', 'integer_log', 'trailing', 'Mul', 'prod', + 'Add', 'Mod', 'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', 'Equality', + 'GreaterThan', 'LessThan', 'Unequality', 'StrictGreaterThan', + 'StrictLessThan', 'vectorize', 'Lambda', 'WildFunction', 'Derivative', + 'diff', 'FunctionClass', 'Function', 'Subs', 'expand', 'PoleError', + 'count_ops', 'expand_mul', 'expand_log', 'expand_func', 'expand_trig', + 'expand_complex', 'expand_multinomial', 'nfloat', 'expand_power_base', + 'expand_power_exp', 'arity', 'PrecisionExhausted', 'N', 'evalf', 'Tuple', + 'Dict', 'gcd_terms', 'factor_terms', 'factor_nc', 'evaluate', 'Catalan', + 'EulerGamma', 'GoldenRatio', 'TribonacciConstant', 'bottom_up', 'use', + 'postorder_traversal', 'default_sort_key', 'ordered', 'num_digits', + + # sympy.logic + 'to_cnf', 'to_dnf', 'to_nnf', 'And', 'Or', 'Not', 'Xor', 'Nand', 'Nor', + 'Implies', 'Equivalent', 'ITE', 'POSform', 'SOPform', 'simplify_logic', + 'bool_map', 'true', 'false', 'satisfiable', + + # sympy.assumptions + 'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming', 'Q', + 'ask', 'register_handler', 'remove_handler', 'refine', + + # sympy.polys + 'Poly', 'PurePoly', 'poly_from_expr', 'parallel_poly_from_expr', 'degree', + 'total_degree', 'degree_list', 'LC', 'LM', 'LT', 'pdiv', 'prem', 'pquo', + 'pexquo', 'div', 'rem', 'quo', 'exquo', 'half_gcdex', 'gcdex', 'invert', + 'subresultants', 'resultant', 'discriminant', 'cofactors', 'gcd_list', + 'gcd', 'lcm_list', 'lcm', 'terms_gcd', 'trunc', 'monic', 'content', + 'primitive', 'compose', 'decompose', 'sturm', 'gff_list', 'gff', + 'sqf_norm', 'sqf_part', 'sqf_list', 'sqf', 'factor_list', 'factor', + 'intervals', 'refine_root', 'count_roots', 'all_roots', 'real_roots', + 'nroots', 'ground_roots', 'nth_power_roots_poly', 'cancel', 'reduced', + 'groebner', 'is_zero_dimensional', 'GroebnerBasis', 'poly', 'symmetrize', + 'horner', 'interpolate', 'rational_interpolate', 'viete', 'together', + 'BasePolynomialError', 'ExactQuotientFailed', 'PolynomialDivisionFailed', + 'OperationNotSupported', 'HeuristicGCDFailed', 'HomomorphismFailed', + 'IsomorphismFailed', 'ExtraneousFactors', 'EvaluationFailed', + 'RefinementFailed', 'CoercionFailed', 'NotInvertible', 'NotReversible', + 'NotAlgebraic', 'DomainError', 'PolynomialError', 'UnificationFailed', + 'GeneratorsError', 'GeneratorsNeeded', 'ComputationFailed', + 'UnivariatePolynomialError', 'MultivariatePolynomialError', + 'PolificationFailed', 'OptionError', 'FlagError', 'minpoly', + 'minimal_polynomial', 'primitive_element', 'field_isomorphism', + 'to_number_field', 'isolate', 'round_two', 'prime_decomp', + 'prime_valuation', 'galois_group', 'itermonomials', 'Monomial', 'lex', 'grlex', + 'grevlex', 'ilex', 'igrlex', 'igrevlex', 'CRootOf', 'rootof', 'RootOf', + 'ComplexRootOf', 'RootSum', 'roots', 'Domain', 'FiniteField', + 'IntegerRing', 'RationalField', 'RealField', 'ComplexField', + 'PythonFiniteField', 'GMPYFiniteField', 'PythonIntegerRing', + 'GMPYIntegerRing', 'PythonRational', 'GMPYRationalField', + 'AlgebraicField', 'PolynomialRing', 'FractionField', 'ExpressionDomain', + 'FF_python', 'FF_gmpy', 'ZZ_python', 'ZZ_gmpy', 'QQ_python', 'QQ_gmpy', + 'GF', 'FF', 'ZZ', 'QQ', 'ZZ_I', 'QQ_I', 'RR', 'CC', 'EX', 'EXRAW', + 'construct_domain', 'swinnerton_dyer_poly', 'cyclotomic_poly', + 'symmetric_poly', 'random_poly', 'interpolating_poly', 'jacobi_poly', + 'chebyshevt_poly', 'chebyshevu_poly', 'hermite_poly', 'hermite_prob_poly', + 'legendre_poly', 'laguerre_poly', 'apart', 'apart_list', 'assemble_partfrac_list', + 'Options', 'ring', 'xring', 'vring', 'sring', 'field', 'xfield', 'vfield', + 'sfield', + + # sympy.series + 'Order', 'O', 'limit', 'Limit', 'gruntz', 'series', 'approximants', + 'residue', 'EmptySequence', 'SeqPer', 'SeqFormula', 'sequence', 'SeqAdd', + 'SeqMul', 'fourier_series', 'fps', 'difference_delta', 'limit_seq', + + # sympy.functions + 'factorial', 'factorial2', 'rf', 'ff', 'binomial', 'RisingFactorial', + 'FallingFactorial', 'subfactorial', 'carmichael', 'fibonacci', 'lucas', + 'motzkin', 'tribonacci', 'harmonic', 'bernoulli', 'bell', 'euler', 'catalan', + 'genocchi', 'andre', 'partition', 'divisor_sigma', 'legendre_symbol', 'jacobi_symbol', + 'kronecker_symbol', 'mobius', 'primenu', 'primeomega', 'totient', 'primepi', + 'reduced_totient', 'sqrt', 'root', 'Min', 'Max', 'Id', 'real_root', + 'Rem', 'cbrt', 're', 'im', 'sign', 'Abs', 'conjugate', 'arg', 'polar_lift', + 'periodic_argument', 'unbranched_argument', 'principal_branch', + 'transpose', 'adjoint', 'polarify', 'unpolarify', 'sin', 'cos', 'tan', + 'sec', 'csc', 'cot', 'sinc', 'asin', 'acos', 'atan', 'asec', 'acsc', + 'acot', 'atan2', 'exp_polar', 'exp', 'ln', 'log', 'LambertW', 'sinh', + 'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh', + 'acoth', 'asech', 'acsch', 'floor', 'ceiling', 'frac', 'Piecewise', + 'piecewise_fold', 'piecewise_exclusive', 'erf', 'erfc', 'erfi', 'erf2', + 'erfinv', 'erfcinv', 'erf2inv', 'Ei', 'expint', 'E1', 'li', 'Li', 'Si', + 'Ci', 'Shi', 'Chi', 'fresnels', 'fresnelc', 'gamma', 'lowergamma', + 'uppergamma', 'polygamma', 'loggamma', 'digamma', 'trigamma', 'multigamma', + 'dirichlet_eta', 'zeta', 'lerchphi', 'polylog', 'stieltjes', 'Eijk', 'LeviCivita', + 'KroneckerDelta', 'SingularityFunction', 'DiracDelta', 'Heaviside', + 'bspline_basis', 'bspline_basis_set', 'interpolating_spline', 'besselj', + 'bessely', 'besseli', 'besselk', 'hankel1', 'hankel2', 'jn', 'yn', + 'jn_zeros', 'hn1', 'hn2', 'airyai', 'airybi', 'airyaiprime', + 'airybiprime', 'marcumq', 'hyper', 'meijerg', 'appellf1', 'legendre', + 'assoc_legendre', 'hermite', 'hermite_prob', 'chebyshevt', 'chebyshevu', + 'chebyshevu_root', 'chebyshevt_root', 'laguerre', 'assoc_laguerre', + 'gegenbauer', 'jacobi', 'jacobi_normalized', 'Ynm', 'Ynm_c', 'Znm', + 'elliptic_k', 'elliptic_f', 'elliptic_e', 'elliptic_pi', 'beta', + 'mathieus', 'mathieuc', 'mathieusprime', 'mathieucprime', 'riemann_xi','betainc', + 'betainc_regularized', + + # sympy.ntheory + 'nextprime', 'prevprime', 'prime', 'primerange', 'randprime', + 'Sieve', 'sieve', 'primorial', 'cycle_length', 'composite', 'compositepi', + 'isprime', 'divisors', 'proper_divisors', 'factorint', 'multiplicity', + 'perfect_power', 'pollard_pm1', 'factor_cache', 'pollard_rho', 'primefactors', + 'divisor_count', 'proper_divisor_count', + 'factorrat', + 'mersenne_prime_exponent', 'is_perfect', 'is_mersenne_prime', + 'is_abundant', 'is_deficient', 'is_amicable', 'is_carmichael', 'abundance', + 'npartitions', + 'is_primitive_root', 'is_quad_residue', + 'n_order', 'sqrt_mod', 'quadratic_residues', + 'primitive_root', 'nthroot_mod', 'is_nthpow_residue', 'sqrt_mod_iter', + 'discrete_log', 'quadratic_congruence', 'binomial_coefficients', + 'binomial_coefficients_list', 'multinomial_coefficients', + 'continued_fraction_periodic', 'continued_fraction_iterator', + 'continued_fraction_reduce', 'continued_fraction_convergents', + 'continued_fraction', 'egyptian_fraction', + + # sympy.concrete + 'product', 'Product', 'summation', 'Sum', + + # sympy.discrete + 'fft', 'ifft', 'ntt', 'intt', 'fwht', 'ifwht', 'mobius_transform', + 'inverse_mobius_transform', 'convolution', 'covering_product', + 'intersecting_product', + + # sympy.simplify + 'simplify', 'hypersimp', 'hypersimilar', 'logcombine', 'separatevars', + 'posify', 'besselsimp', 'kroneckersimp', 'signsimp', + 'nsimplify', 'FU', 'fu', 'sqrtdenest', 'cse', 'epath', 'EPath', + 'hyperexpand', 'collect', 'rcollect', 'radsimp', 'collect_const', + 'fraction', 'numer', 'denom', 'trigsimp', 'exptrigsimp', 'powsimp', + 'powdenest', 'combsimp', 'gammasimp', 'ratsimp', 'ratsimpmodprime', + + # sympy.sets + 'Set', 'Interval', 'Union', 'EmptySet', 'FiniteSet', 'ProductSet', + 'Intersection', 'imageset', 'DisjointUnion', 'Complement', 'SymmetricDifference', + 'ImageSet', 'Range', 'ComplexRegion', 'Reals', 'Contains', 'ConditionSet', + 'Ordinal', 'OmegaPower', 'ord0', 'PowerSet', 'Naturals', + 'Naturals0', 'UniversalSet', 'Integers', 'Rationals', 'Complexes', + + # sympy.solvers + 'solve', 'solve_linear_system', 'solve_linear_system_LU', + 'solve_undetermined_coeffs', 'nsolve', 'solve_linear', 'checksol', + 'det_quick', 'inv_quick', 'check_assumptions', 'failing_assumptions', + 'diophantine', 'rsolve', 'rsolve_poly', 'rsolve_ratio', 'rsolve_hyper', + 'checkodesol', 'classify_ode', 'dsolve', 'homogeneous_order', + 'solve_poly_system', 'factor_system', 'solve_triangulated', 'pde_separate', + 'pde_separate_add', 'pde_separate_mul', 'pdsolve', 'classify_pde', + 'checkpdesol', 'ode_order', 'reduce_inequalities', + 'reduce_abs_inequality', 'reduce_abs_inequalities', + 'solve_poly_inequality', 'solve_rational_inequalities', + 'solve_univariate_inequality', 'decompogen', 'solveset', 'linsolve', + 'linear_eq_to_matrix', 'nonlinsolve', 'substitution', + + # sympy.matrices + 'ShapeError', 'NonSquareMatrixError', 'GramSchmidt', 'casoratian', 'diag', + 'eye', 'hessian', 'jordan_cell', 'list2numpy', 'matrix2numpy', + 'matrix_multiply_elementwise', 'ones', 'randMatrix', 'rot_axis1', + 'rot_axis2', 'rot_axis3', 'symarray', 'wronskian', 'zeros', + 'MutableDenseMatrix', 'DeferredVector', 'MatrixBase', 'Matrix', + 'MutableMatrix', 'MutableSparseMatrix', 'banded', 'ImmutableDenseMatrix', + 'ImmutableSparseMatrix', 'ImmutableMatrix', 'SparseMatrix', 'MatrixSlice', + 'BlockDiagMatrix', 'BlockMatrix', 'FunctionMatrix', 'Identity', 'Inverse', + 'MatAdd', 'MatMul', 'MatPow', 'MatrixExpr', 'MatrixSymbol', 'Trace', + 'Transpose', 'ZeroMatrix', 'OneMatrix', 'blockcut', 'block_collapse', + 'matrix_symbols', 'Adjoint', 'hadamard_product', 'HadamardProduct', + 'HadamardPower', 'Determinant', 'det', 'diagonalize_vector', 'DiagMatrix', + 'DiagonalMatrix', 'DiagonalOf', 'trace', 'DotProduct', + 'kronecker_product', 'KroneckerProduct', 'PermutationMatrix', + 'MatrixPermute', 'Permanent', 'per', 'rot_ccw_axis1', 'rot_ccw_axis2', + 'rot_ccw_axis3', 'rot_givens', + + # sympy.geometry + 'Point', 'Point2D', 'Point3D', 'Line', 'Ray', 'Segment', 'Line2D', + 'Segment2D', 'Ray2D', 'Line3D', 'Segment3D', 'Ray3D', 'Plane', 'Ellipse', + 'Circle', 'Polygon', 'RegularPolygon', 'Triangle', 'rad', 'deg', + 'are_similar', 'centroid', 'convex_hull', 'idiff', 'intersection', + 'closest_points', 'farthest_points', 'GeometryError', 'Curve', 'Parabola', + + # sympy.utilities + 'flatten', 'group', 'take', 'subsets', 'variations', 'numbered_symbols', + 'cartes', 'capture', 'dict_merge', 'prefixes', 'postfixes', 'sift', + 'topological_sort', 'unflatten', 'has_dups', 'has_variety', 'reshape', + 'rotations', 'filldedent', 'lambdify', 'threaded', 'xthreaded', + 'public', 'memoize_property', 'timed', + + # sympy.integrals + 'integrate', 'Integral', 'line_integrate', 'mellin_transform', + 'inverse_mellin_transform', 'MellinTransform', 'InverseMellinTransform', + 'laplace_transform', 'inverse_laplace_transform', 'LaplaceTransform', + 'laplace_correspondence', 'laplace_initial_conds', + 'InverseLaplaceTransform', 'fourier_transform', + 'inverse_fourier_transform', 'FourierTransform', + 'InverseFourierTransform', 'sine_transform', 'inverse_sine_transform', + 'SineTransform', 'InverseSineTransform', 'cosine_transform', + 'inverse_cosine_transform', 'CosineTransform', 'InverseCosineTransform', + 'hankel_transform', 'inverse_hankel_transform', 'HankelTransform', + 'InverseHankelTransform', 'singularityintegrate', + + # sympy.tensor + 'IndexedBase', 'Idx', 'Indexed', 'get_contraction_structure', + 'get_indices', 'shape', 'MutableDenseNDimArray', 'ImmutableDenseNDimArray', + 'MutableSparseNDimArray', 'ImmutableSparseNDimArray', 'NDimArray', + 'tensorproduct', 'tensorcontraction', 'tensordiagonal', 'derive_by_array', + 'permutedims', 'Array', 'DenseNDimArray', 'SparseNDimArray', + + # sympy.parsing + 'parse_expr', + + # sympy.calculus + 'euler_equations', 'singularities', 'is_increasing', + 'is_strictly_increasing', 'is_decreasing', 'is_strictly_decreasing', + 'is_monotonic', 'finite_diff_weights', 'apply_finite_diff', + 'differentiate_finite', 'periodicity', 'not_empty_in', + 'AccumBounds', 'is_convex', 'stationary_points', 'minimum', 'maximum', + + # sympy.algebras + 'Quaternion', + + # sympy.printing + 'pager_print', 'pretty', 'pretty_print', 'pprint', 'pprint_use_unicode', + 'pprint_try_use_unicode', 'latex', 'print_latex', 'multiline_latex', + 'mathml', 'print_mathml', 'python', 'print_python', 'pycode', 'ccode', + 'print_ccode', 'smtlib_code', 'glsl_code', 'print_glsl', 'cxxcode', 'fcode', + 'print_fcode', 'rcode', 'print_rcode', 'jscode', 'print_jscode', + 'julia_code', 'mathematica_code', 'octave_code', 'rust_code', 'print_gtk', + 'preview', 'srepr', 'print_tree', 'StrPrinter', 'sstr', 'sstrrepr', + 'TableForm', 'dotprint', 'maple_code', 'print_maple_code', + + # sympy.plotting + 'plot', 'textplot', 'plot_backends', 'plot_implicit', 'plot_parametric', + + # sympy.interactive + 'init_session', 'init_printing', 'interactive_traversal', + + # sympy.testing + 'test', 'doctest', +] + + +#===========================================================================# +# # +# XXX: The names below were importable before SymPy 1.6 using # +# # +# from sympy import * # +# # +# This happened implicitly because there was no __all__ defined in this # +# __init__.py file. Not every package is imported. The list matches what # +# would have been imported before. It is possible that these packages will # +# not be imported by a star-import from sympy in future. # +# # +#===========================================================================# + + +__all__.extend(( + 'algebras', + 'assumptions', + 'calculus', + 'concrete', + 'discrete', + 'external', + 'functions', + 'geometry', + 'interactive', + 'multipledispatch', + 'ntheory', + 'parsing', + 'plotting', + 'polys', + 'printing', + 'release', + 'strategies', + 'tensor', + 'utilities', +)) diff --git a/phivenv/Lib/site-packages/sympy/abc.py b/phivenv/Lib/site-packages/sympy/abc.py new file mode 100644 index 0000000000000000000000000000000000000000..a6f7a4056e60b0d651becc0b9909bd0ab3c1723f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/abc.py @@ -0,0 +1,111 @@ +""" +This module exports all latin and greek letters as Symbols, so you can +conveniently do + + >>> from sympy.abc import x, y + +instead of the slightly more clunky-looking + + >>> from sympy import symbols + >>> x, y = symbols('x y') + +Caveats +======= + +1. As of the time of writing this, the names ``O``, ``S``, ``I``, ``N``, +``E``, and ``Q`` are colliding with names defined in SymPy. If you import them +from both ``sympy.abc`` and ``sympy``, the second import will "win". +This is an issue only for * imports, which should only be used for short-lived +code such as interactive sessions and throwaway scripts that do not survive +until the next SymPy upgrade, where ``sympy`` may contain a different set of +names. + +2. This module does not define symbol names on demand, i.e. +``from sympy.abc import foo`` will be reported as an error because +``sympy.abc`` does not contain the name ``foo``. To get a symbol named ``foo``, +you still need to use ``Symbol('foo')`` or ``symbols('foo')``. +You can freely mix usage of ``sympy.abc`` and ``Symbol``/``symbols``, though +sticking with one and only one way to get the symbols does tend to make the code +more readable. + +The module also defines some special names to help detect which names clash +with the default SymPy namespace. + +``_clash1`` defines all the single letter variables that clash with +SymPy objects; ``_clash2`` defines the multi-letter clashing symbols; +and ``_clash`` is the union of both. These can be passed for ``locals`` +during sympification if one desires Symbols rather than the non-Symbol +objects for those names. + +Examples +======== + +>>> from sympy import S +>>> from sympy.abc import _clash1, _clash2, _clash +>>> S("Q & C", locals=_clash1) +C & Q +>>> S('pi(x)', locals=_clash2) +pi(x) +>>> S('pi(C, Q)', locals=_clash) +pi(C, Q) + +""" +from __future__ import annotations +from typing import Any + +import string + +from .core import Symbol, symbols +from .core.alphabets import greeks +from sympy.parsing.sympy_parser import null + +##### Symbol definitions ##### + +# Implementation note: The easiest way to avoid typos in the symbols() +# parameter is to copy it from the left-hand side of the assignment. + +a, b, c, d, e, f, g, h, i, j = symbols('a, b, c, d, e, f, g, h, i, j') +k, l, m, n, o, p, q, r, s, t = symbols('k, l, m, n, o, p, q, r, s, t') +u, v, w, x, y, z = symbols('u, v, w, x, y, z') + +A, B, C, D, E, F, G, H, I, J = symbols('A, B, C, D, E, F, G, H, I, J') +K, L, M, N, O, P, Q, R, S, T = symbols('K, L, M, N, O, P, Q, R, S, T') +U, V, W, X, Y, Z = symbols('U, V, W, X, Y, Z') + +alpha, beta, gamma, delta = symbols('alpha, beta, gamma, delta') +epsilon, zeta, eta, theta = symbols('epsilon, zeta, eta, theta') +iota, kappa, lamda, mu = symbols('iota, kappa, lamda, mu') +nu, xi, omicron, pi = symbols('nu, xi, omicron, pi') +rho, sigma, tau, upsilon = symbols('rho, sigma, tau, upsilon') +phi, chi, psi, omega = symbols('phi, chi, psi, omega') + + +##### Clashing-symbols diagnostics ##### + +# We want to know which names in SymPy collide with those in here. +# This is mostly for diagnosing SymPy's namespace during SymPy development. + +_latin = list(string.ascii_letters) +# QOSINE should not be imported as they clash; gamma, pi and zeta clash, too +_greek = list(greeks) # make a copy, so we can mutate it +# Note: We import lamda since lambda is a reserved keyword in Python +_greek.remove("lambda") +_greek.append("lamda") + +ns: dict[str, Any] = {} +exec('from sympy import *', ns) +_clash1: dict[str, Any] = {} +_clash2: dict[str, Any] = {} +while ns: + _k, _ = ns.popitem() + if _k in _greek: + _clash2[_k] = null + _greek.remove(_k) + elif _k in _latin: + _clash1[_k] = null + _latin.remove(_k) +_clash = {} +_clash.update(_clash1) +_clash.update(_clash2) + +del _latin, _greek, Symbol, _k, null diff --git a/phivenv/Lib/site-packages/sympy/algebras/__init__.py b/phivenv/Lib/site-packages/sympy/algebras/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..58013d2e0377a016d1a21fbf21c344ee76765189 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/algebras/__init__.py @@ -0,0 +1,3 @@ +from .quaternion import Quaternion + +__all__ = ["Quaternion",] diff --git a/phivenv/Lib/site-packages/sympy/algebras/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/algebras/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ee0a65a60c053dc1f22714d4743b25311aae182 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/algebras/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/algebras/__pycache__/quaternion.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/algebras/__pycache__/quaternion.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20c797d1789155ce3c4951f11f0d23f782a152e3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/algebras/__pycache__/quaternion.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/algebras/quaternion.py b/phivenv/Lib/site-packages/sympy/algebras/quaternion.py new file mode 100644 index 0000000000000000000000000000000000000000..1bf4b363545128e50294e825461106ef9b41990d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/algebras/quaternion.py @@ -0,0 +1,1666 @@ +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.relational import is_eq +from sympy.functions.elementary.complexes import (conjugate, im, re, sign) +from sympy.functions.elementary.exponential import (exp, log as ln) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, asin, atan2) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.simplify.trigsimp import trigsimp +from sympy.integrals.integrals import integrate +from sympy.matrices.dense import MutableDenseMatrix as Matrix +from sympy.core.sympify import sympify, _sympify +from sympy.core.expr import Expr +from sympy.core.logic import fuzzy_not, fuzzy_or +from sympy.utilities.misc import as_int + +from mpmath.libmp.libmpf import prec_to_dps + + +def _check_norm(elements, norm): + """validate if input norm is consistent""" + if norm is not None and norm.is_number: + if norm.is_positive is False: + raise ValueError("Input norm must be positive.") + + numerical = all(i.is_number and i.is_real is True for i in elements) + if numerical and is_eq(norm**2, sum(i**2 for i in elements)) is False: + raise ValueError("Incompatible value for norm.") + + +def _is_extrinsic(seq): + """validate seq and return True if seq is lowercase and False if uppercase""" + if type(seq) != str: + raise ValueError('Expected seq to be a string.') + if len(seq) != 3: + raise ValueError("Expected 3 axes, got `{}`.".format(seq)) + + intrinsic = seq.isupper() + extrinsic = seq.islower() + if not (intrinsic or extrinsic): + raise ValueError("seq must either be fully uppercase (for extrinsic " + "rotations), or fully lowercase, for intrinsic " + "rotations).") + + i, j, k = seq.lower() + if (i == j) or (j == k): + raise ValueError("Consecutive axes must be different") + + bad = set(seq) - set('xyzXYZ') + if bad: + raise ValueError("Expected axes from `seq` to be from " + "['x', 'y', 'z'] or ['X', 'Y', 'Z'], " + "got {}".format(''.join(bad))) + + return extrinsic + + +class Quaternion(Expr): + """Provides basic quaternion operations. + Quaternion objects can be instantiated as ``Quaternion(a, b, c, d)`` + as in $q = a + bi + cj + dk$. + + Parameters + ========== + + norm : None or number + Pre-defined quaternion norm. If a value is given, Quaternion.norm + returns this pre-defined value instead of calculating the norm + + Examples + ======== + + >>> from sympy import Quaternion + >>> q = Quaternion(1, 2, 3, 4) + >>> q + 1 + 2*i + 3*j + 4*k + + Quaternions over complex fields can be defined as: + + >>> from sympy import Quaternion + >>> from sympy import symbols, I + >>> x = symbols('x') + >>> q1 = Quaternion(x, x**3, x, x**2, real_field = False) + >>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False) + >>> q1 + x + x**3*i + x*j + x**2*k + >>> q2 + (3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k + + Defining symbolic unit quaternions: + + >>> from sympy import Quaternion + >>> from sympy.abc import w, x, y, z + >>> q = Quaternion(w, x, y, z, norm=1) + >>> q + w + x*i + y*j + z*k + >>> q.norm() + 1 + + References + ========== + + .. [1] https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/ + .. [2] https://en.wikipedia.org/wiki/Quaternion + + """ + _op_priority = 11.0 + + is_commutative = False + + def __new__(cls, a=0, b=0, c=0, d=0, real_field=True, norm=None): + a, b, c, d = map(sympify, (a, b, c, d)) + + if any(i.is_commutative is False for i in [a, b, c, d]): + raise ValueError("arguments have to be commutative") + obj = super().__new__(cls, a, b, c, d) + obj._real_field = real_field + obj.set_norm(norm) + return obj + + def set_norm(self, norm): + """Sets norm of an already instantiated quaternion. + + Parameters + ========== + + norm : None or number + Pre-defined quaternion norm. If a value is given, Quaternion.norm + returns this pre-defined value instead of calculating the norm + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy.abc import a, b, c, d + >>> q = Quaternion(a, b, c, d) + >>> q.norm() + sqrt(a**2 + b**2 + c**2 + d**2) + + Setting the norm: + + >>> q.set_norm(1) + >>> q.norm() + 1 + + Removing set norm: + + >>> q.set_norm(None) + >>> q.norm() + sqrt(a**2 + b**2 + c**2 + d**2) + + """ + norm = sympify(norm) + _check_norm(self.args, norm) + self._norm = norm + + @property + def a(self): + return self.args[0] + + @property + def b(self): + return self.args[1] + + @property + def c(self): + return self.args[2] + + @property + def d(self): + return self.args[3] + + @property + def real_field(self): + return self._real_field + + @property + def product_matrix_left(self): + r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the + left. This can be useful when treating quaternion elements as column + vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d + are real numbers, the product matrix from the left is: + + .. math:: + + M = \begin{bmatrix} a &-b &-c &-d \\ + b & a &-d & c \\ + c & d & a &-b \\ + d &-c & b & a \end{bmatrix} + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy.abc import a, b, c, d + >>> q1 = Quaternion(1, 0, 0, 1) + >>> q2 = Quaternion(a, b, c, d) + >>> q1.product_matrix_left + Matrix([ + [1, 0, 0, -1], + [0, 1, -1, 0], + [0, 1, 1, 0], + [1, 0, 0, 1]]) + + >>> q1.product_matrix_left * q2.to_Matrix() + Matrix([ + [a - d], + [b - c], + [b + c], + [a + d]]) + + This is equivalent to: + + >>> (q1 * q2).to_Matrix() + Matrix([ + [a - d], + [b - c], + [b + c], + [a + d]]) + """ + return Matrix([ + [self.a, -self.b, -self.c, -self.d], + [self.b, self.a, -self.d, self.c], + [self.c, self.d, self.a, -self.b], + [self.d, -self.c, self.b, self.a]]) + + @property + def product_matrix_right(self): + r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the + right. This can be useful when treating quaternion elements as column + vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d + are real numbers, the product matrix from the left is: + + .. math:: + + M = \begin{bmatrix} a &-b &-c &-d \\ + b & a & d &-c \\ + c &-d & a & b \\ + d & c &-b & a \end{bmatrix} + + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy.abc import a, b, c, d + >>> q1 = Quaternion(a, b, c, d) + >>> q2 = Quaternion(1, 0, 0, 1) + >>> q2.product_matrix_right + Matrix([ + [1, 0, 0, -1], + [0, 1, 1, 0], + [0, -1, 1, 0], + [1, 0, 0, 1]]) + + Note the switched arguments: the matrix represents the quaternion on + the right, but is still considered as a matrix multiplication from the + left. + + >>> q2.product_matrix_right * q1.to_Matrix() + Matrix([ + [ a - d], + [ b + c], + [-b + c], + [ a + d]]) + + This is equivalent to: + + >>> (q1 * q2).to_Matrix() + Matrix([ + [ a - d], + [ b + c], + [-b + c], + [ a + d]]) + """ + return Matrix([ + [self.a, -self.b, -self.c, -self.d], + [self.b, self.a, self.d, -self.c], + [self.c, -self.d, self.a, self.b], + [self.d, self.c, -self.b, self.a]]) + + def to_Matrix(self, vector_only=False): + """Returns elements of quaternion as a column vector. + By default, a ``Matrix`` of length 4 is returned, with the real part as the + first element. + If ``vector_only`` is ``True``, returns only imaginary part as a Matrix of + length 3. + + Parameters + ========== + + vector_only : bool + If True, only imaginary part is returned. + Default value: False + + Returns + ======= + + Matrix + A column vector constructed by the elements of the quaternion. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy.abc import a, b, c, d + >>> q = Quaternion(a, b, c, d) + >>> q + a + b*i + c*j + d*k + + >>> q.to_Matrix() + Matrix([ + [a], + [b], + [c], + [d]]) + + + >>> q.to_Matrix(vector_only=True) + Matrix([ + [b], + [c], + [d]]) + + """ + if vector_only: + return Matrix(self.args[1:]) + else: + return Matrix(self.args) + + @classmethod + def from_Matrix(cls, elements): + """Returns quaternion from elements of a column vector`. + If vector_only is True, returns only imaginary part as a Matrix of + length 3. + + Parameters + ========== + + elements : Matrix, list or tuple of length 3 or 4. If length is 3, + assume real part is zero. + Default value: False + + Returns + ======= + + Quaternion + A quaternion created from the input elements. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy.abc import a, b, c, d + >>> q = Quaternion.from_Matrix([a, b, c, d]) + >>> q + a + b*i + c*j + d*k + + >>> q = Quaternion.from_Matrix([b, c, d]) + >>> q + 0 + b*i + c*j + d*k + + """ + length = len(elements) + if length != 3 and length != 4: + raise ValueError("Input elements must have length 3 or 4, got {} " + "elements".format(length)) + + if length == 3: + return Quaternion(0, *elements) + else: + return Quaternion(*elements) + + @classmethod + def from_euler(cls, angles, seq): + """Returns quaternion equivalent to rotation represented by the Euler + angles, in the sequence defined by ``seq``. + + Parameters + ========== + + angles : list, tuple or Matrix of 3 numbers + The Euler angles (in radians). + seq : string of length 3 + Represents the sequence of rotations. + For extrinsic rotations, seq must be all lowercase and its elements + must be from the set ``{'x', 'y', 'z'}`` + For intrinsic rotations, seq must be all uppercase and its elements + must be from the set ``{'X', 'Y', 'Z'}`` + + Returns + ======= + + Quaternion + The normalized rotation quaternion calculated from the Euler angles + in the given sequence. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import pi + >>> q = Quaternion.from_euler([pi/2, 0, 0], 'xyz') + >>> q + sqrt(2)/2 + sqrt(2)/2*i + 0*j + 0*k + + >>> q = Quaternion.from_euler([0, pi/2, pi] , 'zyz') + >>> q + 0 + (-sqrt(2)/2)*i + 0*j + sqrt(2)/2*k + + >>> q = Quaternion.from_euler([0, pi/2, pi] , 'ZYZ') + >>> q + 0 + sqrt(2)/2*i + 0*j + sqrt(2)/2*k + + """ + + if len(angles) != 3: + raise ValueError("3 angles must be given.") + + extrinsic = _is_extrinsic(seq) + i, j, k = seq.lower() + + # get elementary basis vectors + ei = [1 if n == i else 0 for n in 'xyz'] + ej = [1 if n == j else 0 for n in 'xyz'] + ek = [1 if n == k else 0 for n in 'xyz'] + + # calculate distinct quaternions + qi = cls.from_axis_angle(ei, angles[0]) + qj = cls.from_axis_angle(ej, angles[1]) + qk = cls.from_axis_angle(ek, angles[2]) + + if extrinsic: + return trigsimp(qk * qj * qi) + else: + return trigsimp(qi * qj * qk) + + def to_euler(self, seq, angle_addition=True, avoid_square_root=False): + r"""Returns Euler angles representing same rotation as the quaternion, + in the sequence given by ``seq``. This implements the method described + in [1]_. + + For degenerate cases (gymbal lock cases), the third angle is + set to zero. + + Parameters + ========== + + seq : string of length 3 + Represents the sequence of rotations. + For extrinsic rotations, seq must be all lowercase and its elements + must be from the set ``{'x', 'y', 'z'}`` + For intrinsic rotations, seq must be all uppercase and its elements + must be from the set ``{'X', 'Y', 'Z'}`` + + angle_addition : bool + When True, first and third angles are given as an addition and + subtraction of two simpler ``atan2`` expressions. When False, the + first and third angles are each given by a single more complicated + ``atan2`` expression. This equivalent expression is given by: + + .. math:: + + \operatorname{atan_2} (b,a) \pm \operatorname{atan_2} (d,c) = + \operatorname{atan_2} (bc\pm ad, ac\mp bd) + + Default value: True + + avoid_square_root : bool + When True, the second angle is calculated with an expression based + on ``acos``, which is slightly more complicated but avoids a square + root. When False, second angle is calculated with ``atan2``, which + is simpler and can be better for numerical reasons (some + numerical implementations of ``acos`` have problems near zero). + Default value: False + + + Returns + ======= + + Tuple + The Euler angles calculated from the quaternion + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy.abc import a, b, c, d + >>> euler = Quaternion(a, b, c, d).to_euler('zyz') + >>> euler + (-atan2(-b, c) + atan2(d, a), + 2*atan2(sqrt(b**2 + c**2), sqrt(a**2 + d**2)), + atan2(-b, c) + atan2(d, a)) + + + References + ========== + + .. [1] https://doi.org/10.1371/journal.pone.0276302 + + """ + if self.is_zero_quaternion(): + raise ValueError('Cannot convert a quaternion with norm 0.') + + angles = [0, 0, 0] + + extrinsic = _is_extrinsic(seq) + i, j, k = seq.lower() + + # get index corresponding to elementary basis vectors + i = 'xyz'.index(i) + 1 + j = 'xyz'.index(j) + 1 + k = 'xyz'.index(k) + 1 + + if not extrinsic: + i, k = k, i + + # check if sequence is symmetric + symmetric = i == k + if symmetric: + k = 6 - i - j + + # parity of the permutation + sign = (i - j) * (j - k) * (k - i) // 2 + + # permutate elements + elements = [self.a, self.b, self.c, self.d] + a = elements[0] + b = elements[i] + c = elements[j] + d = elements[k] * sign + + if not symmetric: + a, b, c, d = a - c, b + d, c + a, d - b + + if avoid_square_root: + if symmetric: + n2 = self.norm()**2 + angles[1] = acos((a * a + b * b - c * c - d * d) / n2) + else: + n2 = 2 * self.norm()**2 + angles[1] = asin((c * c + d * d - a * a - b * b) / n2) + else: + angles[1] = 2 * atan2(sqrt(c * c + d * d), sqrt(a * a + b * b)) + if not symmetric: + angles[1] -= S.Pi / 2 + + # Check for singularities in numerical cases + case = 0 + if is_eq(c, S.Zero) and is_eq(d, S.Zero): + case = 1 + if is_eq(a, S.Zero) and is_eq(b, S.Zero): + case = 2 + + if case == 0: + if angle_addition: + angles[0] = atan2(b, a) + atan2(d, c) + angles[2] = atan2(b, a) - atan2(d, c) + else: + angles[0] = atan2(b*c + a*d, a*c - b*d) + angles[2] = atan2(b*c - a*d, a*c + b*d) + + else: # any degenerate case + angles[2 * (not extrinsic)] = S.Zero + if case == 1: + angles[2 * extrinsic] = 2 * atan2(b, a) + else: + angles[2 * extrinsic] = 2 * atan2(d, c) + angles[2 * extrinsic] *= (-1 if extrinsic else 1) + + # for Tait-Bryan angles + if not symmetric: + angles[0] *= sign + + if extrinsic: + return tuple(angles[::-1]) + else: + return tuple(angles) + + @classmethod + def from_axis_angle(cls, vector, angle): + """Returns a rotation quaternion given the axis and the angle of rotation. + + Parameters + ========== + + vector : tuple of three numbers + The vector representation of the given axis. + angle : number + The angle by which axis is rotated (in radians). + + Returns + ======= + + Quaternion + The normalized rotation quaternion calculated from the given axis and the angle of rotation. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import pi, sqrt + >>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3) + >>> q + 1/2 + 1/2*i + 1/2*j + 1/2*k + + """ + (x, y, z) = vector + norm = sqrt(x**2 + y**2 + z**2) + (x, y, z) = (x / norm, y / norm, z / norm) + s = sin(angle * S.Half) + a = cos(angle * S.Half) + b = x * s + c = y * s + d = z * s + + # note that this quaternion is already normalized by construction: + # c^2 + (s*x)^2 + (s*y)^2 + (s*z)^2 = c^2 + s^2*(x^2 + y^2 + z^2) = c^2 + s^2 * 1 = c^2 + s^2 = 1 + # so, what we return is a normalized quaternion + + return cls(a, b, c, d) + + @classmethod + def from_rotation_matrix(cls, M): + """Returns the equivalent quaternion of a matrix. The quaternion will be normalized + only if the matrix is special orthogonal (orthogonal and det(M) = 1). + + Parameters + ========== + + M : Matrix + Input matrix to be converted to equivalent quaternion. M must be special + orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized. + + Returns + ======= + + Quaternion + The quaternion equivalent to given matrix. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import Matrix, symbols, cos, sin, trigsimp + >>> x = symbols('x') + >>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]]) + >>> q = trigsimp(Quaternion.from_rotation_matrix(M)) + >>> q + sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k + + """ + + absQ = M.det()**Rational(1, 3) + + a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2 + b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2 + c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2 + d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2 + + b = b * sign(M[2, 1] - M[1, 2]) + c = c * sign(M[0, 2] - M[2, 0]) + d = d * sign(M[1, 0] - M[0, 1]) + + return Quaternion(a, b, c, d) + + def __add__(self, other): + return self.add(other) + + def __radd__(self, other): + return self.add(other) + + def __sub__(self, other): + return self.add(other*-1) + + def __mul__(self, other): + return self._generic_mul(self, _sympify(other)) + + def __rmul__(self, other): + return self._generic_mul(_sympify(other), self) + + def __pow__(self, p): + return self.pow(p) + + def __neg__(self): + return Quaternion(-self.a, -self.b, -self.c, -self.d) + + def __truediv__(self, other): + return self * sympify(other)**-1 + + def __rtruediv__(self, other): + return sympify(other) * self**-1 + + def _eval_Integral(self, *args): + return self.integrate(*args) + + def diff(self, *symbols, **kwargs): + kwargs.setdefault('evaluate', True) + return self.func(*[a.diff(*symbols, **kwargs) for a in self.args]) + + def add(self, other): + """Adds quaternions. + + Parameters + ========== + + other : Quaternion + The quaternion to add to current (self) quaternion. + + Returns + ======= + + Quaternion + The resultant quaternion after adding self to other + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import symbols + >>> q1 = Quaternion(1, 2, 3, 4) + >>> q2 = Quaternion(5, 6, 7, 8) + >>> q1.add(q2) + 6 + 8*i + 10*j + 12*k + >>> q1 + 5 + 6 + 2*i + 3*j + 4*k + >>> x = symbols('x', real = True) + >>> q1.add(x) + (x + 1) + 2*i + 3*j + 4*k + + Quaternions over complex fields : + + >>> from sympy import Quaternion + >>> from sympy import I + >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False) + >>> q3.add(2 + 3*I) + (5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k + + """ + q1 = self + q2 = sympify(other) + + # If q2 is a number or a SymPy expression instead of a quaternion + if not isinstance(q2, Quaternion): + if q1.real_field and q2.is_complex: + return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d) + elif q2.is_commutative: + return Quaternion(q1.a + q2, q1.b, q1.c, q1.d) + else: + raise ValueError("Only commutative expressions can be added with a Quaternion.") + + return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d + + q2.d) + + def mul(self, other): + """Multiplies quaternions. + + Parameters + ========== + + other : Quaternion or symbol + The quaternion to multiply to current (self) quaternion. + + Returns + ======= + + Quaternion + The resultant quaternion after multiplying self with other + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import symbols + >>> q1 = Quaternion(1, 2, 3, 4) + >>> q2 = Quaternion(5, 6, 7, 8) + >>> q1.mul(q2) + (-60) + 12*i + 30*j + 24*k + >>> q1.mul(2) + 2 + 4*i + 6*j + 8*k + >>> x = symbols('x', real = True) + >>> q1.mul(x) + x + 2*x*i + 3*x*j + 4*x*k + + Quaternions over complex fields : + + >>> from sympy import Quaternion + >>> from sympy import I + >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False) + >>> q3.mul(2 + 3*I) + (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k + + """ + return self._generic_mul(self, _sympify(other)) + + @staticmethod + def _generic_mul(q1, q2): + """Generic multiplication. + + Parameters + ========== + + q1 : Quaternion or symbol + q2 : Quaternion or symbol + + It is important to note that if neither q1 nor q2 is a Quaternion, + this function simply returns q1 * q2. + + Returns + ======= + + Quaternion + The resultant quaternion after multiplying q1 and q2 + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import Symbol, S + >>> q1 = Quaternion(1, 2, 3, 4) + >>> q2 = Quaternion(5, 6, 7, 8) + >>> Quaternion._generic_mul(q1, q2) + (-60) + 12*i + 30*j + 24*k + >>> Quaternion._generic_mul(q1, S(2)) + 2 + 4*i + 6*j + 8*k + >>> x = Symbol('x', real = True) + >>> Quaternion._generic_mul(q1, x) + x + 2*x*i + 3*x*j + 4*x*k + + Quaternions over complex fields : + + >>> from sympy import I + >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False) + >>> Quaternion._generic_mul(q3, 2 + 3*I) + (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k + + """ + # None is a Quaternion: + if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion): + return q1 * q2 + + # If q1 is a number or a SymPy expression instead of a quaternion + if not isinstance(q1, Quaternion): + if q2.real_field and q1.is_complex: + return Quaternion(re(q1), im(q1), 0, 0) * q2 + elif q1.is_commutative: + return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d) + else: + raise ValueError("Only commutative expressions can be multiplied with a Quaternion.") + + # If q2 is a number or a SymPy expression instead of a quaternion + if not isinstance(q2, Quaternion): + if q1.real_field and q2.is_complex: + return q1 * Quaternion(re(q2), im(q2), 0, 0) + elif q2.is_commutative: + return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d) + else: + raise ValueError("Only commutative expressions can be multiplied with a Quaternion.") + + # If any of the quaternions has a fixed norm, pre-compute norm + if q1._norm is None and q2._norm is None: + norm = None + else: + norm = q1.norm() * q2.norm() + + return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a, + q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b, + -q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c, + q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d, + norm=norm) + + def _eval_conjugate(self): + """Returns the conjugate of the quaternion.""" + q = self + return Quaternion(q.a, -q.b, -q.c, -q.d, norm=q._norm) + + def norm(self): + """Returns the norm of the quaternion.""" + if self._norm is None: # check if norm is pre-defined + q = self + # trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms + # arise when from_axis_angle is used). + return sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2)) + + return self._norm + + def normalize(self): + """Returns the normalized form of the quaternion.""" + q = self + return q * (1/q.norm()) + + def inverse(self): + """Returns the inverse of the quaternion.""" + q = self + if not q.norm(): + raise ValueError("Cannot compute inverse for a quaternion with zero norm") + return conjugate(q) * (1/q.norm()**2) + + def pow(self, p): + """Finds the pth power of the quaternion. + + Parameters + ========== + + p : int + Power to be applied on quaternion. + + Returns + ======= + + Quaternion + Returns the p-th power of the current quaternion. + Returns the inverse if p = -1. + + Examples + ======== + + >>> from sympy import Quaternion + >>> q = Quaternion(1, 2, 3, 4) + >>> q.pow(4) + 668 + (-224)*i + (-336)*j + (-448)*k + + """ + try: + q, p = self, as_int(p) + except ValueError: + return NotImplemented + + if p < 0: + q, p = q.inverse(), -p + + if p == 1: + return q + + res = Quaternion(1, 0, 0, 0) + while p > 0: + if p & 1: + res *= q + q *= q + p >>= 1 + + return res + + def exp(self): + """Returns the exponential of $q$, given by $e^q$. + + Returns + ======= + + Quaternion + The exponential of the quaternion. + + Examples + ======== + + >>> from sympy import Quaternion + >>> q = Quaternion(1, 2, 3, 4) + >>> q.exp() + E*cos(sqrt(29)) + + 2*sqrt(29)*E*sin(sqrt(29))/29*i + + 3*sqrt(29)*E*sin(sqrt(29))/29*j + + 4*sqrt(29)*E*sin(sqrt(29))/29*k + + """ + # exp(q) = e^a(cos||v|| + v/||v||*sin||v||) + q = self + vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2) + a = exp(q.a) * cos(vector_norm) + b = exp(q.a) * sin(vector_norm) * q.b / vector_norm + c = exp(q.a) * sin(vector_norm) * q.c / vector_norm + d = exp(q.a) * sin(vector_norm) * q.d / vector_norm + + return Quaternion(a, b, c, d) + + def log(self): + r"""Returns the logarithm of the quaternion, given by $\log q$. + + Examples + ======== + + >>> from sympy import Quaternion + >>> q = Quaternion(1, 2, 3, 4) + >>> q.log() + log(sqrt(30)) + + 2*sqrt(29)*acos(sqrt(30)/30)/29*i + + 3*sqrt(29)*acos(sqrt(30)/30)/29*j + + 4*sqrt(29)*acos(sqrt(30)/30)/29*k + + """ + # log(q) = log||q|| + v/||v||*arccos(a/||q||) + q = self + vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2) + q_norm = q.norm() + a = ln(q_norm) + b = q.b * acos(q.a / q_norm) / vector_norm + c = q.c * acos(q.a / q_norm) / vector_norm + d = q.d * acos(q.a / q_norm) / vector_norm + + return Quaternion(a, b, c, d) + + def _eval_subs(self, *args): + elements = [i.subs(*args) for i in self.args] + norm = self._norm + if norm is not None: + norm = norm.subs(*args) + _check_norm(elements, norm) + return Quaternion(*elements, norm=norm) + + def _eval_evalf(self, prec): + """Returns the floating point approximations (decimal numbers) of the quaternion. + + Returns + ======= + + Quaternion + Floating point approximations of quaternion(self) + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import sqrt + >>> q = Quaternion(1/sqrt(1), 1/sqrt(2), 1/sqrt(3), 1/sqrt(4)) + >>> q.evalf() + 1.00000000000000 + + 0.707106781186547*i + + 0.577350269189626*j + + 0.500000000000000*k + + """ + nprec = prec_to_dps(prec) + return Quaternion(*[arg.evalf(n=nprec) for arg in self.args]) + + def pow_cos_sin(self, p): + """Computes the pth power in the cos-sin form. + + Parameters + ========== + + p : int + Power to be applied on quaternion. + + Returns + ======= + + Quaternion + The p-th power in the cos-sin form. + + Examples + ======== + + >>> from sympy import Quaternion + >>> q = Quaternion(1, 2, 3, 4) + >>> q.pow_cos_sin(4) + 900*cos(4*acos(sqrt(30)/30)) + + 1800*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*i + + 2700*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*j + + 3600*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*k + + """ + # q = ||q||*(cos(a) + u*sin(a)) + # q^p = ||q||^p * (cos(p*a) + u*sin(p*a)) + + q = self + (v, angle) = q.to_axis_angle() + q2 = Quaternion.from_axis_angle(v, p * angle) + return q2 * (q.norm()**p) + + def integrate(self, *args): + """Computes integration of quaternion. + + Returns + ======= + + Quaternion + Integration of the quaternion(self) with the given variable. + + Examples + ======== + + Indefinite Integral of quaternion : + + >>> from sympy import Quaternion + >>> from sympy.abc import x + >>> q = Quaternion(1, 2, 3, 4) + >>> q.integrate(x) + x + 2*x*i + 3*x*j + 4*x*k + + Definite integral of quaternion : + + >>> from sympy import Quaternion + >>> from sympy.abc import x + >>> q = Quaternion(1, 2, 3, 4) + >>> q.integrate((x, 1, 5)) + 4 + 8*i + 12*j + 16*k + + """ + return Quaternion(integrate(self.a, *args), integrate(self.b, *args), + integrate(self.c, *args), integrate(self.d, *args)) + + @staticmethod + def rotate_point(pin, r): + """Returns the coordinates of the point pin (a 3 tuple) after rotation. + + Parameters + ========== + + pin : tuple + A 3-element tuple of coordinates of a point which needs to be + rotated. + r : Quaternion or tuple + Axis and angle of rotation. + + It's important to note that when r is a tuple, it must be of the form + (axis, angle) + + Returns + ======= + + tuple + The coordinates of the point after rotation. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import symbols, trigsimp, cos, sin + >>> x = symbols('x') + >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2)) + >>> trigsimp(Quaternion.rotate_point((1, 1, 1), q)) + (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1) + >>> (axis, angle) = q.to_axis_angle() + >>> trigsimp(Quaternion.rotate_point((1, 1, 1), (axis, angle))) + (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1) + + """ + if isinstance(r, tuple): + # if r is of the form (vector, angle) + q = Quaternion.from_axis_angle(r[0], r[1]) + else: + # if r is a quaternion + q = r.normalize() + pout = q * Quaternion(0, pin[0], pin[1], pin[2]) * conjugate(q) + return (pout.b, pout.c, pout.d) + + def to_axis_angle(self): + """Returns the axis and angle of rotation of a quaternion. + + Returns + ======= + + tuple + Tuple of (axis, angle) + + Examples + ======== + + >>> from sympy import Quaternion + >>> q = Quaternion(1, 1, 1, 1) + >>> (axis, angle) = q.to_axis_angle() + >>> axis + (sqrt(3)/3, sqrt(3)/3, sqrt(3)/3) + >>> angle + 2*pi/3 + + """ + q = self + if q.a.is_negative: + q = q * -1 + + q = q.normalize() + angle = trigsimp(2 * acos(q.a)) + + # Since quaternion is normalised, q.a is less than 1. + s = sqrt(1 - q.a*q.a) + + x = trigsimp(q.b / s) + y = trigsimp(q.c / s) + z = trigsimp(q.d / s) + + v = (x, y, z) + t = (v, angle) + + return t + + def to_rotation_matrix(self, v=None, homogeneous=True): + """Returns the equivalent rotation transformation matrix of the quaternion + which represents rotation about the origin if ``v`` is not passed. + + Parameters + ========== + + v : tuple or None + Default value: None + homogeneous : bool + When True, gives an expression that may be more efficient for + symbolic calculations but less so for direct evaluation. Both + formulas are mathematically equivalent. + Default value: True + + Returns + ======= + + tuple + Returns the equivalent rotation transformation matrix of the quaternion + which represents rotation about the origin if v is not passed. + + Examples + ======== + + >>> from sympy import Quaternion + >>> from sympy import symbols, trigsimp, cos, sin + >>> x = symbols('x') + >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2)) + >>> trigsimp(q.to_rotation_matrix()) + Matrix([ + [cos(x), -sin(x), 0], + [sin(x), cos(x), 0], + [ 0, 0, 1]]) + + Generates a 4x4 transformation matrix (used for rotation about a point + other than the origin) if the point(v) is passed as an argument. + """ + + q = self + s = q.norm()**-2 + + # diagonal elements are different according to parameter normal + if homogeneous: + m00 = s*(q.a**2 + q.b**2 - q.c**2 - q.d**2) + m11 = s*(q.a**2 - q.b**2 + q.c**2 - q.d**2) + m22 = s*(q.a**2 - q.b**2 - q.c**2 + q.d**2) + else: + m00 = 1 - 2*s*(q.c**2 + q.d**2) + m11 = 1 - 2*s*(q.b**2 + q.d**2) + m22 = 1 - 2*s*(q.b**2 + q.c**2) + + m01 = 2*s*(q.b*q.c - q.d*q.a) + m02 = 2*s*(q.b*q.d + q.c*q.a) + + m10 = 2*s*(q.b*q.c + q.d*q.a) + m12 = 2*s*(q.c*q.d - q.b*q.a) + + m20 = 2*s*(q.b*q.d - q.c*q.a) + m21 = 2*s*(q.c*q.d + q.b*q.a) + + if not v: + return Matrix([[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]]) + + else: + (x, y, z) = v + + m03 = x - x*m00 - y*m01 - z*m02 + m13 = y - x*m10 - y*m11 - z*m12 + m23 = z - x*m20 - y*m21 - z*m22 + m30 = m31 = m32 = 0 + m33 = 1 + + return Matrix([[m00, m01, m02, m03], [m10, m11, m12, m13], + [m20, m21, m22, m23], [m30, m31, m32, m33]]) + + def scalar_part(self): + r"""Returns scalar part($\mathbf{S}(q)$) of the quaternion q. + + Explanation + =========== + + Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{S}(q) = a$. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(4, 8, 13, 12) + >>> q.scalar_part() + 4 + + """ + + return self.a + + def vector_part(self): + r""" + Returns $\mathbf{V}(q)$, the vector part of the quaternion $q$. + + Explanation + =========== + + Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{V}(q) = bi + cj + dk$. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(1, 1, 1, 1) + >>> q.vector_part() + 0 + 1*i + 1*j + 1*k + + >>> q = Quaternion(4, 8, 13, 12) + >>> q.vector_part() + 0 + 8*i + 13*j + 12*k + + """ + + return Quaternion(0, self.b, self.c, self.d) + + def axis(self): + r""" + Returns $\mathbf{Ax}(q)$, the axis of the quaternion $q$. + + Explanation + =========== + + Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{Ax}(q)$ i.e., the versor of the vector part of that quaternion + equal to $\mathbf{U}[\mathbf{V}(q)]$. + The axis is always an imaginary unit with square equal to $-1 + 0i + 0j + 0k$. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(1, 1, 1, 1) + >>> q.axis() + 0 + sqrt(3)/3*i + sqrt(3)/3*j + sqrt(3)/3*k + + See Also + ======== + + vector_part + + """ + axis = self.vector_part().normalize() + + return Quaternion(0, axis.b, axis.c, axis.d) + + def is_pure(self): + """ + Returns true if the quaternion is pure, false if the quaternion is not pure + or returns none if it is unknown. + + Explanation + =========== + + A pure quaternion (also a vector quaternion) is a quaternion with scalar + part equal to 0. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(0, 8, 13, 12) + >>> q.is_pure() + True + + See Also + ======== + scalar_part + + """ + + return self.a.is_zero + + def is_zero_quaternion(self): + """ + Returns true if the quaternion is a zero quaternion or false if it is not a zero quaternion + and None if the value is unknown. + + Explanation + =========== + + A zero quaternion is a quaternion with both scalar part and + vector part equal to 0. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(1, 0, 0, 0) + >>> q.is_zero_quaternion() + False + + >>> q = Quaternion(0, 0, 0, 0) + >>> q.is_zero_quaternion() + True + + See Also + ======== + scalar_part + vector_part + + """ + + return self.norm().is_zero + + def angle(self): + r""" + Returns the angle of the quaternion measured in the real-axis plane. + + Explanation + =========== + + Given a quaternion $q = a + bi + cj + dk$ where $a$, $b$, $c$ and $d$ + are real numbers, returns the angle of the quaternion given by + + .. math:: + \theta := 2 \operatorname{atan_2}\left(\sqrt{b^2 + c^2 + d^2}, {a}\right) + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(1, 4, 4, 4) + >>> q.angle() + 2*atan(4*sqrt(3)) + + """ + + return 2 * atan2(self.vector_part().norm(), self.scalar_part()) + + + def arc_coplanar(self, other): + """ + Returns True if the transformation arcs represented by the input quaternions happen in the same plane. + + Explanation + =========== + + Two quaternions are said to be coplanar (in this arc sense) when their axes are parallel. + The plane of a quaternion is the one normal to its axis. + + Parameters + ========== + + other : a Quaternion + + Returns + ======= + + True : if the planes of the two quaternions are the same, apart from its orientation/sign. + False : if the planes of the two quaternions are not the same, apart from its orientation/sign. + None : if plane of either of the quaternion is unknown. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q1 = Quaternion(1, 4, 4, 4) + >>> q2 = Quaternion(3, 8, 8, 8) + >>> Quaternion.arc_coplanar(q1, q2) + True + + >>> q1 = Quaternion(2, 8, 13, 12) + >>> Quaternion.arc_coplanar(q1, q2) + False + + See Also + ======== + + vector_coplanar + is_pure + + """ + if (self.is_zero_quaternion()) or (other.is_zero_quaternion()): + raise ValueError('Neither of the given quaternions can be 0') + + return fuzzy_or([(self.axis() - other.axis()).is_zero_quaternion(), (self.axis() + other.axis()).is_zero_quaternion()]) + + @classmethod + def vector_coplanar(cls, q1, q2, q3): + r""" + Returns True if the axis of the pure quaternions seen as 3D vectors + ``q1``, ``q2``, and ``q3`` are coplanar. + + Explanation + =========== + + Three pure quaternions are vector coplanar if the quaternions seen as 3D vectors are coplanar. + + Parameters + ========== + + q1 + A pure Quaternion. + q2 + A pure Quaternion. + q3 + A pure Quaternion. + + Returns + ======= + + True : if the axis of the pure quaternions seen as 3D vectors + q1, q2, and q3 are coplanar. + False : if the axis of the pure quaternions seen as 3D vectors + q1, q2, and q3 are not coplanar. + None : if the axis of the pure quaternions seen as 3D vectors + q1, q2, and q3 are coplanar is unknown. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q1 = Quaternion(0, 4, 4, 4) + >>> q2 = Quaternion(0, 8, 8, 8) + >>> q3 = Quaternion(0, 24, 24, 24) + >>> Quaternion.vector_coplanar(q1, q2, q3) + True + + >>> q1 = Quaternion(0, 8, 16, 8) + >>> q2 = Quaternion(0, 8, 3, 12) + >>> Quaternion.vector_coplanar(q1, q2, q3) + False + + See Also + ======== + + axis + is_pure + + """ + + if fuzzy_not(q1.is_pure()) or fuzzy_not(q2.is_pure()) or fuzzy_not(q3.is_pure()): + raise ValueError('The given quaternions must be pure') + + M = Matrix([[q1.b, q1.c, q1.d], [q2.b, q2.c, q2.d], [q3.b, q3.c, q3.d]]).det() + return M.is_zero + + def parallel(self, other): + """ + Returns True if the two pure quaternions seen as 3D vectors are parallel. + + Explanation + =========== + + Two pure quaternions are called parallel when their vector product is commutative which + implies that the quaternions seen as 3D vectors have same direction. + + Parameters + ========== + + other : a Quaternion + + Returns + ======= + + True : if the two pure quaternions seen as 3D vectors are parallel. + False : if the two pure quaternions seen as 3D vectors are not parallel. + None : if the two pure quaternions seen as 3D vectors are parallel is unknown. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(0, 4, 4, 4) + >>> q1 = Quaternion(0, 8, 8, 8) + >>> q.parallel(q1) + True + + >>> q1 = Quaternion(0, 8, 13, 12) + >>> q.parallel(q1) + False + + """ + + if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()): + raise ValueError('The provided quaternions must be pure') + + return (self*other - other*self).is_zero_quaternion() + + def orthogonal(self, other): + """ + Returns the orthogonality of two quaternions. + + Explanation + =========== + + Two pure quaternions are called orthogonal when their product is anti-commutative. + + Parameters + ========== + + other : a Quaternion + + Returns + ======= + + True : if the two pure quaternions seen as 3D vectors are orthogonal. + False : if the two pure quaternions seen as 3D vectors are not orthogonal. + None : if the two pure quaternions seen as 3D vectors are orthogonal is unknown. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(0, 4, 4, 4) + >>> q1 = Quaternion(0, 8, 8, 8) + >>> q.orthogonal(q1) + False + + >>> q1 = Quaternion(0, 2, 2, 0) + >>> q = Quaternion(0, 2, -2, 0) + >>> q.orthogonal(q1) + True + + """ + + if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()): + raise ValueError('The given quaternions must be pure') + + return (self*other + other*self).is_zero_quaternion() + + def index_vector(self): + r""" + Returns the index vector of the quaternion. + + Explanation + =========== + + The index vector is given by $\mathbf{T}(q)$, the norm (or magnitude) of + the quaternion $q$, multiplied by $\mathbf{Ax}(q)$, the axis of $q$. + + Returns + ======= + + Quaternion: representing index vector of the provided quaternion. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(2, 4, 2, 4) + >>> q.index_vector() + 0 + 4*sqrt(10)/3*i + 2*sqrt(10)/3*j + 4*sqrt(10)/3*k + + See Also + ======== + + axis + norm + + """ + + return self.norm() * self.axis() + + def mensor(self): + """ + Returns the natural logarithm of the norm(magnitude) of the quaternion. + + Examples + ======== + + >>> from sympy.algebras.quaternion import Quaternion + >>> q = Quaternion(2, 4, 2, 4) + >>> q.mensor() + log(2*sqrt(10)) + >>> q.norm() + 2*sqrt(10) + + See Also + ======== + + norm + + """ + + return ln(self.norm()) diff --git a/phivenv/Lib/site-packages/sympy/algebras/tests/__init__.py b/phivenv/Lib/site-packages/sympy/algebras/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/algebras/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/algebras/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8a9ed06211fd13097ff9c474b7e85e44d01d59d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/algebras/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/algebras/tests/__pycache__/test_quaternion.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/algebras/tests/__pycache__/test_quaternion.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88bd16848aeb0a54199a6a52952585969c254b3b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/algebras/tests/__pycache__/test_quaternion.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/algebras/tests/test_quaternion.py b/phivenv/Lib/site-packages/sympy/algebras/tests/test_quaternion.py new file mode 100644 index 0000000000000000000000000000000000000000..a4331cd6afa05c96e8e11d59df4b7520e4810930 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/algebras/tests/test_quaternion.py @@ -0,0 +1,437 @@ +from sympy.testing.pytest import slow +from sympy.core.function import diff +from sympy.core.function import expand +from sympy.core.numbers import (E, I, Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (Abs, conjugate, im, re, sign) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, asin, cos, sin, atan2, atan) +from sympy.integrals.integrals import integrate +from sympy.matrices.dense import Matrix +from sympy.simplify import simplify +from sympy.simplify.trigsimp import trigsimp +from sympy.algebras.quaternion import Quaternion +from sympy.testing.pytest import raises +import math +from itertools import permutations, product + +w, x, y, z = symbols('w:z') +phi = symbols('phi') + +def test_quaternion_construction(): + q = Quaternion(w, x, y, z) + assert q + q == Quaternion(2*w, 2*x, 2*y, 2*z) + + q2 = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), + pi*Rational(2, 3)) + assert q2 == Quaternion(S.Half, S.Half, + S.Half, S.Half) + + M = Matrix([[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]]) + q3 = trigsimp(Quaternion.from_rotation_matrix(M)) + assert q3 == Quaternion( + sqrt(2)*sqrt(cos(phi) + 1)/2, 0, 0, sqrt(2 - 2*cos(phi))*sign(sin(phi))/2) + + nc = Symbol('nc', commutative=False) + raises(ValueError, lambda: Quaternion(w, x, nc, z)) + + +def test_quaternion_construction_norm(): + q1 = Quaternion(*symbols('a:d')) + + q2 = Quaternion(w, x, y, z) + assert expand((q1*q2).norm()**2 - (q1.norm()**2 * q2.norm()**2)) == 0 + + q3 = Quaternion(w, x, y, z, norm=1) + assert (q1 * q3).norm() == q1.norm() + + +def test_issue_25254(): + # calculating the inverse cached the norm which caused problems + # when multiplying + p = Quaternion(1, 0, 0, 0) + q = Quaternion.from_axis_angle((1, 1, 1), 3 * math.pi/4) + qi = q.inverse() # this operation cached the norm + test = q * p * qi + assert ((test - p).norm() < 1E-10) + + +def test_to_and_from_Matrix(): + q = Quaternion(w, x, y, z) + q_full = Quaternion.from_Matrix(q.to_Matrix()) + q_vect = Quaternion.from_Matrix(q.to_Matrix(True)) + assert (q - q_full).is_zero_quaternion() + assert (q.vector_part() - q_vect).is_zero_quaternion() + + +def test_product_matrices(): + q1 = Quaternion(w, x, y, z) + q2 = Quaternion(*(symbols("a:d"))) + assert (q1 * q2).to_Matrix() == q1.product_matrix_left * q2.to_Matrix() + assert (q1 * q2).to_Matrix() == q2.product_matrix_right * q1.to_Matrix() + + R1 = (q1.product_matrix_left * q1.product_matrix_right.T)[1:, 1:] + R2 = simplify(q1.to_rotation_matrix()*q1.norm()**2) + assert R1 == R2 + + +def test_quaternion_axis_angle(): + + test_data = [ # axis, angle, expected_quaternion + ((1, 0, 0), 0, (1, 0, 0, 0)), + ((1, 0, 0), pi/2, (sqrt(2)/2, sqrt(2)/2, 0, 0)), + ((0, 1, 0), pi/2, (sqrt(2)/2, 0, sqrt(2)/2, 0)), + ((0, 0, 1), pi/2, (sqrt(2)/2, 0, 0, sqrt(2)/2)), + ((1, 0, 0), pi, (0, 1, 0, 0)), + ((0, 1, 0), pi, (0, 0, 1, 0)), + ((0, 0, 1), pi, (0, 0, 0, 1)), + ((1, 1, 1), pi, (0, 1/sqrt(3),1/sqrt(3),1/sqrt(3))), + ((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), pi*2/3, (S.Half, S.Half, S.Half, S.Half)) + ] + + for axis, angle, expected in test_data: + assert Quaternion.from_axis_angle(axis, angle) == Quaternion(*expected) + + +def test_quaternion_axis_angle_simplification(): + result = Quaternion.from_axis_angle((1, 2, 3), asin(4)) + assert result.a == cos(asin(4)/2) + assert result.b == sqrt(14)*sin(asin(4)/2)/14 + assert result.c == sqrt(14)*sin(asin(4)/2)/7 + assert result.d == 3*sqrt(14)*sin(asin(4)/2)/14 + +def test_quaternion_complex_real_addition(): + a = symbols("a", complex=True) + b = symbols("b", real=True) + # This symbol is not complex: + c = symbols("c", commutative=False) + + q = Quaternion(w, x, y, z) + assert a + q == Quaternion(w + re(a), x + im(a), y, z) + assert 1 + q == Quaternion(1 + w, x, y, z) + assert I + q == Quaternion(w, 1 + x, y, z) + assert b + q == Quaternion(w + b, x, y, z) + raises(ValueError, lambda: c + q) + raises(ValueError, lambda: q * c) + raises(ValueError, lambda: c * q) + + assert -q == Quaternion(-w, -x, -y, -z) + + q1 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False) + q2 = Quaternion(1, 4, 7, 8) + + assert q1 + (2 + 3*I) == Quaternion(5 + 7*I, 2 + 5*I, 0, 7 + 8*I) + assert q2 + (2 + 3*I) == Quaternion(3, 7, 7, 8) + assert q1 * (2 + 3*I) == \ + Quaternion((2 + 3*I)*(3 + 4*I), (2 + 3*I)*(2 + 5*I), 0, (2 + 3*I)*(7 + 8*I)) + assert q2 * (2 + 3*I) == Quaternion(-10, 11, 38, -5) + + q1 = Quaternion(1, 2, 3, 4) + q0 = Quaternion(0, 0, 0, 0) + assert q1 + q0 == q1 + assert q1 - q0 == q1 + assert q1 - q1 == q0 + + +def test_quaternion_subs(): + q = Quaternion.from_axis_angle((0, 0, 1), phi) + assert q.subs(phi, 0) == Quaternion(1, 0, 0, 0) + + +def test_quaternion_evalf(): + assert (Quaternion(sqrt(2), 0, 0, sqrt(3)).evalf() == + Quaternion(sqrt(2).evalf(), 0, 0, sqrt(3).evalf())) + assert (Quaternion(1/sqrt(2), 0, 0, 1/sqrt(2)).evalf() == + Quaternion((1/sqrt(2)).evalf(), 0, 0, (1/sqrt(2)).evalf())) + + +def test_quaternion_functions(): + q = Quaternion(w, x, y, z) + q1 = Quaternion(1, 2, 3, 4) + q0 = Quaternion(0, 0, 0, 0) + + assert conjugate(q) == Quaternion(w, -x, -y, -z) + assert q.norm() == sqrt(w**2 + x**2 + y**2 + z**2) + assert q.normalize() == Quaternion(w, x, y, z) / sqrt(w**2 + x**2 + y**2 + z**2) + assert q.inverse() == Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**2) + assert q.inverse() == q.pow(-1) + raises(ValueError, lambda: q0.inverse()) + assert q.pow(2) == Quaternion(w**2 - x**2 - y**2 - z**2, 2*w*x, 2*w*y, 2*w*z) + assert q**(2) == Quaternion(w**2 - x**2 - y**2 - z**2, 2*w*x, 2*w*y, 2*w*z) + assert q1.pow(-2) == Quaternion( + Rational(-7, 225), Rational(-1, 225), Rational(-1, 150), Rational(-2, 225)) + assert q1**(-2) == Quaternion( + Rational(-7, 225), Rational(-1, 225), Rational(-1, 150), Rational(-2, 225)) + assert q1.pow(-0.5) == NotImplemented + raises(TypeError, lambda: q1**(-0.5)) + + assert q1.exp() == \ + Quaternion(E * cos(sqrt(29)), + 2 * sqrt(29) * E * sin(sqrt(29)) / 29, + 3 * sqrt(29) * E * sin(sqrt(29)) / 29, + 4 * sqrt(29) * E * sin(sqrt(29)) / 29) + assert q1.log() == \ + Quaternion(log(sqrt(30)), + 2 * sqrt(29) * acos(sqrt(30)/30) / 29, + 3 * sqrt(29) * acos(sqrt(30)/30) / 29, + 4 * sqrt(29) * acos(sqrt(30)/30) / 29) + + assert q1.pow_cos_sin(2) == \ + Quaternion(30 * cos(2 * acos(sqrt(30)/30)), + 60 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29, + 90 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29, + 120 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29) + + assert diff(Quaternion(x, x, x, x), x) == Quaternion(1, 1, 1, 1) + + assert integrate(Quaternion(x, x, x, x), x) == \ + Quaternion(x**2 / 2, x**2 / 2, x**2 / 2, x**2 / 2) + + assert Quaternion(1, x, x**2, x**3).integrate(x) == \ + Quaternion(x, x**2/2, x**3/3, x**4/4) + + assert Quaternion(sin(x), cos(x), sin(2*x), cos(2*x)).integrate(x) == \ + Quaternion(-cos(x), sin(x), -cos(2*x)/2, sin(2*x)/2) + + assert Quaternion(x**2, y**2, z**2, x*y*z).integrate(x, y) == \ + Quaternion(x**3*y/3, x*y**3/3, x*y*z**2, x**2*y**2*z/4) + + assert Quaternion.rotate_point((1, 1, 1), q1) == (S.One / 5, 1, S(7) / 5) + n = Symbol('n') + raises(TypeError, lambda: q1**n) + n = Symbol('n', integer=True) + raises(TypeError, lambda: q1**n) + + assert Quaternion(22, 23, 55, 8).scalar_part() == 22 + assert Quaternion(w, x, y, z).scalar_part() == w + + assert Quaternion(22, 23, 55, 8).vector_part() == Quaternion(0, 23, 55, 8) + assert Quaternion(w, x, y, z).vector_part() == Quaternion(0, x, y, z) + + assert q1.axis() == Quaternion(0, 2*sqrt(29)/29, 3*sqrt(29)/29, 4*sqrt(29)/29) + assert q1.axis().pow(2) == Quaternion(-1, 0, 0, 0) + assert q0.axis().scalar_part() == 0 + assert (q.axis() == Quaternion(0, + x/sqrt(x**2 + y**2 + z**2), + y/sqrt(x**2 + y**2 + z**2), + z/sqrt(x**2 + y**2 + z**2))) + + assert q0.is_pure() is True + assert q1.is_pure() is False + assert Quaternion(0, 0, 0, 3).is_pure() is True + assert Quaternion(0, 2, 10, 3).is_pure() is True + assert Quaternion(w, 2, 10, 3).is_pure() is None + + assert q1.angle() == 2*atan(sqrt(29)) + assert q.angle() == 2*atan2(sqrt(x**2 + y**2 + z**2), w) + + assert Quaternion.arc_coplanar(q1, Quaternion(2, 4, 6, 8)) is True + assert Quaternion.arc_coplanar(q1, Quaternion(1, -2, -3, -4)) is True + assert Quaternion.arc_coplanar(q1, Quaternion(1, 8, 12, 16)) is True + assert Quaternion.arc_coplanar(q1, Quaternion(1, 2, 3, 4)) is True + assert Quaternion.arc_coplanar(q1, Quaternion(w, 4, 6, 8)) is True + assert Quaternion.arc_coplanar(q1, Quaternion(2, 7, 4, 1)) is False + assert Quaternion.arc_coplanar(q1, Quaternion(w, x, y, z)) is None + raises(ValueError, lambda: Quaternion.arc_coplanar(q1, q0)) + + assert Quaternion.vector_coplanar( + Quaternion(0, 8, 12, 16), + Quaternion(0, 4, 6, 8), + Quaternion(0, 2, 3, 4)) is True + assert Quaternion.vector_coplanar( + Quaternion(0, 0, 0, 0), Quaternion(0, 4, 6, 8), Quaternion(0, 2, 3, 4)) is True + assert Quaternion.vector_coplanar( + Quaternion(0, 8, 2, 6), Quaternion(0, 1, 6, 6), Quaternion(0, 0, 3, 4)) is False + assert Quaternion.vector_coplanar( + Quaternion(0, 1, 3, 4), + Quaternion(0, 4, w, 6), + Quaternion(0, 6, 8, 1)) is None + raises(ValueError, lambda: + Quaternion.vector_coplanar(q0, Quaternion(0, 4, 6, 8), q1)) + + assert Quaternion(0, 1, 2, 3).parallel(Quaternion(0, 2, 4, 6)) is True + assert Quaternion(0, 1, 2, 3).parallel(Quaternion(0, 2, 2, 6)) is False + assert Quaternion(0, 1, 2, 3).parallel(Quaternion(w, x, y, 6)) is None + raises(ValueError, lambda: q0.parallel(q1)) + + assert Quaternion(0, 1, 2, 3).orthogonal(Quaternion(0, -2, 1, 0)) is True + assert Quaternion(0, 2, 4, 7).orthogonal(Quaternion(0, 2, 2, 6)) is False + assert Quaternion(0, 2, 4, 7).orthogonal(Quaternion(w, x, y, 6)) is None + raises(ValueError, lambda: q0.orthogonal(q1)) + + assert q1.index_vector() == Quaternion( + 0, 2*sqrt(870)/29, + 3*sqrt(870)/29, + 4*sqrt(870)/29) + assert Quaternion(0, 3, 9, 4).index_vector() == Quaternion(0, 3, 9, 4) + + assert Quaternion(4, 3, 9, 4).mensor() == log(sqrt(122)) + assert Quaternion(3, 3, 0, 2).mensor() == log(sqrt(22)) + + assert q0.is_zero_quaternion() is True + assert q1.is_zero_quaternion() is False + assert Quaternion(w, 0, 0, 0).is_zero_quaternion() is None + +def test_quaternion_conversions(): + q1 = Quaternion(1, 2, 3, 4) + + assert q1.to_axis_angle() == ((2 * sqrt(29)/29, + 3 * sqrt(29)/29, + 4 * sqrt(29)/29), + 2 * acos(sqrt(30)/30)) + + assert (q1.to_rotation_matrix() == + Matrix([[Rational(-2, 3), Rational(2, 15), Rational(11, 15)], + [Rational(2, 3), Rational(-1, 3), Rational(2, 3)], + [Rational(1, 3), Rational(14, 15), Rational(2, 15)]])) + + assert (q1.to_rotation_matrix((1, 1, 1)) == + Matrix([ + [Rational(-2, 3), Rational(2, 15), Rational(11, 15), Rational(4, 5)], + [Rational(2, 3), Rational(-1, 3), Rational(2, 3), S.Zero], + [Rational(1, 3), Rational(14, 15), Rational(2, 15), Rational(-2, 5)], + [S.Zero, S.Zero, S.Zero, S.One]])) + + theta = symbols("theta", real=True) + q2 = Quaternion(cos(theta/2), 0, 0, sin(theta/2)) + + assert trigsimp(q2.to_rotation_matrix()) == Matrix([ + [cos(theta), -sin(theta), 0], + [sin(theta), cos(theta), 0], + [0, 0, 1]]) + + assert q2.to_axis_angle() == ((0, 0, sin(theta/2)/Abs(sin(theta/2))), + 2*acos(cos(theta/2))) + + assert trigsimp(q2.to_rotation_matrix((1, 1, 1))) == Matrix([ + [cos(theta), -sin(theta), 0, sin(theta) - cos(theta) + 1], + [sin(theta), cos(theta), 0, -sin(theta) - cos(theta) + 1], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + +def test_rotation_matrix_homogeneous(): + q = Quaternion(w, x, y, z) + R1 = q.to_rotation_matrix(homogeneous=True) * q.norm()**2 + R2 = simplify(q.to_rotation_matrix(homogeneous=False) * q.norm()**2) + assert R1 == R2 + + +def test_quaternion_rotation_iss1593(): + """ + There was a sign mistake in the definition, + of the rotation matrix. This tests that particular sign mistake. + See issue 1593 for reference. + See wikipedia + https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix + for the correct definition + """ + q = Quaternion(cos(phi/2), sin(phi/2), 0, 0) + assert(trigsimp(q.to_rotation_matrix()) == Matrix([ + [1, 0, 0], + [0, cos(phi), -sin(phi)], + [0, sin(phi), cos(phi)]])) + + +def test_quaternion_multiplication(): + q1 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False) + q2 = Quaternion(1, 2, 3, 5) + q3 = Quaternion(1, 1, 1, y) + + assert Quaternion._generic_mul(S(4), S.One) == 4 + assert (Quaternion._generic_mul(S(4), q1) == + Quaternion(12 + 16*I, 8 + 20*I, 0, 28 + 32*I)) + assert q2.mul(2) == Quaternion(2, 4, 6, 10) + assert q2.mul(q3) == Quaternion(-5*y - 4, 3*y - 2, 9 - 2*y, y + 4) + assert q2.mul(q3) == q2*q3 + + z = symbols('z', complex=True) + z_quat = Quaternion(re(z), im(z), 0, 0) + q = Quaternion(*symbols('q:4', real=True)) + + assert z * q == z_quat * q + assert q * z == q * z_quat + + +def test_issue_16318(): + #for rtruediv + q0 = Quaternion(0, 0, 0, 0) + raises(ValueError, lambda: 1/q0) + #for rotate_point + q = Quaternion(1, 2, 3, 4) + (axis, angle) = q.to_axis_angle() + assert Quaternion.rotate_point((1, 1, 1), (axis, angle)) == (S.One / 5, 1, S(7) / 5) + #test for to_axis_angle + q = Quaternion(-1, 1, 1, 1) + axis = (-sqrt(3)/3, -sqrt(3)/3, -sqrt(3)/3) + angle = 2*pi/3 + assert (axis, angle) == q.to_axis_angle() + + +@slow +def test_to_euler(): + q = Quaternion(w, x, y, z) + q_normalized = q.normalize() + + seqs = ['zxy', 'zyx', 'zyz', 'zxz'] + seqs += [seq.upper() for seq in seqs] + + for seq in seqs: + euler_from_q = q.to_euler(seq) + q_back = simplify(Quaternion.from_euler(euler_from_q, seq)) + assert q_back == q_normalized + + +def test_to_euler_iss24504(): + """ + There was a mistake in the degenerate case testing + See issue 24504 for reference. + """ + q = Quaternion.from_euler((phi, 0, 0), 'zyz') + assert trigsimp(q.to_euler('zyz'), inverse=True) == (phi, 0, 0) + + +def test_to_euler_numerical_singilarities(): + + def test_one_case(angles, seq): + q = Quaternion.from_euler(angles, seq) + assert q.to_euler(seq) == angles + + # symmetric + test_one_case((pi/2, 0, 0), 'zyz') + test_one_case((pi/2, 0, 0), 'ZYZ') + test_one_case((pi/2, pi, 0), 'zyz') + test_one_case((pi/2, pi, 0), 'ZYZ') + + # asymmetric + test_one_case((pi/2, pi/2, 0), 'zyx') + test_one_case((pi/2, -pi/2, 0), 'zyx') + test_one_case((pi/2, pi/2, 0), 'ZYX') + test_one_case((pi/2, -pi/2, 0), 'ZYX') + + +@slow +def test_to_euler_options(): + def test_one_case(q): + angles1 = Matrix(q.to_euler(seq, True, True)) + angles2 = Matrix(q.to_euler(seq, False, False)) + angle_errors = simplify(angles1-angles2).evalf() + for angle_error in angle_errors: + # forcing angles to set {-pi, pi} + angle_error = (angle_error + pi) % (2 * pi) - pi + assert angle_error < 10e-7 + + for xyz in ('xyz', 'XYZ'): + for seq_tuple in permutations(xyz): + for symmetric in (True, False): + if symmetric: + seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) + else: + seq = ''.join(seq_tuple) + + for elements in product([-1, 0, 1], repeat=4): + q = Quaternion(*elements) + if not q.is_zero_quaternion(): + test_one_case(q) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__init__.py b/phivenv/Lib/site-packages/sympy/assumptions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..af862653f3ce0eeb67f7764e16c32f3466e87024 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/__init__.py @@ -0,0 +1,18 @@ +""" +A module to implement logical predicates and assumption system. +""" + +from .assume import ( + AppliedPredicate, Predicate, AssumptionsContext, assuming, + global_assumptions +) +from .ask import Q, ask, register_handler, remove_handler +from .refine import refine +from .relation import BinaryRelation, AppliedBinaryRelation + +__all__ = [ + 'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming', + 'global_assumptions', 'Q', 'ask', 'register_handler', 'remove_handler', + 'refine', + 'BinaryRelation', 'AppliedBinaryRelation' +] diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c47e7f58d57a9b5441419d17d1665144e54dbab0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/ask.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/ask.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de3b4fad0d5253acff85d83dd6707586ec9a3d24 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/ask.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/ask_generated.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/ask_generated.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9311e59fe63bea22eb56de0e15ec2c18906dc34 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/ask_generated.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/assume.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/assume.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a1362a1166949d288bd960a87cd192eff818499 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/assume.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/cnf.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/cnf.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a1527b3a57c81c7be363971d960726e86fb0f65 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/cnf.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/facts.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/facts.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0f33a9a442df77b7fa95f098e0f64548adbe58e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/facts.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/lra_satask.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/lra_satask.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b9448281bd3db8f8c37c3cdb9bb1054b482097f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/lra_satask.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/refine.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/refine.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c34c109a124e65536090bee7f23e4ca9234cddd Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/refine.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/satask.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/satask.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..346ebc0c35ac4d2e9e65b04c04350a278079d647 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/satask.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/sathandlers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/sathandlers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ed886b580397a15e25443a783232bc41c4d4cbf Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/sathandlers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/wrapper.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/wrapper.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d44c29468f97eb94b67d9cf60f864cc974d7cb6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/__pycache__/wrapper.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/ask.py b/phivenv/Lib/site-packages/sympy/assumptions/ask.py new file mode 100644 index 0000000000000000000000000000000000000000..ec81ec8ecce245c2a798cf9e71af1e9373292bc1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/ask.py @@ -0,0 +1,651 @@ +"""Module for querying SymPy objects about assumptions.""" + +from sympy.assumptions.assume import (global_assumptions, Predicate, + AppliedPredicate) +from sympy.assumptions.cnf import CNF, EncodedCNF, Literal +from sympy.core import sympify +from sympy.core.kind import BooleanKind +from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le +from sympy.logic.inference import satisfiable +from sympy.utilities.decorator import memoize_property +from sympy.utilities.exceptions import (sympy_deprecation_warning, + SymPyDeprecationWarning, + ignore_warnings) + + +# Memoization is necessary for the properties of AssumptionKeys to +# ensure that only one object of Predicate objects are created. +# This is because assumption handlers are registered on those objects. + + +class AssumptionKeys: + """ + This class contains all the supported keys by ``ask``. + It should be accessed via the instance ``sympy.Q``. + + """ + + # DO NOT add methods or properties other than predicate keys. + # SAT solver checks the properties of Q and use them to compute the + # fact system. Non-predicate attributes will break this. + + @memoize_property + def hermitian(self): + from .handlers.sets import HermitianPredicate + return HermitianPredicate() + + @memoize_property + def antihermitian(self): + from .handlers.sets import AntihermitianPredicate + return AntihermitianPredicate() + + @memoize_property + def real(self): + from .handlers.sets import RealPredicate + return RealPredicate() + + @memoize_property + def extended_real(self): + from .handlers.sets import ExtendedRealPredicate + return ExtendedRealPredicate() + + @memoize_property + def imaginary(self): + from .handlers.sets import ImaginaryPredicate + return ImaginaryPredicate() + + @memoize_property + def complex(self): + from .handlers.sets import ComplexPredicate + return ComplexPredicate() + + @memoize_property + def algebraic(self): + from .handlers.sets import AlgebraicPredicate + return AlgebraicPredicate() + + @memoize_property + def transcendental(self): + from .predicates.sets import TranscendentalPredicate + return TranscendentalPredicate() + + @memoize_property + def integer(self): + from .handlers.sets import IntegerPredicate + return IntegerPredicate() + + @memoize_property + def noninteger(self): + from .predicates.sets import NonIntegerPredicate + return NonIntegerPredicate() + + @memoize_property + def rational(self): + from .handlers.sets import RationalPredicate + return RationalPredicate() + + @memoize_property + def irrational(self): + from .handlers.sets import IrrationalPredicate + return IrrationalPredicate() + + @memoize_property + def finite(self): + from .handlers.calculus import FinitePredicate + return FinitePredicate() + + @memoize_property + def infinite(self): + from .handlers.calculus import InfinitePredicate + return InfinitePredicate() + + @memoize_property + def positive_infinite(self): + from .handlers.calculus import PositiveInfinitePredicate + return PositiveInfinitePredicate() + + @memoize_property + def negative_infinite(self): + from .handlers.calculus import NegativeInfinitePredicate + return NegativeInfinitePredicate() + + @memoize_property + def positive(self): + from .handlers.order import PositivePredicate + return PositivePredicate() + + @memoize_property + def negative(self): + from .handlers.order import NegativePredicate + return NegativePredicate() + + @memoize_property + def zero(self): + from .handlers.order import ZeroPredicate + return ZeroPredicate() + + @memoize_property + def extended_positive(self): + from .handlers.order import ExtendedPositivePredicate + return ExtendedPositivePredicate() + + @memoize_property + def extended_negative(self): + from .handlers.order import ExtendedNegativePredicate + return ExtendedNegativePredicate() + + @memoize_property + def nonzero(self): + from .handlers.order import NonZeroPredicate + return NonZeroPredicate() + + @memoize_property + def nonpositive(self): + from .handlers.order import NonPositivePredicate + return NonPositivePredicate() + + @memoize_property + def nonnegative(self): + from .handlers.order import NonNegativePredicate + return NonNegativePredicate() + + @memoize_property + def extended_nonzero(self): + from .handlers.order import ExtendedNonZeroPredicate + return ExtendedNonZeroPredicate() + + @memoize_property + def extended_nonpositive(self): + from .handlers.order import ExtendedNonPositivePredicate + return ExtendedNonPositivePredicate() + + @memoize_property + def extended_nonnegative(self): + from .handlers.order import ExtendedNonNegativePredicate + return ExtendedNonNegativePredicate() + + @memoize_property + def even(self): + from .handlers.ntheory import EvenPredicate + return EvenPredicate() + + @memoize_property + def odd(self): + from .handlers.ntheory import OddPredicate + return OddPredicate() + + @memoize_property + def prime(self): + from .handlers.ntheory import PrimePredicate + return PrimePredicate() + + @memoize_property + def composite(self): + from .handlers.ntheory import CompositePredicate + return CompositePredicate() + + @memoize_property + def commutative(self): + from .handlers.common import CommutativePredicate + return CommutativePredicate() + + @memoize_property + def is_true(self): + from .handlers.common import IsTruePredicate + return IsTruePredicate() + + @memoize_property + def symmetric(self): + from .handlers.matrices import SymmetricPredicate + return SymmetricPredicate() + + @memoize_property + def invertible(self): + from .handlers.matrices import InvertiblePredicate + return InvertiblePredicate() + + @memoize_property + def orthogonal(self): + from .handlers.matrices import OrthogonalPredicate + return OrthogonalPredicate() + + @memoize_property + def unitary(self): + from .handlers.matrices import UnitaryPredicate + return UnitaryPredicate() + + @memoize_property + def positive_definite(self): + from .handlers.matrices import PositiveDefinitePredicate + return PositiveDefinitePredicate() + + @memoize_property + def upper_triangular(self): + from .handlers.matrices import UpperTriangularPredicate + return UpperTriangularPredicate() + + @memoize_property + def lower_triangular(self): + from .handlers.matrices import LowerTriangularPredicate + return LowerTriangularPredicate() + + @memoize_property + def diagonal(self): + from .handlers.matrices import DiagonalPredicate + return DiagonalPredicate() + + @memoize_property + def fullrank(self): + from .handlers.matrices import FullRankPredicate + return FullRankPredicate() + + @memoize_property + def square(self): + from .handlers.matrices import SquarePredicate + return SquarePredicate() + + @memoize_property + def integer_elements(self): + from .handlers.matrices import IntegerElementsPredicate + return IntegerElementsPredicate() + + @memoize_property + def real_elements(self): + from .handlers.matrices import RealElementsPredicate + return RealElementsPredicate() + + @memoize_property + def complex_elements(self): + from .handlers.matrices import ComplexElementsPredicate + return ComplexElementsPredicate() + + @memoize_property + def singular(self): + from .predicates.matrices import SingularPredicate + return SingularPredicate() + + @memoize_property + def normal(self): + from .predicates.matrices import NormalPredicate + return NormalPredicate() + + @memoize_property + def triangular(self): + from .predicates.matrices import TriangularPredicate + return TriangularPredicate() + + @memoize_property + def unit_triangular(self): + from .predicates.matrices import UnitTriangularPredicate + return UnitTriangularPredicate() + + @memoize_property + def eq(self): + from .relation.equality import EqualityPredicate + return EqualityPredicate() + + @memoize_property + def ne(self): + from .relation.equality import UnequalityPredicate + return UnequalityPredicate() + + @memoize_property + def gt(self): + from .relation.equality import StrictGreaterThanPredicate + return StrictGreaterThanPredicate() + + @memoize_property + def ge(self): + from .relation.equality import GreaterThanPredicate + return GreaterThanPredicate() + + @memoize_property + def lt(self): + from .relation.equality import StrictLessThanPredicate + return StrictLessThanPredicate() + + @memoize_property + def le(self): + from .relation.equality import LessThanPredicate + return LessThanPredicate() + + +Q = AssumptionKeys() + +def _extract_all_facts(assump, exprs): + """ + Extract all relevant assumptions from *assump* with respect to given *exprs*. + + Parameters + ========== + + assump : sympy.assumptions.cnf.CNF + + exprs : tuple of expressions + + Returns + ======= + + sympy.assumptions.cnf.CNF + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.cnf import CNF + >>> from sympy.assumptions.ask import _extract_all_facts + >>> from sympy.abc import x, y + >>> assump = CNF.from_prop(Q.positive(x) & Q.integer(y)) + >>> exprs = (x,) + >>> cnf = _extract_all_facts(assump, exprs) + >>> cnf.clauses + {frozenset({Literal(Q.positive, False)})} + + """ + facts = set() + + for clause in assump.clauses: + args = [] + for literal in clause: + if isinstance(literal.lit, AppliedPredicate) and len(literal.lit.arguments) == 1: + if literal.lit.arg in exprs: + # Add literal if it has matching in it + args.append(Literal(literal.lit.function, literal.is_Not)) + else: + # If any of the literals doesn't have matching expr don't add the whole clause. + break + else: + # If any of the literals aren't unary predicate don't add the whole clause. + break + + else: + if args: + facts.add(frozenset(args)) + return CNF(facts) + + +def ask(proposition, assumptions=True, context=global_assumptions): + """ + Function to evaluate the proposition with assumptions. + + Explanation + =========== + + This function evaluates the proposition to ``True`` or ``False`` if + the truth value can be determined. If not, it returns ``None``. + + It should be discerned from :func:`~.refine` which, when applied to a + proposition, simplifies the argument to symbolic ``Boolean`` instead of + Python built-in ``True``, ``False`` or ``None``. + + **Syntax** + + * ask(proposition) + Evaluate the *proposition* in global assumption context. + + * ask(proposition, assumptions) + Evaluate the *proposition* with respect to *assumptions* in + global assumption context. + + Parameters + ========== + + proposition : Boolean + Proposition which will be evaluated to boolean value. If this is + not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``. + + assumptions : Boolean, optional + Local assumptions to evaluate the *proposition*. + + context : AssumptionsContext, optional + Default assumptions to evaluate the *proposition*. By default, + this is ``sympy.assumptions.global_assumptions`` variable. + + Returns + ======= + + ``True``, ``False``, or ``None`` + + Raises + ====== + + TypeError : *proposition* or *assumptions* is not valid logical expression. + + ValueError : assumptions are inconsistent. + + Examples + ======== + + >>> from sympy import ask, Q, pi + >>> from sympy.abc import x, y + >>> ask(Q.rational(pi)) + False + >>> ask(Q.even(x*y), Q.even(x) & Q.integer(y)) + True + >>> ask(Q.prime(4*x), Q.integer(x)) + False + + If the truth value cannot be determined, ``None`` will be returned. + + >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x + None + + ``ValueError`` is raised if assumptions are inconsistent. + + >>> ask(Q.integer(x), Q.even(x) & Q.odd(x)) + Traceback (most recent call last): + ... + ValueError: inconsistent assumptions Q.even(x) & Q.odd(x) + + Notes + ===== + + Relations in assumptions are not implemented (yet), so the following + will not give a meaningful result. + + >>> ask(Q.positive(x), x > 0) + + It is however a work in progress. + + See Also + ======== + + sympy.assumptions.refine.refine : Simplification using assumptions. + Proposition is not reduced to ``None`` if the truth value cannot + be determined. + """ + from sympy.assumptions.satask import satask + from sympy.assumptions.lra_satask import lra_satask + from sympy.logic.algorithms.lra_theory import UnhandledInput + + proposition = sympify(proposition) + assumptions = sympify(assumptions) + + if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind: + raise TypeError("proposition must be a valid logical expression") + + if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind: + raise TypeError("assumptions must be a valid logical expression") + + binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le} + if isinstance(proposition, AppliedPredicate): + key, args = proposition.function, proposition.arguments + elif proposition.func in binrelpreds: + key, args = binrelpreds[type(proposition)], proposition.args + else: + key, args = Q.is_true, (proposition,) + + # convert local and global assumptions to CNF + assump_cnf = CNF.from_prop(assumptions) + assump_cnf.extend(context) + + # extract the relevant facts from assumptions with respect to args + local_facts = _extract_all_facts(assump_cnf, args) + + # convert default facts and assumed facts to encoded CNF + known_facts_cnf = get_all_known_facts() + enc_cnf = EncodedCNF() + enc_cnf.from_cnf(CNF(known_facts_cnf)) + enc_cnf.add_from_cnf(local_facts) + + # check the satisfiability of given assumptions + if local_facts.clauses and satisfiable(enc_cnf) is False: + raise ValueError("inconsistent assumptions %s" % assumptions) + + # quick computation for single fact + res = _ask_single_fact(key, local_facts) + if res is not None: + return res + + # direct resolution method, no logic + res = key(*args)._eval_ask(assumptions) + if res is not None: + return bool(res) + + # using satask (still costly) + res = satask(proposition, assumptions=assumptions, context=context) + if res is not None: + return res + + try: + res = lra_satask(proposition, assumptions=assumptions, context=context) + except UnhandledInput: + return None + + return res + + +def _ask_single_fact(key, local_facts): + """ + Compute the truth value of single predicate using assumptions. + + Parameters + ========== + + key : sympy.assumptions.assume.Predicate + Proposition predicate. + + local_facts : sympy.assumptions.cnf.CNF + Local assumption in CNF form. + + Returns + ======= + + ``True``, ``False`` or ``None`` + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.cnf import CNF + >>> from sympy.assumptions.ask import _ask_single_fact + + If prerequisite of proposition is rejected by the assumption, + return ``False``. + + >>> key, assump = Q.zero, ~Q.zero + >>> local_facts = CNF.from_prop(assump) + >>> _ask_single_fact(key, local_facts) + False + >>> key, assump = Q.zero, ~Q.even + >>> local_facts = CNF.from_prop(assump) + >>> _ask_single_fact(key, local_facts) + False + + If assumption implies the proposition, return ``True``. + + >>> key, assump = Q.even, Q.zero + >>> local_facts = CNF.from_prop(assump) + >>> _ask_single_fact(key, local_facts) + True + + If proposition rejects the assumption, return ``False``. + + >>> key, assump = Q.even, Q.odd + >>> local_facts = CNF.from_prop(assump) + >>> _ask_single_fact(key, local_facts) + False + """ + if local_facts.clauses: + + known_facts_dict = get_known_facts_dict() + + if len(local_facts.clauses) == 1: + cl, = local_facts.clauses + if len(cl) == 1: + f, = cl + prop_facts = known_facts_dict.get(key, None) + prop_req = prop_facts[0] if prop_facts is not None else set() + if f.is_Not and f.arg in prop_req: + # the prerequisite of proposition is rejected + return False + + for clause in local_facts.clauses: + if len(clause) == 1: + f, = clause + prop_facts = known_facts_dict.get(f.arg, None) if not f.is_Not else None + if prop_facts is None: + continue + + prop_req, prop_rej = prop_facts + if key in prop_req: + # assumption implies the proposition + return True + elif key in prop_rej: + # proposition rejects the assumption + return False + + return None + + +def register_handler(key, handler): + """ + Register a handler in the ask system. key must be a string and handler a + class inheriting from AskHandler. + + .. deprecated:: 1.8. + Use multipledispatch handler instead. See :obj:`~.Predicate`. + + """ + sympy_deprecation_warning( + """ + The AskHandler system is deprecated. The register_handler() function + should be replaced with the multipledispatch handler of Predicate. + """, + deprecated_since_version="1.8", + active_deprecations_target='deprecated-askhandler', + ) + if isinstance(key, Predicate): + key = key.name.name + Qkey = getattr(Q, key, None) + if Qkey is not None: + Qkey.add_handler(handler) + else: + setattr(Q, key, Predicate(key, handlers=[handler])) + + +def remove_handler(key, handler): + """ + Removes a handler from the ask system. + + .. deprecated:: 1.8. + Use multipledispatch handler instead. See :obj:`~.Predicate`. + + """ + sympy_deprecation_warning( + """ + The AskHandler system is deprecated. The remove_handler() function + should be replaced with the multipledispatch handler of Predicate. + """, + deprecated_since_version="1.8", + active_deprecations_target='deprecated-askhandler', + ) + if isinstance(key, Predicate): + key = key.name.name + # Don't show the same warning again recursively + with ignore_warnings(SymPyDeprecationWarning): + getattr(Q, key).remove_handler(handler) + + +from sympy.assumptions.ask_generated import (get_all_known_facts, + get_known_facts_dict) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/ask_generated.py b/phivenv/Lib/site-packages/sympy/assumptions/ask_generated.py new file mode 100644 index 0000000000000000000000000000000000000000..d90cdffc1e127d78e18f70cda13d8d5e0530d41b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/ask_generated.py @@ -0,0 +1,352 @@ +""" +Do NOT manually edit this file. +Instead, run ./bin/ask_update.py. +""" + +from sympy.assumptions.ask import Q +from sympy.assumptions.cnf import Literal +from sympy.core.cache import cacheit + +@cacheit +def get_all_known_facts(): + """ + Known facts between unary predicates as CNF clauses. + """ + return { + frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))), + frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))), + frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))), + frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))), + frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))), + frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))), + frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))), + frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))), + frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))), + frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))), + frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))), + frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))), + frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))), + frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))), + frozenset((Literal(Q.composite, True), Literal(Q.positive, False))), + frozenset((Literal(Q.composite, True), Literal(Q.prime, True))), + frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))), + frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))), + frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))), + frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))), + frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))), + frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))), + frozenset((Literal(Q.even, False), Literal(Q.zero, True))), + frozenset((Literal(Q.even, True), Literal(Q.odd, True))), + frozenset((Literal(Q.even, True), Literal(Q.rational, False))), + frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))), + frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))), + frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))), + frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))), + frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))), + frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))), + frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))), + frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))), + frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))), + frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))), + frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))), + frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))), + frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))), + frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))), + frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))), + frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))), + frozenset((Literal(Q.invertible, True), Literal(Q.square, False))), + frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))), + frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))), + frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))), + frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))), + frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))), + frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))), + frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))), + frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))), + frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))), + frozenset((Literal(Q.negative, True), Literal(Q.positive, True))), + frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.negative, True), Literal(Q.zero, True))), + frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))), + frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))), + frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))), + frozenset((Literal(Q.normal, True), Literal(Q.square, False))), + frozenset((Literal(Q.odd, True), Literal(Q.rational, False))), + frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))), + frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))), + frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))), + frozenset((Literal(Q.positive, False), Literal(Q.prime, True))), + frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.positive, True), Literal(Q.zero, True))), + frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True))), + frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))), + frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))), + frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True))) + } + +@cacheit +def get_all_known_matrix_facts(): + """ + Known facts between unary predicates for matrices as CNF clauses. + """ + return { + frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))), + frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))), + frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))), + frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))), + frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))), + frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))), + frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))), + frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))), + frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))), + frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))), + frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))), + frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))), + frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))), + frozenset((Literal(Q.invertible, True), Literal(Q.square, False))), + frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))), + frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))), + frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))), + frozenset((Literal(Q.normal, True), Literal(Q.square, False))), + frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))), + frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))), + frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))), + frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))), + frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))), + frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True))) + } + +@cacheit +def get_all_known_number_facts(): + """ + Known facts between unary predicates for numbers as CNF clauses. + """ + return { + frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))), + frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))), + frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))), + frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))), + frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))), + frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))), + frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))), + frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))), + frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))), + frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))), + frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))), + frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))), + frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))), + frozenset((Literal(Q.composite, True), Literal(Q.positive, False))), + frozenset((Literal(Q.composite, True), Literal(Q.prime, True))), + frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))), + frozenset((Literal(Q.even, False), Literal(Q.zero, True))), + frozenset((Literal(Q.even, True), Literal(Q.odd, True))), + frozenset((Literal(Q.even, True), Literal(Q.rational, False))), + frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))), + frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))), + frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))), + frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))), + frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))), + frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))), + frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))), + frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))), + frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))), + frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))), + frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))), + frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))), + frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))), + frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))), + frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))), + frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))), + frozenset((Literal(Q.negative, True), Literal(Q.positive, True))), + frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.negative, True), Literal(Q.zero, True))), + frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))), + frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))), + frozenset((Literal(Q.odd, True), Literal(Q.rational, False))), + frozenset((Literal(Q.positive, False), Literal(Q.prime, True))), + frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))), + frozenset((Literal(Q.positive, True), Literal(Q.zero, True))), + frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True))) + } + +@cacheit +def get_known_facts_dict(): + """ + Logical relations between unary predicates as dictionary. + + Each key is a predicate, and item is two groups of predicates. + First group contains the predicates which are implied by the key, and + second group contains the predicates which are rejected by the key. + + """ + return { + Q.algebraic: (set([Q.algebraic, Q.commutative, Q.complex, Q.finite]), + set([Q.infinite, Q.negative_infinite, Q.positive_infinite, + Q.transcendental])), + Q.antihermitian: (set([Q.antihermitian]), set([])), + Q.commutative: (set([Q.commutative]), set([])), + Q.complex: (set([Q.commutative, Q.complex, Q.finite]), + set([Q.infinite, Q.negative_infinite, Q.positive_infinite])), + Q.complex_elements: (set([Q.complex_elements]), set([])), + Q.composite: (set([Q.algebraic, Q.commutative, Q.complex, Q.composite, + Q.extended_nonnegative, Q.extended_nonzero, + Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian, + Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.rational, + Q.real]), set([Q.extended_negative, Q.extended_nonpositive, + Q.imaginary, Q.infinite, Q.irrational, Q.negative, + Q.negative_infinite, Q.nonpositive, Q.positive_infinite, + Q.prime, Q.transcendental, Q.zero])), + Q.diagonal: (set([Q.diagonal, Q.lower_triangular, Q.normal, Q.square, + Q.symmetric, Q.triangular, Q.upper_triangular]), set([])), + Q.even: (set([Q.algebraic, Q.commutative, Q.complex, Q.even, + Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational, + Q.real]), set([Q.imaginary, Q.infinite, Q.irrational, + Q.negative_infinite, Q.odd, Q.positive_infinite, + Q.transcendental])), + Q.extended_negative: (set([Q.commutative, Q.extended_negative, + Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real]), + set([Q.composite, Q.extended_nonnegative, Q.extended_positive, + Q.imaginary, Q.nonnegative, Q.positive, Q.positive_infinite, + Q.prime, Q.zero])), + Q.extended_nonnegative: (set([Q.commutative, Q.extended_nonnegative, + Q.extended_real]), set([Q.extended_negative, Q.imaginary, + Q.negative, Q.negative_infinite])), + Q.extended_nonpositive: (set([Q.commutative, Q.extended_nonpositive, + Q.extended_real]), set([Q.composite, Q.extended_positive, + Q.imaginary, Q.positive, Q.positive_infinite, Q.prime])), + Q.extended_nonzero: (set([Q.commutative, Q.extended_nonzero, + Q.extended_real]), set([Q.imaginary, Q.zero])), + Q.extended_positive: (set([Q.commutative, Q.extended_nonnegative, + Q.extended_nonzero, Q.extended_positive, Q.extended_real]), + set([Q.extended_negative, Q.extended_nonpositive, Q.imaginary, + Q.negative, Q.negative_infinite, Q.nonpositive, Q.zero])), + Q.extended_real: (set([Q.commutative, Q.extended_real]), + set([Q.imaginary])), + Q.finite: (set([Q.commutative, Q.finite]), set([Q.infinite, + Q.negative_infinite, Q.positive_infinite])), + Q.fullrank: (set([Q.fullrank]), set([])), + Q.hermitian: (set([Q.hermitian]), set([])), + Q.imaginary: (set([Q.antihermitian, Q.commutative, Q.complex, + Q.finite, Q.imaginary]), set([Q.composite, Q.even, + Q.extended_negative, Q.extended_nonnegative, + Q.extended_nonpositive, Q.extended_nonzero, + Q.extended_positive, Q.extended_real, Q.infinite, Q.integer, + Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative, + Q.nonpositive, Q.nonzero, Q.odd, Q.positive, + Q.positive_infinite, Q.prime, Q.rational, Q.real, Q.zero])), + Q.infinite: (set([Q.commutative, Q.infinite]), set([Q.algebraic, + Q.complex, Q.composite, Q.even, Q.finite, Q.imaginary, + Q.integer, Q.irrational, Q.negative, Q.nonnegative, + Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime, + Q.rational, Q.real, Q.transcendental, Q.zero])), + Q.integer: (set([Q.algebraic, Q.commutative, Q.complex, + Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational, + Q.real]), set([Q.imaginary, Q.infinite, Q.irrational, + Q.negative_infinite, Q.positive_infinite, Q.transcendental])), + Q.integer_elements: (set([Q.complex_elements, Q.integer_elements, + Q.real_elements]), set([])), + Q.invertible: (set([Q.fullrank, Q.invertible, Q.square]), + set([Q.singular])), + Q.irrational: (set([Q.commutative, Q.complex, Q.extended_nonzero, + Q.extended_real, Q.finite, Q.hermitian, Q.irrational, + Q.nonzero, Q.real]), set([Q.composite, Q.even, Q.imaginary, + Q.infinite, Q.integer, Q.negative_infinite, Q.odd, + Q.positive_infinite, Q.prime, Q.rational, Q.zero])), + Q.is_true: (set([Q.is_true]), set([])), + Q.lower_triangular: (set([Q.lower_triangular, Q.triangular]), set([])), + Q.negative: (set([Q.commutative, Q.complex, Q.extended_negative, + Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real, + Q.finite, Q.hermitian, Q.negative, Q.nonpositive, Q.nonzero, + Q.real]), set([Q.composite, Q.extended_nonnegative, + Q.extended_positive, Q.imaginary, Q.infinite, + Q.negative_infinite, Q.nonnegative, Q.positive, + Q.positive_infinite, Q.prime, Q.zero])), + Q.negative_infinite: (set([Q.commutative, Q.extended_negative, + Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real, + Q.infinite, Q.negative_infinite]), set([Q.algebraic, + Q.complex, Q.composite, Q.even, Q.extended_nonnegative, + Q.extended_positive, Q.finite, Q.imaginary, Q.integer, + Q.irrational, Q.negative, Q.nonnegative, Q.nonpositive, + Q.nonzero, Q.odd, Q.positive, Q.positive_infinite, Q.prime, + Q.rational, Q.real, Q.transcendental, Q.zero])), + Q.noninteger: (set([Q.noninteger]), set([])), + Q.nonnegative: (set([Q.commutative, Q.complex, Q.extended_nonnegative, + Q.extended_real, Q.finite, Q.hermitian, Q.nonnegative, + Q.real]), set([Q.extended_negative, Q.imaginary, Q.infinite, + Q.negative, Q.negative_infinite, Q.positive_infinite])), + Q.nonpositive: (set([Q.commutative, Q.complex, Q.extended_nonpositive, + Q.extended_real, Q.finite, Q.hermitian, Q.nonpositive, + Q.real]), set([Q.composite, Q.extended_positive, Q.imaginary, + Q.infinite, Q.negative_infinite, Q.positive, + Q.positive_infinite, Q.prime])), + Q.nonzero: (set([Q.commutative, Q.complex, Q.extended_nonzero, + Q.extended_real, Q.finite, Q.hermitian, Q.nonzero, Q.real]), + set([Q.imaginary, Q.infinite, Q.negative_infinite, + Q.positive_infinite, Q.zero])), + Q.normal: (set([Q.normal, Q.square]), set([])), + Q.odd: (set([Q.algebraic, Q.commutative, Q.complex, + Q.extended_nonzero, Q.extended_real, Q.finite, Q.hermitian, + Q.integer, Q.nonzero, Q.odd, Q.rational, Q.real]), + set([Q.even, Q.imaginary, Q.infinite, Q.irrational, + Q.negative_infinite, Q.positive_infinite, Q.transcendental, + Q.zero])), + Q.orthogonal: (set([Q.fullrank, Q.invertible, Q.normal, Q.orthogonal, + Q.positive_definite, Q.square, Q.unitary]), set([Q.singular])), + Q.positive: (set([Q.commutative, Q.complex, Q.extended_nonnegative, + Q.extended_nonzero, Q.extended_positive, Q.extended_real, + Q.finite, Q.hermitian, Q.nonnegative, Q.nonzero, Q.positive, + Q.real]), set([Q.extended_negative, Q.extended_nonpositive, + Q.imaginary, Q.infinite, Q.negative, Q.negative_infinite, + Q.nonpositive, Q.positive_infinite, Q.zero])), + Q.positive_definite: (set([Q.fullrank, Q.invertible, + Q.positive_definite, Q.square]), set([Q.singular])), + Q.positive_infinite: (set([Q.commutative, Q.extended_nonnegative, + Q.extended_nonzero, Q.extended_positive, Q.extended_real, + Q.infinite, Q.positive_infinite]), set([Q.algebraic, + Q.complex, Q.composite, Q.even, Q.extended_negative, + Q.extended_nonpositive, Q.finite, Q.imaginary, Q.integer, + Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative, + Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime, + Q.rational, Q.real, Q.transcendental, Q.zero])), + Q.prime: (set([Q.algebraic, Q.commutative, Q.complex, + Q.extended_nonnegative, Q.extended_nonzero, + Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian, + Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.prime, + Q.rational, Q.real]), set([Q.composite, Q.extended_negative, + Q.extended_nonpositive, Q.imaginary, Q.infinite, Q.irrational, + Q.negative, Q.negative_infinite, Q.nonpositive, + Q.positive_infinite, Q.transcendental, Q.zero])), + Q.rational: (set([Q.algebraic, Q.commutative, Q.complex, + Q.extended_real, Q.finite, Q.hermitian, Q.rational, Q.real]), + set([Q.imaginary, Q.infinite, Q.irrational, + Q.negative_infinite, Q.positive_infinite, Q.transcendental])), + Q.real: (set([Q.commutative, Q.complex, Q.extended_real, Q.finite, + Q.hermitian, Q.real]), set([Q.imaginary, Q.infinite, + Q.negative_infinite, Q.positive_infinite])), + Q.real_elements: (set([Q.complex_elements, Q.real_elements]), set([])), + Q.singular: (set([Q.singular]), set([Q.invertible, Q.orthogonal, + Q.positive_definite, Q.unitary])), + Q.square: (set([Q.square]), set([])), + Q.symmetric: (set([Q.square, Q.symmetric]), set([])), + Q.transcendental: (set([Q.commutative, Q.complex, Q.finite, + Q.transcendental]), set([Q.algebraic, Q.composite, Q.even, + Q.infinite, Q.integer, Q.negative_infinite, Q.odd, + Q.positive_infinite, Q.prime, Q.rational, Q.zero])), + Q.triangular: (set([Q.triangular]), set([])), + Q.unit_triangular: (set([Q.triangular, Q.unit_triangular]), set([])), + Q.unitary: (set([Q.fullrank, Q.invertible, Q.normal, Q.square, + Q.unitary]), set([Q.singular])), + Q.upper_triangular: (set([Q.triangular, Q.upper_triangular]), set([])), + Q.zero: (set([Q.algebraic, Q.commutative, Q.complex, Q.even, + Q.extended_nonnegative, Q.extended_nonpositive, + Q.extended_real, Q.finite, Q.hermitian, Q.integer, + Q.nonnegative, Q.nonpositive, Q.rational, Q.real, Q.zero]), + set([Q.composite, Q.extended_negative, Q.extended_nonzero, + Q.extended_positive, Q.imaginary, Q.infinite, Q.irrational, + Q.negative, Q.negative_infinite, Q.nonzero, Q.odd, Q.positive, + Q.positive_infinite, Q.prime, Q.transcendental])), + } diff --git a/phivenv/Lib/site-packages/sympy/assumptions/assume.py b/phivenv/Lib/site-packages/sympy/assumptions/assume.py new file mode 100644 index 0000000000000000000000000000000000000000..743195a865a1d39389d471b95728ca79834ed019 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/assume.py @@ -0,0 +1,485 @@ +"""A module which implements predicates and assumption context.""" + +from contextlib import contextmanager +import inspect +from sympy.core.symbol import Str +from sympy.core.sympify import _sympify +from sympy.logic.boolalg import Boolean, false, true +from sympy.multipledispatch.dispatcher import Dispatcher, str_signature +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence +from sympy.utilities.source import get_class + + +class AssumptionsContext(set): + """ + Set containing default assumptions which are applied to the ``ask()`` + function. + + Explanation + =========== + + This is used to represent global assumptions, but you can also use this + class to create your own local assumptions contexts. It is basically a thin + wrapper to Python's set, so see its documentation for advanced usage. + + Examples + ======== + + The default assumption context is ``global_assumptions``, which is initially empty: + + >>> from sympy import ask, Q + >>> from sympy.assumptions import global_assumptions + >>> global_assumptions + AssumptionsContext() + + You can add default assumptions: + + >>> from sympy.abc import x + >>> global_assumptions.add(Q.real(x)) + >>> global_assumptions + AssumptionsContext({Q.real(x)}) + >>> ask(Q.real(x)) + True + + And remove them: + + >>> global_assumptions.remove(Q.real(x)) + >>> print(ask(Q.real(x))) + None + + The ``clear()`` method removes every assumption: + + >>> global_assumptions.add(Q.positive(x)) + >>> global_assumptions + AssumptionsContext({Q.positive(x)}) + >>> global_assumptions.clear() + >>> global_assumptions + AssumptionsContext() + + See Also + ======== + + assuming + + """ + + def add(self, *assumptions): + """Add assumptions.""" + for a in assumptions: + super().add(a) + + def _sympystr(self, printer): + if not self: + return "%s()" % self.__class__.__name__ + return "{}({})".format(self.__class__.__name__, printer._print_set(self)) + +global_assumptions = AssumptionsContext() + + +class AppliedPredicate(Boolean): + """ + The class of expressions resulting from applying ``Predicate`` to + the arguments. ``AppliedPredicate`` merely wraps its argument and + remain unevaluated. To evaluate it, use the ``ask()`` function. + + Examples + ======== + + >>> from sympy import Q, ask + >>> Q.integer(1) + Q.integer(1) + + The ``function`` attribute returns the predicate, and the ``arguments`` + attribute returns the tuple of arguments. + + >>> type(Q.integer(1)) + + >>> Q.integer(1).function + Q.integer + >>> Q.integer(1).arguments + (1,) + + Applied predicates can be evaluated to a boolean value with ``ask``: + + >>> ask(Q.integer(1)) + True + + """ + __slots__ = () + + def __new__(cls, predicate, *args): + if not isinstance(predicate, Predicate): + raise TypeError("%s is not a Predicate." % predicate) + args = map(_sympify, args) + return super().__new__(cls, predicate, *args) + + @property + def arg(self): + """ + Return the expression used by this assumption. + + Examples + ======== + + >>> from sympy import Q, Symbol + >>> x = Symbol('x') + >>> a = Q.integer(x + 1) + >>> a.arg + x + 1 + + """ + # Will be deprecated + args = self._args + if len(args) == 2: + # backwards compatibility + return args[1] + raise TypeError("'arg' property is allowed only for unary predicates.") + + @property + def function(self): + """ + Return the predicate. + """ + # Will be changed to self.args[0] after args overriding is removed + return self._args[0] + + @property + def arguments(self): + """ + Return the arguments which are applied to the predicate. + """ + # Will be changed to self.args[1:] after args overriding is removed + return self._args[1:] + + def _eval_ask(self, assumptions): + return self.function.eval(self.arguments, assumptions) + + @property + def binary_symbols(self): + from .ask import Q + if self.function == Q.is_true: + i = self.arguments[0] + if i.is_Boolean or i.is_Symbol: + return i.binary_symbols + if self.function in (Q.eq, Q.ne): + if true in self.arguments or false in self.arguments: + if self.arguments[0].is_Symbol: + return {self.arguments[0]} + elif self.arguments[1].is_Symbol: + return {self.arguments[1]} + return set() + + +class PredicateMeta(type): + def __new__(cls, clsname, bases, dct): + # If handler is not defined, assign empty dispatcher. + if "handler" not in dct: + name = f"Ask{clsname.capitalize()}Handler" + handler = Dispatcher(name, doc="Handler for key %s" % name) + dct["handler"] = handler + + dct["_orig_doc"] = dct.get("__doc__", "") + + return super().__new__(cls, clsname, bases, dct) + + @property + def __doc__(cls): + handler = cls.handler + doc = cls._orig_doc + if cls is not Predicate and handler is not None: + doc += "Handler\n" + doc += " =======\n\n" + + # Append the handler's doc without breaking sphinx documentation. + docs = [" Multiply dispatched method: %s" % handler.name] + if handler.doc: + for line in handler.doc.splitlines(): + if not line: + continue + docs.append(" %s" % line) + other = [] + for sig in handler.ordering[::-1]: + func = handler.funcs[sig] + if func.__doc__: + s = ' Inputs: <%s>' % str_signature(sig) + lines = [] + for line in func.__doc__.splitlines(): + lines.append(" %s" % line) + s += "\n".join(lines) + docs.append(s) + else: + other.append(str_signature(sig)) + if other: + othersig = " Other signatures:" + for line in other: + othersig += "\n * %s" % line + docs.append(othersig) + + doc += '\n\n'.join(docs) + + return doc + + +class Predicate(Boolean, metaclass=PredicateMeta): + """ + Base class for mathematical predicates. It also serves as a + constructor for undefined predicate objects. + + Explanation + =========== + + Predicate is a function that returns a boolean value [1]. + + Predicate function is object, and it is instance of predicate class. + When a predicate is applied to arguments, ``AppliedPredicate`` + instance is returned. This merely wraps the argument and remain + unevaluated. To obtain the truth value of applied predicate, use the + function ``ask``. + + Evaluation of predicate is done by multiple dispatching. You can + register new handler to the predicate to support new types. + + Every predicate in SymPy can be accessed via the property of ``Q``. + For example, ``Q.even`` returns the predicate which checks if the + argument is even number. + + To define a predicate which can be evaluated, you must subclass this + class, make an instance of it, and register it to ``Q``. After then, + dispatch the handler by argument types. + + If you directly construct predicate using this class, you will get + ``UndefinedPredicate`` which cannot be dispatched. This is useful + when you are building boolean expressions which do not need to be + evaluated. + + Examples + ======== + + Applying and evaluating to boolean value: + + >>> from sympy import Q, ask + >>> ask(Q.prime(7)) + True + + You can define a new predicate by subclassing and dispatching. Here, + we define a predicate for sexy primes [2] as an example. + + >>> from sympy import Predicate, Integer + >>> class SexyPrimePredicate(Predicate): + ... name = "sexyprime" + >>> Q.sexyprime = SexyPrimePredicate() + >>> @Q.sexyprime.register(Integer, Integer) + ... def _(int1, int2, assumptions): + ... args = sorted([int1, int2]) + ... if not all(ask(Q.prime(a), assumptions) for a in args): + ... return False + ... return args[1] - args[0] == 6 + >>> ask(Q.sexyprime(5, 11)) + True + + Direct constructing returns ``UndefinedPredicate``, which can be + applied but cannot be dispatched. + + >>> from sympy import Predicate, Integer + >>> Q.P = Predicate("P") + >>> type(Q.P) + + >>> Q.P(1) + Q.P(1) + >>> Q.P.register(Integer)(lambda expr, assump: True) + Traceback (most recent call last): + ... + TypeError: cannot be dispatched. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29 + .. [2] https://en.wikipedia.org/wiki/Sexy_prime + + """ + + is_Atom = True + + def __new__(cls, *args, **kwargs): + if cls is Predicate: + return UndefinedPredicate(*args, **kwargs) + obj = super().__new__(cls, *args) + return obj + + @property + def name(self): + # May be overridden + return type(self).__name__ + + @classmethod + def register(cls, *types, **kwargs): + """ + Register the signature to the handler. + """ + if cls.handler is None: + raise TypeError("%s cannot be dispatched." % type(cls)) + return cls.handler.register(*types, **kwargs) + + @classmethod + def register_many(cls, *types, **kwargs): + """ + Register multiple signatures to same handler. + """ + def _(func): + for t in types: + if not is_sequence(t): + t = (t,) # for convenience, allow passing `type` to mean `(type,)` + cls.register(*t, **kwargs)(func) + return _ + + def __call__(self, *args): + return AppliedPredicate(self, *args) + + def eval(self, args, assumptions=True): + """ + Evaluate ``self(*args)`` under the given assumptions. + + This uses only direct resolution methods, not logical inference. + """ + result = None + try: + result = self.handler(*args, assumptions=assumptions) + except NotImplementedError: + pass + return result + + def _eval_refine(self, assumptions): + # When Predicate is no longer Boolean, delete this method + return self + + +class UndefinedPredicate(Predicate): + """ + Predicate without handler. + + Explanation + =========== + + This predicate is generated by using ``Predicate`` directly for + construction. It does not have a handler, and evaluating this with + arguments is done by SAT solver. + + Examples + ======== + + >>> from sympy import Predicate, Q + >>> Q.P = Predicate('P') + >>> Q.P.func + + >>> Q.P.name + Str('P') + + """ + + handler = None + + def __new__(cls, name, handlers=None): + # "handlers" parameter supports old design + if not isinstance(name, Str): + name = Str(name) + obj = super(Boolean, cls).__new__(cls, name) + obj.handlers = handlers or [] + return obj + + @property + def name(self): + return self.args[0] + + def _hashable_content(self): + return (self.name,) + + def __getnewargs__(self): + return (self.name,) + + def __call__(self, expr): + return AppliedPredicate(self, expr) + + def add_handler(self, handler): + sympy_deprecation_warning( + """ + The AskHandler system is deprecated. Predicate.add_handler() + should be replaced with the multipledispatch handler of Predicate. + """, + deprecated_since_version="1.8", + active_deprecations_target='deprecated-askhandler', + ) + self.handlers.append(handler) + + def remove_handler(self, handler): + sympy_deprecation_warning( + """ + The AskHandler system is deprecated. Predicate.remove_handler() + should be replaced with the multipledispatch handler of Predicate. + """, + deprecated_since_version="1.8", + active_deprecations_target='deprecated-askhandler', + ) + self.handlers.remove(handler) + + def eval(self, args, assumptions=True): + # Support for deprecated design + # When old design is removed, this will always return None + sympy_deprecation_warning( + """ + The AskHandler system is deprecated. Evaluating UndefinedPredicate + objects should be replaced with the multipledispatch handler of + Predicate. + """, + deprecated_since_version="1.8", + active_deprecations_target='deprecated-askhandler', + stacklevel=5, + ) + expr, = args + res, _res = None, None + mro = inspect.getmro(type(expr)) + for handler in self.handlers: + cls = get_class(handler) + for subclass in mro: + eval_ = getattr(cls, subclass.__name__, None) + if eval_ is None: + continue + res = eval_(expr, assumptions) + # Do not stop if value returned is None + # Try to check for higher classes + if res is None: + continue + if _res is None: + _res = res + else: + # only check consistency if both resolutors have concluded + if _res != res: + raise ValueError('incompatible resolutors') + break + return res + + +@contextmanager +def assuming(*assumptions): + """ + Context manager for assumptions. + + Examples + ======== + + >>> from sympy import assuming, Q, ask + >>> from sympy.abc import x, y + >>> print(ask(Q.integer(x + y))) + None + >>> with assuming(Q.integer(x), Q.integer(y)): + ... print(ask(Q.integer(x + y))) + True + """ + old_global_assumptions = global_assumptions.copy() + global_assumptions.update(assumptions) + try: + yield + finally: + global_assumptions.clear() + global_assumptions.update(old_global_assumptions) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/cnf.py b/phivenv/Lib/site-packages/sympy/assumptions/cnf.py new file mode 100644 index 0000000000000000000000000000000000000000..a95d27bed6eeb64c42f4edd9d49bd8e5753069e5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/cnf.py @@ -0,0 +1,445 @@ +""" +The classes used here are for the internal use of assumptions system +only and should not be used anywhere else as these do not possess the +signatures common to SymPy objects. For general use of logic constructs +please refer to sympy.logic classes And, Or, Not, etc. +""" +from itertools import combinations, product, zip_longest +from sympy.assumptions.assume import AppliedPredicate, Predicate +from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le +from sympy.core.singleton import S +from sympy.logic.boolalg import Or, And, Not, Xnor +from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor) + + +class Literal: + """ + The smallest element of a CNF object. + + Parameters + ========== + + lit : Boolean expression + + is_Not : bool + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.cnf import Literal + >>> from sympy.abc import x + >>> Literal(Q.even(x)) + Literal(Q.even(x), False) + >>> Literal(~Q.even(x)) + Literal(Q.even(x), True) + """ + + def __new__(cls, lit, is_Not=False): + if isinstance(lit, Not): + lit = lit.args[0] + is_Not = True + elif isinstance(lit, (AND, OR, Literal)): + return ~lit if is_Not else lit + obj = super().__new__(cls) + obj.lit = lit + obj.is_Not = is_Not + return obj + + @property + def arg(self): + return self.lit + + def rcall(self, expr): + if callable(self.lit): + lit = self.lit(expr) + else: + lit = self.lit.apply(expr) + return type(self)(lit, self.is_Not) + + def __invert__(self): + is_Not = not self.is_Not + return Literal(self.lit, is_Not) + + def __str__(self): + return '{}({}, {})'.format(type(self).__name__, self.lit, self.is_Not) + + __repr__ = __str__ + + def __eq__(self, other): + return self.arg == other.arg and self.is_Not == other.is_Not + + def __hash__(self): + h = hash((type(self).__name__, self.arg, self.is_Not)) + return h + + +class OR: + """ + A low-level implementation for Or + """ + def __init__(self, *args): + self._args = args + + @property + def args(self): + return sorted(self._args, key=str) + + def rcall(self, expr): + return type(self)(*[arg.rcall(expr) + for arg in self._args + ]) + + def __invert__(self): + return AND(*[~arg for arg in self._args]) + + def __hash__(self): + return hash((type(self).__name__,) + tuple(self.args)) + + def __eq__(self, other): + return self.args == other.args + + def __str__(self): + s = '(' + ' | '.join([str(arg) for arg in self.args]) + ')' + return s + + __repr__ = __str__ + + +class AND: + """ + A low-level implementation for And + """ + def __init__(self, *args): + self._args = args + + def __invert__(self): + return OR(*[~arg for arg in self._args]) + + @property + def args(self): + return sorted(self._args, key=str) + + def rcall(self, expr): + return type(self)(*[arg.rcall(expr) + for arg in self._args + ]) + + def __hash__(self): + return hash((type(self).__name__,) + tuple(self.args)) + + def __eq__(self, other): + return self.args == other.args + + def __str__(self): + s = '('+' & '.join([str(arg) for arg in self.args])+')' + return s + + __repr__ = __str__ + + +def to_NNF(expr, composite_map=None): + """ + Generates the Negation Normal Form of any boolean expression in terms + of AND, OR, and Literal objects. + + Examples + ======== + + >>> from sympy import Q, Eq + >>> from sympy.assumptions.cnf import to_NNF + >>> from sympy.abc import x, y + >>> expr = Q.even(x) & ~Q.positive(x) + >>> to_NNF(expr) + (Literal(Q.even(x), False) & Literal(Q.positive(x), True)) + + Supported boolean objects are converted to corresponding predicates. + + >>> to_NNF(Eq(x, y)) + Literal(Q.eq(x, y), False) + + If ``composite_map`` argument is given, ``to_NNF`` decomposes the + specified predicate into a combination of primitive predicates. + + >>> cmap = {Q.nonpositive: Q.negative | Q.zero} + >>> to_NNF(Q.nonpositive, cmap) + (Literal(Q.negative, False) | Literal(Q.zero, False)) + >>> to_NNF(Q.nonpositive(x), cmap) + (Literal(Q.negative(x), False) | Literal(Q.zero(x), False)) + """ + from sympy.assumptions.ask import Q + + if composite_map is None: + composite_map = {} + + + binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le} + if type(expr) in binrelpreds: + pred = binrelpreds[type(expr)] + expr = pred(*expr.args) + + if isinstance(expr, Not): + arg = expr.args[0] + tmp = to_NNF(arg, composite_map) # Strategy: negate the NNF of expr + return ~tmp + + if isinstance(expr, Or): + return OR(*[to_NNF(x, composite_map) for x in Or.make_args(expr)]) + + if isinstance(expr, And): + return AND(*[to_NNF(x, composite_map) for x in And.make_args(expr)]) + + if isinstance(expr, Nand): + tmp = AND(*[to_NNF(x, composite_map) for x in expr.args]) + return ~tmp + + if isinstance(expr, Nor): + tmp = OR(*[to_NNF(x, composite_map) for x in expr.args]) + return ~tmp + + if isinstance(expr, Xor): + cnfs = [] + for i in range(0, len(expr.args) + 1, 2): + for neg in combinations(expr.args, i): + clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map) + for s in expr.args] + cnfs.append(OR(*clause)) + return AND(*cnfs) + + if isinstance(expr, Xnor): + cnfs = [] + for i in range(0, len(expr.args) + 1, 2): + for neg in combinations(expr.args, i): + clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map) + for s in expr.args] + cnfs.append(OR(*clause)) + return ~AND(*cnfs) + + if isinstance(expr, Implies): + L, R = to_NNF(expr.args[0], composite_map), to_NNF(expr.args[1], composite_map) + return OR(~L, R) + + if isinstance(expr, Equivalent): + cnfs = [] + for a, b in zip_longest(expr.args, expr.args[1:], fillvalue=expr.args[0]): + a = to_NNF(a, composite_map) + b = to_NNF(b, composite_map) + cnfs.append(OR(~a, b)) + return AND(*cnfs) + + if isinstance(expr, ITE): + L = to_NNF(expr.args[0], composite_map) + M = to_NNF(expr.args[1], composite_map) + R = to_NNF(expr.args[2], composite_map) + return AND(OR(~L, M), OR(L, R)) + + if isinstance(expr, AppliedPredicate): + pred, args = expr.function, expr.arguments + newpred = composite_map.get(pred, None) + if newpred is not None: + return to_NNF(newpred.rcall(*args), composite_map) + + if isinstance(expr, Predicate): + newpred = composite_map.get(expr, None) + if newpred is not None: + return to_NNF(newpred, composite_map) + + return Literal(expr) + + +def distribute_AND_over_OR(expr): + """ + Distributes AND over OR in the NNF expression. + Returns the result( Conjunctive Normal Form of expression) + as a CNF object. + """ + if not isinstance(expr, (AND, OR)): + tmp = set() + tmp.add(frozenset((expr,))) + return CNF(tmp) + + if isinstance(expr, OR): + return CNF.all_or(*[distribute_AND_over_OR(arg) + for arg in expr._args]) + + if isinstance(expr, AND): + return CNF.all_and(*[distribute_AND_over_OR(arg) + for arg in expr._args]) + + +class CNF: + """ + Class to represent CNF of a Boolean expression. + Consists of set of clauses, which themselves are stored as + frozenset of Literal objects. + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.cnf import CNF + >>> from sympy.abc import x + >>> cnf = CNF.from_prop(Q.real(x) & ~Q.zero(x)) + >>> cnf.clauses + {frozenset({Literal(Q.zero(x), True)}), + frozenset({Literal(Q.negative(x), False), + Literal(Q.positive(x), False), Literal(Q.zero(x), False)})} + """ + def __init__(self, clauses=None): + if not clauses: + clauses = set() + self.clauses = clauses + + def add(self, prop): + clauses = CNF.to_CNF(prop).clauses + self.add_clauses(clauses) + + def __str__(self): + s = ' & '.join( + ['(' + ' | '.join([str(lit) for lit in clause]) +')' + for clause in self.clauses] + ) + return s + + def extend(self, props): + for p in props: + self.add(p) + return self + + def copy(self): + return CNF(set(self.clauses)) + + def add_clauses(self, clauses): + self.clauses |= clauses + + @classmethod + def from_prop(cls, prop): + res = cls() + res.add(prop) + return res + + def __iand__(self, other): + self.add_clauses(other.clauses) + return self + + def all_predicates(self): + predicates = set() + for c in self.clauses: + predicates |= {arg.lit for arg in c} + return predicates + + def _or(self, cnf): + clauses = set() + for a, b in product(self.clauses, cnf.clauses): + tmp = set(a) + tmp.update(b) + clauses.add(frozenset(tmp)) + return CNF(clauses) + + def _and(self, cnf): + clauses = self.clauses.union(cnf.clauses) + return CNF(clauses) + + def _not(self): + clss = list(self.clauses) + ll = {frozenset((~x,)) for x in clss[-1]} + ll = CNF(ll) + + for rest in clss[:-1]: + p = {frozenset((~x,)) for x in rest} + ll = ll._or(CNF(p)) + return ll + + def rcall(self, expr): + clause_list = [] + for clause in self.clauses: + lits = [arg.rcall(expr) for arg in clause] + clause_list.append(OR(*lits)) + expr = AND(*clause_list) + return distribute_AND_over_OR(expr) + + @classmethod + def all_or(cls, *cnfs): + b = cnfs[0].copy() + for rest in cnfs[1:]: + b = b._or(rest) + return b + + @classmethod + def all_and(cls, *cnfs): + b = cnfs[0].copy() + for rest in cnfs[1:]: + b = b._and(rest) + return b + + @classmethod + def to_CNF(cls, expr): + from sympy.assumptions.facts import get_composite_predicates + expr = to_NNF(expr, get_composite_predicates()) + expr = distribute_AND_over_OR(expr) + return expr + + @classmethod + def CNF_to_cnf(cls, cnf): + """ + Converts CNF object to SymPy's boolean expression + retaining the form of expression. + """ + def remove_literal(arg): + return Not(arg.lit) if arg.is_Not else arg.lit + + return And(*(Or(*(remove_literal(arg) for arg in clause)) for clause in cnf.clauses)) + + +class EncodedCNF: + """ + Class for encoding the CNF expression. + """ + def __init__(self, data=None, encoding=None): + if not data and not encoding: + data = [] + encoding = {} + self.data = data + self.encoding = encoding + self._symbols = list(encoding.keys()) + + def from_cnf(self, cnf): + self._symbols = list(cnf.all_predicates()) + n = len(self._symbols) + self.encoding = dict(zip(self._symbols, range(1, n + 1))) + self.data = [self.encode(clause) for clause in cnf.clauses] + + @property + def symbols(self): + return self._symbols + + @property + def variables(self): + return range(1, len(self._symbols) + 1) + + def copy(self): + new_data = [set(clause) for clause in self.data] + return EncodedCNF(new_data, dict(self.encoding)) + + def add_prop(self, prop): + cnf = CNF.from_prop(prop) + self.add_from_cnf(cnf) + + def add_from_cnf(self, cnf): + clauses = [self.encode(clause) for clause in cnf.clauses] + self.data += clauses + + def encode_arg(self, arg): + literal = arg.lit + value = self.encoding.get(literal, None) + if value is None: + n = len(self._symbols) + self._symbols.append(literal) + value = self.encoding[literal] = n + 1 + if arg.is_Not: + return -value + else: + return value + + def encode(self, clause): + return {self.encode_arg(arg) if not arg.lit == S.false else 0 for arg in clause} diff --git a/phivenv/Lib/site-packages/sympy/assumptions/facts.py b/phivenv/Lib/site-packages/sympy/assumptions/facts.py new file mode 100644 index 0000000000000000000000000000000000000000..2ff268677cf74e252ac6c3bc3eecbea08b9414d0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/facts.py @@ -0,0 +1,270 @@ +""" +Known facts in assumptions module. + +This module defines the facts between unary predicates in ``get_known_facts()``, +and supports functions to generate the contents in +``sympy.assumptions.ask_generated`` file. +""" + +from sympy.assumptions.ask import Q +from sympy.assumptions.assume import AppliedPredicate +from sympy.core.cache import cacheit +from sympy.core.symbol import Symbol +from sympy.logic.boolalg import (to_cnf, And, Not, Implies, Equivalent, + Exclusive,) +from sympy.logic.inference import satisfiable + + +@cacheit +def get_composite_predicates(): + # To reduce the complexity of sat solver, these predicates are + # transformed into the combination of primitive predicates. + return { + Q.real : Q.negative | Q.zero | Q.positive, + Q.integer : Q.even | Q.odd, + Q.nonpositive : Q.negative | Q.zero, + Q.nonzero : Q.negative | Q.positive, + Q.nonnegative : Q.zero | Q.positive, + Q.extended_real : Q.negative_infinite | Q.negative | Q.zero | Q.positive | Q.positive_infinite, + Q.extended_positive: Q.positive | Q.positive_infinite, + Q.extended_negative: Q.negative | Q.negative_infinite, + Q.extended_nonzero: Q.negative_infinite | Q.negative | Q.positive | Q.positive_infinite, + Q.extended_nonpositive: Q.negative_infinite | Q.negative | Q.zero, + Q.extended_nonnegative: Q.zero | Q.positive | Q.positive_infinite, + Q.complex : Q.algebraic | Q.transcendental + } + + +@cacheit +def get_known_facts(x=None): + """ + Facts between unary predicates. + + Parameters + ========== + + x : Symbol, optional + Placeholder symbol for unary facts. Default is ``Symbol('x')``. + + Returns + ======= + + fact : Known facts in conjugated normal form. + + """ + if x is None: + x = Symbol('x') + + fact = And( + get_number_facts(x), + get_matrix_facts(x) + ) + return fact + + +@cacheit +def get_number_facts(x = None): + """ + Facts between unary number predicates. + + Parameters + ========== + + x : Symbol, optional + Placeholder symbol for unary facts. Default is ``Symbol('x')``. + + Returns + ======= + + fact : Known facts in conjugated normal form. + + """ + if x is None: + x = Symbol('x') + + fact = And( + # primitive predicates for extended real exclude each other. + Exclusive(Q.negative_infinite(x), Q.negative(x), Q.zero(x), + Q.positive(x), Q.positive_infinite(x)), + + # build complex plane + Exclusive(Q.real(x), Q.imaginary(x)), + Implies(Q.real(x) | Q.imaginary(x), Q.complex(x)), + + # other subsets of complex + Exclusive(Q.transcendental(x), Q.algebraic(x)), + Equivalent(Q.real(x), Q.rational(x) | Q.irrational(x)), + Exclusive(Q.irrational(x), Q.rational(x)), + Implies(Q.rational(x), Q.algebraic(x)), + + # integers + Exclusive(Q.even(x), Q.odd(x)), + Implies(Q.integer(x), Q.rational(x)), + Implies(Q.zero(x), Q.even(x)), + Exclusive(Q.composite(x), Q.prime(x)), + Implies(Q.composite(x) | Q.prime(x), Q.integer(x) & Q.positive(x)), + Implies(Q.even(x) & Q.positive(x) & ~Q.prime(x), Q.composite(x)), + + # hermitian and antihermitian + Implies(Q.real(x), Q.hermitian(x)), + Implies(Q.imaginary(x), Q.antihermitian(x)), + Implies(Q.zero(x), Q.hermitian(x) | Q.antihermitian(x)), + + # define finity and infinity, and build extended real line + Exclusive(Q.infinite(x), Q.finite(x)), + Implies(Q.complex(x), Q.finite(x)), + Implies(Q.negative_infinite(x) | Q.positive_infinite(x), Q.infinite(x)), + + # commutativity + Implies(Q.finite(x) | Q.infinite(x), Q.commutative(x)), + ) + return fact + + +@cacheit +def get_matrix_facts(x = None): + """ + Facts between unary matrix predicates. + + Parameters + ========== + + x : Symbol, optional + Placeholder symbol for unary facts. Default is ``Symbol('x')``. + + Returns + ======= + + fact : Known facts in conjugated normal form. + + """ + if x is None: + x = Symbol('x') + + fact = And( + # matrices + Implies(Q.orthogonal(x), Q.positive_definite(x)), + Implies(Q.orthogonal(x), Q.unitary(x)), + Implies(Q.unitary(x) & Q.real_elements(x), Q.orthogonal(x)), + Implies(Q.unitary(x), Q.normal(x)), + Implies(Q.unitary(x), Q.invertible(x)), + Implies(Q.normal(x), Q.square(x)), + Implies(Q.diagonal(x), Q.normal(x)), + Implies(Q.positive_definite(x), Q.invertible(x)), + Implies(Q.diagonal(x), Q.upper_triangular(x)), + Implies(Q.diagonal(x), Q.lower_triangular(x)), + Implies(Q.lower_triangular(x), Q.triangular(x)), + Implies(Q.upper_triangular(x), Q.triangular(x)), + Implies(Q.triangular(x), Q.upper_triangular(x) | Q.lower_triangular(x)), + Implies(Q.upper_triangular(x) & Q.lower_triangular(x), Q.diagonal(x)), + Implies(Q.diagonal(x), Q.symmetric(x)), + Implies(Q.unit_triangular(x), Q.triangular(x)), + Implies(Q.invertible(x), Q.fullrank(x)), + Implies(Q.invertible(x), Q.square(x)), + Implies(Q.symmetric(x), Q.square(x)), + Implies(Q.fullrank(x) & Q.square(x), Q.invertible(x)), + Equivalent(Q.invertible(x), ~Q.singular(x)), + Implies(Q.integer_elements(x), Q.real_elements(x)), + Implies(Q.real_elements(x), Q.complex_elements(x)), + ) + return fact + + + +def generate_known_facts_dict(keys, fact): + """ + Computes and returns a dictionary which contains the relations between + unary predicates. + + Each key is a predicate, and item is two groups of predicates. + First group contains the predicates which are implied by the key, and + second group contains the predicates which are rejected by the key. + + All predicates in *keys* and *fact* must be unary and have same placeholder + symbol. + + Parameters + ========== + + keys : list of AppliedPredicate instances. + + fact : Fact between predicates in conjugated normal form. + + Examples + ======== + + >>> from sympy import Q, And, Implies + >>> from sympy.assumptions.facts import generate_known_facts_dict + >>> from sympy.abc import x + >>> keys = [Q.even(x), Q.odd(x), Q.zero(x)] + >>> fact = And(Implies(Q.even(x), ~Q.odd(x)), + ... Implies(Q.zero(x), Q.even(x))) + >>> generate_known_facts_dict(keys, fact) + {Q.even: ({Q.even}, {Q.odd}), + Q.odd: ({Q.odd}, {Q.even, Q.zero}), + Q.zero: ({Q.even, Q.zero}, {Q.odd})} + """ + fact_cnf = to_cnf(fact) + mapping = single_fact_lookup(keys, fact_cnf) + + ret = {} + for key, value in mapping.items(): + implied = set() + rejected = set() + for expr in value: + if isinstance(expr, AppliedPredicate): + implied.add(expr.function) + elif isinstance(expr, Not): + pred = expr.args[0] + rejected.add(pred.function) + ret[key.function] = (implied, rejected) + return ret + + +@cacheit +def get_known_facts_keys(): + """ + Return every unary predicates registered to ``Q``. + + This function is used to generate the keys for + ``generate_known_facts_dict``. + + """ + # exclude polyadic predicates + exclude = {Q.eq, Q.ne, Q.gt, Q.lt, Q.ge, Q.le} + + result = [] + for attr in Q.__class__.__dict__: + if attr.startswith('__'): + continue + pred = getattr(Q, attr) + if pred in exclude: + continue + result.append(pred) + return result + + +def single_fact_lookup(known_facts_keys, known_facts_cnf): + # Return the dictionary for quick lookup of single fact + mapping = {} + for key in known_facts_keys: + mapping[key] = {key} + for other_key in known_facts_keys: + if other_key != key: + if ask_full_inference(other_key, key, known_facts_cnf): + mapping[key].add(other_key) + if ask_full_inference(~other_key, key, known_facts_cnf): + mapping[key].add(~other_key) + return mapping + + +def ask_full_inference(proposition, assumptions, known_facts_cnf): + """ + Method for inferring properties about objects. + + """ + if not satisfiable(And(known_facts_cnf, assumptions, proposition)): + return False + if not satisfiable(And(known_facts_cnf, assumptions, Not(proposition))): + return True + return None diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__init__.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0fbe618eb8b43e252ac8fb0baf1eeee22bf347cc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__init__.py @@ -0,0 +1,13 @@ +""" +Multipledispatch handlers for ``Predicate`` are implemented here. +Handlers in this module are not directly imported to other modules in +order to avoid circular import problem. +""" + +from .common import (AskHandler, CommonHandler, + test_closed_group) + +__all__ = [ + 'AskHandler', 'CommonHandler', + 'test_closed_group' +] diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2e41a981b53a01b68d4f66c7e45183bc62ae95a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/calculus.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/calculus.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e3b96c4b6904d5be038037e6cc9dc85f062efd9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/calculus.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/common.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/common.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b121c034873738d3bc757cc1fa676cdd5279abe Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/common.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/matrices.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/matrices.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7519154010b8ebe4f5ace6c314fe71274d812ee0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/matrices.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/ntheory.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/ntheory.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34780d77229435bebc2a403718607b08f378d1bf Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/ntheory.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/order.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/order.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4595cbbb0078fb58f1fc43b1006aa461cc66ebe2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/order.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/sets.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/sets.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..467244d619cdb76b1c51bb41ed96a438b2939f3f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/handlers/__pycache__/sets.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/calculus.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/calculus.py new file mode 100644 index 0000000000000000000000000000000000000000..e2b9c43ccea216988a25aa671ea23bc81d2209ff --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/calculus.py @@ -0,0 +1,273 @@ +""" +This module contains query handlers responsible for calculus queries: +infinitesimal, finite, etc. +""" + +from sympy.assumptions import Q, ask +from sympy.core import Expr, Add, Mul, Pow, Symbol +from sympy.core.numbers import (NegativeInfinity, GoldenRatio, + Infinity, Exp1, ComplexInfinity, ImaginaryUnit, NaN, Number, Pi, E, + TribonacciConstant) +from sympy.functions import cos, exp, log, sign, sin +from sympy.logic.boolalg import conjuncts + +from ..predicates.calculus import (FinitePredicate, InfinitePredicate, + PositiveInfinitePredicate, NegativeInfinitePredicate) + + +# FinitePredicate + + +@FinitePredicate.register(Symbol) +def _(expr, assumptions): + """ + Handles Symbol. + """ + if expr.is_finite is not None: + return expr.is_finite + if Q.finite(expr) in conjuncts(assumptions): + return True + return None + +@FinitePredicate.register(Add) +def _(expr, assumptions): + """ + Return True if expr is bounded, False if not and None if unknown. + + Truth Table: + + +-------+-----+-----------+-----------+ + | | | | | + | | B | U | ? | + | | | | | + +-------+-----+---+---+---+---+---+---+ + | | | | | | | | | + | | |'+'|'-'|'x'|'+'|'-'|'x'| + | | | | | | | | | + +-------+-----+---+---+---+---+---+---+ + | | | | | + | B | B | U | ? | + | | | | | + +---+---+-----+---+---+---+---+---+---+ + | | | | | | | | | | + | |'+'| | U | ? | ? | U | ? | ? | + | | | | | | | | | | + | +---+-----+---+---+---+---+---+---+ + | | | | | | | | | | + | U |'-'| | ? | U | ? | ? | U | ? | + | | | | | | | | | | + | +---+-----+---+---+---+---+---+---+ + | | | | | | + | |'x'| | ? | ? | + | | | | | | + +---+---+-----+---+---+---+---+---+---+ + | | | | | + | ? | | | ? | + | | | | | + +-------+-----+-----------+---+---+---+ + + * 'B' = Bounded + + * 'U' = Unbounded + + * '?' = unknown boundedness + + * '+' = positive sign + + * '-' = negative sign + + * 'x' = sign unknown + + * All Bounded -> True + + * 1 Unbounded and the rest Bounded -> False + + * >1 Unbounded, all with same known sign -> False + + * Any Unknown and unknown sign -> None + + * Else -> None + + When the signs are not the same you can have an undefined + result as in oo - oo, hence 'bounded' is also undefined. + """ + sign = -1 # sign of unknown or infinite + result = True + for arg in expr.args: + _bounded = ask(Q.finite(arg), assumptions) + if _bounded: + continue + s = ask(Q.extended_positive(arg), assumptions) + # if there has been more than one sign or if the sign of this arg + # is None and Bounded is None or there was already + # an unknown sign, return None + if sign != -1 and s != sign or \ + s is None and None in (_bounded, sign): + return None + else: + sign = s + # once False, do not change + if result is not False: + result = _bounded + return result + +@FinitePredicate.register(Mul) +def _(expr, assumptions): + """ + Return True if expr is bounded, False if not and None if unknown. + + Truth Table: + + +---+---+---+--------+ + | | | | | + | | B | U | ? | + | | | | | + +---+---+---+---+----+ + | | | | | | + | | | | s | /s | + | | | | | | + +---+---+---+---+----+ + | | | | | + | B | B | U | ? | + | | | | | + +---+---+---+---+----+ + | | | | | | + | U | | U | U | ? | + | | | | | | + +---+---+---+---+----+ + | | | | | + | ? | | | ? | + | | | | | + +---+---+---+---+----+ + + * B = Bounded + + * U = Unbounded + + * ? = unknown boundedness + + * s = signed (hence nonzero) + + * /s = not signed + """ + result = True + possible_zero = False + for arg in expr.args: + _bounded = ask(Q.finite(arg), assumptions) + if _bounded: + if ask(Q.zero(arg), assumptions) is not False: + if result is False: + return None + possible_zero = True + elif _bounded is None: + if result is None: + return None + if ask(Q.extended_nonzero(arg), assumptions) is None: + return None + if result is not False: + result = None + else: + if possible_zero: + return None + result = False + return result + +@FinitePredicate.register(Pow) +def _(expr, assumptions): + """ + * Unbounded ** NonZero -> Unbounded + + * Bounded ** Bounded -> Bounded + + * Abs()<=1 ** Positive -> Bounded + + * Abs()>=1 ** Negative -> Bounded + + * Otherwise unknown + """ + if expr.base == E: + return ask(Q.finite(expr.exp), assumptions) + + base_bounded = ask(Q.finite(expr.base), assumptions) + exp_bounded = ask(Q.finite(expr.exp), assumptions) + if base_bounded is None and exp_bounded is None: # Common Case + return None + if base_bounded is False and ask(Q.extended_nonzero(expr.exp), assumptions): + return False + if base_bounded and exp_bounded: + is_base_zero = ask(Q.zero(expr.base),assumptions) + is_exp_negative = ask(Q.negative(expr.exp),assumptions) + if is_base_zero is True and is_exp_negative is True: + return False + if is_base_zero is not False and is_exp_negative is not False: + return None + return True + if (abs(expr.base) <= 1) == True and ask(Q.extended_positive(expr.exp), assumptions): + return True + if (abs(expr.base) >= 1) == True and ask(Q.extended_negative(expr.exp), assumptions): + return True + if (abs(expr.base) >= 1) == True and exp_bounded is False: + return False + return None + +@FinitePredicate.register(exp) +def _(expr, assumptions): + return ask(Q.finite(expr.exp), assumptions) + +@FinitePredicate.register(log) +def _(expr, assumptions): + # After complex -> finite fact is registered to new assumption system, + # querying Q.infinite may be removed. + if ask(Q.infinite(expr.args[0]), assumptions): + return False + return ask(~Q.zero(expr.args[0]), assumptions) + +@FinitePredicate.register_many(cos, sin, Number, Pi, Exp1, GoldenRatio, + TribonacciConstant, ImaginaryUnit, sign) +def _(expr, assumptions): + return True + +@FinitePredicate.register_many(ComplexInfinity, Infinity, NegativeInfinity) +def _(expr, assumptions): + return False + +@FinitePredicate.register(NaN) +def _(expr, assumptions): + return None + + +# InfinitePredicate + + +@InfinitePredicate.register(Expr) +def _(expr, assumptions): + is_finite = Q.finite(expr)._eval_ask(assumptions) + if is_finite is None: + return None + return not is_finite + + +# PositiveInfinitePredicate + + +@PositiveInfinitePredicate.register(Infinity) +def _(expr, assumptions): + return True + + +@PositiveInfinitePredicate.register_many(NegativeInfinity, ComplexInfinity) +def _(expr, assumptions): + return False + + +# NegativeInfinitePredicate + + +@NegativeInfinitePredicate.register(NegativeInfinity) +def _(expr, assumptions): + return True + + +@NegativeInfinitePredicate.register_many(Infinity, ComplexInfinity) +def _(expr, assumptions): + return False diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/common.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/common.py new file mode 100644 index 0000000000000000000000000000000000000000..f6e9f6f321be461c09b16c03b9cec5708404d21a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/common.py @@ -0,0 +1,164 @@ +""" +This module defines base class for handlers and some core handlers: +``Q.commutative`` and ``Q.is_true``. +""" + +from sympy.assumptions import Q, ask, AppliedPredicate +from sympy.core import Basic, Symbol +from sympy.core.logic import _fuzzy_group, fuzzy_and, fuzzy_or +from sympy.core.numbers import NaN, Number +from sympy.logic.boolalg import (And, BooleanTrue, BooleanFalse, conjuncts, + Equivalent, Implies, Not, Or) +from sympy.utilities.exceptions import sympy_deprecation_warning + +from ..predicates.common import CommutativePredicate, IsTruePredicate + + +class AskHandler: + """Base class that all Ask Handlers must inherit.""" + def __new__(cls, *args, **kwargs): + sympy_deprecation_warning( + """ + The AskHandler system is deprecated. The AskHandler class should + be replaced with the multipledispatch handler of Predicate + """, + deprecated_since_version="1.8", + active_deprecations_target='deprecated-askhandler', + ) + return super().__new__(cls, *args, **kwargs) + + +class CommonHandler(AskHandler): + # Deprecated + """Defines some useful methods common to most Handlers. """ + + @staticmethod + def AlwaysTrue(expr, assumptions): + return True + + @staticmethod + def AlwaysFalse(expr, assumptions): + return False + + @staticmethod + def AlwaysNone(expr, assumptions): + return None + + NaN = AlwaysFalse + + +# CommutativePredicate + +@CommutativePredicate.register(Symbol) +def _(expr, assumptions): + """Objects are expected to be commutative unless otherwise stated""" + assumps = conjuncts(assumptions) + if expr.is_commutative is not None: + return expr.is_commutative and not ~Q.commutative(expr) in assumps + if Q.commutative(expr) in assumps: + return True + elif ~Q.commutative(expr) in assumps: + return False + return True + +@CommutativePredicate.register(Basic) +def _(expr, assumptions): + for arg in expr.args: + if not ask(Q.commutative(arg), assumptions): + return False + return True + +@CommutativePredicate.register(Number) +def _(expr, assumptions): + return True + +@CommutativePredicate.register(NaN) +def _(expr, assumptions): + return True + + +# IsTruePredicate + +@IsTruePredicate.register(bool) +def _(expr, assumptions): + return expr + +@IsTruePredicate.register(BooleanTrue) +def _(expr, assumptions): + return True + +@IsTruePredicate.register(BooleanFalse) +def _(expr, assumptions): + return False + +@IsTruePredicate.register(AppliedPredicate) +def _(expr, assumptions): + return ask(expr, assumptions) + +@IsTruePredicate.register(Not) +def _(expr, assumptions): + arg = expr.args[0] + if arg.is_Symbol: + # symbol used as abstract boolean object + return None + value = ask(arg, assumptions=assumptions) + if value in (True, False): + return not value + else: + return None + +@IsTruePredicate.register(Or) +def _(expr, assumptions): + result = False + for arg in expr.args: + p = ask(arg, assumptions=assumptions) + if p is True: + return True + if p is None: + result = None + return result + +@IsTruePredicate.register(And) +def _(expr, assumptions): + result = True + for arg in expr.args: + p = ask(arg, assumptions=assumptions) + if p is False: + return False + if p is None: + result = None + return result + +@IsTruePredicate.register(Implies) +def _(expr, assumptions): + p, q = expr.args + return ask(~p | q, assumptions=assumptions) + +@IsTruePredicate.register(Equivalent) +def _(expr, assumptions): + p, q = expr.args + pt = ask(p, assumptions=assumptions) + if pt is None: + return None + qt = ask(q, assumptions=assumptions) + if qt is None: + return None + return pt == qt + + +#### Helper methods +def test_closed_group(expr, assumptions, key): + """ + Test for membership in a group with respect + to the current operation. + """ + return _fuzzy_group( + (ask(key(a), assumptions) for a in expr.args), quick_exit=True) + +def ask_all(*queries, assumptions): + return fuzzy_and( + (ask(query, assumptions) for query in queries)) + +def ask_any(*queries, assumptions): + return fuzzy_or( + (ask(query, assumptions) for query in queries)) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/matrices.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..3b20385360136629ea037eb7238c45b70ba57fd2 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/matrices.py @@ -0,0 +1,716 @@ +""" +This module contains query handlers responsible for Matrices queries: +Square, Symmetric, Invertible etc. +""" + +from sympy.logic.boolalg import conjuncts +from sympy.assumptions import Q, ask +from sympy.assumptions.handlers import test_closed_group +from sympy.matrices import MatrixBase +from sympy.matrices.expressions import (BlockMatrix, BlockDiagMatrix, Determinant, + DiagMatrix, DiagonalMatrix, HadamardProduct, Identity, Inverse, MatAdd, MatMul, + MatPow, MatrixExpr, MatrixSlice, MatrixSymbol, OneMatrix, Trace, Transpose, + ZeroMatrix) +from sympy.matrices.expressions.blockmatrix import reblock_2x2 +from sympy.matrices.expressions.factorizations import Factorization +from sympy.matrices.expressions.fourier import DFT +from sympy.core.logic import fuzzy_and +from sympy.utilities.iterables import sift +from sympy.core import Basic + +from ..predicates.matrices import (SquarePredicate, SymmetricPredicate, + InvertiblePredicate, OrthogonalPredicate, UnitaryPredicate, + FullRankPredicate, PositiveDefinitePredicate, UpperTriangularPredicate, + LowerTriangularPredicate, DiagonalPredicate, IntegerElementsPredicate, + RealElementsPredicate, ComplexElementsPredicate) + + +def _Factorization(predicate, expr, assumptions): + if predicate in expr.predicates: + return True + + +# SquarePredicate + +@SquarePredicate.register(MatrixExpr) +def _(expr, assumptions): + return expr.shape[0] == expr.shape[1] + + +# SymmetricPredicate + +@SymmetricPredicate.register(MatMul) +def _(expr, assumptions): + factor, mmul = expr.as_coeff_mmul() + if all(ask(Q.symmetric(arg), assumptions) for arg in mmul.args): + return True + # TODO: implement sathandlers system for the matrices. + # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric). + if ask(Q.diagonal(expr), assumptions): + return True + if len(mmul.args) >= 2 and mmul.args[0] == mmul.args[-1].T: + if len(mmul.args) == 2: + return True + return ask(Q.symmetric(MatMul(*mmul.args[1:-1])), assumptions) + +@SymmetricPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + non_negative = ask(~Q.negative(exp), assumptions) + if (non_negative or non_negative == False + and ask(Q.invertible(base), assumptions)): + return ask(Q.symmetric(base), assumptions) + return None + +@SymmetricPredicate.register(MatAdd) +def _(expr, assumptions): + return all(ask(Q.symmetric(arg), assumptions) for arg in expr.args) + +@SymmetricPredicate.register(MatrixSymbol) +def _(expr, assumptions): + if not expr.is_square: + return False + # TODO: implement sathandlers system for the matrices. + # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric). + if ask(Q.diagonal(expr), assumptions): + return True + if Q.symmetric(expr) in conjuncts(assumptions): + return True + +@SymmetricPredicate.register_many(OneMatrix, ZeroMatrix) +def _(expr, assumptions): + return ask(Q.square(expr), assumptions) + +@SymmetricPredicate.register_many(Inverse, Transpose) +def _(expr, assumptions): + return ask(Q.symmetric(expr.arg), assumptions) + +@SymmetricPredicate.register(MatrixSlice) +def _(expr, assumptions): + # TODO: implement sathandlers system for the matrices. + # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric). + if ask(Q.diagonal(expr), assumptions): + return True + if not expr.on_diag: + return None + else: + return ask(Q.symmetric(expr.parent), assumptions) + +@SymmetricPredicate.register(Identity) +def _(expr, assumptions): + return True + + +# InvertiblePredicate + +@InvertiblePredicate.register(MatMul) +def _(expr, assumptions): + factor, mmul = expr.as_coeff_mmul() + if all(ask(Q.invertible(arg), assumptions) for arg in mmul.args): + return True + if any(ask(Q.invertible(arg), assumptions) is False + for arg in mmul.args): + return False + +@InvertiblePredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + if exp.is_negative == False: + return ask(Q.invertible(base), assumptions) + return None + +@InvertiblePredicate.register(MatAdd) +def _(expr, assumptions): + return None + +@InvertiblePredicate.register(MatrixSymbol) +def _(expr, assumptions): + if not expr.is_square: + return False + if Q.invertible(expr) in conjuncts(assumptions): + return True + +@InvertiblePredicate.register_many(Identity, Inverse) +def _(expr, assumptions): + return True + +@InvertiblePredicate.register(ZeroMatrix) +def _(expr, assumptions): + return False + +@InvertiblePredicate.register(OneMatrix) +def _(expr, assumptions): + return expr.shape[0] == 1 and expr.shape[1] == 1 + +@InvertiblePredicate.register(Transpose) +def _(expr, assumptions): + return ask(Q.invertible(expr.arg), assumptions) + +@InvertiblePredicate.register(MatrixSlice) +def _(expr, assumptions): + if not expr.on_diag: + return None + else: + return ask(Q.invertible(expr.parent), assumptions) + +@InvertiblePredicate.register(MatrixBase) +def _(expr, assumptions): + if not expr.is_square: + return False + return expr.rank() == expr.rows + +@InvertiblePredicate.register(MatrixExpr) +def _(expr, assumptions): + if not expr.is_square: + return False + return None + +@InvertiblePredicate.register(BlockMatrix) +def _(expr, assumptions): + if not expr.is_square: + return False + if expr.blockshape == (1, 1): + return ask(Q.invertible(expr.blocks[0, 0]), assumptions) + expr = reblock_2x2(expr) + if expr.blockshape == (2, 2): + [[A, B], [C, D]] = expr.blocks.tolist() + if ask(Q.invertible(A), assumptions) == True: + invertible = ask(Q.invertible(D - C * A.I * B), assumptions) + if invertible is not None: + return invertible + if ask(Q.invertible(B), assumptions) == True: + invertible = ask(Q.invertible(C - D * B.I * A), assumptions) + if invertible is not None: + return invertible + if ask(Q.invertible(C), assumptions) == True: + invertible = ask(Q.invertible(B - A * C.I * D), assumptions) + if invertible is not None: + return invertible + if ask(Q.invertible(D), assumptions) == True: + invertible = ask(Q.invertible(A - B * D.I * C), assumptions) + if invertible is not None: + return invertible + return None + +@InvertiblePredicate.register(BlockDiagMatrix) +def _(expr, assumptions): + if expr.rowblocksizes != expr.colblocksizes: + return None + return fuzzy_and([ask(Q.invertible(a), assumptions) for a in expr.diag]) + + +# OrthogonalPredicate + +@OrthogonalPredicate.register(MatMul) +def _(expr, assumptions): + factor, mmul = expr.as_coeff_mmul() + if (all(ask(Q.orthogonal(arg), assumptions) for arg in mmul.args) and + factor == 1): + return True + if any(ask(Q.invertible(arg), assumptions) is False + for arg in mmul.args): + return False + +@OrthogonalPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if int_exp: + return ask(Q.orthogonal(base), assumptions) + return None + +@OrthogonalPredicate.register(MatAdd) +def _(expr, assumptions): + if (len(expr.args) == 1 and + ask(Q.orthogonal(expr.args[0]), assumptions)): + return True + +@OrthogonalPredicate.register(MatrixSymbol) +def _(expr, assumptions): + if (not expr.is_square or + ask(Q.invertible(expr), assumptions) is False): + return False + if Q.orthogonal(expr) in conjuncts(assumptions): + return True + +@OrthogonalPredicate.register(Identity) +def _(expr, assumptions): + return True + +@OrthogonalPredicate.register(ZeroMatrix) +def _(expr, assumptions): + return False + +@OrthogonalPredicate.register_many(Inverse, Transpose) +def _(expr, assumptions): + return ask(Q.orthogonal(expr.arg), assumptions) + +@OrthogonalPredicate.register(MatrixSlice) +def _(expr, assumptions): + if not expr.on_diag: + return None + else: + return ask(Q.orthogonal(expr.parent), assumptions) + +@OrthogonalPredicate.register(Factorization) +def _(expr, assumptions): + return _Factorization(Q.orthogonal, expr, assumptions) + + +# UnitaryPredicate + +@UnitaryPredicate.register(MatMul) +def _(expr, assumptions): + factor, mmul = expr.as_coeff_mmul() + if (all(ask(Q.unitary(arg), assumptions) for arg in mmul.args) and + abs(factor) == 1): + return True + if any(ask(Q.invertible(arg), assumptions) is False + for arg in mmul.args): + return False + +@UnitaryPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if int_exp: + return ask(Q.unitary(base), assumptions) + return None + +@UnitaryPredicate.register(MatrixSymbol) +def _(expr, assumptions): + if (not expr.is_square or + ask(Q.invertible(expr), assumptions) is False): + return False + if Q.unitary(expr) in conjuncts(assumptions): + return True + +@UnitaryPredicate.register_many(Inverse, Transpose) +def _(expr, assumptions): + return ask(Q.unitary(expr.arg), assumptions) + +@UnitaryPredicate.register(MatrixSlice) +def _(expr, assumptions): + if not expr.on_diag: + return None + else: + return ask(Q.unitary(expr.parent), assumptions) + +@UnitaryPredicate.register_many(DFT, Identity) +def _(expr, assumptions): + return True + +@UnitaryPredicate.register(ZeroMatrix) +def _(expr, assumptions): + return False + +@UnitaryPredicate.register(Factorization) +def _(expr, assumptions): + return _Factorization(Q.unitary, expr, assumptions) + + +# FullRankPredicate + +@FullRankPredicate.register(MatMul) +def _(expr, assumptions): + if all(ask(Q.fullrank(arg), assumptions) for arg in expr.args): + return True + +@FullRankPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if int_exp and ask(~Q.negative(exp), assumptions): + return ask(Q.fullrank(base), assumptions) + return None + +@FullRankPredicate.register(Identity) +def _(expr, assumptions): + return True + +@FullRankPredicate.register(ZeroMatrix) +def _(expr, assumptions): + return False + +@FullRankPredicate.register(OneMatrix) +def _(expr, assumptions): + return expr.shape[0] == 1 and expr.shape[1] == 1 + +@FullRankPredicate.register_many(Inverse, Transpose) +def _(expr, assumptions): + return ask(Q.fullrank(expr.arg), assumptions) + +@FullRankPredicate.register(MatrixSlice) +def _(expr, assumptions): + if ask(Q.orthogonal(expr.parent), assumptions): + return True + + +# PositiveDefinitePredicate + +@PositiveDefinitePredicate.register(MatMul) +def _(expr, assumptions): + factor, mmul = expr.as_coeff_mmul() + if (all(ask(Q.positive_definite(arg), assumptions) + for arg in mmul.args) and factor > 0): + return True + if (len(mmul.args) >= 2 + and mmul.args[0] == mmul.args[-1].T + and ask(Q.fullrank(mmul.args[0]), assumptions)): + return ask(Q.positive_definite( + MatMul(*mmul.args[1:-1])), assumptions) + +@PositiveDefinitePredicate.register(MatPow) +def _(expr, assumptions): + # a power of a positive definite matrix is positive definite + if ask(Q.positive_definite(expr.args[0]), assumptions): + return True + +@PositiveDefinitePredicate.register(MatAdd) +def _(expr, assumptions): + if all(ask(Q.positive_definite(arg), assumptions) + for arg in expr.args): + return True + +@PositiveDefinitePredicate.register(MatrixSymbol) +def _(expr, assumptions): + if not expr.is_square: + return False + if Q.positive_definite(expr) in conjuncts(assumptions): + return True + +@PositiveDefinitePredicate.register(Identity) +def _(expr, assumptions): + return True + +@PositiveDefinitePredicate.register(ZeroMatrix) +def _(expr, assumptions): + return False + +@PositiveDefinitePredicate.register(OneMatrix) +def _(expr, assumptions): + return expr.shape[0] == 1 and expr.shape[1] == 1 + +@PositiveDefinitePredicate.register_many(Inverse, Transpose) +def _(expr, assumptions): + return ask(Q.positive_definite(expr.arg), assumptions) + +@PositiveDefinitePredicate.register(MatrixSlice) +def _(expr, assumptions): + if not expr.on_diag: + return None + else: + return ask(Q.positive_definite(expr.parent), assumptions) + + +# UpperTriangularPredicate + +@UpperTriangularPredicate.register(MatMul) +def _(expr, assumptions): + factor, matrices = expr.as_coeff_matrices() + if all(ask(Q.upper_triangular(m), assumptions) for m in matrices): + return True + +@UpperTriangularPredicate.register(MatAdd) +def _(expr, assumptions): + if all(ask(Q.upper_triangular(arg), assumptions) for arg in expr.args): + return True + +@UpperTriangularPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + non_negative = ask(~Q.negative(exp), assumptions) + if (non_negative or non_negative == False + and ask(Q.invertible(base), assumptions)): + return ask(Q.upper_triangular(base), assumptions) + return None + +@UpperTriangularPredicate.register(MatrixSymbol) +def _(expr, assumptions): + if Q.upper_triangular(expr) in conjuncts(assumptions): + return True + +@UpperTriangularPredicate.register_many(Identity, ZeroMatrix) +def _(expr, assumptions): + return True + +@UpperTriangularPredicate.register(OneMatrix) +def _(expr, assumptions): + return expr.shape[0] == 1 and expr.shape[1] == 1 + +@UpperTriangularPredicate.register(Transpose) +def _(expr, assumptions): + return ask(Q.lower_triangular(expr.arg), assumptions) + +@UpperTriangularPredicate.register(Inverse) +def _(expr, assumptions): + return ask(Q.upper_triangular(expr.arg), assumptions) + +@UpperTriangularPredicate.register(MatrixSlice) +def _(expr, assumptions): + if not expr.on_diag: + return None + else: + return ask(Q.upper_triangular(expr.parent), assumptions) + +@UpperTriangularPredicate.register(Factorization) +def _(expr, assumptions): + return _Factorization(Q.upper_triangular, expr, assumptions) + +# LowerTriangularPredicate + +@LowerTriangularPredicate.register(MatMul) +def _(expr, assumptions): + factor, matrices = expr.as_coeff_matrices() + if all(ask(Q.lower_triangular(m), assumptions) for m in matrices): + return True + +@LowerTriangularPredicate.register(MatAdd) +def _(expr, assumptions): + if all(ask(Q.lower_triangular(arg), assumptions) for arg in expr.args): + return True + +@LowerTriangularPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + non_negative = ask(~Q.negative(exp), assumptions) + if (non_negative or non_negative == False + and ask(Q.invertible(base), assumptions)): + return ask(Q.lower_triangular(base), assumptions) + return None + +@LowerTriangularPredicate.register(MatrixSymbol) +def _(expr, assumptions): + if Q.lower_triangular(expr) in conjuncts(assumptions): + return True + +@LowerTriangularPredicate.register_many(Identity, ZeroMatrix) +def _(expr, assumptions): + return True + +@LowerTriangularPredicate.register(OneMatrix) +def _(expr, assumptions): + return expr.shape[0] == 1 and expr.shape[1] == 1 + +@LowerTriangularPredicate.register(Transpose) +def _(expr, assumptions): + return ask(Q.upper_triangular(expr.arg), assumptions) + +@LowerTriangularPredicate.register(Inverse) +def _(expr, assumptions): + return ask(Q.lower_triangular(expr.arg), assumptions) + +@LowerTriangularPredicate.register(MatrixSlice) +def _(expr, assumptions): + if not expr.on_diag: + return None + else: + return ask(Q.lower_triangular(expr.parent), assumptions) + +@LowerTriangularPredicate.register(Factorization) +def _(expr, assumptions): + return _Factorization(Q.lower_triangular, expr, assumptions) + + +# DiagonalPredicate + +def _is_empty_or_1x1(expr): + return expr.shape in ((0, 0), (1, 1)) + +@DiagonalPredicate.register(MatMul) +def _(expr, assumptions): + if _is_empty_or_1x1(expr): + return True + factor, matrices = expr.as_coeff_matrices() + if all(ask(Q.diagonal(m), assumptions) for m in matrices): + return True + +@DiagonalPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + non_negative = ask(~Q.negative(exp), assumptions) + if (non_negative or non_negative == False + and ask(Q.invertible(base), assumptions)): + return ask(Q.diagonal(base), assumptions) + return None + +@DiagonalPredicate.register(MatAdd) +def _(expr, assumptions): + if all(ask(Q.diagonal(arg), assumptions) for arg in expr.args): + return True + +@DiagonalPredicate.register(MatrixSymbol) +def _(expr, assumptions): + if _is_empty_or_1x1(expr): + return True + if Q.diagonal(expr) in conjuncts(assumptions): + return True + +@DiagonalPredicate.register(OneMatrix) +def _(expr, assumptions): + return expr.shape[0] == 1 and expr.shape[1] == 1 + +@DiagonalPredicate.register_many(Inverse, Transpose) +def _(expr, assumptions): + return ask(Q.diagonal(expr.arg), assumptions) + +@DiagonalPredicate.register(MatrixSlice) +def _(expr, assumptions): + if _is_empty_or_1x1(expr): + return True + if not expr.on_diag: + return None + else: + return ask(Q.diagonal(expr.parent), assumptions) + +@DiagonalPredicate.register_many(DiagonalMatrix, DiagMatrix, Identity, ZeroMatrix) +def _(expr, assumptions): + return True + +@DiagonalPredicate.register(Factorization) +def _(expr, assumptions): + return _Factorization(Q.diagonal, expr, assumptions) + + +# IntegerElementsPredicate + +def BM_elements(predicate, expr, assumptions): + """ Block Matrix elements. """ + return all(ask(predicate(b), assumptions) for b in expr.blocks) + +def MS_elements(predicate, expr, assumptions): + """ Matrix Slice elements. """ + return ask(predicate(expr.parent), assumptions) + +def MatMul_elements(matrix_predicate, scalar_predicate, expr, assumptions): + d = sift(expr.args, lambda x: isinstance(x, MatrixExpr)) + factors, matrices = d[False], d[True] + return fuzzy_and([ + test_closed_group(Basic(*factors), assumptions, scalar_predicate), + test_closed_group(Basic(*matrices), assumptions, matrix_predicate)]) + + +@IntegerElementsPredicate.register_many(Determinant, HadamardProduct, MatAdd, + Trace, Transpose) +def _(expr, assumptions): + return test_closed_group(expr, assumptions, Q.integer_elements) + +@IntegerElementsPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + if exp.is_negative == False: + return ask(Q.integer_elements(base), assumptions) + return None + +@IntegerElementsPredicate.register_many(Identity, OneMatrix, ZeroMatrix) +def _(expr, assumptions): + return True + +@IntegerElementsPredicate.register(MatMul) +def _(expr, assumptions): + return MatMul_elements(Q.integer_elements, Q.integer, expr, assumptions) + +@IntegerElementsPredicate.register(MatrixSlice) +def _(expr, assumptions): + return MS_elements(Q.integer_elements, expr, assumptions) + +@IntegerElementsPredicate.register(BlockMatrix) +def _(expr, assumptions): + return BM_elements(Q.integer_elements, expr, assumptions) + + +# RealElementsPredicate + +@RealElementsPredicate.register_many(Determinant, Factorization, HadamardProduct, + MatAdd, Trace, Transpose) +def _(expr, assumptions): + return test_closed_group(expr, assumptions, Q.real_elements) + +@RealElementsPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + non_negative = ask(~Q.negative(exp), assumptions) + if (non_negative or non_negative == False + and ask(Q.invertible(base), assumptions)): + return ask(Q.real_elements(base), assumptions) + return None + +@RealElementsPredicate.register(MatMul) +def _(expr, assumptions): + return MatMul_elements(Q.real_elements, Q.real, expr, assumptions) + +@RealElementsPredicate.register(MatrixSlice) +def _(expr, assumptions): + return MS_elements(Q.real_elements, expr, assumptions) + +@RealElementsPredicate.register(BlockMatrix) +def _(expr, assumptions): + return BM_elements(Q.real_elements, expr, assumptions) + + +# ComplexElementsPredicate + +@ComplexElementsPredicate.register_many(Determinant, Factorization, HadamardProduct, + Inverse, MatAdd, Trace, Transpose) +def _(expr, assumptions): + return test_closed_group(expr, assumptions, Q.complex_elements) + +@ComplexElementsPredicate.register(MatPow) +def _(expr, assumptions): + # only for integer powers + base, exp = expr.args + int_exp = ask(Q.integer(exp), assumptions) + if not int_exp: + return None + non_negative = ask(~Q.negative(exp), assumptions) + if (non_negative or non_negative == False + and ask(Q.invertible(base), assumptions)): + return ask(Q.complex_elements(base), assumptions) + return None + +@ComplexElementsPredicate.register(MatMul) +def _(expr, assumptions): + return MatMul_elements(Q.complex_elements, Q.complex, expr, assumptions) + +@ComplexElementsPredicate.register(MatrixSlice) +def _(expr, assumptions): + return MS_elements(Q.complex_elements, expr, assumptions) + +@ComplexElementsPredicate.register(BlockMatrix) +def _(expr, assumptions): + return BM_elements(Q.complex_elements, expr, assumptions) + +@ComplexElementsPredicate.register(DFT) +def _(expr, assumptions): + return True diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/ntheory.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/ntheory.py new file mode 100644 index 0000000000000000000000000000000000000000..cfe63ba6467ea6863c6112c5e35bb3a78191a23e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/ntheory.py @@ -0,0 +1,279 @@ +""" +Handlers for keys related to number theory: prime, even, odd, etc. +""" + +from sympy.assumptions import Q, ask +from sympy.core import Add, Basic, Expr, Float, Mul, Pow, S +from sympy.core.numbers import (ImaginaryUnit, Infinity, Integer, NaN, + NegativeInfinity, NumberSymbol, Rational, int_valued) +from sympy.functions import Abs, im, re +from sympy.ntheory import isprime + +from sympy.multipledispatch import MDNotImplementedError + +from ..predicates.ntheory import (PrimePredicate, CompositePredicate, + EvenPredicate, OddPredicate) + + +# PrimePredicate + +def _PrimePredicate_number(expr, assumptions): + # helper method + exact = not expr.atoms(Float) + try: + i = int(expr.round()) + if (expr - i).equals(0) is False: + raise TypeError + except TypeError: + return False + if exact: + return isprime(i) + # when not exact, we won't give a True or False + # since the number represents an approximate value + +@PrimePredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_prime + if ret is None: + raise MDNotImplementedError + return ret + +@PrimePredicate.register(Basic) +def _(expr, assumptions): + if expr.is_number: + return _PrimePredicate_number(expr, assumptions) + +@PrimePredicate.register(Mul) +def _(expr, assumptions): + if expr.is_number: + return _PrimePredicate_number(expr, assumptions) + for arg in expr.args: + if not ask(Q.integer(arg), assumptions): + return None + for arg in expr.args: + if arg.is_number and arg.is_composite: + return False + +@PrimePredicate.register(Pow) +def _(expr, assumptions): + """ + Integer**Integer -> !Prime + """ + if expr.is_number: + return _PrimePredicate_number(expr, assumptions) + if ask(Q.integer(expr.exp), assumptions) and \ + ask(Q.integer(expr.base), assumptions): + prime_base = ask(Q.prime(expr.base), assumptions) + if prime_base is False: + return False + is_exp_one = ask(Q.eq(expr.exp, 1), assumptions) + if is_exp_one is False: + return False + if prime_base is True and is_exp_one is True: + return True + +@PrimePredicate.register(Integer) +def _(expr, assumptions): + return isprime(expr) + +@PrimePredicate.register_many(Rational, Infinity, NegativeInfinity, ImaginaryUnit) +def _(expr, assumptions): + return False + +@PrimePredicate.register(Float) +def _(expr, assumptions): + return _PrimePredicate_number(expr, assumptions) + +@PrimePredicate.register(NumberSymbol) +def _(expr, assumptions): + return _PrimePredicate_number(expr, assumptions) + +@PrimePredicate.register(NaN) +def _(expr, assumptions): + return None + + +# CompositePredicate + +@CompositePredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_composite + if ret is None: + raise MDNotImplementedError + return ret + +@CompositePredicate.register(Basic) +def _(expr, assumptions): + _positive = ask(Q.positive(expr), assumptions) + if _positive: + _integer = ask(Q.integer(expr), assumptions) + if _integer: + _prime = ask(Q.prime(expr), assumptions) + if _prime is None: + return + # Positive integer which is not prime is not + # necessarily composite + _is_one = ask(Q.eq(expr, 1), assumptions) + if _is_one: + return False + if _is_one is None: + return None + return not _prime + else: + return _integer + else: + return _positive + + +# EvenPredicate + +def _EvenPredicate_number(expr, assumptions): + # helper method + if isinstance(expr, (float, Float)): + if int_valued(expr): + return None + return False + try: + i = int(expr.round()) + except TypeError: + return False + if not (expr - i).equals(0): + return False + return i % 2 == 0 + +@EvenPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_even + if ret is None: + raise MDNotImplementedError + return ret + +@EvenPredicate.register(Basic) +def _(expr, assumptions): + if expr.is_number: + return _EvenPredicate_number(expr, assumptions) + +@EvenPredicate.register(Mul) +def _(expr, assumptions): + """ + Even * Integer -> Even + Even * Odd -> Even + Integer * Odd -> ? + Odd * Odd -> Odd + Even * Even -> Even + Integer * Integer -> Even if Integer + Integer = Odd + otherwise -> ? + """ + if expr.is_number: + return _EvenPredicate_number(expr, assumptions) + even, odd, irrational, acc = False, 0, False, 1 + for arg in expr.args: + # check for all integers and at least one even + if ask(Q.integer(arg), assumptions): + if ask(Q.even(arg), assumptions): + even = True + elif ask(Q.odd(arg), assumptions): + odd += 1 + elif not even and acc != 1: + if ask(Q.odd(acc + arg), assumptions): + even = True + elif ask(Q.irrational(arg), assumptions): + # one irrational makes the result False + # two makes it undefined + if irrational: + break + irrational = True + else: + break + acc = arg + else: + if irrational: + return False + if even: + return True + if odd == len(expr.args): + return False + +@EvenPredicate.register(Add) +def _(expr, assumptions): + """ + Even + Odd -> Odd + Even + Even -> Even + Odd + Odd -> Even + + """ + if expr.is_number: + return _EvenPredicate_number(expr, assumptions) + _result = True + for arg in expr.args: + if ask(Q.even(arg), assumptions): + pass + elif ask(Q.odd(arg), assumptions): + _result = not _result + else: + break + else: + return _result + +@EvenPredicate.register(Pow) +def _(expr, assumptions): + if expr.is_number: + return _EvenPredicate_number(expr, assumptions) + if ask(Q.integer(expr.exp), assumptions): + if ask(Q.positive(expr.exp), assumptions): + return ask(Q.even(expr.base), assumptions) + elif ask(~Q.negative(expr.exp) & Q.odd(expr.base), assumptions): + return False + elif expr.base is S.NegativeOne: + return False + +@EvenPredicate.register(Integer) +def _(expr, assumptions): + return not bool(expr.p & 1) + +@EvenPredicate.register_many(Rational, Infinity, NegativeInfinity, ImaginaryUnit) +def _(expr, assumptions): + return False + +@EvenPredicate.register(NumberSymbol) +def _(expr, assumptions): + return _EvenPredicate_number(expr, assumptions) + +@EvenPredicate.register(Abs) +def _(expr, assumptions): + if ask(Q.real(expr.args[0]), assumptions): + return ask(Q.even(expr.args[0]), assumptions) + +@EvenPredicate.register(re) +def _(expr, assumptions): + if ask(Q.real(expr.args[0]), assumptions): + return ask(Q.even(expr.args[0]), assumptions) + +@EvenPredicate.register(im) +def _(expr, assumptions): + if ask(Q.real(expr.args[0]), assumptions): + return True + +@EvenPredicate.register(NaN) +def _(expr, assumptions): + return None + + +# OddPredicate + +@OddPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_odd + if ret is None: + raise MDNotImplementedError + return ret + +@OddPredicate.register(Basic) +def _(expr, assumptions): + _integer = ask(Q.integer(expr), assumptions) + if _integer: + _even = ask(Q.even(expr), assumptions) + if _even is None: + return None + return not _even + return _integer diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/order.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/order.py new file mode 100644 index 0000000000000000000000000000000000000000..24a8bae7f30777f62a1bec0579d58b9875143679 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/order.py @@ -0,0 +1,440 @@ +""" +Handlers related to order relations: positive, negative, etc. +""" + +from sympy.assumptions import Q, ask +from sympy.core import Add, Basic, Expr, Mul, Pow, S +from sympy.core.logic import fuzzy_not, fuzzy_and, fuzzy_or +from sympy.core.numbers import E, ImaginaryUnit, NaN, I, pi +from sympy.functions import Abs, acos, acot, asin, atan, exp, factorial, log +from sympy.matrices import Determinant, Trace +from sympy.matrices.expressions.matexpr import MatrixElement + +from sympy.multipledispatch import MDNotImplementedError + +from ..predicates.order import (NegativePredicate, NonNegativePredicate, + NonZeroPredicate, ZeroPredicate, NonPositivePredicate, PositivePredicate, + ExtendedNegativePredicate, ExtendedNonNegativePredicate, + ExtendedNonPositivePredicate, ExtendedNonZeroPredicate, + ExtendedPositivePredicate,) + + +# NegativePredicate + +def _NegativePredicate_number(expr, assumptions): + r, i = expr.as_real_imag() + + if r == S.NaN or i == S.NaN: + return None + + # If the imaginary part can symbolically be shown to be zero then + # we just evaluate the real part; otherwise we evaluate the imaginary + # part to see if it actually evaluates to zero and if it does then + # we make the comparison between the real part and zero. + if not i: + r = r.evalf(2) + if r._prec != 1: + return r < 0 + else: + i = i.evalf(2) + if i._prec != 1: + if i != 0: + return False + r = r.evalf(2) + if r._prec != 1: + return r < 0 + +@NegativePredicate.register(Basic) +def _(expr, assumptions): + if expr.is_number: + return _NegativePredicate_number(expr, assumptions) + +@NegativePredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_negative + if ret is None: + raise MDNotImplementedError + return ret + +@NegativePredicate.register(Add) +def _(expr, assumptions): + """ + Positive + Positive -> Positive, + Negative + Negative -> Negative + """ + if expr.is_number: + return _NegativePredicate_number(expr, assumptions) + + r = ask(Q.real(expr), assumptions) + if r is not True: + return r + + nonpos = 0 + for arg in expr.args: + if ask(Q.negative(arg), assumptions) is not True: + if ask(Q.positive(arg), assumptions) is False: + nonpos += 1 + else: + break + else: + if nonpos < len(expr.args): + return True + +@NegativePredicate.register(Mul) +def _(expr, assumptions): + if expr.is_number: + return _NegativePredicate_number(expr, assumptions) + result = None + for arg in expr.args: + if result is None: + result = False + if ask(Q.negative(arg), assumptions): + result = not result + elif ask(Q.positive(arg), assumptions): + pass + else: + return + return result + +@NegativePredicate.register(Pow) +def _(expr, assumptions): + """ + Real ** Even -> NonNegative + Real ** Odd -> same_as_base + NonNegative ** Positive -> NonNegative + """ + if expr.base == E: + # Exponential is always positive: + if ask(Q.real(expr.exp), assumptions): + return False + return + + if expr.is_number: + return _NegativePredicate_number(expr, assumptions) + if ask(Q.real(expr.base), assumptions): + if ask(Q.positive(expr.base), assumptions): + if ask(Q.real(expr.exp), assumptions): + return False + if ask(Q.even(expr.exp), assumptions): + return False + if ask(Q.odd(expr.exp), assumptions): + return ask(Q.negative(expr.base), assumptions) + +@NegativePredicate.register_many(Abs, ImaginaryUnit) +def _(expr, assumptions): + return False + +@NegativePredicate.register(exp) +def _(expr, assumptions): + if ask(Q.real(expr.exp), assumptions): + return False + raise MDNotImplementedError + + +# NonNegativePredicate + +@NonNegativePredicate.register(Basic) +def _(expr, assumptions): + if expr.is_number: + notnegative = fuzzy_not(_NegativePredicate_number(expr, assumptions)) + if notnegative: + return ask(Q.real(expr), assumptions) + else: + return notnegative + +@NonNegativePredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_nonnegative + if ret is None: + raise MDNotImplementedError + return ret + + +# NonZeroPredicate + +@NonZeroPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_nonzero + if ret is None: + raise MDNotImplementedError + return ret + +@NonZeroPredicate.register(Basic) +def _(expr, assumptions): + if ask(Q.real(expr)) is False: + return False + if expr.is_number: + # if there are no symbols just evalf + i = expr.evalf(2) + def nonz(i): + if i._prec != 1: + return i != 0 + return fuzzy_or(nonz(i) for i in i.as_real_imag()) + +@NonZeroPredicate.register(Add) +def _(expr, assumptions): + if all(ask(Q.positive(x), assumptions) for x in expr.args) \ + or all(ask(Q.negative(x), assumptions) for x in expr.args): + return True + +@NonZeroPredicate.register(Mul) +def _(expr, assumptions): + for arg in expr.args: + result = ask(Q.nonzero(arg), assumptions) + if result: + continue + return result + return True + +@NonZeroPredicate.register(Pow) +def _(expr, assumptions): + return ask(Q.nonzero(expr.base), assumptions) + +@NonZeroPredicate.register(Abs) +def _(expr, assumptions): + return ask(Q.nonzero(expr.args[0]), assumptions) + +@NonZeroPredicate.register(NaN) +def _(expr, assumptions): + return None + + +# ZeroPredicate + +@ZeroPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_zero + if ret is None: + raise MDNotImplementedError + return ret + +@ZeroPredicate.register(Basic) +def _(expr, assumptions): + return fuzzy_and([fuzzy_not(ask(Q.nonzero(expr), assumptions)), + ask(Q.real(expr), assumptions)]) + +@ZeroPredicate.register(Mul) +def _(expr, assumptions): + # TODO: This should be deducible from the nonzero handler + return fuzzy_or(ask(Q.zero(arg), assumptions) for arg in expr.args) + + +# NonPositivePredicate + +@NonPositivePredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_nonpositive + if ret is None: + raise MDNotImplementedError + return ret + +@NonPositivePredicate.register(Basic) +def _(expr, assumptions): + if expr.is_number: + notpositive = fuzzy_not(_PositivePredicate_number(expr, assumptions)) + if notpositive: + return ask(Q.real(expr), assumptions) + else: + return notpositive + + +# PositivePredicate + +def _PositivePredicate_number(expr, assumptions): + r, i = expr.as_real_imag() + # If the imaginary part can symbolically be shown to be zero then + # we just evaluate the real part; otherwise we evaluate the imaginary + # part to see if it actually evaluates to zero and if it does then + # we make the comparison between the real part and zero. + if not i: + r = r.evalf(2) + if r._prec != 1: + return r > 0 + else: + i = i.evalf(2) + if i._prec != 1: + if i != 0: + return False + r = r.evalf(2) + if r._prec != 1: + return r > 0 + +@PositivePredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_positive + if ret is None: + raise MDNotImplementedError + return ret + +@PositivePredicate.register(Basic) +def _(expr, assumptions): + if expr.is_number: + return _PositivePredicate_number(expr, assumptions) + +@PositivePredicate.register(Mul) +def _(expr, assumptions): + if expr.is_number: + return _PositivePredicate_number(expr, assumptions) + result = True + for arg in expr.args: + if ask(Q.positive(arg), assumptions): + continue + elif ask(Q.negative(arg), assumptions): + result = result ^ True + else: + return + return result + +@PositivePredicate.register(Add) +def _(expr, assumptions): + if expr.is_number: + return _PositivePredicate_number(expr, assumptions) + + r = ask(Q.real(expr), assumptions) + if r is not True: + return r + + nonneg = 0 + for arg in expr.args: + if ask(Q.positive(arg), assumptions) is not True: + if ask(Q.negative(arg), assumptions) is False: + nonneg += 1 + else: + break + else: + if nonneg < len(expr.args): + return True + +@PositivePredicate.register(Pow) +def _(expr, assumptions): + if expr.base == E: + if ask(Q.real(expr.exp), assumptions): + return True + if ask(Q.imaginary(expr.exp), assumptions): + return ask(Q.even(expr.exp/(I*pi)), assumptions) + return + + if expr.is_number: + return _PositivePredicate_number(expr, assumptions) + if ask(Q.positive(expr.base), assumptions): + if ask(Q.real(expr.exp), assumptions): + return True + if ask(Q.negative(expr.base), assumptions): + if ask(Q.even(expr.exp), assumptions): + return True + if ask(Q.odd(expr.exp), assumptions): + return False + +@PositivePredicate.register(exp) +def _(expr, assumptions): + if ask(Q.real(expr.exp), assumptions): + return True + if ask(Q.imaginary(expr.exp), assumptions): + return ask(Q.even(expr.exp/(I*pi)), assumptions) + +@PositivePredicate.register(log) +def _(expr, assumptions): + r = ask(Q.real(expr.args[0]), assumptions) + if r is not True: + return r + if ask(Q.positive(expr.args[0] - 1), assumptions): + return True + if ask(Q.negative(expr.args[0] - 1), assumptions): + return False + +@PositivePredicate.register(factorial) +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.integer(x) & Q.positive(x), assumptions): + return True + +@PositivePredicate.register(ImaginaryUnit) +def _(expr, assumptions): + return False + +@PositivePredicate.register(Abs) +def _(expr, assumptions): + return ask(Q.nonzero(expr), assumptions) + +@PositivePredicate.register(Trace) +def _(expr, assumptions): + if ask(Q.positive_definite(expr.arg), assumptions): + return True + +@PositivePredicate.register(Determinant) +def _(expr, assumptions): + if ask(Q.positive_definite(expr.arg), assumptions): + return True + +@PositivePredicate.register(MatrixElement) +def _(expr, assumptions): + if (expr.i == expr.j + and ask(Q.positive_definite(expr.parent), assumptions)): + return True + +@PositivePredicate.register(atan) +def _(expr, assumptions): + return ask(Q.positive(expr.args[0]), assumptions) + +@PositivePredicate.register(asin) +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.positive(x) & Q.nonpositive(x - 1), assumptions): + return True + if ask(Q.negative(x) & Q.nonnegative(x + 1), assumptions): + return False + +@PositivePredicate.register(acos) +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.nonpositive(x - 1) & Q.nonnegative(x + 1), assumptions): + return True + +@PositivePredicate.register(acot) +def _(expr, assumptions): + return ask(Q.real(expr.args[0]), assumptions) + +@PositivePredicate.register(NaN) +def _(expr, assumptions): + return None + + +# ExtendedNegativePredicate + +@ExtendedNegativePredicate.register(object) +def _(expr, assumptions): + return ask(Q.negative(expr) | Q.negative_infinite(expr), assumptions) + + +# ExtendedPositivePredicate + +@ExtendedPositivePredicate.register(object) +def _(expr, assumptions): + return ask(Q.positive(expr) | Q.positive_infinite(expr), assumptions) + + +# ExtendedNonZeroPredicate + +@ExtendedNonZeroPredicate.register(object) +def _(expr, assumptions): + return ask( + Q.negative_infinite(expr) | Q.negative(expr) | Q.positive(expr) | Q.positive_infinite(expr), + assumptions) + + +# ExtendedNonPositivePredicate + +@ExtendedNonPositivePredicate.register(object) +def _(expr, assumptions): + return ask( + Q.negative_infinite(expr) | Q.negative(expr) | Q.zero(expr), + assumptions) + + +# ExtendedNonNegativePredicate + +@ExtendedNonNegativePredicate.register(object) +def _(expr, assumptions): + return ask( + Q.zero(expr) | Q.positive(expr) | Q.positive_infinite(expr), + assumptions) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/handlers/sets.py b/phivenv/Lib/site-packages/sympy/assumptions/handlers/sets.py new file mode 100644 index 0000000000000000000000000000000000000000..7a13ed9bf99c5b0ffc4f32fd55cb60c2c15ab836 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/handlers/sets.py @@ -0,0 +1,816 @@ +""" +Handlers for predicates related to set membership: integer, rational, etc. +""" + +from sympy.assumptions import Q, ask +from sympy.core import Add, Basic, Expr, Mul, Pow, S +from sympy.core.numbers import (AlgebraicNumber, ComplexInfinity, Exp1, Float, + GoldenRatio, ImaginaryUnit, Infinity, Integer, NaN, NegativeInfinity, + Number, NumberSymbol, Pi, pi, Rational, TribonacciConstant, E) +from sympy.core.logic import fuzzy_bool +from sympy.functions import (Abs, acos, acot, asin, atan, cos, cot, exp, im, + log, re, sin, tan) +from sympy.core.numbers import I +from sympy.core.relational import Eq +from sympy.functions.elementary.complexes import conjugate +from sympy.matrices import Determinant, MatrixBase, Trace +from sympy.matrices.expressions.matexpr import MatrixElement + +from sympy.multipledispatch import MDNotImplementedError + +from .common import test_closed_group, ask_all, ask_any +from ..predicates.sets import (IntegerPredicate, RationalPredicate, + IrrationalPredicate, RealPredicate, ExtendedRealPredicate, + HermitianPredicate, ComplexPredicate, ImaginaryPredicate, + AntihermitianPredicate, AlgebraicPredicate) + + +# IntegerPredicate + +def _IntegerPredicate_number(expr, assumptions): + # helper function + try: + i = int(expr.round()) + if not (expr - i).equals(0): + raise TypeError + return True + except TypeError: + return False + +@IntegerPredicate.register_many(int, Integer) # type:ignore +def _(expr, assumptions): + return True + +@IntegerPredicate.register_many(Exp1, GoldenRatio, ImaginaryUnit, Infinity, + NegativeInfinity, Pi, Rational, TribonacciConstant) +def _(expr, assumptions): + return False + +@IntegerPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_integer + if ret is None: + raise MDNotImplementedError + return ret + +@IntegerPredicate.register(Add) +def _(expr, assumptions): + """ + * Integer + Integer -> Integer + * Integer + !Integer -> !Integer + * !Integer + !Integer -> ? + """ + if expr.is_number: + return _IntegerPredicate_number(expr, assumptions) + return test_closed_group(expr, assumptions, Q.integer) + +@IntegerPredicate.register(Pow) +def _(expr,assumptions): + if expr.is_number: + return _IntegerPredicate_number(expr, assumptions) + if ask_all(~Q.zero(expr.base), Q.finite(expr.base), Q.zero(expr.exp), assumptions=assumptions): + return True + if ask_all(Q.integer(expr.base), Q.integer(expr.exp), assumptions=assumptions): + if ask_any(Q.positive(expr.exp), Q.nonnegative(expr.exp) & ~Q.zero(expr.base), Q.zero(expr.base-1), Q.zero(expr.base+1), assumptions=assumptions): + return True + +@IntegerPredicate.register(Mul) +def _(expr, assumptions): + """ + * Integer*Integer -> Integer + * Integer*Irrational -> !Integer + * Odd/Even -> !Integer + * Integer*Rational -> ? + """ + if expr.is_number: + return _IntegerPredicate_number(expr, assumptions) + _output = True + for arg in expr.args: + if not ask(Q.integer(arg), assumptions): + if arg.is_Rational: + if arg.q == 2: + return ask(Q.even(2*expr), assumptions) + if ~(arg.q & 1): + return None + elif ask(Q.irrational(arg), assumptions): + if _output: + _output = False + else: + return + else: + return + + return _output + +@IntegerPredicate.register(Abs) +def _(expr, assumptions): + if ask(Q.integer(expr.args[0]), assumptions): + return True + +@IntegerPredicate.register_many(Determinant, MatrixElement, Trace) +def _(expr, assumptions): + return ask(Q.integer_elements(expr.args[0]), assumptions) + + +# RationalPredicate + +@RationalPredicate.register(Rational) +def _(expr, assumptions): + return True + +@RationalPredicate.register(Float) +def _(expr, assumptions): + return None + +@RationalPredicate.register_many(Exp1, GoldenRatio, ImaginaryUnit, Infinity, + NegativeInfinity, Pi, TribonacciConstant) +def _(expr, assumptions): + return False + +@RationalPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_rational + if ret is None: + raise MDNotImplementedError + return ret + +@RationalPredicate.register_many(Add, Mul) +def _(expr, assumptions): + """ + * Rational + Rational -> Rational + * Rational + !Rational -> !Rational + * !Rational + !Rational -> ? + """ + if expr.is_number: + if expr.as_real_imag()[1]: + return False + return test_closed_group(expr, assumptions, Q.rational) + +@RationalPredicate.register(Pow) +def _(expr, assumptions): + """ + * Rational ** Integer -> Rational + * Irrational ** Rational -> Irrational + * Rational ** Irrational -> ? + """ + if expr.base == E: + x = expr.exp + if ask(Q.rational(x), assumptions): + return ask(Q.zero(x), assumptions) + return + + is_exp_integer = ask(Q.integer(expr.exp), assumptions) + if is_exp_integer: + is_base_rational = ask(Q.rational(expr.base),assumptions) + if is_base_rational: + is_base_zero = ask(Q.zero(expr.base),assumptions) + if is_base_zero is False: + return True + if is_base_zero and ask(Q.positive(expr.exp)): + return True + if ask(Q.algebraic(expr.base),assumptions) is False: + return ask(Q.zero(expr.exp), assumptions) + if ask(Q.irrational(expr.base),assumptions) and ask(Q.eq(expr.exp,-1)): + return False + return + elif ask(Q.rational(expr.exp), assumptions): + if ask(Q.prime(expr.base), assumptions) and is_exp_integer is False: + return False + if ask(Q.zero(expr.base)) and ask(Q.positive(expr.exp)): + return True + if ask(Q.eq(expr.base,1)): + return True + +@RationalPredicate.register_many(asin, atan, cos, sin, tan) +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.rational(x), assumptions): + return ask(~Q.nonzero(x), assumptions) + +@RationalPredicate.register(exp) +def _(expr, assumptions): + x = expr.exp + if ask(Q.rational(x), assumptions): + return ask(~Q.nonzero(x), assumptions) + +@RationalPredicate.register_many(acot, cot) +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.rational(x), assumptions): + return False + +@RationalPredicate.register_many(acos, log) +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.rational(x), assumptions): + return ask(~Q.nonzero(x - 1), assumptions) + + +# IrrationalPredicate + +@IrrationalPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_irrational + if ret is None: + raise MDNotImplementedError + return ret + +@IrrationalPredicate.register(Basic) +def _(expr, assumptions): + _real = ask(Q.real(expr), assumptions) + if _real: + _rational = ask(Q.rational(expr), assumptions) + if _rational is None: + return None + return not _rational + else: + return _real + + +# RealPredicate + +def _RealPredicate_number(expr, assumptions): + # let as_real_imag() work first since the expression may + # be simpler to evaluate + i = expr.as_real_imag()[1].evalf(2) + if i._prec != 1: + return not i + # allow None to be returned if we couldn't show for sure + # that i was 0 + +@RealPredicate.register_many(Abs, Exp1, Float, GoldenRatio, im, Pi, Rational, + re, TribonacciConstant) +def _(expr, assumptions): + return True + +@RealPredicate.register_many(ImaginaryUnit, Infinity, NegativeInfinity) +def _(expr, assumptions): + return False + +@RealPredicate.register(Expr) +def _(expr, assumptions): + ret = expr.is_real + if ret is None: + raise MDNotImplementedError + return ret + +@RealPredicate.register(Add) +def _(expr, assumptions): + """ + * Real + Real -> Real + * Real + (Complex & !Real) -> !Real + """ + if expr.is_number: + return _RealPredicate_number(expr, assumptions) + return test_closed_group(expr, assumptions, Q.real) + +@RealPredicate.register(Mul) +def _(expr, assumptions): + """ + * Real*Real -> Real + * Real*Imaginary -> !Real + * Imaginary*Imaginary -> Real + """ + if expr.is_number: + return _RealPredicate_number(expr, assumptions) + result = True + for arg in expr.args: + if ask(Q.real(arg), assumptions): + pass + elif ask(Q.imaginary(arg), assumptions): + result = result ^ True + else: + break + else: + return result + +@RealPredicate.register(Pow) +def _(expr, assumptions): + """ + * Real**Integer -> Real + * Positive**Real -> Real + * Negative**Real -> ? + * Real**(Integer/Even) -> Real if base is nonnegative + * Real**(Integer/Odd) -> Real + * Imaginary**(Integer/Even) -> Real + * Imaginary**(Integer/Odd) -> not Real + * Imaginary**Real -> ? since Real could be 0 (giving real) + or 1 (giving imaginary) + * b**Imaginary -> Real if log(b) is imaginary and b != 0 + and exponent != integer multiple of + I*pi/log(b) + * Real**Real -> ? e.g. sqrt(-1) is imaginary and + sqrt(2) is not + """ + if expr.is_number: + return _RealPredicate_number(expr, assumptions) + + if expr.base == E: + return ask( + Q.integer(expr.exp/I/pi) | Q.real(expr.exp), assumptions + ) + + if expr.base.func == exp or (expr.base.is_Pow and expr.base.base == E): + if ask(Q.imaginary(expr.base.exp), assumptions): + if ask(Q.imaginary(expr.exp), assumptions): + return True + # If the i = (exp's arg)/(I*pi) is an integer or half-integer + # multiple of I*pi then 2*i will be an integer. In addition, + # exp(i*I*pi) = (-1)**i so the overall realness of the expr + # can be determined by replacing exp(i*I*pi) with (-1)**i. + i = expr.base.exp/I/pi + if ask(Q.integer(2*i), assumptions): + return ask(Q.real((S.NegativeOne**i)**expr.exp), assumptions) + return + + if ask(Q.imaginary(expr.base), assumptions): + if ask(Q.integer(expr.exp), assumptions): + odd = ask(Q.odd(expr.exp), assumptions) + if odd is not None: + return not odd + return + + if ask(Q.imaginary(expr.exp), assumptions): + imlog = ask(Q.imaginary(log(expr.base)), assumptions) + if imlog is not None: + # I**i -> real, log(I) is imag; + # (2*I)**i -> complex, log(2*I) is not imag + return imlog + + if ask(Q.real(expr.base), assumptions): + if ask(Q.real(expr.exp), assumptions): + if ask(Q.zero(expr.base), assumptions) is not False: + if ask(Q.positive(expr.exp), assumptions): + return True + return + if expr.exp.is_Rational and \ + ask(Q.even(expr.exp.q), assumptions): + return ask(Q.positive(expr.base), assumptions) + elif ask(Q.integer(expr.exp), assumptions): + return True + elif ask(Q.positive(expr.base), assumptions): + return True + +@RealPredicate.register_many(cos, sin) +def _(expr, assumptions): + if ask(Q.real(expr.args[0]), assumptions): + return True + +@RealPredicate.register(exp) +def _(expr, assumptions): + return ask( + Q.integer(expr.exp/I/pi) | Q.real(expr.exp), assumptions + ) + +@RealPredicate.register(log) +def _(expr, assumptions): + return ask(Q.positive(expr.args[0]), assumptions) + +@RealPredicate.register_many(Determinant, MatrixElement, Trace) +def _(expr, assumptions): + return ask(Q.real_elements(expr.args[0]), assumptions) + + +# ExtendedRealPredicate + +@ExtendedRealPredicate.register(object) +def _(expr, assumptions): + return ask(Q.negative_infinite(expr) + | Q.negative(expr) + | Q.zero(expr) + | Q.positive(expr) + | Q.positive_infinite(expr), + assumptions) + +@ExtendedRealPredicate.register_many(Infinity, NegativeInfinity) +def _(expr, assumptions): + return True + +@ExtendedRealPredicate.register_many(Add, Mul, Pow) # type:ignore +def _(expr, assumptions): + return test_closed_group(expr, assumptions, Q.extended_real) + + +# HermitianPredicate + +@HermitianPredicate.register(object) # type:ignore +def _(expr, assumptions): + if isinstance(expr, MatrixBase): + return None + return ask(Q.real(expr), assumptions) + +@HermitianPredicate.register(Add) # type:ignore +def _(expr, assumptions): + """ + * Hermitian + Hermitian -> Hermitian + * Hermitian + !Hermitian -> !Hermitian + """ + if expr.is_number: + raise MDNotImplementedError + return test_closed_group(expr, assumptions, Q.hermitian) + +@HermitianPredicate.register(Mul) # type:ignore +def _(expr, assumptions): + """ + As long as there is at most only one noncommutative term: + + * Hermitian*Hermitian -> Hermitian + * Hermitian*Antihermitian -> !Hermitian + * Antihermitian*Antihermitian -> Hermitian + """ + if expr.is_number: + raise MDNotImplementedError + nccount = 0 + result = True + for arg in expr.args: + if ask(Q.antihermitian(arg), assumptions): + result = result ^ True + elif not ask(Q.hermitian(arg), assumptions): + break + if ask(~Q.commutative(arg), assumptions): + nccount += 1 + if nccount > 1: + break + else: + return result + +@HermitianPredicate.register(Pow) # type:ignore +def _(expr, assumptions): + """ + * Hermitian**Integer -> Hermitian + """ + if expr.is_number: + raise MDNotImplementedError + if expr.base == E: + if ask(Q.hermitian(expr.exp), assumptions): + return True + raise MDNotImplementedError + if ask(Q.hermitian(expr.base), assumptions): + if ask(Q.integer(expr.exp), assumptions): + return True + raise MDNotImplementedError + +@HermitianPredicate.register_many(cos, sin) # type:ignore +def _(expr, assumptions): + if ask(Q.hermitian(expr.args[0]), assumptions): + return True + raise MDNotImplementedError + +@HermitianPredicate.register(exp) # type:ignore +def _(expr, assumptions): + if ask(Q.hermitian(expr.exp), assumptions): + return True + raise MDNotImplementedError + +@HermitianPredicate.register(MatrixBase) # type:ignore +def _(mat, assumptions): + rows, cols = mat.shape + ret_val = True + for i in range(rows): + for j in range(i, cols): + cond = fuzzy_bool(Eq(mat[i, j], conjugate(mat[j, i]))) + if cond is None: + ret_val = None + if cond == False: + return False + if ret_val is None: + raise MDNotImplementedError + return ret_val + + +# ComplexPredicate + +@ComplexPredicate.register_many(Abs, cos, exp, im, ImaginaryUnit, log, Number, # type:ignore + NumberSymbol, re, sin) +def _(expr, assumptions): + return True + +@ComplexPredicate.register_many(Infinity, NegativeInfinity) # type:ignore +def _(expr, assumptions): + return False + +@ComplexPredicate.register(Expr) # type:ignore +def _(expr, assumptions): + ret = expr.is_complex + if ret is None: + raise MDNotImplementedError + return ret + +@ComplexPredicate.register_many(Add, Mul) # type:ignore +def _(expr, assumptions): + return test_closed_group(expr, assumptions, Q.complex) + +@ComplexPredicate.register(Pow) # type:ignore +def _(expr, assumptions): + if expr.base == E: + return True + return test_closed_group(expr, assumptions, Q.complex) + +@ComplexPredicate.register_many(Determinant, MatrixElement, Trace) # type:ignore +def _(expr, assumptions): + return ask(Q.complex_elements(expr.args[0]), assumptions) + +@ComplexPredicate.register(NaN) # type:ignore +def _(expr, assumptions): + return None + + +# ImaginaryPredicate + +def _Imaginary_number(expr, assumptions): + # let as_real_imag() work first since the expression may + # be simpler to evaluate + r = expr.as_real_imag()[0].evalf(2) + if r._prec != 1: + return not r + # allow None to be returned if we couldn't show for sure + # that r was 0 + +@ImaginaryPredicate.register(ImaginaryUnit) # type:ignore +def _(expr, assumptions): + return True + +@ImaginaryPredicate.register(Expr) # type:ignore +def _(expr, assumptions): + ret = expr.is_imaginary + if ret is None: + raise MDNotImplementedError + return ret + +@ImaginaryPredicate.register(Add) # type:ignore +def _(expr, assumptions): + """ + * Imaginary + Imaginary -> Imaginary + * Imaginary + Complex -> ? + * Imaginary + Real -> !Imaginary + """ + if expr.is_number: + return _Imaginary_number(expr, assumptions) + + reals = 0 + for arg in expr.args: + if ask(Q.imaginary(arg), assumptions): + pass + elif ask(Q.real(arg), assumptions): + reals += 1 + else: + break + else: + if reals == 0: + return True + if reals in (1, len(expr.args)): + # two reals could sum 0 thus giving an imaginary + return False + +@ImaginaryPredicate.register(Mul) # type:ignore +def _(expr, assumptions): + """ + * Real*Imaginary -> Imaginary + * Imaginary*Imaginary -> Real + """ + if expr.is_number: + return _Imaginary_number(expr, assumptions) + result = False + reals = 0 + for arg in expr.args: + if ask(Q.imaginary(arg), assumptions): + result = result ^ True + elif not ask(Q.real(arg), assumptions): + break + else: + if reals == len(expr.args): + return False + return result + +@ImaginaryPredicate.register(Pow) # type:ignore +def _(expr, assumptions): + """ + * Imaginary**Odd -> Imaginary + * Imaginary**Even -> Real + * b**Imaginary -> !Imaginary if exponent is an integer + multiple of I*pi/log(b) + * Imaginary**Real -> ? + * Positive**Real -> Real + * Negative**Integer -> Real + * Negative**(Integer/2) -> Imaginary + * Negative**Real -> not Imaginary if exponent is not Rational + """ + if expr.is_number: + return _Imaginary_number(expr, assumptions) + + if expr.base == E: + a = expr.exp/I/pi + return ask(Q.integer(2*a) & ~Q.integer(a), assumptions) + + if expr.base.func == exp or (expr.base.is_Pow and expr.base.base == E): + if ask(Q.imaginary(expr.base.exp), assumptions): + if ask(Q.imaginary(expr.exp), assumptions): + return False + i = expr.base.exp/I/pi + if ask(Q.integer(2*i), assumptions): + return ask(Q.imaginary((S.NegativeOne**i)**expr.exp), assumptions) + + if ask(Q.imaginary(expr.base), assumptions): + if ask(Q.integer(expr.exp), assumptions): + odd = ask(Q.odd(expr.exp), assumptions) + if odd is not None: + return odd + return + + if ask(Q.imaginary(expr.exp), assumptions): + imlog = ask(Q.imaginary(log(expr.base)), assumptions) + if imlog is not None: + # I**i -> real; (2*I)**i -> complex ==> not imaginary + return False + + if ask(Q.real(expr.base) & Q.real(expr.exp), assumptions): + if ask(Q.positive(expr.base), assumptions): + return False + else: + rat = ask(Q.rational(expr.exp), assumptions) + if not rat: + return rat + if ask(Q.integer(expr.exp), assumptions): + return False + else: + half = ask(Q.integer(2*expr.exp), assumptions) + if half: + return ask(Q.negative(expr.base), assumptions) + return half + +@ImaginaryPredicate.register(log) # type:ignore +def _(expr, assumptions): + if ask(Q.real(expr.args[0]), assumptions): + if ask(Q.positive(expr.args[0]), assumptions): + return False + return + # XXX it should be enough to do + # return ask(Q.nonpositive(expr.args[0]), assumptions) + # but ask(Q.nonpositive(exp(x)), Q.imaginary(x)) -> None; + # it should return True since exp(x) will be either 0 or complex + if expr.args[0].func == exp or (expr.args[0].is_Pow and expr.args[0].base == E): + if expr.args[0].exp in [I, -I]: + return True + im = ask(Q.imaginary(expr.args[0]), assumptions) + if im is False: + return False + +@ImaginaryPredicate.register(exp) # type:ignore +def _(expr, assumptions): + a = expr.exp/I/pi + return ask(Q.integer(2*a) & ~Q.integer(a), assumptions) + +@ImaginaryPredicate.register_many(Number, NumberSymbol) # type:ignore +def _(expr, assumptions): + return not (expr.as_real_imag()[1] == 0) + +@ImaginaryPredicate.register(NaN) # type:ignore +def _(expr, assumptions): + return None + + +# AntihermitianPredicate + +@AntihermitianPredicate.register(object) # type:ignore +def _(expr, assumptions): + if isinstance(expr, MatrixBase): + return None + if ask(Q.zero(expr), assumptions): + return True + return ask(Q.imaginary(expr), assumptions) + +@AntihermitianPredicate.register(Add) # type:ignore +def _(expr, assumptions): + """ + * Antihermitian + Antihermitian -> Antihermitian + * Antihermitian + !Antihermitian -> !Antihermitian + """ + if expr.is_number: + raise MDNotImplementedError + return test_closed_group(expr, assumptions, Q.antihermitian) + +@AntihermitianPredicate.register(Mul) # type:ignore +def _(expr, assumptions): + """ + As long as there is at most only one noncommutative term: + + * Hermitian*Hermitian -> !Antihermitian + * Hermitian*Antihermitian -> Antihermitian + * Antihermitian*Antihermitian -> !Antihermitian + """ + if expr.is_number: + raise MDNotImplementedError + nccount = 0 + result = False + for arg in expr.args: + if ask(Q.antihermitian(arg), assumptions): + result = result ^ True + elif not ask(Q.hermitian(arg), assumptions): + break + if ask(~Q.commutative(arg), assumptions): + nccount += 1 + if nccount > 1: + break + else: + return result + +@AntihermitianPredicate.register(Pow) # type:ignore +def _(expr, assumptions): + """ + * Hermitian**Integer -> !Antihermitian + * Antihermitian**Even -> !Antihermitian + * Antihermitian**Odd -> Antihermitian + """ + if expr.is_number: + raise MDNotImplementedError + if ask(Q.hermitian(expr.base), assumptions): + if ask(Q.integer(expr.exp), assumptions): + return False + elif ask(Q.antihermitian(expr.base), assumptions): + if ask(Q.even(expr.exp), assumptions): + return False + elif ask(Q.odd(expr.exp), assumptions): + return True + raise MDNotImplementedError + +@AntihermitianPredicate.register(MatrixBase) # type:ignore +def _(mat, assumptions): + rows, cols = mat.shape + ret_val = True + for i in range(rows): + for j in range(i, cols): + cond = fuzzy_bool(Eq(mat[i, j], -conjugate(mat[j, i]))) + if cond is None: + ret_val = None + if cond == False: + return False + if ret_val is None: + raise MDNotImplementedError + return ret_val + + +# AlgebraicPredicate + +@AlgebraicPredicate.register_many(AlgebraicNumber, Float, GoldenRatio, # type:ignore + ImaginaryUnit, TribonacciConstant) +def _(expr, assumptions): + return True + +@AlgebraicPredicate.register_many(ComplexInfinity, Exp1, Infinity, # type:ignore + NegativeInfinity, Pi) +def _(expr, assumptions): + return False + +@AlgebraicPredicate.register_many(Add, Mul) # type:ignore +def _(expr, assumptions): + return test_closed_group(expr, assumptions, Q.algebraic) + +@AlgebraicPredicate.register(Pow) # type:ignore +def _(expr, assumptions): + if expr.base == E: + if ask(Q.algebraic(expr.exp), assumptions): + return ask(~Q.nonzero(expr.exp), assumptions) + return + if expr.base == pi: + if ask(Q.integer(expr.exp), assumptions) and ask(Q.positive(expr.exp), assumptions): + return False + return + exp_rational = ask(Q.rational(expr.exp), assumptions) + base_algebraic = ask(Q.algebraic(expr.base), assumptions) + exp_algebraic = ask(Q.algebraic(expr.exp),assumptions) + if base_algebraic and exp_algebraic: + if exp_rational: + return True + # Check based on the Gelfond-Schneider theorem: + # If the base is algebraic and not equal to 0 or 1, and the exponent + # is irrational,then the result is transcendental. + if ask(Q.ne(expr.base,0) & Q.ne(expr.base,1)) and exp_rational is False: + return False + +@AlgebraicPredicate.register(Rational) # type:ignore +def _(expr, assumptions): + return expr.q != 0 + +@AlgebraicPredicate.register_many(asin, atan, cos, sin, tan) # type:ignore +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.algebraic(x), assumptions): + return ask(~Q.nonzero(x), assumptions) + +@AlgebraicPredicate.register(exp) # type:ignore +def _(expr, assumptions): + x = expr.exp + if ask(Q.algebraic(x), assumptions): + return ask(~Q.nonzero(x), assumptions) + +@AlgebraicPredicate.register_many(acot, cot) # type:ignore +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.algebraic(x), assumptions): + return False + +@AlgebraicPredicate.register_many(acos, log) # type:ignore +def _(expr, assumptions): + x = expr.args[0] + if ask(Q.algebraic(x), assumptions): + return ask(~Q.nonzero(x - 1), assumptions) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/lra_satask.py b/phivenv/Lib/site-packages/sympy/assumptions/lra_satask.py new file mode 100644 index 0000000000000000000000000000000000000000..53afe3e5abe99109ec01a47f19f1a8a4c99c5628 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/lra_satask.py @@ -0,0 +1,286 @@ +from sympy.assumptions.assume import global_assumptions +from sympy.assumptions.cnf import CNF, EncodedCNF +from sympy.assumptions.ask import Q +from sympy.logic.inference import satisfiable +from sympy.logic.algorithms.lra_theory import UnhandledInput, ALLOWED_PRED +from sympy.matrices.kind import MatrixKind +from sympy.core.kind import NumberKind +from sympy.assumptions.assume import AppliedPredicate +from sympy.core.mul import Mul +from sympy.core.singleton import S + + +def lra_satask(proposition, assumptions=True, context=global_assumptions): + """ + Function to evaluate the proposition with assumptions using SAT algorithm + in conjunction with an Linear Real Arithmetic theory solver. + + Used to handle inequalities. Should eventually be depreciated and combined + into satask, but infinity handling and other things need to be implemented + before that can happen. + """ + props = CNF.from_prop(proposition) + _props = CNF.from_prop(~proposition) + + cnf = CNF.from_prop(assumptions) + assumptions = EncodedCNF() + assumptions.from_cnf(cnf) + + context_cnf = CNF() + if context: + context_cnf = context_cnf.extend(context) + + assumptions.add_from_cnf(context_cnf) + + return check_satisfiability(props, _props, assumptions) + +# Some predicates such as Q.prime can't be handled by lra_satask. +# For example, (x > 0) & (x < 1) & Q.prime(x) is unsat but lra_satask would think it was sat. +# WHITE_LIST is a list of predicates that can always be handled. +WHITE_LIST = ALLOWED_PRED | {Q.positive, Q.negative, Q.zero, Q.nonzero, Q.nonpositive, Q.nonnegative, + Q.extended_positive, Q.extended_negative, Q.extended_nonpositive, + Q.extended_negative, Q.extended_nonzero, Q.negative_infinite, + Q.positive_infinite} + + +def check_satisfiability(prop, _prop, factbase): + sat_true = factbase.copy() + sat_false = factbase.copy() + sat_true.add_from_cnf(prop) + sat_false.add_from_cnf(_prop) + + all_pred, all_exprs = get_all_pred_and_expr_from_enc_cnf(sat_true) + + for pred in all_pred: + if pred.function not in WHITE_LIST and pred.function != Q.ne: + raise UnhandledInput(f"LRASolver: {pred} is an unhandled predicate") + for expr in all_exprs: + if expr.kind == MatrixKind(NumberKind): + raise UnhandledInput(f"LRASolver: {expr} is of MatrixKind") + if expr == S.NaN: + raise UnhandledInput("LRASolver: nan") + + # convert old assumptions into predicates and add them to sat_true and sat_false + # also check for unhandled predicates + for assm in extract_pred_from_old_assum(all_exprs): + n = len(sat_true.encoding) + if assm not in sat_true.encoding: + sat_true.encoding[assm] = n+1 + sat_true.data.append([sat_true.encoding[assm]]) + + n = len(sat_false.encoding) + if assm not in sat_false.encoding: + sat_false.encoding[assm] = n+1 + sat_false.data.append([sat_false.encoding[assm]]) + + + sat_true = _preprocess(sat_true) + sat_false = _preprocess(sat_false) + + can_be_true = satisfiable(sat_true, use_lra_theory=True) is not False + can_be_false = satisfiable(sat_false, use_lra_theory=True) is not False + + if can_be_true and can_be_false: + return None + + if can_be_true and not can_be_false: + return True + + if not can_be_true and can_be_false: + return False + + if not can_be_true and not can_be_false: + raise ValueError("Inconsistent assumptions") + + +def _preprocess(enc_cnf): + """ + Returns an encoded cnf with only Q.eq, Q.gt, Q.lt, + Q.ge, and Q.le predicate. + + Converts every unequality into a disjunction of strict + inequalities. For example, x != 3 would become + x < 3 OR x > 3. + + Also converts all negated Q.ne predicates into + equalities. + """ + + # loops through each literal in each clause + # to construct a new, preprocessed encodedCNF + + enc_cnf = enc_cnf.copy() + cur_enc = 1 + rev_encoding = {value: key for key, value in enc_cnf.encoding.items()} + + new_encoding = {} + new_data = [] + for clause in enc_cnf.data: + new_clause = [] + for lit in clause: + if lit == 0: + new_clause.append(lit) + new_encoding[lit] = False + continue + prop = rev_encoding[abs(lit)] + negated = lit < 0 + sign = (lit > 0) - (lit < 0) + + prop = _pred_to_binrel(prop) + + if not isinstance(prop, AppliedPredicate): + if prop not in new_encoding: + new_encoding[prop] = cur_enc + cur_enc += 1 + lit = new_encoding[prop] + new_clause.append(sign*lit) + continue + + + if negated and prop.function == Q.eq: + negated = False + prop = Q.ne(*prop.arguments) + + if prop.function == Q.ne: + arg1, arg2 = prop.arguments + if negated: + new_prop = Q.eq(arg1, arg2) + if new_prop not in new_encoding: + new_encoding[new_prop] = cur_enc + cur_enc += 1 + + new_enc = new_encoding[new_prop] + new_clause.append(new_enc) + continue + else: + new_props = (Q.gt(arg1, arg2), Q.lt(arg1, arg2)) + for new_prop in new_props: + if new_prop not in new_encoding: + new_encoding[new_prop] = cur_enc + cur_enc += 1 + + new_enc = new_encoding[new_prop] + new_clause.append(new_enc) + continue + + if prop.function == Q.eq and negated: + assert False + + if prop not in new_encoding: + new_encoding[prop] = cur_enc + cur_enc += 1 + new_clause.append(new_encoding[prop]*sign) + new_data.append(new_clause) + + assert len(new_encoding) >= cur_enc - 1 + + enc_cnf = EncodedCNF(new_data, new_encoding) + return enc_cnf + + +def _pred_to_binrel(pred): + if not isinstance(pred, AppliedPredicate): + return pred + + if pred.function in pred_to_pos_neg_zero: + f = pred_to_pos_neg_zero[pred.function] + if f is False: + return False + pred = f(pred.arguments[0]) + + if pred.function == Q.positive: + pred = Q.gt(pred.arguments[0], 0) + elif pred.function == Q.negative: + pred = Q.lt(pred.arguments[0], 0) + elif pred.function == Q.zero: + pred = Q.eq(pred.arguments[0], 0) + elif pred.function == Q.nonpositive: + pred = Q.le(pred.arguments[0], 0) + elif pred.function == Q.nonnegative: + pred = Q.ge(pred.arguments[0], 0) + elif pred.function == Q.nonzero: + pred = Q.ne(pred.arguments[0], 0) + + return pred + +pred_to_pos_neg_zero = { + Q.extended_positive: Q.positive, + Q.extended_negative: Q.negative, + Q.extended_nonpositive: Q.nonpositive, + Q.extended_negative: Q.negative, + Q.extended_nonzero: Q.nonzero, + Q.negative_infinite: False, + Q.positive_infinite: False +} + +def get_all_pred_and_expr_from_enc_cnf(enc_cnf): + all_exprs = set() + all_pred = set() + for pred in enc_cnf.encoding.keys(): + if isinstance(pred, AppliedPredicate): + all_pred.add(pred) + all_exprs.update(pred.arguments) + + return all_pred, all_exprs + +def extract_pred_from_old_assum(all_exprs): + """ + Returns a list of relevant new assumption predicate + based on any old assumptions. + + Raises an UnhandledInput exception if any of the assumptions are + unhandled. + + Ignored predicate: + - commutative + - complex + - algebraic + - transcendental + - extended_real + - real + - all matrix predicate + - rational + - irrational + + Example + ======= + >>> from sympy.assumptions.lra_satask import extract_pred_from_old_assum + >>> from sympy import symbols + >>> x, y = symbols("x y", positive=True) + >>> extract_pred_from_old_assum([x, y, 2]) + [Q.positive(x), Q.positive(y)] + """ + ret = [] + for expr in all_exprs: + if not hasattr(expr, "free_symbols"): + continue + if len(expr.free_symbols) == 0: + continue + + if expr.is_real is not True: + raise UnhandledInput(f"LRASolver: {expr} must be real") + # test for I times imaginary variable; such expressions are considered real + if isinstance(expr, Mul) and any(arg.is_real is not True for arg in expr.args): + raise UnhandledInput(f"LRASolver: {expr} must be real") + + if expr.is_integer == True and expr.is_zero != True: + raise UnhandledInput(f"LRASolver: {expr} is an integer") + if expr.is_integer == False: + raise UnhandledInput(f"LRASolver: {expr} can't be an integer") + if expr.is_rational == False: + raise UnhandledInput(f"LRASolver: {expr} is irational") + + if expr.is_zero: + ret.append(Q.zero(expr)) + elif expr.is_positive: + ret.append(Q.positive(expr)) + elif expr.is_negative: + ret.append(Q.negative(expr)) + elif expr.is_nonzero: + ret.append(Q.nonzero(expr)) + elif expr.is_nonpositive: + ret.append(Q.nonpositive(expr)) + elif expr.is_nonnegative: + ret.append(Q.nonnegative(expr)) + + return ret diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__init__.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8e294544bfdce13633ecff762ff42861aa12719f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__init__.py @@ -0,0 +1,5 @@ +""" +Module to implement predicate classes. + +Class of every predicate registered to ``Q`` is defined here. +""" diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f763aaef67e7d60f86c25cb163b2c6df2be44675 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/calculus.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/calculus.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5195d7e2644d610c407f24611f1ab56f6ab3f116 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/calculus.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/common.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/common.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bb208c19cee6b9255b65ecc665f715cd4b5a106 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/common.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/matrices.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/matrices.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..443f5550bd59979e9b5587ef16a3d52cfd91f4fe Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/matrices.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/ntheory.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/ntheory.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f3edd4e35a9720002788abe0076df9002ded9ee Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/ntheory.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/order.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/order.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea9d2da32689d0c5448be2ec84da6e33090c03e2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/order.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/sets.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/sets.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdd231541c7cad5e557d4603543acd9e25ef937b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/predicates/__pycache__/sets.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/calculus.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/calculus.py new file mode 100644 index 0000000000000000000000000000000000000000..f300703788683c07649ee3a0afd6e9d4eabd4567 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/calculus.py @@ -0,0 +1,82 @@ +from sympy.assumptions import Predicate +from sympy.multipledispatch import Dispatcher + +class FinitePredicate(Predicate): + """ + Finite number predicate. + + Explanation + =========== + + ``Q.finite(x)`` is true if ``x`` is a number but neither an infinity + nor a ``NaN``. In other words, ``ask(Q.finite(x))`` is true for all + numerical ``x`` having a bounded absolute value. + + Examples + ======== + + >>> from sympy import Q, ask, S, oo, I, zoo + >>> from sympy.abc import x + >>> ask(Q.finite(oo)) + False + >>> ask(Q.finite(-oo)) + False + >>> ask(Q.finite(zoo)) + False + >>> ask(Q.finite(1)) + True + >>> ask(Q.finite(2 + 3*I)) + True + >>> ask(Q.finite(x), Q.positive(x)) + True + >>> print(ask(Q.finite(S.NaN))) + None + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Finite + + """ + name = 'finite' + handler = Dispatcher( + "FiniteHandler", + doc=("Handler for Q.finite. Test that an expression is bounded respect" + " to all its variables.") + ) + + +class InfinitePredicate(Predicate): + """ + Infinite number predicate. + + ``Q.infinite(x)`` is true iff the absolute value of ``x`` is + infinity. + + """ + # TODO: Add examples + name = 'infinite' + handler = Dispatcher( + "InfiniteHandler", + doc="""Handler for Q.infinite key.""" + ) + + +class PositiveInfinitePredicate(Predicate): + """ + Positive infinity predicate. + + ``Q.positive_infinite(x)`` is true iff ``x`` is positive infinity ``oo``. + """ + name = 'positive_infinite' + handler = Dispatcher("PositiveInfiniteHandler") + + +class NegativeInfinitePredicate(Predicate): + """ + Negative infinity predicate. + + ``Q.negative_infinite(x)`` is true iff ``x`` is negative infinity ``-oo``. + """ + name = 'negative_infinite' + handler = Dispatcher("NegativeInfiniteHandler") diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/common.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/common.py new file mode 100644 index 0000000000000000000000000000000000000000..a53892747131b03636abeb8f563c4f76cf3e281e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/common.py @@ -0,0 +1,81 @@ +from sympy.assumptions import Predicate, AppliedPredicate, Q +from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le +from sympy.multipledispatch import Dispatcher + + +class CommutativePredicate(Predicate): + """ + Commutative predicate. + + Explanation + =========== + + ``ask(Q.commutative(x))`` is true iff ``x`` commutes with any other + object with respect to multiplication operation. + + """ + # TODO: Add examples + name = 'commutative' + handler = Dispatcher("CommutativeHandler", doc="Handler for key 'commutative'.") + + +binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le} + +class IsTruePredicate(Predicate): + """ + Generic predicate. + + Explanation + =========== + + ``ask(Q.is_true(x))`` is true iff ``x`` is true. This only makes + sense if ``x`` is a boolean object. + + Examples + ======== + + >>> from sympy import ask, Q + >>> from sympy.abc import x, y + >>> ask(Q.is_true(True)) + True + + Wrapping another applied predicate just returns the applied predicate. + + >>> Q.is_true(Q.even(x)) + Q.even(x) + + Wrapping binary relation classes in SymPy core returns applied binary + relational predicates. + + >>> from sympy import Eq, Gt + >>> Q.is_true(Eq(x, y)) + Q.eq(x, y) + >>> Q.is_true(Gt(x, y)) + Q.gt(x, y) + + Notes + ===== + + This class is designed to wrap the boolean objects so that they can + behave as if they are applied predicates. Consequently, wrapping another + applied predicate is unnecessary and thus it just returns the argument. + Also, binary relation classes in SymPy core have binary predicates to + represent themselves and thus wrapping them with ``Q.is_true`` converts them + to these applied predicates. + + """ + name = 'is_true' + handler = Dispatcher( + "IsTrueHandler", + doc="Wrapper allowing to query the truth value of a boolean expression." + ) + + def __call__(self, arg): + # No need to wrap another predicate + if isinstance(arg, AppliedPredicate): + return arg + # Convert relational predicates instead of wrapping them + if getattr(arg, "is_Relational", False): + pred = binrelpreds[type(arg)] + return pred(*arg.args) + return super().__call__(arg) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/matrices.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..151e78c4ff345800e1d2f17973fb0591b8d379d2 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/matrices.py @@ -0,0 +1,511 @@ +from sympy.assumptions import Predicate +from sympy.multipledispatch import Dispatcher + +class SquarePredicate(Predicate): + """ + Square matrix predicate. + + Explanation + =========== + + ``Q.square(x)`` is true iff ``x`` is a square matrix. A square matrix + is a matrix with the same number of rows and columns. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix, Identity + >>> X = MatrixSymbol('X', 2, 2) + >>> Y = MatrixSymbol('X', 2, 3) + >>> ask(Q.square(X)) + True + >>> ask(Q.square(Y)) + False + >>> ask(Q.square(ZeroMatrix(3, 3))) + True + >>> ask(Q.square(Identity(3))) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Square_matrix + + """ + name = 'square' + handler = Dispatcher("SquareHandler", doc="Handler for Q.square.") + + +class SymmetricPredicate(Predicate): + """ + Symmetric matrix predicate. + + Explanation + =========== + + ``Q.symmetric(x)`` is true iff ``x`` is a square matrix and is equal to + its transpose. Every square diagonal matrix is a symmetric matrix. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 2, 2) + >>> Y = MatrixSymbol('Y', 2, 3) + >>> Z = MatrixSymbol('Z', 2, 2) + >>> ask(Q.symmetric(X*Z), Q.symmetric(X) & Q.symmetric(Z)) + True + >>> ask(Q.symmetric(X + Z), Q.symmetric(X) & Q.symmetric(Z)) + True + >>> ask(Q.symmetric(Y)) + False + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Symmetric_matrix + + """ + # TODO: Add handlers to make these keys work with + # actual matrices and add more examples in the docstring. + name = 'symmetric' + handler = Dispatcher("SymmetricHandler", doc="Handler for Q.symmetric.") + + +class InvertiblePredicate(Predicate): + """ + Invertible matrix predicate. + + Explanation + =========== + + ``Q.invertible(x)`` is true iff ``x`` is an invertible matrix. + A square matrix is called invertible only if its determinant is 0. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 2, 2) + >>> Y = MatrixSymbol('Y', 2, 3) + >>> Z = MatrixSymbol('Z', 2, 2) + >>> ask(Q.invertible(X*Y), Q.invertible(X)) + False + >>> ask(Q.invertible(X*Z), Q.invertible(X) & Q.invertible(Z)) + True + >>> ask(Q.invertible(X), Q.fullrank(X) & Q.square(X)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Invertible_matrix + + """ + name = 'invertible' + handler = Dispatcher("InvertibleHandler", doc="Handler for Q.invertible.") + + +class OrthogonalPredicate(Predicate): + """ + Orthogonal matrix predicate. + + Explanation + =========== + + ``Q.orthogonal(x)`` is true iff ``x`` is an orthogonal matrix. + A square matrix ``M`` is an orthogonal matrix if it satisfies + ``M^TM = MM^T = I`` where ``M^T`` is the transpose matrix of + ``M`` and ``I`` is an identity matrix. Note that an orthogonal + matrix is necessarily invertible. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol, Identity + >>> X = MatrixSymbol('X', 2, 2) + >>> Y = MatrixSymbol('Y', 2, 3) + >>> Z = MatrixSymbol('Z', 2, 2) + >>> ask(Q.orthogonal(Y)) + False + >>> ask(Q.orthogonal(X*Z*X), Q.orthogonal(X) & Q.orthogonal(Z)) + True + >>> ask(Q.orthogonal(Identity(3))) + True + >>> ask(Q.invertible(X), Q.orthogonal(X)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Orthogonal_matrix + + """ + name = 'orthogonal' + handler = Dispatcher("OrthogonalHandler", doc="Handler for key 'orthogonal'.") + + +class UnitaryPredicate(Predicate): + """ + Unitary matrix predicate. + + Explanation + =========== + + ``Q.unitary(x)`` is true iff ``x`` is a unitary matrix. + Unitary matrix is an analogue to orthogonal matrix. A square + matrix ``M`` with complex elements is unitary if :math:``M^TM = MM^T= I`` + where :math:``M^T`` is the conjugate transpose matrix of ``M``. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol, Identity + >>> X = MatrixSymbol('X', 2, 2) + >>> Y = MatrixSymbol('Y', 2, 3) + >>> Z = MatrixSymbol('Z', 2, 2) + >>> ask(Q.unitary(Y)) + False + >>> ask(Q.unitary(X*Z*X), Q.unitary(X) & Q.unitary(Z)) + True + >>> ask(Q.unitary(Identity(3))) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Unitary_matrix + + """ + name = 'unitary' + handler = Dispatcher("UnitaryHandler", doc="Handler for key 'unitary'.") + + +class FullRankPredicate(Predicate): + """ + Fullrank matrix predicate. + + Explanation + =========== + + ``Q.fullrank(x)`` is true iff ``x`` is a full rank matrix. + A matrix is full rank if all rows and columns of the matrix + are linearly independent. A square matrix is full rank iff + its determinant is nonzero. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix, Identity + >>> X = MatrixSymbol('X', 2, 2) + >>> ask(Q.fullrank(X.T), Q.fullrank(X)) + True + >>> ask(Q.fullrank(ZeroMatrix(3, 3))) + False + >>> ask(Q.fullrank(Identity(3))) + True + + """ + name = 'fullrank' + handler = Dispatcher("FullRankHandler", doc="Handler for key 'fullrank'.") + + +class PositiveDefinitePredicate(Predicate): + r""" + Positive definite matrix predicate. + + Explanation + =========== + + If $M$ is a :math:`n \times n` symmetric real matrix, it is said + to be positive definite if :math:`Z^TMZ` is positive for + every non-zero column vector $Z$ of $n$ real numbers. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol, Identity + >>> X = MatrixSymbol('X', 2, 2) + >>> Y = MatrixSymbol('Y', 2, 3) + >>> Z = MatrixSymbol('Z', 2, 2) + >>> ask(Q.positive_definite(Y)) + False + >>> ask(Q.positive_definite(Identity(3))) + True + >>> ask(Q.positive_definite(X + Z), Q.positive_definite(X) & + ... Q.positive_definite(Z)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Positive-definite_matrix + + """ + name = "positive_definite" + handler = Dispatcher("PositiveDefiniteHandler", doc="Handler for key 'positive_definite'.") + + +class UpperTriangularPredicate(Predicate): + """ + Upper triangular matrix predicate. + + Explanation + =========== + + A matrix $M$ is called upper triangular matrix if :math:`M_{ij}=0` + for :math:`i>> from sympy import Q, ask, ZeroMatrix, Identity + >>> ask(Q.upper_triangular(Identity(3))) + True + >>> ask(Q.upper_triangular(ZeroMatrix(3, 3))) + True + + References + ========== + + .. [1] https://mathworld.wolfram.com/UpperTriangularMatrix.html + + """ + name = "upper_triangular" + handler = Dispatcher("UpperTriangularHandler", doc="Handler for key 'upper_triangular'.") + + +class LowerTriangularPredicate(Predicate): + """ + Lower triangular matrix predicate. + + Explanation + =========== + + A matrix $M$ is called lower triangular matrix if :math:`M_{ij}=0` + for :math:`i>j`. + + Examples + ======== + + >>> from sympy import Q, ask, ZeroMatrix, Identity + >>> ask(Q.lower_triangular(Identity(3))) + True + >>> ask(Q.lower_triangular(ZeroMatrix(3, 3))) + True + + References + ========== + + .. [1] https://mathworld.wolfram.com/LowerTriangularMatrix.html + + """ + name = "lower_triangular" + handler = Dispatcher("LowerTriangularHandler", doc="Handler for key 'lower_triangular'.") + + +class DiagonalPredicate(Predicate): + """ + Diagonal matrix predicate. + + Explanation + =========== + + ``Q.diagonal(x)`` is true iff ``x`` is a diagonal matrix. A diagonal + matrix is a matrix in which the entries outside the main diagonal + are all zero. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix + >>> X = MatrixSymbol('X', 2, 2) + >>> ask(Q.diagonal(ZeroMatrix(3, 3))) + True + >>> ask(Q.diagonal(X), Q.lower_triangular(X) & + ... Q.upper_triangular(X)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Diagonal_matrix + + """ + name = "diagonal" + handler = Dispatcher("DiagonalHandler", doc="Handler for key 'diagonal'.") + + +class IntegerElementsPredicate(Predicate): + """ + Integer elements matrix predicate. + + Explanation + =========== + + ``Q.integer_elements(x)`` is true iff all the elements of ``x`` + are integers. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.integer(X[1, 2]), Q.integer_elements(X)) + True + + """ + name = "integer_elements" + handler = Dispatcher("IntegerElementsHandler", doc="Handler for key 'integer_elements'.") + + +class RealElementsPredicate(Predicate): + """ + Real elements matrix predicate. + + Explanation + =========== + + ``Q.real_elements(x)`` is true iff all the elements of ``x`` + are real numbers. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.real(X[1, 2]), Q.real_elements(X)) + True + + """ + name = "real_elements" + handler = Dispatcher("RealElementsHandler", doc="Handler for key 'real_elements'.") + + +class ComplexElementsPredicate(Predicate): + """ + Complex elements matrix predicate. + + Explanation + =========== + + ``Q.complex_elements(x)`` is true iff all the elements of ``x`` + are complex numbers. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.complex(X[1, 2]), Q.complex_elements(X)) + True + >>> ask(Q.complex_elements(X), Q.integer_elements(X)) + True + + """ + name = "complex_elements" + handler = Dispatcher("ComplexElementsHandler", doc="Handler for key 'complex_elements'.") + + +class SingularPredicate(Predicate): + """ + Singular matrix predicate. + + A matrix is singular iff the value of its determinant is 0. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.singular(X), Q.invertible(X)) + False + >>> ask(Q.singular(X), ~Q.invertible(X)) + True + + References + ========== + + .. [1] https://mathworld.wolfram.com/SingularMatrix.html + + """ + name = "singular" + handler = Dispatcher("SingularHandler", doc="Predicate fore key 'singular'.") + + +class NormalPredicate(Predicate): + """ + Normal matrix predicate. + + A matrix is normal if it commutes with its conjugate transpose. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.normal(X), Q.unitary(X)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Normal_matrix + + """ + name = "normal" + handler = Dispatcher("NormalHandler", doc="Predicate fore key 'normal'.") + + +class TriangularPredicate(Predicate): + """ + Triangular matrix predicate. + + Explanation + =========== + + ``Q.triangular(X)`` is true if ``X`` is one that is either lower + triangular or upper triangular. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.triangular(X), Q.upper_triangular(X)) + True + >>> ask(Q.triangular(X), Q.lower_triangular(X)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Triangular_matrix + + """ + name = "triangular" + handler = Dispatcher("TriangularHandler", doc="Predicate fore key 'triangular'.") + + +class UnitTriangularPredicate(Predicate): + """ + Unit triangular matrix predicate. + + Explanation + =========== + + A unit triangular matrix is a triangular matrix with 1s + on the diagonal. + + Examples + ======== + + >>> from sympy import Q, ask, MatrixSymbol + >>> X = MatrixSymbol('X', 4, 4) + >>> ask(Q.triangular(X), Q.unit_triangular(X)) + True + + """ + name = "unit_triangular" + handler = Dispatcher("UnitTriangularHandler", doc="Predicate fore key 'unit_triangular'.") diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/ntheory.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/ntheory.py new file mode 100644 index 0000000000000000000000000000000000000000..6c598e0ed1bd4a1170aa28044f9ae6de2fa1a1e0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/ntheory.py @@ -0,0 +1,126 @@ +from sympy.assumptions import Predicate +from sympy.multipledispatch import Dispatcher + + +class PrimePredicate(Predicate): + """ + Prime number predicate. + + Explanation + =========== + + ``ask(Q.prime(x))`` is true iff ``x`` is a natural number greater + than 1 that has no positive divisors other than ``1`` and the + number itself. + + Examples + ======== + + >>> from sympy import Q, ask + >>> ask(Q.prime(0)) + False + >>> ask(Q.prime(1)) + False + >>> ask(Q.prime(2)) + True + >>> ask(Q.prime(20)) + False + >>> ask(Q.prime(-3)) + False + + """ + name = 'prime' + handler = Dispatcher( + "PrimeHandler", + doc=("Handler for key 'prime'. Test that an expression represents a prime" + " number. When the expression is an exact number, the result (when True)" + " is subject to the limitations of isprime() which is used to return the " + "result.") + ) + + +class CompositePredicate(Predicate): + """ + Composite number predicate. + + Explanation + =========== + + ``ask(Q.composite(x))`` is true iff ``x`` is a positive integer and has + at least one positive divisor other than ``1`` and the number itself. + + Examples + ======== + + >>> from sympy import Q, ask + >>> ask(Q.composite(0)) + False + >>> ask(Q.composite(1)) + False + >>> ask(Q.composite(2)) + False + >>> ask(Q.composite(20)) + True + + """ + name = 'composite' + handler = Dispatcher("CompositeHandler", doc="Handler for key 'composite'.") + + +class EvenPredicate(Predicate): + """ + Even number predicate. + + Explanation + =========== + + ``ask(Q.even(x))`` is true iff ``x`` belongs to the set of even + integers. + + Examples + ======== + + >>> from sympy import Q, ask, pi + >>> ask(Q.even(0)) + True + >>> ask(Q.even(2)) + True + >>> ask(Q.even(3)) + False + >>> ask(Q.even(pi)) + False + + """ + name = 'even' + handler = Dispatcher("EvenHandler", doc="Handler for key 'even'.") + + +class OddPredicate(Predicate): + """ + Odd number predicate. + + Explanation + =========== + + ``ask(Q.odd(x))`` is true iff ``x`` belongs to the set of odd numbers. + + Examples + ======== + + >>> from sympy import Q, ask, pi + >>> ask(Q.odd(0)) + False + >>> ask(Q.odd(2)) + False + >>> ask(Q.odd(3)) + True + >>> ask(Q.odd(pi)) + False + + """ + name = 'odd' + handler = Dispatcher( + "OddHandler", + doc=("Handler for key 'odd'. Test that an expression represents an odd" + " number.") + ) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/order.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/order.py new file mode 100644 index 0000000000000000000000000000000000000000..86bfb2ae49789efd5b0df99e2cfc63984e956dd0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/order.py @@ -0,0 +1,390 @@ +from sympy.assumptions import Predicate +from sympy.multipledispatch import Dispatcher + + +class NegativePredicate(Predicate): + r""" + Negative number predicate. + + Explanation + =========== + + ``Q.negative(x)`` is true iff ``x`` is a real number and :math:`x < 0`, that is, + it is in the interval :math:`(-\infty, 0)`. Note in particular that negative + infinity is not negative. + + A few important facts about negative numbers: + + - Note that ``Q.nonnegative`` and ``~Q.negative`` are *not* the same + thing. ``~Q.negative(x)`` simply means that ``x`` is not negative, + whereas ``Q.nonnegative(x)`` means that ``x`` is real and not + negative, i.e., ``Q.nonnegative(x)`` is logically equivalent to + ``Q.zero(x) | Q.positive(x)``. So for example, ``~Q.negative(I)`` is + true, whereas ``Q.nonnegative(I)`` is false. + + - See the documentation of ``Q.real`` for more information about + related facts. + + Examples + ======== + + >>> from sympy import Q, ask, symbols, I + >>> x = symbols('x') + >>> ask(Q.negative(x), Q.real(x) & ~Q.positive(x) & ~Q.zero(x)) + True + >>> ask(Q.negative(-1)) + True + >>> ask(Q.nonnegative(I)) + False + >>> ask(~Q.negative(I)) + True + + """ + name = 'negative' + handler = Dispatcher( + "NegativeHandler", + doc=("Handler for Q.negative. Test that an expression is strictly less" + " than zero.") + ) + + +class NonNegativePredicate(Predicate): + """ + Nonnegative real number predicate. + + Explanation + =========== + + ``ask(Q.nonnegative(x))`` is true iff ``x`` belongs to the set of + positive numbers including zero. + + - Note that ``Q.nonnegative`` and ``~Q.negative`` are *not* the same + thing. ``~Q.negative(x)`` simply means that ``x`` is not negative, + whereas ``Q.nonnegative(x)`` means that ``x`` is real and not + negative, i.e., ``Q.nonnegative(x)`` is logically equivalent to + ``Q.zero(x) | Q.positive(x)``. So for example, ``~Q.negative(I)`` is + true, whereas ``Q.nonnegative(I)`` is false. + + Examples + ======== + + >>> from sympy import Q, ask, I + >>> ask(Q.nonnegative(1)) + True + >>> ask(Q.nonnegative(0)) + True + >>> ask(Q.nonnegative(-1)) + False + >>> ask(Q.nonnegative(I)) + False + >>> ask(Q.nonnegative(-I)) + False + + """ + name = 'nonnegative' + handler = Dispatcher( + "NonNegativeHandler", + doc=("Handler for Q.nonnegative.") + ) + + +class NonZeroPredicate(Predicate): + """ + Nonzero real number predicate. + + Explanation + =========== + + ``ask(Q.nonzero(x))`` is true iff ``x`` is real and ``x`` is not zero. Note in + particular that ``Q.nonzero(x)`` is false if ``x`` is not real. Use + ``~Q.zero(x)`` if you want the negation of being zero without any real + assumptions. + + A few important facts about nonzero numbers: + + - ``Q.nonzero`` is logically equivalent to ``Q.positive | Q.negative``. + + - See the documentation of ``Q.real`` for more information about + related facts. + + Examples + ======== + + >>> from sympy import Q, ask, symbols, I, oo + >>> x = symbols('x') + >>> print(ask(Q.nonzero(x), ~Q.zero(x))) + None + >>> ask(Q.nonzero(x), Q.positive(x)) + True + >>> ask(Q.nonzero(x), Q.zero(x)) + False + >>> ask(Q.nonzero(0)) + False + >>> ask(Q.nonzero(I)) + False + >>> ask(~Q.zero(I)) + True + >>> ask(Q.nonzero(oo)) + False + + """ + name = 'nonzero' + handler = Dispatcher( + "NonZeroHandler", + doc=("Handler for key 'nonzero'. Test that an expression is not identically" + " zero.") + ) + + +class ZeroPredicate(Predicate): + """ + Zero number predicate. + + Explanation + =========== + + ``ask(Q.zero(x))`` is true iff the value of ``x`` is zero. + + Examples + ======== + + >>> from sympy import ask, Q, oo, symbols + >>> x, y = symbols('x, y') + >>> ask(Q.zero(0)) + True + >>> ask(Q.zero(1/oo)) + True + >>> print(ask(Q.zero(0*oo))) + None + >>> ask(Q.zero(1)) + False + >>> ask(Q.zero(x*y), Q.zero(x) | Q.zero(y)) + True + + """ + name = 'zero' + handler = Dispatcher( + "ZeroHandler", + doc="Handler for key 'zero'." + ) + + +class NonPositivePredicate(Predicate): + """ + Nonpositive real number predicate. + + Explanation + =========== + + ``ask(Q.nonpositive(x))`` is true iff ``x`` belongs to the set of + negative numbers including zero. + + - Note that ``Q.nonpositive`` and ``~Q.positive`` are *not* the same + thing. ``~Q.positive(x)`` simply means that ``x`` is not positive, + whereas ``Q.nonpositive(x)`` means that ``x`` is real and not + positive, i.e., ``Q.nonpositive(x)`` is logically equivalent to + `Q.negative(x) | Q.zero(x)``. So for example, ``~Q.positive(I)`` is + true, whereas ``Q.nonpositive(I)`` is false. + + Examples + ======== + + >>> from sympy import Q, ask, I + + >>> ask(Q.nonpositive(-1)) + True + >>> ask(Q.nonpositive(0)) + True + >>> ask(Q.nonpositive(1)) + False + >>> ask(Q.nonpositive(I)) + False + >>> ask(Q.nonpositive(-I)) + False + + """ + name = 'nonpositive' + handler = Dispatcher( + "NonPositiveHandler", + doc="Handler for key 'nonpositive'." + ) + + +class PositivePredicate(Predicate): + r""" + Positive real number predicate. + + Explanation + =========== + + ``Q.positive(x)`` is true iff ``x`` is real and `x > 0`, that is if ``x`` + is in the interval `(0, \infty)`. In particular, infinity is not + positive. + + A few important facts about positive numbers: + + - Note that ``Q.nonpositive`` and ``~Q.positive`` are *not* the same + thing. ``~Q.positive(x)`` simply means that ``x`` is not positive, + whereas ``Q.nonpositive(x)`` means that ``x`` is real and not + positive, i.e., ``Q.nonpositive(x)`` is logically equivalent to + `Q.negative(x) | Q.zero(x)``. So for example, ``~Q.positive(I)`` is + true, whereas ``Q.nonpositive(I)`` is false. + + - See the documentation of ``Q.real`` for more information about + related facts. + + Examples + ======== + + >>> from sympy import Q, ask, symbols, I + >>> x = symbols('x') + >>> ask(Q.positive(x), Q.real(x) & ~Q.negative(x) & ~Q.zero(x)) + True + >>> ask(Q.positive(1)) + True + >>> ask(Q.nonpositive(I)) + False + >>> ask(~Q.positive(I)) + True + + """ + name = 'positive' + handler = Dispatcher( + "PositiveHandler", + doc=("Handler for key 'positive'. Test that an expression is strictly" + " greater than zero.") + ) + + +class ExtendedPositivePredicate(Predicate): + r""" + Positive extended real number predicate. + + Explanation + =========== + + ``Q.extended_positive(x)`` is true iff ``x`` is extended real and + `x > 0`, that is if ``x`` is in the interval `(0, \infty]`. + + Examples + ======== + + >>> from sympy import ask, I, oo, Q + >>> ask(Q.extended_positive(1)) + True + >>> ask(Q.extended_positive(oo)) + True + >>> ask(Q.extended_positive(I)) + False + + """ + name = 'extended_positive' + handler = Dispatcher("ExtendedPositiveHandler") + + +class ExtendedNegativePredicate(Predicate): + r""" + Negative extended real number predicate. + + Explanation + =========== + + ``Q.extended_negative(x)`` is true iff ``x`` is extended real and + `x < 0`, that is if ``x`` is in the interval `[-\infty, 0)`. + + Examples + ======== + + >>> from sympy import ask, I, oo, Q + >>> ask(Q.extended_negative(-1)) + True + >>> ask(Q.extended_negative(-oo)) + True + >>> ask(Q.extended_negative(-I)) + False + + """ + name = 'extended_negative' + handler = Dispatcher("ExtendedNegativeHandler") + + +class ExtendedNonZeroPredicate(Predicate): + """ + Nonzero extended real number predicate. + + Explanation + =========== + + ``ask(Q.extended_nonzero(x))`` is true iff ``x`` is extended real and + ``x`` is not zero. + + Examples + ======== + + >>> from sympy import ask, I, oo, Q + >>> ask(Q.extended_nonzero(-1)) + True + >>> ask(Q.extended_nonzero(oo)) + True + >>> ask(Q.extended_nonzero(I)) + False + + """ + name = 'extended_nonzero' + handler = Dispatcher("ExtendedNonZeroHandler") + + +class ExtendedNonPositivePredicate(Predicate): + """ + Nonpositive extended real number predicate. + + Explanation + =========== + + ``ask(Q.extended_nonpositive(x))`` is true iff ``x`` is extended real and + ``x`` is not positive. + + Examples + ======== + + >>> from sympy import ask, I, oo, Q + >>> ask(Q.extended_nonpositive(-1)) + True + >>> ask(Q.extended_nonpositive(oo)) + False + >>> ask(Q.extended_nonpositive(0)) + True + >>> ask(Q.extended_nonpositive(I)) + False + + """ + name = 'extended_nonpositive' + handler = Dispatcher("ExtendedNonPositiveHandler") + + +class ExtendedNonNegativePredicate(Predicate): + """ + Nonnegative extended real number predicate. + + Explanation + =========== + + ``ask(Q.extended_nonnegative(x))`` is true iff ``x`` is extended real and + ``x`` is not negative. + + Examples + ======== + + >>> from sympy import ask, I, oo, Q + >>> ask(Q.extended_nonnegative(-1)) + False + >>> ask(Q.extended_nonnegative(oo)) + True + >>> ask(Q.extended_nonnegative(0)) + True + >>> ask(Q.extended_nonnegative(I)) + False + + """ + name = 'extended_nonnegative' + handler = Dispatcher("ExtendedNonNegativeHandler") diff --git a/phivenv/Lib/site-packages/sympy/assumptions/predicates/sets.py b/phivenv/Lib/site-packages/sympy/assumptions/predicates/sets.py new file mode 100644 index 0000000000000000000000000000000000000000..18261cee2d9de65df14a31a56b2cd22328328ed0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/predicates/sets.py @@ -0,0 +1,399 @@ +from sympy.assumptions import Predicate +from sympy.multipledispatch import Dispatcher + + +class IntegerPredicate(Predicate): + """ + Integer predicate. + + Explanation + =========== + + ``Q.integer(x)`` is true iff ``x`` belongs to the set of integer + numbers. + + Examples + ======== + + >>> from sympy import Q, ask, S + >>> ask(Q.integer(5)) + True + >>> ask(Q.integer(S(1)/2)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Integer + + """ + name = 'integer' + handler = Dispatcher( + "IntegerHandler", + doc=("Handler for Q.integer.\n\n" + "Test that an expression belongs to the field of integer numbers.") + ) + + +class NonIntegerPredicate(Predicate): + """ + Non-integer extended real predicate. + """ + name = 'noninteger' + handler = Dispatcher( + "NonIntegerHandler", + doc=("Handler for Q.noninteger.\n\n" + "Test that an expression is a non-integer extended real number.") + ) + + +class RationalPredicate(Predicate): + """ + Rational number predicate. + + Explanation + =========== + + ``Q.rational(x)`` is true iff ``x`` belongs to the set of + rational numbers. + + Examples + ======== + + >>> from sympy import ask, Q, pi, S + >>> ask(Q.rational(0)) + True + >>> ask(Q.rational(S(1)/2)) + True + >>> ask(Q.rational(pi)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Rational_number + + """ + name = 'rational' + handler = Dispatcher( + "RationalHandler", + doc=("Handler for Q.rational.\n\n" + "Test that an expression belongs to the field of rational numbers.") + ) + + +class IrrationalPredicate(Predicate): + """ + Irrational number predicate. + + Explanation + =========== + + ``Q.irrational(x)`` is true iff ``x`` is any real number that + cannot be expressed as a ratio of integers. + + Examples + ======== + + >>> from sympy import ask, Q, pi, S, I + >>> ask(Q.irrational(0)) + False + >>> ask(Q.irrational(S(1)/2)) + False + >>> ask(Q.irrational(pi)) + True + >>> ask(Q.irrational(I)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Irrational_number + + """ + name = 'irrational' + handler = Dispatcher( + "IrrationalHandler", + doc=("Handler for Q.irrational.\n\n" + "Test that an expression is irrational numbers.") + ) + + +class RealPredicate(Predicate): + r""" + Real number predicate. + + Explanation + =========== + + ``Q.real(x)`` is true iff ``x`` is a real number, i.e., it is in the + interval `(-\infty, \infty)`. Note that, in particular the + infinities are not real. Use ``Q.extended_real`` if you want to + consider those as well. + + A few important facts about reals: + + - Every real number is positive, negative, or zero. Furthermore, + because these sets are pairwise disjoint, each real number is + exactly one of those three. + + - Every real number is also complex. + + - Every real number is finite. + + - Every real number is either rational or irrational. + + - Every real number is either algebraic or transcendental. + + - The facts ``Q.negative``, ``Q.zero``, ``Q.positive``, + ``Q.nonnegative``, ``Q.nonpositive``, ``Q.nonzero``, + ``Q.integer``, ``Q.rational``, and ``Q.irrational`` all imply + ``Q.real``, as do all facts that imply those facts. + + - The facts ``Q.algebraic``, and ``Q.transcendental`` do not imply + ``Q.real``; they imply ``Q.complex``. An algebraic or + transcendental number may or may not be real. + + - The "non" facts (i.e., ``Q.nonnegative``, ``Q.nonzero``, + ``Q.nonpositive`` and ``Q.noninteger``) are not equivalent to + not the fact, but rather, not the fact *and* ``Q.real``. + For example, ``Q.nonnegative`` means ``~Q.negative & Q.real``. + So for example, ``I`` is not nonnegative, nonzero, or + nonpositive. + + Examples + ======== + + >>> from sympy import Q, ask, symbols + >>> x = symbols('x') + >>> ask(Q.real(x), Q.positive(x)) + True + >>> ask(Q.real(0)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Real_number + + """ + name = 'real' + handler = Dispatcher( + "RealHandler", + doc=("Handler for Q.real.\n\n" + "Test that an expression belongs to the field of real numbers.") + ) + + +class ExtendedRealPredicate(Predicate): + r""" + Extended real predicate. + + Explanation + =========== + + ``Q.extended_real(x)`` is true iff ``x`` is a real number or + `\{-\infty, \infty\}`. + + See documentation of ``Q.real`` for more information about related + facts. + + Examples + ======== + + >>> from sympy import ask, Q, oo, I + >>> ask(Q.extended_real(1)) + True + >>> ask(Q.extended_real(I)) + False + >>> ask(Q.extended_real(oo)) + True + + """ + name = 'extended_real' + handler = Dispatcher( + "ExtendedRealHandler", + doc=("Handler for Q.extended_real.\n\n" + "Test that an expression belongs to the field of extended real\n" + "numbers, that is real numbers union {Infinity, -Infinity}.") + ) + + +class HermitianPredicate(Predicate): + """ + Hermitian predicate. + + Explanation + =========== + + ``ask(Q.hermitian(x))`` is true iff ``x`` belongs to the set of + Hermitian operators. + + References + ========== + + .. [1] https://mathworld.wolfram.com/HermitianOperator.html + + """ + # TODO: Add examples + name = 'hermitian' + handler = Dispatcher( + "HermitianHandler", + doc=("Handler for Q.hermitian.\n\n" + "Test that an expression belongs to the field of Hermitian operators.") + ) + + +class ComplexPredicate(Predicate): + """ + Complex number predicate. + + Explanation + =========== + + ``Q.complex(x)`` is true iff ``x`` belongs to the set of complex + numbers. Note that every complex number is finite. + + Examples + ======== + + >>> from sympy import Q, Symbol, ask, I, oo + >>> x = Symbol('x') + >>> ask(Q.complex(0)) + True + >>> ask(Q.complex(2 + 3*I)) + True + >>> ask(Q.complex(oo)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Complex_number + + """ + name = 'complex' + handler = Dispatcher( + "ComplexHandler", + doc=("Handler for Q.complex.\n\n" + "Test that an expression belongs to the field of complex numbers.") + ) + + +class ImaginaryPredicate(Predicate): + """ + Imaginary number predicate. + + Explanation + =========== + + ``Q.imaginary(x)`` is true iff ``x`` can be written as a real + number multiplied by the imaginary unit ``I``. Please note that ``0`` + is not considered to be an imaginary number. + + Examples + ======== + + >>> from sympy import Q, ask, I + >>> ask(Q.imaginary(3*I)) + True + >>> ask(Q.imaginary(2 + 3*I)) + False + >>> ask(Q.imaginary(0)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Imaginary_number + + """ + name = 'imaginary' + handler = Dispatcher( + "ImaginaryHandler", + doc=("Handler for Q.imaginary.\n\n" + "Test that an expression belongs to the field of imaginary numbers,\n" + "that is, numbers in the form x*I, where x is real.") + ) + + +class AntihermitianPredicate(Predicate): + """ + Antihermitian predicate. + + Explanation + =========== + + ``Q.antihermitian(x)`` is true iff ``x`` belongs to the field of + antihermitian operators, i.e., operators in the form ``x*I``, where + ``x`` is Hermitian. + + References + ========== + + .. [1] https://mathworld.wolfram.com/HermitianOperator.html + + """ + # TODO: Add examples + name = 'antihermitian' + handler = Dispatcher( + "AntiHermitianHandler", + doc=("Handler for Q.antihermitian.\n\n" + "Test that an expression belongs to the field of anti-Hermitian\n" + "operators, that is, operators in the form x*I, where x is Hermitian.") + ) + + +class AlgebraicPredicate(Predicate): + r""" + Algebraic number predicate. + + Explanation + =========== + + ``Q.algebraic(x)`` is true iff ``x`` belongs to the set of + algebraic numbers. ``x`` is algebraic if there is some polynomial + in ``p(x)\in \mathbb\{Q\}[x]`` such that ``p(x) = 0``. + + Examples + ======== + + >>> from sympy import ask, Q, sqrt, I, pi + >>> ask(Q.algebraic(sqrt(2))) + True + >>> ask(Q.algebraic(I)) + True + >>> ask(Q.algebraic(pi)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Algebraic_number + + """ + name = 'algebraic' + AlgebraicHandler = Dispatcher( + "AlgebraicHandler", + doc="""Handler for Q.algebraic key.""" + ) + + +class TranscendentalPredicate(Predicate): + """ + Transcedental number predicate. + + Explanation + =========== + + ``Q.transcendental(x)`` is true iff ``x`` belongs to the set of + transcendental numbers. A transcendental number is a real + or complex number that is not algebraic. + + """ + # TODO: Add examples + name = 'transcendental' + handler = Dispatcher( + "Transcendental", + doc="""Handler for Q.transcendental key.""" + ) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/refine.py b/phivenv/Lib/site-packages/sympy/assumptions/refine.py new file mode 100644 index 0000000000000000000000000000000000000000..c36a4e1cdb40f1b59a96f60a3b36182b587920fa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/refine.py @@ -0,0 +1,405 @@ +from __future__ import annotations +from typing import Callable + +from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational +from sympy.core.logic import fuzzy_not +from sympy.logic.boolalg import Boolean + +from sympy.assumptions import ask, Q # type: ignore + + +def refine(expr, assumptions=True): + """ + Simplify an expression using assumptions. + + Explanation + =========== + + Unlike :func:`~.simplify` which performs structural simplification + without any assumption, this function transforms the expression into + the form which is only valid under certain assumptions. Note that + ``simplify()`` is generally not done in refining process. + + Refining boolean expression involves reducing it to ``S.true`` or + ``S.false``. Unlike :func:`~.ask`, the expression will not be reduced + if the truth value cannot be determined. + + Examples + ======== + + >>> from sympy import refine, sqrt, Q + >>> from sympy.abc import x + >>> refine(sqrt(x**2), Q.real(x)) + Abs(x) + >>> refine(sqrt(x**2), Q.positive(x)) + x + + >>> refine(Q.real(x), Q.positive(x)) + True + >>> refine(Q.positive(x), Q.real(x)) + Q.positive(x) + + See Also + ======== + + sympy.simplify.simplify.simplify : Structural simplification without assumptions. + sympy.assumptions.ask.ask : Query for boolean expressions using assumptions. + """ + if not isinstance(expr, Basic): + return expr + + if not expr.is_Atom: + args = [refine(arg, assumptions) for arg in expr.args] + # TODO: this will probably not work with Integral or Polynomial + expr = expr.func(*args) + if hasattr(expr, '_eval_refine'): + ref_expr = expr._eval_refine(assumptions) + if ref_expr is not None: + return ref_expr + name = expr.__class__.__name__ + handler = handlers_dict.get(name, None) + if handler is None: + return expr + new_expr = handler(expr, assumptions) + if (new_expr is None) or (expr == new_expr): + return expr + if not isinstance(new_expr, Expr): + return new_expr + return refine(new_expr, assumptions) + + +def refine_abs(expr, assumptions): + """ + Handler for the absolute value. + + Examples + ======== + + >>> from sympy import Q, Abs + >>> from sympy.assumptions.refine import refine_abs + >>> from sympy.abc import x + >>> refine_abs(Abs(x), Q.real(x)) + >>> refine_abs(Abs(x), Q.positive(x)) + x + >>> refine_abs(Abs(x), Q.negative(x)) + -x + + """ + from sympy.functions.elementary.complexes import Abs + arg = expr.args[0] + if ask(Q.real(arg), assumptions) and \ + fuzzy_not(ask(Q.negative(arg), assumptions)): + # if it's nonnegative + return arg + if ask(Q.negative(arg), assumptions): + return -arg + # arg is Mul + if isinstance(arg, Mul): + r = [refine(abs(a), assumptions) for a in arg.args] + non_abs = [] + in_abs = [] + for i in r: + if isinstance(i, Abs): + in_abs.append(i.args[0]) + else: + non_abs.append(i) + return Mul(*non_abs) * Abs(Mul(*in_abs)) + + +def refine_Pow(expr, assumptions): + """ + Handler for instances of Pow. + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.refine import refine_Pow + >>> from sympy.abc import x,y,z + >>> refine_Pow((-1)**x, Q.real(x)) + >>> refine_Pow((-1)**x, Q.even(x)) + 1 + >>> refine_Pow((-1)**x, Q.odd(x)) + -1 + + For powers of -1, even parts of the exponent can be simplified: + + >>> refine_Pow((-1)**(x+y), Q.even(x)) + (-1)**y + >>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z)) + (-1)**y + >>> refine_Pow((-1)**(x+y+2), Q.odd(x)) + (-1)**(y + 1) + >>> refine_Pow((-1)**(x+3), True) + (-1)**(x + 1) + + """ + from sympy.functions.elementary.complexes import Abs + from sympy.functions import sign + if isinstance(expr.base, Abs): + if ask(Q.real(expr.base.args[0]), assumptions) and \ + ask(Q.even(expr.exp), assumptions): + return expr.base.args[0] ** expr.exp + if ask(Q.real(expr.base), assumptions): + if expr.base.is_number: + if ask(Q.even(expr.exp), assumptions): + return abs(expr.base) ** expr.exp + if ask(Q.odd(expr.exp), assumptions): + return sign(expr.base) * abs(expr.base) ** expr.exp + if isinstance(expr.exp, Rational): + if isinstance(expr.base, Pow): + return abs(expr.base.base) ** (expr.base.exp * expr.exp) + + if expr.base is S.NegativeOne: + if expr.exp.is_Add: + + old = expr + + # For powers of (-1) we can remove + # - even terms + # - pairs of odd terms + # - a single odd term + 1 + # - A numerical constant N can be replaced with mod(N,2) + + coeff, terms = expr.exp.as_coeff_add() + terms = set(terms) + even_terms = set() + odd_terms = set() + initial_number_of_terms = len(terms) + + for t in terms: + if ask(Q.even(t), assumptions): + even_terms.add(t) + elif ask(Q.odd(t), assumptions): + odd_terms.add(t) + + terms -= even_terms + if len(odd_terms) % 2: + terms -= odd_terms + new_coeff = (coeff + S.One) % 2 + else: + terms -= odd_terms + new_coeff = coeff % 2 + + if new_coeff != coeff or len(terms) < initial_number_of_terms: + terms.add(new_coeff) + expr = expr.base**(Add(*terms)) + + # Handle (-1)**((-1)**n/2 + m/2) + e2 = 2*expr.exp + if ask(Q.even(e2), assumptions): + if e2.could_extract_minus_sign(): + e2 *= expr.base + if e2.is_Add: + i, p = e2.as_two_terms() + if p.is_Pow and p.base is S.NegativeOne: + if ask(Q.integer(p.exp), assumptions): + i = (i + 1)/2 + if ask(Q.even(i), assumptions): + return expr.base**p.exp + elif ask(Q.odd(i), assumptions): + return expr.base**(p.exp + 1) + else: + return expr.base**(p.exp + i) + + if old != expr: + return expr + + +def refine_atan2(expr, assumptions): + """ + Handler for the atan2 function. + + Examples + ======== + + >>> from sympy import Q, atan2 + >>> from sympy.assumptions.refine import refine_atan2 + >>> from sympy.abc import x, y + >>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x)) + atan(y/x) + >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x)) + atan(y/x) - pi + >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x)) + atan(y/x) + pi + >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x)) + pi + >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x)) + pi/2 + >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x)) + -pi/2 + >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x)) + nan + """ + from sympy.functions.elementary.trigonometric import atan + y, x = expr.args + if ask(Q.real(y) & Q.positive(x), assumptions): + return atan(y / x) + elif ask(Q.negative(y) & Q.negative(x), assumptions): + return atan(y / x) - S.Pi + elif ask(Q.positive(y) & Q.negative(x), assumptions): + return atan(y / x) + S.Pi + elif ask(Q.zero(y) & Q.negative(x), assumptions): + return S.Pi + elif ask(Q.positive(y) & Q.zero(x), assumptions): + return S.Pi/2 + elif ask(Q.negative(y) & Q.zero(x), assumptions): + return -S.Pi/2 + elif ask(Q.zero(y) & Q.zero(x), assumptions): + return S.NaN + else: + return expr + + +def refine_re(expr, assumptions): + """ + Handler for real part. + + Examples + ======== + + >>> from sympy.assumptions.refine import refine_re + >>> from sympy import Q, re + >>> from sympy.abc import x + >>> refine_re(re(x), Q.real(x)) + x + >>> refine_re(re(x), Q.imaginary(x)) + 0 + """ + arg = expr.args[0] + if ask(Q.real(arg), assumptions): + return arg + if ask(Q.imaginary(arg), assumptions): + return S.Zero + return _refine_reim(expr, assumptions) + + +def refine_im(expr, assumptions): + """ + Handler for imaginary part. + + Explanation + =========== + + >>> from sympy.assumptions.refine import refine_im + >>> from sympy import Q, im + >>> from sympy.abc import x + >>> refine_im(im(x), Q.real(x)) + 0 + >>> refine_im(im(x), Q.imaginary(x)) + -I*x + """ + arg = expr.args[0] + if ask(Q.real(arg), assumptions): + return S.Zero + if ask(Q.imaginary(arg), assumptions): + return - S.ImaginaryUnit * arg + return _refine_reim(expr, assumptions) + +def refine_arg(expr, assumptions): + """ + Handler for complex argument + + Explanation + =========== + + >>> from sympy.assumptions.refine import refine_arg + >>> from sympy import Q, arg + >>> from sympy.abc import x + >>> refine_arg(arg(x), Q.positive(x)) + 0 + >>> refine_arg(arg(x), Q.negative(x)) + pi + """ + rg = expr.args[0] + if ask(Q.positive(rg), assumptions): + return S.Zero + if ask(Q.negative(rg), assumptions): + return S.Pi + return None + + +def _refine_reim(expr, assumptions): + # Helper function for refine_re & refine_im + expanded = expr.expand(complex = True) + if expanded != expr: + refined = refine(expanded, assumptions) + if refined != expanded: + return refined + # Best to leave the expression as is + return None + + +def refine_sign(expr, assumptions): + """ + Handler for sign. + + Examples + ======== + + >>> from sympy.assumptions.refine import refine_sign + >>> from sympy import Symbol, Q, sign, im + >>> x = Symbol('x', real = True) + >>> expr = sign(x) + >>> refine_sign(expr, Q.positive(x) & Q.nonzero(x)) + 1 + >>> refine_sign(expr, Q.negative(x) & Q.nonzero(x)) + -1 + >>> refine_sign(expr, Q.zero(x)) + 0 + >>> y = Symbol('y', imaginary = True) + >>> expr = sign(y) + >>> refine_sign(expr, Q.positive(im(y))) + I + >>> refine_sign(expr, Q.negative(im(y))) + -I + """ + arg = expr.args[0] + if ask(Q.zero(arg), assumptions): + return S.Zero + if ask(Q.real(arg)): + if ask(Q.positive(arg), assumptions): + return S.One + if ask(Q.negative(arg), assumptions): + return S.NegativeOne + if ask(Q.imaginary(arg)): + arg_re, arg_im = arg.as_real_imag() + if ask(Q.positive(arg_im), assumptions): + return S.ImaginaryUnit + if ask(Q.negative(arg_im), assumptions): + return -S.ImaginaryUnit + return expr + + +def refine_matrixelement(expr, assumptions): + """ + Handler for symmetric part. + + Examples + ======== + + >>> from sympy.assumptions.refine import refine_matrixelement + >>> from sympy import MatrixSymbol, Q + >>> X = MatrixSymbol('X', 3, 3) + >>> refine_matrixelement(X[0, 1], Q.symmetric(X)) + X[0, 1] + >>> refine_matrixelement(X[1, 0], Q.symmetric(X)) + X[0, 1] + """ + from sympy.matrices.expressions.matexpr import MatrixElement + matrix, i, j = expr.args + if ask(Q.symmetric(matrix), assumptions): + if (i - j).could_extract_minus_sign(): + return expr + return MatrixElement(matrix, j, i) + +handlers_dict: dict[str, Callable[[Expr, Boolean], Expr]] = { + 'Abs': refine_abs, + 'Pow': refine_Pow, + 'atan2': refine_atan2, + 're': refine_re, + 'im': refine_im, + 'arg': refine_arg, + 'sign': refine_sign, + 'MatrixElement': refine_matrixelement +} diff --git a/phivenv/Lib/site-packages/sympy/assumptions/relation/__init__.py b/phivenv/Lib/site-packages/sympy/assumptions/relation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..04f5ed37893766feec941614691a9177f14e4027 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/relation/__init__.py @@ -0,0 +1,13 @@ +""" +A module to implement finitary relations [1] as predicate. + +References +========== + +.. [1] https://en.wikipedia.org/wiki/Finitary_relation + +""" + +__all__ = ['BinaryRelation', 'AppliedBinaryRelation'] + +from .binrel import BinaryRelation, AppliedBinaryRelation diff --git a/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51604f6c17cd19dc3a5996fe45fcbc3705d1b828 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/binrel.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/binrel.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26045cbcb9d33cad8c43f1259249ff060decb969 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/binrel.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/equality.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/equality.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93df76ef9e591f1abd7433741283facc36cfc293 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/relation/__pycache__/equality.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/relation/binrel.py b/phivenv/Lib/site-packages/sympy/assumptions/relation/binrel.py new file mode 100644 index 0000000000000000000000000000000000000000..4b4eba05bcce40f1a05483a30136b6ccd891c42f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/relation/binrel.py @@ -0,0 +1,212 @@ +""" +General binary relations. +""" +from typing import Optional + +from sympy.core.singleton import S +from sympy.assumptions import AppliedPredicate, ask, Predicate, Q # type: ignore +from sympy.core.kind import BooleanKind +from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le +from sympy.logic.boolalg import conjuncts, Not + +__all__ = ["BinaryRelation", "AppliedBinaryRelation"] + + +class BinaryRelation(Predicate): + """ + Base class for all binary relational predicates. + + Explanation + =========== + + Binary relation takes two arguments and returns ``AppliedBinaryRelation`` + instance. To evaluate it to boolean value, use :obj:`~.ask()` or + :obj:`~.refine()` function. + + You can add support for new types by registering the handler to dispatcher. + See :obj:`~.Predicate()` for more information about predicate dispatching. + + Examples + ======== + + Applying and evaluating to boolean value: + + >>> from sympy import Q, ask, sin, cos + >>> from sympy.abc import x + >>> Q.eq(sin(x)**2+cos(x)**2, 1) + Q.eq(sin(x)**2 + cos(x)**2, 1) + >>> ask(_) + True + + You can define a new binary relation by subclassing and dispatching. + Here, we define a relation $R$ such that $x R y$ returns true if + $x = y + 1$. + + >>> from sympy import ask, Number, Q + >>> from sympy.assumptions import BinaryRelation + >>> class MyRel(BinaryRelation): + ... name = "R" + ... is_reflexive = False + >>> Q.R = MyRel() + >>> @Q.R.register(Number, Number) + ... def _(n1, n2, assumptions): + ... return ask(Q.zero(n1 - n2 - 1), assumptions) + >>> Q.R(2, 1) + Q.R(2, 1) + + Now, we can use ``ask()`` to evaluate it to boolean value. + + >>> ask(Q.R(2, 1)) + True + >>> ask(Q.R(1, 2)) + False + + ``Q.R`` returns ``False`` with minimum cost if two arguments have same + structure because it is antireflexive relation [1] by + ``is_reflexive = False``. + + >>> ask(Q.R(x, x)) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Reflexive_relation + """ + + is_reflexive: Optional[bool] = None + is_symmetric: Optional[bool] = None + + def __call__(self, *args): + if not len(args) == 2: + raise ValueError("Binary relation takes two arguments, but got %s." % len(args)) + return AppliedBinaryRelation(self, *args) + + @property + def reversed(self): + if self.is_symmetric: + return self + return None + + @property + def negated(self): + return None + + def _compare_reflexive(self, lhs, rhs): + # quick exit for structurally same arguments + # do not check != here because it cannot catch the + # equivalent arguments with different structures. + + # reflexivity does not hold to NaN + if lhs is S.NaN or rhs is S.NaN: + return None + + reflexive = self.is_reflexive + if reflexive is None: + pass + elif reflexive and (lhs == rhs): + return True + elif not reflexive and (lhs == rhs): + return False + return None + + def eval(self, args, assumptions=True): + # quick exit for structurally same arguments + ret = self._compare_reflexive(*args) + if ret is not None: + return ret + + # don't perform simplify on args here. (done by AppliedBinaryRelation._eval_ask) + # evaluate by multipledispatch + lhs, rhs = args + ret = self.handler(lhs, rhs, assumptions=assumptions) + if ret is not None: + return ret + + # check reversed order if the relation is reflexive + if self.is_reflexive: + types = (type(lhs), type(rhs)) + if self.handler.dispatch(*types) is not self.handler.dispatch(*reversed(types)): + ret = self.handler(rhs, lhs, assumptions=assumptions) + + return ret + + +class AppliedBinaryRelation(AppliedPredicate): + """ + The class of expressions resulting from applying ``BinaryRelation`` + to the arguments. + + """ + + @property + def lhs(self): + """The left-hand side of the relation.""" + return self.arguments[0] + + @property + def rhs(self): + """The right-hand side of the relation.""" + return self.arguments[1] + + @property + def reversed(self): + """ + Try to return the relationship with sides reversed. + """ + revfunc = self.function.reversed + if revfunc is None: + return self + return revfunc(self.rhs, self.lhs) + + @property + def reversedsign(self): + """ + Try to return the relationship with signs reversed. + """ + revfunc = self.function.reversed + if revfunc is None: + return self + if not any(side.kind is BooleanKind for side in self.arguments): + return revfunc(-self.lhs, -self.rhs) + return self + + @property + def negated(self): + neg_rel = self.function.negated + if neg_rel is None: + return Not(self, evaluate=False) + return neg_rel(*self.arguments) + + def _eval_ask(self, assumptions): + conj_assumps = set() + binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le} + for a in conjuncts(assumptions): + if a.func in binrelpreds: + conj_assumps.add(binrelpreds[type(a)](*a.args)) + else: + conj_assumps.add(a) + + # After CNF in assumptions module is modified to take polyadic + # predicate, this will be removed + if any(rel in conj_assumps for rel in (self, self.reversed)): + return True + neg_rels = (self.negated, self.reversed.negated, Not(self, evaluate=False), + Not(self.reversed, evaluate=False)) + if any(rel in conj_assumps for rel in neg_rels): + return False + + # evaluation using multipledispatching + ret = self.function.eval(self.arguments, assumptions) + if ret is not None: + return ret + + # simplify the args and try again + args = tuple(a.simplify() for a in self.arguments) + return self.function.eval(args, assumptions) + + def __bool__(self): + ret = ask(self) + if ret is None: + raise TypeError("Cannot determine truth value of %s" % self) + return ret diff --git a/phivenv/Lib/site-packages/sympy/assumptions/relation/equality.py b/phivenv/Lib/site-packages/sympy/assumptions/relation/equality.py new file mode 100644 index 0000000000000000000000000000000000000000..d467cea2da706de2cbbc9875f93c7f8e324a9088 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/relation/equality.py @@ -0,0 +1,302 @@ +""" +Module for mathematical equality [1] and inequalities [2]. + +The purpose of this module is to provide the instances which represent the +binary predicates in order to combine the relationals into logical inference +system. Objects such as ``Q.eq``, ``Q.lt`` should remain internal to +assumptions module, and user must use the classes such as :obj:`~.Eq()`, +:obj:`~.Lt()` instead to construct the relational expressions. + +References +========== + +.. [1] https://en.wikipedia.org/wiki/Equality_(mathematics) +.. [2] https://en.wikipedia.org/wiki/Inequality_(mathematics) +""" +from sympy.assumptions import Q +from sympy.core.relational import is_eq, is_neq, is_gt, is_ge, is_lt, is_le + +from .binrel import BinaryRelation + +__all__ = ['EqualityPredicate', 'UnequalityPredicate', 'StrictGreaterThanPredicate', + 'GreaterThanPredicate', 'StrictLessThanPredicate', 'LessThanPredicate'] + + +class EqualityPredicate(BinaryRelation): + """ + Binary predicate for $=$. + + The purpose of this class is to provide the instance which represent + the equality predicate in order to allow the logical inference. + This class must remain internal to assumptions module and user must + use :obj:`~.Eq()` instead to construct the equality expression. + + Evaluating this predicate to ``True`` or ``False`` is done by + :func:`~.core.relational.is_eq` + + Examples + ======== + + >>> from sympy import ask, Q + >>> Q.eq(0, 0) + Q.eq(0, 0) + >>> ask(_) + True + + See Also + ======== + + sympy.core.relational.Eq + + """ + is_reflexive = True + is_symmetric = True + + name = 'eq' + handler = None # Do not allow dispatching by this predicate + + @property + def negated(self): + return Q.ne + + def eval(self, args, assumptions=True): + if assumptions == True: + # default assumptions for is_eq is None + assumptions = None + return is_eq(*args, assumptions) + + +class UnequalityPredicate(BinaryRelation): + r""" + Binary predicate for $\neq$. + + The purpose of this class is to provide the instance which represent + the inequation predicate in order to allow the logical inference. + This class must remain internal to assumptions module and user must + use :obj:`~.Ne()` instead to construct the inequation expression. + + Evaluating this predicate to ``True`` or ``False`` is done by + :func:`~.core.relational.is_neq` + + Examples + ======== + + >>> from sympy import ask, Q + >>> Q.ne(0, 0) + Q.ne(0, 0) + >>> ask(_) + False + + See Also + ======== + + sympy.core.relational.Ne + + """ + is_reflexive = False + is_symmetric = True + + name = 'ne' + handler = None + + @property + def negated(self): + return Q.eq + + def eval(self, args, assumptions=True): + if assumptions == True: + # default assumptions for is_neq is None + assumptions = None + return is_neq(*args, assumptions) + + +class StrictGreaterThanPredicate(BinaryRelation): + """ + Binary predicate for $>$. + + The purpose of this class is to provide the instance which represent + the ">" predicate in order to allow the logical inference. + This class must remain internal to assumptions module and user must + use :obj:`~.Gt()` instead to construct the equality expression. + + Evaluating this predicate to ``True`` or ``False`` is done by + :func:`~.core.relational.is_gt` + + Examples + ======== + + >>> from sympy import ask, Q + >>> Q.gt(0, 0) + Q.gt(0, 0) + >>> ask(_) + False + + See Also + ======== + + sympy.core.relational.Gt + + """ + is_reflexive = False + is_symmetric = False + + name = 'gt' + handler = None + + @property + def reversed(self): + return Q.lt + + @property + def negated(self): + return Q.le + + def eval(self, args, assumptions=True): + if assumptions == True: + # default assumptions for is_gt is None + assumptions = None + return is_gt(*args, assumptions) + + +class GreaterThanPredicate(BinaryRelation): + """ + Binary predicate for $>=$. + + The purpose of this class is to provide the instance which represent + the ">=" predicate in order to allow the logical inference. + This class must remain internal to assumptions module and user must + use :obj:`~.Ge()` instead to construct the equality expression. + + Evaluating this predicate to ``True`` or ``False`` is done by + :func:`~.core.relational.is_ge` + + Examples + ======== + + >>> from sympy import ask, Q + >>> Q.ge(0, 0) + Q.ge(0, 0) + >>> ask(_) + True + + See Also + ======== + + sympy.core.relational.Ge + + """ + is_reflexive = True + is_symmetric = False + + name = 'ge' + handler = None + + @property + def reversed(self): + return Q.le + + @property + def negated(self): + return Q.lt + + def eval(self, args, assumptions=True): + if assumptions == True: + # default assumptions for is_ge is None + assumptions = None + return is_ge(*args, assumptions) + + +class StrictLessThanPredicate(BinaryRelation): + """ + Binary predicate for $<$. + + The purpose of this class is to provide the instance which represent + the "<" predicate in order to allow the logical inference. + This class must remain internal to assumptions module and user must + use :obj:`~.Lt()` instead to construct the equality expression. + + Evaluating this predicate to ``True`` or ``False`` is done by + :func:`~.core.relational.is_lt` + + Examples + ======== + + >>> from sympy import ask, Q + >>> Q.lt(0, 0) + Q.lt(0, 0) + >>> ask(_) + False + + See Also + ======== + + sympy.core.relational.Lt + + """ + is_reflexive = False + is_symmetric = False + + name = 'lt' + handler = None + + @property + def reversed(self): + return Q.gt + + @property + def negated(self): + return Q.ge + + def eval(self, args, assumptions=True): + if assumptions == True: + # default assumptions for is_lt is None + assumptions = None + return is_lt(*args, assumptions) + + +class LessThanPredicate(BinaryRelation): + """ + Binary predicate for $<=$. + + The purpose of this class is to provide the instance which represent + the "<=" predicate in order to allow the logical inference. + This class must remain internal to assumptions module and user must + use :obj:`~.Le()` instead to construct the equality expression. + + Evaluating this predicate to ``True`` or ``False`` is done by + :func:`~.core.relational.is_le` + + Examples + ======== + + >>> from sympy import ask, Q + >>> Q.le(0, 0) + Q.le(0, 0) + >>> ask(_) + True + + See Also + ======== + + sympy.core.relational.Le + + """ + is_reflexive = True + is_symmetric = False + + name = 'le' + handler = None + + @property + def reversed(self): + return Q.ge + + @property + def negated(self): + return Q.gt + + def eval(self, args, assumptions=True): + if assumptions == True: + # default assumptions for is_le is None + assumptions = None + return is_le(*args, assumptions) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/satask.py b/phivenv/Lib/site-packages/sympy/assumptions/satask.py new file mode 100644 index 0000000000000000000000000000000000000000..ffc13f6d3bc3fb7f573c8d5d0564b780440c1a8c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/satask.py @@ -0,0 +1,369 @@ +""" +Module to evaluate the proposition with assumptions using SAT algorithm. +""" + +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.kind import NumberKind, UndefinedKind +from sympy.assumptions.ask_generated import get_all_known_matrix_facts, get_all_known_number_facts +from sympy.assumptions.assume import global_assumptions, AppliedPredicate +from sympy.assumptions.sathandlers import class_fact_registry +from sympy.core import oo +from sympy.logic.inference import satisfiable +from sympy.assumptions.cnf import CNF, EncodedCNF +from sympy.matrices.kind import MatrixKind + + +def satask(proposition, assumptions=True, context=global_assumptions, + use_known_facts=True, iterations=oo): + """ + Function to evaluate the proposition with assumptions using SAT algorithm. + + This function extracts every fact relevant to the expressions composing + proposition and assumptions. For example, if a predicate containing + ``Abs(x)`` is proposed, then ``Q.zero(Abs(x)) | Q.positive(Abs(x))`` + will be found and passed to SAT solver because ``Q.nonnegative`` is + registered as a fact for ``Abs``. + + Proposition is evaluated to ``True`` or ``False`` if the truth value can be + determined. If not, ``None`` is returned. + + Parameters + ========== + + proposition : Any boolean expression. + Proposition which will be evaluated to boolean value. + + assumptions : Any boolean expression, optional. + Local assumptions to evaluate the *proposition*. + + context : AssumptionsContext, optional. + Default assumptions to evaluate the *proposition*. By default, + this is ``sympy.assumptions.global_assumptions`` variable. + + use_known_facts : bool, optional. + If ``True``, facts from ``sympy.assumptions.ask_generated`` + module are passed to SAT solver as well. + + iterations : int, optional. + Number of times that relevant facts are recursively extracted. + Default is infinite times until no new fact is found. + + Returns + ======= + + ``True``, ``False``, or ``None`` + + Examples + ======== + + >>> from sympy import Abs, Q + >>> from sympy.assumptions.satask import satask + >>> from sympy.abc import x + >>> satask(Q.zero(Abs(x)), Q.zero(x)) + True + + """ + props = CNF.from_prop(proposition) + _props = CNF.from_prop(~proposition) + + assumptions = CNF.from_prop(assumptions) + + context_cnf = CNF() + if context: + context_cnf = context_cnf.extend(context) + + sat = get_all_relevant_facts(props, assumptions, context_cnf, + use_known_facts=use_known_facts, iterations=iterations) + sat.add_from_cnf(assumptions) + if context: + sat.add_from_cnf(context_cnf) + + return check_satisfiability(props, _props, sat) + + +def check_satisfiability(prop, _prop, factbase): + sat_true = factbase.copy() + sat_false = factbase.copy() + sat_true.add_from_cnf(prop) + sat_false.add_from_cnf(_prop) + can_be_true = satisfiable(sat_true) + can_be_false = satisfiable(sat_false) + + if can_be_true and can_be_false: + return None + + if can_be_true and not can_be_false: + return True + + if not can_be_true and can_be_false: + return False + + if not can_be_true and not can_be_false: + # TODO: Run additional checks to see which combination of the + # assumptions, global_assumptions, and relevant_facts are + # inconsistent. + raise ValueError("Inconsistent assumptions") + + +def extract_predargs(proposition, assumptions=None, context=None): + """ + Extract every expression in the argument of predicates from *proposition*, + *assumptions* and *context*. + + Parameters + ========== + + proposition : sympy.assumptions.cnf.CNF + + assumptions : sympy.assumptions.cnf.CNF, optional. + + context : sympy.assumptions.cnf.CNF, optional. + CNF generated from assumptions context. + + Examples + ======== + + >>> from sympy import Q, Abs + >>> from sympy.assumptions.cnf import CNF + >>> from sympy.assumptions.satask import extract_predargs + >>> from sympy.abc import x, y + >>> props = CNF.from_prop(Q.zero(Abs(x*y))) + >>> assump = CNF.from_prop(Q.zero(x) & Q.zero(y)) + >>> extract_predargs(props, assump) + {x, y, Abs(x*y)} + + """ + req_keys = find_symbols(proposition) + keys = proposition.all_predicates() + # XXX: We need this since True/False are not Basic + lkeys = set() + if assumptions: + lkeys |= assumptions.all_predicates() + if context: + lkeys |= context.all_predicates() + + lkeys = lkeys - {S.true, S.false} + tmp_keys = None + while tmp_keys != set(): + tmp = set() + for l in lkeys: + syms = find_symbols(l) + if (syms & req_keys) != set(): + tmp |= syms + tmp_keys = tmp - req_keys + req_keys |= tmp_keys + keys |= {l for l in lkeys if find_symbols(l) & req_keys != set()} + + exprs = set() + for key in keys: + if isinstance(key, AppliedPredicate): + exprs |= set(key.arguments) + else: + exprs.add(key) + return exprs + +def find_symbols(pred): + """ + Find every :obj:`~.Symbol` in *pred*. + + Parameters + ========== + + pred : sympy.assumptions.cnf.CNF, or any Expr. + + """ + if isinstance(pred, CNF): + symbols = set() + for a in pred.all_predicates(): + symbols |= find_symbols(a) + return symbols + return pred.atoms(Symbol) + + +def get_relevant_clsfacts(exprs, relevant_facts=None): + """ + Extract relevant facts from the items in *exprs*. Facts are defined in + ``assumptions.sathandlers`` module. + + This function is recursively called by ``get_all_relevant_facts()``. + + Parameters + ========== + + exprs : set + Expressions whose relevant facts are searched. + + relevant_facts : sympy.assumptions.cnf.CNF, optional. + Pre-discovered relevant facts. + + Returns + ======= + + exprs : set + Candidates for next relevant fact searching. + + relevant_facts : sympy.assumptions.cnf.CNF + Updated relevant facts. + + Examples + ======== + + Here, we will see how facts relevant to ``Abs(x*y)`` are recursively + extracted. On the first run, set containing the expression is passed + without pre-discovered relevant facts. The result is a set containing + candidates for next run, and ``CNF()`` instance containing facts + which are relevant to ``Abs`` and its argument. + + >>> from sympy import Abs + >>> from sympy.assumptions.satask import get_relevant_clsfacts + >>> from sympy.abc import x, y + >>> exprs = {Abs(x*y)} + >>> exprs, facts = get_relevant_clsfacts(exprs) + >>> exprs + {x*y} + >>> facts.clauses #doctest: +SKIP + {frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}), + frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}), + frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}), + frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}), + frozenset({Literal(Q.even(Abs(x*y)), False), + Literal(Q.odd(Abs(x*y)), False), + Literal(Q.odd(x*y), True)}), + frozenset({Literal(Q.even(Abs(x*y)), False), + Literal(Q.even(x*y), True), + Literal(Q.odd(Abs(x*y)), False)}), + frozenset({Literal(Q.positive(Abs(x*y)), False), + Literal(Q.zero(Abs(x*y)), False)})} + + We pass the first run's results to the second run, and get the expressions + for next run and updated facts. + + >>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts) + >>> exprs + {x, y} + + On final run, no more candidate is returned thus we know that all + relevant facts are successfully retrieved. + + >>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts) + >>> exprs + set() + + """ + if not relevant_facts: + relevant_facts = CNF() + + newexprs = set() + for expr in exprs: + for fact in class_fact_registry(expr): + newfact = CNF.to_CNF(fact) + relevant_facts = relevant_facts._and(newfact) + for key in newfact.all_predicates(): + if isinstance(key, AppliedPredicate): + newexprs |= set(key.arguments) + + return newexprs - exprs, relevant_facts + + +def get_all_relevant_facts(proposition, assumptions, context, + use_known_facts=True, iterations=oo): + """ + Extract all relevant facts from *proposition* and *assumptions*. + + This function extracts the facts by recursively calling + ``get_relevant_clsfacts()``. Extracted facts are converted to + ``EncodedCNF`` and returned. + + Parameters + ========== + + proposition : sympy.assumptions.cnf.CNF + CNF generated from proposition expression. + + assumptions : sympy.assumptions.cnf.CNF + CNF generated from assumption expression. + + context : sympy.assumptions.cnf.CNF + CNF generated from assumptions context. + + use_known_facts : bool, optional. + If ``True``, facts from ``sympy.assumptions.ask_generated`` + module are encoded as well. + + iterations : int, optional. + Number of times that relevant facts are recursively extracted. + Default is infinite times until no new fact is found. + + Returns + ======= + + sympy.assumptions.cnf.EncodedCNF + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.cnf import CNF + >>> from sympy.assumptions.satask import get_all_relevant_facts + >>> from sympy.abc import x, y + >>> props = CNF.from_prop(Q.nonzero(x*y)) + >>> assump = CNF.from_prop(Q.nonzero(x)) + >>> context = CNF.from_prop(Q.nonzero(y)) + >>> get_all_relevant_facts(props, assump, context) #doctest: +SKIP + + + """ + # The relevant facts might introduce new keys, e.g., Q.zero(x*y) will + # introduce the keys Q.zero(x) and Q.zero(y), so we need to run it until + # we stop getting new things. Hopefully this strategy won't lead to an + # infinite loop in the future. + i = 0 + relevant_facts = CNF() + all_exprs = set() + while True: + if i == 0: + exprs = extract_predargs(proposition, assumptions, context) + all_exprs |= exprs + exprs, relevant_facts = get_relevant_clsfacts(exprs, relevant_facts) + i += 1 + if i >= iterations: + break + if not exprs: + break + + if use_known_facts: + known_facts_CNF = CNF() + + if any(expr.kind == MatrixKind(NumberKind) for expr in all_exprs): + known_facts_CNF.add_clauses(get_all_known_matrix_facts()) + # check for undefinedKind since kind system isn't fully implemented + if any(((expr.kind == NumberKind) or (expr.kind == UndefinedKind)) for expr in all_exprs): + known_facts_CNF.add_clauses(get_all_known_number_facts()) + + kf_encoded = EncodedCNF() + kf_encoded.from_cnf(known_facts_CNF) + + def translate_literal(lit, delta): + if lit > 0: + return lit + delta + else: + return lit - delta + + def translate_data(data, delta): + return [{translate_literal(i, delta) for i in clause} for clause in data] + data = [] + symbols = [] + n_lit = len(kf_encoded.symbols) + for i, expr in enumerate(all_exprs): + symbols += [pred(expr) for pred in kf_encoded.symbols] + data += translate_data(kf_encoded.data, i * n_lit) + + encoding = dict(list(zip(symbols, range(1, len(symbols)+1)))) + ctx = EncodedCNF(data, encoding) + else: + ctx = EncodedCNF() + + ctx.add_from_cnf(relevant_facts) + + return ctx diff --git a/phivenv/Lib/site-packages/sympy/assumptions/sathandlers.py b/phivenv/Lib/site-packages/sympy/assumptions/sathandlers.py new file mode 100644 index 0000000000000000000000000000000000000000..a11199eb0e547187ab280c18196c0259c178e004 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/sathandlers.py @@ -0,0 +1,322 @@ +from collections import defaultdict + +from sympy.assumptions.ask import Q +from sympy.core import (Add, Mul, Pow, Number, NumberSymbol, Symbol) +from sympy.core.numbers import ImaginaryUnit +from sympy.functions.elementary.complexes import Abs +from sympy.logic.boolalg import (Equivalent, And, Or, Implies) +from sympy.matrices.expressions import MatMul + +# APIs here may be subject to change + + +### Helper functions ### + +def allargs(symbol, fact, expr): + """ + Apply all arguments of the expression to the fact structure. + + Parameters + ========== + + symbol : Symbol + A placeholder symbol. + + fact : Boolean + Resulting ``Boolean`` expression. + + expr : Expr + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.sathandlers import allargs + >>> from sympy.abc import x, y + >>> allargs(x, Q.negative(x) | Q.positive(x), x*y) + (Q.negative(x) | Q.positive(x)) & (Q.negative(y) | Q.positive(y)) + + """ + return And(*[fact.subs(symbol, arg) for arg in expr.args]) + + +def anyarg(symbol, fact, expr): + """ + Apply any argument of the expression to the fact structure. + + Parameters + ========== + + symbol : Symbol + A placeholder symbol. + + fact : Boolean + Resulting ``Boolean`` expression. + + expr : Expr + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.sathandlers import anyarg + >>> from sympy.abc import x, y + >>> anyarg(x, Q.negative(x) & Q.positive(x), x*y) + (Q.negative(x) & Q.positive(x)) | (Q.negative(y) & Q.positive(y)) + + """ + return Or(*[fact.subs(symbol, arg) for arg in expr.args]) + + +def exactlyonearg(symbol, fact, expr): + """ + Apply exactly one argument of the expression to the fact structure. + + Parameters + ========== + + symbol : Symbol + A placeholder symbol. + + fact : Boolean + Resulting ``Boolean`` expression. + + expr : Expr + + Examples + ======== + + >>> from sympy import Q + >>> from sympy.assumptions.sathandlers import exactlyonearg + >>> from sympy.abc import x, y + >>> exactlyonearg(x, Q.positive(x), x*y) + (Q.positive(x) & ~Q.positive(y)) | (Q.positive(y) & ~Q.positive(x)) + + """ + pred_args = [fact.subs(symbol, arg) for arg in expr.args] + res = Or(*[And(pred_args[i], *[~lit for lit in pred_args[:i] + + pred_args[i+1:]]) for i in range(len(pred_args))]) + return res + + +### Fact registry ### + +class ClassFactRegistry: + """ + Register handlers against classes. + + Explanation + =========== + + ``register`` method registers the handler function for a class. Here, + handler function should return a single fact. ``multiregister`` method + registers the handler function for multiple classes. Here, handler function + should return a container of multiple facts. + + ``registry(expr)`` returns a set of facts for *expr*. + + Examples + ======== + + Here, we register the facts for ``Abs``. + + >>> from sympy import Abs, Equivalent, Q + >>> from sympy.assumptions.sathandlers import ClassFactRegistry + >>> reg = ClassFactRegistry() + >>> @reg.register(Abs) + ... def f1(expr): + ... return Q.nonnegative(expr) + >>> @reg.register(Abs) + ... def f2(expr): + ... arg = expr.args[0] + ... return Equivalent(~Q.zero(arg), ~Q.zero(expr)) + + Calling the registry with expression returns the defined facts for the + expression. + + >>> from sympy.abc import x + >>> reg(Abs(x)) + {Q.nonnegative(Abs(x)), Equivalent(~Q.zero(x), ~Q.zero(Abs(x)))} + + Multiple facts can be registered at once by ``multiregister`` method. + + >>> reg2 = ClassFactRegistry() + >>> @reg2.multiregister(Abs) + ... def _(expr): + ... arg = expr.args[0] + ... return [Q.even(arg) >> Q.even(expr), Q.odd(arg) >> Q.odd(expr)] + >>> reg2(Abs(x)) + {Implies(Q.even(x), Q.even(Abs(x))), Implies(Q.odd(x), Q.odd(Abs(x)))} + + """ + def __init__(self): + self.singlefacts = defaultdict(frozenset) + self.multifacts = defaultdict(frozenset) + + def register(self, cls): + def _(func): + self.singlefacts[cls] |= {func} + return func + return _ + + def multiregister(self, *classes): + def _(func): + for cls in classes: + self.multifacts[cls] |= {func} + return func + return _ + + def __getitem__(self, key): + ret1 = self.singlefacts[key] + for k in self.singlefacts: + if issubclass(key, k): + ret1 |= self.singlefacts[k] + + ret2 = self.multifacts[key] + for k in self.multifacts: + if issubclass(key, k): + ret2 |= self.multifacts[k] + + return ret1, ret2 + + def __call__(self, expr): + ret = set() + + handlers1, handlers2 = self[type(expr)] + + ret.update(h(expr) for h in handlers1) + for h in handlers2: + ret.update(h(expr)) + return ret + +class_fact_registry = ClassFactRegistry() + + + +### Class fact registration ### + +x = Symbol('x') + +## Abs ## + +@class_fact_registry.multiregister(Abs) +def _(expr): + arg = expr.args[0] + return [Q.nonnegative(expr), + Equivalent(~Q.zero(arg), ~Q.zero(expr)), + Q.even(arg) >> Q.even(expr), + Q.odd(arg) >> Q.odd(expr), + Q.integer(arg) >> Q.integer(expr), + ] + + +### Add ## + +@class_fact_registry.multiregister(Add) +def _(expr): + return [allargs(x, Q.positive(x), expr) >> Q.positive(expr), + allargs(x, Q.negative(x), expr) >> Q.negative(expr), + allargs(x, Q.real(x), expr) >> Q.real(expr), + allargs(x, Q.rational(x), expr) >> Q.rational(expr), + allargs(x, Q.integer(x), expr) >> Q.integer(expr), + exactlyonearg(x, ~Q.integer(x), expr) >> ~Q.integer(expr), + ] + +@class_fact_registry.register(Add) +def _(expr): + allargs_real = allargs(x, Q.real(x), expr) + onearg_irrational = exactlyonearg(x, Q.irrational(x), expr) + return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr))) + + +### Mul ### + +@class_fact_registry.multiregister(Mul) +def _(expr): + return [Equivalent(Q.zero(expr), anyarg(x, Q.zero(x), expr)), + allargs(x, Q.positive(x), expr) >> Q.positive(expr), + allargs(x, Q.real(x), expr) >> Q.real(expr), + allargs(x, Q.rational(x), expr) >> Q.rational(expr), + allargs(x, Q.integer(x), expr) >> Q.integer(expr), + exactlyonearg(x, ~Q.rational(x), expr) >> ~Q.integer(expr), + allargs(x, Q.commutative(x), expr) >> Q.commutative(expr), + ] + +@class_fact_registry.register(Mul) +def _(expr): + # Implicitly assumes Mul has more than one arg + # Would be allargs(x, Q.prime(x) | Q.composite(x)) except 1 is composite + # More advanced prime assumptions will require inequalities, as 1 provides + # a corner case. + allargs_prime = allargs(x, Q.prime(x), expr) + return Implies(allargs_prime, ~Q.prime(expr)) + +@class_fact_registry.register(Mul) +def _(expr): + # General Case: Odd number of imaginary args implies mul is imaginary(To be implemented) + allargs_imag_or_real = allargs(x, Q.imaginary(x) | Q.real(x), expr) + onearg_imaginary = exactlyonearg(x, Q.imaginary(x), expr) + return Implies(allargs_imag_or_real, Implies(onearg_imaginary, Q.imaginary(expr))) + +@class_fact_registry.register(Mul) +def _(expr): + allargs_real = allargs(x, Q.real(x), expr) + onearg_irrational = exactlyonearg(x, Q.irrational(x), expr) + return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr))) + +@class_fact_registry.register(Mul) +def _(expr): + # Including the integer qualification means we don't need to add any facts + # for odd, since the assumptions already know that every integer is + # exactly one of even or odd. + allargs_integer = allargs(x, Q.integer(x), expr) + anyarg_even = anyarg(x, Q.even(x), expr) + return Implies(allargs_integer, Equivalent(anyarg_even, Q.even(expr))) + + +### MatMul ### + +@class_fact_registry.register(MatMul) +def _(expr): + allargs_square = allargs(x, Q.square(x), expr) + allargs_invertible = allargs(x, Q.invertible(x), expr) + return Implies(allargs_square, Equivalent(Q.invertible(expr), allargs_invertible)) + + +### Pow ### + +@class_fact_registry.multiregister(Pow) +def _(expr): + base, exp = expr.base, expr.exp + return [ + (Q.real(base) & Q.even(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr), + (Q.nonnegative(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr), + (Q.nonpositive(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonpositive(expr), + Equivalent(Q.zero(expr), Q.zero(base) & Q.positive(exp)) + ] + + +### Numbers ### + +_old_assump_getters = { + Q.positive: lambda o: o.is_positive, + Q.zero: lambda o: o.is_zero, + Q.negative: lambda o: o.is_negative, + Q.rational: lambda o: o.is_rational, + Q.irrational: lambda o: o.is_irrational, + Q.even: lambda o: o.is_even, + Q.odd: lambda o: o.is_odd, + Q.imaginary: lambda o: o.is_imaginary, + Q.prime: lambda o: o.is_prime, + Q.composite: lambda o: o.is_composite, +} + +@class_fact_registry.multiregister(Number, NumberSymbol, ImaginaryUnit) +def _(expr): + ret = [] + for p, getter in _old_assump_getters.items(): + pred = p(expr) + prop = getter(expr) + if prop is not None: + ret.append(Equivalent(pred, prop)) + return ret diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__init__.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1165384d7fe53c3d91bc6404918b326a09586b11 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_assumptions_2.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_assumptions_2.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b107c1007866edd692f7a775eb35c1e4461d22e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_assumptions_2.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_context.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_context.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6da56de102944b80bff6620ea2b7e91e792b5e9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_context.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_matrices.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_matrices.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e412b32072ccd8e1dc1360e999a958d2b9fdd3de Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_matrices.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_query.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_query.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bba41eba0bcd807ee35a8124e624a82b84e7b48c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_query.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_refine.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_refine.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe7130e2aa7abf48dcd6d3e136bee49c3d6b6a5a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_refine.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_rel_queries.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_rel_queries.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca4243f650dbedf67b62b3fefd4066cf26371100 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_rel_queries.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_satask.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_satask.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22000064c1812749c174a94399eb1e2159576527 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_satask.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_sathandlers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_sathandlers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f42f7ad4446557cec32595d77f48ba4648bdb00 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_sathandlers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_wrapper.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_wrapper.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..017ff8d25e488b5392e84a4e15843a3c2b1546aa Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/assumptions/tests/__pycache__/test_wrapper.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_assumptions_2.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_assumptions_2.py new file mode 100644 index 0000000000000000000000000000000000000000..493fe4a7ed70301754ad2cfe181c5acf30433768 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_assumptions_2.py @@ -0,0 +1,35 @@ +""" +rename this to test_assumptions.py when the old assumptions system is deleted +""" +from sympy.abc import x, y +from sympy.assumptions.assume import global_assumptions +from sympy.assumptions.ask import Q +from sympy.printing import pretty + + +def test_equal(): + """Test for equality""" + assert Q.positive(x) == Q.positive(x) + assert Q.positive(x) != ~Q.positive(x) + assert ~Q.positive(x) == ~Q.positive(x) + + +def test_pretty(): + assert pretty(Q.positive(x)) == "Q.positive(x)" + assert pretty( + {Q.positive, Q.integer}) == "{Q.integer, Q.positive}" + + +def test_global(): + """Test for global assumptions""" + global_assumptions.add(x > 0) + assert (x > 0) in global_assumptions + global_assumptions.remove(x > 0) + assert not (x > 0) in global_assumptions + # same with multiple of assumptions + global_assumptions.add(x > 0, y > 0) + assert (x > 0) in global_assumptions + assert (y > 0) in global_assumptions + global_assumptions.clear() + assert not (x > 0) in global_assumptions + assert not (y > 0) in global_assumptions diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_context.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_context.py new file mode 100644 index 0000000000000000000000000000000000000000..be162f1c69492218ff90ea69492925d7779567a4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_context.py @@ -0,0 +1,39 @@ +from sympy.assumptions import ask, Q +from sympy.assumptions.assume import assuming, global_assumptions +from sympy.abc import x, y + +def test_assuming(): + with assuming(Q.integer(x)): + assert ask(Q.integer(x)) + assert not ask(Q.integer(x)) + +def test_assuming_nested(): + assert not ask(Q.integer(x)) + assert not ask(Q.integer(y)) + with assuming(Q.integer(x)): + assert ask(Q.integer(x)) + assert not ask(Q.integer(y)) + with assuming(Q.integer(y)): + assert ask(Q.integer(x)) + assert ask(Q.integer(y)) + assert ask(Q.integer(x)) + assert not ask(Q.integer(y)) + assert not ask(Q.integer(x)) + assert not ask(Q.integer(y)) + +def test_finally(): + try: + with assuming(Q.integer(x)): + 1/0 + except ZeroDivisionError: + pass + assert not ask(Q.integer(x)) + +def test_remove_safe(): + global_assumptions.add(Q.integer(x)) + with assuming(): + assert ask(Q.integer(x)) + global_assumptions.remove(Q.integer(x)) + assert not ask(Q.integer(x)) + assert ask(Q.integer(x)) + global_assumptions.clear() # for the benefit of other tests diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_matrices.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..8bfa990f080eebe4d6dd5bfdd733ce1a19adf329 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_matrices.py @@ -0,0 +1,283 @@ +from sympy.assumptions.ask import (Q, ask) +from sympy.core.symbol import Symbol +from sympy.matrices.expressions.diagonal import (DiagMatrix, DiagonalMatrix) +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions import (MatrixSymbol, Identity, ZeroMatrix, + OneMatrix, Trace, MatrixSlice, Determinant, BlockMatrix, BlockDiagMatrix) +from sympy.matrices.expressions.factorizations import LofLU +from sympy.testing.pytest import XFAIL + +X = MatrixSymbol('X', 2, 2) +Y = MatrixSymbol('Y', 2, 3) +Z = MatrixSymbol('Z', 2, 2) +A1x1 = MatrixSymbol('A1x1', 1, 1) +B1x1 = MatrixSymbol('B1x1', 1, 1) +C0x0 = MatrixSymbol('C0x0', 0, 0) +V1 = MatrixSymbol('V1', 2, 1) +V2 = MatrixSymbol('V2', 2, 1) + +def test_square(): + assert ask(Q.square(X)) + assert not ask(Q.square(Y)) + assert ask(Q.square(Y*Y.T)) + +def test_invertible(): + assert ask(Q.invertible(X), Q.invertible(X)) + assert ask(Q.invertible(Y)) is False + assert ask(Q.invertible(X*Y), Q.invertible(X)) is False + assert ask(Q.invertible(X*Z), Q.invertible(X)) is None + assert ask(Q.invertible(X*Z), Q.invertible(X) & Q.invertible(Z)) is True + assert ask(Q.invertible(X.T)) is None + assert ask(Q.invertible(X.T), Q.invertible(X)) is True + assert ask(Q.invertible(X.I)) is True + assert ask(Q.invertible(Identity(3))) is True + assert ask(Q.invertible(ZeroMatrix(3, 3))) is False + assert ask(Q.invertible(OneMatrix(1, 1))) is True + assert ask(Q.invertible(OneMatrix(3, 3))) is False + assert ask(Q.invertible(X), Q.fullrank(X) & Q.square(X)) + +def test_singular(): + assert ask(Q.singular(X)) is None + assert ask(Q.singular(X), Q.invertible(X)) is False + assert ask(Q.singular(X), ~Q.invertible(X)) is True + +@XFAIL +def test_invertible_fullrank(): + assert ask(Q.invertible(X), Q.fullrank(X)) is True + + +def test_invertible_BlockMatrix(): + assert ask(Q.invertible(BlockMatrix([Identity(3)]))) == True + assert ask(Q.invertible(BlockMatrix([ZeroMatrix(3, 3)]))) == False + + X = Matrix([[1, 2, 3], [3, 5, 4]]) + Y = Matrix([[4, 2, 7], [2, 3, 5]]) + # non-invertible A block + assert ask(Q.invertible(BlockMatrix([ + [Matrix.ones(3, 3), Y.T], + [X, Matrix.eye(2)], + ]))) == True + # non-invertible B block + assert ask(Q.invertible(BlockMatrix([ + [Y.T, Matrix.ones(3, 3)], + [Matrix.eye(2), X], + ]))) == True + # non-invertible C block + assert ask(Q.invertible(BlockMatrix([ + [X, Matrix.eye(2)], + [Matrix.ones(3, 3), Y.T], + ]))) == True + # non-invertible D block + assert ask(Q.invertible(BlockMatrix([ + [Matrix.eye(2), X], + [Y.T, Matrix.ones(3, 3)], + ]))) == True + + +def test_invertible_BlockDiagMatrix(): + assert ask(Q.invertible(BlockDiagMatrix(Identity(3), Identity(5)))) == True + assert ask(Q.invertible(BlockDiagMatrix(ZeroMatrix(3, 3), Identity(5)))) == False + assert ask(Q.invertible(BlockDiagMatrix(Identity(3), OneMatrix(5, 5)))) == False + + +def test_symmetric(): + assert ask(Q.symmetric(X), Q.symmetric(X)) + assert ask(Q.symmetric(X*Z), Q.symmetric(X)) is None + assert ask(Q.symmetric(X*Z), Q.symmetric(X) & Q.symmetric(Z)) is True + assert ask(Q.symmetric(X + Z), Q.symmetric(X) & Q.symmetric(Z)) is True + assert ask(Q.symmetric(Y)) is False + assert ask(Q.symmetric(Y*Y.T)) is True + assert ask(Q.symmetric(Y.T*X*Y)) is None + assert ask(Q.symmetric(Y.T*X*Y), Q.symmetric(X)) is True + assert ask(Q.symmetric(X**10), Q.symmetric(X)) is True + assert ask(Q.symmetric(A1x1)) is True + assert ask(Q.symmetric(A1x1 + B1x1)) is True + assert ask(Q.symmetric(A1x1 * B1x1)) is True + assert ask(Q.symmetric(V1.T*V1)) is True + assert ask(Q.symmetric(V1.T*(V1 + V2))) is True + assert ask(Q.symmetric(V1.T*(V1 + V2) + A1x1)) is True + assert ask(Q.symmetric(MatrixSlice(Y, (0, 1), (1, 2)))) is True + assert ask(Q.symmetric(Identity(3))) is True + assert ask(Q.symmetric(ZeroMatrix(3, 3))) is True + assert ask(Q.symmetric(OneMatrix(3, 3))) is True + +def _test_orthogonal_unitary(predicate): + assert ask(predicate(X), predicate(X)) + assert ask(predicate(X.T), predicate(X)) is True + assert ask(predicate(X.I), predicate(X)) is True + assert ask(predicate(X**2), predicate(X)) + assert ask(predicate(Y)) is False + assert ask(predicate(X)) is None + assert ask(predicate(X), ~Q.invertible(X)) is False + assert ask(predicate(X*Z*X), predicate(X) & predicate(Z)) is True + assert ask(predicate(Identity(3))) is True + assert ask(predicate(ZeroMatrix(3, 3))) is False + assert ask(Q.invertible(X), predicate(X)) + assert not ask(predicate(X + Z), predicate(X) & predicate(Z)) + +def test_orthogonal(): + _test_orthogonal_unitary(Q.orthogonal) + +def test_unitary(): + _test_orthogonal_unitary(Q.unitary) + assert ask(Q.unitary(X), Q.orthogonal(X)) + +def test_fullrank(): + assert ask(Q.fullrank(X), Q.fullrank(X)) + assert ask(Q.fullrank(X**2), Q.fullrank(X)) + assert ask(Q.fullrank(X.T), Q.fullrank(X)) is True + assert ask(Q.fullrank(X)) is None + assert ask(Q.fullrank(Y)) is None + assert ask(Q.fullrank(X*Z), Q.fullrank(X) & Q.fullrank(Z)) is True + assert ask(Q.fullrank(Identity(3))) is True + assert ask(Q.fullrank(ZeroMatrix(3, 3))) is False + assert ask(Q.fullrank(OneMatrix(1, 1))) is True + assert ask(Q.fullrank(OneMatrix(3, 3))) is False + assert ask(Q.invertible(X), ~Q.fullrank(X)) == False + + +def test_positive_definite(): + assert ask(Q.positive_definite(X), Q.positive_definite(X)) + assert ask(Q.positive_definite(X.T), Q.positive_definite(X)) is True + assert ask(Q.positive_definite(X.I), Q.positive_definite(X)) is True + assert ask(Q.positive_definite(Y)) is False + assert ask(Q.positive_definite(X)) is None + assert ask(Q.positive_definite(X**3), Q.positive_definite(X)) + assert ask(Q.positive_definite(X*Z*X), + Q.positive_definite(X) & Q.positive_definite(Z)) is True + assert ask(Q.positive_definite(X), Q.orthogonal(X)) + assert ask(Q.positive_definite(Y.T*X*Y), + Q.positive_definite(X) & Q.fullrank(Y)) is True + assert not ask(Q.positive_definite(Y.T*X*Y), Q.positive_definite(X)) + assert ask(Q.positive_definite(Identity(3))) is True + assert ask(Q.positive_definite(ZeroMatrix(3, 3))) is False + assert ask(Q.positive_definite(OneMatrix(1, 1))) is True + assert ask(Q.positive_definite(OneMatrix(3, 3))) is False + assert ask(Q.positive_definite(X + Z), Q.positive_definite(X) & + Q.positive_definite(Z)) is True + assert not ask(Q.positive_definite(-X), Q.positive_definite(X)) + assert ask(Q.positive(X[1, 1]), Q.positive_definite(X)) + +def test_triangular(): + assert ask(Q.upper_triangular(X + Z.T + Identity(2)), Q.upper_triangular(X) & + Q.lower_triangular(Z)) is True + assert ask(Q.upper_triangular(X*Z.T), Q.upper_triangular(X) & + Q.lower_triangular(Z)) is True + assert ask(Q.lower_triangular(Identity(3))) is True + assert ask(Q.lower_triangular(ZeroMatrix(3, 3))) is True + assert ask(Q.upper_triangular(ZeroMatrix(3, 3))) is True + assert ask(Q.lower_triangular(OneMatrix(1, 1))) is True + assert ask(Q.upper_triangular(OneMatrix(1, 1))) is True + assert ask(Q.lower_triangular(OneMatrix(3, 3))) is False + assert ask(Q.upper_triangular(OneMatrix(3, 3))) is False + assert ask(Q.triangular(X), Q.unit_triangular(X)) + assert ask(Q.upper_triangular(X**3), Q.upper_triangular(X)) + assert ask(Q.lower_triangular(X**3), Q.lower_triangular(X)) + + +def test_diagonal(): + assert ask(Q.diagonal(X + Z.T + Identity(2)), Q.diagonal(X) & + Q.diagonal(Z)) is True + assert ask(Q.diagonal(ZeroMatrix(3, 3))) + assert ask(Q.diagonal(OneMatrix(1, 1))) is True + assert ask(Q.diagonal(OneMatrix(3, 3))) is False + assert ask(Q.lower_triangular(X) & Q.upper_triangular(X), Q.diagonal(X)) + assert ask(Q.diagonal(X), Q.lower_triangular(X) & Q.upper_triangular(X)) + assert ask(Q.symmetric(X), Q.diagonal(X)) + assert ask(Q.triangular(X), Q.diagonal(X)) + assert ask(Q.diagonal(C0x0)) + assert ask(Q.diagonal(A1x1)) + assert ask(Q.diagonal(A1x1 + B1x1)) + assert ask(Q.diagonal(A1x1*B1x1)) + assert ask(Q.diagonal(V1.T*V2)) + assert ask(Q.diagonal(V1.T*(X + Z)*V1)) + assert ask(Q.diagonal(MatrixSlice(Y, (0, 1), (1, 2)))) is True + assert ask(Q.diagonal(V1.T*(V1 + V2))) is True + assert ask(Q.diagonal(X**3), Q.diagonal(X)) + assert ask(Q.diagonal(Identity(3))) + assert ask(Q.diagonal(DiagMatrix(V1))) + assert ask(Q.diagonal(DiagonalMatrix(X))) + + +def test_non_atoms(): + assert ask(Q.real(Trace(X)), Q.positive(Trace(X))) + +@XFAIL +def test_non_trivial_implies(): + X = MatrixSymbol('X', 3, 3) + Y = MatrixSymbol('Y', 3, 3) + assert ask(Q.lower_triangular(X+Y), Q.lower_triangular(X) & + Q.lower_triangular(Y)) is True + assert ask(Q.triangular(X), Q.lower_triangular(X)) is True + assert ask(Q.triangular(X+Y), Q.lower_triangular(X) & + Q.lower_triangular(Y)) is True + +def test_MatrixSlice(): + X = MatrixSymbol('X', 4, 4) + B = MatrixSlice(X, (1, 3), (1, 3)) + C = MatrixSlice(X, (0, 3), (1, 3)) + assert ask(Q.symmetric(B), Q.symmetric(X)) + assert ask(Q.invertible(B), Q.invertible(X)) + assert ask(Q.diagonal(B), Q.diagonal(X)) + assert ask(Q.orthogonal(B), Q.orthogonal(X)) + assert ask(Q.upper_triangular(B), Q.upper_triangular(X)) + + assert not ask(Q.symmetric(C), Q.symmetric(X)) + assert not ask(Q.invertible(C), Q.invertible(X)) + assert not ask(Q.diagonal(C), Q.diagonal(X)) + assert not ask(Q.orthogonal(C), Q.orthogonal(X)) + assert not ask(Q.upper_triangular(C), Q.upper_triangular(X)) + +def test_det_trace_positive(): + X = MatrixSymbol('X', 4, 4) + assert ask(Q.positive(Trace(X)), Q.positive_definite(X)) + assert ask(Q.positive(Determinant(X)), Q.positive_definite(X)) + +def test_field_assumptions(): + X = MatrixSymbol('X', 4, 4) + Y = MatrixSymbol('Y', 4, 4) + assert ask(Q.real_elements(X), Q.real_elements(X)) + assert not ask(Q.integer_elements(X), Q.real_elements(X)) + assert ask(Q.complex_elements(X), Q.real_elements(X)) + assert ask(Q.complex_elements(X**2), Q.real_elements(X)) + assert ask(Q.real_elements(X**2), Q.integer_elements(X)) + assert ask(Q.real_elements(X+Y), Q.real_elements(X)) is None + assert ask(Q.real_elements(X+Y), Q.real_elements(X) & Q.real_elements(Y)) + from sympy.matrices.expressions.hadamard import HadamardProduct + assert ask(Q.real_elements(HadamardProduct(X, Y)), + Q.real_elements(X) & Q.real_elements(Y)) + assert ask(Q.complex_elements(X+Y), Q.real_elements(X) & Q.complex_elements(Y)) + + assert ask(Q.real_elements(X.T), Q.real_elements(X)) + assert ask(Q.real_elements(X.I), Q.real_elements(X) & Q.invertible(X)) + assert ask(Q.real_elements(Trace(X)), Q.real_elements(X)) + assert ask(Q.integer_elements(Determinant(X)), Q.integer_elements(X)) + assert not ask(Q.integer_elements(X.I), Q.integer_elements(X)) + alpha = Symbol('alpha') + assert ask(Q.real_elements(alpha*X), Q.real_elements(X) & Q.real(alpha)) + assert ask(Q.real_elements(LofLU(X)), Q.real_elements(X)) + e = Symbol('e', integer=True, negative=True) + assert ask(Q.real_elements(X**e), Q.real_elements(X) & Q.invertible(X)) + assert ask(Q.real_elements(X**e), Q.real_elements(X)) is None + +def test_matrix_element_sets(): + X = MatrixSymbol('X', 4, 4) + assert ask(Q.real(X[1, 2]), Q.real_elements(X)) + assert ask(Q.integer(X[1, 2]), Q.integer_elements(X)) + assert ask(Q.complex(X[1, 2]), Q.complex_elements(X)) + assert ask(Q.integer_elements(Identity(3))) + assert ask(Q.integer_elements(ZeroMatrix(3, 3))) + assert ask(Q.integer_elements(OneMatrix(3, 3))) + from sympy.matrices.expressions.fourier import DFT + assert ask(Q.complex_elements(DFT(3))) + + +def test_matrix_element_sets_slices_blocks(): + X = MatrixSymbol('X', 4, 4) + assert ask(Q.integer_elements(X[:, 3]), Q.integer_elements(X)) + assert ask(Q.integer_elements(BlockMatrix([[X], [X]])), + Q.integer_elements(X)) + +def test_matrix_element_sets_determinant_trace(): + assert ask(Q.integer(Determinant(X)), Q.integer_elements(X)) + assert ask(Q.integer(Trace(X)), Q.integer_elements(X)) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_query.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_query.py new file mode 100644 index 0000000000000000000000000000000000000000..e1ae1f1e482e5c9d19e3dcce3f37ad46b6821817 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_query.py @@ -0,0 +1,2541 @@ +from sympy.abc import t, w, x, y, z, n, k, m, p, i +from sympy.assumptions import (ask, AssumptionsContext, Q, register_handler, + remove_handler) +from sympy.assumptions.assume import assuming, global_assumptions, Predicate +from sympy.assumptions.cnf import CNF, Literal +from sympy.assumptions.facts import (single_fact_lookup, + get_known_facts, generate_known_facts_dict, get_known_facts_keys) +from sympy.assumptions.handlers import AskHandler +from sympy.assumptions.ask_generated import (get_all_known_facts, + get_known_facts_dict) +from sympy.core.add import Add +from sympy.core.numbers import (I, Integer, Rational, oo, zoo, pi) +from sympy.core.singleton import S +from sympy.core.power import Pow +from sympy.core.symbol import Str, symbols, Symbol +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (Abs, im, re, sign) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import ( + acos, acot, asin, atan, cos, cot, sin, tan) +from sympy.logic.boolalg import Equivalent, Implies, Xor, And, to_cnf +from sympy.matrices import Matrix, SparseMatrix +from sympy.testing.pytest import (XFAIL, slow, raises, warns_deprecated_sympy, + _both_exp_pow) +import math + + +def test_int_1(): + z = 1 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is True + assert ask(Q.rational(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is True + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_int_11(): + z = 11 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is True + assert ask(Q.rational(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is True + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is True + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_int_12(): + z = 12 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is True + assert ask(Q.rational(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is True + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is True + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_float_1(): + z = 1.0 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is None + assert ask(Q.rational(z)) is None + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is None + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is None + assert ask(Q.odd(z)) is None + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is None + assert ask(Q.composite(z)) is None + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + z = 7.2123 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is None + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is None + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + # test for issue #12168 + assert ask(Q.rational(math.pi)) is None + + +def test_zero_0(): + z = Integer(0) + assert ask(Q.nonzero(z)) is False + assert ask(Q.zero(z)) is True + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is True + assert ask(Q.rational(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is False + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is True + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is True + + +def test_negativeone(): + z = Integer(-1) + assert ask(Q.nonzero(z)) is True + assert ask(Q.zero(z)) is False + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is True + assert ask(Q.rational(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is False + assert ask(Q.negative(z)) is True + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is True + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_infinity(): + assert ask(Q.commutative(oo)) is True + assert ask(Q.integer(oo)) is False + assert ask(Q.rational(oo)) is False + assert ask(Q.algebraic(oo)) is False + assert ask(Q.real(oo)) is False + assert ask(Q.extended_real(oo)) is True + assert ask(Q.complex(oo)) is False + assert ask(Q.irrational(oo)) is False + assert ask(Q.imaginary(oo)) is False + assert ask(Q.positive(oo)) is False + assert ask(Q.extended_positive(oo)) is True + assert ask(Q.negative(oo)) is False + assert ask(Q.even(oo)) is False + assert ask(Q.odd(oo)) is False + assert ask(Q.finite(oo)) is False + assert ask(Q.infinite(oo)) is True + assert ask(Q.prime(oo)) is False + assert ask(Q.composite(oo)) is False + assert ask(Q.hermitian(oo)) is False + assert ask(Q.antihermitian(oo)) is False + assert ask(Q.positive_infinite(oo)) is True + assert ask(Q.negative_infinite(oo)) is False + + +def test_neg_infinity(): + mm = S.NegativeInfinity + assert ask(Q.commutative(mm)) is True + assert ask(Q.integer(mm)) is False + assert ask(Q.rational(mm)) is False + assert ask(Q.algebraic(mm)) is False + assert ask(Q.real(mm)) is False + assert ask(Q.extended_real(mm)) is True + assert ask(Q.complex(mm)) is False + assert ask(Q.irrational(mm)) is False + assert ask(Q.imaginary(mm)) is False + assert ask(Q.positive(mm)) is False + assert ask(Q.negative(mm)) is False + assert ask(Q.extended_negative(mm)) is True + assert ask(Q.even(mm)) is False + assert ask(Q.odd(mm)) is False + assert ask(Q.finite(mm)) is False + assert ask(Q.infinite(oo)) is True + assert ask(Q.prime(mm)) is False + assert ask(Q.composite(mm)) is False + assert ask(Q.hermitian(mm)) is False + assert ask(Q.antihermitian(mm)) is False + assert ask(Q.positive_infinite(-oo)) is False + assert ask(Q.negative_infinite(-oo)) is True + + +def test_complex_infinity(): + assert ask(Q.commutative(zoo)) is True + assert ask(Q.integer(zoo)) is False + assert ask(Q.rational(zoo)) is False + assert ask(Q.algebraic(zoo)) is False + assert ask(Q.real(zoo)) is False + assert ask(Q.extended_real(zoo)) is False + assert ask(Q.complex(zoo)) is False + assert ask(Q.irrational(zoo)) is False + assert ask(Q.imaginary(zoo)) is False + assert ask(Q.positive(zoo)) is False + assert ask(Q.negative(zoo)) is False + assert ask(Q.zero(zoo)) is False + assert ask(Q.nonzero(zoo)) is False + assert ask(Q.even(zoo)) is False + assert ask(Q.odd(zoo)) is False + assert ask(Q.finite(zoo)) is False + assert ask(Q.infinite(zoo)) is True + assert ask(Q.prime(zoo)) is False + assert ask(Q.composite(zoo)) is False + assert ask(Q.hermitian(zoo)) is False + assert ask(Q.antihermitian(zoo)) is False + assert ask(Q.positive_infinite(zoo)) is False + assert ask(Q.negative_infinite(zoo)) is False + + +def test_nan(): + nan = S.NaN + assert ask(Q.commutative(nan)) is True + assert ask(Q.integer(nan)) is None + assert ask(Q.rational(nan)) is None + assert ask(Q.algebraic(nan)) is None + assert ask(Q.real(nan)) is None + assert ask(Q.extended_real(nan)) is None + assert ask(Q.complex(nan)) is None + assert ask(Q.irrational(nan)) is None + assert ask(Q.imaginary(nan)) is None + assert ask(Q.positive(nan)) is None + assert ask(Q.nonzero(nan)) is None + assert ask(Q.zero(nan)) is None + assert ask(Q.even(nan)) is None + assert ask(Q.odd(nan)) is None + assert ask(Q.finite(nan)) is None + assert ask(Q.infinite(nan)) is None + assert ask(Q.prime(nan)) is None + assert ask(Q.composite(nan)) is None + assert ask(Q.hermitian(nan)) is None + assert ask(Q.antihermitian(nan)) is None + + +def test_Rational_number(): + r = Rational(3, 4) + assert ask(Q.commutative(r)) is True + assert ask(Q.integer(r)) is False + assert ask(Q.rational(r)) is True + assert ask(Q.real(r)) is True + assert ask(Q.complex(r)) is True + assert ask(Q.irrational(r)) is False + assert ask(Q.imaginary(r)) is False + assert ask(Q.positive(r)) is True + assert ask(Q.negative(r)) is False + assert ask(Q.even(r)) is False + assert ask(Q.odd(r)) is False + assert ask(Q.finite(r)) is True + assert ask(Q.prime(r)) is False + assert ask(Q.composite(r)) is False + assert ask(Q.hermitian(r)) is True + assert ask(Q.antihermitian(r)) is False + + r = Rational(1, 4) + assert ask(Q.positive(r)) is True + assert ask(Q.negative(r)) is False + + r = Rational(5, 4) + assert ask(Q.negative(r)) is False + assert ask(Q.positive(r)) is True + + r = Rational(5, 3) + assert ask(Q.positive(r)) is True + assert ask(Q.negative(r)) is False + + r = Rational(-3, 4) + assert ask(Q.positive(r)) is False + assert ask(Q.negative(r)) is True + + r = Rational(-1, 4) + assert ask(Q.positive(r)) is False + assert ask(Q.negative(r)) is True + + r = Rational(-5, 4) + assert ask(Q.negative(r)) is True + assert ask(Q.positive(r)) is False + + r = Rational(-5, 3) + assert ask(Q.positive(r)) is False + assert ask(Q.negative(r)) is True + + +def test_sqrt_2(): + z = sqrt(2) + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_pi(): + z = S.Pi + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is False + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + z = S.Pi + 1 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is False + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + z = 2*S.Pi + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is False + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + z = S.Pi ** 2 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is False + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + z = (1 + S.Pi) ** 2 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is None + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_E(): + z = S.Exp1 + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is False + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_GoldenRatio(): + z = S.GoldenRatio + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_TribonacciConstant(): + z = S.TribonacciConstant + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is True + assert ask(Q.real(z)) is True + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is True + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is True + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is True + assert ask(Q.antihermitian(z)) is False + + +def test_I(): + z = I + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is True + assert ask(Q.real(z)) is False + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is True + assert ask(Q.positive(z)) is False + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is False + assert ask(Q.antihermitian(z)) is True + + z = 1 + I + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is True + assert ask(Q.real(z)) is False + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is False + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is False + assert ask(Q.antihermitian(z)) is False + + z = I*(1 + I) + assert ask(Q.commutative(z)) is True + assert ask(Q.integer(z)) is False + assert ask(Q.rational(z)) is False + assert ask(Q.algebraic(z)) is True + assert ask(Q.real(z)) is False + assert ask(Q.complex(z)) is True + assert ask(Q.irrational(z)) is False + assert ask(Q.imaginary(z)) is False + assert ask(Q.positive(z)) is False + assert ask(Q.negative(z)) is False + assert ask(Q.even(z)) is False + assert ask(Q.odd(z)) is False + assert ask(Q.finite(z)) is True + assert ask(Q.prime(z)) is False + assert ask(Q.composite(z)) is False + assert ask(Q.hermitian(z)) is False + assert ask(Q.antihermitian(z)) is False + + z = I**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + + z = (-I)**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + + z = (3*I)**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is False + + z = (1)**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + + z = (-1)**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + + z = (1+I)**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is False + + z = (I)**(I+3) + assert ask(Q.imaginary(z)) is True + assert ask(Q.real(z)) is False + + z = (I)**(I+2) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + + z = (I)**(2) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + + z = (I)**(3) + assert ask(Q.imaginary(z)) is True + assert ask(Q.real(z)) is False + + z = (3)**(I) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is False + + z = (I)**(0) + assert ask(Q.imaginary(z)) is False + assert ask(Q.real(z)) is True + +def test_bounded(): + x, y, z = symbols('x,y,z') + a = x + y + x, y = a.args + assert ask(Q.finite(a), Q.positive_infinite(y)) is None + assert ask(Q.finite(x)) is None + assert ask(Q.finite(x), Q.finite(x)) is True + assert ask(Q.finite(x), Q.finite(y)) is None + assert ask(Q.finite(x), Q.complex(x)) is True + assert ask(Q.finite(x), Q.extended_real(x)) is None + + assert ask(Q.finite(x + 1)) is None + assert ask(Q.finite(x + 1), Q.finite(x)) is True + a = x + y + x, y = a.args + # B + B + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y)) is True + assert ask(Q.finite(a), Q.positive(x) & Q.finite(y)) is True + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y)) is True + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y)) is True + assert ask(Q.finite(a), Q.positive(x) & Q.finite(y) + & ~Q.positive(y)) is True + assert ask(Q.finite(a), Q.finite(x) & ~Q.positive(x) + & Q.positive(y)) is True + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) & ~Q.positive(x) + & ~Q.positive(y)) is True + # B + U + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y)) is False + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y)) is False + assert ask(Q.finite(a), Q.finite(x) + & Q.positive_infinite(y)) is False + assert ask(Q.finite(a), Q.positive(x) + & Q.positive_infinite(y)) is False + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y) + & ~Q.positive(y)) is False + assert ask(Q.finite(a), Q.finite(x) & ~Q.positive(x) + & Q.positive_infinite(y)) is False + assert ask(Q.finite(a), Q.finite(x) & ~Q.positive(x) & ~Q.finite(y) + & ~Q.positive(y)) is False + # B + ? + assert ask(Q.finite(a), Q.finite(x)) is None + assert ask(Q.finite(a), Q.positive(x)) is None + assert ask(Q.finite(a), Q.finite(x) + & Q.extended_positive(y)) is None + assert ask(Q.finite(a), Q.positive(x) + & Q.extended_positive(y)) is None + assert ask(Q.finite(a), Q.positive(x) & ~Q.positive(y)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.positive(x) + & Q.extended_positive(y)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.positive(x) + & ~Q.positive(y)) is None + # U + U + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & ~Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.positive_infinite(y)) is False + assert ask(Q.finite(a), Q.positive_infinite(x) & ~Q.finite(y) + & ~Q.extended_positive(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.extended_positive(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & ~Q.extended_positive(x) & ~Q.extended_positive(y)) is False + # U + ? + assert ask(Q.finite(a), ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.extended_positive(x) + & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.extended_positive(x) + & Q.positive_infinite(y)) is False + assert ask(Q.finite(a), Q.extended_positive(x) + & ~Q.finite(y) & ~Q.extended_positive(y)) is None + assert ask(Q.finite(a), ~Q.extended_positive(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), ~Q.extended_positive(x) & ~Q.finite(y) + & ~Q.extended_positive(y)) is False + # ? + ? + assert ask(Q.finite(a)) is None + assert ask(Q.finite(a), Q.extended_positive(x)) is None + assert ask(Q.finite(a), Q.extended_positive(y)) is None + assert ask(Q.finite(a), Q.extended_positive(x) + & Q.extended_positive(y)) is None + assert ask(Q.finite(a), Q.extended_positive(x) + & ~Q.extended_positive(y)) is None + assert ask(Q.finite(a), ~Q.extended_positive(x) + & Q.extended_positive(y)) is None + assert ask(Q.finite(a), ~Q.extended_positive(x) + & ~Q.extended_positive(y)) is None + + x, y, z = symbols('x,y,z') + a = x + y + z + x, y, z = a.args + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.negative(z)) is True + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.finite(z)) is True + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.positive(z)) is True + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.negative(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & Q.finite(z)) is True + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & Q.positive(z)) is True + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.finite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.positive(y) + & Q.positive(z)) is True + assert ask(Q.finite(a), Q.negative(x) & Q.positive(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.positive(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.positive(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.positive(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.extended_positive(y) + & Q.finite(y)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.negative_infinite(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.negative_infinite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.negative_infinite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.negative_infinite(y) + & Q.extended_negative(z)) is False + assert ask(Q.finite(a), Q.negative(x) + & Q.negative_infinite(y)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.negative_infinite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x) & ~Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.negative(x) & ~Q.finite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.negative(x) & ~Q.finite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.negative(x) & ~Q.finite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.positive_infinite(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.positive_infinite(y) + & Q.negative_infinite(z)) is None + assert ask(Q.finite(a), Q.negative(x) & + Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.positive_infinite(y) + & Q.extended_positive(z)) is False + assert ask(Q.finite(a), Q.negative(x) & Q.extended_negative(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.extended_negative(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x)) is None + assert ask(Q.finite(a), Q.negative(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative(x) & Q.extended_positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.finite(z)) is True + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.positive(z)) is True + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y) + & Q.positive(z)) is True + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.negative_infinite(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.negative_infinite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.negative_infinite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.negative_infinite(y) + & Q.extended_negative(z)) is False + assert ask(Q.finite(a), Q.finite(x) + & Q.negative_infinite(y)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.negative_infinite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.positive_infinite(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.positive_infinite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.finite(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.positive_infinite(y) + & Q.extended_positive(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.extended_negative(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.finite(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.extended_negative(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x)) is None + assert ask(Q.finite(a), Q.finite(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.extended_positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y) + & Q.positive(z)) is True + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.negative_infinite(y) + & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.positive(x) & Q.negative_infinite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.negative_infinite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.negative_infinite(y) + & Q.extended_negative(z)) is False + assert ask(Q.finite(a), Q.positive(x) + & Q.negative_infinite(y)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.negative_infinite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.positive(x) & ~Q.finite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.positive_infinite(y) + & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.positive(x) & Q.positive_infinite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.positive(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.positive_infinite(y) + & Q.extended_positive(z)) is False + assert ask(Q.finite(a), Q.positive(x) & Q.extended_negative(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.positive(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.extended_negative(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive(x)) is None + assert ask(Q.finite(a), Q.positive(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive(x) & Q.extended_positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.negative_infinite(y) & Q.negative_infinite(z)) is False + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.negative_infinite(y) & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.negative_infinite(y)& Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.negative_infinite(y) & Q.extended_negative(z)) is False + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.negative_infinite(y)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.negative_infinite(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & ~Q.finite(y) & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & ~Q.finite(y) & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & ~Q.finite(y) & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & ~Q.finite(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.positive_infinite(y) & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.positive_infinite(y) & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.positive_infinite(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.extended_negative(y) & Q.extended_negative(z)) is False + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.extended_negative(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.negative_infinite(x) + & Q.extended_positive(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.positive_infinite(z) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.positive_infinite(y) + & Q.positive_infinite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.positive_infinite(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.positive_infinite(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.extended_negative(y) + & Q.extended_negative(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.extended_negative(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), ~Q.finite(x)) is None + assert ask(Q.finite(a), ~Q.finite(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.extended_positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.positive_infinite(y) & Q.positive_infinite(z)) is False + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.positive_infinite(y) & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.positive_infinite(y)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.positive_infinite(y) & Q.extended_positive(z)) is False + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.extended_negative(y) & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.extended_negative(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive_infinite(x)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.positive_infinite(x) + & Q.extended_positive(y) & Q.extended_positive(z)) is False + assert ask(Q.finite(a), Q.extended_negative(x) + & Q.extended_negative(y) & Q.extended_negative(z)) is None + assert ask(Q.finite(a), Q.extended_negative(x) + & Q.extended_negative(y)) is None + assert ask(Q.finite(a), Q.extended_negative(x) + & Q.extended_negative(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.extended_negative(x)) is None + assert ask(Q.finite(a), Q.extended_negative(x) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.extended_negative(x) + & Q.extended_positive(y) & Q.extended_positive(z)) is None + assert ask(Q.finite(a)) is None + assert ask(Q.finite(a), Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.extended_positive(y) + & Q.extended_positive(z)) is None + assert ask(Q.finite(a), Q.extended_positive(x) + & Q.extended_positive(y) & Q.extended_positive(z)) is None + + assert ask(Q.finite(2*x)) is None + assert ask(Q.finite(2*x), Q.finite(x)) is True + + x, y, z = symbols('x,y,z') + a = x*y + x, y = a.args + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y)) is True + assert ask(Q.finite(a), Q.finite(x) & ~Q.zero(x) & ~Q.finite(y)) is False + assert ask(Q.finite(a), Q.finite(x)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(y) &~Q.zero(y)) is False + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y)) is False + assert ask(Q.finite(a), ~Q.finite(x)) is None + assert ask(Q.finite(a), Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(y)) is None + assert ask(Q.finite(a)) is None + a = x*y*z + x, y, z = a.args + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & Q.finite(z)) is True + assert ask(Q.finite(a), Q.finite(x) & ~Q.zero(x) & Q.finite(y) + & ~Q.zero(y) & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.zero(x) & ~Q.finite(y) + & Q.finite(z) & ~Q.zero(z)) is False + assert ask(Q.finite(a), Q.finite(x) & ~Q.zero(x) & ~Q.finite(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.finite(x) & Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(x)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(y) & ~Q.zero(y) + & Q.finite(z) & ~Q.zero(z)) is False + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.zero(x) & Q.finite(y) + & ~Q.zero(y) & ~Q.finite(z)) is False + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & Q.finite(z) & ~Q.zero(z)) is False + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & ~Q.finite(z)) is False + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x)) is None + assert ask(Q.finite(a), Q.finite(y) & Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(y) & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(y) & Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(y) & ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(y)) is None + assert ask(Q.finite(a), Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(z) & Q.extended_nonzero(x) + & Q.extended_nonzero(y) & Q.extended_nonzero(z)) is None + assert ask(Q.finite(a), Q.extended_nonzero(x) & ~Q.finite(y) + & Q.extended_nonzero(y) & ~Q.finite(z) + & Q.extended_nonzero(z)) is False + + x, y, z = symbols('x,y,z') + assert ask(Q.finite(x**2)) is None + assert ask(Q.finite(2**x)) is None + assert ask(Q.finite(2**x), Q.finite(x)) is True + assert ask(Q.finite(x**x)) is None + assert ask(Q.finite(S.Half ** x)) is None + assert ask(Q.finite(S.Half ** x), Q.extended_positive(x)) is True + assert ask(Q.finite(S.Half ** x), Q.extended_negative(x)) is None + assert ask(Q.finite(2**x), Q.extended_negative(x)) is True + assert ask(Q.finite(sqrt(x))) is None + assert ask(Q.finite(2**x), ~Q.finite(x)) is False + assert ask(Q.finite(x**2), ~Q.finite(x)) is False + + # https://github.com/sympy/sympy/issues/27707 + assert ask(Q.finite(x**y), Q.real(x) & Q.real(y)) is None + assert ask(Q.finite(x**y), Q.real(x) & Q.negative(y)) is None + assert ask(Q.finite(x**y), Q.zero(x) & Q.negative(y)) is False + assert ask(Q.finite(x**y), Q.real(x) & Q.positive(y)) is True + assert ask(Q.finite(x**y), Q.nonzero(x) & Q.real(y)) is True + assert ask(Q.finite(x**y), Q.nonzero(x) & Q.negative(y)) is True + assert ask(Q.finite(x**y), Q.zero(x) & Q.positive(y)) is True + + # sign function + assert ask(Q.finite(sign(x))) is True + assert ask(Q.finite(sign(x)), ~Q.finite(x)) is True + + # exponential functions + assert ask(Q.finite(log(x))) is None + assert ask(Q.finite(log(x)), Q.finite(x)) is None + assert ask(Q.finite(log(x)), ~Q.zero(x)) is True + assert ask(Q.finite(log(x)), Q.infinite(x)) is False + assert ask(Q.finite(log(x)), Q.zero(x)) is False + assert ask(Q.finite(exp(x))) is None + assert ask(Q.finite(exp(x)), Q.finite(x)) is True + assert ask(Q.finite(exp(2))) is True + + # trigonometric functions + assert ask(Q.finite(sin(x))) is True + assert ask(Q.finite(sin(x)), ~Q.finite(x)) is True + assert ask(Q.finite(cos(x))) is True + assert ask(Q.finite(cos(x)), ~Q.finite(x)) is True + assert ask(Q.finite(2*sin(x))) is True + assert ask(Q.finite(sin(x)**2)) is True + assert ask(Q.finite(cos(x)**2)) is True + assert ask(Q.finite(cos(x) + sin(x))) is True + + +def test_unbounded(): + assert ask(Q.infinite(I * oo)) is True + assert ask(Q.infinite(1 + I*oo)) is True + assert ask(Q.infinite(3 * (I * oo))) is True + assert ask(Q.infinite(-I * oo)) is True + assert ask(Q.infinite(1 + zoo)) is True + assert ask(Q.infinite(I * zoo)) is True + assert ask(Q.infinite(x / y), Q.infinite(x) & Q.finite(y) & ~Q.zero(y)) is True + assert ask(Q.infinite(I * oo - I * oo)) is None + assert ask(Q.infinite(x * I * oo)) is None + assert ask(Q.infinite(1 / x), Q.finite(x) & ~Q.zero(x)) is False + assert ask(Q.infinite(1 / (I * oo))) is False + + +def test_issue_27441(): + # https://github.com/sympy/sympy/issues/27441 + assert ask(Q.composite(y), Q.integer(y) & Q.positive(y) & ~Q.prime(y)) is None + + +def test_issue_27447(): + x,y,z = symbols('x y z') + a = x*y + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(y)) is None + + a = x*y*z + assert ask(Q.finite(a), Q.finite(x) & Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y) + & Q.finite(z) ) is None + assert ask(Q.finite(a), Q.finite(x) & ~Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(y) + & Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & Q.finite(y) + & ~Q.finite(z)) is None + assert ask(Q.finite(a), ~Q.finite(x) & ~Q.finite(y) + & Q.finite(z)) is None + + +@XFAIL +def test_issue_27662_xfail(): + assert ask(Q.finite(x*y), ~Q.finite(x) + & Q.zero(y)) is None + + +@XFAIL +def test_bounded_xfail(): + """We need to support relations in ask for this to work""" + assert ask(Q.finite(sin(x)**x)) is True + assert ask(Q.finite(cos(x)**x)) is True + + +def test_commutative(): + """By default objects are Q.commutative that is why it returns True + for both key=True and key=False""" + assert ask(Q.commutative(x)) is True + assert ask(Q.commutative(x), ~Q.commutative(x)) is False + assert ask(Q.commutative(x), Q.complex(x)) is True + assert ask(Q.commutative(x), Q.imaginary(x)) is True + assert ask(Q.commutative(x), Q.real(x)) is True + assert ask(Q.commutative(x), Q.positive(x)) is True + assert ask(Q.commutative(x), ~Q.commutative(y)) is True + + assert ask(Q.commutative(2*x)) is True + assert ask(Q.commutative(2*x), ~Q.commutative(x)) is False + + assert ask(Q.commutative(x + 1)) is True + assert ask(Q.commutative(x + 1), ~Q.commutative(x)) is False + + assert ask(Q.commutative(x**2)) is True + assert ask(Q.commutative(x**2), ~Q.commutative(x)) is False + + assert ask(Q.commutative(log(x))) is True + + +@_both_exp_pow +def test_complex(): + assert ask(Q.complex(x)) is None + assert ask(Q.complex(x), Q.complex(x)) is True + assert ask(Q.complex(x), Q.complex(y)) is None + assert ask(Q.complex(x), ~Q.complex(x)) is False + assert ask(Q.complex(x), Q.real(x)) is True + assert ask(Q.complex(x), ~Q.real(x)) is None + assert ask(Q.complex(x), Q.rational(x)) is True + assert ask(Q.complex(x), Q.irrational(x)) is True + assert ask(Q.complex(x), Q.positive(x)) is True + assert ask(Q.complex(x), Q.imaginary(x)) is True + assert ask(Q.complex(x), Q.algebraic(x)) is True + + # a+b + assert ask(Q.complex(x + 1), Q.complex(x)) is True + assert ask(Q.complex(x + 1), Q.real(x)) is True + assert ask(Q.complex(x + 1), Q.rational(x)) is True + assert ask(Q.complex(x + 1), Q.irrational(x)) is True + assert ask(Q.complex(x + 1), Q.imaginary(x)) is True + assert ask(Q.complex(x + 1), Q.integer(x)) is True + assert ask(Q.complex(x + 1), Q.even(x)) is True + assert ask(Q.complex(x + 1), Q.odd(x)) is True + assert ask(Q.complex(x + y), Q.complex(x) & Q.complex(y)) is True + assert ask(Q.complex(x + y), Q.real(x) & Q.imaginary(y)) is True + + # a*x +b + assert ask(Q.complex(2*x + 1), Q.complex(x)) is True + assert ask(Q.complex(2*x + 1), Q.real(x)) is True + assert ask(Q.complex(2*x + 1), Q.positive(x)) is True + assert ask(Q.complex(2*x + 1), Q.rational(x)) is True + assert ask(Q.complex(2*x + 1), Q.irrational(x)) is True + assert ask(Q.complex(2*x + 1), Q.imaginary(x)) is True + assert ask(Q.complex(2*x + 1), Q.integer(x)) is True + assert ask(Q.complex(2*x + 1), Q.even(x)) is True + assert ask(Q.complex(2*x + 1), Q.odd(x)) is True + + # x**2 + assert ask(Q.complex(x**2), Q.complex(x)) is True + assert ask(Q.complex(x**2), Q.real(x)) is True + assert ask(Q.complex(x**2), Q.positive(x)) is True + assert ask(Q.complex(x**2), Q.rational(x)) is True + assert ask(Q.complex(x**2), Q.irrational(x)) is True + assert ask(Q.complex(x**2), Q.imaginary(x)) is True + assert ask(Q.complex(x**2), Q.integer(x)) is True + assert ask(Q.complex(x**2), Q.even(x)) is True + assert ask(Q.complex(x**2), Q.odd(x)) is True + + # 2**x + assert ask(Q.complex(2**x), Q.complex(x)) is True + assert ask(Q.complex(2**x), Q.real(x)) is True + assert ask(Q.complex(2**x), Q.positive(x)) is True + assert ask(Q.complex(2**x), Q.rational(x)) is True + assert ask(Q.complex(2**x), Q.irrational(x)) is True + assert ask(Q.complex(2**x), Q.imaginary(x)) is True + assert ask(Q.complex(2**x), Q.integer(x)) is True + assert ask(Q.complex(2**x), Q.even(x)) is True + assert ask(Q.complex(2**x), Q.odd(x)) is True + assert ask(Q.complex(x**y), Q.complex(x) & Q.complex(y)) is True + + # trigonometric expressions + assert ask(Q.complex(sin(x))) is True + assert ask(Q.complex(sin(2*x + 1))) is True + assert ask(Q.complex(cos(x))) is True + assert ask(Q.complex(cos(2*x + 1))) is True + + # exponential + assert ask(Q.complex(exp(x))) is True + assert ask(Q.complex(exp(x))) is True + + # Q.complexes + assert ask(Q.complex(Abs(x))) is True + assert ask(Q.complex(re(x))) is True + assert ask(Q.complex(im(x))) is True + + +def test_even_query(): + assert ask(Q.even(x)) is None + assert ask(Q.even(x), Q.integer(x)) is None + assert ask(Q.even(x), ~Q.integer(x)) is False + assert ask(Q.even(x), Q.rational(x)) is None + assert ask(Q.even(x), Q.positive(x)) is None + + assert ask(Q.even(2*x)) is None + assert ask(Q.even(2*x), Q.integer(x)) is True + assert ask(Q.even(2*x), Q.even(x)) is True + assert ask(Q.even(2*x), Q.irrational(x)) is False + assert ask(Q.even(2*x), Q.odd(x)) is True + assert ask(Q.even(2*x), ~Q.integer(x)) is None + assert ask(Q.even(3*x), Q.integer(x)) is None + assert ask(Q.even(3*x), Q.even(x)) is True + assert ask(Q.even(3*x), Q.odd(x)) is False + + assert ask(Q.even(x + 1), Q.odd(x)) is True + assert ask(Q.even(x + 1), Q.even(x)) is False + assert ask(Q.even(x + 2), Q.odd(x)) is False + assert ask(Q.even(x + 2), Q.even(x)) is True + assert ask(Q.even(7 - x), Q.odd(x)) is True + assert ask(Q.even(7 + x), Q.odd(x)) is True + assert ask(Q.even(x + y), Q.odd(x) & Q.odd(y)) is True + assert ask(Q.even(x + y), Q.odd(x) & Q.even(y)) is False + assert ask(Q.even(x + y), Q.even(x) & Q.even(y)) is True + + assert ask(Q.even(2*x + 1), Q.integer(x)) is False + assert ask(Q.even(2*x*y), Q.rational(x) & Q.rational(x)) is None + assert ask(Q.even(2*x*y), Q.irrational(x) & Q.irrational(x)) is None + + assert ask(Q.even(x + y + z), Q.odd(x) & Q.odd(y) & Q.even(z)) is True + assert ask(Q.even(x + y + z + t), + Q.odd(x) & Q.odd(y) & Q.even(z) & Q.integer(t)) is None + + assert ask(Q.even(Abs(x)), Q.even(x)) is True + assert ask(Q.even(Abs(x)), ~Q.even(x)) is None + assert ask(Q.even(re(x)), Q.even(x)) is True + assert ask(Q.even(re(x)), ~Q.even(x)) is None + assert ask(Q.even(im(x)), Q.even(x)) is True + assert ask(Q.even(im(x)), Q.real(x)) is True + + assert ask(Q.even((-1)**n), Q.integer(n)) is False + + assert ask(Q.even(k**2), Q.even(k)) is True + assert ask(Q.even(n**2), Q.odd(n)) is False + assert ask(Q.even(2**k), Q.even(k)) is None + assert ask(Q.even(x**2)) is None + + assert ask(Q.even(k**m), Q.even(k) & Q.integer(m) & ~Q.negative(m)) is None + assert ask(Q.even(n**m), Q.odd(n) & Q.integer(m) & ~Q.negative(m)) is False + + assert ask(Q.even(k**p), Q.even(k) & Q.integer(p) & Q.positive(p)) is True + assert ask(Q.even(n**p), Q.odd(n) & Q.integer(p) & Q.positive(p)) is False + + assert ask(Q.even(m**k), Q.even(k) & Q.integer(m) & ~Q.negative(m)) is None + assert ask(Q.even(p**k), Q.even(k) & Q.integer(p) & Q.positive(p)) is None + + assert ask(Q.even(m**n), Q.odd(n) & Q.integer(m) & ~Q.negative(m)) is None + assert ask(Q.even(p**n), Q.odd(n) & Q.integer(p) & Q.positive(p)) is None + + assert ask(Q.even(k**x), Q.even(k)) is None + assert ask(Q.even(n**x), Q.odd(n)) is None + + assert ask(Q.even(x*y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.even(x*x), Q.integer(x)) is None + assert ask(Q.even(x*(x + y)), Q.integer(x) & Q.odd(y)) is True + assert ask(Q.even(x*(x + y)), Q.integer(x) & Q.even(y)) is None + + +@XFAIL +def test_evenness_in_ternary_integer_product_with_odd(): + # Tests that oddness inference is independent of term ordering. + # Term ordering at the point of testing depends on SymPy's symbol order, so + # we try to force a different order by modifying symbol names. + assert ask(Q.even(x*y*(y + z)), Q.integer(x) & Q.integer(y) & Q.odd(z)) is True + assert ask(Q.even(y*x*(x + z)), Q.integer(x) & Q.integer(y) & Q.odd(z)) is True + + +def test_evenness_in_ternary_integer_product_with_even(): + assert ask(Q.even(x*y*(y + z)), Q.integer(x) & Q.integer(y) & Q.even(z)) is None + + +def test_extended_real(): + assert ask(Q.extended_real(x), Q.positive_infinite(x)) is True + assert ask(Q.extended_real(x), Q.positive(x)) is True + assert ask(Q.extended_real(x), Q.zero(x)) is True + assert ask(Q.extended_real(x), Q.negative(x)) is True + assert ask(Q.extended_real(x), Q.negative_infinite(x)) is True + + assert ask(Q.extended_real(-x), Q.positive(x)) is True + assert ask(Q.extended_real(-x), Q.negative(x)) is True + + assert ask(Q.extended_real(x + S.Infinity), Q.real(x)) is True + + assert ask(Q.extended_real(x), Q.infinite(x)) is None + + +@_both_exp_pow +def test_rational(): + assert ask(Q.rational(x), Q.integer(x)) is True + assert ask(Q.rational(x), Q.irrational(x)) is False + assert ask(Q.rational(x), Q.real(x)) is None + assert ask(Q.rational(x), Q.positive(x)) is None + assert ask(Q.rational(x), Q.negative(x)) is None + assert ask(Q.rational(x), Q.nonzero(x)) is None + assert ask(Q.rational(x), ~Q.algebraic(x)) is False + + assert ask(Q.rational(2*x), Q.rational(x)) is True + assert ask(Q.rational(2*x), Q.integer(x)) is True + assert ask(Q.rational(2*x), Q.even(x)) is True + assert ask(Q.rational(2*x), Q.odd(x)) is True + assert ask(Q.rational(2*x), Q.irrational(x)) is False + + assert ask(Q.rational(x/2), Q.rational(x)) is True + assert ask(Q.rational(x/2), Q.integer(x)) is True + assert ask(Q.rational(x/2), Q.even(x)) is True + assert ask(Q.rational(x/2), Q.odd(x)) is True + assert ask(Q.rational(x/2), Q.irrational(x)) is False + + assert ask(Q.rational(1/x), Q.rational(x) & Q.nonzero(x)) is True + assert ask(Q.rational(1/x), Q.integer(x) & Q.nonzero(x)) is True + assert ask(Q.rational(1/x), Q.even(x) & Q.nonzero(x)) is True + assert ask(Q.rational(1/x), Q.odd(x)) is True + assert ask(Q.rational(1/x), Q.irrational(x)) is False + + assert ask(Q.rational(2/x), Q.rational(x) & Q.nonzero(x)) is True + assert ask(Q.rational(2/x), Q.integer(x) & Q.nonzero(x)) is True + assert ask(Q.rational(2/x), Q.even(x) & Q.nonzero(x)) is True + assert ask(Q.rational(2/x), Q.odd(x)) is True + assert ask(Q.rational(2/x), Q.irrational(x)) is False + + assert ask(Q.rational(x), ~Q.algebraic(x)) is False + + # with multiple symbols + assert ask(Q.rational(x*y), Q.irrational(x) & Q.irrational(y)) is None + assert ask(Q.rational(y/x), Q.rational(x) & Q.rational(y) & Q.nonzero(x)) is True + assert ask(Q.rational(y/x), Q.integer(x) & Q.rational(y) & Q.nonzero(x)) is True + assert ask(Q.rational(y/x), Q.even(x) & Q.rational(y) & Q.nonzero(x)) is True + assert ask(Q.rational(y/x), Q.odd(x) & Q.rational(y)) is True + assert ask(Q.rational(y/x), Q.irrational(x) & Q.rational(y) & Q.nonzero(y)) is False + + for f in [exp, sin, tan, asin, atan, cos]: + assert ask(Q.rational(f(7))) is False + assert ask(Q.rational(f(7, evaluate=False))) is False + assert ask(Q.rational(f(0, evaluate=False))) is True + assert ask(Q.rational(f(x)), Q.rational(x)) is None + assert ask(Q.rational(f(x)), Q.rational(x) & Q.nonzero(x)) is False + + for g in [log, acos]: + assert ask(Q.rational(g(7))) is False + assert ask(Q.rational(g(7, evaluate=False))) is False + assert ask(Q.rational(g(1, evaluate=False))) is True + assert ask(Q.rational(g(x)), Q.rational(x)) is None + assert ask(Q.rational(g(x)), Q.rational(x) & Q.nonzero(x - 1)) is False + + for h in [cot, acot]: + assert ask(Q.rational(h(7))) is False + assert ask(Q.rational(h(7, evaluate=False))) is False + assert ask(Q.rational(h(x)), Q.rational(x)) is False + + # https://github.com/sympy/sympy/issues/27442 + assert ask(Q.rational(x**y),Q.irrational(x) & Q.rational(y)) is None + assert ask(Q.rational(x**y),Q.integer(x) & Q.prime(x) & Q.rational(y)) is None + assert ask(Q.rational(x**y),Q.integer(x) & Q.integer(y)) is None + assert ask(Q.rational(x**y),Q.integer(x) & Q.eq(x,0) & Q.integer(y)) is None + assert ask(Q.rational(x**y),Q.eq(x,1) & Q.rational(y)) is None + assert ask(Q.rational(x**y),Q.eq(x,-1) & Q.rational(y)) is None + assert ask(Q.rational(x**y), Q.prime(x) & Q.rational(y)) is None + assert ask(Q.rational(x**y), ~Q.rational(x) & Q.integer(y) ) is None + assert ask(Q.rational(Pow(-1, x, evaluate=False), Q.rational(x))) is None + assert ask(Q.rational(x**y), Q.integer(y) & ~Q. algebraic(x)) is None + assert ask(Q.rational(x**y), Q.integer(y) & ~Q. algebraic(x) & ~Q.zero(x)) is None + assert ask(Q.rational(x**y), Q.integer(y) & ~Q.algebraic(x) & Q.complex(x) & ~Q.real(x)) is None + assert ask(Q.rational(x**y), Q.integer(y) & ~Q.algebraic(x) & Q.complex(x)) is None + + +def test_hermitian(): + assert ask(Q.hermitian(x)) is None + assert ask(Q.hermitian(x), Q.antihermitian(x)) is None + assert ask(Q.hermitian(x), Q.imaginary(x)) is False + assert ask(Q.hermitian(x), Q.prime(x)) is True + assert ask(Q.hermitian(x), Q.real(x)) is True + assert ask(Q.hermitian(x), Q.zero(x)) is True + + assert ask(Q.hermitian(x + 1), Q.antihermitian(x)) is None + assert ask(Q.hermitian(x + 1), Q.complex(x)) is None + assert ask(Q.hermitian(x + 1), Q.hermitian(x)) is True + assert ask(Q.hermitian(x + 1), Q.imaginary(x)) is False + assert ask(Q.hermitian(x + 1), Q.real(x)) is True + assert ask(Q.hermitian(x + I), Q.antihermitian(x)) is None + assert ask(Q.hermitian(x + I), Q.complex(x)) is None + assert ask(Q.hermitian(x + I), Q.hermitian(x)) is False + assert ask(Q.hermitian(x + I), Q.imaginary(x)) is None + assert ask(Q.hermitian(x + I), Q.real(x)) is False + assert ask( + Q.hermitian(x + y), Q.antihermitian(x) & Q.antihermitian(y)) is None + assert ask(Q.hermitian(x + y), Q.antihermitian(x) & Q.complex(y)) is None + assert ask( + Q.hermitian(x + y), Q.antihermitian(x) & Q.hermitian(y)) is None + assert ask(Q.hermitian(x + y), Q.antihermitian(x) & Q.imaginary(y)) is None + assert ask(Q.hermitian(x + y), Q.antihermitian(x) & Q.real(y)) is None + assert ask(Q.hermitian(x + y), Q.hermitian(x) & Q.complex(y)) is None + assert ask(Q.hermitian(x + y), Q.hermitian(x) & Q.hermitian(y)) is True + assert ask(Q.hermitian(x + y), Q.hermitian(x) & Q.imaginary(y)) is False + assert ask(Q.hermitian(x + y), Q.hermitian(x) & Q.real(y)) is True + assert ask(Q.hermitian(x + y), Q.imaginary(x) & Q.complex(y)) is None + assert ask(Q.hermitian(x + y), Q.imaginary(x) & Q.imaginary(y)) is None + assert ask(Q.hermitian(x + y), Q.imaginary(x) & Q.real(y)) is False + assert ask(Q.hermitian(x + y), Q.real(x) & Q.complex(y)) is None + assert ask(Q.hermitian(x + y), Q.real(x) & Q.real(y)) is True + + assert ask(Q.hermitian(I*x), Q.antihermitian(x)) is True + assert ask(Q.hermitian(I*x), Q.complex(x)) is None + assert ask(Q.hermitian(I*x), Q.hermitian(x)) is False + assert ask(Q.hermitian(I*x), Q.imaginary(x)) is True + assert ask(Q.hermitian(I*x), Q.real(x)) is False + assert ask(Q.hermitian(x*y), Q.hermitian(x) & Q.real(y)) is True + + assert ask( + Q.hermitian(x + y + z), Q.real(x) & Q.real(y) & Q.real(z)) is True + assert ask(Q.hermitian(x + y + z), + Q.real(x) & Q.real(y) & Q.imaginary(z)) is False + assert ask(Q.hermitian(x + y + z), + Q.real(x) & Q.imaginary(y) & Q.imaginary(z)) is None + assert ask(Q.hermitian(x + y + z), + Q.imaginary(x) & Q.imaginary(y) & Q.imaginary(z)) is None + + assert ask(Q.antihermitian(x)) is None + assert ask(Q.antihermitian(x), Q.real(x)) is False + assert ask(Q.antihermitian(x), Q.prime(x)) is False + + assert ask(Q.antihermitian(x + 1), Q.antihermitian(x)) is False + assert ask(Q.antihermitian(x + 1), Q.complex(x)) is None + assert ask(Q.antihermitian(x + 1), Q.hermitian(x)) is None + assert ask(Q.antihermitian(x + 1), Q.imaginary(x)) is False + assert ask(Q.antihermitian(x + 1), Q.real(x)) is None + assert ask(Q.antihermitian(x + I), Q.antihermitian(x)) is True + assert ask(Q.antihermitian(x + I), Q.complex(x)) is None + assert ask(Q.antihermitian(x + I), Q.hermitian(x)) is None + assert ask(Q.antihermitian(x + I), Q.imaginary(x)) is True + assert ask(Q.antihermitian(x + I), Q.real(x)) is False + assert ask(Q.antihermitian(x), Q.zero(x)) is True + + assert ask( + Q.antihermitian(x + y), Q.antihermitian(x) & Q.antihermitian(y) + ) is True + assert ask( + Q.antihermitian(x + y), Q.antihermitian(x) & Q.complex(y)) is None + assert ask( + Q.antihermitian(x + y), Q.antihermitian(x) & Q.hermitian(y)) is None + assert ask( + Q.antihermitian(x + y), Q.antihermitian(x) & Q.imaginary(y)) is True + assert ask(Q.antihermitian(x + y), Q.antihermitian(x) & Q.real(y) + ) is False + assert ask(Q.antihermitian(x + y), Q.hermitian(x) & Q.complex(y)) is None + assert ask(Q.antihermitian(x + y), Q.hermitian(x) & Q.hermitian(y) + ) is None + assert ask( + Q.antihermitian(x + y), Q.hermitian(x) & Q.imaginary(y)) is None + assert ask(Q.antihermitian(x + y), Q.hermitian(x) & Q.real(y)) is None + assert ask(Q.antihermitian(x + y), Q.imaginary(x) & Q.complex(y)) is None + assert ask(Q.antihermitian(x + y), Q.imaginary(x) & Q.imaginary(y)) is True + assert ask(Q.antihermitian(x + y), Q.imaginary(x) & Q.real(y)) is False + assert ask(Q.antihermitian(x + y), Q.real(x) & Q.complex(y)) is None + assert ask(Q.antihermitian(x + y), Q.real(x) & Q.real(y)) is None + + assert ask(Q.antihermitian(I*x), Q.real(x)) is True + assert ask(Q.antihermitian(I*x), Q.antihermitian(x)) is False + assert ask(Q.antihermitian(I*x), Q.complex(x)) is None + assert ask(Q.antihermitian(x*y), Q.antihermitian(x) & Q.real(y)) is True + + assert ask(Q.antihermitian(x + y + z), + Q.real(x) & Q.real(y) & Q.real(z)) is None + assert ask(Q.antihermitian(x + y + z), + Q.real(x) & Q.real(y) & Q.imaginary(z)) is None + assert ask(Q.antihermitian(x + y + z), + Q.real(x) & Q.imaginary(y) & Q.imaginary(z)) is False + assert ask(Q.antihermitian(x + y + z), + Q.imaginary(x) & Q.imaginary(y) & Q.imaginary(z)) is True + + +@_both_exp_pow +def test_imaginary(): + assert ask(Q.imaginary(x)) is None + assert ask(Q.imaginary(x), Q.real(x)) is False + assert ask(Q.imaginary(x), Q.prime(x)) is False + + assert ask(Q.imaginary(x + 1), Q.real(x)) is False + assert ask(Q.imaginary(x + 1), Q.imaginary(x)) is False + assert ask(Q.imaginary(x + I), Q.real(x)) is False + assert ask(Q.imaginary(x + I), Q.imaginary(x)) is True + assert ask(Q.imaginary(x + y), Q.imaginary(x) & Q.imaginary(y)) is True + assert ask(Q.imaginary(x + y), Q.real(x) & Q.real(y)) is False + assert ask(Q.imaginary(x + y), Q.imaginary(x) & Q.real(y)) is False + assert ask(Q.imaginary(x + y), Q.complex(x) & Q.real(y)) is None + assert ask( + Q.imaginary(x + y + z), Q.real(x) & Q.real(y) & Q.real(z)) is False + assert ask(Q.imaginary(x + y + z), + Q.real(x) & Q.real(y) & Q.imaginary(z)) is None + assert ask(Q.imaginary(x + y + z), + Q.real(x) & Q.imaginary(y) & Q.imaginary(z)) is False + + assert ask(Q.imaginary(I*x), Q.real(x)) is True + assert ask(Q.imaginary(I*x), Q.imaginary(x)) is False + assert ask(Q.imaginary(I*x), Q.complex(x)) is None + assert ask(Q.imaginary(x*y), Q.imaginary(x) & Q.real(y)) is True + assert ask(Q.imaginary(x*y), Q.real(x) & Q.real(y)) is False + + assert ask(Q.imaginary(I**x), Q.negative(x)) is None + assert ask(Q.imaginary(I**x), Q.positive(x)) is None + assert ask(Q.imaginary(I**x), Q.even(x)) is False + assert ask(Q.imaginary(I**x), Q.odd(x)) is True + assert ask(Q.imaginary(I**x), Q.imaginary(x)) is False + assert ask(Q.imaginary((2*I)**x), Q.imaginary(x)) is False + assert ask(Q.imaginary(x**0), Q.imaginary(x)) is False + assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.imaginary(y)) is None + assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.real(y)) is None + assert ask(Q.imaginary(x**y), Q.real(x) & Q.imaginary(y)) is None + assert ask(Q.imaginary(x**y), Q.real(x) & Q.real(y)) is None + assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.integer(y)) is None + assert ask(Q.imaginary(x**y), Q.imaginary(y) & Q.integer(x)) is None + assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.odd(y)) is True + assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.rational(y)) is None + assert ask(Q.imaginary(x**y), Q.imaginary(x) & Q.even(y)) is False + + assert ask(Q.imaginary(x**y), Q.real(x) & Q.integer(y)) is False + assert ask(Q.imaginary(x**y), Q.positive(x) & Q.real(y)) is False + assert ask(Q.imaginary(x**y), Q.negative(x) & Q.real(y)) is None + assert ask(Q.imaginary(x**y), Q.negative(x) & Q.real(y) & ~Q.rational(y)) is False + assert ask(Q.imaginary(x**y), Q.integer(x) & Q.imaginary(y)) is None + assert ask(Q.imaginary(x**y), Q.negative(x) & Q.rational(y) & Q.integer(2*y)) is True + assert ask(Q.imaginary(x**y), Q.negative(x) & Q.rational(y) & ~Q.integer(2*y)) is False + assert ask(Q.imaginary(x**y), Q.negative(x) & Q.rational(y)) is None + assert ask(Q.imaginary(x**y), Q.real(x) & Q.rational(y) & ~Q.integer(2*y)) is False + assert ask(Q.imaginary(x**y), Q.real(x) & Q.rational(y) & Q.integer(2*y)) is None + + # logarithm + assert ask(Q.imaginary(log(I))) is True + assert ask(Q.imaginary(log(2*I))) is False + assert ask(Q.imaginary(log(I + 1))) is False + assert ask(Q.imaginary(log(x)), Q.complex(x)) is None + assert ask(Q.imaginary(log(x)), Q.imaginary(x)) is None + assert ask(Q.imaginary(log(x)), Q.positive(x)) is False + assert ask(Q.imaginary(log(exp(x))), Q.complex(x)) is None + assert ask(Q.imaginary(log(exp(x))), Q.imaginary(x)) is None # zoo/I/a+I*b + assert ask(Q.imaginary(log(exp(I)))) is True + + # exponential + assert ask(Q.imaginary(exp(x)**x), Q.imaginary(x)) is False + eq = Pow(exp(pi*I*x, evaluate=False), x, evaluate=False) + assert ask(Q.imaginary(eq), Q.even(x)) is False + eq = Pow(exp(pi*I*x/2, evaluate=False), x, evaluate=False) + assert ask(Q.imaginary(eq), Q.odd(x)) is True + assert ask(Q.imaginary(exp(3*I*pi*x)**x), Q.integer(x)) is False + assert ask(Q.imaginary(exp(2*pi*I, evaluate=False))) is False + assert ask(Q.imaginary(exp(pi*I/2, evaluate=False))) is True + + # issue 7886 + assert ask(Q.imaginary(Pow(x, Rational(1, 4))), Q.real(x) & Q.negative(x)) is False + + +def test_integer(): + assert ask(Q.integer(x)) is None + assert ask(Q.integer(x), Q.integer(x)) is True + assert ask(Q.integer(x), ~Q.integer(x)) is False + assert ask(Q.integer(x), ~Q.real(x)) is False + assert ask(Q.integer(x), ~Q.positive(x)) is None + assert ask(Q.integer(x), Q.even(x) | Q.odd(x)) is True + + assert ask(Q.integer(2*x), Q.integer(x)) is True + assert ask(Q.integer(2*x), Q.even(x)) is True + assert ask(Q.integer(2*x), Q.prime(x)) is True + assert ask(Q.integer(2*x), Q.rational(x)) is None + assert ask(Q.integer(2*x), Q.real(x)) is None + assert ask(Q.integer(sqrt(2)*x), Q.integer(x)) is False + assert ask(Q.integer(sqrt(2)*x), Q.irrational(x)) is None + + assert ask(Q.integer(x/2), Q.odd(x)) is False + assert ask(Q.integer(x/2), Q.even(x)) is True + assert ask(Q.integer(x/3), Q.odd(x)) is None + assert ask(Q.integer(x/3), Q.even(x)) is None + + # https://github.com/sympy/sympy/issues/7286 + assert ask(Q.integer(Abs(x)),Q.integer(x)) is True + assert ask(Q.integer(Abs(-x)),Q.integer(x)) is True + assert ask(Q.integer(Abs(x)), ~Q.integer(x)) is None + assert ask(Q.integer(Abs(x)),Q.complex(x)) is None + assert ask(Q.integer(Abs(x+I*y)),Q.real(x) & Q.real(y)) is None + + # https://github.com/sympy/sympy/issues/27739 + assert ask(Q.integer(x/y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.integer(1/x), Q.integer(x)) is None + assert ask(Q.integer(x**y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.integer(sqrt(5))) is False + assert ask(Q.integer(x**y), Q.nonzero(x) & Q.zero(y)) is True + assert ask(Q.integer(x**y), Q.integer(x) & Q.integer(y) & Q.positive(y)) is True + assert ask(Q.integer(-1**x), Q.integer(x)) is True + assert ask(Q.integer(x**y), Q.integer(x) & Q.integer(y) & Q.positive(y)) is True + assert ask(Q.integer(x**y), Q.zero(x) & Q.integer(y) & Q.positive(y)) is True + assert ask(Q.integer(pi**x), Q.zero(x)) is True + assert ask(Q.integer(x**y), Q.imaginary(x) & Q.zero(y)) is True + + +def test_negative(): + assert ask(Q.negative(x), Q.negative(x)) is True + assert ask(Q.negative(x), Q.positive(x)) is False + assert ask(Q.negative(x), ~Q.real(x)) is False + assert ask(Q.negative(x), Q.prime(x)) is False + assert ask(Q.negative(x), ~Q.prime(x)) is None + + assert ask(Q.negative(-x), Q.positive(x)) is True + assert ask(Q.negative(-x), ~Q.positive(x)) is None + assert ask(Q.negative(-x), Q.negative(x)) is False + assert ask(Q.negative(-x), Q.positive(x)) is True + + assert ask(Q.negative(x - 1), Q.negative(x)) is True + assert ask(Q.negative(x + y)) is None + assert ask(Q.negative(x + y), Q.negative(x)) is None + assert ask(Q.negative(x + y), Q.negative(x) & Q.negative(y)) is True + assert ask(Q.negative(x + y), Q.negative(x) & Q.nonpositive(y)) is True + assert ask(Q.negative(2 + I)) is False + # although this could be False, it is representative of expressions + # that don't evaluate to a zero with precision + assert ask(Q.negative(cos(I)**2 + sin(I)**2 - 1)) is None + assert ask(Q.negative(-I + I*(cos(2)**2 + sin(2)**2))) is None + + assert ask(Q.negative(x**2)) is None + assert ask(Q.negative(x**2), Q.real(x)) is False + assert ask(Q.negative(x**1.4), Q.real(x)) is None + + assert ask(Q.negative(x**I), Q.positive(x)) is None + + assert ask(Q.negative(x*y)) is None + assert ask(Q.negative(x*y), Q.positive(x) & Q.positive(y)) is False + assert ask(Q.negative(x*y), Q.positive(x) & Q.negative(y)) is True + assert ask(Q.negative(x*y), Q.complex(x) & Q.complex(y)) is None + + assert ask(Q.negative(x**y)) is None + assert ask(Q.negative(x**y), Q.negative(x) & Q.even(y)) is False + assert ask(Q.negative(x**y), Q.negative(x) & Q.odd(y)) is True + assert ask(Q.negative(x**y), Q.positive(x) & Q.integer(y)) is False + + assert ask(Q.negative(Abs(x))) is False + + +def test_nonzero(): + assert ask(Q.nonzero(x)) is None + assert ask(Q.nonzero(x), Q.real(x)) is None + assert ask(Q.nonzero(x), Q.positive(x)) is True + assert ask(Q.nonzero(x), Q.negative(x)) is True + assert ask(Q.nonzero(x), Q.negative(x) | Q.positive(x)) is True + + assert ask(Q.nonzero(x + y)) is None + assert ask(Q.nonzero(x + y), Q.positive(x) & Q.positive(y)) is True + assert ask(Q.nonzero(x + y), Q.positive(x) & Q.negative(y)) is None + assert ask(Q.nonzero(x + y), Q.negative(x) & Q.negative(y)) is True + + assert ask(Q.nonzero(2*x)) is None + assert ask(Q.nonzero(2*x), Q.positive(x)) is True + assert ask(Q.nonzero(2*x), Q.negative(x)) is True + assert ask(Q.nonzero(x*y), Q.nonzero(x)) is None + assert ask(Q.nonzero(x*y), Q.nonzero(x) & Q.nonzero(y)) is True + + assert ask(Q.nonzero(x**y), Q.nonzero(x)) is True + + assert ask(Q.nonzero(Abs(x))) is None + assert ask(Q.nonzero(Abs(x)), Q.nonzero(x)) is True + + assert ask(Q.nonzero(log(exp(2*I)))) is False + # although this could be False, it is representative of expressions + # that don't evaluate to a zero with precision + assert ask(Q.nonzero(cos(1)**2 + sin(1)**2 - 1)) is None + + +def test_zero(): + assert ask(Q.zero(x)) is None + assert ask(Q.zero(x), Q.real(x)) is None + assert ask(Q.zero(x), Q.positive(x)) is False + assert ask(Q.zero(x), Q.negative(x)) is False + assert ask(Q.zero(x), Q.negative(x) | Q.positive(x)) is False + + assert ask(Q.zero(x), Q.nonnegative(x) & Q.nonpositive(x)) is True + + assert ask(Q.zero(x + y)) is None + assert ask(Q.zero(x + y), Q.positive(x) & Q.positive(y)) is False + assert ask(Q.zero(x + y), Q.positive(x) & Q.negative(y)) is None + assert ask(Q.zero(x + y), Q.negative(x) & Q.negative(y)) is False + + assert ask(Q.zero(2*x)) is None + assert ask(Q.zero(2*x), Q.positive(x)) is False + assert ask(Q.zero(2*x), Q.negative(x)) is False + assert ask(Q.zero(x*y), Q.nonzero(x)) is None + + assert ask(Q.zero(Abs(x))) is None + assert ask(Q.zero(Abs(x)), Q.zero(x)) is True + + assert ask(Q.integer(x), Q.zero(x)) is True + assert ask(Q.even(x), Q.zero(x)) is True + assert ask(Q.odd(x), Q.zero(x)) is False + assert ask(Q.zero(x), Q.even(x)) is None + assert ask(Q.zero(x), Q.odd(x)) is False + assert ask(Q.zero(x) | Q.zero(y), Q.zero(x*y)) is True + + +def test_odd_query(): + assert ask(Q.odd(x)) is None + assert ask(Q.odd(x), Q.odd(x)) is True + assert ask(Q.odd(x), Q.integer(x)) is None + assert ask(Q.odd(x), ~Q.integer(x)) is False + assert ask(Q.odd(x), Q.rational(x)) is None + assert ask(Q.odd(x), Q.positive(x)) is None + + assert ask(Q.odd(-x), Q.odd(x)) is True + + assert ask(Q.odd(2*x)) is None + assert ask(Q.odd(2*x), Q.integer(x)) is False + assert ask(Q.odd(2*x), Q.odd(x)) is False + assert ask(Q.odd(2*x), Q.irrational(x)) is False + assert ask(Q.odd(2*x), ~Q.integer(x)) is None + assert ask(Q.odd(3*x), Q.integer(x)) is None + + assert ask(Q.odd(x/3), Q.odd(x)) is None + assert ask(Q.odd(x/3), Q.even(x)) is None + + assert ask(Q.odd(x + 1), Q.even(x)) is True + assert ask(Q.odd(x + 2), Q.even(x)) is False + assert ask(Q.odd(x + 2), Q.odd(x)) is True + assert ask(Q.odd(3 - x), Q.odd(x)) is False + assert ask(Q.odd(3 - x), Q.even(x)) is True + assert ask(Q.odd(3 + x), Q.odd(x)) is False + assert ask(Q.odd(3 + x), Q.even(x)) is True + assert ask(Q.odd(x + y), Q.odd(x) & Q.odd(y)) is False + assert ask(Q.odd(x + y), Q.odd(x) & Q.even(y)) is True + assert ask(Q.odd(x - y), Q.even(x) & Q.odd(y)) is True + assert ask(Q.odd(x - y), Q.odd(x) & Q.odd(y)) is False + + assert ask(Q.odd(x + y + z), Q.odd(x) & Q.odd(y) & Q.even(z)) is False + assert ask(Q.odd(x + y + z + t), + Q.odd(x) & Q.odd(y) & Q.even(z) & Q.integer(t)) is None + + assert ask(Q.odd(2*x + 1), Q.integer(x)) is True + assert ask(Q.odd(2*x + y), Q.integer(x) & Q.odd(y)) is True + assert ask(Q.odd(2*x + y), Q.integer(x) & Q.even(y)) is False + assert ask(Q.odd(2*x + y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.odd(x*y), Q.odd(x) & Q.even(y)) is False + assert ask(Q.odd(x*y), Q.odd(x) & Q.odd(y)) is True + assert ask(Q.odd(2*x*y), Q.rational(x) & Q.rational(x)) is None + assert ask(Q.odd(2*x*y), Q.irrational(x) & Q.irrational(x)) is None + + assert ask(Q.odd(Abs(x)), Q.odd(x)) is True + + assert ask(Q.odd((-1)**n), Q.integer(n)) is True + + assert ask(Q.odd(k**2), Q.even(k)) is False + assert ask(Q.odd(n**2), Q.odd(n)) is True + assert ask(Q.odd(3**k), Q.even(k)) is None + + assert ask(Q.odd(k**m), Q.even(k) & Q.integer(m) & ~Q.negative(m)) is None + assert ask(Q.odd(n**m), Q.odd(n) & Q.integer(m) & ~Q.negative(m)) is True + + assert ask(Q.odd(k**p), Q.even(k) & Q.integer(p) & Q.positive(p)) is False + assert ask(Q.odd(n**p), Q.odd(n) & Q.integer(p) & Q.positive(p)) is True + + assert ask(Q.odd(m**k), Q.even(k) & Q.integer(m) & ~Q.negative(m)) is None + assert ask(Q.odd(p**k), Q.even(k) & Q.integer(p) & Q.positive(p)) is None + + assert ask(Q.odd(m**n), Q.odd(n) & Q.integer(m) & ~Q.negative(m)) is None + assert ask(Q.odd(p**n), Q.odd(n) & Q.integer(p) & Q.positive(p)) is None + + assert ask(Q.odd(k**x), Q.even(k)) is None + assert ask(Q.odd(n**x), Q.odd(n)) is None + + assert ask(Q.odd(x*y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.odd(x*x), Q.integer(x)) is None + assert ask(Q.odd(x*(x + y)), Q.integer(x) & Q.odd(y)) is False + assert ask(Q.odd(x*(x + y)), Q.integer(x) & Q.even(y)) is None + + +@XFAIL +def test_oddness_in_ternary_integer_product_with_odd(): + # Tests that oddness inference is independent of term ordering. + # Term ordering at the point of testing depends on SymPy's symbol order, so + # we try to force a different order by modifying symbol names. + assert ask(Q.odd(x*y*(y + z)), Q.integer(x) & Q.integer(y) & Q.odd(z)) is False + assert ask(Q.odd(y*x*(x + z)), Q.integer(x) & Q.integer(y) & Q.odd(z)) is False + + +def test_oddness_in_ternary_integer_product_with_even(): + assert ask(Q.odd(x*y*(y + z)), Q.integer(x) & Q.integer(y) & Q.even(z)) is None + + +def test_prime(): + assert ask(Q.prime(x), Q.prime(x)) is True + assert ask(Q.prime(x), ~Q.prime(x)) is False + assert ask(Q.prime(x), Q.integer(x)) is None + assert ask(Q.prime(x), ~Q.integer(x)) is False + + assert ask(Q.prime(2*x), Q.integer(x)) is None + assert ask(Q.prime(x*y)) is None + assert ask(Q.prime(x*y), Q.prime(x)) is None + assert ask(Q.prime(x*y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.prime(4*x), Q.integer(x)) is False + assert ask(Q.prime(4*x)) is None + + assert ask(Q.prime(x**2), Q.integer(x)) is False + assert ask(Q.prime(x**2), Q.prime(x)) is False + + # https://github.com/sympy/sympy/issues/27446 + assert ask(Q.prime(4**x), Q.integer(x)) is False + assert ask(Q.prime(p**x), Q.prime(p) & Q.integer(x) & Q.ne(x, 1)) is False + assert ask(Q.prime(n**x), Q.integer(x) & Q.composite(n)) is False + assert ask(Q.prime(x**y), Q.integer(x) & Q.integer(y)) is None + assert ask(Q.prime(2**x), Q.integer(x)) is None + assert ask(Q.prime(p**x), Q.prime(p) & Q.integer(x)) is None + + # Ideally, these should return True since the base is prime and the exponent is one, + # but currently, they return None. + assert ask(Q.prime(x**y), Q.prime(x) & Q.eq(y,1)) is None + assert ask(Q.prime(x**y), Q.prime(x) & Q.integer(y) & Q.gt(y,0) & Q.lt(y,2)) is None + + assert ask(Q.prime(Pow(x,1, evaluate=False)), Q.prime(x)) is True + + +@_both_exp_pow +def test_positive(): + assert ask(Q.positive(cos(I) ** 2 + sin(I) ** 2 - 1)) is None + assert ask(Q.positive(x), Q.positive(x)) is True + assert ask(Q.positive(x), Q.negative(x)) is False + assert ask(Q.positive(x), Q.nonzero(x)) is None + + assert ask(Q.positive(-x), Q.positive(x)) is False + assert ask(Q.positive(-x), Q.negative(x)) is True + + assert ask(Q.positive(x + y), Q.positive(x) & Q.positive(y)) is True + assert ask(Q.positive(x + y), Q.positive(x) & Q.nonnegative(y)) is True + assert ask(Q.positive(x + y), Q.positive(x) & Q.negative(y)) is None + assert ask(Q.positive(x + y), Q.positive(x) & Q.imaginary(y)) is False + + assert ask(Q.positive(2*x), Q.positive(x)) is True + assumptions = Q.positive(x) & Q.negative(y) & Q.negative(z) & Q.positive(w) + assert ask(Q.positive(x*y*z)) is None + assert ask(Q.positive(x*y*z), assumptions) is True + assert ask(Q.positive(-x*y*z), assumptions) is False + + assert ask(Q.positive(x**I), Q.positive(x)) is None + + assert ask(Q.positive(x**2), Q.positive(x)) is True + assert ask(Q.positive(x**2), Q.negative(x)) is True + assert ask(Q.positive(x**3), Q.negative(x)) is False + assert ask(Q.positive(1/(1 + x**2)), Q.real(x)) is True + assert ask(Q.positive(2**I)) is False + assert ask(Q.positive(2 + I)) is False + # although this could be False, it is representative of expressions + # that don't evaluate to a zero with precision + assert ask(Q.positive(cos(I)**2 + sin(I)**2 - 1)) is None + assert ask(Q.positive(-I + I*(cos(2)**2 + sin(2)**2))) is None + + #exponential + assert ask(Q.positive(exp(x)), Q.real(x)) is True + assert ask(~Q.negative(exp(x)), Q.real(x)) is True + assert ask(Q.positive(x + exp(x)), Q.real(x)) is None + assert ask(Q.positive(exp(x)), Q.imaginary(x)) is None + assert ask(Q.positive(exp(2*pi*I, evaluate=False)), Q.imaginary(x)) is True + assert ask(Q.negative(exp(pi*I, evaluate=False)), Q.imaginary(x)) is True + assert ask(Q.positive(exp(x*pi*I)), Q.even(x)) is True + assert ask(Q.positive(exp(x*pi*I)), Q.odd(x)) is False + assert ask(Q.positive(exp(x*pi*I)), Q.real(x)) is None + + # logarithm + assert ask(Q.positive(log(x)), Q.imaginary(x)) is False + assert ask(Q.positive(log(x)), Q.negative(x)) is False + assert ask(Q.positive(log(x)), Q.positive(x)) is None + assert ask(Q.positive(log(x + 2)), Q.positive(x)) is True + + # factorial + assert ask(Q.positive(factorial(x)), Q.integer(x) & Q.positive(x)) + assert ask(Q.positive(factorial(x)), Q.integer(x)) is None + + #absolute value + assert ask(Q.positive(Abs(x))) is None # Abs(0) = 0 + assert ask(Q.positive(Abs(x)), Q.positive(x)) is True + + +def test_nonpositive(): + assert ask(Q.nonpositive(-1)) + assert ask(Q.nonpositive(0)) + assert ask(Q.nonpositive(1)) is False + assert ask(~Q.positive(x), Q.nonpositive(x)) + assert ask(Q.nonpositive(x), Q.positive(x)) is False + assert ask(Q.nonpositive(sqrt(-1))) is False + assert ask(Q.nonpositive(x), Q.imaginary(x)) is False + + +def test_nonnegative(): + assert ask(Q.nonnegative(-1)) is False + assert ask(Q.nonnegative(0)) + assert ask(Q.nonnegative(1)) + assert ask(~Q.negative(x), Q.nonnegative(x)) + assert ask(Q.nonnegative(x), Q.negative(x)) is False + assert ask(Q.nonnegative(sqrt(-1))) is False + assert ask(Q.nonnegative(x), Q.imaginary(x)) is False + +def test_real_basic(): + assert ask(Q.real(x)) is None + assert ask(Q.real(x), Q.real(x)) is True + assert ask(Q.real(x), Q.nonzero(x)) is True + assert ask(Q.real(x), Q.positive(x)) is True + assert ask(Q.real(x), Q.negative(x)) is True + assert ask(Q.real(x), Q.integer(x)) is True + assert ask(Q.real(x), Q.even(x)) is True + assert ask(Q.real(x), Q.prime(x)) is True + + assert ask(Q.real(x/sqrt(2)), Q.real(x)) is True + assert ask(Q.real(x/sqrt(-2)), Q.real(x)) is False + + assert ask(Q.real(x + 1), Q.real(x)) is True + assert ask(Q.real(x + I), Q.real(x)) is False + assert ask(Q.real(x + I), Q.complex(x)) is None + + assert ask(Q.real(2*x), Q.real(x)) is True + assert ask(Q.real(I*x), Q.real(x)) is False + assert ask(Q.real(I*x), Q.imaginary(x)) is True + assert ask(Q.real(I*x), Q.complex(x)) is None + + +def test_real_pow(): + assert ask(Q.real(x**2), Q.real(x)) is True + assert ask(Q.real(sqrt(x)), Q.negative(x)) is False + assert ask(Q.real(x**y), Q.real(x) & Q.integer(y)) is None + assert ask(Q.real(x**y), Q.real(x) & Q.real(y)) is None + assert ask(Q.real(x**y), Q.positive(x) & Q.real(y)) is True + assert ask(Q.real(x**y), Q.imaginary(x) & Q.imaginary(y)) is None # I**I or (2*I)**I + assert ask(Q.real(x**y), Q.imaginary(x) & Q.real(y)) is None # I**1 or I**0 + assert ask(Q.real(x**y), Q.real(x) & Q.imaginary(y)) is None # could be exp(2*pi*I) or 2**I + assert ask(Q.real(x**0), Q.imaginary(x)) is True + assert ask(Q.real(x**y), Q.positive(x) & Q.real(y)) is True + assert ask(Q.real(x**y), Q.real(x) & Q.rational(y)) is None + assert ask(Q.real(x**y), Q.imaginary(x) & Q.integer(y)) is None + assert ask(Q.real(x**y), Q.imaginary(x) & Q.odd(y)) is False + assert ask(Q.real(x**y), Q.imaginary(x) & Q.even(y)) is True + assert ask(Q.real(x**(y/z)), Q.real(x) & Q.real(y/z) & Q.rational(y/z) & Q.even(z) & Q.positive(x)) is True + assert ask(Q.real(x**(y/z)), Q.real(x) & Q.rational(y/z) & Q.even(z) & Q.negative(x)) is None + assert ask(Q.real(x**(y/z)), Q.real(x) & Q.integer(y/z)) is None + assert ask(Q.real(x**(y/z)), Q.real(x) & Q.real(y/z) & Q.positive(x)) is True + assert ask(Q.real(x**(y/z)), Q.real(x) & Q.real(y/z) & Q.negative(x)) is None + assert ask(Q.real((-I)**i), Q.imaginary(i)) is True + assert ask(Q.real(I**i), Q.imaginary(i)) is True + assert ask(Q.real(i**i), Q.imaginary(i)) is None # i might be 2*I + assert ask(Q.real(x**i), Q.imaginary(i)) is None # x could be 0 + assert ask(Q.real(x**(I*pi/log(x))), Q.real(x)) is True + + # https://github.com/sympy/sympy/issues/27485 + assert ask(Q.real(n**p), Q.negative(n) & Q.positive(p)) is None + + # https://github.com/sympy/sympy/issues/16530 + assert ask(Q.real(1/Abs(x))) is None + assert ask(Q.real(x**y), Q.zero(x) & Q.real(y)) is None + assert ask(Q.real(x**y), Q.zero(x) & Q.positive(y)) is True + + +@_both_exp_pow +def test_real_functions(): + # trigonometric functions + assert ask(Q.real(sin(x))) is None + assert ask(Q.real(cos(x))) is None + assert ask(Q.real(sin(x)), Q.real(x)) is True + assert ask(Q.real(cos(x)), Q.real(x)) is True + + # exponential function + assert ask(Q.real(exp(x))) is None + assert ask(Q.real(exp(x)), Q.real(x)) is True + assert ask(Q.real(x + exp(x)), Q.real(x)) is True + assert ask(Q.real(exp(2*pi*I, evaluate=False))) is True + assert ask(Q.real(exp(pi*I, evaluate=False))) is True + assert ask(Q.real(exp(pi*I/2, evaluate=False))) is False + + # logarithm + assert ask(Q.real(log(I))) is False + assert ask(Q.real(log(2*I))) is False + assert ask(Q.real(log(I + 1))) is False + assert ask(Q.real(log(x)), Q.complex(x)) is None + assert ask(Q.real(log(x)), Q.imaginary(x)) is False + assert ask(Q.real(log(exp(x))), Q.imaginary(x)) is None # exp(2*pi*I) is 1, log(exp(pi*I)) is pi*I (disregarding periodicity) + assert ask(Q.real(log(exp(x))), Q.complex(x)) is None + eq = Pow(exp(2*pi*I*x, evaluate=False), x, evaluate=False) + assert ask(Q.real(eq), Q.integer(x)) is True + assert ask(Q.real(exp(x)**x), Q.imaginary(x)) is True + assert ask(Q.real(exp(x)**x), Q.complex(x)) is None + + # Q.complexes + assert ask(Q.real(re(x))) is True + assert ask(Q.real(im(x))) is True + + +def test_matrix(): + + # hermitian + assert ask(Q.hermitian(Matrix([[2, 2 + I, 4], [2 - I, 3, I], [4, -I, 1]]))) == True + assert ask(Q.hermitian(Matrix([[2, 2 + I, 4], [2 + I, 3, I], [4, -I, 1]]))) == False + z = symbols('z', complex=True) + assert ask(Q.hermitian(Matrix([[2, 2 + I, z], [2 - I, 3, I], [4, -I, 1]]))) == None + assert ask(Q.hermitian(SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))))) == True + assert ask(Q.hermitian(SparseMatrix(((25, 15, -5), (15, I, 0), (-5, 0, 11))))) == False + assert ask(Q.hermitian(SparseMatrix(((25, 15, -5), (15, z, 0), (-5, 0, 11))))) == None + + # antihermitian + A = Matrix([[0, -2 - I, 0], [2 - I, 0, -I], [0, -I, 0]]) + B = Matrix([[-I, 2 + I, 0], [-2 + I, 0, 2 + I], [0, -2 + I, -I]]) + assert ask(Q.antihermitian(A)) is True + assert ask(Q.antihermitian(B)) is True + assert ask(Q.antihermitian(A**2)) is False + C = (B**3) + C.simplify() + assert ask(Q.antihermitian(C)) is True + _A = Matrix([[0, -2 - I, 0], [z, 0, -I], [0, -I, 0]]) + assert ask(Q.antihermitian(_A)) is None + + +@_both_exp_pow +def test_algebraic(): + assert ask(Q.algebraic(x)) is None + + assert ask(Q.algebraic(I)) is True + assert ask(Q.algebraic(2*I)) is True + assert ask(Q.algebraic(I/3)) is True + + assert ask(Q.algebraic(sqrt(7))) is True + assert ask(Q.algebraic(2*sqrt(7))) is True + assert ask(Q.algebraic(sqrt(7)/3)) is True + + assert ask(Q.algebraic(I*sqrt(3))) is True + assert ask(Q.algebraic(sqrt(1 + I*sqrt(3)))) is True + + assert ask(Q.algebraic(1 + I*sqrt(3)**Rational(17, 31))) is True + assert ask(Q.algebraic(1 + I*sqrt(3)**(17/pi))) is None + + for f in [exp, sin, tan, asin, atan, cos]: + assert ask(Q.algebraic(f(7))) is False + assert ask(Q.algebraic(f(7, evaluate=False))) is False + assert ask(Q.algebraic(f(0, evaluate=False))) is True + assert ask(Q.algebraic(f(x)), Q.algebraic(x)) is None + assert ask(Q.algebraic(f(x)), Q.algebraic(x) & Q.nonzero(x)) is False + + for g in [log, acos]: + assert ask(Q.algebraic(g(7))) is False + assert ask(Q.algebraic(g(7, evaluate=False))) is False + assert ask(Q.algebraic(g(1, evaluate=False))) is True + assert ask(Q.algebraic(g(x)), Q.algebraic(x)) is None + assert ask(Q.algebraic(g(x)), Q.algebraic(x) & Q.nonzero(x - 1)) is False + + for h in [cot, acot]: + assert ask(Q.algebraic(h(7))) is False + assert ask(Q.algebraic(h(7, evaluate=False))) is False + assert ask(Q.algebraic(h(x)), Q.algebraic(x)) is False + + assert ask(Q.algebraic(sqrt(sin(7)))) is None + assert ask(Q.algebraic(sqrt(y + I*sqrt(7)))) is None + + assert ask(Q.algebraic(2.47)) is True + + assert ask(Q.algebraic(x), Q.transcendental(x)) is False + assert ask(Q.transcendental(x), Q.algebraic(x)) is False + + #https://github.com/sympy/sympy/issues/27445 + assert ask(Q.algebraic(Pow(1, x, evaluate=False)), Q.algebraic(x)) is None + assert ask(Q.algebraic(Pow(x, y))) is None + assert ask(Q.algebraic(Pow(1, x, evaluate=False))) is None + assert ask(Q.algebraic(x**(pi*I))) is None + assert ask(Q.algebraic(pi**n),Q.integer(n) & Q.positive(n)) is False + assert ask(Q.algebraic(x**y),Q.algebraic(x) & Q.rational(y)) is True + + +def test_global(): + """Test ask with global assumptions""" + assert ask(Q.integer(x)) is None + global_assumptions.add(Q.integer(x)) + assert ask(Q.integer(x)) is True + global_assumptions.clear() + assert ask(Q.integer(x)) is None + + +def test_custom_context(): + """Test ask with custom assumptions context""" + assert ask(Q.integer(x)) is None + local_context = AssumptionsContext() + local_context.add(Q.integer(x)) + assert ask(Q.integer(x), context=local_context) is True + assert ask(Q.integer(x)) is None + + +def test_functions_in_assumptions(): + assert ask(Q.negative(x), Q.real(x) >> Q.positive(x)) is False + assert ask(Q.negative(x), Equivalent(Q.real(x), Q.positive(x))) is False + assert ask(Q.negative(x), Xor(Q.real(x), Q.negative(x))) is False + + +def test_composite_ask(): + assert ask(Q.negative(x) & Q.integer(x), + assumptions=Q.real(x) >> Q.positive(x)) is False + + +def test_composite_proposition(): + assert ask(True) is True + assert ask(False) is False + assert ask(~Q.negative(x), Q.positive(x)) is True + assert ask(~Q.real(x), Q.commutative(x)) is None + assert ask(Q.negative(x) & Q.integer(x), Q.positive(x)) is False + assert ask(Q.negative(x) & Q.integer(x)) is None + assert ask(Q.real(x) | Q.integer(x), Q.positive(x)) is True + assert ask(Q.real(x) | Q.integer(x)) is None + assert ask(Q.real(x) >> Q.positive(x), Q.negative(x)) is False + assert ask(Implies( + Q.real(x), Q.positive(x), evaluate=False), Q.negative(x)) is False + assert ask(Implies(Q.real(x), Q.positive(x), evaluate=False)) is None + assert ask(Equivalent(Q.integer(x), Q.even(x)), Q.even(x)) is True + assert ask(Equivalent(Q.integer(x), Q.even(x))) is None + assert ask(Equivalent(Q.positive(x), Q.integer(x)), Q.integer(x)) is None + assert ask(Q.real(x) | Q.integer(x), Q.real(x) | Q.integer(x)) is True + +def test_tautology(): + assert ask(Q.real(x) | ~Q.real(x)) is True + assert ask(Q.real(x) & ~Q.real(x)) is False + +def test_composite_assumptions(): + assert ask(Q.real(x), Q.real(x) & Q.real(y)) is True + assert ask(Q.positive(x), Q.positive(x) | Q.positive(y)) is None + assert ask(Q.positive(x), Q.real(x) >> Q.positive(y)) is None + assert ask(Q.real(x), ~(Q.real(x) >> Q.real(y))) is True + +def test_key_extensibility(): + """test that you can add keys to the ask system at runtime""" + # make sure the key is not defined + raises(AttributeError, lambda: ask(Q.my_key(x))) + + # Old handler system + class MyAskHandler(AskHandler): + @staticmethod + def Symbol(expr, assumptions): + return True + try: + with warns_deprecated_sympy(): + register_handler('my_key', MyAskHandler) + with warns_deprecated_sympy(): + assert ask(Q.my_key(x)) is True + with warns_deprecated_sympy(): + assert ask(Q.my_key(x + 1)) is None + finally: + # We have to disable the stacklevel testing here because this raises + # the warning twice from two different places + with warns_deprecated_sympy(): + remove_handler('my_key', MyAskHandler) + del Q.my_key + raises(AttributeError, lambda: ask(Q.my_key(x))) + + # New handler system + class MyPredicate(Predicate): + pass + try: + Q.my_key = MyPredicate() + @Q.my_key.register(Symbol) + def _(expr, assumptions): + return True + assert ask(Q.my_key(x)) is True + assert ask(Q.my_key(x+1)) is None + finally: + del Q.my_key + raises(AttributeError, lambda: ask(Q.my_key(x))) + + +def test_type_extensibility(): + """test that new types can be added to the ask system at runtime + """ + from sympy.core import Basic + + class MyType(Basic): + pass + + @Q.prime.register(MyType) + def _(expr, assumptions): + return True + + assert ask(Q.prime(MyType())) is True + + +def test_single_fact_lookup(): + known_facts = And(Implies(Q.integer, Q.rational), + Implies(Q.rational, Q.real), + Implies(Q.real, Q.complex)) + known_facts_keys = {Q.integer, Q.rational, Q.real, Q.complex} + + known_facts_cnf = to_cnf(known_facts) + mapping = single_fact_lookup(known_facts_keys, known_facts_cnf) + + assert mapping[Q.rational] == {Q.real, Q.rational, Q.complex} + + +def test_generate_known_facts_dict(): + known_facts = And(Implies(Q.integer(x), Q.rational(x)), + Implies(Q.rational(x), Q.real(x)), + Implies(Q.real(x), Q.complex(x))) + known_facts_keys = {Q.integer(x), Q.rational(x), Q.real(x), Q.complex(x)} + + assert generate_known_facts_dict(known_facts_keys, known_facts) == \ + {Q.complex: ({Q.complex}, set()), + Q.integer: ({Q.complex, Q.integer, Q.rational, Q.real}, set()), + Q.rational: ({Q.complex, Q.rational, Q.real}, set()), + Q.real: ({Q.complex, Q.real}, set())} + + +@slow +def test_known_facts_consistent(): + """"Test that ask_generated.py is up-to-date""" + x = Symbol('x') + fact = get_known_facts(x) + # test cnf clauses of fact between unary predicates + cnf = CNF.to_CNF(fact) + clauses = set() + clauses.update(frozenset(Literal(lit.arg.function, lit.is_Not) for lit in sorted(cl, key=str)) for cl in cnf.clauses) + assert get_all_known_facts() == clauses + # test dictionary of fact between unary predicates + keys = [pred(x) for pred in get_known_facts_keys()] + mapping = generate_known_facts_dict(keys, fact) + assert get_known_facts_dict() == mapping + + +def test_Add_queries(): + assert ask(Q.prime(12345678901234567890 + (cos(1)**2 + sin(1)**2))) is True + assert ask(Q.even(Add(S(2), S(2), evaluate=False))) is True + assert ask(Q.prime(Add(S(2), S(2), evaluate=False))) is False + assert ask(Q.integer(Add(S(2), S(2), evaluate=False))) is True + + +def test_positive_assuming(): + with assuming(Q.positive(x + 1)): + assert not ask(Q.positive(x)) + + +def test_issue_5421(): + raises(TypeError, lambda: ask(pi/log(x), Q.real)) + + +def test_issue_3906(): + raises(TypeError, lambda: ask(Q.positive)) + + +def test_issue_5833(): + assert ask(Q.positive(log(x)**2), Q.positive(x)) is None + assert ask(~Q.negative(log(x)**2), Q.positive(x)) is True + + +def test_issue_6732(): + raises(ValueError, lambda: ask(Q.positive(x), Q.positive(x) & Q.negative(x))) + raises(ValueError, lambda: ask(Q.negative(x), Q.positive(x) & Q.negative(x))) + + +def test_issue_7246(): + assert ask(Q.positive(atan(p)), Q.positive(p)) is True + assert ask(Q.positive(atan(p)), Q.negative(p)) is False + assert ask(Q.positive(atan(p)), Q.zero(p)) is False + assert ask(Q.positive(atan(x))) is None + + assert ask(Q.positive(asin(p)), Q.positive(p)) is None + assert ask(Q.positive(asin(p)), Q.zero(p)) is None + assert ask(Q.positive(asin(Rational(1, 7)))) is True + assert ask(Q.positive(asin(x)), Q.positive(x) & Q.nonpositive(x - 1)) is True + assert ask(Q.positive(asin(x)), Q.negative(x) & Q.nonnegative(x + 1)) is False + + assert ask(Q.positive(acos(p)), Q.positive(p)) is None + assert ask(Q.positive(acos(Rational(1, 7)))) is True + assert ask(Q.positive(acos(x)), Q.nonnegative(x + 1) & Q.nonpositive(x - 1)) is True + assert ask(Q.positive(acos(x)), Q.nonnegative(x - 1)) is None + + assert ask(Q.positive(acot(x)), Q.positive(x)) is True + assert ask(Q.positive(acot(x)), Q.real(x)) is True + assert ask(Q.positive(acot(x)), Q.imaginary(x)) is False + assert ask(Q.positive(acot(x))) is None + + +@XFAIL +def test_issue_7246_failing(): + #Move this test to test_issue_7246 once + #the new assumptions module is improved. + assert ask(Q.positive(acos(x)), Q.zero(x)) is True + + +def test_check_old_assumption(): + x = symbols('x', real=True) + assert ask(Q.real(x)) is True + assert ask(Q.imaginary(x)) is False + assert ask(Q.complex(x)) is True + + x = symbols('x', imaginary=True) + assert ask(Q.real(x)) is False + assert ask(Q.imaginary(x)) is True + assert ask(Q.complex(x)) is True + + x = symbols('x', complex=True) + assert ask(Q.real(x)) is None + assert ask(Q.complex(x)) is True + + x = symbols('x', positive=True) + assert ask(Q.positive(x)) is True + assert ask(Q.negative(x)) is False + assert ask(Q.real(x)) is True + + x = symbols('x', commutative=False) + assert ask(Q.commutative(x)) is False + + x = symbols('x', negative=True) + assert ask(Q.positive(x)) is False + assert ask(Q.negative(x)) is True + + x = symbols('x', nonnegative=True) + assert ask(Q.negative(x)) is False + assert ask(Q.positive(x)) is None + assert ask(Q.zero(x)) is None + + x = symbols('x', finite=True) + assert ask(Q.finite(x)) is True + + x = symbols('x', prime=True) + assert ask(Q.prime(x)) is True + assert ask(Q.composite(x)) is False + + x = symbols('x', composite=True) + assert ask(Q.prime(x)) is False + assert ask(Q.composite(x)) is True + + x = symbols('x', even=True) + assert ask(Q.even(x)) is True + assert ask(Q.odd(x)) is False + + x = symbols('x', odd=True) + assert ask(Q.even(x)) is False + assert ask(Q.odd(x)) is True + + x = symbols('x', nonzero=True) + assert ask(Q.nonzero(x)) is True + assert ask(Q.zero(x)) is False + + x = symbols('x', zero=True) + assert ask(Q.zero(x)) is True + + x = symbols('x', integer=True) + assert ask(Q.integer(x)) is True + + x = symbols('x', rational=True) + assert ask(Q.rational(x)) is True + assert ask(Q.irrational(x)) is False + + x = symbols('x', irrational=True) + assert ask(Q.irrational(x)) is True + assert ask(Q.rational(x)) is False + + +def test_issue_9636(): + assert ask(Q.integer(1.0)) is None + assert ask(Q.prime(3.0)) is None + assert ask(Q.composite(4.0)) is None + assert ask(Q.even(2.0)) is None + assert ask(Q.odd(3.0)) is None + + +def test_autosimp_used_to_fail(): + # See issue #9807 + assert ask(Q.imaginary(0**I)) is None + assert ask(Q.imaginary(0**(-I))) is None + assert ask(Q.real(0**I)) is None + assert ask(Q.real(0**(-I))) is None + + +def test_custom_AskHandler(): + from sympy.logic.boolalg import conjuncts + + # Old handler system + class MersenneHandler(AskHandler): + @staticmethod + def Integer(expr, assumptions): + if ask(Q.integer(log(expr + 1, 2))): + return True + @staticmethod + def Symbol(expr, assumptions): + if expr in conjuncts(assumptions): + return True + try: + with warns_deprecated_sympy(): + register_handler('mersenne', MersenneHandler) + n = Symbol('n', integer=True) + with warns_deprecated_sympy(): + assert ask(Q.mersenne(7)) + with warns_deprecated_sympy(): + assert ask(Q.mersenne(n), Q.mersenne(n)) + finally: + del Q.mersenne + + # New handler system + class MersennePredicate(Predicate): + pass + try: + Q.mersenne = MersennePredicate() + @Q.mersenne.register(Integer) + def _(expr, assumptions): + if ask(Q.integer(log(expr + 1, 2))): + return True + @Q.mersenne.register(Symbol) + def _(expr, assumptions): + if expr in conjuncts(assumptions): + return True + assert ask(Q.mersenne(7)) + assert ask(Q.mersenne(n), Q.mersenne(n)) + finally: + del Q.mersenne + + +def test_polyadic_predicate(): + + class SexyPredicate(Predicate): + pass + try: + Q.sexyprime = SexyPredicate() + + @Q.sexyprime.register(Integer, Integer) + def _(int1, int2, assumptions): + args = sorted([int1, int2]) + if not all(ask(Q.prime(a), assumptions) for a in args): + return False + return args[1] - args[0] == 6 + + @Q.sexyprime.register(Integer, Integer, Integer) + def _(int1, int2, int3, assumptions): + args = sorted([int1, int2, int3]) + if not all(ask(Q.prime(a), assumptions) for a in args): + return False + return args[2] - args[1] == 6 and args[1] - args[0] == 6 + + assert ask(Q.sexyprime(5, 11)) + assert ask(Q.sexyprime(7, 13, 19)) + finally: + del Q.sexyprime + + +def test_Predicate_handler_is_unique(): + + # Undefined predicate does not have a handler + assert Predicate('mypredicate').handler is None + + # Handler of defined predicate is unique to the class + class MyPredicate(Predicate): + pass + mp1 = MyPredicate(Str('mp1')) + mp2 = MyPredicate(Str('mp2')) + assert mp1.handler is mp2.handler + + +def test_relational(): + assert ask(Q.eq(x, 0), Q.zero(x)) + assert not ask(Q.eq(x, 0), Q.nonzero(x)) + assert not ask(Q.ne(x, 0), Q.zero(x)) + assert ask(Q.ne(x, 0), Q.nonzero(x)) + + +def test_issue_25221(): + assert ask(Q.transcendental(x), Q.algebraic(x) | Q.positive(y,y)) is None + assert ask(Q.transcendental(x), Q.algebraic(x) | (0 > y)) is None + assert ask(Q.transcendental(x), Q.algebraic(x) | Q.gt(0,y)) is None + + +def test_issue_27440(): + nan = S.NaN + assert ask(Q.negative(nan)) is None diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_refine.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_refine.py new file mode 100644 index 0000000000000000000000000000000000000000..81533a88b232cd5c3cfb9be17d09dad404d679dc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_refine.py @@ -0,0 +1,227 @@ +from sympy.assumptions.ask import Q +from sympy.assumptions.refine import refine +from sympy.core.expr import Expr +from sympy.core.numbers import (I, Rational, nan, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import (Abs, arg, im, re, sign) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (atan, atan2) +from sympy.abc import w, x, y, z +from sympy.core.relational import Eq, Ne +from sympy.functions.elementary.piecewise import Piecewise +from sympy.matrices.expressions.matexpr import MatrixSymbol + + +def test_Abs(): + assert refine(Abs(x), Q.positive(x)) == x + assert refine(1 + Abs(x), Q.positive(x)) == 1 + x + assert refine(Abs(x), Q.negative(x)) == -x + assert refine(1 + Abs(x), Q.negative(x)) == 1 - x + + assert refine(Abs(x**2)) != x**2 + assert refine(Abs(x**2), Q.real(x)) == x**2 + + +def test_pow1(): + assert refine((-1)**x, Q.even(x)) == 1 + assert refine((-1)**x, Q.odd(x)) == -1 + assert refine((-2)**x, Q.even(x)) == 2**x + + # nested powers + assert refine(sqrt(x**2)) != Abs(x) + assert refine(sqrt(x**2), Q.complex(x)) != Abs(x) + assert refine(sqrt(x**2), Q.real(x)) == Abs(x) + assert refine(sqrt(x**2), Q.positive(x)) == x + assert refine((x**3)**Rational(1, 3)) != x + + assert refine((x**3)**Rational(1, 3), Q.real(x)) != x + assert refine((x**3)**Rational(1, 3), Q.positive(x)) == x + + assert refine(sqrt(1/x), Q.real(x)) != 1/sqrt(x) + assert refine(sqrt(1/x), Q.positive(x)) == 1/sqrt(x) + + # powers of (-1) + assert refine((-1)**(x + y), Q.even(x)) == (-1)**y + assert refine((-1)**(x + y + z), Q.odd(x) & Q.odd(z)) == (-1)**y + assert refine((-1)**(x + y + 1), Q.odd(x)) == (-1)**y + assert refine((-1)**(x + y + 2), Q.odd(x)) == (-1)**(y + 1) + assert refine((-1)**(x + 3)) == (-1)**(x + 1) + + # continuation + assert refine((-1)**((-1)**x/2 - S.Half), Q.integer(x)) == (-1)**x + assert refine((-1)**((-1)**x/2 + S.Half), Q.integer(x)) == (-1)**(x + 1) + assert refine((-1)**((-1)**x/2 + 5*S.Half), Q.integer(x)) == (-1)**(x + 1) + + +def test_pow2(): + assert refine((-1)**((-1)**x/2 - 7*S.Half), Q.integer(x)) == (-1)**(x + 1) + assert refine((-1)**((-1)**x/2 - 9*S.Half), Q.integer(x)) == (-1)**x + + # powers of Abs + assert refine(Abs(x)**2, Q.real(x)) == x**2 + assert refine(Abs(x)**3, Q.real(x)) == Abs(x)**3 + assert refine(Abs(x)**2) == Abs(x)**2 + + +def test_exp(): + x = Symbol('x', integer=True) + assert refine(exp(pi*I*2*x)) == 1 + assert refine(exp(pi*I*2*(x + S.Half))) == -1 + assert refine(exp(pi*I*2*(x + Rational(1, 4)))) == I + assert refine(exp(pi*I*2*(x + Rational(3, 4)))) == -I + + +def test_Piecewise(): + assert refine(Piecewise((1, x < 0), (3, True)), (x < 0)) == 1 + assert refine(Piecewise((1, x < 0), (3, True)), ~(x < 0)) == 3 + assert refine(Piecewise((1, x < 0), (3, True)), (y < 0)) == \ + Piecewise((1, x < 0), (3, True)) + assert refine(Piecewise((1, x > 0), (3, True)), (x > 0)) == 1 + assert refine(Piecewise((1, x > 0), (3, True)), ~(x > 0)) == 3 + assert refine(Piecewise((1, x > 0), (3, True)), (y > 0)) == \ + Piecewise((1, x > 0), (3, True)) + assert refine(Piecewise((1, x <= 0), (3, True)), (x <= 0)) == 1 + assert refine(Piecewise((1, x <= 0), (3, True)), ~(x <= 0)) == 3 + assert refine(Piecewise((1, x <= 0), (3, True)), (y <= 0)) == \ + Piecewise((1, x <= 0), (3, True)) + assert refine(Piecewise((1, x >= 0), (3, True)), (x >= 0)) == 1 + assert refine(Piecewise((1, x >= 0), (3, True)), ~(x >= 0)) == 3 + assert refine(Piecewise((1, x >= 0), (3, True)), (y >= 0)) == \ + Piecewise((1, x >= 0), (3, True)) + assert refine(Piecewise((1, Eq(x, 0)), (3, True)), (Eq(x, 0)))\ + == 1 + assert refine(Piecewise((1, Eq(x, 0)), (3, True)), (Eq(0, x)))\ + == 1 + assert refine(Piecewise((1, Eq(x, 0)), (3, True)), ~(Eq(x, 0)))\ + == 3 + assert refine(Piecewise((1, Eq(x, 0)), (3, True)), ~(Eq(0, x)))\ + == 3 + assert refine(Piecewise((1, Eq(x, 0)), (3, True)), (Eq(y, 0)))\ + == Piecewise((1, Eq(x, 0)), (3, True)) + assert refine(Piecewise((1, Ne(x, 0)), (3, True)), (Ne(x, 0)))\ + == 1 + assert refine(Piecewise((1, Ne(x, 0)), (3, True)), ~(Ne(x, 0)))\ + == 3 + assert refine(Piecewise((1, Ne(x, 0)), (3, True)), (Ne(y, 0)))\ + == Piecewise((1, Ne(x, 0)), (3, True)) + + +def test_atan2(): + assert refine(atan2(y, x), Q.real(y) & Q.positive(x)) == atan(y/x) + assert refine(atan2(y, x), Q.negative(y) & Q.positive(x)) == atan(y/x) + assert refine(atan2(y, x), Q.negative(y) & Q.negative(x)) == atan(y/x) - pi + assert refine(atan2(y, x), Q.positive(y) & Q.negative(x)) == atan(y/x) + pi + assert refine(atan2(y, x), Q.zero(y) & Q.negative(x)) == pi + assert refine(atan2(y, x), Q.positive(y) & Q.zero(x)) == pi/2 + assert refine(atan2(y, x), Q.negative(y) & Q.zero(x)) == -pi/2 + assert refine(atan2(y, x), Q.zero(y) & Q.zero(x)) is nan + + +def test_re(): + assert refine(re(x), Q.real(x)) == x + assert refine(re(x), Q.imaginary(x)) is S.Zero + assert refine(re(x+y), Q.real(x) & Q.real(y)) == x + y + assert refine(re(x+y), Q.real(x) & Q.imaginary(y)) == x + assert refine(re(x*y), Q.real(x) & Q.real(y)) == x * y + assert refine(re(x*y), Q.real(x) & Q.imaginary(y)) == 0 + assert refine(re(x*y*z), Q.real(x) & Q.real(y) & Q.real(z)) == x * y * z + + +def test_im(): + assert refine(im(x), Q.imaginary(x)) == -I*x + assert refine(im(x), Q.real(x)) is S.Zero + assert refine(im(x+y), Q.imaginary(x) & Q.imaginary(y)) == -I*x - I*y + assert refine(im(x+y), Q.real(x) & Q.imaginary(y)) == -I*y + assert refine(im(x*y), Q.imaginary(x) & Q.real(y)) == -I*x*y + assert refine(im(x*y), Q.imaginary(x) & Q.imaginary(y)) == 0 + assert refine(im(1/x), Q.imaginary(x)) == -I/x + assert refine(im(x*y*z), Q.imaginary(x) & Q.imaginary(y) + & Q.imaginary(z)) == -I*x*y*z + + +def test_complex(): + assert refine(re(1/(x + I*y)), Q.real(x) & Q.real(y)) == \ + x/(x**2 + y**2) + assert refine(im(1/(x + I*y)), Q.real(x) & Q.real(y)) == \ + -y/(x**2 + y**2) + assert refine(re((w + I*x) * (y + I*z)), Q.real(w) & Q.real(x) & Q.real(y) + & Q.real(z)) == w*y - x*z + assert refine(im((w + I*x) * (y + I*z)), Q.real(w) & Q.real(x) & Q.real(y) + & Q.real(z)) == w*z + x*y + + +def test_sign(): + x = Symbol('x', real = True) + assert refine(sign(x), Q.positive(x)) == 1 + assert refine(sign(x), Q.negative(x)) == -1 + assert refine(sign(x), Q.zero(x)) == 0 + assert refine(sign(x), True) == sign(x) + assert refine(sign(Abs(x)), Q.nonzero(x)) == 1 + + x = Symbol('x', imaginary=True) + assert refine(sign(x), Q.positive(im(x))) == S.ImaginaryUnit + assert refine(sign(x), Q.negative(im(x))) == -S.ImaginaryUnit + assert refine(sign(x), True) == sign(x) + + x = Symbol('x', complex=True) + assert refine(sign(x), Q.zero(x)) == 0 + +def test_arg(): + x = Symbol('x', complex = True) + assert refine(arg(x), Q.positive(x)) == 0 + assert refine(arg(x), Q.negative(x)) == pi + +def test_func_args(): + class MyClass(Expr): + # A class with nontrivial .func + + def __init__(self, *args): + self.my_member = "" + + @property + def func(self): + def my_func(*args): + obj = MyClass(*args) + obj.my_member = self.my_member + return obj + return my_func + + x = MyClass() + x.my_member = "A very important value" + assert x.my_member == refine(x).my_member + +def test_issue_refine_9384(): + assert refine(Piecewise((1, x < 0), (0, True)), Q.positive(x)) == 0 + assert refine(Piecewise((1, x < 0), (0, True)), Q.negative(x)) == 1 + assert refine(Piecewise((1, x > 0), (0, True)), Q.positive(x)) == 1 + assert refine(Piecewise((1, x > 0), (0, True)), Q.negative(x)) == 0 + + +def test_eval_refine(): + class MockExpr(Expr): + def _eval_refine(self, assumptions): + return True + + mock_obj = MockExpr() + assert refine(mock_obj) + +def test_refine_issue_12724(): + expr1 = refine(Abs(x * y), Q.positive(x)) + expr2 = refine(Abs(x * y * z), Q.positive(x)) + assert expr1 == x * Abs(y) + assert expr2 == x * Abs(y * z) + y1 = Symbol('y1', real = True) + expr3 = refine(Abs(x * y1**2 * z), Q.positive(x)) + assert expr3 == x * y1**2 * Abs(z) + + +def test_matrixelement(): + x = MatrixSymbol('x', 3, 3) + i = Symbol('i', positive = True) + j = Symbol('j', positive = True) + assert refine(x[0, 1], Q.symmetric(x)) == x[0, 1] + assert refine(x[1, 0], Q.symmetric(x)) == x[0, 1] + assert refine(x[i, j], Q.symmetric(x)) == x[j, i] + assert refine(x[j, i], Q.symmetric(x)) == x[j, i] diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_rel_queries.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_rel_queries.py new file mode 100644 index 0000000000000000000000000000000000000000..46fe3a35dc1adb23668e88d5794fe1c0ab22f33a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_rel_queries.py @@ -0,0 +1,172 @@ +from sympy.assumptions.lra_satask import lra_satask +from sympy.logic.algorithms.lra_theory import UnhandledInput +from sympy.assumptions.ask import Q, ask + +from sympy.core import symbols, Symbol +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.core.numbers import I + +from sympy.testing.pytest import raises, XFAIL +x, y, z = symbols("x y z", real=True) + +def test_lra_satask(): + im = Symbol('im', imaginary=True) + + # test preprocessing of unequalities is working correctly + assert lra_satask(Q.eq(x, 1), ~Q.ne(x, 0)) is False + assert lra_satask(Q.eq(x, 0), ~Q.ne(x, 0)) is True + assert lra_satask(~Q.ne(x, 0), Q.eq(x, 0)) is True + assert lra_satask(~Q.eq(x, 0), Q.eq(x, 0)) is False + assert lra_satask(Q.ne(x, 0), Q.eq(x, 0)) is False + + # basic tests + assert lra_satask(Q.ne(x, x)) is False + assert lra_satask(Q.eq(x, x)) is True + assert lra_satask(Q.gt(x, 0), Q.gt(x, 1)) is True + + # check that True/False are handled + assert lra_satask(Q.gt(x, 0), True) is None + assert raises(ValueError, lambda: lra_satask(Q.gt(x, 0), False)) + + # check imaginary numbers are correctly handled + # (im * I).is_real returns True so this is an edge case + raises(UnhandledInput, lambda: lra_satask(Q.gt(im * I, 0), Q.gt(im * I, 0))) + + # check matrix inputs + X = MatrixSymbol("X", 2, 2) + raises(UnhandledInput, lambda: lra_satask(Q.lt(X, 2) & Q.gt(X, 3))) + + +def test_old_assumptions(): + # test unhandled old assumptions + w = symbols("w") + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", rational=False, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", odd=True, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", even=True, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", prime=True, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", composite=True, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", integer=True, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + w = symbols("w", integer=False, real=True) + raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3))) + + # test handled + w = symbols("w", positive=True, real=True) + assert lra_satask(Q.le(w, 0)) is False + assert lra_satask(Q.gt(w, 0)) is True + w = symbols("w", negative=True, real=True) + assert lra_satask(Q.lt(w, 0)) is True + assert lra_satask(Q.ge(w, 0)) is False + w = symbols("w", zero=True, real=True) + assert lra_satask(Q.eq(w, 0)) is True + assert lra_satask(Q.ne(w, 0)) is False + w = symbols("w", nonzero=True, real=True) + assert lra_satask(Q.ne(w, 0)) is True + assert lra_satask(Q.eq(w, 1)) is None + w = symbols("w", nonpositive=True, real=True) + assert lra_satask(Q.le(w, 0)) is True + assert lra_satask(Q.gt(w, 0)) is False + w = symbols("w", nonnegative=True, real=True) + assert lra_satask(Q.ge(w, 0)) is True + assert lra_satask(Q.lt(w, 0)) is False + + +def test_rel_queries(): + assert ask(Q.lt(x, 2) & Q.gt(x, 3)) is False + assert ask(Q.positive(x - z), (x > y) & (y > z)) is True + assert ask(x + y > 2, (x < 0) & (y <0)) is False + assert ask(x > z, (x > y) & (y > z)) is True + + +def test_unhandled_queries(): + X = MatrixSymbol("X", 2, 2) + assert ask(Q.lt(X, 2) & Q.gt(X, 3)) is None + + +def test_all_pred(): + # test usable pred + assert lra_satask(Q.extended_positive(x), (x > 2)) is True + assert lra_satask(Q.positive_infinite(x)) is False + assert lra_satask(Q.negative_infinite(x)) is False + + # test disallowed pred + raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.prime(x))) + raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.composite(x))) + raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.odd(x))) + raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.even(x))) + raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.integer(x))) + + +def test_number_line_properties(): + # From: + # https://en.wikipedia.org/wiki/Inequality_(mathematics)#Properties_on_the_number_line + + a, b, c = symbols("a b c", real=True) + + # Transitivity + # If a <= b and b <= c, then a <= c. + assert ask(a <= c, (a <= b) & (b <= c)) is True + # If a <= b and b < c, then a < c. + assert ask(a < c, (a <= b) & (b < c)) is True + # If a < b and b <= c, then a < c. + assert ask(a < c, (a < b) & (b <= c)) is True + + # Addition and subtraction + # If a <= b, then a + c <= b + c and a - c <= b - c. + assert ask(a + c <= b + c, a <= b) is True + assert ask(a - c <= b - c, a <= b) is True + + +@XFAIL +def test_failing_number_line_properties(): + # From: + # https://en.wikipedia.org/wiki/Inequality_(mathematics)#Properties_on_the_number_line + + a, b, c = symbols("a b c", real=True) + + # Multiplication and division + # If a <= b and c > 0, then ac <= bc and a/c <= b/c. (True for non-zero c) + assert ask(a*c <= b*c, (a <= b) & (c > 0) & ~ Q.zero(c)) is True + assert ask(a/c <= b/c, (a <= b) & (c > 0) & ~ Q.zero(c)) is True + # If a <= b and c < 0, then ac >= bc and a/c >= b/c. (True for non-zero c) + assert ask(a*c >= b*c, (a <= b) & (c < 0) & ~ Q.zero(c)) is True + assert ask(a/c >= b/c, (a <= b) & (c < 0) & ~ Q.zero(c)) is True + + # Additive inverse + # If a <= b, then -a >= -b. + assert ask(-a >= -b, a <= b) is True + + # Multiplicative inverse + # For a, b that are both negative or both positive: + # If a <= b, then 1/a >= 1/b . + assert ask(1/a >= 1/b, (a <= b) & Q.positive(x) & Q.positive(b)) is True + assert ask(1/a >= 1/b, (a <= b) & Q.negative(x) & Q.negative(b)) is True + + +def test_equality(): + # test symmetry and reflexivity + assert ask(Q.eq(x, x)) is True + assert ask(Q.eq(y, x), Q.eq(x, y)) is True + assert ask(Q.eq(y, x), ~Q.eq(z, z) | Q.eq(x, y)) is True + + # test transitivity + assert ask(Q.eq(x,z), Q.eq(x,y) & Q.eq(y,z)) is True + + +@XFAIL +def test_equality_failing(): + # Note that implementing the substitution property of equality + # most likely requires a redesign of the new assumptions. + # See issue #25485 for why this is the case and general ideas + # about how things could be redesigned. + + # test substitution property + assert ask(Q.prime(x), Q.eq(x, y) & Q.prime(y)) is True + assert ask(Q.real(x), Q.eq(x, y) & Q.real(y)) is True + assert ask(Q.imaginary(x), Q.eq(x, y) & Q.imaginary(y)) is True diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_satask.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_satask.py new file mode 100644 index 0000000000000000000000000000000000000000..5831b69e3e6bf2b1a906d1140967510c2ea8b630 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_satask.py @@ -0,0 +1,378 @@ +from sympy.assumptions.ask import Q +from sympy.assumptions.assume import assuming +from sympy.core.numbers import (I, pi) +from sympy.core.relational import (Eq, Gt) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import Abs +from sympy.logic.boolalg import Implies +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.assumptions.cnf import CNF, Literal +from sympy.assumptions.satask import (satask, extract_predargs, + get_relevant_clsfacts) + +from sympy.testing.pytest import raises, XFAIL + + +x, y, z = symbols('x y z') + + +def test_satask(): + # No relevant facts + assert satask(Q.real(x), Q.real(x)) is True + assert satask(Q.real(x), ~Q.real(x)) is False + assert satask(Q.real(x)) is None + + assert satask(Q.real(x), Q.positive(x)) is True + assert satask(Q.positive(x), Q.real(x)) is None + assert satask(Q.real(x), ~Q.positive(x)) is None + assert satask(Q.positive(x), ~Q.real(x)) is False + + raises(ValueError, lambda: satask(Q.real(x), Q.real(x) & ~Q.real(x))) + + with assuming(Q.positive(x)): + assert satask(Q.real(x)) is True + assert satask(~Q.positive(x)) is False + raises(ValueError, lambda: satask(Q.real(x), ~Q.positive(x))) + + assert satask(Q.zero(x), Q.nonzero(x)) is False + assert satask(Q.positive(x), Q.zero(x)) is False + assert satask(Q.real(x), Q.zero(x)) is True + assert satask(Q.zero(x), Q.zero(x*y)) is None + assert satask(Q.zero(x*y), Q.zero(x)) + + +def test_zero(): + """ + Everything in this test doesn't work with the ask handlers, and most + things would be very difficult or impossible to make work under that + model. + + """ + assert satask(Q.zero(x) | Q.zero(y), Q.zero(x*y)) is True + assert satask(Q.zero(x*y), Q.zero(x) | Q.zero(y)) is True + + assert satask(Implies(Q.zero(x), Q.zero(x*y))) is True + + # This one in particular requires computing the fixed-point of the + # relevant facts, because going from Q.nonzero(x*y) -> ~Q.zero(x*y) and + # Q.zero(x*y) -> Equivalent(Q.zero(x*y), Q.zero(x) | Q.zero(y)) takes two + # steps. + assert satask(Q.zero(x) | Q.zero(y), Q.nonzero(x*y)) is False + + assert satask(Q.zero(x), Q.zero(x**2)) is True + + +def test_zero_positive(): + assert satask(Q.zero(x + y), Q.positive(x) & Q.positive(y)) is False + assert satask(Q.positive(x) & Q.positive(y), Q.zero(x + y)) is False + assert satask(Q.nonzero(x + y), Q.positive(x) & Q.positive(y)) is True + assert satask(Q.positive(x) & Q.positive(y), Q.nonzero(x + y)) is None + + # This one requires several levels of forward chaining + assert satask(Q.zero(x*(x + y)), Q.positive(x) & Q.positive(y)) is False + + assert satask(Q.positive(pi*x*y + 1), Q.positive(x) & Q.positive(y)) is True + assert satask(Q.positive(pi*x*y - 5), Q.positive(x) & Q.positive(y)) is None + + +def test_zero_pow(): + assert satask(Q.zero(x**y), Q.zero(x) & Q.positive(y)) is True + assert satask(Q.zero(x**y), Q.nonzero(x) & Q.zero(y)) is False + + assert satask(Q.zero(x), Q.zero(x**y)) is True + + assert satask(Q.zero(x**y), Q.zero(x)) is None + + +@XFAIL +# Requires correct Q.square calculation first +def test_invertible(): + A = MatrixSymbol('A', 5, 5) + B = MatrixSymbol('B', 5, 5) + assert satask(Q.invertible(A*B), Q.invertible(A) & Q.invertible(B)) is True + assert satask(Q.invertible(A), Q.invertible(A*B)) is True + assert satask(Q.invertible(A) & Q.invertible(B), Q.invertible(A*B)) is True + + +def test_prime(): + assert satask(Q.prime(5)) is True + assert satask(Q.prime(6)) is False + assert satask(Q.prime(-5)) is False + + assert satask(Q.prime(x*y), Q.integer(x) & Q.integer(y)) is None + assert satask(Q.prime(x*y), Q.prime(x) & Q.prime(y)) is False + + +def test_old_assump(): + assert satask(Q.positive(1)) is True + assert satask(Q.positive(-1)) is False + assert satask(Q.positive(0)) is False + assert satask(Q.positive(I)) is False + assert satask(Q.positive(pi)) is True + + assert satask(Q.negative(1)) is False + assert satask(Q.negative(-1)) is True + assert satask(Q.negative(0)) is False + assert satask(Q.negative(I)) is False + assert satask(Q.negative(pi)) is False + + assert satask(Q.zero(1)) is False + assert satask(Q.zero(-1)) is False + assert satask(Q.zero(0)) is True + assert satask(Q.zero(I)) is False + assert satask(Q.zero(pi)) is False + + assert satask(Q.nonzero(1)) is True + assert satask(Q.nonzero(-1)) is True + assert satask(Q.nonzero(0)) is False + assert satask(Q.nonzero(I)) is False + assert satask(Q.nonzero(pi)) is True + + assert satask(Q.nonpositive(1)) is False + assert satask(Q.nonpositive(-1)) is True + assert satask(Q.nonpositive(0)) is True + assert satask(Q.nonpositive(I)) is False + assert satask(Q.nonpositive(pi)) is False + + assert satask(Q.nonnegative(1)) is True + assert satask(Q.nonnegative(-1)) is False + assert satask(Q.nonnegative(0)) is True + assert satask(Q.nonnegative(I)) is False + assert satask(Q.nonnegative(pi)) is True + + +def test_rational_irrational(): + assert satask(Q.irrational(2)) is False + assert satask(Q.rational(2)) is True + assert satask(Q.irrational(pi)) is True + assert satask(Q.rational(pi)) is False + assert satask(Q.irrational(I)) is False + assert satask(Q.rational(I)) is False + + assert satask(Q.irrational(x*y*z), Q.irrational(x) & Q.irrational(y) & + Q.rational(z)) is None + assert satask(Q.irrational(x*y*z), Q.irrational(x) & Q.rational(y) & + Q.rational(z)) is True + assert satask(Q.irrational(pi*x*y), Q.rational(x) & Q.rational(y)) is True + + assert satask(Q.irrational(x + y + z), Q.irrational(x) & Q.irrational(y) & + Q.rational(z)) is None + assert satask(Q.irrational(x + y + z), Q.irrational(x) & Q.rational(y) & + Q.rational(z)) is True + assert satask(Q.irrational(pi + x + y), Q.rational(x) & Q.rational(y)) is True + + assert satask(Q.irrational(x*y*z), Q.rational(x) & Q.rational(y) & + Q.rational(z)) is False + assert satask(Q.rational(x*y*z), Q.rational(x) & Q.rational(y) & + Q.rational(z)) is True + + assert satask(Q.irrational(x + y + z), Q.rational(x) & Q.rational(y) & + Q.rational(z)) is False + assert satask(Q.rational(x + y + z), Q.rational(x) & Q.rational(y) & + Q.rational(z)) is True + + +def test_even_satask(): + assert satask(Q.even(2)) is True + assert satask(Q.even(3)) is False + + assert satask(Q.even(x*y), Q.even(x) & Q.odd(y)) is True + assert satask(Q.even(x*y), Q.even(x) & Q.integer(y)) is True + assert satask(Q.even(x*y), Q.even(x) & Q.even(y)) is True + assert satask(Q.even(x*y), Q.odd(x) & Q.odd(y)) is False + assert satask(Q.even(x*y), Q.even(x)) is None + assert satask(Q.even(x*y), Q.odd(x) & Q.integer(y)) is None + assert satask(Q.even(x*y), Q.odd(x) & Q.odd(y)) is False + + assert satask(Q.even(abs(x)), Q.even(x)) is True + assert satask(Q.even(abs(x)), Q.odd(x)) is False + assert satask(Q.even(x), Q.even(abs(x))) is None # x could be complex + + +def test_odd_satask(): + assert satask(Q.odd(2)) is False + assert satask(Q.odd(3)) is True + + assert satask(Q.odd(x*y), Q.even(x) & Q.odd(y)) is False + assert satask(Q.odd(x*y), Q.even(x) & Q.integer(y)) is False + assert satask(Q.odd(x*y), Q.even(x) & Q.even(y)) is False + assert satask(Q.odd(x*y), Q.odd(x) & Q.odd(y)) is True + assert satask(Q.odd(x*y), Q.even(x)) is None + assert satask(Q.odd(x*y), Q.odd(x) & Q.integer(y)) is None + assert satask(Q.odd(x*y), Q.odd(x) & Q.odd(y)) is True + + assert satask(Q.odd(abs(x)), Q.even(x)) is False + assert satask(Q.odd(abs(x)), Q.odd(x)) is True + assert satask(Q.odd(x), Q.odd(abs(x))) is None # x could be complex + + +def test_integer(): + assert satask(Q.integer(1)) is True + assert satask(Q.integer(S.Half)) is False + + assert satask(Q.integer(x + y), Q.integer(x) & Q.integer(y)) is True + assert satask(Q.integer(x + y), Q.integer(x)) is None + + assert satask(Q.integer(x + y), Q.integer(x) & ~Q.integer(y)) is False + assert satask(Q.integer(x + y + z), Q.integer(x) & Q.integer(y) & + ~Q.integer(z)) is False + assert satask(Q.integer(x + y + z), Q.integer(x) & ~Q.integer(y) & + ~Q.integer(z)) is None + assert satask(Q.integer(x + y + z), Q.integer(x) & ~Q.integer(y)) is None + assert satask(Q.integer(x + y), Q.integer(x) & Q.irrational(y)) is False + + assert satask(Q.integer(x*y), Q.integer(x) & Q.integer(y)) is True + assert satask(Q.integer(x*y), Q.integer(x)) is None + + assert satask(Q.integer(x*y), Q.integer(x) & ~Q.integer(y)) is None + assert satask(Q.integer(x*y), Q.integer(x) & ~Q.rational(y)) is False + assert satask(Q.integer(x*y*z), Q.integer(x) & Q.integer(y) & + ~Q.rational(z)) is False + assert satask(Q.integer(x*y*z), Q.integer(x) & ~Q.rational(y) & + ~Q.rational(z)) is None + assert satask(Q.integer(x*y*z), Q.integer(x) & ~Q.rational(y)) is None + assert satask(Q.integer(x*y), Q.integer(x) & Q.irrational(y)) is False + + +def test_abs(): + assert satask(Q.nonnegative(abs(x))) is True + assert satask(Q.positive(abs(x)), ~Q.zero(x)) is True + assert satask(Q.zero(x), ~Q.zero(abs(x))) is False + assert satask(Q.zero(x), Q.zero(abs(x))) is True + assert satask(Q.nonzero(x), ~Q.zero(abs(x))) is None # x could be complex + assert satask(Q.zero(abs(x)), Q.zero(x)) is True + + +def test_imaginary(): + assert satask(Q.imaginary(2*I)) is True + assert satask(Q.imaginary(x*y), Q.imaginary(x)) is None + assert satask(Q.imaginary(x*y), Q.imaginary(x) & Q.real(y)) is True + assert satask(Q.imaginary(x), Q.real(x)) is False + assert satask(Q.imaginary(1)) is False + assert satask(Q.imaginary(x*y), Q.real(x) & Q.real(y)) is False + assert satask(Q.imaginary(x + y), Q.real(x) & Q.real(y)) is False + + +def test_real(): + assert satask(Q.real(x*y), Q.real(x) & Q.real(y)) is True + assert satask(Q.real(x + y), Q.real(x) & Q.real(y)) is True + assert satask(Q.real(x*y*z), Q.real(x) & Q.real(y) & Q.real(z)) is True + assert satask(Q.real(x*y*z), Q.real(x) & Q.real(y)) is None + assert satask(Q.real(x*y*z), Q.real(x) & Q.real(y) & Q.imaginary(z)) is False + assert satask(Q.real(x + y + z), Q.real(x) & Q.real(y) & Q.real(z)) is True + assert satask(Q.real(x + y + z), Q.real(x) & Q.real(y)) is None + + +def test_pos_neg(): + assert satask(~Q.positive(x), Q.negative(x)) is True + assert satask(~Q.negative(x), Q.positive(x)) is True + assert satask(Q.positive(x + y), Q.positive(x) & Q.positive(y)) is True + assert satask(Q.negative(x + y), Q.negative(x) & Q.negative(y)) is True + assert satask(Q.positive(x + y), Q.negative(x) & Q.negative(y)) is False + assert satask(Q.negative(x + y), Q.positive(x) & Q.positive(y)) is False + + +def test_pow_pos_neg(): + assert satask(Q.nonnegative(x**2), Q.positive(x)) is True + assert satask(Q.nonpositive(x**2), Q.positive(x)) is False + assert satask(Q.positive(x**2), Q.positive(x)) is True + assert satask(Q.negative(x**2), Q.positive(x)) is False + assert satask(Q.real(x**2), Q.positive(x)) is True + + assert satask(Q.nonnegative(x**2), Q.negative(x)) is True + assert satask(Q.nonpositive(x**2), Q.negative(x)) is False + assert satask(Q.positive(x**2), Q.negative(x)) is True + assert satask(Q.negative(x**2), Q.negative(x)) is False + assert satask(Q.real(x**2), Q.negative(x)) is True + + assert satask(Q.nonnegative(x**2), Q.nonnegative(x)) is True + assert satask(Q.nonpositive(x**2), Q.nonnegative(x)) is None + assert satask(Q.positive(x**2), Q.nonnegative(x)) is None + assert satask(Q.negative(x**2), Q.nonnegative(x)) is False + assert satask(Q.real(x**2), Q.nonnegative(x)) is True + + assert satask(Q.nonnegative(x**2), Q.nonpositive(x)) is True + assert satask(Q.nonpositive(x**2), Q.nonpositive(x)) is None + assert satask(Q.positive(x**2), Q.nonpositive(x)) is None + assert satask(Q.negative(x**2), Q.nonpositive(x)) is False + assert satask(Q.real(x**2), Q.nonpositive(x)) is True + + assert satask(Q.nonnegative(x**3), Q.positive(x)) is True + assert satask(Q.nonpositive(x**3), Q.positive(x)) is False + assert satask(Q.positive(x**3), Q.positive(x)) is True + assert satask(Q.negative(x**3), Q.positive(x)) is False + assert satask(Q.real(x**3), Q.positive(x)) is True + + assert satask(Q.nonnegative(x**3), Q.negative(x)) is False + assert satask(Q.nonpositive(x**3), Q.negative(x)) is True + assert satask(Q.positive(x**3), Q.negative(x)) is False + assert satask(Q.negative(x**3), Q.negative(x)) is True + assert satask(Q.real(x**3), Q.negative(x)) is True + + assert satask(Q.nonnegative(x**3), Q.nonnegative(x)) is True + assert satask(Q.nonpositive(x**3), Q.nonnegative(x)) is None + assert satask(Q.positive(x**3), Q.nonnegative(x)) is None + assert satask(Q.negative(x**3), Q.nonnegative(x)) is False + assert satask(Q.real(x**3), Q.nonnegative(x)) is True + + assert satask(Q.nonnegative(x**3), Q.nonpositive(x)) is None + assert satask(Q.nonpositive(x**3), Q.nonpositive(x)) is True + assert satask(Q.positive(x**3), Q.nonpositive(x)) is False + assert satask(Q.negative(x**3), Q.nonpositive(x)) is None + assert satask(Q.real(x**3), Q.nonpositive(x)) is True + + # If x is zero, x**negative is not real. + assert satask(Q.nonnegative(x**-2), Q.nonpositive(x)) is None + assert satask(Q.nonpositive(x**-2), Q.nonpositive(x)) is None + assert satask(Q.positive(x**-2), Q.nonpositive(x)) is None + assert satask(Q.negative(x**-2), Q.nonpositive(x)) is None + assert satask(Q.real(x**-2), Q.nonpositive(x)) is None + + # We could deduce things for negative powers if x is nonzero, but it + # isn't implemented yet. + + +def test_prime_composite(): + assert satask(Q.prime(x), Q.composite(x)) is False + assert satask(Q.composite(x), Q.prime(x)) is False + assert satask(Q.composite(x), ~Q.prime(x)) is None + assert satask(Q.prime(x), ~Q.composite(x)) is None + # since 1 is neither prime nor composite the following should hold + assert satask(Q.prime(x), Q.integer(x) & Q.positive(x) & ~Q.composite(x)) is None + assert satask(Q.prime(2)) is True + assert satask(Q.prime(4)) is False + assert satask(Q.prime(1)) is False + assert satask(Q.composite(1)) is False + + +def test_extract_predargs(): + props = CNF.from_prop(Q.zero(Abs(x*y)) & Q.zero(x*y)) + assump = CNF.from_prop(Q.zero(x)) + context = CNF.from_prop(Q.zero(y)) + assert extract_predargs(props) == {Abs(x*y), x*y} + assert extract_predargs(props, assump) == {Abs(x*y), x*y, x} + assert extract_predargs(props, assump, context) == {Abs(x*y), x*y, x, y} + + props = CNF.from_prop(Eq(x, y)) + assump = CNF.from_prop(Gt(y, z)) + assert extract_predargs(props, assump) == {x, y, z} + + +def test_get_relevant_clsfacts(): + exprs = {Abs(x*y)} + exprs, facts = get_relevant_clsfacts(exprs) + assert exprs == {x*y} + assert facts.clauses == \ + {frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}), + frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}), + frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}), + frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}), + frozenset({Literal(Q.even(Abs(x*y)), False), + Literal(Q.odd(Abs(x*y)), False), + Literal(Q.odd(x*y), True)}), + frozenset({Literal(Q.even(Abs(x*y)), False), + Literal(Q.even(x*y), True), + Literal(Q.odd(Abs(x*y)), False)}), + frozenset({Literal(Q.positive(Abs(x*y)), False), + Literal(Q.zero(Abs(x*y)), False)})} diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_sathandlers.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_sathandlers.py new file mode 100644 index 0000000000000000000000000000000000000000..9d568ad8efe6ba7cf7f5eb03879ad6764c16e729 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_sathandlers.py @@ -0,0 +1,50 @@ +from sympy.assumptions.ask import Q +from sympy.core.basic import Basic +from sympy.core.expr import Expr +from sympy.core.mul import Mul +from sympy.core.symbol import symbols +from sympy.logic.boolalg import (And, Or) + +from sympy.assumptions.sathandlers import (ClassFactRegistry, allargs, + anyarg, exactlyonearg,) + +x, y, z = symbols('x y z') + + +def test_class_handler_registry(): + my_handler_registry = ClassFactRegistry() + + # The predicate doesn't matter here, so just pass + @my_handler_registry.register(Mul) + def fact1(expr): + pass + @my_handler_registry.multiregister(Expr) + def fact2(expr): + pass + + assert my_handler_registry[Basic] == (frozenset(), frozenset()) + assert my_handler_registry[Expr] == (frozenset(), frozenset({fact2})) + assert my_handler_registry[Mul] == (frozenset({fact1}), frozenset({fact2})) + + +def test_allargs(): + assert allargs(x, Q.zero(x), x*y) == And(Q.zero(x), Q.zero(y)) + assert allargs(x, Q.positive(x) | Q.negative(x), x*y) == And(Q.positive(x) | Q.negative(x), Q.positive(y) | Q.negative(y)) + + +def test_anyarg(): + assert anyarg(x, Q.zero(x), x*y) == Or(Q.zero(x), Q.zero(y)) + assert anyarg(x, Q.positive(x) & Q.negative(x), x*y) == \ + Or(Q.positive(x) & Q.negative(x), Q.positive(y) & Q.negative(y)) + + +def test_exactlyonearg(): + assert exactlyonearg(x, Q.zero(x), x*y) == \ + Or(Q.zero(x) & ~Q.zero(y), Q.zero(y) & ~Q.zero(x)) + assert exactlyonearg(x, Q.zero(x), x*y*z) == \ + Or(Q.zero(x) & ~Q.zero(y) & ~Q.zero(z), Q.zero(y) + & ~Q.zero(x) & ~Q.zero(z), Q.zero(z) & ~Q.zero(x) & ~Q.zero(y)) + assert exactlyonearg(x, Q.positive(x) | Q.negative(x), x*y) == \ + Or((Q.positive(x) | Q.negative(x)) & + ~(Q.positive(y) | Q.negative(y)), (Q.positive(y) | Q.negative(y)) & + ~(Q.positive(x) | Q.negative(x))) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/tests/test_wrapper.py b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..af9afd5d51fb1341e0b08149dc842b78a39c329b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/tests/test_wrapper.py @@ -0,0 +1,39 @@ +from sympy.assumptions.ask import Q +from sympy.assumptions.wrapper import (AssumptionsWrapper, is_infinite, + is_extended_real) +from sympy.core.symbol import Symbol +from sympy.core.assumptions import _assume_defined + + +def test_all_predicates(): + for fact in _assume_defined: + method_name = f'_eval_is_{fact}' + assert hasattr(AssumptionsWrapper, method_name) + + +def test_AssumptionsWrapper(): + x = Symbol('x', positive=True) + y = Symbol('y') + assert AssumptionsWrapper(x).is_positive + assert AssumptionsWrapper(y).is_positive is None + assert AssumptionsWrapper(y, Q.positive(y)).is_positive + + +def test_is_infinite(): + x = Symbol('x', infinite=True) + y = Symbol('y', infinite=False) + z = Symbol('z') + assert is_infinite(x) + assert not is_infinite(y) + assert is_infinite(z) is None + assert is_infinite(z, Q.infinite(z)) + + +def test_is_extended_real(): + x = Symbol('x', extended_real=True) + y = Symbol('y', extended_real=False) + z = Symbol('z') + assert is_extended_real(x) + assert not is_extended_real(y) + assert is_extended_real(z) is None + assert is_extended_real(z, Q.extended_real(z)) diff --git a/phivenv/Lib/site-packages/sympy/assumptions/wrapper.py b/phivenv/Lib/site-packages/sympy/assumptions/wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..cb06e9de770ed41a2b3d6fe63381ad1cb59acacc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/assumptions/wrapper.py @@ -0,0 +1,164 @@ +""" +Functions and wrapper object to call assumption property and predicate +query with same syntax. + +In SymPy, there are two assumption systems. Old assumption system is +defined in sympy/core/assumptions, and it can be accessed by attribute +such as ``x.is_even``. New assumption system is defined in +sympy/assumptions, and it can be accessed by predicates such as +``Q.even(x)``. + +Old assumption is fast, while new assumptions can freely take local facts. +In general, old assumption is used in evaluation method and new assumption +is used in refinement method. + +In most cases, both evaluation and refinement follow the same process, and +the only difference is which assumption system is used. This module provides +``is_[...]()`` functions and ``AssumptionsWrapper()`` class which allows +using two systems with same syntax so that parallel code implementation can be +avoided. + +Examples +======== + +For multiple use, use ``AssumptionsWrapper()``. + +>>> from sympy import Q, Symbol +>>> from sympy.assumptions.wrapper import AssumptionsWrapper +>>> x = Symbol('x') +>>> _x = AssumptionsWrapper(x, Q.even(x)) +>>> _x.is_integer +True +>>> _x.is_odd +False + +For single use, use ``is_[...]()`` functions. + +>>> from sympy.assumptions.wrapper import is_infinite +>>> a = Symbol('a') +>>> print(is_infinite(a)) +None +>>> is_infinite(a, Q.finite(a)) +False + +""" + +from sympy.assumptions import ask, Q +from sympy.core.basic import Basic +from sympy.core.sympify import _sympify + + +def make_eval_method(fact): + def getit(self): + pred = getattr(Q, fact) + ret = ask(pred(self.expr), self.assumptions) + return ret + return getit + + +# we subclass Basic to use the fact deduction and caching +class AssumptionsWrapper(Basic): + """ + Wrapper over ``Basic`` instances to call predicate query by + ``.is_[...]`` property + + Parameters + ========== + + expr : Basic + + assumptions : Boolean, optional + + Examples + ======== + + >>> from sympy import Q, Symbol + >>> from sympy.assumptions.wrapper import AssumptionsWrapper + >>> x = Symbol('x', even=True) + >>> AssumptionsWrapper(x).is_integer + True + >>> y = Symbol('y') + >>> AssumptionsWrapper(y, Q.even(y)).is_integer + True + + With ``AssumptionsWrapper``, both evaluation and refinement can be supported + by single implementation. + + >>> from sympy import Function + >>> class MyAbs(Function): + ... @classmethod + ... def eval(cls, x, assumptions=True): + ... _x = AssumptionsWrapper(x, assumptions) + ... if _x.is_nonnegative: + ... return x + ... if _x.is_negative: + ... return -x + ... def _eval_refine(self, assumptions): + ... return MyAbs.eval(self.args[0], assumptions) + >>> MyAbs(x) + MyAbs(x) + >>> MyAbs(x).refine(Q.positive(x)) + x + >>> MyAbs(Symbol('y', negative=True)) + -y + + """ + def __new__(cls, expr, assumptions=None): + if assumptions is None: + return expr + obj = super().__new__(cls, expr, _sympify(assumptions)) + obj.expr = expr + obj.assumptions = assumptions + return obj + + _eval_is_algebraic = make_eval_method("algebraic") + _eval_is_antihermitian = make_eval_method("antihermitian") + _eval_is_commutative = make_eval_method("commutative") + _eval_is_complex = make_eval_method("complex") + _eval_is_composite = make_eval_method("composite") + _eval_is_even = make_eval_method("even") + _eval_is_extended_negative = make_eval_method("extended_negative") + _eval_is_extended_nonnegative = make_eval_method("extended_nonnegative") + _eval_is_extended_nonpositive = make_eval_method("extended_nonpositive") + _eval_is_extended_nonzero = make_eval_method("extended_nonzero") + _eval_is_extended_positive = make_eval_method("extended_positive") + _eval_is_extended_real = make_eval_method("extended_real") + _eval_is_finite = make_eval_method("finite") + _eval_is_hermitian = make_eval_method("hermitian") + _eval_is_imaginary = make_eval_method("imaginary") + _eval_is_infinite = make_eval_method("infinite") + _eval_is_integer = make_eval_method("integer") + _eval_is_irrational = make_eval_method("irrational") + _eval_is_negative = make_eval_method("negative") + _eval_is_noninteger = make_eval_method("noninteger") + _eval_is_nonnegative = make_eval_method("nonnegative") + _eval_is_nonpositive = make_eval_method("nonpositive") + _eval_is_nonzero = make_eval_method("nonzero") + _eval_is_odd = make_eval_method("odd") + _eval_is_polar = make_eval_method("polar") + _eval_is_positive = make_eval_method("positive") + _eval_is_prime = make_eval_method("prime") + _eval_is_rational = make_eval_method("rational") + _eval_is_real = make_eval_method("real") + _eval_is_transcendental = make_eval_method("transcendental") + _eval_is_zero = make_eval_method("zero") + + +# one shot functions which are faster than AssumptionsWrapper + +def is_infinite(obj, assumptions=None): + if assumptions is None: + return obj.is_infinite + return ask(Q.infinite(obj), assumptions) + + +def is_extended_real(obj, assumptions=None): + if assumptions is None: + return obj.is_extended_real + return ask(Q.extended_real(obj), assumptions) + + +def is_extended_nonnegative(obj, assumptions=None): + if assumptions is None: + return obj.is_extended_nonnegative + return ask(Q.extended_nonnegative(obj), assumptions) diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/__init__.py b/phivenv/Lib/site-packages/sympy/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb75a475b4e69bdbe82958d9174371f4347be4e6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab5d4aaa695ac2a77914efb3f2af1a4ee89a2f1e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_discrete_log.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea5e65a3ba28a09aa49082ccc22924db7f85ac91 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_meijerint.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b478080565e7046bc98da1d733014b361794e46 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/benchmarks/__pycache__/bench_symbench.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/bench_discrete_log.py b/phivenv/Lib/site-packages/sympy/benchmarks/bench_discrete_log.py new file mode 100644 index 0000000000000000000000000000000000000000..76b273909e415318a7d3bace00ffff2a0bc53762 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/benchmarks/bench_discrete_log.py @@ -0,0 +1,83 @@ +import sys +from time import time +from sympy.ntheory.residue_ntheory import (discrete_log, + _discrete_log_trial_mul, _discrete_log_shanks_steps, + _discrete_log_pollard_rho, _discrete_log_pohlig_hellman) + + +# Cyclic group (Z/pZ)* with p prime, order p - 1 and generator g +data_set_1 = [ + # p, p - 1, g + [191, 190, 19], + [46639, 46638, 6], + [14789363, 14789362, 2], + [4254225211, 4254225210, 2], + [432751500361, 432751500360, 7], + [158505390797053, 158505390797052, 2], + [6575202655312007, 6575202655312006, 5], + [8430573471995353769, 8430573471995353768, 3], + [3938471339744997827267, 3938471339744997827266, 2], + [875260951364705563393093, 875260951364705563393092, 5], + ] + + +# Cyclic sub-groups of (Z/nZ)* with prime order p and generator g +# (n, p are primes and n = 2 * p + 1) +data_set_2 = [ + # n, p, g + [227, 113, 3], + [2447, 1223, 2], + [24527, 12263, 2], + [245639, 122819, 2], + [2456747, 1228373, 3], + [24567899, 12283949, 3], + [245679023, 122839511, 2], + [2456791307, 1228395653, 3], + [24567913439, 12283956719, 2], + [245679135407, 122839567703, 2], + [2456791354763, 1228395677381, 3], + [24567913550903, 12283956775451, 2], + [245679135509519, 122839567754759, 2], + ] + + +# Cyclic sub-groups of (Z/nZ)* with smooth order o and generator g +data_set_3 = [ + # n, o, g + [2**118, 2**116, 3], + ] + + +def bench_discrete_log(data_set, algo=None): + if algo is None: + f = discrete_log + elif algo == 'trial': + f = _discrete_log_trial_mul + elif algo == 'shanks': + f = _discrete_log_shanks_steps + elif algo == 'rho': + f = _discrete_log_pollard_rho + elif algo == 'ph': + f = _discrete_log_pohlig_hellman + else: + raise ValueError("Argument 'algo' should be one" + " of ('trial', 'shanks', 'rho' or 'ph')") + + for i, data in enumerate(data_set): + for j, (n, p, g) in enumerate(data): + t = time() + l = f(n, pow(g, p - 1, n), g, p) + t = time() - t + print('[%02d-%03d] %15.10f' % (i, j, t)) + assert l == p - 1 + + +if __name__ == '__main__': + algo = sys.argv[1] \ + if len(sys.argv) > 1 else None + data_set = [ + data_set_1, + data_set_2, + data_set_3, + ] + bench_discrete_log(data_set, algo) diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/bench_meijerint.py b/phivenv/Lib/site-packages/sympy/benchmarks/bench_meijerint.py new file mode 100644 index 0000000000000000000000000000000000000000..d648c3e02463d5a7ee1dcbe3b22af5cc22fef43d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/benchmarks/bench_meijerint.py @@ -0,0 +1,261 @@ +# conceal the implicit import from the code quality tester +from sympy.core.numbers import (oo, pi) +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.bessel import besseli +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import integrate +from sympy.integrals.transforms import (mellin_transform, + inverse_fourier_transform, inverse_mellin_transform, + laplace_transform, inverse_laplace_transform, fourier_transform) + +LT = laplace_transform +FT = fourier_transform +MT = mellin_transform +IFT = inverse_fourier_transform +ILT = inverse_laplace_transform +IMT = inverse_mellin_transform + +from sympy.abc import x, y +nu, beta, rho = symbols('nu beta rho') + +apos, bpos, cpos, dpos, posk, p = symbols('a b c d k p', positive=True) +k = Symbol('k', real=True) +negk = Symbol('k', negative=True) + +mu1, mu2 = symbols('mu1 mu2', real=True, nonzero=True, finite=True) +sigma1, sigma2 = symbols('sigma1 sigma2', real=True, nonzero=True, + finite=True, positive=True) +rate = Symbol('lambda', positive=True) + + +def normal(x, mu, sigma): + return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2) + + +def exponential(x, rate): + return rate*exp(-rate*x) +alpha, beta = symbols('alpha beta', positive=True) +betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \ + /gamma(alpha)/gamma(beta) +kint = Symbol('k', integer=True, positive=True) +chi = 2**(1 - kint/2)*x**(kint - 1)*exp(-x**2/2)/gamma(kint/2) +chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2) +dagum = apos*p/x*(x/bpos)**(apos*p)/(1 + x**apos/bpos**apos)**(p + 1) +d1, d2 = symbols('d1 d2', positive=True) +f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \ + /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2) +nupos, sigmapos = symbols('nu sigma', positive=True) +rice = x/sigmapos**2*exp(-(x**2 + nupos**2)/2/sigmapos**2)*besseli(0, x* + nupos/sigmapos**2) +mu = Symbol('mu', real=True) +laplace = exp(-abs(x - mu)/bpos)/2/bpos + +u = Symbol('u', polar=True) +tpos = Symbol('t', positive=True) + + +def E(expr): + integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1), + (x, 0, oo), (y, -oo, oo), meijerg=True) + integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1), + (y, -oo, oo), (x, 0, oo), meijerg=True) + +bench = [ + 'MT(x**nu*Heaviside(x - 1), x, s)', + 'MT(x**nu*Heaviside(1 - x), x, s)', + 'MT((1-x)**(beta - 1)*Heaviside(1-x), x, s)', + 'MT((x-1)**(beta - 1)*Heaviside(x-1), x, s)', + 'MT((1+x)**(-rho), x, s)', + 'MT(abs(1-x)**(-rho), x, s)', + 'MT((1-x)**(beta-1)*Heaviside(1-x) + a*(x-1)**(beta-1)*Heaviside(x-1), x, s)', + 'MT((x**a-b**a)/(x-b), x, s)', + 'MT((x**a-bpos**a)/(x-bpos), x, s)', + 'MT(exp(-x), x, s)', + 'MT(exp(-1/x), x, s)', + 'MT(log(x)**4*Heaviside(1-x), x, s)', + 'MT(log(x)**3*Heaviside(x-1), x, s)', + 'MT(log(x + 1), x, s)', + 'MT(log(1/x + 1), x, s)', + 'MT(log(abs(1 - x)), x, s)', + 'MT(log(abs(1 - 1/x)), x, s)', + 'MT(log(x)/(x+1), x, s)', + 'MT(log(x)**2/(x+1), x, s)', + 'MT(log(x)/(x+1)**2, x, s)', + 'MT(erf(sqrt(x)), x, s)', + + 'MT(besselj(a, 2*sqrt(x)), x, s)', + 'MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s)', + 'MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))**2, x, s)', + 'MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s)', + 'MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)', + 'MT(bessely(a, 2*sqrt(x)), x, s)', + 'MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s)', + 'MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s)', + 'MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s)', + 'MT(bessely(a, sqrt(x))**2, x, s)', + + 'MT(besselk(a, 2*sqrt(x)), x, s)', + 'MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk(a, 2*sqrt(2*sqrt(x))), x, s)', + 'MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s)', + 'MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s)', + 'MT(exp(-x/2)*besselk(a, x/2), x, s)', + + # later: ILT, IMT + + 'LT((t-apos)**bpos*exp(-cpos*(t-apos))*Heaviside(t-apos), t, s)', + 'LT(t**apos, t, s)', + 'LT(Heaviside(t), t, s)', + 'LT(Heaviside(t - apos), t, s)', + 'LT(1 - exp(-apos*t), t, s)', + 'LT((exp(2*t)-1)*exp(-bpos - t)*Heaviside(t)/2, t, s, noconds=True)', + 'LT(exp(t), t, s)', + 'LT(exp(2*t), t, s)', + 'LT(exp(apos*t), t, s)', + 'LT(log(t/apos), t, s)', + 'LT(erf(t), t, s)', + 'LT(sin(apos*t), t, s)', + 'LT(cos(apos*t), t, s)', + 'LT(exp(-apos*t)*sin(bpos*t), t, s)', + 'LT(exp(-apos*t)*cos(bpos*t), t, s)', + 'LT(besselj(0, t), t, s, noconds=True)', + 'LT(besselj(1, t), t, s, noconds=True)', + + 'FT(Heaviside(1 - abs(2*apos*x)), x, k)', + 'FT(Heaviside(1-abs(apos*x))*(1-abs(apos*x)), x, k)', + 'FT(exp(-apos*x)*Heaviside(x), x, k)', + 'IFT(1/(apos + 2*pi*I*x), x, posk, noconds=False)', + 'IFT(1/(apos + 2*pi*I*x), x, -posk, noconds=False)', + 'IFT(1/(apos + 2*pi*I*x), x, negk)', + 'FT(x*exp(-apos*x)*Heaviside(x), x, k)', + 'FT(exp(-apos*x)*sin(bpos*x)*Heaviside(x), x, k)', + 'FT(exp(-apos*x**2), x, k)', + 'IFT(sqrt(pi/apos)*exp(-(pi*k)**2/apos), k, x)', + 'FT(exp(-apos*abs(x)), x, k)', + + 'integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)', + 'integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate((x+y+1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate((x+y-1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),' + ' (x, -oo, oo), (y, -oo, oo), meijerg=True)', + 'integrate(exponential(x, rate), (x, 0, oo), meijerg=True)', + 'integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True)', + 'integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True)', + 'E(1)', + 'E(x*y)', + 'E(x*y**2)', + 'E((x+y+1)**2)', + 'E(x+y+1)', + 'E((x+y-1)**2)', + 'integrate(betadist, (x, 0, oo), meijerg=True)', + 'integrate(x*betadist, (x, 0, oo), meijerg=True)', + 'integrate(x**2*betadist, (x, 0, oo), meijerg=True)', + 'integrate(chi, (x, 0, oo), meijerg=True)', + 'integrate(x*chi, (x, 0, oo), meijerg=True)', + 'integrate(x**2*chi, (x, 0, oo), meijerg=True)', + 'integrate(chisquared, (x, 0, oo), meijerg=True)', + 'integrate(x*chisquared, (x, 0, oo), meijerg=True)', + 'integrate(x**2*chisquared, (x, 0, oo), meijerg=True)', + 'integrate(((x-k)/sqrt(2*k))**3*chisquared, (x, 0, oo), meijerg=True)', + 'integrate(dagum, (x, 0, oo), meijerg=True)', + 'integrate(x*dagum, (x, 0, oo), meijerg=True)', + 'integrate(x**2*dagum, (x, 0, oo), meijerg=True)', + 'integrate(f, (x, 0, oo), meijerg=True)', + 'integrate(x*f, (x, 0, oo), meijerg=True)', + 'integrate(x**2*f, (x, 0, oo), meijerg=True)', + 'integrate(rice, (x, 0, oo), meijerg=True)', + 'integrate(laplace, (x, -oo, oo), meijerg=True)', + 'integrate(x*laplace, (x, -oo, oo), meijerg=True)', + 'integrate(x**2*laplace, (x, -oo, oo), meijerg=True)', + 'integrate(log(x) * x**(k-1) * exp(-x) / gamma(k), (x, 0, oo))', + + 'integrate(sin(z*x)*(x**2-1)**(-(y+S(1)/2)), (x, 1, oo), meijerg=True)', + 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)', + 'integrate(besselj(0,x)*besselj(1,x)*besselk(0,x), (x, 0, oo), meijerg=True)', + 'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)', + 'integrate(besselj(a,x)*besselj(b,x)/x, (x,0,oo), meijerg=True)', + + 'hyperexpand(meijerg((-s - a/2 + 1, -s + a/2 + 1), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), (a/2, -a/2), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), 1))', + "gammasimp(S('2**(2*s)*(-pi*gamma(-a + 1)*gamma(a + 1)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 3/2)*gamma(a + s + 1)/(a*(a + s)) - gamma(-a - 1/2)*gamma(-a + 1)*gamma(a + 1)*gamma(a + 3/2)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a + s + 1)*gamma(a - s + 1)/(a*(-a + s)))*gamma(-2*s + 1)*gamma(s + 1)/(pi*s*gamma(-a - 1/2)*gamma(a + 3/2)*gamma(-s + 1)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 1)*gamma(a - s + 3/2))'))", + + 'mellin_transform(E1(x), x, s)', + 'inverse_mellin_transform(gamma(s)/s, s, x, (0, oo))', + 'mellin_transform(expint(a, x), x, s)', + 'mellin_transform(Si(x), x, s)', + 'inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2)/(2*s*gamma(-s/2 + 1)), s, x, (-1, 0))', + 'mellin_transform(Ci(sqrt(x)), x, s)', + 'inverse_mellin_transform(-4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S(1)/2)),s, u, (0, 1))', + 'laplace_transform(Ci(x), x, s)', + 'laplace_transform(expint(a, x), x, s)', + 'laplace_transform(expint(1, x), x, s)', + 'laplace_transform(expint(2, x), x, s)', + 'inverse_laplace_transform(-log(1 + s**2)/2/s, s, u)', + 'inverse_laplace_transform(log(s + 1)/s, s, x)', + 'inverse_laplace_transform((s - log(s + 1))/s**2, s, x)', + 'laplace_transform(Chi(x), x, s)', + 'laplace_transform(Shi(x), x, s)', + + 'integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, conds="none")', + 'integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, conds="none")', + 'integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,conds="none")', + 'integrate(-cos(x)/x, (x, tpos, oo), meijerg=True)', + 'integrate(-sin(x)/x, (x, tpos, oo), meijerg=True)', + 'integrate(sin(x)/x, (x, 0, z), meijerg=True)', + 'integrate(sinh(x)/x, (x, 0, z), meijerg=True)', + 'integrate(exp(-x)/x, x, meijerg=True)', + 'integrate(exp(-x)/x**2, x, meijerg=True)', + 'integrate(cos(u)/u, u, meijerg=True)', + 'integrate(cosh(u)/u, u, meijerg=True)', + 'integrate(expint(1, x), x, meijerg=True)', + 'integrate(expint(2, x), x, meijerg=True)', + 'integrate(Si(x), x, meijerg=True)', + 'integrate(Ci(u), u, meijerg=True)', + 'integrate(Shi(x), x, meijerg=True)', + 'integrate(Chi(u), u, meijerg=True)', + 'integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True)', + 'integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True)' +] + +from time import time +from sympy.core.cache import clear_cache +import sys + +timings = [] + +if __name__ == '__main__': + for n, string in enumerate(bench): + clear_cache() + _t = time() + exec(string) + _t = time() - _t + timings += [(_t, string)] + sys.stdout.write('.') + sys.stdout.flush() + if n % (len(bench) // 10) == 0: + sys.stdout.write('%s' % (10*n // len(bench))) + print() + + timings.sort(key=lambda x: -x[0]) + + for ti, string in timings: + print('%.2fs %s' % (ti, string)) diff --git a/phivenv/Lib/site-packages/sympy/benchmarks/bench_symbench.py b/phivenv/Lib/site-packages/sympy/benchmarks/bench_symbench.py new file mode 100644 index 0000000000000000000000000000000000000000..8ea700b44b677107f5345196a8895e8ed5a9d56d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/benchmarks/bench_symbench.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +from sympy.core.random import random +from sympy.core.numbers import (I, Integer, pi) +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin +from sympy.polys.polytools import factor +from sympy.simplify.simplify import simplify +from sympy.abc import x, y, z +from timeit import default_timer as clock + + +def bench_R1(): + "real(f(f(f(f(f(f(f(f(f(f(i/2)))))))))))" + def f(z): + return sqrt(Integer(1)/3)*z**2 + I/3 + f(f(f(f(f(f(f(f(f(f(I/2)))))))))).as_real_imag()[0] + + +def bench_R2(): + "Hermite polynomial hermite(15, y)" + def hermite(n, y): + if n == 1: + return 2*y + if n == 0: + return 1 + return (2*y*hermite(n - 1, y) - 2*(n - 1)*hermite(n - 2, y)).expand() + + hermite(15, y) + + +def bench_R3(): + "a = [bool(f==f) for _ in range(10)]" + f = x + y + z + [bool(f == f) for _ in range(10)] + + +def bench_R4(): + # we don't have Tuples + pass + + +def bench_R5(): + "blowup(L, 8); L=uniq(L)" + def blowup(L, n): + for i in range(n): + L.append( (L[i] + L[i + 1]) * L[i + 2] ) + + def uniq(x): + v = set(x) + return v + L = [x, y, z] + blowup(L, 8) + L = uniq(L) + + +def bench_R6(): + "sum(simplify((x+sin(i))/x+(x-sin(i))/x) for i in range(100))" + sum(simplify((x + sin(i))/x + (x - sin(i))/x) for i in range(100)) + + +def bench_R7(): + "[f.subs(x, random()) for _ in range(10**4)]" + f = x**24 + 34*x**12 + 45*x**3 + 9*x**18 + 34*x**10 + 32*x**21 + [f.subs(x, random()) for _ in range(10**4)] + + +def bench_R8(): + "right(x^2,0,5,10^4)" + def right(f, a, b, n): + a = sympify(a) + b = sympify(b) + n = sympify(n) + x = f.atoms(Symbol).pop() + Deltax = (b - a)/n + c = a + est = 0 + for i in range(n): + c += Deltax + est += f.subs(x, c) + return est*Deltax + + right(x**2, 0, 5, 10**4) + + +def _bench_R9(): + "factor(x^20 - pi^5*y^20)" + factor(x**20 - pi**5*y**20) + + +def bench_R10(): + "v = [-pi,-pi+1/10..,pi]" + def srange(min, max, step): + v = [min] + while (max - v[-1]).evalf() > 0: + v.append(v[-1] + step) + return v[:-1] + srange(-pi, pi, sympify(1)/10) + + +def bench_R11(): + "a = [random() + random()*I for w in [0..1000]]" + [random() + random()*I for w in range(1000)] + + +def bench_S1(): + "e=(x+y+z+1)**7;f=e*(e+1);f.expand()" + e = (x + y + z + 1)**7 + f = e*(e + 1) + f.expand() + + +if __name__ == '__main__': + benchmarks = [ + bench_R1, + bench_R2, + bench_R3, + bench_R5, + bench_R6, + bench_R7, + bench_R8, + #_bench_R9, + bench_R10, + bench_R11, + #bench_S1, + ] + + report = [] + for b in benchmarks: + t = clock() + b() + t = clock() - t + print("%s%65s: %f" % (b.__name__, b.__doc__, t)) diff --git a/phivenv/Lib/site-packages/sympy/calculus/__init__.py b/phivenv/Lib/site-packages/sympy/calculus/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..865c0556769e35ca7e8a09a4c208a1cfbd17369c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/__init__.py @@ -0,0 +1,25 @@ +"""Calculus-related methods.""" + +from .euler import euler_equations +from .singularities import (singularities, is_increasing, + is_strictly_increasing, is_decreasing, + is_strictly_decreasing, is_monotonic) +from .finite_diff import finite_diff_weights, apply_finite_diff, differentiate_finite +from .util import (periodicity, not_empty_in, is_convex, + stationary_points, minimum, maximum) +from .accumulationbounds import AccumBounds + +__all__ = [ +'euler_equations', + +'singularities', 'is_increasing', +'is_strictly_increasing', 'is_decreasing', +'is_strictly_decreasing', 'is_monotonic', + +'finite_diff_weights', 'apply_finite_diff', 'differentiate_finite', + +'periodicity', 'not_empty_in', 'is_convex', 'stationary_points', +'minimum', 'maximum', + +'AccumBounds' +] diff --git a/phivenv/Lib/site-packages/sympy/calculus/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..807d14fb0ca7817c32c833bcf77fa15dc0eba2c0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/__pycache__/accumulationbounds.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/accumulationbounds.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e12c6f18a046b64c6952ffdce12d6739a9bee1d7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/accumulationbounds.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/__pycache__/euler.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/euler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a91226f981a3370ff44e29f131f03b6312f7de93 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/euler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/__pycache__/finite_diff.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/finite_diff.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a482cdaa9a265de378fc480d3d866303e31f1856 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/finite_diff.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/__pycache__/singularities.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/singularities.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04d9a38c46e3cbd1f1e49ddaf83ecb02b6da0d27 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/singularities.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/__pycache__/util.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65f2085d09dc0926cf463170654ea3bb3ddc8499 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/__pycache__/util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/accumulationbounds.py b/phivenv/Lib/site-packages/sympy/calculus/accumulationbounds.py new file mode 100644 index 0000000000000000000000000000000000000000..8a2b0e634da1028c25399c8b03884865d981a56e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/accumulationbounds.py @@ -0,0 +1,804 @@ +from sympy.core import Add, Mul, Pow, S +from sympy.core.basic import Basic +from sympy.core.expr import Expr +from sympy.core.numbers import _sympifyit, oo, zoo +from sympy.core.relational import is_le, is_lt, is_ge, is_gt +from sympy.core.sympify import _sympify +from sympy.functions.elementary.miscellaneous import Min, Max +from sympy.logic.boolalg import And +from sympy.multipledispatch import dispatch +from sympy.series.order import Order +from sympy.sets.sets import FiniteSet + + +class AccumulationBounds(Expr): + r"""An accumulation bounds. + + # Note AccumulationBounds has an alias: AccumBounds + + AccumulationBounds represent an interval `[a, b]`, which is always closed + at the ends. Here `a` and `b` can be any value from extended real numbers. + + The intended meaning of AccummulationBounds is to give an approximate + location of the accumulation points of a real function at a limit point. + + Let `a` and `b` be reals such that `a \le b`. + + `\left\langle a, b\right\rangle = \{x \in \mathbb{R} \mid a \le x \le b\}` + + `\left\langle -\infty, b\right\rangle = \{x \in \mathbb{R} \mid x \le b\} \cup \{-\infty, \infty\}` + + `\left\langle a, \infty \right\rangle = \{x \in \mathbb{R} \mid a \le x\} \cup \{-\infty, \infty\}` + + `\left\langle -\infty, \infty \right\rangle = \mathbb{R} \cup \{-\infty, \infty\}` + + ``oo`` and ``-oo`` are added to the second and third definition respectively, + since if either ``-oo`` or ``oo`` is an argument, then the other one should + be included (though not as an end point). This is forced, since we have, + for example, ``1/AccumBounds(0, 1) = AccumBounds(1, oo)``, and the limit at + `0` is not one-sided. As `x` tends to `0-`, then `1/x \rightarrow -\infty`, so `-\infty` + should be interpreted as belonging to ``AccumBounds(1, oo)`` though it need + not appear explicitly. + + In many cases it suffices to know that the limit set is bounded. + However, in some other cases more exact information could be useful. + For example, all accumulation values of `\cos(x) + 1` are non-negative. + (``AccumBounds(-1, 1) + 1 = AccumBounds(0, 2)``) + + A AccumulationBounds object is defined to be real AccumulationBounds, + if its end points are finite reals. + + Let `X`, `Y` be real AccumulationBounds, then their sum, difference, + product are defined to be the following sets: + + `X + Y = \{ x+y \mid x \in X \cap y \in Y\}` + + `X - Y = \{ x-y \mid x \in X \cap y \in Y\}` + + `X \times Y = \{ x \times y \mid x \in X \cap y \in Y\}` + + When an AccumBounds is raised to a negative power, if 0 is contained + between the bounds then an infinite range is returned, otherwise if an + endpoint is 0 then a semi-infinite range with consistent sign will be returned. + + AccumBounds in expressions behave a lot like Intervals but the + semantics are not necessarily the same. Division (or exponentiation + to a negative integer power) could be handled with *intervals* by + returning a union of the results obtained after splitting the + bounds between negatives and positives, but that is not done with + AccumBounds. In addition, bounds are assumed to be independent of + each other; if the same bound is used in more than one place in an + expression, the result may not be the supremum or infimum of the + expression (see below). Finally, when a boundary is ``1``, + exponentiation to the power of ``oo`` yields ``oo``, neither + ``1`` nor ``nan``. + + Examples + ======== + + >>> from sympy import AccumBounds, sin, exp, log, pi, E, S, oo + >>> from sympy.abc import x + + >>> AccumBounds(0, 1) + AccumBounds(1, 2) + AccumBounds(1, 3) + + >>> AccumBounds(0, 1) - AccumBounds(0, 2) + AccumBounds(-2, 1) + + >>> AccumBounds(-2, 3)*AccumBounds(-1, 1) + AccumBounds(-3, 3) + + >>> AccumBounds(1, 2)*AccumBounds(3, 5) + AccumBounds(3, 10) + + The exponentiation of AccumulationBounds is defined + as follows: + + If 0 does not belong to `X` or `n > 0` then + + `X^n = \{ x^n \mid x \in X\}` + + >>> AccumBounds(1, 4)**(S(1)/2) + AccumBounds(1, 2) + + otherwise, an infinite or semi-infinite result is obtained: + + >>> 1/AccumBounds(-1, 1) + AccumBounds(-oo, oo) + >>> 1/AccumBounds(0, 2) + AccumBounds(1/2, oo) + >>> 1/AccumBounds(-oo, 0) + AccumBounds(-oo, 0) + + A boundary of 1 will always generate all nonnegatives: + + >>> AccumBounds(1, 2)**oo + AccumBounds(0, oo) + >>> AccumBounds(0, 1)**oo + AccumBounds(0, oo) + + If the exponent is itself an AccumulationBounds or is not an + integer then unevaluated results will be returned unless the base + values are positive: + + >>> AccumBounds(2, 3)**AccumBounds(-1, 2) + AccumBounds(1/3, 9) + >>> AccumBounds(-2, 3)**AccumBounds(-1, 2) + AccumBounds(-2, 3)**AccumBounds(-1, 2) + + >>> AccumBounds(-2, -1)**(S(1)/2) + sqrt(AccumBounds(-2, -1)) + + Note: `\left\langle a, b\right\rangle^2` is not same as `\left\langle a, b\right\rangle \times \left\langle a, b\right\rangle` + + >>> AccumBounds(-1, 1)**2 + AccumBounds(0, 1) + + >>> AccumBounds(1, 3) < 4 + True + + >>> AccumBounds(1, 3) < -1 + False + + Some elementary functions can also take AccumulationBounds as input. + A function `f` evaluated for some real AccumulationBounds `\left\langle a, b \right\rangle` + is defined as `f(\left\langle a, b\right\rangle) = \{ f(x) \mid a \le x \le b \}` + + >>> sin(AccumBounds(pi/6, pi/3)) + AccumBounds(1/2, sqrt(3)/2) + + >>> exp(AccumBounds(0, 1)) + AccumBounds(1, E) + + >>> log(AccumBounds(1, E)) + AccumBounds(0, 1) + + Some symbol in an expression can be substituted for a AccumulationBounds + object. But it does not necessarily evaluate the AccumulationBounds for + that expression. + + The same expression can be evaluated to different values depending upon + the form it is used for substitution since each instance of an + AccumulationBounds is considered independent. For example: + + >>> (x**2 + 2*x + 1).subs(x, AccumBounds(-1, 1)) + AccumBounds(-1, 4) + + >>> ((x + 1)**2).subs(x, AccumBounds(-1, 1)) + AccumBounds(0, 4) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Interval_arithmetic + + .. [2] https://fab.cba.mit.edu/classes/S62.12/docs/Hickey_interval.pdf + + Notes + ===== + + Do not use ``AccumulationBounds`` for floating point interval arithmetic + calculations, use ``mpmath.iv`` instead. + """ + + is_extended_real = True + is_number = False + + def __new__(cls, min, max) -> Expr: # type: ignore + + min = _sympify(min) + max = _sympify(max) + + # Only allow real intervals (use symbols with 'is_extended_real=True'). + if not min.is_extended_real or not max.is_extended_real: + raise ValueError("Only real AccumulationBounds are supported") + + if max == min: + return max + + # Make sure that the created AccumBounds object will be valid. + if max.is_number and min.is_number: + bad = max.is_comparable and min.is_comparable and max < min + else: + bad = (max - min).is_extended_negative + if bad: + raise ValueError( + "Lower limit should be smaller than upper limit") + + return Basic.__new__(cls, min, max) + + # setting the operation priority + _op_priority = 11.0 + + def _eval_is_real(self): + if self.min.is_real and self.max.is_real: + return True + + @property + def min(self): + """ + Returns the minimum possible value attained by AccumulationBounds + object. + + Examples + ======== + + >>> from sympy import AccumBounds + >>> AccumBounds(1, 3).min + 1 + + """ + return self.args[0] + + @property + def max(self): + """ + Returns the maximum possible value attained by AccumulationBounds + object. + + Examples + ======== + + >>> from sympy import AccumBounds + >>> AccumBounds(1, 3).max + 3 + + """ + return self.args[1] + + @property + def delta(self): + """ + Returns the difference of maximum possible value attained by + AccumulationBounds object and minimum possible value attained + by AccumulationBounds object. + + Examples + ======== + + >>> from sympy import AccumBounds + >>> AccumBounds(1, 3).delta + 2 + + """ + return self.max - self.min + + @property + def mid(self): + """ + Returns the mean of maximum possible value attained by + AccumulationBounds object and minimum possible value + attained by AccumulationBounds object. + + Examples + ======== + + >>> from sympy import AccumBounds + >>> AccumBounds(1, 3).mid + 2 + + """ + return (self.min + self.max) / 2 + + @_sympifyit('other', NotImplemented) + def _eval_power(self, other): + return self.__pow__(other) + + @_sympifyit('other', NotImplemented) + def __add__(self, other): + if isinstance(other, Expr): + if isinstance(other, AccumBounds): + return AccumBounds( + Add(self.min, other.min), + Add(self.max, other.max)) + if other is S.Infinity and self.min is S.NegativeInfinity or \ + other is S.NegativeInfinity and self.max is S.Infinity: + return AccumBounds(-oo, oo) + elif other.is_extended_real: + if self.min is S.NegativeInfinity and self.max is S.Infinity: + return AccumBounds(-oo, oo) + elif self.min is S.NegativeInfinity: + return AccumBounds(-oo, self.max + other) + elif self.max is S.Infinity: + return AccumBounds(self.min + other, oo) + else: + return AccumBounds(Add(self.min, other), Add(self.max, other)) + return Add(self, other, evaluate=False) + return NotImplemented + + __radd__ = __add__ + + def __neg__(self): + return AccumBounds(-self.max, -self.min) + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + if isinstance(other, Expr): + if isinstance(other, AccumBounds): + return AccumBounds( + Add(self.min, -other.max), + Add(self.max, -other.min)) + if other is S.NegativeInfinity and self.min is S.NegativeInfinity or \ + other is S.Infinity and self.max is S.Infinity: + return AccumBounds(-oo, oo) + elif other.is_extended_real: + if self.min is S.NegativeInfinity and self.max is S.Infinity: + return AccumBounds(-oo, oo) + elif self.min is S.NegativeInfinity: + return AccumBounds(-oo, self.max - other) + elif self.max is S.Infinity: + return AccumBounds(self.min - other, oo) + else: + return AccumBounds( + Add(self.min, -other), + Add(self.max, -other)) + return Add(self, -other, evaluate=False) + return NotImplemented + + @_sympifyit('other', NotImplemented) + def __rsub__(self, other): + return self.__neg__() + other + + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + if self.args == (-oo, oo): + return self + if isinstance(other, Expr): + if isinstance(other, AccumBounds): + if other.args == (-oo, oo): + return other + v = set() + for a in self.args: + vi = other*a + v.update(vi.args or (vi,)) + return AccumBounds(Min(*v), Max(*v)) + if other is S.Infinity: + if self.min.is_zero: + return AccumBounds(0, oo) + if self.max.is_zero: + return AccumBounds(-oo, 0) + if other is S.NegativeInfinity: + if self.min.is_zero: + return AccumBounds(-oo, 0) + if self.max.is_zero: + return AccumBounds(0, oo) + if other.is_extended_real: + if other.is_zero: + if self.max is S.Infinity: + return AccumBounds(0, oo) + if self.min is S.NegativeInfinity: + return AccumBounds(-oo, 0) + return S.Zero + if other.is_extended_positive: + return AccumBounds( + Mul(self.min, other), + Mul(self.max, other)) + elif other.is_extended_negative: + return AccumBounds( + Mul(self.max, other), + Mul(self.min, other)) + if isinstance(other, Order): + return other + return Mul(self, other, evaluate=False) + return NotImplemented + + __rmul__ = __mul__ + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + if isinstance(other, Expr): + if isinstance(other, AccumBounds): + if other.min.is_positive or other.max.is_negative: + return self * AccumBounds(1/other.max, 1/other.min) + + if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative and + other.min.is_extended_nonpositive and other.max.is_extended_nonnegative): + if self.min.is_zero and other.min.is_zero: + return AccumBounds(0, oo) + if self.max.is_zero and other.min.is_zero: + return AccumBounds(-oo, 0) + return AccumBounds(-oo, oo) + + if self.max.is_extended_negative: + if other.min.is_extended_negative: + if other.max.is_zero: + return AccumBounds(self.max / other.min, oo) + if other.max.is_extended_positive: + # if we were dealing with intervals we would return + # Union(Interval(-oo, self.max/other.max), + # Interval(self.max/other.min, oo)) + return AccumBounds(-oo, oo) + + if other.min.is_zero and other.max.is_extended_positive: + return AccumBounds(-oo, self.max / other.max) + + if self.min.is_extended_positive: + if other.min.is_extended_negative: + if other.max.is_zero: + return AccumBounds(-oo, self.min / other.min) + if other.max.is_extended_positive: + # if we were dealing with intervals we would return + # Union(Interval(-oo, self.min/other.min), + # Interval(self.min/other.max, oo)) + return AccumBounds(-oo, oo) + + if other.min.is_zero and other.max.is_extended_positive: + return AccumBounds(self.min / other.max, oo) + + elif other.is_extended_real: + if other in (S.Infinity, S.NegativeInfinity): + if self == AccumBounds(-oo, oo): + return AccumBounds(-oo, oo) + if self.max is S.Infinity: + return AccumBounds(Min(0, other), Max(0, other)) + if self.min is S.NegativeInfinity: + return AccumBounds(Min(0, -other), Max(0, -other)) + if other.is_extended_positive: + return AccumBounds(self.min / other, self.max / other) + elif other.is_extended_negative: + return AccumBounds(self.max / other, self.min / other) + if (1 / other) is S.ComplexInfinity: + return Mul(self, 1 / other, evaluate=False) + else: + return Mul(self, 1 / other) + + return NotImplemented + + @_sympifyit('other', NotImplemented) + def __rtruediv__(self, other): + if isinstance(other, Expr): + if other.is_extended_real: + if other.is_zero: + return S.Zero + if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative): + if self.min.is_zero: + if other.is_extended_positive: + return AccumBounds(Mul(other, 1 / self.max), oo) + if other.is_extended_negative: + return AccumBounds(-oo, Mul(other, 1 / self.max)) + if self.max.is_zero: + if other.is_extended_positive: + return AccumBounds(-oo, Mul(other, 1 / self.min)) + if other.is_extended_negative: + return AccumBounds(Mul(other, 1 / self.min), oo) + return AccumBounds(-oo, oo) + else: + return AccumBounds(Min(other / self.min, other / self.max), + Max(other / self.min, other / self.max)) + return Mul(other, 1 / self, evaluate=False) + else: + return NotImplemented + + @_sympifyit('other', NotImplemented) + def __pow__(self, other): + if isinstance(other, Expr): + if other is S.Infinity: + if self.min.is_extended_nonnegative: + if self.max < 1: + return S.Zero + if self.min > 1: + return S.Infinity + return AccumBounds(0, oo) + elif self.max.is_extended_negative: + if self.min > -1: + return S.Zero + if self.max < -1: + return zoo + return S.NaN + else: + if self.min > -1: + if self.max < 1: + return S.Zero + return AccumBounds(0, oo) + return AccumBounds(-oo, oo) + + if other is S.NegativeInfinity: + return (1/self)**oo + + # generically true + if (self.max - self.min).is_nonnegative: + # well defined + if self.min.is_nonnegative: + # no 0 to worry about + if other.is_nonnegative: + # no infinity to worry about + return self.func(self.min**other, self.max**other) + + if other.is_zero: + return S.One # x**0 = 1 + + if other.is_Integer or other.is_integer: + if self.min.is_extended_positive: + return AccumBounds( + Min(self.min**other, self.max**other), + Max(self.min**other, self.max**other)) + elif self.max.is_extended_negative: + return AccumBounds( + Min(self.max**other, self.min**other), + Max(self.max**other, self.min**other)) + + if other % 2 == 0: + if other.is_extended_negative: + if self.min.is_zero: + return AccumBounds(self.max**other, oo) + if self.max.is_zero: + return AccumBounds(self.min**other, oo) + return (1/self)**(-other) + return AccumBounds( + S.Zero, Max(self.min**other, self.max**other)) + elif other % 2 == 1: + if other.is_extended_negative: + if self.min.is_zero: + return AccumBounds(self.max**other, oo) + if self.max.is_zero: + return AccumBounds(-oo, self.min**other) + return (1/self)**(-other) + return AccumBounds(self.min**other, self.max**other) + + # non-integer exponent + # 0**neg or neg**frac yields complex + if (other.is_number or other.is_rational) and ( + self.min.is_extended_nonnegative or ( + other.is_extended_nonnegative and + self.min.is_extended_nonnegative)): + num, den = other.as_numer_denom() + if num is S.One: + return AccumBounds(*[i**(1/den) for i in self.args]) + + elif den is not S.One: # e.g. if other is not Float + return (self**num)**(1/den) # ok for non-negative base + + if isinstance(other, AccumBounds): + if (self.min.is_extended_positive or + self.min.is_extended_nonnegative and + other.min.is_extended_nonnegative): + p = [self**i for i in other.args] + if not any(i.is_Pow for i in p): + a = [j for i in p for j in i.args or (i,)] + try: + return self.func(min(a), max(a)) + except TypeError: # can't sort + pass + + return Pow(self, other, evaluate=False) + + return NotImplemented + + @_sympifyit('other', NotImplemented) + def __rpow__(self, other): + if other.is_real and other.is_extended_nonnegative and ( + self.max - self.min).is_extended_positive: + if other is S.One: + return S.One + if other.is_extended_positive: + a, b = [other**i for i in self.args] + if min(a, b) != a: + a, b = b, a + return self.func(a, b) + if other.is_zero: + if self.min.is_zero: + return self.func(0, 1) + if self.min.is_extended_positive: + return S.Zero + + return Pow(other, self, evaluate=False) + + def __abs__(self): + if self.max.is_extended_negative: + return self.__neg__() + elif self.min.is_extended_negative: + return AccumBounds(S.Zero, Max(abs(self.min), self.max)) + else: + return self + + + def __contains__(self, other): + """ + Returns ``True`` if other is contained in self, where other + belongs to extended real numbers, ``False`` if not contained, + otherwise TypeError is raised. + + Examples + ======== + + >>> from sympy import AccumBounds, oo + >>> 1 in AccumBounds(-1, 3) + True + + -oo and oo go together as limits (in AccumulationBounds). + + >>> -oo in AccumBounds(1, oo) + True + + >>> oo in AccumBounds(-oo, 0) + True + + """ + other = _sympify(other) + + if other in (S.Infinity, S.NegativeInfinity): + if self.min is S.NegativeInfinity or self.max is S.Infinity: + return True + return False + + rv = And(self.min <= other, self.max >= other) + if rv not in (True, False): + raise TypeError("input failed to evaluate") + return rv + + def intersection(self, other): + """ + Returns the intersection of 'self' and 'other'. + Here other can be an instance of :py:class:`~.FiniteSet` or AccumulationBounds. + + Parameters + ========== + + other : AccumulationBounds + Another AccumulationBounds object with which the intersection + has to be computed. + + Returns + ======= + + AccumulationBounds + Intersection of ``self`` and ``other``. + + Examples + ======== + + >>> from sympy import AccumBounds, FiniteSet + >>> AccumBounds(1, 3).intersection(AccumBounds(2, 4)) + AccumBounds(2, 3) + + >>> AccumBounds(1, 3).intersection(AccumBounds(4, 6)) + EmptySet + + >>> AccumBounds(1, 4).intersection(FiniteSet(1, 2, 5)) + {1, 2} + + """ + if not isinstance(other, (AccumBounds, FiniteSet)): + raise TypeError( + "Input must be AccumulationBounds or FiniteSet object") + + if isinstance(other, FiniteSet): + fin_set = S.EmptySet + for i in other: + if i in self: + fin_set = fin_set + FiniteSet(i) + return fin_set + + if self.max < other.min or self.min > other.max: + return S.EmptySet + + if self.min <= other.min: + if self.max <= other.max: + return AccumBounds(other.min, self.max) + if self.max > other.max: + return other + + if other.min <= self.min: + if other.max < self.max: + return AccumBounds(self.min, other.max) + if other.max > self.max: + return self + + def union(self, other): + # TODO : Devise a better method for Union of AccumBounds + # this method is not actually correct and + # can be made better + if not isinstance(other, AccumBounds): + raise TypeError( + "Input must be AccumulationBounds or FiniteSet object") + + if self.min <= other.min and self.max >= other.min: + return AccumBounds(self.min, Max(self.max, other.max)) + + if other.min <= self.min and other.max >= self.min: + return AccumBounds(other.min, Max(self.max, other.max)) + + +@dispatch(AccumulationBounds, AccumulationBounds) # type: ignore # noqa:F811 +def _eval_is_le(lhs, rhs): # noqa:F811 + if is_le(lhs.max, rhs.min): + return True + if is_gt(lhs.min, rhs.max): + return False + + +@dispatch(AccumulationBounds, Basic) # type: ignore # noqa:F811 +def _eval_is_le(lhs, rhs): # noqa: F811 + + """ + Returns ``True `` if range of values attained by ``lhs`` AccumulationBounds + object is greater than the range of values attained by ``rhs``, + where ``rhs`` may be any value of type AccumulationBounds object or + extended real number value, ``False`` if ``rhs`` satisfies + the same property, else an unevaluated :py:class:`~.Relational`. + + Examples + ======== + + >>> from sympy import AccumBounds, oo + >>> AccumBounds(1, 3) > AccumBounds(4, oo) + False + >>> AccumBounds(1, 4) > AccumBounds(3, 4) + AccumBounds(1, 4) > AccumBounds(3, 4) + >>> AccumBounds(1, oo) > -1 + True + + """ + if not rhs.is_extended_real: + raise TypeError( + "Invalid comparison of %s %s" % + (type(rhs), rhs)) + elif rhs.is_comparable: + if is_le(lhs.max, rhs): + return True + if is_gt(lhs.min, rhs): + return False + + +@dispatch(AccumulationBounds, AccumulationBounds) +def _eval_is_ge(lhs, rhs): # noqa:F811 + if is_ge(lhs.min, rhs.max): + return True + if is_lt(lhs.max, rhs.min): + return False + + +@dispatch(AccumulationBounds, Expr) # type:ignore +def _eval_is_ge(lhs, rhs): # noqa: F811 + """ + Returns ``True`` if range of values attained by ``lhs`` AccumulationBounds + object is less that the range of values attained by ``rhs``, where + other may be any value of type AccumulationBounds object or extended + real number value, ``False`` if ``rhs`` satisfies the same + property, else an unevaluated :py:class:`~.Relational`. + + Examples + ======== + + >>> from sympy import AccumBounds, oo + >>> AccumBounds(1, 3) >= AccumBounds(4, oo) + False + >>> AccumBounds(1, 4) >= AccumBounds(3, 4) + AccumBounds(1, 4) >= AccumBounds(3, 4) + >>> AccumBounds(1, oo) >= 1 + True + """ + + if not rhs.is_extended_real: + raise TypeError( + "Invalid comparison of %s %s" % + (type(rhs), rhs)) + elif rhs.is_comparable: + if is_ge(lhs.min, rhs): + return True + if is_lt(lhs.max, rhs): + return False + + +@dispatch(Expr, AccumulationBounds) # type:ignore +def _eval_is_ge(lhs, rhs): # noqa:F811 + if not lhs.is_extended_real: + raise TypeError( + "Invalid comparison of %s %s" % + (type(lhs), lhs)) + elif lhs.is_comparable: + if is_le(rhs.max, lhs): + return True + if is_gt(rhs.min, lhs): + return False + + +@dispatch(AccumulationBounds, AccumulationBounds) # type:ignore +def _eval_is_ge(lhs, rhs): # noqa:F811 + if is_ge(lhs.min, rhs.max): + return True + if is_lt(lhs.max, rhs.min): + return False + +# setting an alias for AccumulationBounds +AccumBounds = AccumulationBounds diff --git a/phivenv/Lib/site-packages/sympy/calculus/euler.py b/phivenv/Lib/site-packages/sympy/calculus/euler.py new file mode 100644 index 0000000000000000000000000000000000000000..817acf76091dfba2dba40487ca7735e307c0fc15 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/euler.py @@ -0,0 +1,108 @@ +""" +This module implements a method to find +Euler-Lagrange Equations for given Lagrangian. +""" +from itertools import combinations_with_replacement +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.utilities.iterables import iterable + + +def euler_equations(L, funcs=(), vars=()): + r""" + Find the Euler-Lagrange equations [1]_ for a given Lagrangian. + + Parameters + ========== + + L : Expr + The Lagrangian that should be a function of the functions listed + in the second argument and their derivatives. + + For example, in the case of two functions $f(x,y)$, $g(x,y)$ and + two independent variables $x$, $y$ the Lagrangian has the form: + + .. math:: L\left(f(x,y),g(x,y),\frac{\partial f(x,y)}{\partial x}, + \frac{\partial f(x,y)}{\partial y}, + \frac{\partial g(x,y)}{\partial x}, + \frac{\partial g(x,y)}{\partial y},x,y\right) + + In many cases it is not necessary to provide anything, except the + Lagrangian, it will be auto-detected (and an error raised if this + cannot be done). + + funcs : Function or an iterable of Functions + The functions that the Lagrangian depends on. The Euler equations + are differential equations for each of these functions. + + vars : Symbol or an iterable of Symbols + The Symbols that are the independent variables of the functions. + + Returns + ======= + + eqns : list of Eq + The list of differential equations, one for each function. + + Examples + ======== + + >>> from sympy import euler_equations, Symbol, Function + >>> x = Function('x') + >>> t = Symbol('t') + >>> L = (x(t).diff(t))**2/2 - x(t)**2/2 + >>> euler_equations(L, x(t), t) + [Eq(-x(t) - Derivative(x(t), (t, 2)), 0)] + >>> u = Function('u') + >>> x = Symbol('x') + >>> L = (u(t, x).diff(t))**2/2 - (u(t, x).diff(x))**2/2 + >>> euler_equations(L, u(t, x), [t, x]) + [Eq(-Derivative(u(t, x), (t, 2)) + Derivative(u(t, x), (x, 2)), 0)] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation + + """ + + funcs = tuple(funcs) if iterable(funcs) else (funcs,) + + if not funcs: + funcs = tuple(L.atoms(Function)) + else: + for f in funcs: + if not isinstance(f, Function): + raise TypeError('Function expected, got: %s' % f) + + vars = tuple(vars) if iterable(vars) else (vars,) + + if not vars: + vars = funcs[0].args + else: + vars = tuple(sympify(var) for var in vars) + + if not all(isinstance(v, Symbol) for v in vars): + raise TypeError('Variables are not symbols, got %s' % vars) + + for f in funcs: + if not vars == f.args: + raise ValueError("Variables %s do not match args: %s" % (vars, f)) + + order = max([len(d.variables) for d in L.atoms(Derivative) + if d.expr in funcs] + [0]) + + eqns = [] + for f in funcs: + eq = diff(L, f) + for i in range(1, order + 1): + for p in combinations_with_replacement(vars, i): + eq = eq + S.NegativeOne**i*diff(L, diff(f, *p), *p) + new_eq = Eq(eq, 0) + if isinstance(new_eq, Eq): + eqns.append(new_eq) + + return eqns diff --git a/phivenv/Lib/site-packages/sympy/calculus/finite_diff.py b/phivenv/Lib/site-packages/sympy/calculus/finite_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..17eece149aadad236cefeb350e1ef4a383c84f01 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/finite_diff.py @@ -0,0 +1,476 @@ +""" +Finite difference weights +========================= + +This module implements an algorithm for efficient generation of finite +difference weights for ordinary differentials of functions for +derivatives from 0 (interpolation) up to arbitrary order. + +The core algorithm is provided in the finite difference weight generating +function (``finite_diff_weights``), and two convenience functions are provided +for: + +- estimating a derivative (or interpolate) directly from a series of points + is also provided (``apply_finite_diff``). +- differentiating by using finite difference approximations + (``differentiate_finite``). + +""" + +from sympy.core.function import Derivative +from sympy.core.singleton import S +from sympy.core.function import Subs +from sympy.core.traversal import preorder_traversal +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import iterable + + + +def finite_diff_weights(order, x_list, x0=S.One): + """ + Calculates the finite difference weights for an arbitrarily spaced + one-dimensional grid (``x_list``) for derivatives at ``x0`` of order + 0, 1, ..., up to ``order`` using a recursive formula. Order of accuracy + is at least ``len(x_list) - order``, if ``x_list`` is defined correctly. + + Parameters + ========== + + order: int + Up to what derivative order weights should be calculated. + 0 corresponds to interpolation. + x_list: sequence + Sequence of (unique) values for the independent variable. + It is useful (but not necessary) to order ``x_list`` from + nearest to furthest from ``x0``; see examples below. + x0: Number or Symbol + Root or value of the independent variable for which the finite + difference weights should be generated. Default is ``S.One``. + + Returns + ======= + + list + A list of sublists, each corresponding to coefficients for + increasing derivative order, and each containing lists of + coefficients for increasing subsets of x_list. + + Examples + ======== + + >>> from sympy import finite_diff_weights, S + >>> res = finite_diff_weights(1, [-S(1)/2, S(1)/2, S(3)/2, S(5)/2], 0) + >>> res + [[[1, 0, 0, 0], + [1/2, 1/2, 0, 0], + [3/8, 3/4, -1/8, 0], + [5/16, 15/16, -5/16, 1/16]], + [[0, 0, 0, 0], + [-1, 1, 0, 0], + [-1, 1, 0, 0], + [-23/24, 7/8, 1/8, -1/24]]] + >>> res[0][-1] # FD weights for 0th derivative, using full x_list + [5/16, 15/16, -5/16, 1/16] + >>> res[1][-1] # FD weights for 1st derivative + [-23/24, 7/8, 1/8, -1/24] + >>> res[1][-2] # FD weights for 1st derivative, using x_list[:-1] + [-1, 1, 0, 0] + >>> res[1][-1][0] # FD weight for 1st deriv. for x_list[0] + -23/24 + >>> res[1][-1][1] # FD weight for 1st deriv. for x_list[1], etc. + 7/8 + + Each sublist contains the most accurate formula at the end. + Note, that in the above example ``res[1][1]`` is the same as ``res[1][2]``. + Since res[1][2] has an order of accuracy of + ``len(x_list[:3]) - order = 3 - 1 = 2``, the same is true for ``res[1][1]``! + + >>> res = finite_diff_weights(1, [S(0), S(1), -S(1), S(2), -S(2)], 0)[1] + >>> res + [[0, 0, 0, 0, 0], + [-1, 1, 0, 0, 0], + [0, 1/2, -1/2, 0, 0], + [-1/2, 1, -1/3, -1/6, 0], + [0, 2/3, -2/3, -1/12, 1/12]] + >>> res[0] # no approximation possible, using x_list[0] only + [0, 0, 0, 0, 0] + >>> res[1] # classic forward step approximation + [-1, 1, 0, 0, 0] + >>> res[2] # classic centered approximation + [0, 1/2, -1/2, 0, 0] + >>> res[3:] # higher order approximations + [[-1/2, 1, -1/3, -1/6, 0], [0, 2/3, -2/3, -1/12, 1/12]] + + Let us compare this to a differently defined ``x_list``. Pay attention to + ``foo[i][k]`` corresponding to the gridpoint defined by ``x_list[k]``. + + >>> foo = finite_diff_weights(1, [-S(2), -S(1), S(0), S(1), S(2)], 0)[1] + >>> foo + [[0, 0, 0, 0, 0], + [-1, 1, 0, 0, 0], + [1/2, -2, 3/2, 0, 0], + [1/6, -1, 1/2, 1/3, 0], + [1/12, -2/3, 0, 2/3, -1/12]] + >>> foo[1] # not the same and of lower accuracy as res[1]! + [-1, 1, 0, 0, 0] + >>> foo[2] # classic double backward step approximation + [1/2, -2, 3/2, 0, 0] + >>> foo[4] # the same as res[4] + [1/12, -2/3, 0, 2/3, -1/12] + + Note that, unless you plan on using approximations based on subsets of + ``x_list``, the order of gridpoints does not matter. + + The capability to generate weights at arbitrary points can be + used e.g. to minimize Runge's phenomenon by using Chebyshev nodes: + + >>> from sympy import cos, symbols, pi, simplify + >>> N, (h, x) = 4, symbols('h x') + >>> x_list = [x+h*cos(i*pi/(N)) for i in range(N,-1,-1)] # chebyshev nodes + >>> print(x_list) + [-h + x, -sqrt(2)*h/2 + x, x, sqrt(2)*h/2 + x, h + x] + >>> mycoeffs = finite_diff_weights(1, x_list, 0)[1][4] + >>> [simplify(c) for c in mycoeffs] #doctest: +NORMALIZE_WHITESPACE + [(h**3/2 + h**2*x - 3*h*x**2 - 4*x**3)/h**4, + (-sqrt(2)*h**3 - 4*h**2*x + 3*sqrt(2)*h*x**2 + 8*x**3)/h**4, + (6*h**2*x - 8*x**3)/h**4, + (sqrt(2)*h**3 - 4*h**2*x - 3*sqrt(2)*h*x**2 + 8*x**3)/h**4, + (-h**3/2 + h**2*x + 3*h*x**2 - 4*x**3)/h**4] + + Notes + ===== + + If weights for a finite difference approximation of 3rd order + derivative is wanted, weights for 0th, 1st and 2nd order are + calculated "for free", so are formulae using subsets of ``x_list``. + This is something one can take advantage of to save computational cost. + Be aware that one should define ``x_list`` from nearest to furthest from + ``x0``. If not, subsets of ``x_list`` will yield poorer approximations, + which might not grand an order of accuracy of ``len(x_list) - order``. + + See also + ======== + + sympy.calculus.finite_diff.apply_finite_diff + + References + ========== + + .. [1] Generation of Finite Difference Formulas on Arbitrarily Spaced + Grids, Bengt Fornberg; Mathematics of computation; 51; 184; + (1988); 699-706; doi:10.1090/S0025-5718-1988-0935077-0 + + """ + # The notation below closely corresponds to the one used in the paper. + order = S(order) + if not order.is_number: + raise ValueError("Cannot handle symbolic order.") + if order < 0: + raise ValueError("Negative derivative order illegal.") + if int(order) != order: + raise ValueError("Non-integer order illegal") + M = order + N = len(x_list) - 1 + delta = [[[0 for nu in range(N+1)] for n in range(N+1)] for + m in range(M+1)] + delta[0][0][0] = S.One + c1 = S.One + for n in range(1, N+1): + c2 = S.One + for nu in range(n): + c3 = x_list[n] - x_list[nu] + c2 = c2 * c3 + if n <= M: + delta[n][n-1][nu] = 0 + for m in range(min(n, M)+1): + delta[m][n][nu] = (x_list[n]-x0)*delta[m][n-1][nu] -\ + m*delta[m-1][n-1][nu] + delta[m][n][nu] /= c3 + for m in range(min(n, M)+1): + delta[m][n][n] = c1/c2*(m*delta[m-1][n-1][n-1] - + (x_list[n-1]-x0)*delta[m][n-1][n-1]) + c1 = c2 + return delta + + +def apply_finite_diff(order, x_list, y_list, x0=S.Zero): + """ + Calculates the finite difference approximation of + the derivative of requested order at ``x0`` from points + provided in ``x_list`` and ``y_list``. + + Parameters + ========== + + order: int + order of derivative to approximate. 0 corresponds to interpolation. + x_list: sequence + Sequence of (unique) values for the independent variable. + y_list: sequence + The function value at corresponding values for the independent + variable in x_list. + x0: Number or Symbol + At what value of the independent variable the derivative should be + evaluated. Defaults to 0. + + Returns + ======= + + sympy.core.add.Add or sympy.core.numbers.Number + The finite difference expression approximating the requested + derivative order at ``x0``. + + Examples + ======== + + >>> from sympy import apply_finite_diff + >>> cube = lambda arg: (1.0*arg)**3 + >>> xlist = range(-3,3+1) + >>> apply_finite_diff(2, xlist, map(cube, xlist), 2) - 12 # doctest: +SKIP + -3.55271367880050e-15 + + we see that the example above only contain rounding errors. + apply_finite_diff can also be used on more abstract objects: + + >>> from sympy import IndexedBase, Idx + >>> x, y = map(IndexedBase, 'xy') + >>> i = Idx('i') + >>> x_list, y_list = zip(*[(x[i+j], y[i+j]) for j in range(-1,2)]) + >>> apply_finite_diff(1, x_list, y_list, x[i]) + ((x[i + 1] - x[i])/(-x[i - 1] + x[i]) - 1)*y[i]/(x[i + 1] - x[i]) - + (x[i + 1] - x[i])*y[i - 1]/((x[i + 1] - x[i - 1])*(-x[i - 1] + x[i])) + + (-x[i - 1] + x[i])*y[i + 1]/((x[i + 1] - x[i - 1])*(x[i + 1] - x[i])) + + Notes + ===== + + Order = 0 corresponds to interpolation. + Only supply so many points you think makes sense + to around x0 when extracting the derivative (the function + need to be well behaved within that region). Also beware + of Runge's phenomenon. + + See also + ======== + + sympy.calculus.finite_diff.finite_diff_weights + + References + ========== + + Fortran 90 implementation with Python interface for numerics: finitediff_ + + .. _finitediff: https://github.com/bjodah/finitediff + + """ + + # In the original paper the following holds for the notation: + # M = order + # N = len(x_list) - 1 + + N = len(x_list) - 1 + if len(x_list) != len(y_list): + raise ValueError("x_list and y_list not equal in length.") + + delta = finite_diff_weights(order, x_list, x0) + + derivative = 0 + for nu in range(len(x_list)): + derivative += delta[order][N][nu]*y_list[nu] + return derivative + + +def _as_finite_diff(derivative, points=1, x0=None, wrt=None): + """ + Returns an approximation of a derivative of a function in + the form of a finite difference formula. The expression is a + weighted sum of the function at a number of discrete values of + (one of) the independent variable(s). + + Parameters + ========== + + derivative: a Derivative instance + + points: sequence or coefficient, optional + If sequence: discrete values (length >= order+1) of the + independent variable used for generating the finite + difference weights. + If it is a coefficient, it will be used as the step-size + for generating an equidistant sequence of length order+1 + centered around ``x0``. default: 1 (step-size 1) + + x0: number or Symbol, optional + the value of the independent variable (``wrt``) at which the + derivative is to be approximated. Default: same as ``wrt``. + + wrt: Symbol, optional + "with respect to" the variable for which the (partial) + derivative is to be approximated for. If not provided it + is required that the Derivative is ordinary. Default: ``None``. + + Examples + ======== + + >>> from sympy import symbols, Function, exp, sqrt, Symbol + >>> from sympy.calculus.finite_diff import _as_finite_diff + >>> x, h = symbols('x h') + >>> f = Function('f') + >>> _as_finite_diff(f(x).diff(x)) + -f(x - 1/2) + f(x + 1/2) + + The default step size and number of points are 1 and ``order + 1`` + respectively. We can change the step size by passing a symbol + as a parameter: + + >>> _as_finite_diff(f(x).diff(x), h) + -f(-h/2 + x)/h + f(h/2 + x)/h + + We can also specify the discretized values to be used in a sequence: + + >>> _as_finite_diff(f(x).diff(x), [x, x+h, x+2*h]) + -3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h) + + The algorithm is not restricted to use equidistant spacing, nor + do we need to make the approximation around ``x0``, but we can get + an expression estimating the derivative at an offset: + + >>> e, sq2 = exp(1), sqrt(2) + >>> xl = [x-h, x+h, x+e*h] + >>> _as_finite_diff(f(x).diff(x, 1), xl, x+h*sq2) + 2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/((-h + E*h)*(h + E*h)) + + (-(-sqrt(2)*h + h)/(2*h) - (-sqrt(2)*h + E*h)/(2*h))*f(-h + x)/(h + E*h) + + (-(h + sqrt(2)*h)/(2*h) + (-sqrt(2)*h + E*h)/(2*h))*f(h + x)/(-h + E*h) + + Partial derivatives are also supported: + + >>> y = Symbol('y') + >>> d2fdxdy=f(x,y).diff(x,y) + >>> _as_finite_diff(d2fdxdy, wrt=x) + -Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y) + + See also + ======== + + sympy.calculus.finite_diff.apply_finite_diff + sympy.calculus.finite_diff.finite_diff_weights + + """ + if derivative.is_Derivative: + pass + elif derivative.is_Atom: + return derivative + else: + return derivative.fromiter( + [_as_finite_diff(ar, points, x0, wrt) for ar + in derivative.args], **derivative.assumptions0) + + if wrt is None: + old = None + for v in derivative.variables: + if old is v: + continue + derivative = _as_finite_diff(derivative, points, x0, v) + old = v + return derivative + + order = derivative.variables.count(wrt) + + if x0 is None: + x0 = wrt + + if not iterable(points): + if getattr(points, 'is_Function', False) and wrt in points.args: + points = points.subs(wrt, x0) + # points is simply the step-size, let's make it a + # equidistant sequence centered around x0 + if order % 2 == 0: + # even order => odd number of points, grid point included + points = [x0 + points*i for i + in range(-order//2, order//2 + 1)] + else: + # odd order => even number of points, half-way wrt grid point + points = [x0 + points*S(i)/2 for i + in range(-order, order + 1, 2)] + others = [wrt, 0] + for v in set(derivative.variables): + if v == wrt: + continue + others += [v, derivative.variables.count(v)] + if len(points) < order+1: + raise ValueError("Too few points for order %d" % order) + return apply_finite_diff(order, points, [ + Derivative(derivative.expr.subs({wrt: x}), *others) for + x in points], x0) + + +def differentiate_finite(expr, *symbols, + points=1, x0=None, wrt=None, evaluate=False): + r""" Differentiate expr and replace Derivatives with finite differences. + + Parameters + ========== + + expr : expression + \*symbols : differentiate with respect to symbols + points: sequence, coefficient or undefined function, optional + see ``Derivative.as_finite_difference`` + x0: number or Symbol, optional + see ``Derivative.as_finite_difference`` + wrt: Symbol, optional + see ``Derivative.as_finite_difference`` + + Examples + ======== + + >>> from sympy import sin, Function, differentiate_finite + >>> from sympy.abc import x, y, h + >>> f, g = Function('f'), Function('g') + >>> differentiate_finite(f(x)*g(x), x, points=[x-h, x+h]) + -f(-h + x)*g(-h + x)/(2*h) + f(h + x)*g(h + x)/(2*h) + + ``differentiate_finite`` works on any expression, including the expressions + with embedded derivatives: + + >>> differentiate_finite(f(x) + sin(x), x, 2) + -2*f(x) + f(x - 1) + f(x + 1) - 2*sin(x) + sin(x - 1) + sin(x + 1) + >>> differentiate_finite(f(x, y), x, y) + f(x - 1/2, y - 1/2) - f(x - 1/2, y + 1/2) - f(x + 1/2, y - 1/2) + f(x + 1/2, y + 1/2) + >>> differentiate_finite(f(x)*g(x).diff(x), x) + (-g(x) + g(x + 1))*f(x + 1/2) - (g(x) - g(x - 1))*f(x - 1/2) + + To make finite difference with non-constant discretization step use + undefined functions: + + >>> dx = Function('dx') + >>> differentiate_finite(f(x)*g(x).diff(x), points=dx(x)) + -(-g(x - dx(x)/2 - dx(x - dx(x)/2)/2)/dx(x - dx(x)/2) + + g(x - dx(x)/2 + dx(x - dx(x)/2)/2)/dx(x - dx(x)/2))*f(x - dx(x)/2)/dx(x) + + (-g(x + dx(x)/2 - dx(x + dx(x)/2)/2)/dx(x + dx(x)/2) + + g(x + dx(x)/2 + dx(x + dx(x)/2)/2)/dx(x + dx(x)/2))*f(x + dx(x)/2)/dx(x) + + """ + if any(term.is_Derivative for term in list(preorder_traversal(expr))): + evaluate = False + + Dexpr = expr.diff(*symbols, evaluate=evaluate) + if evaluate: + sympy_deprecation_warning(""" + The evaluate flag to differentiate_finite() is deprecated. + + evaluate=True expands the intermediate derivatives before computing + differences, but this usually not what you want, as it does not + satisfy the product rule. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-differentiate_finite-evaluate", + ) + return Dexpr.replace( + lambda arg: arg.is_Derivative, + lambda arg: arg.as_finite_difference(points=points, x0=x0, wrt=wrt)) + else: + DFexpr = Dexpr.as_finite_difference(points=points, x0=x0, wrt=wrt) + return DFexpr.replace( + lambda arg: isinstance(arg, Subs), + lambda arg: arg.expr.as_finite_difference( + points=points, x0=arg.point[0], wrt=arg.variables[0])) diff --git a/phivenv/Lib/site-packages/sympy/calculus/singularities.py b/phivenv/Lib/site-packages/sympy/calculus/singularities.py new file mode 100644 index 0000000000000000000000000000000000000000..5adafc59efaf0bff44707f6b5e3f074be6bc1f32 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/singularities.py @@ -0,0 +1,406 @@ +""" +Singularities +============= + +This module implements algorithms for finding singularities for a function +and identifying types of functions. + +The differential calculus methods in this module include methods to identify +the following function types in the given ``Interval``: +- Increasing +- Strictly Increasing +- Decreasing +- Strictly Decreasing +- Monotonic + +""" + +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.trigonometric import sec, csc, cot, tan, cos +from sympy.functions.elementary.hyperbolic import ( + sech, csch, coth, tanh, cosh, asech, acsch, atanh, acoth) +from sympy.utilities.misc import filldedent + + +def singularities(expression, symbol, domain=None): + """ + Find singularities of a given function. + + Parameters + ========== + + expression : Expr + The target function in which singularities need to be found. + symbol : Symbol + The symbol over the values of which the singularity in + expression in being searched for. + + Returns + ======= + + Set + A set of values for ``symbol`` for which ``expression`` has a + singularity. An ``EmptySet`` is returned if ``expression`` has no + singularities for any given value of ``Symbol``. + + Raises + ====== + + NotImplementedError + Methods for determining the singularities of this function have + not been developed. + + Notes + ===== + + This function does not find non-isolated singularities + nor does it find branch points of the expression. + + Currently supported functions are: + - univariate continuous (real or complex) functions + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Mathematical_singularity + + Examples + ======== + + >>> from sympy import singularities, Symbol, log + >>> x = Symbol('x', real=True) + >>> y = Symbol('y', real=False) + >>> singularities(x**2 + x + 1, x) + EmptySet + >>> singularities(1/(x + 1), x) + {-1} + >>> singularities(1/(y**2 + 1), y) + {-I, I} + >>> singularities(1/(y**3 + 1), y) + {-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2} + >>> singularities(log(x), x) + {0} + + """ + from sympy.solvers.solveset import solveset + + if domain is None: + domain = S.Reals if symbol.is_real else S.Complexes + try: + sings = S.EmptySet + e = expression.rewrite([sec, csc, cot, tan], cos) + e = e.rewrite([sech, csch, coth, tanh], cosh) + for i in e.atoms(Pow): + if i.exp.is_infinite: + raise NotImplementedError + if i.exp.is_negative: + # XXX: exponent of varying sign not handled + sings += solveset(i.base, symbol, domain) + for i in expression.atoms(log, asech, acsch): + sings += solveset(i.args[0], symbol, domain) + for i in expression.atoms(atanh, acoth): + sings += solveset(i.args[0] - 1, symbol, domain) + sings += solveset(i.args[0] + 1, symbol, domain) + return sings + except NotImplementedError: + raise NotImplementedError(filldedent(''' + Methods for determining the singularities + of this function have not been developed.''')) + + +########################################################################### +# DIFFERENTIAL CALCULUS METHODS # +########################################################################### + + +def monotonicity_helper(expression, predicate, interval=S.Reals, symbol=None): + """ + Helper function for functions checking function monotonicity. + + Parameters + ========== + + expression : Expr + The target function which is being checked + predicate : function + The property being tested for. The function takes in an integer + and returns a boolean. The integer input is the derivative and + the boolean result should be true if the property is being held, + and false otherwise. + interval : Set, optional + The range of values in which we are testing, defaults to all reals. + symbol : Symbol, optional + The symbol present in expression which gets varied over the given range. + + It returns a boolean indicating whether the interval in which + the function's derivative satisfies given predicate is a superset + of the given interval. + + Returns + ======= + + Boolean + True if ``predicate`` is true for all the derivatives when ``symbol`` + is varied in ``range``, False otherwise. + + """ + from sympy.solvers.solveset import solveset + + expression = sympify(expression) + free = expression.free_symbols + + if symbol is None: + if len(free) > 1: + raise NotImplementedError( + 'The function has not yet been implemented' + ' for all multivariate expressions.' + ) + + variable = symbol or (free.pop() if free else Symbol('x')) + derivative = expression.diff(variable) + predicate_interval = solveset(predicate(derivative), variable, S.Reals) + return interval.is_subset(predicate_interval) + + +def is_increasing(expression, interval=S.Reals, symbol=None): + """ + Return whether the function is increasing in the given interval. + + Parameters + ========== + + expression : Expr + The target function which is being checked. + interval : Set, optional + The range of values in which we are testing (defaults to set of + all real numbers). + symbol : Symbol, optional + The symbol present in expression which gets varied over the given range. + + Returns + ======= + + Boolean + True if ``expression`` is increasing (either strictly increasing or + constant) in the given ``interval``, False otherwise. + + Examples + ======== + + >>> from sympy import is_increasing + >>> from sympy.abc import x, y + >>> from sympy import S, Interval, oo + >>> is_increasing(x**3 - 3*x**2 + 4*x, S.Reals) + True + >>> is_increasing(-x**2, Interval(-oo, 0)) + True + >>> is_increasing(-x**2, Interval(0, oo)) + False + >>> is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3)) + False + >>> is_increasing(x**2 + y, Interval(1, 2), x) + True + + """ + return monotonicity_helper(expression, lambda x: x >= 0, interval, symbol) + + +def is_strictly_increasing(expression, interval=S.Reals, symbol=None): + """ + Return whether the function is strictly increasing in the given interval. + + Parameters + ========== + + expression : Expr + The target function which is being checked. + interval : Set, optional + The range of values in which we are testing (defaults to set of + all real numbers). + symbol : Symbol, optional + The symbol present in expression which gets varied over the given range. + + Returns + ======= + + Boolean + True if ``expression`` is strictly increasing in the given ``interval``, + False otherwise. + + Examples + ======== + + >>> from sympy import is_strictly_increasing + >>> from sympy.abc import x, y + >>> from sympy import Interval, oo + >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2)) + True + >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo)) + True + >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3)) + False + >>> is_strictly_increasing(-x**2, Interval(0, oo)) + False + >>> is_strictly_increasing(-x**2 + y, Interval(-oo, 0), x) + False + + """ + return monotonicity_helper(expression, lambda x: x > 0, interval, symbol) + + +def is_decreasing(expression, interval=S.Reals, symbol=None): + """ + Return whether the function is decreasing in the given interval. + + Parameters + ========== + + expression : Expr + The target function which is being checked. + interval : Set, optional + The range of values in which we are testing (defaults to set of + all real numbers). + symbol : Symbol, optional + The symbol present in expression which gets varied over the given range. + + Returns + ======= + + Boolean + True if ``expression`` is decreasing (either strictly decreasing or + constant) in the given ``interval``, False otherwise. + + Examples + ======== + + >>> from sympy import is_decreasing + >>> from sympy.abc import x, y + >>> from sympy import S, Interval, oo + >>> is_decreasing(1/(x**2 - 3*x), Interval.open(S(3)/2, 3)) + True + >>> is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3)) + True + >>> is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo)) + True + >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2)) + False + >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5)) + False + >>> is_decreasing(-x**2, Interval(-oo, 0)) + False + >>> is_decreasing(-x**2 + y, Interval(-oo, 0), x) + False + + """ + return monotonicity_helper(expression, lambda x: x <= 0, interval, symbol) + + +def is_strictly_decreasing(expression, interval=S.Reals, symbol=None): + """ + Return whether the function is strictly decreasing in the given interval. + + Parameters + ========== + + expression : Expr + The target function which is being checked. + interval : Set, optional + The range of values in which we are testing (defaults to set of + all real numbers). + symbol : Symbol, optional + The symbol present in expression which gets varied over the given range. + + Returns + ======= + + Boolean + True if ``expression`` is strictly decreasing in the given ``interval``, + False otherwise. + + Examples + ======== + + >>> from sympy import is_strictly_decreasing + >>> from sympy.abc import x, y + >>> from sympy import S, Interval, oo + >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo)) + True + >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2)) + False + >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5)) + False + >>> is_strictly_decreasing(-x**2, Interval(-oo, 0)) + False + >>> is_strictly_decreasing(-x**2 + y, Interval(-oo, 0), x) + False + + """ + return monotonicity_helper(expression, lambda x: x < 0, interval, symbol) + + +def is_monotonic(expression, interval=S.Reals, symbol=None): + """ + Return whether the function is monotonic in the given interval. + + Parameters + ========== + + expression : Expr + The target function which is being checked. + interval : Set, optional + The range of values in which we are testing (defaults to set of + all real numbers). + symbol : Symbol, optional + The symbol present in expression which gets varied over the given range. + + Returns + ======= + + Boolean + True if ``expression`` is monotonic in the given ``interval``, + False otherwise. + + Raises + ====== + + NotImplementedError + Monotonicity check has not been implemented for the queried function. + + Examples + ======== + + >>> from sympy import is_monotonic + >>> from sympy.abc import x, y + >>> from sympy import S, Interval, oo + >>> is_monotonic(1/(x**2 - 3*x), Interval.open(S(3)/2, 3)) + True + >>> is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3)) + True + >>> is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo)) + True + >>> is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals) + True + >>> is_monotonic(-x**2, S.Reals) + False + >>> is_monotonic(x**2 + y + 1, Interval(1, 2), x) + True + + """ + from sympy.solvers.solveset import solveset + + expression = sympify(expression) + + free = expression.free_symbols + if symbol is None and len(free) > 1: + raise NotImplementedError( + 'is_monotonic has not yet been implemented' + ' for all multivariate expressions.' + ) + + variable = symbol or (free.pop() if free else Symbol('x')) + turning_points = solveset(expression.diff(variable), variable, interval) + return interval.intersection(turning_points) is S.EmptySet diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__init__.py b/phivenv/Lib/site-packages/sympy/calculus/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b50ca8eb8facfd4fc75c37bdec479841c5988ff7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_accumulationbounds.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_accumulationbounds.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42650068d3babe6c2d5aa6b281592a9bcd7a8ee3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_accumulationbounds.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_euler.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_euler.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d997d1a96a585895fbdd265e7adc994521e2848a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_euler.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_finite_diff.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_finite_diff.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c00e6b0840ea6b652430fe26783611b7012a053d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_finite_diff.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_singularities.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_singularities.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb24d1af839e8e2f8889f5d3c7bddfc84fd75229 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_singularities.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_util.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e08a2e0ae57e4f789151fa88227ffd895bc5907 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/calculus/tests/__pycache__/test_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/test_accumulationbounds.py b/phivenv/Lib/site-packages/sympy/calculus/tests/test_accumulationbounds.py new file mode 100644 index 0000000000000000000000000000000000000000..bcc47c66327fe21ddca3a6b73ca5914e0441b38e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/tests/test_accumulationbounds.py @@ -0,0 +1,336 @@ +from sympy.core.numbers import (E, Rational, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt) +from sympy.functions.elementary.trigonometric import (cos, sin, tan) +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core import Add, Mul, Pow +from sympy.core.expr import unchanged +from sympy.testing.pytest import raises, XFAIL +from sympy.abc import x + +a = Symbol('a', real=True) +B = AccumBounds + + +def test_AccumBounds(): + assert B(1, 2).args == (1, 2) + assert B(1, 2).delta is S.One + assert B(1, 2).mid == Rational(3, 2) + assert B(1, 3).is_real == True + + assert B(1, 1) is S.One + + assert B(1, 2) + 1 == B(2, 3) + assert 1 + B(1, 2) == B(2, 3) + assert B(1, 2) + B(2, 3) == B(3, 5) + + assert -B(1, 2) == B(-2, -1) + + assert B(1, 2) - 1 == B(0, 1) + assert 1 - B(1, 2) == B(-1, 0) + assert B(2, 3) - B(1, 2) == B(0, 2) + + assert x + B(1, 2) == Add(B(1, 2), x) + assert a + B(1, 2) == B(1 + a, 2 + a) + assert B(1, 2) - x == Add(B(1, 2), -x) + + assert B(-oo, 1) + oo == B(-oo, oo) + assert B(1, oo) + oo is oo + assert B(1, oo) - oo == B(-oo, oo) + assert (-oo - B(-1, oo)) is -oo + assert B(-oo, 1) - oo is -oo + + assert B(1, oo) - oo == B(-oo, oo) + assert B(-oo, 1) - (-oo) == B(-oo, oo) + assert (oo - B(1, oo)) == B(-oo, oo) + assert (-oo - B(1, oo)) is -oo + + assert B(1, 2)/2 == B(S.Half, 1) + assert 2/B(2, 3) == B(Rational(2, 3), 1) + assert 1/B(-1, 1) == B(-oo, oo) + + assert abs(B(1, 2)) == B(1, 2) + assert abs(B(-2, -1)) == B(1, 2) + assert abs(B(-2, 1)) == B(0, 2) + assert abs(B(-1, 2)) == B(0, 2) + c = Symbol('c') + raises(ValueError, lambda: B(0, c)) + raises(ValueError, lambda: B(1, -1)) + r = Symbol('r', real=True) + raises(ValueError, lambda: B(r, r - 1)) + + +def test_AccumBounds_mul(): + assert B(1, 2)*2 == B(2, 4) + assert 2*B(1, 2) == B(2, 4) + assert B(1, 2)*B(2, 3) == B(2, 6) + assert B(0, 2)*B(2, oo) == B(0, oo) + l, r = B(-oo, oo), B(-a, a) + assert l*r == B(-oo, oo) + assert r*l == B(-oo, oo) + l, r = B(1, oo), B(-3, -2) + assert l*r == B(-oo, -2) + assert r*l == B(-oo, -2) + assert B(1, 2)*0 == 0 + assert B(1, oo)*0 == B(0, oo) + assert B(-oo, 1)*0 == B(-oo, 0) + assert B(-oo, oo)*0 == B(-oo, oo) + + assert B(1, 2)*x == Mul(B(1, 2), x, evaluate=False) + + assert B(0, 2)*oo == B(0, oo) + assert B(-2, 0)*oo == B(-oo, 0) + assert B(0, 2)*(-oo) == B(-oo, 0) + assert B(-2, 0)*(-oo) == B(0, oo) + assert B(-1, 1)*oo == B(-oo, oo) + assert B(-1, 1)*(-oo) == B(-oo, oo) + assert B(-oo, oo)*oo == B(-oo, oo) + + +def test_AccumBounds_div(): + assert B(-1, 3)/B(3, 4) == B(Rational(-1, 3), 1) + assert B(-2, 4)/B(-3, 4) == B(-oo, oo) + assert B(-3, -2)/B(-4, 0) == B(S.Half, oo) + + # these two tests can have a better answer + # after Union of B is improved + assert B(-3, -2)/B(-2, 1) == B(-oo, oo) + assert B(2, 3)/B(-2, 2) == B(-oo, oo) + + assert B(-3, -2)/B(0, 4) == B(-oo, Rational(-1, 2)) + assert B(2, 4)/B(-3, 0) == B(-oo, Rational(-2, 3)) + assert B(2, 4)/B(0, 3) == B(Rational(2, 3), oo) + + assert B(0, 1)/B(0, 1) == B(0, oo) + assert B(-1, 0)/B(0, 1) == B(-oo, 0) + assert B(-1, 2)/B(-2, 2) == B(-oo, oo) + + assert 1/B(-1, 2) == B(-oo, oo) + assert 1/B(0, 2) == B(S.Half, oo) + assert (-1)/B(0, 2) == B(-oo, Rational(-1, 2)) + assert 1/B(-oo, 0) == B(-oo, 0) + assert 1/B(-1, 0) == B(-oo, -1) + assert (-2)/B(-oo, 0) == B(0, oo) + assert 1/B(-oo, -1) == B(-1, 0) + + assert B(1, 2)/a == Mul(B(1, 2), 1/a, evaluate=False) + + assert B(1, 2)/0 == B(1, 2)*zoo + assert B(1, oo)/oo == B(0, oo) + assert B(1, oo)/(-oo) == B(-oo, 0) + assert B(-oo, -1)/oo == B(-oo, 0) + assert B(-oo, -1)/(-oo) == B(0, oo) + assert B(-oo, oo)/oo == B(-oo, oo) + assert B(-oo, oo)/(-oo) == B(-oo, oo) + assert B(-1, oo)/oo == B(0, oo) + assert B(-1, oo)/(-oo) == B(-oo, 0) + assert B(-oo, 1)/oo == B(-oo, 0) + assert B(-oo, 1)/(-oo) == B(0, oo) + + +def test_issue_18795(): + r = Symbol('r', real=True) + a = B(-1,1) + c = B(7, oo) + b = B(-oo, oo) + assert c - tan(r) == B(7-tan(r), oo) + assert b + tan(r) == B(-oo, oo) + assert (a + r)/a == B(-oo, oo)*B(r - 1, r + 1) + assert (b + a)/a == B(-oo, oo) + + +def test_AccumBounds_func(): + assert (x**2 + 2*x + 1).subs(x, B(-1, 1)) == B(-1, 4) + assert exp(B(0, 1)) == B(1, E) + assert exp(B(-oo, oo)) == B(0, oo) + assert log(B(3, 6)) == B(log(3), log(6)) + + +@XFAIL +def test_AccumBounds_powf(): + nn = Symbol('nn', nonnegative=True) + assert B(1 + nn, 2 + nn)**B(1, 2) == B(1 + nn, (2 + nn)**2) + i = Symbol('i', integer=True, negative=True) + assert B(1, 2)**i == B(2**i, 1) + + +def test_AccumBounds_pow(): + assert B(0, 2)**2 == B(0, 4) + assert B(-1, 1)**2 == B(0, 1) + assert B(1, 2)**2 == B(1, 4) + assert B(-1, 2)**3 == B(-1, 8) + assert B(-1, 1)**0 == 1 + + assert B(1, 2)**Rational(5, 2) == B(1, 4*sqrt(2)) + assert B(0, 2)**S.Half == B(0, sqrt(2)) + + neg = Symbol('neg', negative=True) + assert unchanged(Pow, B(neg, 1), S.Half) + nn = Symbol('nn', nonnegative=True) + assert B(nn, nn + 1)**S.Half == B(sqrt(nn), sqrt(nn + 1)) + assert B(nn, nn + 1)**nn == B(nn**nn, (nn + 1)**nn) + assert unchanged(Pow, B(nn, nn + 1), x) + i = Symbol('i', integer=True) + assert B(1, 2)**i == B(Min(1, 2**i), Max(1, 2**i)) + i = Symbol('i', integer=True, nonnegative=True) + assert B(1, 2)**i == B(1, 2**i) + assert B(0, 1)**i == B(0**i, 1) + + assert B(1, 5)**(-2) == B(Rational(1, 25), 1) + assert B(-1, 3)**(-2) == B(0, oo) + assert B(0, 2)**(-3) == B(Rational(1, 8), oo) + assert B(-2, 0)**(-3) == B(-oo, -Rational(1, 8)) + assert B(0, 2)**(-2) == B(Rational(1, 4), oo) + assert B(-1, 2)**(-3) == B(-oo, oo) + assert B(-3, -2)**(-3) == B(Rational(-1, 8), Rational(-1, 27)) + assert B(-3, -2)**(-2) == B(Rational(1, 9), Rational(1, 4)) + assert B(0, oo)**S.Half == B(0, oo) + assert B(-oo, 0)**(-2) == B(0, oo) + assert B(-2, 0)**(-2) == B(Rational(1, 4), oo) + + assert B(Rational(1, 3), S.Half)**oo is S.Zero + assert B(0, S.Half)**oo is S.Zero + assert B(S.Half, 1)**oo == B(0, oo) + assert B(0, 1)**oo == B(0, oo) + assert B(2, 3)**oo is oo + assert B(1, 2)**oo == B(0, oo) + assert B(S.Half, 3)**oo == B(0, oo) + assert B(Rational(-1, 3), Rational(-1, 4))**oo is S.Zero + assert B(-1, Rational(-1, 2))**oo is S.NaN + assert B(-3, -2)**oo is zoo + assert B(-2, -1)**oo is S.NaN + assert B(-2, Rational(-1, 2))**oo is S.NaN + assert B(Rational(-1, 2), S.Half)**oo is S.Zero + assert B(Rational(-1, 2), 1)**oo == B(0, oo) + assert B(Rational(-2, 3), 2)**oo == B(0, oo) + assert B(-1, 1)**oo == B(-oo, oo) + assert B(-1, S.Half)**oo == B(-oo, oo) + assert B(-1, 2)**oo == B(-oo, oo) + assert B(-2, S.Half)**oo == B(-oo, oo) + + assert B(1, 2)**x == Pow(B(1, 2), x, evaluate=False) + + assert B(2, 3)**(-oo) is S.Zero + assert B(0, 2)**(-oo) == B(0, oo) + assert B(-1, 2)**(-oo) == B(-oo, oo) + + assert (tan(x)**sin(2*x)).subs(x, B(0, pi/2)) == \ + Pow(B(-oo, oo), B(0, 1)) + + +def test_AccumBounds_exponent(): + # base is 0 + z = 0**B(a, a + S.Half) + assert z.subs(a, 0) == B(0, 1) + assert z.subs(a, 1) == 0 + p = z.subs(a, -1) + assert p.is_Pow and p.args == (0, B(-1, -S.Half)) + # base > 0 + # when base is 1 the type of bounds does not matter + assert 1**B(a, a + 1) == 1 + # otherwise we need to know if 0 is in the bounds + assert S.Half**B(-2, 2) == B(S(1)/4, 4) + assert 2**B(-2, 2) == B(S(1)/4, 4) + + # +eps may introduce +oo + # if there is a negative integer exponent + assert B(0, 1)**B(S(1)/2, 1) == B(0, 1) + assert B(0, 1)**B(0, 1) == B(0, 1) + + # positive bases have positive bounds + assert B(2, 3)**B(-3, -2) == B(S(1)/27, S(1)/4) + assert B(2, 3)**B(-3, 2) == B(S(1)/27, 9) + + # bounds generating imaginary parts unevaluated + assert unchanged(Pow, B(-1, 1), B(1, 2)) + assert B(0, S(1)/2)**B(1, oo) == B(0, S(1)/2) + assert B(0, 1)**B(1, oo) == B(0, oo) + assert B(0, 2)**B(1, oo) == B(0, oo) + assert B(0, oo)**B(1, oo) == B(0, oo) + assert B(S(1)/2, 1)**B(1, oo) == B(0, oo) + assert B(S(1)/2, 1)**B(-oo, -1) == B(0, oo) + assert B(S(1)/2, 1)**B(-oo, oo) == B(0, oo) + assert B(S(1)/2, 2)**B(1, oo) == B(0, oo) + assert B(S(1)/2, 2)**B(-oo, -1) == B(0, oo) + assert B(S(1)/2, 2)**B(-oo, oo) == B(0, oo) + assert B(S(1)/2, oo)**B(1, oo) == B(0, oo) + assert B(S(1)/2, oo)**B(-oo, -1) == B(0, oo) + assert B(S(1)/2, oo)**B(-oo, oo) == B(0, oo) + assert B(1, 2)**B(1, oo) == B(0, oo) + assert B(1, 2)**B(-oo, -1) == B(0, oo) + assert B(1, 2)**B(-oo, oo) == B(0, oo) + assert B(1, oo)**B(1, oo) == B(0, oo) + assert B(1, oo)**B(-oo, -1) == B(0, oo) + assert B(1, oo)**B(-oo, oo) == B(0, oo) + assert B(2, oo)**B(1, oo) == B(2, oo) + assert B(2, oo)**B(-oo, -1) == B(0, S(1)/2) + assert B(2, oo)**B(-oo, oo) == B(0, oo) + + +def test_comparison_AccumBounds(): + assert (B(1, 3) < 4) == S.true + assert (B(1, 3) < -1) == S.false + assert (B(1, 3) < 2).rel_op == '<' + assert (B(1, 3) <= 2).rel_op == '<=' + + assert (B(1, 3) > 4) == S.false + assert (B(1, 3) > -1) == S.true + assert (B(1, 3) > 2).rel_op == '>' + assert (B(1, 3) >= 2).rel_op == '>=' + + assert (B(1, 3) < B(4, 6)) == S.true + assert (B(1, 3) < B(2, 4)).rel_op == '<' + assert (B(1, 3) < B(-2, 0)) == S.false + + assert (B(1, 3) <= B(4, 6)) == S.true + assert (B(1, 3) <= B(-2, 0)) == S.false + + assert (B(1, 3) > B(4, 6)) == S.false + assert (B(1, 3) > B(-2, 0)) == S.true + + assert (B(1, 3) >= B(4, 6)) == S.false + assert (B(1, 3) >= B(-2, 0)) == S.true + + # issue 13499 + assert (cos(x) > 0).subs(x, oo) == (B(-1, 1) > 0) + + c = Symbol('c') + raises(TypeError, lambda: (B(0, 1) < c)) + raises(TypeError, lambda: (B(0, 1) <= c)) + raises(TypeError, lambda: (B(0, 1) > c)) + raises(TypeError, lambda: (B(0, 1) >= c)) + + +def test_contains_AccumBounds(): + assert (1 in B(1, 2)) == S.true + raises(TypeError, lambda: a in B(1, 2)) + assert 0 in B(-1, 0) + raises(TypeError, lambda: + (cos(1)**2 + sin(1)**2 - 1) in B(-1, 0)) + assert (-oo in B(1, oo)) == S.true + assert (oo in B(-oo, 0)) == S.true + + # issue 13159 + assert Mul(0, B(-1, 1)) == Mul(B(-1, 1), 0) == 0 + import itertools + for perm in itertools.permutations([0, B(-1, 1), x]): + assert Mul(*perm) == 0 + + +def test_intersection_AccumBounds(): + assert B(0, 3).intersection(B(1, 2)) == B(1, 2) + assert B(0, 3).intersection(B(1, 4)) == B(1, 3) + assert B(0, 3).intersection(B(-1, 2)) == B(0, 2) + assert B(0, 3).intersection(B(-1, 4)) == B(0, 3) + assert B(0, 1).intersection(B(2, 3)) == S.EmptySet + raises(TypeError, lambda: B(0, 3).intersection(1)) + + +def test_union_AccumBounds(): + assert B(0, 3).union(B(1, 2)) == B(0, 3) + assert B(0, 3).union(B(1, 4)) == B(0, 4) + assert B(0, 3).union(B(-1, 2)) == B(-1, 3) + assert B(0, 3).union(B(-1, 4)) == B(-1, 4) + raises(TypeError, lambda: B(0, 3).union(1)) diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/test_euler.py b/phivenv/Lib/site-packages/sympy/calculus/tests/test_euler.py new file mode 100644 index 0000000000000000000000000000000000000000..56371c8c787d9459d1390e18c306fddde94d2745 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/tests/test_euler.py @@ -0,0 +1,74 @@ +from sympy.core.function import (Derivative as D, Function) +from sympy.core.relational import Eq +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.testing.pytest import raises +from sympy.calculus.euler import euler_equations as euler + + +def test_euler_interface(): + x = Function('x') + y = Symbol('y') + t = Symbol('t') + raises(TypeError, lambda: euler()) + raises(TypeError, lambda: euler(D(x(t), t)*y(t), [x(t), y])) + raises(ValueError, lambda: euler(D(x(t), t)*x(y), [x(t), x(y)])) + raises(TypeError, lambda: euler(D(x(t), t)**2, x(0))) + raises(TypeError, lambda: euler(D(x(t), t)*y(t), [t])) + assert euler(D(x(t), t)**2/2, {x(t)}) == [Eq(-D(x(t), t, t), 0)] + assert euler(D(x(t), t)**2/2, x(t), {t}) == [Eq(-D(x(t), t, t), 0)] + + +def test_euler_pendulum(): + x = Function('x') + t = Symbol('t') + L = D(x(t), t)**2/2 + cos(x(t)) + assert euler(L, x(t), t) == [Eq(-sin(x(t)) - D(x(t), t, t), 0)] + + +def test_euler_henonheiles(): + x = Function('x') + y = Function('y') + t = Symbol('t') + L = sum(D(z(t), t)**2/2 - z(t)**2/2 for z in [x, y]) + L += -x(t)**2*y(t) + y(t)**3/3 + assert euler(L, [x(t), y(t)], t) == [Eq(-2*x(t)*y(t) - x(t) - + D(x(t), t, t), 0), + Eq(-x(t)**2 + y(t)**2 - + y(t) - D(y(t), t, t), 0)] + + +def test_euler_sineg(): + psi = Function('psi') + t = Symbol('t') + x = Symbol('x') + L = D(psi(t, x), t)**2/2 - D(psi(t, x), x)**2/2 + cos(psi(t, x)) + assert euler(L, psi(t, x), [t, x]) == [Eq(-sin(psi(t, x)) - + D(psi(t, x), t, t) + + D(psi(t, x), x, x), 0)] + + +def test_euler_high_order(): + # an example from hep-th/0309038 + m = Symbol('m') + k = Symbol('k') + x = Function('x') + y = Function('y') + t = Symbol('t') + L = (m*D(x(t), t)**2/2 + m*D(y(t), t)**2/2 - + k*D(x(t), t)*D(y(t), t, t) + k*D(y(t), t)*D(x(t), t, t)) + assert euler(L, [x(t), y(t)]) == [Eq(2*k*D(y(t), t, t, t) - + m*D(x(t), t, t), 0), + Eq(-2*k*D(x(t), t, t, t) - + m*D(y(t), t, t), 0)] + + w = Symbol('w') + L = D(x(t, w), t, w)**2/2 + assert euler(L) == [Eq(D(x(t, w), t, t, w, w), 0)] + +def test_issue_18653(): + x, y, z = symbols("x y z") + f, g, h = symbols("f g h", cls=Function, args=(x, y)) + f, g, h = f(), g(), h() + expr2 = f.diff(x)*h.diff(z) + assert euler(expr2, (f,), (x, y)) == [] diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/test_finite_diff.py b/phivenv/Lib/site-packages/sympy/calculus/tests/test_finite_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..e9ecfbdd61b15f516c54bd6d716ba1f264ee2ca0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/tests/test_finite_diff.py @@ -0,0 +1,164 @@ +from itertools import product + +from sympy.core.function import (Function, diff) +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import exp +from sympy.calculus.finite_diff import ( + apply_finite_diff, differentiate_finite, finite_diff_weights, + _as_finite_diff +) +from sympy.testing.pytest import raises, warns_deprecated_sympy + + +def test_apply_finite_diff(): + x, h = symbols('x h') + f = Function('f') + assert (apply_finite_diff(1, [x-h, x+h], [f(x-h), f(x+h)], x) - + (f(x+h)-f(x-h))/(2*h)).simplify() == 0 + + assert (apply_finite_diff(1, [5, 6, 7], [f(5), f(6), f(7)], 5) - + (Rational(-3, 2)*f(5) + 2*f(6) - S.Half*f(7))).simplify() == 0 + raises(ValueError, lambda: apply_finite_diff(1, [x, h], [f(x)])) + + +def test_finite_diff_weights(): + + d = finite_diff_weights(1, [5, 6, 7], 5) + assert d[1][2] == [Rational(-3, 2), 2, Rational(-1, 2)] + + # Table 1, p. 702 in doi:10.1090/S0025-5718-1988-0935077-0 + # -------------------------------------------------------- + xl = [0, 1, -1, 2, -2, 3, -3, 4, -4] + + # d holds all coefficients + d = finite_diff_weights(4, xl, S.Zero) + + # Zeroeth derivative + for i in range(5): + assert d[0][i] == [S.One] + [S.Zero]*8 + + # First derivative + assert d[1][0] == [S.Zero]*9 + assert d[1][2] == [S.Zero, S.Half, Rational(-1, 2)] + [S.Zero]*6 + assert d[1][4] == [S.Zero, Rational(2, 3), Rational(-2, 3), Rational(-1, 12), Rational(1, 12)] + [S.Zero]*4 + assert d[1][6] == [S.Zero, Rational(3, 4), Rational(-3, 4), Rational(-3, 20), Rational(3, 20), + Rational(1, 60), Rational(-1, 60)] + [S.Zero]*2 + assert d[1][8] == [S.Zero, Rational(4, 5), Rational(-4, 5), Rational(-1, 5), Rational(1, 5), + Rational(4, 105), Rational(-4, 105), Rational(-1, 280), Rational(1, 280)] + + # Second derivative + for i in range(2): + assert d[2][i] == [S.Zero]*9 + assert d[2][2] == [-S(2), S.One, S.One] + [S.Zero]*6 + assert d[2][4] == [Rational(-5, 2), Rational(4, 3), Rational(4, 3), Rational(-1, 12), Rational(-1, 12)] + [S.Zero]*4 + assert d[2][6] == [Rational(-49, 18), Rational(3, 2), Rational(3, 2), Rational(-3, 20), Rational(-3, 20), + Rational(1, 90), Rational(1, 90)] + [S.Zero]*2 + assert d[2][8] == [Rational(-205, 72), Rational(8, 5), Rational(8, 5), Rational(-1, 5), Rational(-1, 5), + Rational(8, 315), Rational(8, 315), Rational(-1, 560), Rational(-1, 560)] + + # Third derivative + for i in range(3): + assert d[3][i] == [S.Zero]*9 + assert d[3][4] == [S.Zero, -S.One, S.One, S.Half, Rational(-1, 2)] + [S.Zero]*4 + assert d[3][6] == [S.Zero, Rational(-13, 8), Rational(13, 8), S.One, -S.One, + Rational(-1, 8), Rational(1, 8)] + [S.Zero]*2 + assert d[3][8] == [S.Zero, Rational(-61, 30), Rational(61, 30), Rational(169, 120), Rational(-169, 120), + Rational(-3, 10), Rational(3, 10), Rational(7, 240), Rational(-7, 240)] + + # Fourth derivative + for i in range(4): + assert d[4][i] == [S.Zero]*9 + assert d[4][4] == [S(6), -S(4), -S(4), S.One, S.One] + [S.Zero]*4 + assert d[4][6] == [Rational(28, 3), Rational(-13, 2), Rational(-13, 2), S(2), S(2), + Rational(-1, 6), Rational(-1, 6)] + [S.Zero]*2 + assert d[4][8] == [Rational(91, 8), Rational(-122, 15), Rational(-122, 15), Rational(169, 60), Rational(169, 60), + Rational(-2, 5), Rational(-2, 5), Rational(7, 240), Rational(7, 240)] + + # Table 2, p. 703 in doi:10.1090/S0025-5718-1988-0935077-0 + # -------------------------------------------------------- + xl = [[j/S(2) for j in list(range(-i*2+1, 0, 2))+list(range(1, i*2+1, 2))] + for i in range(1, 5)] + + # d holds all coefficients + d = [finite_diff_weights({0: 1, 1: 2, 2: 4, 3: 4}[i], xl[i], 0) for + i in range(4)] + + # Zeroth derivative + assert d[0][0][1] == [S.Half, S.Half] + assert d[1][0][3] == [Rational(-1, 16), Rational(9, 16), Rational(9, 16), Rational(-1, 16)] + assert d[2][0][5] == [Rational(3, 256), Rational(-25, 256), Rational(75, 128), Rational(75, 128), + Rational(-25, 256), Rational(3, 256)] + assert d[3][0][7] == [Rational(-5, 2048), Rational(49, 2048), Rational(-245, 2048), Rational(1225, 2048), + Rational(1225, 2048), Rational(-245, 2048), Rational(49, 2048), Rational(-5, 2048)] + + # First derivative + assert d[0][1][1] == [-S.One, S.One] + assert d[1][1][3] == [Rational(1, 24), Rational(-9, 8), Rational(9, 8), Rational(-1, 24)] + assert d[2][1][5] == [Rational(-3, 640), Rational(25, 384), Rational(-75, 64), + Rational(75, 64), Rational(-25, 384), Rational(3, 640)] + assert d[3][1][7] == [Rational(5, 7168), Rational(-49, 5120), + Rational(245, 3072), Rational(-1225, 1024), + Rational(1225, 1024), Rational(-245, 3072), + Rational(49, 5120), Rational(-5, 7168)] + + # Reasonably the rest of the table is also correct... (testing of that + # deemed excessive at the moment) + raises(ValueError, lambda: finite_diff_weights(-1, [1, 2])) + raises(ValueError, lambda: finite_diff_weights(1.2, [1, 2])) + x = symbols('x') + raises(ValueError, lambda: finite_diff_weights(x, [1, 2])) + + +def test_as_finite_diff(): + x = symbols('x') + f = Function('f') + dx = Function('dx') + + _as_finite_diff(f(x).diff(x), [x-2, x-1, x, x+1, x+2]) + + # Use of undefined functions in ``points`` + df_true = -f(x+dx(x)/2-dx(x+dx(x)/2)/2) / dx(x+dx(x)/2) \ + + f(x+dx(x)/2+dx(x+dx(x)/2)/2) / dx(x+dx(x)/2) + df_test = diff(f(x), x).as_finite_difference(points=dx(x), x0=x+dx(x)/2) + assert (df_test - df_true).simplify() == 0 + + +def test_differentiate_finite(): + x, y, h = symbols('x y h') + f = Function('f') + with warns_deprecated_sympy(): + res0 = differentiate_finite(f(x, y) + exp(42), x, y, evaluate=True) + xm, xp, ym, yp = [v + sign*S.Half for v, sign in product([x, y], [-1, 1])] + ref0 = f(xm, ym) + f(xp, yp) - f(xm, yp) - f(xp, ym) + assert (res0 - ref0).simplify() == 0 + + g = Function('g') + with warns_deprecated_sympy(): + res1 = differentiate_finite(f(x)*g(x) + 42, x, evaluate=True) + ref1 = (-f(x - S.Half) + f(x + S.Half))*g(x) + \ + (-g(x - S.Half) + g(x + S.Half))*f(x) + assert (res1 - ref1).simplify() == 0 + + res2 = differentiate_finite(f(x) + x**3 + 42, x, points=[x-1, x+1]) + ref2 = (f(x + 1) + (x + 1)**3 - f(x - 1) - (x - 1)**3)/2 + assert (res2 - ref2).simplify() == 0 + raises(TypeError, lambda: differentiate_finite(f(x)*g(x), x, + pints=[x-1, x+1])) + + res3 = differentiate_finite(f(x)*g(x).diff(x), x) + ref3 = (-g(x) + g(x + 1))*f(x + S.Half) - (g(x) - g(x - 1))*f(x - S.Half) + assert res3 == ref3 + + res4 = differentiate_finite(f(x)*g(x).diff(x).diff(x), x) + ref4 = -((g(x - Rational(3, 2)) - 2*g(x - S.Half) + g(x + S.Half))*f(x - S.Half)) \ + + (g(x - S.Half) - 2*g(x + S.Half) + g(x + Rational(3, 2)))*f(x + S.Half) + assert res4 == ref4 + + res5_expr = f(x).diff(x)*g(x).diff(x) + res5 = differentiate_finite(res5_expr, points=[x-h, x, x+h]) + ref5 = (-2*f(x)/h + f(-h + x)/(2*h) + 3*f(h + x)/(2*h))*(-2*g(x)/h + g(-h + x)/(2*h) \ + + 3*g(h + x)/(2*h))/(2*h) - (2*f(x)/h - 3*f(-h + x)/(2*h) - \ + f(h + x)/(2*h))*(2*g(x)/h - 3*g(-h + x)/(2*h) - g(h + x)/(2*h))/(2*h) + assert res5 == ref5 diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/test_singularities.py b/phivenv/Lib/site-packages/sympy/calculus/tests/test_singularities.py new file mode 100644 index 0000000000000000000000000000000000000000..19a042332326658021ce12a38f4e058f55903869 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/tests/test_singularities.py @@ -0,0 +1,122 @@ +from sympy.core.numbers import (I, Rational, pi, oo) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, Dummy +from sympy.core.function import Lambda +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.trigonometric import sec, csc +from sympy.functions.elementary.hyperbolic import (coth, sech, + atanh, asech, acoth, acsch) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.calculus.singularities import ( + singularities, + is_increasing, + is_strictly_increasing, + is_decreasing, + is_strictly_decreasing, + is_monotonic +) +from sympy.sets import Interval, FiniteSet, Union, ImageSet +from sympy.testing.pytest import raises +from sympy.abc import x, y + + +def test_singularities(): + x = Symbol('x') + assert singularities(x**2, x) == S.EmptySet + assert singularities(x/(x**2 + 3*x + 2), x) == FiniteSet(-2, -1) + assert singularities(1/(x**2 + 1), x) == FiniteSet(I, -I) + assert singularities(x/(x**3 + 1), x) == \ + FiniteSet(-1, (1 - sqrt(3) * I) / 2, (1 + sqrt(3) * I) / 2) + assert singularities(1/(y**2 + 2*I*y + 1), y) == \ + FiniteSet(-I + sqrt(2)*I, -I - sqrt(2)*I) + _n = Dummy('n') + assert singularities(sech(x), x).dummy_eq(Union( + ImageSet(Lambda(_n, 2*_n*I*pi + I*pi/2), S.Integers), + ImageSet(Lambda(_n, 2*_n*I*pi + 3*I*pi/2), S.Integers))) + assert singularities(coth(x), x).dummy_eq(Union( + ImageSet(Lambda(_n, 2*_n*I*pi + I*pi), S.Integers), + ImageSet(Lambda(_n, 2*_n*I*pi), S.Integers))) + assert singularities(atanh(x), x) == FiniteSet(-1, 1) + assert singularities(acoth(x), x) == FiniteSet(-1, 1) + assert singularities(asech(x), x) == FiniteSet(0) + assert singularities(acsch(x), x) == FiniteSet(0) + + x = Symbol('x', real=True) + assert singularities(1/(x**2 + 1), x) == S.EmptySet + assert singularities(exp(1/x), x, S.Reals) == FiniteSet(0) + assert singularities(exp(1/x), x, Interval(1, 2)) == S.EmptySet + assert singularities(log((x - 2)**2), x, Interval(1, 3)) == FiniteSet(2) + raises(NotImplementedError, lambda: singularities(x**-oo, x)) + assert singularities(sec(x), x, Interval(0, 3*pi)) == FiniteSet( + pi/2, 3*pi/2, 5*pi/2) + assert singularities(csc(x), x, Interval(0, 3*pi)) == FiniteSet( + 0, pi, 2*pi, 3*pi) + + +def test_is_increasing(): + """Test whether is_increasing returns correct value.""" + a = Symbol('a', negative=True) + + assert is_increasing(x**3 - 3*x**2 + 4*x, S.Reals) + assert is_increasing(-x**2, Interval(-oo, 0)) + assert not is_increasing(-x**2, Interval(0, oo)) + assert not is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3)) + assert is_increasing(x**2 + y, Interval(1, oo), x) + assert is_increasing(-x**2*a, Interval(1, oo), x) + assert is_increasing(1) + + assert is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3)) is False + + +def test_is_strictly_increasing(): + """Test whether is_strictly_increasing returns correct value.""" + assert is_strictly_increasing( + 4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2)) + assert is_strictly_increasing( + 4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo)) + assert not is_strictly_increasing( + 4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3)) + assert not is_strictly_increasing(-x**2, Interval(0, oo)) + assert not is_strictly_decreasing(1) + + assert is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3)) is False + + +def test_is_decreasing(): + """Test whether is_decreasing returns correct value.""" + b = Symbol('b', positive=True) + + assert is_decreasing(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3)) + assert is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3)) + assert is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo)) + assert not is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, Rational(3, 2))) + assert not is_decreasing(-x**2, Interval(-oo, 0)) + assert not is_decreasing(-x**2*b, Interval(-oo, 0), x) + + +def test_is_strictly_decreasing(): + """Test whether is_strictly_decreasing returns correct value.""" + assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo)) + assert not is_strictly_decreasing( + 1/(x**2 - 3*x), Interval.Ropen(-oo, Rational(3, 2))) + assert not is_strictly_decreasing(-x**2, Interval(-oo, 0)) + assert not is_strictly_decreasing(1) + assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3)) + assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3)) + + +def test_is_monotonic(): + """Test whether is_monotonic returns correct value.""" + assert is_monotonic(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3)) + assert is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3)) + assert is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo)) + assert is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals) + assert not is_monotonic(-x**2, S.Reals) + assert is_monotonic(x**2 + y + 1, Interval(1, 2), x) + raises(NotImplementedError, lambda: is_monotonic(x**2 + y + 1)) + + +def test_issue_23401(): + x = Symbol('x') + expr = (x + 1)/(-1.0e-3*x**2 + 0.1*x + 0.1) + assert is_increasing(expr, Interval(1,2), x) diff --git a/phivenv/Lib/site-packages/sympy/calculus/tests/test_util.py b/phivenv/Lib/site-packages/sympy/calculus/tests/test_util.py new file mode 100644 index 0000000000000000000000000000000000000000..c18b7a79fd54fdb2638cc746d43ab26753fc72a9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/tests/test_util.py @@ -0,0 +1,392 @@ +from sympy.core.function import Lambda +from sympy.core.numbers import (E, I, Rational, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.elementary.complexes import (Abs, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.integers import frac +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import ( + cos, cot, csc, sec, sin, tan, asin, acos, atan, acot, asec, acsc) +from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth, + sech, csch, asinh, acosh, atanh, acoth, asech, acsch) +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.error_functions import expint +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.simplify.simplify import simplify +from sympy.calculus.util import (function_range, continuous_domain, not_empty_in, + periodicity, lcim, is_convex, + stationary_points, minimum, maximum) +from sympy.sets.sets import (Interval, FiniteSet, Complement, Union) +from sympy.sets.fancysets import ImageSet +from sympy.sets.conditionset import ConditionSet +from sympy.testing.pytest import XFAIL, raises, _both_exp_pow, slow +from sympy.abc import x, y + +a = Symbol('a', real=True) + +def test_function_range(): + assert function_range(sin(x), x, Interval(-pi/2, pi/2) + ) == Interval(-1, 1) + assert function_range(sin(x), x, Interval(0, pi) + ) == Interval(0, 1) + assert function_range(tan(x), x, Interval(0, pi) + ) == Interval(-oo, oo) + assert function_range(tan(x), x, Interval(pi/2, pi) + ) == Interval(-oo, 0) + assert function_range((x + 3)/(x - 2), x, Interval(-5, 5) + ) == Union(Interval(-oo, Rational(2, 7)), Interval(Rational(8, 3), oo)) + assert function_range(1/(x**2), x, Interval(-1, 1) + ) == Interval(1, oo) + assert function_range(exp(x), x, Interval(-1, 1) + ) == Interval(exp(-1), exp(1)) + assert function_range(log(x) - x, x, S.Reals + ) == Interval(-oo, -1) + assert function_range(sqrt(3*x - 1), x, Interval(0, 2) + ) == Interval(0, sqrt(5)) + assert function_range(x*(x - 1) - (x**2 - x), x, S.Reals + ) == FiniteSet(0) + assert function_range(x*(x - 1) - (x**2 - x) + y, x, S.Reals + ) == FiniteSet(y) + assert function_range(sin(x), x, Union(Interval(-5, -3), FiniteSet(4)) + ) == Union(Interval(-sin(3), 1), FiniteSet(sin(4))) + assert function_range(cos(x), x, Interval(-oo, -4) + ) == Interval(-1, 1) + assert function_range(cos(x), x, S.EmptySet) == S.EmptySet + assert function_range(x/sqrt(x**2+1), x, S.Reals) == Interval.open(-1,1) + raises(NotImplementedError, lambda : function_range( + exp(x)*(sin(x) - cos(x))/2 - x, x, S.Reals)) + raises(NotImplementedError, lambda : function_range( + sin(x) + x, x, S.Reals)) # issue 13273 + raises(NotImplementedError, lambda : function_range( + log(x), x, S.Integers)) + raises(NotImplementedError, lambda : function_range( + sin(x)/2, x, S.Naturals)) + + +@slow +def test_function_range1(): + assert function_range(tan(x)**2 + tan(3*x)**2 + 1, x, S.Reals) == Interval(1,oo) + + +def test_continuous_domain(): + assert continuous_domain(sin(x), x, Interval(0, 2*pi)) == Interval(0, 2*pi) + assert continuous_domain(tan(x), x, Interval(0, 2*pi)) == \ + Union(Interval(0, pi/2, False, True), Interval(pi/2, pi*Rational(3, 2), True, True), + Interval(pi*Rational(3, 2), 2*pi, True, False)) + assert continuous_domain(cot(x), x, Interval(0, 2*pi)) == Union( + Interval.open(0, pi), Interval.open(pi, 2*pi)) + assert continuous_domain((x - 1)/((x - 1)**2), x, S.Reals) == \ + Union(Interval(-oo, 1, True, True), Interval(1, oo, True, True)) + assert continuous_domain(log(x) + log(4*x - 1), x, S.Reals) == \ + Interval(Rational(1, 4), oo, True, True) + assert continuous_domain(1/sqrt(x - 3), x, S.Reals) == Interval(3, oo, True, True) + assert continuous_domain(1/x - 2, x, S.Reals) == \ + Union(Interval.open(-oo, 0), Interval.open(0, oo)) + assert continuous_domain(1/(x**2 - 4) + 2, x, S.Reals) == \ + Union(Interval.open(-oo, -2), Interval.open(-2, 2), Interval.open(2, oo)) + assert continuous_domain((x+1)**pi, x, S.Reals) == Interval(-1, oo) + assert continuous_domain((x+1)**(pi/2), x, S.Reals) == Interval(-1, oo) + assert continuous_domain(x**x, x, S.Reals) == Interval(0, oo) + assert continuous_domain((x+1)**log(x**2), x, S.Reals) == Union( + Interval.Ropen(-1, 0), Interval.open(0, oo)) + domain = continuous_domain(log(tan(x)**2 + 1), x, S.Reals) + assert not domain.contains(3*pi/2) + assert domain.contains(5) + d = Symbol('d', even=True, zero=False) + assert continuous_domain(x**(1/d), x, S.Reals) == Interval(0, oo) + n = Dummy('n') + assert continuous_domain(1/sin(x), x, S.Reals).dummy_eq(Complement( + S.Reals, Union(ImageSet(Lambda(n, 2*n*pi + pi), S.Integers), + ImageSet(Lambda(n, 2*n*pi), S.Integers)))) + assert continuous_domain(sin(x) + cos(x), x, S.Reals) == S.Reals + assert continuous_domain(asin(x), x, S.Reals) == Interval(-1, 1) # issue #21786 + assert continuous_domain(1/acos(log(x)), x, S.Reals) == Interval.Ropen(exp(-1), E) + assert continuous_domain(sinh(x)+cosh(x), x, S.Reals) == S.Reals + assert continuous_domain(tanh(x)+sech(x), x, S.Reals) == S.Reals + assert continuous_domain(atan(x)+asinh(x), x, S.Reals) == S.Reals + assert continuous_domain(acosh(x), x, S.Reals) == Interval(1, oo) + assert continuous_domain(atanh(x), x, S.Reals) == Interval.open(-1, 1) + assert continuous_domain(atanh(x)+acosh(x), x, S.Reals) == S.EmptySet + assert continuous_domain(asech(x), x, S.Reals) == Interval.Lopen(0, 1) + assert continuous_domain(acoth(x), x, S.Reals) == Union( + Interval.open(-oo, -1), Interval.open(1, oo)) + assert continuous_domain(asec(x), x, S.Reals) == Union( + Interval(-oo, -1), Interval(1, oo)) + assert continuous_domain(acsc(x), x, S.Reals) == Union( + Interval(-oo, -1), Interval(1, oo)) + for f in (coth, acsch, csch): + assert continuous_domain(f(x), x, S.Reals) == Union( + Interval.open(-oo, 0), Interval.open(0, oo)) + assert continuous_domain(acot(x), x, S.Reals).contains(0) == False + assert continuous_domain(1/(exp(x) - x), x, S.Reals) == Complement( + S.Reals, ConditionSet(x, Eq(-x + exp(x), 0), S.Reals)) + assert continuous_domain(frac(x**2), x, Interval(-2,-1)) == Union( + Interval.open(-2, -sqrt(3)), Interval.open(-sqrt(2), -1), + Interval.open(-sqrt(3), -sqrt(2))) + assert continuous_domain(frac(x), x, S.Reals) == Complement( + S.Reals, S.Integers) + raises(NotImplementedError, lambda : continuous_domain( + 1/(x**2+1), x, S.Complexes)) + raises(NotImplementedError, lambda : continuous_domain( + gamma(x), x, Interval(-5,0))) + assert continuous_domain(x + gamma(pi), x, S.Reals) == S.Reals + + +@XFAIL +def test_continuous_domain_acot(): + acot_cont = Piecewise((pi+acot(x), x<0), (acot(x), True)) + assert continuous_domain(acot_cont, x, S.Reals) == S.Reals + +@XFAIL +def test_continuous_domain_gamma(): + assert continuous_domain(gamma(x), x, S.Reals).contains(-1) == False + +@XFAIL +def test_continuous_domain_neg_power(): + assert continuous_domain((x-2)**(1-x), x, S.Reals) == Interval.open(2, oo) + + +def test_not_empty_in(): + assert not_empty_in(FiniteSet(x, 2*x).intersect(Interval(1, 2, True, False)), x) == \ + Interval(S.Half, 2, True, False) + assert not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x) == \ + Union(Interval(-sqrt(2), -1), Interval(1, 2)) + assert not_empty_in(FiniteSet(x**2 + x, x).intersect(Interval(2, 4)), x) == \ + Union(Interval(-sqrt(17)/2 - S.Half, -2), + Interval(1, Rational(-1, 2) + sqrt(17)/2), Interval(2, 4)) + assert not_empty_in(FiniteSet(x/(x - 1)).intersect(S.Reals), x) == \ + Complement(S.Reals, FiniteSet(1)) + assert not_empty_in(FiniteSet(a/(a - 1)).intersect(S.Reals), a) == \ + Complement(S.Reals, FiniteSet(1)) + assert not_empty_in(FiniteSet((x**2 - 3*x + 2)/(x - 1)).intersect(S.Reals), x) == \ + Complement(S.Reals, FiniteSet(1)) + assert not_empty_in(FiniteSet(3, 4, x/(x - 1)).intersect(Interval(2, 3)), x) == \ + Interval(-oo, oo) + assert not_empty_in(FiniteSet(4, x/(x - 1)).intersect(Interval(2, 3)), x) == \ + Interval(S(3)/2, 2) + assert not_empty_in(FiniteSet(x/(x**2 - 1)).intersect(S.Reals), x) == \ + Complement(S.Reals, FiniteSet(-1, 1)) + assert not_empty_in(FiniteSet(x, x**2).intersect(Union(Interval(1, 3, True, True), + Interval(4, 5))), x) == \ + Union(Interval(-sqrt(5), -2), Interval(-sqrt(3), -1, True, True), + Interval(1, 3, True, True), Interval(4, 5)) + assert not_empty_in(FiniteSet(1).intersect(Interval(3, 4)), x) == S.EmptySet + assert not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x) == \ + Union(Interval(-2, -1, True, False), Interval(2, oo)) + raises(ValueError, lambda: not_empty_in(x)) + raises(ValueError, lambda: not_empty_in(Interval(0, 1), x)) + raises(NotImplementedError, + lambda: not_empty_in(FiniteSet(x).intersect(S.Reals), x, a)) + + +@_both_exp_pow +def test_periodicity(): + assert periodicity(sin(2*x), x) == pi + assert periodicity((-2)*tan(4*x), x) == pi/4 + assert periodicity(sin(x)**2, x) == 2*pi + assert periodicity(3**tan(3*x), x) == pi/3 + assert periodicity(tan(x)*cos(x), x) == 2*pi + assert periodicity(sin(x)**(tan(x)), x) == 2*pi + assert periodicity(tan(x)*sec(x), x) == 2*pi + assert periodicity(sin(2*x)*cos(2*x) - y, x) == pi/2 + assert periodicity(tan(x) + cot(x), x) == pi + assert periodicity(sin(x) - cos(2*x), x) == 2*pi + assert periodicity(sin(x) - 1, x) == 2*pi + assert periodicity(sin(4*x) + sin(x)*cos(x), x) == pi + assert periodicity(exp(sin(x)), x) == 2*pi + assert periodicity(log(cot(2*x)) - sin(cos(2*x)), x) == pi + assert periodicity(sin(2*x)*exp(tan(x) - csc(2*x)), x) == pi + assert periodicity(cos(sec(x) - csc(2*x)), x) == 2*pi + assert periodicity(tan(sin(2*x)), x) == pi + assert periodicity(2*tan(x)**2, x) == pi + assert periodicity(sin(x%4), x) == 4 + assert periodicity(sin(x)%4, x) == 2*pi + assert periodicity(tan((3*x-2)%4), x) == Rational(4, 3) + assert periodicity((sqrt(2)*(x+1)+x) % 3, x) == 3 / (sqrt(2)+1) + assert periodicity((x**2+1) % x, x) is None + assert periodicity(sin(re(x)), x) == 2*pi + assert periodicity(sin(x)**2 + cos(x)**2, x) is S.Zero + assert periodicity(tan(x), y) is S.Zero + assert periodicity(sin(x) + I*cos(x), x) == 2*pi + assert periodicity(x - sin(2*y), y) == pi + + assert periodicity(exp(x), x) is None + assert periodicity(exp(I*x), x) == 2*pi + assert periodicity(exp(I*a), a) == 2*pi + assert periodicity(exp(a), a) is None + assert periodicity(exp(log(sin(a) + I*cos(2*a)), evaluate=False), a) == 2*pi + assert periodicity(exp(log(sin(2*a) + I*cos(a)), evaluate=False), a) == 2*pi + assert periodicity(exp(sin(a)), a) == 2*pi + assert periodicity(exp(2*I*a), a) == pi + assert periodicity(exp(a + I*sin(a)), a) is None + assert periodicity(exp(cos(a/2) + sin(a)), a) == 4*pi + assert periodicity(log(x), x) is None + assert periodicity(exp(x)**sin(x), x) is None + assert periodicity(sin(x)**y, y) is None + + assert periodicity(Abs(sin(Abs(sin(x)))), x) == pi + assert all(periodicity(Abs(f(x)), x) == pi for f in ( + cos, sin, sec, csc, tan, cot)) + assert periodicity(Abs(sin(tan(x))), x) == pi + assert periodicity(Abs(sin(sin(x) + tan(x))), x) == 2*pi + assert periodicity(sin(x) > S.Half, x) == 2*pi + + assert periodicity(x > 2, x) is None + assert periodicity(x**3 - x**2 + 1, x) is None + assert periodicity(Abs(x), x) is None + assert periodicity(Abs(x**2 - 1), x) is None + + assert periodicity((x**2 + 4)%2, x) is None + assert periodicity((E**x)%3, x) is None + + assert periodicity(sin(expint(1, x))/expint(1, x), x) is None + # returning `None` for any Piecewise + p = Piecewise((0, x < -1), (x**2, x <= 1), (log(x), True)) + assert periodicity(p, x) is None + + m = MatrixSymbol('m', 3, 3) + raises(NotImplementedError, lambda: periodicity(sin(m), m)) + raises(NotImplementedError, lambda: periodicity(sin(m[0, 0]), m)) + raises(NotImplementedError, lambda: periodicity(sin(m), m[0, 0])) + raises(NotImplementedError, lambda: periodicity(sin(m[0, 0]), m[0, 0])) + + +def test_periodicity_check(): + assert periodicity(tan(x), x, check=True) == pi + assert periodicity(sin(x) + cos(x), x, check=True) == 2*pi + assert periodicity(sec(x), x) == 2*pi + assert periodicity(sin(x*y), x) == 2*pi/abs(y) + assert periodicity(Abs(sec(sec(x))), x) == pi + + +def test_lcim(): + assert lcim([S.Half, S(2), S(3)]) == 6 + assert lcim([pi/2, pi/4, pi]) == pi + assert lcim([2*pi, pi/2]) == 2*pi + assert lcim([S.One, 2*pi]) is None + assert lcim([S(2) + 2*E, E/3 + Rational(1, 3), S.One + E]) == S(2) + 2*E + + +def test_is_convex(): + assert is_convex(1/x, x, domain=Interval.open(0, oo)) == True + assert is_convex(1/x, x, domain=Interval(-oo, 0)) == False + assert is_convex(x**2, x, domain=Interval(0, oo)) == True + assert is_convex(1/x**3, x, domain=Interval.Lopen(0, oo)) == True + assert is_convex(-1/x**3, x, domain=Interval.Ropen(-oo, 0)) == True + assert is_convex(log(x) ,x) == False + assert is_convex(x**2+y**2, x, y) == True + assert is_convex(cos(x) + cos(y), x) == False + assert is_convex(8*x**2 - 2*y**2, x, y) == False + + +def test_stationary_points(): + assert stationary_points(sin(x), x, Interval(-pi/2, pi/2) + ) == {-pi/2, pi/2} + assert stationary_points(sin(x), x, Interval.Ropen(0, pi/4) + ) is S.EmptySet + assert stationary_points(tan(x), x, + ) is S.EmptySet + assert stationary_points(sin(x)*cos(x), x, Interval(0, pi) + ) == {pi/4, pi*Rational(3, 4)} + assert stationary_points(sec(x), x, Interval(0, pi) + ) == {0, pi} + assert stationary_points((x+3)*(x-2), x + ) == FiniteSet(Rational(-1, 2)) + assert stationary_points((x + 3)/(x - 2), x, Interval(-5, 5) + ) is S.EmptySet + assert stationary_points((x**2+3)/(x-2), x + ) == {2 - sqrt(7), 2 + sqrt(7)} + assert stationary_points((x**2+3)/(x-2), x, Interval(0, 5) + ) == {2 + sqrt(7)} + assert stationary_points(x**4 + x**3 - 5*x**2, x, S.Reals + ) == FiniteSet(-2, 0, Rational(5, 4)) + assert stationary_points(exp(x), x + ) is S.EmptySet + assert stationary_points(log(x) - x, x, S.Reals + ) == {1} + assert stationary_points(cos(x), x, Union(Interval(0, 5), Interval(-6, -3)) + ) == {0, -pi, pi} + assert stationary_points(y, x, S.Reals + ) == S.Reals + assert stationary_points(y, x, S.EmptySet) == S.EmptySet + + +def test_maximum(): + assert maximum(sin(x), x) is S.One + assert maximum(sin(x), x, Interval(0, 1)) == sin(1) + assert maximum(tan(x), x) is oo + assert maximum(tan(x), x, Interval(-pi/4, pi/4)) is S.One + assert maximum(sin(x)*cos(x), x, S.Reals) == S.Half + assert simplify(maximum(sin(x)*cos(x), x, Interval(pi*Rational(3, 8), pi*Rational(5, 8))) + ) == sqrt(2)/4 + assert maximum((x+3)*(x-2), x) is oo + assert maximum((x+3)*(x-2), x, Interval(-5, 0)) == S(14) + assert maximum((x+3)/(x-2), x, Interval(-5, 0)) == Rational(2, 7) + assert simplify(maximum(-x**4-x**3+x**2+10, x) + ) == 41*sqrt(41)/512 + Rational(5419, 512) + assert maximum(exp(x), x, Interval(-oo, 2)) == exp(2) + assert maximum(log(x) - x, x, S.Reals) is S.NegativeOne + assert maximum(cos(x), x, Union(Interval(0, 5), Interval(-6, -3)) + ) is S.One + assert maximum(cos(x)-sin(x), x, S.Reals) == sqrt(2) + assert maximum(y, x, S.Reals) == y + assert maximum(abs(a**3 + a), a, Interval(0, 2)) == 10 + assert maximum(abs(60*a**3 + 24*a), a, Interval(0, 2)) == 528 + assert maximum(abs(12*a*(5*a**2 + 2)), a, Interval(0, 2)) == 528 + assert maximum(x/sqrt(x**2+1), x, S.Reals) == 1 + + raises(ValueError, lambda : maximum(sin(x), x, S.EmptySet)) + raises(ValueError, lambda : maximum(log(cos(x)), x, S.EmptySet)) + raises(ValueError, lambda : maximum(1/(x**2 + y**2 + 1), x, S.EmptySet)) + raises(ValueError, lambda : maximum(sin(x), sin(x))) + raises(ValueError, lambda : maximum(sin(x), x*y, S.EmptySet)) + raises(ValueError, lambda : maximum(sin(x), S.One)) + + +def test_minimum(): + assert minimum(sin(x), x) is S.NegativeOne + assert minimum(sin(x), x, Interval(1, 4)) == sin(4) + assert minimum(tan(x), x) is -oo + assert minimum(tan(x), x, Interval(-pi/4, pi/4)) is S.NegativeOne + assert minimum(sin(x)*cos(x), x, S.Reals) == Rational(-1, 2) + assert simplify(minimum(sin(x)*cos(x), x, Interval(pi*Rational(3, 8), pi*Rational(5, 8))) + ) == -sqrt(2)/4 + assert minimum((x+3)*(x-2), x) == Rational(-25, 4) + assert minimum((x+3)/(x-2), x, Interval(-5, 0)) == Rational(-3, 2) + assert minimum(x**4-x**3+x**2+10, x) == S(10) + assert minimum(exp(x), x, Interval(-2, oo)) == exp(-2) + assert minimum(log(x) - x, x, S.Reals) is -oo + assert minimum(cos(x), x, Union(Interval(0, 5), Interval(-6, -3)) + ) is S.NegativeOne + assert minimum(cos(x)-sin(x), x, S.Reals) == -sqrt(2) + assert minimum(y, x, S.Reals) == y + assert minimum(x/sqrt(x**2+1), x, S.Reals) == -1 + + raises(ValueError, lambda : minimum(sin(x), x, S.EmptySet)) + raises(ValueError, lambda : minimum(log(cos(x)), x, S.EmptySet)) + raises(ValueError, lambda : minimum(1/(x**2 + y**2 + 1), x, S.EmptySet)) + raises(ValueError, lambda : minimum(sin(x), sin(x))) + raises(ValueError, lambda : minimum(sin(x), x*y, S.EmptySet)) + raises(ValueError, lambda : minimum(sin(x), S.One)) + + +def test_issue_19869(): + assert (maximum(sqrt(3)*(x - 1)/(3*sqrt(x**2 + 1)), x) + ) == sqrt(3)/3 + + +def test_issue_16469(): + f = abs(a) + assert function_range(f, a, S.Reals) == Interval(0, oo, False, True) + + +@_both_exp_pow +def test_issue_18747(): + assert periodicity(exp(pi*I*(x/4 + S.Half/2)), x) == 8 + + +def test_issue_25942(): + assert (acos(x) > pi/3).as_set() == Interval.Ropen(-1, S(1)/2) diff --git a/phivenv/Lib/site-packages/sympy/calculus/util.py b/phivenv/Lib/site-packages/sympy/calculus/util.py new file mode 100644 index 0000000000000000000000000000000000000000..dac316358873de2418dd8ab56445823f07a37b1b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/calculus/util.py @@ -0,0 +1,895 @@ +from .accumulationbounds import AccumBounds, AccumulationBounds # noqa: F401 +from .singularities import singularities +from sympy.core import Pow, S +from sympy.core.function import diff, expand_mul, Function +from sympy.core.kind import NumberKind +from sympy.core.mod import Mod +from sympy.core.numbers import equal_valued +from sympy.core.relational import Relational +from sympy.core.symbol import Symbol, Dummy +from sympy.core.sympify import _sympify +from sympy.functions.elementary.complexes import Abs, im, re +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.integers import frac +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import ( + TrigonometricFunction, sin, cos, tan, cot, csc, sec, + asin, acos, acot, atan, asec, acsc) +from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth, + sech, csch, asinh, acosh, atanh, acoth, asech, acsch) +from sympy.polys.polytools import degree, lcm_list +from sympy.sets.sets import (Interval, Intersection, FiniteSet, Union, + Complement) +from sympy.sets.fancysets import ImageSet +from sympy.sets.conditionset import ConditionSet +from sympy.utilities import filldedent +from sympy.utilities.iterables import iterable +from sympy.matrices.dense import hessian + + +def continuous_domain(f, symbol, domain): + """ + Returns the domain on which the function expression f is continuous. + + This function is limited by the ability to determine the various + singularities and discontinuities of the given function. + The result is either given as a union of intervals or constructed using + other set operations. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + symbol : :py:class:`~.Symbol` + The variable for which the intervals are to be determined. + domain : :py:class:`~.Interval` + The domain over which the continuity of the symbol has to be checked. + + Examples + ======== + + >>> from sympy import Interval, Symbol, S, tan, log, pi, sqrt + >>> from sympy.calculus.util import continuous_domain + >>> x = Symbol('x') + >>> continuous_domain(1/x, x, S.Reals) + Union(Interval.open(-oo, 0), Interval.open(0, oo)) + >>> continuous_domain(tan(x), x, Interval(0, pi)) + Union(Interval.Ropen(0, pi/2), Interval.Lopen(pi/2, pi)) + >>> continuous_domain(sqrt(x - 2), x, Interval(-5, 5)) + Interval(2, 5) + >>> continuous_domain(log(2*x - 1), x, S.Reals) + Interval.open(1/2, oo) + + Returns + ======= + + :py:class:`~.Interval` + Union of all intervals where the function is continuous. + + Raises + ====== + + NotImplementedError + If the method to determine continuity of such a function + has not yet been developed. + + """ + from sympy.solvers.inequalities import solve_univariate_inequality + + if not domain.is_subset(S.Reals): + raise NotImplementedError(filldedent(''' + Domain must be a subset of S.Reals. + ''')) + implemented = [Pow, exp, log, Abs, frac, + sin, cos, tan, cot, sec, csc, + asin, acos, atan, acot, asec, acsc, + sinh, cosh, tanh, coth, sech, csch, + asinh, acosh, atanh, acoth, asech, acsch] + used = [fct.func for fct in f.atoms(Function) if fct.has(symbol)] + if any(func not in implemented for func in used): + raise NotImplementedError(filldedent(''' + Unable to determine the domain of the given function. + ''')) + + x = Symbol('x') + constraints = { + log: (x > 0,), + asin: (x >= -1, x <= 1), + acos: (x >= -1, x <= 1), + acosh: (x >= 1,), + atanh: (x > -1, x < 1), + asech: (x > 0, x <= 1) + } + constraints_union = { + asec: (x <= -1, x >= 1), + acsc: (x <= -1, x >= 1), + acoth: (x < -1, x > 1) + } + + cont_domain = domain + for atom in f.atoms(Pow): + den = atom.exp.as_numer_denom()[1] + if atom.exp.is_rational and den.is_odd: + pass # 0**negative handled by singularities() + else: + constraint = solve_univariate_inequality(atom.base >= 0, + symbol).as_set() + cont_domain = Intersection(constraint, cont_domain) + + for atom in f.atoms(Function): + if atom.func in constraints: + for c in constraints[atom.func]: + constraint_relational = c.subs(x, atom.args[0]) + constraint_set = solve_univariate_inequality( + constraint_relational, symbol).as_set() + cont_domain = Intersection(constraint_set, cont_domain) + elif atom.func in constraints_union: + constraint_set = S.EmptySet + for c in constraints_union[atom.func]: + constraint_relational = c.subs(x, atom.args[0]) + constraint_set += solve_univariate_inequality( + constraint_relational, symbol).as_set() + cont_domain = Intersection(constraint_set, cont_domain) + # XXX: the discontinuities below could be factored out in + # a new "discontinuities()". + elif atom.func == acot: + from sympy.solvers.solveset import solveset_real + # Sympy's acot() has a step discontinuity at 0. Since it's + # neither an essential singularity nor a pole, singularities() + # will not report it. But it's still relevant for determining + # the continuity of the function f. + cont_domain -= solveset_real(atom.args[0], symbol) + # Note that the above may introduce spurious discontinuities, e.g. + # for abs(acot(x)) at 0. + elif atom.func == frac: + from sympy.solvers.solveset import solveset_real + r = function_range(atom.args[0], symbol, domain) + r = Intersection(r, S.Integers) + if r.is_finite_set: + discont = S.EmptySet + for n in r: + discont += solveset_real(atom.args[0]-n, symbol) + else: + discont = ConditionSet( + symbol, S.Integers.contains(atom.args[0]), cont_domain) + cont_domain -= discont + + return cont_domain - singularities(f, symbol, domain) + + +def function_range(f, symbol, domain): + """ + Finds the range of a function in a given domain. + This method is limited by the ability to determine the singularities and + determine limits. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + symbol : :py:class:`~.Symbol` + The variable for which the range of function is to be determined. + domain : :py:class:`~.Interval` + The domain under which the range of the function has to be found. + + Examples + ======== + + >>> from sympy import Interval, Symbol, S, exp, log, pi, sqrt, sin, tan + >>> from sympy.calculus.util import function_range + >>> x = Symbol('x') + >>> function_range(sin(x), x, Interval(0, 2*pi)) + Interval(-1, 1) + >>> function_range(tan(x), x, Interval(-pi/2, pi/2)) + Interval(-oo, oo) + >>> function_range(1/x, x, S.Reals) + Union(Interval.open(-oo, 0), Interval.open(0, oo)) + >>> function_range(exp(x), x, S.Reals) + Interval.open(0, oo) + >>> function_range(log(x), x, S.Reals) + Interval(-oo, oo) + >>> function_range(sqrt(x), x, Interval(-5, 9)) + Interval(0, 3) + + Returns + ======= + + :py:class:`~.Interval` + Union of all ranges for all intervals under domain where function is + continuous. + + Raises + ====== + + NotImplementedError + If any of the intervals, in the given domain, for which function + is continuous are not finite or real, + OR if the critical points of the function on the domain cannot be found. + """ + + if domain is S.EmptySet: + return S.EmptySet + + period = periodicity(f, symbol) + if period == S.Zero: + # the expression is constant wrt symbol + return FiniteSet(f.expand()) + + from sympy.series.limits import limit + from sympy.solvers.solveset import solveset + + if period is not None: + if isinstance(domain, Interval): + if (domain.inf - domain.sup).is_infinite: + domain = Interval(0, period) + elif isinstance(domain, Union): + for sub_dom in domain.args: + if isinstance(sub_dom, Interval) and \ + ((sub_dom.inf - sub_dom.sup).is_infinite): + domain = Interval(0, period) + + intervals = continuous_domain(f, symbol, domain) + range_int = S.EmptySet + if isinstance(intervals,(Interval, FiniteSet)): + interval_iter = (intervals,) + elif isinstance(intervals, Union): + interval_iter = intervals.args + else: + raise NotImplementedError("Unable to find range for the given domain.") + + for interval in interval_iter: + if isinstance(interval, FiniteSet): + for singleton in interval: + if singleton in domain: + range_int += FiniteSet(f.subs(symbol, singleton)) + elif isinstance(interval, Interval): + vals = S.EmptySet + critical_values = S.EmptySet + bounds = ((interval.left_open, interval.inf, '+'), + (interval.right_open, interval.sup, '-')) + + for is_open, limit_point, direction in bounds: + if is_open: + critical_values += FiniteSet(limit(f, symbol, limit_point, direction)) + vals += critical_values + else: + vals += FiniteSet(f.subs(symbol, limit_point)) + + critical_points = solveset(f.diff(symbol), symbol, interval) + + if not iterable(critical_points): + raise NotImplementedError( + 'Unable to find critical points for {}'.format(f)) + if isinstance(critical_points, ImageSet): + raise NotImplementedError( + 'Infinite number of critical points for {}'.format(f)) + + for critical_point in critical_points: + vals += FiniteSet(f.subs(symbol, critical_point)) + + left_open, right_open = False, False + + if critical_values is not S.EmptySet: + if critical_values.inf == vals.inf: + left_open = True + + if critical_values.sup == vals.sup: + right_open = True + + range_int += Interval(vals.inf, vals.sup, left_open, right_open) + else: + raise NotImplementedError("Unable to find range for the given domain.") + + return range_int + + +def not_empty_in(finset_intersection, *syms): + """ + Finds the domain of the functions in ``finset_intersection`` in which the + ``finite_set`` is not-empty. + + Parameters + ========== + + finset_intersection : Intersection of FiniteSet + The unevaluated intersection of FiniteSet containing + real-valued functions with Union of Sets + syms : Tuple of symbols + Symbol for which domain is to be found + + Raises + ====== + + NotImplementedError + The algorithms to find the non-emptiness of the given FiniteSet are + not yet implemented. + ValueError + The input is not valid. + RuntimeError + It is a bug, please report it to the github issue tracker + (https://github.com/sympy/sympy/issues). + + Examples + ======== + + >>> from sympy import FiniteSet, Interval, not_empty_in, oo + >>> from sympy.abc import x + >>> not_empty_in(FiniteSet(x/2).intersect(Interval(0, 1)), x) + Interval(0, 2) + >>> not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x) + Union(Interval(1, 2), Interval(-sqrt(2), -1)) + >>> not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x) + Union(Interval.Lopen(-2, -1), Interval(2, oo)) + """ + + # TODO: handle piecewise defined functions + # TODO: handle transcendental functions + # TODO: handle multivariate functions + if len(syms) == 0: + raise ValueError("One or more symbols must be given in syms.") + + if finset_intersection is S.EmptySet: + return S.EmptySet + + if isinstance(finset_intersection, Union): + elm_in_sets = finset_intersection.args[0] + return Union(not_empty_in(finset_intersection.args[1], *syms), + elm_in_sets) + + if isinstance(finset_intersection, FiniteSet): + finite_set = finset_intersection + _sets = S.Reals + else: + finite_set = finset_intersection.args[1] + _sets = finset_intersection.args[0] + + if not isinstance(finite_set, FiniteSet): + raise ValueError('A FiniteSet must be given, not %s: %s' % + (type(finite_set), finite_set)) + + if len(syms) == 1: + symb = syms[0] + else: + raise NotImplementedError('more than one variables %s not handled' % + (syms,)) + + def elm_domain(expr, intrvl): + """ Finds the domain of an expression in any given interval """ + from sympy.solvers.solveset import solveset + + _start = intrvl.start + _end = intrvl.end + _singularities = solveset(expr.as_numer_denom()[1], symb, + domain=S.Reals) + + if intrvl.right_open: + if _end is S.Infinity: + _domain1 = S.Reals + else: + _domain1 = solveset(expr < _end, symb, domain=S.Reals) + else: + _domain1 = solveset(expr <= _end, symb, domain=S.Reals) + + if intrvl.left_open: + if _start is S.NegativeInfinity: + _domain2 = S.Reals + else: + _domain2 = solveset(expr > _start, symb, domain=S.Reals) + else: + _domain2 = solveset(expr >= _start, symb, domain=S.Reals) + + # domain in the interval + expr_with_sing = Intersection(_domain1, _domain2) + expr_domain = Complement(expr_with_sing, _singularities) + return expr_domain + + if isinstance(_sets, Interval): + return Union(*[elm_domain(element, _sets) for element in finite_set]) + + if isinstance(_sets, Union): + _domain = S.EmptySet + for intrvl in _sets.args: + _domain_element = Union(*[elm_domain(element, intrvl) + for element in finite_set]) + _domain = Union(_domain, _domain_element) + return _domain + + +def periodicity(f, symbol, check=False): + """ + Tests the given function for periodicity in the given symbol. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + symbol : :py:class:`~.Symbol` + The variable for which the period is to be determined. + check : bool, optional + The flag to verify whether the value being returned is a period or not. + + Returns + ======= + + period + The period of the function is returned. + ``None`` is returned when the function is aperiodic or has a complex period. + The value of $0$ is returned as the period of a constant function. + + Raises + ====== + + NotImplementedError + The value of the period computed cannot be verified. + + + Notes + ===== + + Currently, we do not support functions with a complex period. + The period of functions having complex periodic values such + as ``exp``, ``sinh`` is evaluated to ``None``. + + The value returned might not be the "fundamental" period of the given + function i.e. it may not be the smallest periodic value of the function. + + The verification of the period through the ``check`` flag is not reliable + due to internal simplification of the given expression. Hence, it is set + to ``False`` by default. + + Examples + ======== + >>> from sympy import periodicity, Symbol, sin, cos, tan, exp + >>> x = Symbol('x') + >>> f = sin(x) + sin(2*x) + sin(3*x) + >>> periodicity(f, x) + 2*pi + >>> periodicity(sin(x)*cos(x), x) + pi + >>> periodicity(exp(tan(2*x) - 1), x) + pi/2 + >>> periodicity(sin(4*x)**cos(2*x), x) + pi + >>> periodicity(exp(x), x) + """ + if symbol.kind is not NumberKind: + raise NotImplementedError("Cannot use symbol of kind %s" % symbol.kind) + temp = Dummy('x', real=True) + f = f.subs(symbol, temp) + symbol = temp + + def _check(orig_f, period): + '''Return the checked period or raise an error.''' + new_f = orig_f.subs(symbol, symbol + period) + if new_f.equals(orig_f): + return period + else: + raise NotImplementedError(filldedent(''' + The period of the given function cannot be verified. + When `%s` was replaced with `%s + %s` in `%s`, the result + was `%s` which was not recognized as being the same as + the original function. + So either the period was wrong or the two forms were + not recognized as being equal. + Set check=False to obtain the value.''' % + (symbol, symbol, period, orig_f, new_f))) + + orig_f = f + period = None + + if isinstance(f, Relational): + f = f.lhs - f.rhs + + f = f.simplify() + + if symbol not in f.free_symbols: + return S.Zero + + if isinstance(f, TrigonometricFunction): + try: + period = f.period(symbol) + except NotImplementedError: + pass + + if isinstance(f, Abs): + arg = f.args[0] + if isinstance(arg, (sec, csc, cos)): + # all but tan and cot might have a + # a period that is half as large + # so recast as sin + arg = sin(arg.args[0]) + period = periodicity(arg, symbol) + if period is not None and isinstance(arg, sin): + # the argument of Abs was a trigonometric other than + # cot or tan; test to see if the half-period + # is valid. Abs(arg) has behaviour equivalent to + # orig_f, so use that for test: + orig_f = Abs(arg) + try: + return _check(orig_f, period/2) + except NotImplementedError as err: + if check: + raise NotImplementedError(err) + # else let new orig_f and period be + # checked below + + if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1): + f = Pow(S.Exp1, expand_mul(f.exp)) + if im(f) != 0: + period_real = periodicity(re(f), symbol) + period_imag = periodicity(im(f), symbol) + if period_real is not None and period_imag is not None: + period = lcim([period_real, period_imag]) + + if f.is_Pow and f.base != S.Exp1: + base, expo = f.args + base_has_sym = base.has(symbol) + expo_has_sym = expo.has(symbol) + + if base_has_sym and not expo_has_sym: + period = periodicity(base, symbol) + + elif expo_has_sym and not base_has_sym: + period = periodicity(expo, symbol) + + else: + period = _periodicity(f.args, symbol) + + elif f.is_Mul: + coeff, g = f.as_independent(symbol, as_Add=False) + if isinstance(g, TrigonometricFunction) or not equal_valued(coeff, 1): + period = periodicity(g, symbol) + else: + period = _periodicity(g.args, symbol) + + elif f.is_Add: + k, g = f.as_independent(symbol) + if k is not S.Zero: + return periodicity(g, symbol) + + period = _periodicity(g.args, symbol) + + elif isinstance(f, Mod): + a, n = f.args + + if a == symbol: + period = n + elif isinstance(a, TrigonometricFunction): + period = periodicity(a, symbol) + #check if 'f' is linear in 'symbol' + elif (a.is_polynomial(symbol) and degree(a, symbol) == 1 and + symbol not in n.free_symbols): + period = Abs(n / a.diff(symbol)) + + elif isinstance(f, Piecewise): + pass # not handling Piecewise yet as the return type is not favorable + + elif period is None: + from sympy.solvers.decompogen import compogen, decompogen + g_s = decompogen(f, symbol) + num_of_gs = len(g_s) + if num_of_gs > 1: + for index, g in enumerate(reversed(g_s)): + start_index = num_of_gs - 1 - index + g = compogen(g_s[start_index:], symbol) + if g not in (orig_f, f): # Fix for issue 12620 + period = periodicity(g, symbol) + if period is not None: + break + + if period is not None: + if check: + return _check(orig_f, period) + return period + + return None + + +def _periodicity(args, symbol): + """ + Helper for `periodicity` to find the period of a list of simpler + functions. + It uses the `lcim` method to find the least common period of + all the functions. + + Parameters + ========== + + args : Tuple of :py:class:`~.Symbol` + All the symbols present in a function. + + symbol : :py:class:`~.Symbol` + The symbol over which the function is to be evaluated. + + Returns + ======= + + period + The least common period of the function for all the symbols + of the function. + ``None`` if for at least one of the symbols the function is aperiodic. + + """ + periods = [] + for f in args: + period = periodicity(f, symbol) + if period is None: + return None + + if period is not S.Zero: + periods.append(period) + + if len(periods) > 1: + return lcim(periods) + + if periods: + return periods[0] + + +def lcim(numbers): + """Returns the least common integral multiple of a list of numbers. + + The numbers can be rational or irrational or a mixture of both. + `None` is returned for incommensurable numbers. + + Parameters + ========== + + numbers : list + Numbers (rational and/or irrational) for which lcim is to be found. + + Returns + ======= + + number + lcim if it exists, otherwise ``None`` for incommensurable numbers. + + Examples + ======== + + >>> from sympy.calculus.util import lcim + >>> from sympy import S, pi + >>> lcim([S(1)/2, S(3)/4, S(5)/6]) + 15/2 + >>> lcim([2*pi, 3*pi, pi, pi/2]) + 6*pi + >>> lcim([S(1), 2*pi]) + """ + result = None + if all(num.is_irrational for num in numbers): + factorized_nums = [num.factor() for num in numbers] + factors_num = [num.as_coeff_Mul() for num in factorized_nums] + term = factors_num[0][1] + if all(factor == term for coeff, factor in factors_num): + common_term = term + coeffs = [coeff for coeff, factor in factors_num] + result = lcm_list(coeffs) * common_term + + elif all(num.is_rational for num in numbers): + result = lcm_list(numbers) + + else: + pass + + return result + +def is_convex(f, *syms, domain=S.Reals): + r"""Determines the convexity of the function passed in the argument. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + syms : Tuple of :py:class:`~.Symbol` + The variables with respect to which the convexity is to be determined. + domain : :py:class:`~.Interval`, optional + The domain over which the convexity of the function has to be checked. + If unspecified, S.Reals will be the default domain. + + Returns + ======= + + bool + The method returns ``True`` if the function is convex otherwise it + returns ``False``. + + Raises + ====== + + NotImplementedError + The check for the convexity of multivariate functions is not implemented yet. + + Notes + ===== + + To determine concavity of a function pass `-f` as the concerned function. + To determine logarithmic convexity of a function pass `\log(f)` as + concerned function. + To determine logarithmic concavity of a function pass `-\log(f)` as + concerned function. + + Currently, convexity check of multivariate functions is not handled. + + Examples + ======== + + >>> from sympy import is_convex, symbols, exp, oo, Interval + >>> x = symbols('x') + >>> is_convex(exp(x), x) + True + >>> is_convex(x**3, x, domain = Interval(-1, oo)) + False + >>> is_convex(1/x**2, x, domain=Interval.open(0, oo)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Convex_function + .. [2] http://www.ifp.illinois.edu/~angelia/L3_convfunc.pdf + .. [3] https://en.wikipedia.org/wiki/Logarithmically_convex_function + .. [4] https://en.wikipedia.org/wiki/Logarithmically_concave_function + .. [5] https://en.wikipedia.org/wiki/Concave_function + + """ + if len(syms) > 1 : + return hessian(f, syms).is_positive_semidefinite + from sympy.solvers.inequalities import solve_univariate_inequality + f = _sympify(f) + var = syms[0] + if any(s in domain for s in singularities(f, var)): + return False + condition = f.diff(var, 2) < 0 + if solve_univariate_inequality(condition, var, False, domain): + return False + return True + + +def stationary_points(f, symbol, domain=S.Reals): + """ + Returns the stationary points of a function (where derivative of the + function is 0) in the given domain. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + symbol : :py:class:`~.Symbol` + The variable for which the stationary points are to be determined. + domain : :py:class:`~.Interval` + The domain over which the stationary points have to be checked. + If unspecified, ``S.Reals`` will be the default domain. + + Returns + ======= + + Set + A set of stationary points for the function. If there are no + stationary point, an :py:class:`~.EmptySet` is returned. + + Examples + ======== + + >>> from sympy import Interval, Symbol, S, sin, pi, pprint, stationary_points + >>> x = Symbol('x') + + >>> stationary_points(1/x, x, S.Reals) + EmptySet + + >>> pprint(stationary_points(sin(x), x), use_unicode=False) + pi 3*pi + {2*n*pi + -- | n in Integers} U {2*n*pi + ---- | n in Integers} + 2 2 + + >>> stationary_points(sin(x),x, Interval(0, 4*pi)) + {pi/2, 3*pi/2, 5*pi/2, 7*pi/2} + + """ + from sympy.solvers.solveset import solveset + + if domain is S.EmptySet: + return S.EmptySet + + domain = continuous_domain(f, symbol, domain) + set = solveset(diff(f, symbol), symbol, domain) + + return set + + +def maximum(f, symbol, domain=S.Reals): + """ + Returns the maximum value of a function in the given domain. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + symbol : :py:class:`~.Symbol` + The variable for maximum value needs to be determined. + domain : :py:class:`~.Interval` + The domain over which the maximum have to be checked. + If unspecified, then the global maximum is returned. + + Returns + ======= + + number + Maximum value of the function in given domain. + + Examples + ======== + + >>> from sympy import Interval, Symbol, S, sin, cos, pi, maximum + >>> x = Symbol('x') + + >>> f = -x**2 + 2*x + 5 + >>> maximum(f, x, S.Reals) + 6 + + >>> maximum(sin(x), x, Interval(-pi, pi/4)) + sqrt(2)/2 + + >>> maximum(sin(x)*cos(x), x) + 1/2 + + """ + if isinstance(symbol, Symbol): + if domain is S.EmptySet: + raise ValueError("Maximum value not defined for empty domain.") + + return function_range(f, symbol, domain).sup + else: + raise ValueError("%s is not a valid symbol." % symbol) + + +def minimum(f, symbol, domain=S.Reals): + """ + Returns the minimum value of a function in the given domain. + + Parameters + ========== + + f : :py:class:`~.Expr` + The concerned function. + symbol : :py:class:`~.Symbol` + The variable for minimum value needs to be determined. + domain : :py:class:`~.Interval` + The domain over which the minimum have to be checked. + If unspecified, then the global minimum is returned. + + Returns + ======= + + number + Minimum value of the function in the given domain. + + Examples + ======== + + >>> from sympy import Interval, Symbol, S, sin, cos, minimum + >>> x = Symbol('x') + + >>> f = x**2 + 2*x + 5 + >>> minimum(f, x, S.Reals) + 4 + + >>> minimum(sin(x), x, Interval(2, 3)) + sin(3) + + >>> minimum(sin(x)*cos(x), x) + -1/2 + + """ + if isinstance(symbol, Symbol): + if domain is S.EmptySet: + raise ValueError("Minimum value not defined for empty domain.") + + return function_range(f, symbol, domain).inf + else: + raise ValueError("%s is not a valid symbol." % symbol) diff --git a/phivenv/Lib/site-packages/sympy/categories/__init__.py b/phivenv/Lib/site-packages/sympy/categories/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4c5007308a1b232e57f9ed164276862df0c5f265 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/categories/__init__.py @@ -0,0 +1,33 @@ +""" +Category Theory module. + +Provides some of the fundamental category-theory-related classes, +including categories, morphisms, diagrams. Functors are not +implemented yet. + +The general reference work this module tries to follow is + + [JoyOfCats] J. Adamek, H. Herrlich. G. E. Strecker: Abstract and + Concrete Categories. The Joy of Cats. + +The latest version of this book should be available for free download +from + + katmat.math.uni-bremen.de/acc/acc.pdf + +""" + +from .baseclasses import (Object, Morphism, IdentityMorphism, + NamedMorphism, CompositeMorphism, Category, + Diagram) + +from .diagram_drawing import (DiagramGrid, XypicDiagramDrawer, + xypic_draw_diagram, preview_diagram) + +__all__ = [ + 'Object', 'Morphism', 'IdentityMorphism', 'NamedMorphism', + 'CompositeMorphism', 'Category', 'Diagram', + + 'DiagramGrid', 'XypicDiagramDrawer', 'xypic_draw_diagram', + 'preview_diagram', +] diff --git a/phivenv/Lib/site-packages/sympy/categories/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/categories/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..186870ef6780e904badbdb27076bcfb98ddb0d0f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/categories/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/categories/__pycache__/baseclasses.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/categories/__pycache__/baseclasses.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e81afe7be5a91485f11c1e58b35feffc25d3cd05 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/categories/__pycache__/baseclasses.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/categories/__pycache__/diagram_drawing.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/categories/__pycache__/diagram_drawing.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a5bfbde4e75fa6a66f1b7195fa668b4734a7664 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/categories/__pycache__/diagram_drawing.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/categories/baseclasses.py b/phivenv/Lib/site-packages/sympy/categories/baseclasses.py new file mode 100644 index 0000000000000000000000000000000000000000..e6ab5153ae4e95f193030864c8f32a52254f2458 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/categories/baseclasses.py @@ -0,0 +1,978 @@ +from sympy.core import S, Basic, Dict, Symbol, Tuple, sympify +from sympy.core.symbol import Str +from sympy.sets import Set, FiniteSet, EmptySet +from sympy.utilities.iterables import iterable + + +class Class(Set): + r""" + The base class for any kind of class in the set-theoretic sense. + + Explanation + =========== + + In axiomatic set theories, everything is a class. A class which + can be a member of another class is a set. A class which is not a + member of another class is a proper class. The class `\{1, 2\}` + is a set; the class of all sets is a proper class. + + This class is essentially a synonym for :class:`sympy.core.Set`. + The goal of this class is to assure easier migration to the + eventual proper implementation of set theory. + """ + is_proper = False + + +class Object(Symbol): + """ + The base class for any kind of object in an abstract category. + + Explanation + =========== + + While technically any instance of :class:`~.Basic` will do, this + class is the recommended way to create abstract objects in + abstract categories. + """ + + +class Morphism(Basic): + """ + The base class for any morphism in an abstract category. + + Explanation + =========== + + In abstract categories, a morphism is an arrow between two + category objects. The object where the arrow starts is called the + domain, while the object where the arrow ends is called the + codomain. + + Two morphisms between the same pair of objects are considered to + be the same morphisms. To distinguish between morphisms between + the same objects use :class:`NamedMorphism`. + + It is prohibited to instantiate this class. Use one of the + derived classes instead. + + See Also + ======== + + IdentityMorphism, NamedMorphism, CompositeMorphism + """ + def __new__(cls, domain, codomain): + raise(NotImplementedError( + "Cannot instantiate Morphism. Use derived classes instead.")) + + @property + def domain(self): + """ + Returns the domain of the morphism. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> f = NamedMorphism(A, B, "f") + >>> f.domain + Object("A") + + """ + return self.args[0] + + @property + def codomain(self): + """ + Returns the codomain of the morphism. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> f = NamedMorphism(A, B, "f") + >>> f.codomain + Object("B") + + """ + return self.args[1] + + def compose(self, other): + r""" + Composes self with the supplied morphism. + + The order of elements in the composition is the usual order, + i.e., to construct `g\circ f` use ``g.compose(f)``. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> g * f + CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"), + NamedMorphism(Object("B"), Object("C"), "g"))) + >>> (g * f).domain + Object("A") + >>> (g * f).codomain + Object("C") + + """ + return CompositeMorphism(other, self) + + def __mul__(self, other): + r""" + Composes self with the supplied morphism. + + The semantics of this operation is given by the following + equation: ``g * f == g.compose(f)`` for composable morphisms + ``g`` and ``f``. + + See Also + ======== + + compose + """ + return self.compose(other) + + +class IdentityMorphism(Morphism): + """ + Represents an identity morphism. + + Explanation + =========== + + An identity morphism is a morphism with equal domain and codomain, + which acts as an identity with respect to composition. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, IdentityMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> f = NamedMorphism(A, B, "f") + >>> id_A = IdentityMorphism(A) + >>> id_B = IdentityMorphism(B) + >>> f * id_A == f + True + >>> id_B * f == f + True + + See Also + ======== + + Morphism + """ + def __new__(cls, domain): + return Basic.__new__(cls, domain) + + @property + def codomain(self): + return self.domain + + +class NamedMorphism(Morphism): + """ + Represents a morphism which has a name. + + Explanation + =========== + + Names are used to distinguish between morphisms which have the + same domain and codomain: two named morphisms are equal if they + have the same domains, codomains, and names. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> f = NamedMorphism(A, B, "f") + >>> f + NamedMorphism(Object("A"), Object("B"), "f") + >>> f.name + 'f' + + See Also + ======== + + Morphism + """ + def __new__(cls, domain, codomain, name): + if not name: + raise ValueError("Empty morphism names not allowed.") + + if not isinstance(name, Str): + name = Str(name) + + return Basic.__new__(cls, domain, codomain, name) + + @property + def name(self): + """ + Returns the name of the morphism. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> f = NamedMorphism(A, B, "f") + >>> f.name + 'f' + + """ + return self.args[2].name + + +class CompositeMorphism(Morphism): + r""" + Represents a morphism which is a composition of other morphisms. + + Explanation + =========== + + Two composite morphisms are equal if the morphisms they were + obtained from (components) are the same and were listed in the + same order. + + The arguments to the constructor for this class should be listed + in diagram order: to obtain the composition `g\circ f` from the + instances of :class:`Morphism` ``g`` and ``f`` use + ``CompositeMorphism(f, g)``. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, CompositeMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> g * f + CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"), + NamedMorphism(Object("B"), Object("C"), "g"))) + >>> CompositeMorphism(f, g) == g * f + True + + """ + @staticmethod + def _add_morphism(t, morphism): + """ + Intelligently adds ``morphism`` to tuple ``t``. + + Explanation + =========== + + If ``morphism`` is a composite morphism, its components are + added to the tuple. If ``morphism`` is an identity, nothing + is added to the tuple. + + No composability checks are performed. + """ + if isinstance(morphism, CompositeMorphism): + # ``morphism`` is a composite morphism; we have to + # denest its components. + return t + morphism.components + elif isinstance(morphism, IdentityMorphism): + # ``morphism`` is an identity. Nothing happens. + return t + else: + return t + Tuple(morphism) + + def __new__(cls, *components): + if components and not isinstance(components[0], Morphism): + # Maybe the user has explicitly supplied a list of + # morphisms. + return CompositeMorphism.__new__(cls, *components[0]) + + normalised_components = Tuple() + + for current, following in zip(components, components[1:]): + if not isinstance(current, Morphism) or \ + not isinstance(following, Morphism): + raise TypeError("All components must be morphisms.") + + if current.codomain != following.domain: + raise ValueError("Uncomposable morphisms.") + + normalised_components = CompositeMorphism._add_morphism( + normalised_components, current) + + # We haven't added the last morphism to the list of normalised + # components. Add it now. + normalised_components = CompositeMorphism._add_morphism( + normalised_components, components[-1]) + + if not normalised_components: + # If ``normalised_components`` is empty, only identities + # were supplied. Since they all were composable, they are + # all the same identities. + return components[0] + elif len(normalised_components) == 1: + # No sense to construct a whole CompositeMorphism. + return normalised_components[0] + + return Basic.__new__(cls, normalised_components) + + @property + def components(self): + """ + Returns the components of this composite morphism. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> (g * f).components + (NamedMorphism(Object("A"), Object("B"), "f"), + NamedMorphism(Object("B"), Object("C"), "g")) + + """ + return self.args[0] + + @property + def domain(self): + """ + Returns the domain of this composite morphism. + + The domain of the composite morphism is the domain of its + first component. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> (g * f).domain + Object("A") + + """ + return self.components[0].domain + + @property + def codomain(self): + """ + Returns the codomain of this composite morphism. + + The codomain of the composite morphism is the codomain of its + last component. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> (g * f).codomain + Object("C") + + """ + return self.components[-1].codomain + + def flatten(self, new_name): + """ + Forgets the composite structure of this morphism. + + Explanation + =========== + + If ``new_name`` is not empty, returns a :class:`NamedMorphism` + with the supplied name, otherwise returns a :class:`Morphism`. + In both cases the domain of the new morphism is the domain of + this composite morphism and the codomain of the new morphism + is the codomain of this composite morphism. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> (g * f).flatten("h") + NamedMorphism(Object("A"), Object("C"), "h") + + """ + return NamedMorphism(self.domain, self.codomain, new_name) + + +class Category(Basic): + r""" + An (abstract) category. + + Explanation + =========== + + A category [JoyOfCats] is a quadruple `\mbox{K} = (O, \hom, id, + \circ)` consisting of + + * a (set-theoretical) class `O`, whose members are called + `K`-objects, + + * for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose + members are called `K`-morphisms from `A` to `B`, + + * for a each `K`-object `A`, a morphism `id:A\rightarrow A`, + called the `K`-identity of `A`, + + * a composition law `\circ` associating with every `K`-morphisms + `f:A\rightarrow B` and `g:B\rightarrow C` a `K`-morphism `g\circ + f:A\rightarrow C`, called the composite of `f` and `g`. + + Composition is associative, `K`-identities are identities with + respect to composition, and the sets `\hom(A, B)` are pairwise + disjoint. + + This class knows nothing about its objects and morphisms. + Concrete cases of (abstract) categories should be implemented as + classes derived from this one. + + Certain instances of :class:`Diagram` can be asserted to be + commutative in a :class:`Category` by supplying the argument + ``commutative_diagrams`` in the constructor. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram, Category + >>> from sympy import FiniteSet + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g]) + >>> K = Category("K", commutative_diagrams=[d]) + >>> K.commutative_diagrams == FiniteSet(d) + True + + See Also + ======== + + Diagram + """ + def __new__(cls, name, objects=EmptySet, commutative_diagrams=EmptySet): + if not name: + raise ValueError("A Category cannot have an empty name.") + + if not isinstance(name, Str): + name = Str(name) + + if not isinstance(objects, Class): + objects = Class(objects) + + new_category = Basic.__new__(cls, name, objects, + FiniteSet(*commutative_diagrams)) + return new_category + + @property + def name(self): + """ + Returns the name of this category. + + Examples + ======== + + >>> from sympy.categories import Category + >>> K = Category("K") + >>> K.name + 'K' + + """ + return self.args[0].name + + @property + def objects(self): + """ + Returns the class of objects of this category. + + Examples + ======== + + >>> from sympy.categories import Object, Category + >>> from sympy import FiniteSet + >>> A = Object("A") + >>> B = Object("B") + >>> K = Category("K", FiniteSet(A, B)) + >>> K.objects + Class({Object("A"), Object("B")}) + + """ + return self.args[1] + + @property + def commutative_diagrams(self): + """ + Returns the :class:`~.FiniteSet` of diagrams which are known to + be commutative in this category. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram, Category + >>> from sympy import FiniteSet + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g]) + >>> K = Category("K", commutative_diagrams=[d]) + >>> K.commutative_diagrams == FiniteSet(d) + True + + """ + return self.args[2] + + def hom(self, A, B): + raise NotImplementedError( + "hom-sets are not implemented in Category.") + + def all_morphisms(self): + raise NotImplementedError( + "Obtaining the class of morphisms is not implemented in Category.") + + +class Diagram(Basic): + r""" + Represents a diagram in a certain category. + + Explanation + =========== + + Informally, a diagram is a collection of objects of a category and + certain morphisms between them. A diagram is still a monoid with + respect to morphism composition; i.e., identity morphisms, as well + as all composites of morphisms included in the diagram belong to + the diagram. For a more formal approach to this notion see + [Pare1970]. + + The components of composite morphisms are also added to the + diagram. No properties are assigned to such morphisms by default. + + A commutative diagram is often accompanied by a statement of the + following kind: "if such morphisms with such properties exist, + then such morphisms which such properties exist and the diagram is + commutative". To represent this, an instance of :class:`Diagram` + includes a collection of morphisms which are the premises and + another collection of conclusions. ``premises`` and + ``conclusions`` associate morphisms belonging to the corresponding + categories with the :class:`~.FiniteSet`'s of their properties. + + The set of properties of a composite morphism is the intersection + of the sets of properties of its components. The domain and + codomain of a conclusion morphism should be among the domains and + codomains of the morphisms listed as the premises of a diagram. + + No checks are carried out of whether the supplied object and + morphisms do belong to one and the same category. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy import pprint, default_sort_key + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g]) + >>> premises_keys = sorted(d.premises.keys(), key=default_sort_key) + >>> pprint(premises_keys, use_unicode=False) + [g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C] + >>> pprint(d.premises, use_unicode=False) + {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, + id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet} + >>> d = Diagram([f, g], {g * f: "unique"}) + >>> pprint(d.conclusions,use_unicode=False) + {g*f:A-->C: {unique}} + + References + ========== + + [Pare1970] B. Pareigis: Categories and functors. Academic Press, 1970. + + """ + @staticmethod + def _set_dict_union(dictionary, key, value): + """ + If ``key`` is in ``dictionary``, set the new value of ``key`` + to be the union between the old value and ``value``. + Otherwise, set the value of ``key`` to ``value. + + Returns ``True`` if the key already was in the dictionary and + ``False`` otherwise. + """ + if key in dictionary: + dictionary[key] = dictionary[key] | value + return True + else: + dictionary[key] = value + return False + + @staticmethod + def _add_morphism_closure(morphisms, morphism, props, add_identities=True, + recurse_composites=True): + """ + Adds a morphism and its attributes to the supplied dictionary + ``morphisms``. If ``add_identities`` is True, also adds the + identity morphisms for the domain and the codomain of + ``morphism``. + """ + if not Diagram._set_dict_union(morphisms, morphism, props): + # We have just added a new morphism. + + if isinstance(morphism, IdentityMorphism): + if props: + # Properties for identity morphisms don't really + # make sense, because very much is known about + # identity morphisms already, so much that they + # are trivial. Having properties for identity + # morphisms would only be confusing. + raise ValueError( + "Instances of IdentityMorphism cannot have properties.") + return + + if add_identities: + empty = EmptySet + + id_dom = IdentityMorphism(morphism.domain) + id_cod = IdentityMorphism(morphism.codomain) + + Diagram._set_dict_union(morphisms, id_dom, empty) + Diagram._set_dict_union(morphisms, id_cod, empty) + + for existing_morphism, existing_props in list(morphisms.items()): + new_props = existing_props & props + if morphism.domain == existing_morphism.codomain: + left = morphism * existing_morphism + Diagram._set_dict_union(morphisms, left, new_props) + if morphism.codomain == existing_morphism.domain: + right = existing_morphism * morphism + Diagram._set_dict_union(morphisms, right, new_props) + + if isinstance(morphism, CompositeMorphism) and recurse_composites: + # This is a composite morphism, add its components as + # well. + empty = EmptySet + for component in morphism.components: + Diagram._add_morphism_closure(morphisms, component, empty, + add_identities) + + def __new__(cls, *args): + """ + Construct a new instance of Diagram. + + Explanation + =========== + + If no arguments are supplied, an empty diagram is created. + + If at least an argument is supplied, ``args[0]`` is + interpreted as the premises of the diagram. If ``args[0]`` is + a list, it is interpreted as a list of :class:`Morphism`'s, in + which each :class:`Morphism` has an empty set of properties. + If ``args[0]`` is a Python dictionary or a :class:`Dict`, it + is interpreted as a dictionary associating to some + :class:`Morphism`'s some properties. + + If at least two arguments are supplied ``args[1]`` is + interpreted as the conclusions of the diagram. The type of + ``args[1]`` is interpreted in exactly the same way as the type + of ``args[0]``. If only one argument is supplied, the diagram + has no conclusions. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import IdentityMorphism, Diagram + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g]) + >>> IdentityMorphism(A) in d.premises.keys() + True + >>> g * f in d.premises.keys() + True + >>> d = Diagram([f, g], {g * f: "unique"}) + >>> d.conclusions[g * f] + {unique} + + """ + premises = {} + conclusions = {} + + # Here we will keep track of the objects which appear in the + # premises. + objects = EmptySet + + if len(args) >= 1: + # We've got some premises in the arguments. + premises_arg = args[0] + + if isinstance(premises_arg, list): + # The user has supplied a list of morphisms, none of + # which have any attributes. + empty = EmptySet + + for morphism in premises_arg: + objects |= FiniteSet(morphism.domain, morphism.codomain) + Diagram._add_morphism_closure(premises, morphism, empty) + elif isinstance(premises_arg, (dict, Dict)): + # The user has supplied a dictionary of morphisms and + # their properties. + for morphism, props in premises_arg.items(): + objects |= FiniteSet(morphism.domain, morphism.codomain) + Diagram._add_morphism_closure( + premises, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props)) + + if len(args) >= 2: + # We also have some conclusions. + conclusions_arg = args[1] + + if isinstance(conclusions_arg, list): + # The user has supplied a list of morphisms, none of + # which have any attributes. + empty = EmptySet + + for morphism in conclusions_arg: + # Check that no new objects appear in conclusions. + if ((sympify(objects.contains(morphism.domain)) is S.true) and + (sympify(objects.contains(morphism.codomain)) is S.true)): + # No need to add identities and recurse + # composites this time. + Diagram._add_morphism_closure( + conclusions, morphism, empty, add_identities=False, + recurse_composites=False) + elif isinstance(conclusions_arg, (dict, Dict)): + # The user has supplied a dictionary of morphisms and + # their properties. + for morphism, props in conclusions_arg.items(): + # Check that no new objects appear in conclusions. + if (morphism.domain in objects) and \ + (morphism.codomain in objects): + # No need to add identities and recurse + # composites this time. + Diagram._add_morphism_closure( + conclusions, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props), + add_identities=False, recurse_composites=False) + + return Basic.__new__(cls, Dict(premises), Dict(conclusions), objects) + + @property + def premises(self): + """ + Returns the premises of this diagram. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import IdentityMorphism, Diagram + >>> from sympy import pretty + >>> A = Object("A") + >>> B = Object("B") + >>> f = NamedMorphism(A, B, "f") + >>> id_A = IdentityMorphism(A) + >>> id_B = IdentityMorphism(B) + >>> d = Diagram([f]) + >>> print(pretty(d.premises, use_unicode=False)) + {id:A-->A: EmptySet, id:B-->B: EmptySet, f:A-->B: EmptySet} + + """ + return self.args[0] + + @property + def conclusions(self): + """ + Returns the conclusions of this diagram. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import IdentityMorphism, Diagram + >>> from sympy import FiniteSet + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g]) + >>> IdentityMorphism(A) in d.premises.keys() + True + >>> g * f in d.premises.keys() + True + >>> d = Diagram([f, g], {g * f: "unique"}) + >>> d.conclusions[g * f] == FiniteSet("unique") + True + + """ + return self.args[1] + + @property + def objects(self): + """ + Returns the :class:`~.FiniteSet` of objects that appear in this + diagram. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g]) + >>> d.objects + {Object("A"), Object("B"), Object("C")} + + """ + return self.args[2] + + def hom(self, A, B): + """ + Returns a 2-tuple of sets of morphisms between objects ``A`` and + ``B``: one set of morphisms listed as premises, and the other set + of morphisms listed as conclusions. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy import pretty + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g], {g * f: "unique"}) + >>> print(pretty(d.hom(A, C), use_unicode=False)) + ({g*f:A-->C}, {g*f:A-->C}) + + See Also + ======== + Object, Morphism + """ + premises = EmptySet + conclusions = EmptySet + + for morphism in self.premises.keys(): + if (morphism.domain == A) and (morphism.codomain == B): + premises |= FiniteSet(morphism) + for morphism in self.conclusions.keys(): + if (morphism.domain == A) and (morphism.codomain == B): + conclusions |= FiniteSet(morphism) + + return (premises, conclusions) + + def is_subdiagram(self, diagram): + """ + Checks whether ``diagram`` is a subdiagram of ``self``. + Diagram `D'` is a subdiagram of `D` if all premises + (conclusions) of `D'` are contained in the premises + (conclusions) of `D`. The morphisms contained + both in `D'` and `D` should have the same properties for `D'` + to be a subdiagram of `D`. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g], {g * f: "unique"}) + >>> d1 = Diagram([f]) + >>> d.is_subdiagram(d1) + True + >>> d1.is_subdiagram(d) + False + """ + premises = all((m in self.premises) and + (diagram.premises[m] == self.premises[m]) + for m in diagram.premises) + if not premises: + return False + + conclusions = all((m in self.conclusions) and + (diagram.conclusions[m] == self.conclusions[m]) + for m in diagram.conclusions) + + # Premises is surely ``True`` here. + return conclusions + + def subdiagram_from_objects(self, objects): + """ + If ``objects`` is a subset of the objects of ``self``, returns + a diagram which has as premises all those premises of ``self`` + which have a domains and codomains in ``objects``, likewise + for conclusions. Properties are preserved. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy import FiniteSet + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g], {f: "unique", g*f: "veryunique"}) + >>> d1 = d.subdiagram_from_objects(FiniteSet(A, B)) + >>> d1 == Diagram([f], {f: "unique"}) + True + """ + if not objects.is_subset(self.objects): + raise ValueError( + "Supplied objects should all belong to the diagram.") + + new_premises = {} + for morphism, props in self.premises.items(): + if ((sympify(objects.contains(morphism.domain)) is S.true) and + (sympify(objects.contains(morphism.codomain)) is S.true)): + new_premises[morphism] = props + + new_conclusions = {} + for morphism, props in self.conclusions.items(): + if ((sympify(objects.contains(morphism.domain)) is S.true) and + (sympify(objects.contains(morphism.codomain)) is S.true)): + new_conclusions[morphism] = props + + return Diagram(new_premises, new_conclusions) diff --git a/phivenv/Lib/site-packages/sympy/categories/diagram_drawing.py b/phivenv/Lib/site-packages/sympy/categories/diagram_drawing.py new file mode 100644 index 0000000000000000000000000000000000000000..2a9b507cd86cf0e633b5abf7a0c9a353740af334 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/categories/diagram_drawing.py @@ -0,0 +1,2580 @@ +r""" +This module contains the functionality to arrange the nodes of a +diagram on an abstract grid, and then to produce a graphical +representation of the grid. + +The currently supported back-ends are Xy-pic [Xypic]. + +Layout Algorithm +================ + +This section provides an overview of the algorithms implemented in +:class:`DiagramGrid` to lay out diagrams. + +The first step of the algorithm is the removal composite and identity +morphisms which do not have properties in the supplied diagram. The +premises and conclusions of the diagram are then merged. + +The generic layout algorithm begins with the construction of the +"skeleton" of the diagram. The skeleton is an undirected graph which +has the objects of the diagram as vertices and has an (undirected) +edge between each pair of objects between which there exist morphisms. +The direction of the morphisms does not matter at this stage. The +skeleton also includes an edge between each pair of vertices `A` and +`C` such that there exists an object `B` which is connected via +a morphism to `A`, and via a morphism to `C`. + +The skeleton constructed in this way has the property that every +object is a vertex of a triangle formed by three edges of the +skeleton. This property lies at the base of the generic layout +algorithm. + +After the skeleton has been constructed, the algorithm lists all +triangles which can be formed. Note that some triangles will not have +all edges corresponding to morphisms which will actually be drawn. +Triangles which have only one edge or less which will actually be +drawn are immediately discarded. + +The list of triangles is sorted according to the number of edges which +correspond to morphisms, then the triangle with the least number of such +edges is selected. One of such edges is picked and the corresponding +objects are placed horizontally, on a grid. This edge is recorded to +be in the fringe. The algorithm then finds a "welding" of a triangle +to the fringe. A welding is an edge in the fringe where a triangle +could be attached. If the algorithm succeeds in finding such a +welding, it adds to the grid that vertex of the triangle which was not +yet included in any edge in the fringe and records the two new edges in +the fringe. This process continues iteratively until all objects of +the diagram has been placed or until no more weldings can be found. + +An edge is only removed from the fringe when a welding to this edge +has been found, and there is no room around this edge to place +another vertex. + +When no more weldings can be found, but there are still triangles +left, the algorithm searches for a possibility of attaching one of the +remaining triangles to the existing structure by a vertex. If such a +possibility is found, the corresponding edge of the found triangle is +placed in the found space and the iterative process of welding +triangles restarts. + +When logical groups are supplied, each of these groups is laid out +independently. Then a diagram is constructed in which groups are +objects and any two logical groups between which there exist morphisms +are connected via a morphism. This diagram is laid out. Finally, +the grid which includes all objects of the initial diagram is +constructed by replacing the cells which contain logical groups with +the corresponding laid out grids, and by correspondingly expanding the +rows and columns. + +The sequential layout algorithm begins by constructing the +underlying undirected graph defined by the morphisms obtained after +simplifying premises and conclusions and merging them (see above). +The vertex with the minimal degree is then picked up and depth-first +search is started from it. All objects which are located at distance +`n` from the root in the depth-first search tree, are positioned in +the `n`-th column of the resulting grid. The sequential layout will +therefore attempt to lay the objects out along a line. + +References +========== + +.. [Xypic] https://xy-pic.sourceforge.net/ + +""" +from sympy.categories import (CompositeMorphism, IdentityMorphism, + NamedMorphism, Diagram) +from sympy.core import Dict, Symbol, default_sort_key +from sympy.printing.latex import latex +from sympy.sets import FiniteSet +from sympy.utilities.iterables import iterable +from sympy.utilities.decorator import doctest_depends_on + +from itertools import chain + + +__doctest_requires__ = {('preview_diagram',): 'pyglet'} + + +class _GrowableGrid: + """ + Holds a growable grid of objects. + + Explanation + =========== + + It is possible to append or prepend a row or a column to the grid + using the corresponding methods. Prepending rows or columns has + the effect of changing the coordinates of the already existing + elements. + + This class currently represents a naive implementation of the + functionality with little attempt at optimisation. + """ + def __init__(self, width, height): + self._width = width + self._height = height + + self._array = [[None for j in range(width)] for i in range(height)] + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + def __getitem__(self, i_j): + """ + Returns the element located at in the i-th line and j-th + column. + """ + i, j = i_j + return self._array[i][j] + + def __setitem__(self, i_j, newvalue): + """ + Sets the element located at in the i-th line and j-th + column. + """ + i, j = i_j + self._array[i][j] = newvalue + + def append_row(self): + """ + Appends an empty row to the grid. + """ + self._height += 1 + self._array.append([None for j in range(self._width)]) + + def append_column(self): + """ + Appends an empty column to the grid. + """ + self._width += 1 + for i in range(self._height): + self._array[i].append(None) + + def prepend_row(self): + """ + Prepends the grid with an empty row. + """ + self._height += 1 + self._array.insert(0, [None for j in range(self._width)]) + + def prepend_column(self): + """ + Prepends the grid with an empty column. + """ + self._width += 1 + for i in range(self._height): + self._array[i].insert(0, None) + + +class DiagramGrid: + r""" + Constructs and holds the fitting of the diagram into a grid. + + Explanation + =========== + + The mission of this class is to analyse the structure of the + supplied diagram and to place its objects on a grid such that, + when the objects and the morphisms are actually drawn, the diagram + would be "readable", in the sense that there will not be many + intersections of moprhisms. This class does not perform any + actual drawing. It does strive nevertheless to offer sufficient + metadata to draw a diagram. + + Consider the following simple diagram. + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import Diagram, DiagramGrid + >>> from sympy import pprint + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g]) + + The simplest way to have a diagram laid out is the following: + + >>> grid = DiagramGrid(diagram) + >>> (grid.width, grid.height) + (2, 2) + >>> pprint(grid) + A B + + C + + Sometimes one sees the diagram as consisting of logical groups. + One can advise ``DiagramGrid`` as to such groups by employing the + ``groups`` keyword argument. + + Consider the following diagram: + + >>> D = Object("D") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> h = NamedMorphism(D, A, "h") + >>> k = NamedMorphism(D, B, "k") + >>> diagram = Diagram([f, g, h, k]) + + Lay it out with generic layout: + + >>> grid = DiagramGrid(diagram) + >>> pprint(grid) + A B D + + C + + Now, we can group the objects `A` and `D` to have them near one + another: + + >>> grid = DiagramGrid(diagram, groups=[[A, D], B, C]) + >>> pprint(grid) + B C + + A D + + Note how the positioning of the other objects changes. + + Further indications can be supplied to the constructor of + :class:`DiagramGrid` using keyword arguments. The currently + supported hints are explained in the following paragraphs. + + :class:`DiagramGrid` does not automatically guess which layout + would suit the supplied diagram better. Consider, for example, + the following linear diagram: + + >>> E = Object("E") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> h = NamedMorphism(C, D, "h") + >>> i = NamedMorphism(D, E, "i") + >>> diagram = Diagram([f, g, h, i]) + + When laid out with the generic layout, it does not get to look + linear: + + >>> grid = DiagramGrid(diagram) + >>> pprint(grid) + A B + + C D + + E + + To get it laid out in a line, use ``layout="sequential"``: + + >>> grid = DiagramGrid(diagram, layout="sequential") + >>> pprint(grid) + A B C D E + + One may sometimes need to transpose the resulting layout. While + this can always be done by hand, :class:`DiagramGrid` provides a + hint for that purpose: + + >>> grid = DiagramGrid(diagram, layout="sequential", transpose=True) + >>> pprint(grid) + A + + B + + C + + D + + E + + Separate hints can also be provided for each group. For an + example, refer to ``tests/test_drawing.py``, and see the different + ways in which the five lemma [FiveLemma] can be laid out. + + See Also + ======== + + Diagram + + References + ========== + + .. [FiveLemma] https://en.wikipedia.org/wiki/Five_lemma + """ + @staticmethod + def _simplify_morphisms(morphisms): + """ + Given a dictionary mapping morphisms to their properties, + returns a new dictionary in which there are no morphisms which + do not have properties, and which are compositions of other + morphisms included in the dictionary. Identities are dropped + as well. + """ + newmorphisms = {} + for morphism, props in morphisms.items(): + if isinstance(morphism, CompositeMorphism) and not props: + continue + elif isinstance(morphism, IdentityMorphism): + continue + else: + newmorphisms[morphism] = props + return newmorphisms + + @staticmethod + def _merge_premises_conclusions(premises, conclusions): + """ + Given two dictionaries of morphisms and their properties, + produces a single dictionary which includes elements from both + dictionaries. If a morphism has some properties in premises + and also in conclusions, the properties in conclusions take + priority. + """ + return dict(chain(premises.items(), conclusions.items())) + + @staticmethod + def _juxtapose_edges(edge1, edge2): + """ + If ``edge1`` and ``edge2`` have precisely one common endpoint, + returns an edge which would form a triangle with ``edge1`` and + ``edge2``. + + If ``edge1`` and ``edge2`` do not have a common endpoint, + returns ``None``. + + If ``edge1`` and ``edge`` are the same edge, returns ``None``. + """ + intersection = edge1 & edge2 + if len(intersection) != 1: + # The edges either have no common points or are equal. + return None + + # The edges have a common endpoint. Extract the different + # endpoints and set up the new edge. + return (edge1 - intersection) | (edge2 - intersection) + + @staticmethod + def _add_edge_append(dictionary, edge, elem): + """ + If ``edge`` is not in ``dictionary``, adds ``edge`` to the + dictionary and sets its value to ``[elem]``. Otherwise + appends ``elem`` to the value of existing entry. + + Note that edges are undirected, thus `(A, B) = (B, A)`. + """ + if edge in dictionary: + dictionary[edge].append(elem) + else: + dictionary[edge] = [elem] + + @staticmethod + def _build_skeleton(morphisms): + """ + Creates a dictionary which maps edges to corresponding + morphisms. Thus for a morphism `f:A\rightarrow B`, the edge + `(A, B)` will be associated with `f`. This function also adds + to the list those edges which are formed by juxtaposition of + two edges already in the list. These new edges are not + associated with any morphism and are only added to assure that + the diagram can be decomposed into triangles. + """ + edges = {} + # Create edges for morphisms. + for morphism in morphisms: + DiagramGrid._add_edge_append( + edges, frozenset([morphism.domain, morphism.codomain]), morphism) + + # Create new edges by juxtaposing existing edges. + edges1 = dict(edges) + for w in edges1: + for v in edges1: + wv = DiagramGrid._juxtapose_edges(w, v) + if wv and wv not in edges: + edges[wv] = [] + + return edges + + @staticmethod + def _list_triangles(edges): + """ + Builds the set of triangles formed by the supplied edges. The + triangles are arbitrary and need not be commutative. A + triangle is a set that contains all three of its sides. + """ + triangles = set() + + for w in edges: + for v in edges: + wv = DiagramGrid._juxtapose_edges(w, v) + if wv and wv in edges: + triangles.add(frozenset([w, v, wv])) + + return triangles + + @staticmethod + def _drop_redundant_triangles(triangles, skeleton): + """ + Returns a list which contains only those triangles who have + morphisms associated with at least two edges. + """ + return [tri for tri in triangles + if len([e for e in tri if skeleton[e]]) >= 2] + + @staticmethod + def _morphism_length(morphism): + """ + Returns the length of a morphism. The length of a morphism is + the number of components it consists of. A non-composite + morphism is of length 1. + """ + if isinstance(morphism, CompositeMorphism): + return len(morphism.components) + else: + return 1 + + @staticmethod + def _compute_triangle_min_sizes(triangles, edges): + r""" + Returns a dictionary mapping triangles to their minimal sizes. + The minimal size of a triangle is the sum of maximal lengths + of morphisms associated to the sides of the triangle. The + length of a morphism is the number of components it consists + of. A non-composite morphism is of length 1. + + Sorting triangles by this metric attempts to address two + aspects of layout. For triangles with only simple morphisms + in the edge, this assures that triangles with all three edges + visible will get typeset after triangles with less visible + edges, which sometimes minimizes the necessity in diagonal + arrows. For triangles with composite morphisms in the edges, + this assures that objects connected with shorter morphisms + will be laid out first, resulting the visual proximity of + those objects which are connected by shorter morphisms. + """ + triangle_sizes = {} + for triangle in triangles: + size = 0 + for e in triangle: + morphisms = edges[e] + if morphisms: + size += max(DiagramGrid._morphism_length(m) + for m in morphisms) + triangle_sizes[triangle] = size + return triangle_sizes + + @staticmethod + def _triangle_objects(triangle): + """ + Given a triangle, returns the objects included in it. + """ + # A triangle is a frozenset of three two-element frozensets + # (the edges). This chains the three edges together and + # creates a frozenset from the iterator, thus producing a + # frozenset of objects of the triangle. + return frozenset(chain(*tuple(triangle))) + + @staticmethod + def _other_vertex(triangle, edge): + """ + Given a triangle and an edge of it, returns the vertex which + opposes the edge. + """ + # This gets the set of objects of the triangle and then + # subtracts the set of objects employed in ``edge`` to get the + # vertex opposite to ``edge``. + return list(DiagramGrid._triangle_objects(triangle) - set(edge))[0] + + @staticmethod + def _empty_point(pt, grid): + """ + Checks if the cell at coordinates ``pt`` is either empty or + out of the bounds of the grid. + """ + if (pt[0] < 0) or (pt[1] < 0) or \ + (pt[0] >= grid.height) or (pt[1] >= grid.width): + return True + return grid[pt] is None + + @staticmethod + def _put_object(coords, obj, grid, fringe): + """ + Places an object at the coordinate ``cords`` in ``grid``, + growing the grid and updating ``fringe``, if necessary. + Returns (0, 0) if no row or column has been prepended, (1, 0) + if a row was prepended, (0, 1) if a column was prepended and + (1, 1) if both a column and a row were prepended. + """ + (i, j) = coords + offset = (0, 0) + if i == -1: + grid.prepend_row() + i = 0 + offset = (1, 0) + for k in range(len(fringe)): + ((i1, j1), (i2, j2)) = fringe[k] + fringe[k] = ((i1 + 1, j1), (i2 + 1, j2)) + elif i == grid.height: + grid.append_row() + + if j == -1: + j = 0 + offset = (offset[0], 1) + grid.prepend_column() + for k in range(len(fringe)): + ((i1, j1), (i2, j2)) = fringe[k] + fringe[k] = ((i1, j1 + 1), (i2, j2 + 1)) + elif j == grid.width: + grid.append_column() + + grid[i, j] = obj + return offset + + @staticmethod + def _choose_target_cell(pt1, pt2, edge, obj, skeleton, grid): + """ + Given two points, ``pt1`` and ``pt2``, and the welding edge + ``edge``, chooses one of the two points to place the opposing + vertex ``obj`` of the triangle. If neither of this points + fits, returns ``None``. + """ + pt1_empty = DiagramGrid._empty_point(pt1, grid) + pt2_empty = DiagramGrid._empty_point(pt2, grid) + + if pt1_empty and pt2_empty: + # Both cells are empty. Of these two, choose that cell + # which will assure that a visible edge of the triangle + # will be drawn perpendicularly to the current welding + # edge. + + A = grid[edge[0]] + + if skeleton.get(frozenset([A, obj])): + return pt1 + else: + return pt2 + if pt1_empty: + return pt1 + elif pt2_empty: + return pt2 + else: + return None + + @staticmethod + def _find_triangle_to_weld(triangles, fringe, grid): + """ + Finds, if possible, a triangle and an edge in the ``fringe`` to + which the triangle could be attached. Returns the tuple + containing the triangle and the index of the corresponding + edge in the ``fringe``. + + This function relies on the fact that objects are unique in + the diagram. + """ + for triangle in triangles: + for (a, b) in fringe: + if frozenset([grid[a], grid[b]]) in triangle: + return (triangle, (a, b)) + return None + + @staticmethod + def _weld_triangle(tri, welding_edge, fringe, grid, skeleton): + """ + If possible, welds the triangle ``tri`` to ``fringe`` and + returns ``False``. If this method encounters a degenerate + situation in the fringe and corrects it such that a restart of + the search is required, it returns ``True`` (which means that + a restart in finding triangle weldings is required). + + A degenerate situation is a situation when an edge listed in + the fringe does not belong to the visual boundary of the + diagram. + """ + a, b = welding_edge + target_cell = None + + obj = DiagramGrid._other_vertex(tri, (grid[a], grid[b])) + + # We now have a triangle and an edge where it can be welded to + # the fringe. Decide where to place the other vertex of the + # triangle and check for degenerate situations en route. + + if (abs(a[0] - b[0]) == 1) and (abs(a[1] - b[1]) == 1): + # A diagonal edge. + target_cell = (a[0], b[1]) + if grid[target_cell]: + # That cell is already occupied. + target_cell = (b[0], a[1]) + + if grid[target_cell]: + # Degenerate situation, this edge is not + # on the actual fringe. Correct the + # fringe and go on. + fringe.remove((a, b)) + return True + elif a[0] == b[0]: + # A horizontal edge. We first attempt to build the + # triangle in the downward direction. + + down_left = a[0] + 1, a[1] + down_right = a[0] + 1, b[1] + + target_cell = DiagramGrid._choose_target_cell( + down_left, down_right, (a, b), obj, skeleton, grid) + + if not target_cell: + # No room below this edge. Check above. + up_left = a[0] - 1, a[1] + up_right = a[0] - 1, b[1] + + target_cell = DiagramGrid._choose_target_cell( + up_left, up_right, (a, b), obj, skeleton, grid) + + if not target_cell: + # This edge is not in the fringe, remove it + # and restart. + fringe.remove((a, b)) + return True + elif a[1] == b[1]: + # A vertical edge. We will attempt to place the other + # vertex of the triangle to the right of this edge. + right_up = a[0], a[1] + 1 + right_down = b[0], a[1] + 1 + + target_cell = DiagramGrid._choose_target_cell( + right_up, right_down, (a, b), obj, skeleton, grid) + + if not target_cell: + # No room to the left. See what's to the right. + left_up = a[0], a[1] - 1 + left_down = b[0], a[1] - 1 + + target_cell = DiagramGrid._choose_target_cell( + left_up, left_down, (a, b), obj, skeleton, grid) + + if not target_cell: + # This edge is not in the fringe, remove it + # and restart. + fringe.remove((a, b)) + return True + + # We now know where to place the other vertex of the + # triangle. + offset = DiagramGrid._put_object(target_cell, obj, grid, fringe) + + # Take care of the displacement of coordinates if a row or + # a column was prepended. + target_cell = (target_cell[0] + offset[0], + target_cell[1] + offset[1]) + a = (a[0] + offset[0], a[1] + offset[1]) + b = (b[0] + offset[0], b[1] + offset[1]) + + fringe.extend([(a, target_cell), (b, target_cell)]) + + # No restart is required. + return False + + @staticmethod + def _triangle_key(tri, triangle_sizes): + """ + Returns a key for the supplied triangle. It should be the + same independently of the hash randomisation. + """ + objects = sorted( + DiagramGrid._triangle_objects(tri), key=default_sort_key) + return (triangle_sizes[tri], default_sort_key(objects)) + + @staticmethod + def _pick_root_edge(tri, skeleton): + """ + For a given triangle always picks the same root edge. The + root edge is the edge that will be placed first on the grid. + """ + candidates = [sorted(e, key=default_sort_key) + for e in tri if skeleton[e]] + sorted_candidates = sorted(candidates, key=default_sort_key) + # Don't forget to assure the proper ordering of the vertices + # in this edge. + return tuple(sorted(sorted_candidates[0], key=default_sort_key)) + + @staticmethod + def _drop_irrelevant_triangles(triangles, placed_objects): + """ + Returns only those triangles whose set of objects is not + completely included in ``placed_objects``. + """ + return [tri for tri in triangles if not placed_objects.issuperset( + DiagramGrid._triangle_objects(tri))] + + @staticmethod + def _grow_pseudopod(triangles, fringe, grid, skeleton, placed_objects): + """ + Starting from an object in the existing structure on the ``grid``, + adds an edge to which a triangle from ``triangles`` could be + welded. If this method has found a way to do so, it returns + the object it has just added. + + This method should be applied when ``_weld_triangle`` cannot + find weldings any more. + """ + for i in range(grid.height): + for j in range(grid.width): + obj = grid[i, j] + if not obj: + continue + + # Here we need to choose a triangle which has only + # ``obj`` in common with the existing structure. The + # situations when this is not possible should be + # handled elsewhere. + + def good_triangle(tri): + objs = DiagramGrid._triangle_objects(tri) + return obj in objs and \ + placed_objects & (objs - {obj}) == set() + + tris = [tri for tri in triangles if good_triangle(tri)] + if not tris: + # This object is not interesting. + continue + + # Pick the "simplest" of the triangles which could be + # attached. Remember that the list of triangles is + # sorted according to their "simplicity" (see + # _compute_triangle_min_sizes for the metric). + # + # Note that ``tris`` are sequentially built from + # ``triangles``, so we don't have to worry about hash + # randomisation. + tri = tris[0] + + # We have found a triangle which could be attached to + # the existing structure by a vertex. + + candidates = sorted([e for e in tri if skeleton[e]], + key=lambda e: FiniteSet(*e).sort_key()) + edges = [e for e in candidates if obj in e] + + # Note that a meaningful edge (i.e., and edge that is + # associated with a morphism) containing ``obj`` + # always exists. That's because all triangles are + # guaranteed to have at least two meaningful edges. + # See _drop_redundant_triangles. + + # Get the object at the other end of the edge. + edge = edges[0] + other_obj = tuple(edge - frozenset([obj]))[0] + + # Now check for free directions. When checking for + # free directions, prefer the horizontal and vertical + # directions. + neighbours = [(i - 1, j), (i, j + 1), (i + 1, j), (i, j - 1), + (i - 1, j - 1), (i - 1, j + 1), (i + 1, j - 1), (i + 1, j + 1)] + + for pt in neighbours: + if DiagramGrid._empty_point(pt, grid): + # We have a found a place to grow the + # pseudopod into. + offset = DiagramGrid._put_object( + pt, other_obj, grid, fringe) + + i += offset[0] + j += offset[1] + pt = (pt[0] + offset[0], pt[1] + offset[1]) + fringe.append(((i, j), pt)) + + return other_obj + + # This diagram is actually cooler that I can handle. Fail cowardly. + return None + + @staticmethod + def _handle_groups(diagram, groups, merged_morphisms, hints): + """ + Given the slightly preprocessed morphisms of the diagram, + produces a grid laid out according to ``groups``. + + If a group has hints, it is laid out with those hints only, + without any influence from ``hints``. Otherwise, it is laid + out with ``hints``. + """ + def lay_out_group(group, local_hints): + """ + If ``group`` is a set of objects, uses a ``DiagramGrid`` + to lay it out and returns the grid. Otherwise returns the + object (i.e., ``group``). If ``local_hints`` is not + empty, it is supplied to ``DiagramGrid`` as the dictionary + of hints. Otherwise, the ``hints`` argument of + ``_handle_groups`` is used. + """ + if isinstance(group, FiniteSet): + # Set up the corresponding object-to-group + # mappings. + for obj in group: + obj_groups[obj] = group + + # Lay out the current group. + if local_hints: + groups_grids[group] = DiagramGrid( + diagram.subdiagram_from_objects(group), **local_hints) + else: + groups_grids[group] = DiagramGrid( + diagram.subdiagram_from_objects(group), **hints) + else: + obj_groups[group] = group + + def group_to_finiteset(group): + """ + Converts ``group`` to a :class:``FiniteSet`` if it is an + iterable. + """ + if iterable(group): + return FiniteSet(*group) + else: + return group + + obj_groups = {} + groups_grids = {} + + # We would like to support various containers to represent + # groups. To achieve that, before laying each group out, it + # should be converted to a FiniteSet, because that is what the + # following code expects. + + if isinstance(groups, (dict, Dict)): + finiteset_groups = {} + for group, local_hints in groups.items(): + finiteset_group = group_to_finiteset(group) + finiteset_groups[finiteset_group] = local_hints + lay_out_group(group, local_hints) + groups = finiteset_groups + else: + finiteset_groups = [] + for group in groups: + finiteset_group = group_to_finiteset(group) + finiteset_groups.append(finiteset_group) + lay_out_group(finiteset_group, None) + groups = finiteset_groups + + new_morphisms = [] + for morphism in merged_morphisms: + dom = obj_groups[morphism.domain] + cod = obj_groups[morphism.codomain] + # Note that we are not really interested in morphisms + # which do not employ two different groups, because + # these do not influence the layout. + if dom != cod: + # These are essentially unnamed morphisms; they are + # not going to mess in the final layout. By giving + # them the same names, we avoid unnecessary + # duplicates. + new_morphisms.append(NamedMorphism(dom, cod, "dummy")) + + # Lay out the new diagram. Since these are dummy morphisms, + # properties and conclusions are irrelevant. + top_grid = DiagramGrid(Diagram(new_morphisms)) + + # We now have to substitute the groups with the corresponding + # grids, laid out at the beginning of this function. Compute + # the size of each row and column in the grid, so that all + # nested grids fit. + + def group_size(group): + """ + For the supplied group (or object, eventually), returns + the size of the cell that will hold this group (object). + """ + if group in groups_grids: + grid = groups_grids[group] + return (grid.height, grid.width) + else: + return (1, 1) + + row_heights = [max(group_size(top_grid[i, j])[0] + for j in range(top_grid.width)) + for i in range(top_grid.height)] + + column_widths = [max(group_size(top_grid[i, j])[1] + for i in range(top_grid.height)) + for j in range(top_grid.width)] + + grid = _GrowableGrid(sum(column_widths), sum(row_heights)) + + real_row = 0 + real_column = 0 + for logical_row in range(top_grid.height): + for logical_column in range(top_grid.width): + obj = top_grid[logical_row, logical_column] + + if obj in groups_grids: + # This is a group. Copy the corresponding grid in + # place. + local_grid = groups_grids[obj] + for i in range(local_grid.height): + for j in range(local_grid.width): + grid[real_row + i, + real_column + j] = local_grid[i, j] + else: + # This is an object. Just put it there. + grid[real_row, real_column] = obj + + real_column += column_widths[logical_column] + real_column = 0 + real_row += row_heights[logical_row] + + return grid + + @staticmethod + def _generic_layout(diagram, merged_morphisms): + """ + Produces the generic layout for the supplied diagram. + """ + all_objects = set(diagram.objects) + if len(all_objects) == 1: + # There only one object in the diagram, just put in on 1x1 + # grid. + grid = _GrowableGrid(1, 1) + grid[0, 0] = tuple(all_objects)[0] + return grid + + skeleton = DiagramGrid._build_skeleton(merged_morphisms) + + grid = _GrowableGrid(2, 1) + + if len(skeleton) == 1: + # This diagram contains only one morphism. Draw it + # horizontally. + objects = sorted(all_objects, key=default_sort_key) + grid[0, 0] = objects[0] + grid[0, 1] = objects[1] + + return grid + + triangles = DiagramGrid._list_triangles(skeleton) + triangles = DiagramGrid._drop_redundant_triangles(triangles, skeleton) + triangle_sizes = DiagramGrid._compute_triangle_min_sizes( + triangles, skeleton) + + triangles = sorted(triangles, key=lambda tri: + DiagramGrid._triangle_key(tri, triangle_sizes)) + + # Place the first edge on the grid. + root_edge = DiagramGrid._pick_root_edge(triangles[0], skeleton) + grid[0, 0], grid[0, 1] = root_edge + fringe = [((0, 0), (0, 1))] + + # Record which objects we now have on the grid. + placed_objects = set(root_edge) + + while placed_objects != all_objects: + welding = DiagramGrid._find_triangle_to_weld( + triangles, fringe, grid) + + if welding: + (triangle, welding_edge) = welding + + restart_required = DiagramGrid._weld_triangle( + triangle, welding_edge, fringe, grid, skeleton) + if restart_required: + continue + + placed_objects.update( + DiagramGrid._triangle_objects(triangle)) + else: + # No more weldings found. Try to attach triangles by + # vertices. + new_obj = DiagramGrid._grow_pseudopod( + triangles, fringe, grid, skeleton, placed_objects) + + if not new_obj: + # No more triangles can be attached, not even by + # the edge. We will set up a new diagram out of + # what has been left, laid it out independently, + # and then attach it to this one. + + remaining_objects = all_objects - placed_objects + + remaining_diagram = diagram.subdiagram_from_objects( + FiniteSet(*remaining_objects)) + remaining_grid = DiagramGrid(remaining_diagram) + + # Now, let's glue ``remaining_grid`` to ``grid``. + final_width = grid.width + remaining_grid.width + final_height = max(grid.height, remaining_grid.height) + final_grid = _GrowableGrid(final_width, final_height) + + for i in range(grid.width): + for j in range(grid.height): + final_grid[i, j] = grid[i, j] + + start_j = grid.width + for i in range(remaining_grid.height): + for j in range(remaining_grid.width): + final_grid[i, start_j + j] = remaining_grid[i, j] + + return final_grid + + placed_objects.add(new_obj) + + triangles = DiagramGrid._drop_irrelevant_triangles( + triangles, placed_objects) + + return grid + + @staticmethod + def _get_undirected_graph(objects, merged_morphisms): + """ + Given the objects and the relevant morphisms of a diagram, + returns the adjacency lists of the underlying undirected + graph. + """ + adjlists = {obj: [] for obj in objects} + + for morphism in merged_morphisms: + adjlists[morphism.domain].append(morphism.codomain) + adjlists[morphism.codomain].append(morphism.domain) + + # Assure that the objects in the adjacency list are always in + # the same order. + for obj in adjlists.keys(): + adjlists[obj].sort(key=default_sort_key) + + return adjlists + + @staticmethod + def _sequential_layout(diagram, merged_morphisms): + r""" + Lays out the diagram in "sequential" layout. This method + will attempt to produce a result as close to a line as + possible. For linear diagrams, the result will actually be a + line. + """ + objects = diagram.objects + sorted_objects = sorted(objects, key=default_sort_key) + + # Set up the adjacency lists of the underlying undirected + # graph of ``merged_morphisms``. + adjlists = DiagramGrid._get_undirected_graph(objects, merged_morphisms) + + root = min(sorted_objects, key=lambda x: len(adjlists[x])) + grid = _GrowableGrid(1, 1) + grid[0, 0] = root + + placed_objects = {root} + + def place_objects(pt, placed_objects): + """ + Does depth-first search in the underlying graph of the + diagram and places the objects en route. + """ + # We will start placing new objects from here. + new_pt = (pt[0], pt[1] + 1) + + for adjacent_obj in adjlists[grid[pt]]: + if adjacent_obj in placed_objects: + # This object has already been placed. + continue + + DiagramGrid._put_object(new_pt, adjacent_obj, grid, []) + placed_objects.add(adjacent_obj) + placed_objects.update(place_objects(new_pt, placed_objects)) + + new_pt = (new_pt[0] + 1, new_pt[1]) + + return placed_objects + + place_objects((0, 0), placed_objects) + + return grid + + @staticmethod + def _drop_inessential_morphisms(merged_morphisms): + r""" + Removes those morphisms which should appear in the diagram, + but which have no relevance to object layout. + + Currently this removes "loop" morphisms: the non-identity + morphisms with the same domains and codomains. + """ + morphisms = [m for m in merged_morphisms if m.domain != m.codomain] + return morphisms + + @staticmethod + def _get_connected_components(objects, merged_morphisms): + """ + Given a container of morphisms, returns a list of connected + components formed by these morphisms. A connected component + is represented by a diagram consisting of the corresponding + morphisms. + """ + component_index = {} + for o in objects: + component_index[o] = None + + # Get the underlying undirected graph of the diagram. + adjlist = DiagramGrid._get_undirected_graph(objects, merged_morphisms) + + def traverse_component(object, current_index): + """ + Does a depth-first search traversal of the component + containing ``object``. + """ + component_index[object] = current_index + for o in adjlist[object]: + if component_index[o] is None: + traverse_component(o, current_index) + + # Traverse all components. + current_index = 0 + for o in adjlist: + if component_index[o] is None: + traverse_component(o, current_index) + current_index += 1 + + # List the objects of the components. + component_objects = [[] for i in range(current_index)] + for o, idx in component_index.items(): + component_objects[idx].append(o) + + # Finally, list the morphisms belonging to each component. + # + # Note: If some objects are isolated, they will not get any + # morphisms at this stage, and since the layout algorithm + # relies, we are essentially going to lose this object. + # Therefore, check if there are isolated objects and, for each + # of them, provide the trivial identity morphism. It will get + # discarded later, but the object will be there. + + component_morphisms = [] + for component in component_objects: + current_morphisms = {} + for m in merged_morphisms: + if (m.domain in component) and (m.codomain in component): + current_morphisms[m] = merged_morphisms[m] + + if len(component) == 1: + # Let's add an identity morphism, for the sake of + # surely having morphisms in this component. + current_morphisms[IdentityMorphism(component[0])] = FiniteSet() + + component_morphisms.append(Diagram(current_morphisms)) + + return component_morphisms + + def __init__(self, diagram, groups=None, **hints): + premises = DiagramGrid._simplify_morphisms(diagram.premises) + conclusions = DiagramGrid._simplify_morphisms(diagram.conclusions) + all_merged_morphisms = DiagramGrid._merge_premises_conclusions( + premises, conclusions) + merged_morphisms = DiagramGrid._drop_inessential_morphisms( + all_merged_morphisms) + + # Store the merged morphisms for later use. + self._morphisms = all_merged_morphisms + + components = DiagramGrid._get_connected_components( + diagram.objects, all_merged_morphisms) + + if groups and (groups != diagram.objects): + # Lay out the diagram according to the groups. + self._grid = DiagramGrid._handle_groups( + diagram, groups, merged_morphisms, hints) + elif len(components) > 1: + # Note that we check for connectedness _before_ checking + # the layout hints because the layout strategies don't + # know how to deal with disconnected diagrams. + + # The diagram is disconnected. Lay out the components + # independently. + grids = [] + + # Sort the components to eventually get the grids arranged + # in a fixed, hash-independent order. + components = sorted(components, key=default_sort_key) + + for component in components: + grid = DiagramGrid(component, **hints) + grids.append(grid) + + # Throw the grids together, in a line. + total_width = sum(g.width for g in grids) + total_height = max(g.height for g in grids) + + grid = _GrowableGrid(total_width, total_height) + start_j = 0 + for g in grids: + for i in range(g.height): + for j in range(g.width): + grid[i, start_j + j] = g[i, j] + + start_j += g.width + + self._grid = grid + elif "layout" in hints: + if hints["layout"] == "sequential": + self._grid = DiagramGrid._sequential_layout( + diagram, merged_morphisms) + else: + self._grid = DiagramGrid._generic_layout(diagram, merged_morphisms) + + if hints.get("transpose"): + # Transpose the resulting grid. + grid = _GrowableGrid(self._grid.height, self._grid.width) + for i in range(self._grid.height): + for j in range(self._grid.width): + grid[j, i] = self._grid[i, j] + self._grid = grid + + @property + def width(self): + """ + Returns the number of columns in this diagram layout. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import Diagram, DiagramGrid + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g]) + >>> grid = DiagramGrid(diagram) + >>> grid.width + 2 + + """ + return self._grid.width + + @property + def height(self): + """ + Returns the number of rows in this diagram layout. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import Diagram, DiagramGrid + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g]) + >>> grid = DiagramGrid(diagram) + >>> grid.height + 2 + + """ + return self._grid.height + + def __getitem__(self, i_j): + """ + Returns the object placed in the row ``i`` and column ``j``. + The indices are 0-based. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import Diagram, DiagramGrid + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g]) + >>> grid = DiagramGrid(diagram) + >>> (grid[0, 0], grid[0, 1]) + (Object("A"), Object("B")) + >>> (grid[1, 0], grid[1, 1]) + (None, Object("C")) + + """ + i, j = i_j + return self._grid[i, j] + + @property + def morphisms(self): + """ + Returns those morphisms (and their properties) which are + sufficiently meaningful to be drawn. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import Diagram, DiagramGrid + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g]) + >>> grid = DiagramGrid(diagram) + >>> grid.morphisms + {NamedMorphism(Object("A"), Object("B"), "f"): EmptySet, + NamedMorphism(Object("B"), Object("C"), "g"): EmptySet} + + """ + return self._morphisms + + def __str__(self): + """ + Produces a string representation of this class. + + This method returns a string representation of the underlying + list of lists of objects. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism + >>> from sympy.categories import Diagram, DiagramGrid + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g]) + >>> grid = DiagramGrid(diagram) + >>> print(grid) + [[Object("A"), Object("B")], + [None, Object("C")]] + + """ + return repr(self._grid._array) + + +class ArrowStringDescription: + r""" + Stores the information necessary for producing an Xy-pic + description of an arrow. + + The principal goal of this class is to abstract away the string + representation of an arrow and to also provide the functionality + to produce the actual Xy-pic string. + + ``unit`` sets the unit which will be used to specify the amount of + curving and other distances. ``horizontal_direction`` should be a + string of ``"r"`` or ``"l"`` specifying the horizontal offset of the + target cell of the arrow relatively to the current one. + ``vertical_direction`` should specify the vertical offset using a + series of either ``"d"`` or ``"u"``. ``label_position`` should be + either ``"^"``, ``"_"``, or ``"|"`` to specify that the label should + be positioned above the arrow, below the arrow or just over the arrow, + in a break. Note that the notions "above" and "below" are relative + to arrow direction. ``label`` stores the morphism label. + + This works as follows (disregard the yet unexplained arguments): + + >>> from sympy.categories.diagram_drawing import ArrowStringDescription + >>> astr = ArrowStringDescription( + ... unit="mm", curving=None, curving_amount=None, + ... looping_start=None, looping_end=None, horizontal_direction="d", + ... vertical_direction="r", label_position="_", label="f") + >>> print(str(astr)) + \ar[dr]_{f} + + ``curving`` should be one of ``"^"``, ``"_"`` to specify in which + direction the arrow is going to curve. ``curving_amount`` is a number + describing how many ``unit``'s the morphism is going to curve: + + >>> astr = ArrowStringDescription( + ... unit="mm", curving="^", curving_amount=12, + ... looping_start=None, looping_end=None, horizontal_direction="d", + ... vertical_direction="r", label_position="_", label="f") + >>> print(str(astr)) + \ar@/^12mm/[dr]_{f} + + ``looping_start`` and ``looping_end`` are currently only used for + loop morphisms, those which have the same domain and codomain. + These two attributes should store a valid Xy-pic direction and + specify, correspondingly, the direction the arrow gets out into + and the direction the arrow gets back from: + + >>> astr = ArrowStringDescription( + ... unit="mm", curving=None, curving_amount=None, + ... looping_start="u", looping_end="l", horizontal_direction="", + ... vertical_direction="", label_position="_", label="f") + >>> print(str(astr)) + \ar@(u,l)[]_{f} + + ``label_displacement`` controls how far the arrow label is from + the ends of the arrow. For example, to position the arrow label + near the arrow head, use ">": + + >>> astr = ArrowStringDescription( + ... unit="mm", curving="^", curving_amount=12, + ... looping_start=None, looping_end=None, horizontal_direction="d", + ... vertical_direction="r", label_position="_", label="f") + >>> astr.label_displacement = ">" + >>> print(str(astr)) + \ar@/^12mm/[dr]_>{f} + + Finally, ``arrow_style`` is used to specify the arrow style. To + get a dashed arrow, for example, use "{-->}" as arrow style: + + >>> astr = ArrowStringDescription( + ... unit="mm", curving="^", curving_amount=12, + ... looping_start=None, looping_end=None, horizontal_direction="d", + ... vertical_direction="r", label_position="_", label="f") + >>> astr.arrow_style = "{-->}" + >>> print(str(astr)) + \ar@/^12mm/@{-->}[dr]_{f} + + Notes + ===== + + Instances of :class:`ArrowStringDescription` will be constructed + by :class:`XypicDiagramDrawer` and provided for further use in + formatters. The user is not expected to construct instances of + :class:`ArrowStringDescription` themselves. + + To be able to properly utilise this class, the reader is encouraged + to checkout the Xy-pic user guide, available at [Xypic]. + + See Also + ======== + + XypicDiagramDrawer + + References + ========== + + .. [Xypic] https://xy-pic.sourceforge.net/ + """ + def __init__(self, unit, curving, curving_amount, looping_start, + looping_end, horizontal_direction, vertical_direction, + label_position, label): + self.unit = unit + self.curving = curving + self.curving_amount = curving_amount + self.looping_start = looping_start + self.looping_end = looping_end + self.horizontal_direction = horizontal_direction + self.vertical_direction = vertical_direction + self.label_position = label_position + self.label = label + + self.label_displacement = "" + self.arrow_style = "" + + # This flag shows that the position of the label of this + # morphism was set while typesetting a curved morphism and + # should not be modified later. + self.forced_label_position = False + + def __str__(self): + if self.curving: + curving_str = "@/%s%d%s/" % (self.curving, self.curving_amount, + self.unit) + else: + curving_str = "" + + if self.looping_start and self.looping_end: + looping_str = "@(%s,%s)" % (self.looping_start, self.looping_end) + else: + looping_str = "" + + if self.arrow_style: + + style_str = "@" + self.arrow_style + else: + style_str = "" + + return "\\ar%s%s%s[%s%s]%s%s{%s}" % \ + (curving_str, looping_str, style_str, self.horizontal_direction, + self.vertical_direction, self.label_position, + self.label_displacement, self.label) + + +class XypicDiagramDrawer: + r""" + Given a :class:`~.Diagram` and the corresponding + :class:`DiagramGrid`, produces the Xy-pic representation of the + diagram. + + The most important method in this class is ``draw``. Consider the + following triangle diagram: + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy.categories import DiagramGrid, XypicDiagramDrawer + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g], {g * f: "unique"}) + + To draw this diagram, its objects need to be laid out with a + :class:`DiagramGrid`:: + + >>> grid = DiagramGrid(diagram) + + Finally, the drawing: + + >>> drawer = XypicDiagramDrawer() + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\ + C & + } + + For further details see the docstring of this method. + + To control the appearance of the arrows, formatters are used. The + dictionary ``arrow_formatters`` maps morphisms to formatter + functions. A formatter is accepts an + :class:`ArrowStringDescription` and is allowed to modify any of + the arrow properties exposed thereby. For example, to have all + morphisms with the property ``unique`` appear as dashed arrows, + and to have their names prepended with `\exists !`, the following + should be done: + + >>> def formatter(astr): + ... astr.label = r"\exists !" + astr.label + ... astr.arrow_style = "{-->}" + >>> drawer.arrow_formatters["unique"] = formatter + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar@{-->}[d]_{\exists !g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\ + C & + } + + To modify the appearance of all arrows in the diagram, set + ``default_arrow_formatter``. For example, to place all morphism + labels a little bit farther from the arrow head so that they look + more centred, do as follows: + + >>> def default_formatter(astr): + ... astr.label_displacement = "(0.45)" + >>> drawer.default_arrow_formatter = default_formatter + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar@{-->}[d]_(0.45){\exists !g\circ f} \ar[r]^(0.45){f} & B \ar[ld]^(0.45){g} \\ + C & + } + + In some diagrams some morphisms are drawn as curved arrows. + Consider the following diagram: + + >>> D = Object("D") + >>> E = Object("E") + >>> h = NamedMorphism(D, A, "h") + >>> k = NamedMorphism(D, B, "k") + >>> diagram = Diagram([f, g, h, k]) + >>> grid = DiagramGrid(diagram) + >>> drawer = XypicDiagramDrawer() + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar[r]_{f} & B \ar[d]^{g} & D \ar[l]^{k} \ar@/_3mm/[ll]_{h} \\ + & C & + } + + To control how far the morphisms are curved by default, one can + use the ``unit`` and ``default_curving_amount`` attributes: + + >>> drawer.unit = "cm" + >>> drawer.default_curving_amount = 1 + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar[r]_{f} & B \ar[d]^{g} & D \ar[l]^{k} \ar@/_1cm/[ll]_{h} \\ + & C & + } + + In some diagrams, there are multiple curved morphisms between the + same two objects. To control by how much the curving changes + between two such successive morphisms, use + ``default_curving_step``: + + >>> drawer.default_curving_step = 1 + >>> h1 = NamedMorphism(A, D, "h1") + >>> diagram = Diagram([f, g, h, k, h1]) + >>> grid = DiagramGrid(diagram) + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar[r]_{f} \ar@/^1cm/[rr]^{h_{1}} & B \ar[d]^{g} & D \ar[l]^{k} \ar@/_2cm/[ll]_{h} \\ + & C & + } + + The default value of ``default_curving_step`` is 4 units. + + See Also + ======== + + draw, ArrowStringDescription + """ + def __init__(self): + self.unit = "mm" + self.default_curving_amount = 3 + self.default_curving_step = 4 + + # This dictionary maps properties to the corresponding arrow + # formatters. + self.arrow_formatters = {} + + # This is the default arrow formatter which will be applied to + # each arrow independently of its properties. + self.default_arrow_formatter = None + + @staticmethod + def _process_loop_morphism(i, j, grid, morphisms_str_info, object_coords): + """ + Produces the information required for constructing the string + representation of a loop morphism. This function is invoked + from ``_process_morphism``. + + See Also + ======== + + _process_morphism + """ + curving = "" + label_pos = "^" + looping_start = "" + looping_end = "" + + # This is a loop morphism. Count how many morphisms stick + # in each of the four quadrants. Note that straight + # vertical and horizontal morphisms count in two quadrants + # at the same time (i.e., a morphism going up counts both + # in the first and the second quadrants). + + # The usual numbering (counterclockwise) of quadrants + # applies. + quadrant = [0, 0, 0, 0] + + obj = grid[i, j] + + for m, m_str_info in morphisms_str_info.items(): + if (m.domain == obj) and (m.codomain == obj): + # That's another loop morphism. Check how it + # loops and mark the corresponding quadrants as + # busy. + (l_s, l_e) = (m_str_info.looping_start, m_str_info.looping_end) + + if (l_s, l_e) == ("r", "u"): + quadrant[0] += 1 + elif (l_s, l_e) == ("u", "l"): + quadrant[1] += 1 + elif (l_s, l_e) == ("l", "d"): + quadrant[2] += 1 + elif (l_s, l_e) == ("d", "r"): + quadrant[3] += 1 + + continue + if m.domain == obj: + (end_i, end_j) = object_coords[m.codomain] + goes_out = True + elif m.codomain == obj: + (end_i, end_j) = object_coords[m.domain] + goes_out = False + else: + continue + + d_i = end_i - i + d_j = end_j - j + m_curving = m_str_info.curving + + if (d_i != 0) and (d_j != 0): + # This is really a diagonal morphism. Detect the + # quadrant. + if (d_i > 0) and (d_j > 0): + quadrant[0] += 1 + elif (d_i > 0) and (d_j < 0): + quadrant[1] += 1 + elif (d_i < 0) and (d_j < 0): + quadrant[2] += 1 + elif (d_i < 0) and (d_j > 0): + quadrant[3] += 1 + elif d_i == 0: + # Knowing where the other end of the morphism is + # and which way it goes, we now have to decide + # which quadrant is now the upper one and which is + # the lower one. + if d_j > 0: + if goes_out: + upper_quadrant = 0 + lower_quadrant = 3 + else: + upper_quadrant = 3 + lower_quadrant = 0 + else: + if goes_out: + upper_quadrant = 2 + lower_quadrant = 1 + else: + upper_quadrant = 1 + lower_quadrant = 2 + + if m_curving: + if m_curving == "^": + quadrant[upper_quadrant] += 1 + elif m_curving == "_": + quadrant[lower_quadrant] += 1 + else: + # This morphism counts in both upper and lower + # quadrants. + quadrant[upper_quadrant] += 1 + quadrant[lower_quadrant] += 1 + elif d_j == 0: + # Knowing where the other end of the morphism is + # and which way it goes, we now have to decide + # which quadrant is now the left one and which is + # the right one. + if d_i < 0: + if goes_out: + left_quadrant = 1 + right_quadrant = 0 + else: + left_quadrant = 0 + right_quadrant = 1 + else: + if goes_out: + left_quadrant = 3 + right_quadrant = 2 + else: + left_quadrant = 2 + right_quadrant = 3 + + if m_curving: + if m_curving == "^": + quadrant[left_quadrant] += 1 + elif m_curving == "_": + quadrant[right_quadrant] += 1 + else: + # This morphism counts in both upper and lower + # quadrants. + quadrant[left_quadrant] += 1 + quadrant[right_quadrant] += 1 + + # Pick the freest quadrant to curve our morphism into. + freest_quadrant = 0 + for i in range(4): + if quadrant[i] < quadrant[freest_quadrant]: + freest_quadrant = i + + # Now set up proper looping. + (looping_start, looping_end) = [("r", "u"), ("u", "l"), ("l", "d"), + ("d", "r")][freest_quadrant] + + return (curving, label_pos, looping_start, looping_end) + + @staticmethod + def _process_horizontal_morphism(i, j, target_j, grid, morphisms_str_info, + object_coords): + """ + Produces the information required for constructing the string + representation of a horizontal morphism. This function is + invoked from ``_process_morphism``. + + See Also + ======== + + _process_morphism + """ + # The arrow is horizontal. Check if it goes from left to + # right (``backwards == False``) or from right to left + # (``backwards == True``). + backwards = False + start = j + end = target_j + if end < start: + (start, end) = (end, start) + backwards = True + + # Let's see which objects are there between ``start`` and + # ``end``, and then count how many morphisms stick out + # upwards, and how many stick out downwards. + # + # For example, consider the situation: + # + # B1 C1 + # | | + # A--B--C--D + # | + # B2 + # + # Between the objects `A` and `D` there are two objects: + # `B` and `C`. Further, there are two morphisms which + # stick out upward (the ones between `B1` and `B` and + # between `C` and `C1`) and one morphism which sticks out + # downward (the one between `B and `B2`). + # + # We need this information to decide how to curve the + # arrow between `A` and `D`. First of all, since there + # are two objects between `A` and `D``, we must curve the + # arrow. Then, we will have it curve downward, because + # there is more space (less morphisms stick out downward + # than upward). + up = [] + down = [] + straight_horizontal = [] + for k in range(start + 1, end): + obj = grid[i, k] + if not obj: + continue + + for m in morphisms_str_info: + if m.domain == obj: + (end_i, end_j) = object_coords[m.codomain] + elif m.codomain == obj: + (end_i, end_j) = object_coords[m.domain] + else: + continue + + if end_i > i: + down.append(m) + elif end_i < i: + up.append(m) + elif not morphisms_str_info[m].curving: + # This is a straight horizontal morphism, + # because it has no curving. + straight_horizontal.append(m) + + if len(up) < len(down): + # More morphisms stick out downward than upward, let's + # curve the morphism up. + if backwards: + curving = "_" + label_pos = "_" + else: + curving = "^" + label_pos = "^" + + # Assure that the straight horizontal morphisms have + # their labels on the lower side of the arrow. + for m in straight_horizontal: + (i1, j1) = object_coords[m.domain] + (i2, j2) = object_coords[m.codomain] + + m_str_info = morphisms_str_info[m] + if j1 < j2: + m_str_info.label_position = "_" + else: + m_str_info.label_position = "^" + + # Don't allow any further modifications of the + # position of this label. + m_str_info.forced_label_position = True + else: + # More morphisms stick out downward than upward, let's + # curve the morphism up. + if backwards: + curving = "^" + label_pos = "^" + else: + curving = "_" + label_pos = "_" + + # Assure that the straight horizontal morphisms have + # their labels on the upper side of the arrow. + for m in straight_horizontal: + (i1, j1) = object_coords[m.domain] + (i2, j2) = object_coords[m.codomain] + + m_str_info = morphisms_str_info[m] + if j1 < j2: + m_str_info.label_position = "^" + else: + m_str_info.label_position = "_" + + # Don't allow any further modifications of the + # position of this label. + m_str_info.forced_label_position = True + + return (curving, label_pos) + + @staticmethod + def _process_vertical_morphism(i, j, target_i, grid, morphisms_str_info, + object_coords): + """ + Produces the information required for constructing the string + representation of a vertical morphism. This function is + invoked from ``_process_morphism``. + + See Also + ======== + + _process_morphism + """ + # This arrow is vertical. Check if it goes from top to + # bottom (``backwards == False``) or from bottom to top + # (``backwards == True``). + backwards = False + start = i + end = target_i + if end < start: + (start, end) = (end, start) + backwards = True + + # Let's see which objects are there between ``start`` and + # ``end``, and then count how many morphisms stick out to + # the left, and how many stick out to the right. + # + # See the corresponding comment in the previous branch of + # this if-statement for more details. + left = [] + right = [] + straight_vertical = [] + for k in range(start + 1, end): + obj = grid[k, j] + if not obj: + continue + + for m in morphisms_str_info: + if m.domain == obj: + (end_i, end_j) = object_coords[m.codomain] + elif m.codomain == obj: + (end_i, end_j) = object_coords[m.domain] + else: + continue + + if end_j > j: + right.append(m) + elif end_j < j: + left.append(m) + elif not morphisms_str_info[m].curving: + # This is a straight vertical morphism, + # because it has no curving. + straight_vertical.append(m) + + if len(left) < len(right): + # More morphisms stick out to the left than to the + # right, let's curve the morphism to the right. + if backwards: + curving = "^" + label_pos = "^" + else: + curving = "_" + label_pos = "_" + + # Assure that the straight vertical morphisms have + # their labels on the left side of the arrow. + for m in straight_vertical: + (i1, j1) = object_coords[m.domain] + (i2, j2) = object_coords[m.codomain] + + m_str_info = morphisms_str_info[m] + if i1 < i2: + m_str_info.label_position = "^" + else: + m_str_info.label_position = "_" + + # Don't allow any further modifications of the + # position of this label. + m_str_info.forced_label_position = True + else: + # More morphisms stick out to the right than to the + # left, let's curve the morphism to the left. + if backwards: + curving = "_" + label_pos = "_" + else: + curving = "^" + label_pos = "^" + + # Assure that the straight vertical morphisms have + # their labels on the right side of the arrow. + for m in straight_vertical: + (i1, j1) = object_coords[m.domain] + (i2, j2) = object_coords[m.codomain] + + m_str_info = morphisms_str_info[m] + if i1 < i2: + m_str_info.label_position = "_" + else: + m_str_info.label_position = "^" + + # Don't allow any further modifications of the + # position of this label. + m_str_info.forced_label_position = True + + return (curving, label_pos) + + def _process_morphism(self, diagram, grid, morphism, object_coords, + morphisms, morphisms_str_info): + """ + Given the required information, produces the string + representation of ``morphism``. + """ + def repeat_string_cond(times, str_gt, str_lt): + """ + If ``times > 0``, repeats ``str_gt`` ``times`` times. + Otherwise, repeats ``str_lt`` ``-times`` times. + """ + if times > 0: + return str_gt * times + else: + return str_lt * (-times) + + def count_morphisms_undirected(A, B): + """ + Counts how many processed morphisms there are between the + two supplied objects. + """ + return len([m for m in morphisms_str_info + if {m.domain, m.codomain} == {A, B}]) + + def count_morphisms_filtered(dom, cod, curving): + """ + Counts the processed morphisms which go out of ``dom`` + into ``cod`` with curving ``curving``. + """ + return len([m for m, m_str_info in morphisms_str_info.items() + if (m.domain, m.codomain) == (dom, cod) and + (m_str_info.curving == curving)]) + + (i, j) = object_coords[morphism.domain] + (target_i, target_j) = object_coords[morphism.codomain] + + # We now need to determine the direction of + # the arrow. + delta_i = target_i - i + delta_j = target_j - j + vertical_direction = repeat_string_cond(delta_i, + "d", "u") + horizontal_direction = repeat_string_cond(delta_j, + "r", "l") + + curving = "" + label_pos = "^" + looping_start = "" + looping_end = "" + + if (delta_i == 0) and (delta_j == 0): + # This is a loop morphism. + (curving, label_pos, looping_start, + looping_end) = XypicDiagramDrawer._process_loop_morphism( + i, j, grid, morphisms_str_info, object_coords) + elif (delta_i == 0) and (abs(j - target_j) > 1): + # This is a horizontal morphism. + (curving, label_pos) = XypicDiagramDrawer._process_horizontal_morphism( + i, j, target_j, grid, morphisms_str_info, object_coords) + elif (delta_j == 0) and (abs(i - target_i) > 1): + # This is a vertical morphism. + (curving, label_pos) = XypicDiagramDrawer._process_vertical_morphism( + i, j, target_i, grid, morphisms_str_info, object_coords) + + count = count_morphisms_undirected(morphism.domain, morphism.codomain) + curving_amount = "" + if curving: + # This morphisms should be curved anyway. + curving_amount = self.default_curving_amount + count * \ + self.default_curving_step + elif count: + # There are no objects between the domain and codomain of + # the current morphism, but this is not there already are + # some morphisms with the same domain and codomain, so we + # have to curve this one. + curving = "^" + filtered_morphisms = count_morphisms_filtered( + morphism.domain, morphism.codomain, curving) + curving_amount = self.default_curving_amount + \ + filtered_morphisms * \ + self.default_curving_step + + # Let's now get the name of the morphism. + morphism_name = "" + if isinstance(morphism, IdentityMorphism): + morphism_name = "id_{%s}" + latex(grid[i, j]) + elif isinstance(morphism, CompositeMorphism): + component_names = [latex(Symbol(component.name)) for + component in morphism.components] + component_names.reverse() + morphism_name = "\\circ ".join(component_names) + elif isinstance(morphism, NamedMorphism): + morphism_name = latex(Symbol(morphism.name)) + + return ArrowStringDescription( + self.unit, curving, curving_amount, looping_start, + looping_end, horizontal_direction, vertical_direction, + label_pos, morphism_name) + + @staticmethod + def _check_free_space_horizontal(dom_i, dom_j, cod_j, grid): + """ + For a horizontal morphism, checks whether there is free space + (i.e., space not occupied by any objects) above the morphism + or below it. + """ + if dom_j < cod_j: + (start, end) = (dom_j, cod_j) + backwards = False + else: + (start, end) = (cod_j, dom_j) + backwards = True + + # Check for free space above. + if dom_i == 0: + free_up = True + else: + free_up = all(grid[dom_i - 1, j] for j in + range(start, end + 1)) + + # Check for free space below. + if dom_i == grid.height - 1: + free_down = True + else: + free_down = not any(grid[dom_i + 1, j] for j in + range(start, end + 1)) + + return (free_up, free_down, backwards) + + @staticmethod + def _check_free_space_vertical(dom_i, cod_i, dom_j, grid): + """ + For a vertical morphism, checks whether there is free space + (i.e., space not occupied by any objects) to the left of the + morphism or to the right of it. + """ + if dom_i < cod_i: + (start, end) = (dom_i, cod_i) + backwards = False + else: + (start, end) = (cod_i, dom_i) + backwards = True + + # Check if there's space to the left. + if dom_j == 0: + free_left = True + else: + free_left = not any(grid[i, dom_j - 1] for i in + range(start, end + 1)) + + if dom_j == grid.width - 1: + free_right = True + else: + free_right = not any(grid[i, dom_j + 1] for i in + range(start, end + 1)) + + return (free_left, free_right, backwards) + + @staticmethod + def _check_free_space_diagonal(dom_i, cod_i, dom_j, cod_j, grid): + """ + For a diagonal morphism, checks whether there is free space + (i.e., space not occupied by any objects) above the morphism + or below it. + """ + def abs_xrange(start, end): + if start < end: + return range(start, end + 1) + else: + return range(end, start + 1) + + if dom_i < cod_i and dom_j < cod_j: + # This morphism goes from top-left to + # bottom-right. + (start_i, start_j) = (dom_i, dom_j) + (end_i, end_j) = (cod_i, cod_j) + backwards = False + elif dom_i > cod_i and dom_j > cod_j: + # This morphism goes from bottom-right to + # top-left. + (start_i, start_j) = (cod_i, cod_j) + (end_i, end_j) = (dom_i, dom_j) + backwards = True + if dom_i < cod_i and dom_j > cod_j: + # This morphism goes from top-right to + # bottom-left. + (start_i, start_j) = (dom_i, dom_j) + (end_i, end_j) = (cod_i, cod_j) + backwards = True + elif dom_i > cod_i and dom_j < cod_j: + # This morphism goes from bottom-left to + # top-right. + (start_i, start_j) = (cod_i, cod_j) + (end_i, end_j) = (dom_i, dom_j) + backwards = False + + # This is an attempt at a fast and furious strategy to + # decide where there is free space on the two sides of + # a diagonal morphism. For a diagonal morphism + # starting at ``(start_i, start_j)`` and ending at + # ``(end_i, end_j)`` the rectangle defined by these + # two points is considered. The slope of the diagonal + # ``alpha`` is then computed. Then, for every cell + # ``(i, j)`` within the rectangle, the slope + # ``alpha1`` of the line through ``(start_i, + # start_j)`` and ``(i, j)`` is considered. If + # ``alpha1`` is between 0 and ``alpha``, the point + # ``(i, j)`` is above the diagonal, if ``alpha1`` is + # between ``alpha`` and infinity, the point is below + # the diagonal. Also note that, with some beforehand + # precautions, this trick works for both the main and + # the secondary diagonals of the rectangle. + + # I have considered the possibility to only follow the + # shorter diagonals immediately above and below the + # main (or secondary) diagonal. This, however, + # wouldn't have resulted in much performance gain or + # better detection of outer edges, because of + # relatively small sizes of diagram grids, while the + # code would have become harder to understand. + + alpha = float(end_i - start_i)/(end_j - start_j) + free_up = True + free_down = True + for i in abs_xrange(start_i, end_i): + if not free_up and not free_down: + break + + for j in abs_xrange(start_j, end_j): + if not free_up and not free_down: + break + + if (i, j) == (start_i, start_j): + continue + + if j == start_j: + alpha1 = "inf" + else: + alpha1 = float(i - start_i)/(j - start_j) + + if grid[i, j]: + if (alpha1 == "inf") or (abs(alpha1) > abs(alpha)): + free_down = False + elif abs(alpha1) < abs(alpha): + free_up = False + + return (free_up, free_down, backwards) + + def _push_labels_out(self, morphisms_str_info, grid, object_coords): + """ + For all straight morphisms which form the visual boundary of + the laid out diagram, puts their labels on their outer sides. + """ + def set_label_position(free1, free2, pos1, pos2, backwards, m_str_info): + """ + Given the information about room available to one side and + to the other side of a morphism (``free1`` and ``free2``), + sets the position of the morphism label in such a way that + it is on the freer side. This latter operations involves + choice between ``pos1`` and ``pos2``, taking ``backwards`` + in consideration. + + Thus this function will do nothing if either both ``free1 + == True`` and ``free2 == True`` or both ``free1 == False`` + and ``free2 == False``. In either case, choosing one side + over the other presents no advantage. + """ + if backwards: + (pos1, pos2) = (pos2, pos1) + + if free1 and not free2: + m_str_info.label_position = pos1 + elif free2 and not free1: + m_str_info.label_position = pos2 + + for m, m_str_info in morphisms_str_info.items(): + if m_str_info.curving or m_str_info.forced_label_position: + # This is either a curved morphism, and curved + # morphisms have other magic, or the position of this + # label has already been fixed. + continue + + if m.domain == m.codomain: + # This is a loop morphism, their labels, again have a + # different magic. + continue + + (dom_i, dom_j) = object_coords[m.domain] + (cod_i, cod_j) = object_coords[m.codomain] + + if dom_i == cod_i: + # Horizontal morphism. + (free_up, free_down, + backwards) = XypicDiagramDrawer._check_free_space_horizontal( + dom_i, dom_j, cod_j, grid) + + set_label_position(free_up, free_down, "^", "_", + backwards, m_str_info) + elif dom_j == cod_j: + # Vertical morphism. + (free_left, free_right, + backwards) = XypicDiagramDrawer._check_free_space_vertical( + dom_i, cod_i, dom_j, grid) + + set_label_position(free_left, free_right, "_", "^", + backwards, m_str_info) + else: + # A diagonal morphism. + (free_up, free_down, + backwards) = XypicDiagramDrawer._check_free_space_diagonal( + dom_i, cod_i, dom_j, cod_j, grid) + + set_label_position(free_up, free_down, "^", "_", + backwards, m_str_info) + + @staticmethod + def _morphism_sort_key(morphism, object_coords): + """ + Provides a morphism sorting key such that horizontal or + vertical morphisms between neighbouring objects come + first, then horizontal or vertical morphisms between more + far away objects, and finally, all other morphisms. + """ + (i, j) = object_coords[morphism.domain] + (target_i, target_j) = object_coords[morphism.codomain] + + if morphism.domain == morphism.codomain: + # Loop morphisms should get after diagonal morphisms + # so that the proper direction in which to curve the + # loop can be determined. + return (3, 0, default_sort_key(morphism)) + + if target_i == i: + return (1, abs(target_j - j), default_sort_key(morphism)) + + if target_j == j: + return (1, abs(target_i - i), default_sort_key(morphism)) + + # Diagonal morphism. + return (2, 0, default_sort_key(morphism)) + + @staticmethod + def _build_xypic_string(diagram, grid, morphisms, + morphisms_str_info, diagram_format): + """ + Given a collection of :class:`ArrowStringDescription` + describing the morphisms of a diagram and the object layout + information of a diagram, produces the final Xy-pic picture. + """ + # Build the mapping between objects and morphisms which have + # them as domains. + object_morphisms = {} + for obj in diagram.objects: + object_morphisms[obj] = [] + for morphism in morphisms: + object_morphisms[morphism.domain].append(morphism) + + result = "\\xymatrix%s{\n" % diagram_format + + for i in range(grid.height): + for j in range(grid.width): + obj = grid[i, j] + if obj: + result += latex(obj) + " " + + morphisms_to_draw = object_morphisms[obj] + for morphism in morphisms_to_draw: + result += str(morphisms_str_info[morphism]) + " " + + # Don't put the & after the last column. + if j < grid.width - 1: + result += "& " + + # Don't put the line break after the last row. + if i < grid.height - 1: + result += "\\\\" + result += "\n" + + result += "}\n" + + return result + + def draw(self, diagram, grid, masked=None, diagram_format=""): + r""" + Returns the Xy-pic representation of ``diagram`` laid out in + ``grid``. + + Consider the following simple triangle diagram. + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy.categories import DiagramGrid, XypicDiagramDrawer + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g], {g * f: "unique"}) + + To draw this diagram, its objects need to be laid out with a + :class:`DiagramGrid`:: + + >>> grid = DiagramGrid(diagram) + + Finally, the drawing: + + >>> drawer = XypicDiagramDrawer() + >>> print(drawer.draw(diagram, grid)) + \xymatrix{ + A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\ + C & + } + + The argument ``masked`` can be used to skip morphisms in the + presentation of the diagram: + + >>> print(drawer.draw(diagram, grid, masked=[g * f])) + \xymatrix{ + A \ar[r]^{f} & B \ar[ld]^{g} \\ + C & + } + + Finally, the ``diagram_format`` argument can be used to + specify the format string of the diagram. For example, to + increase the spacing by 1 cm, proceeding as follows: + + >>> print(drawer.draw(diagram, grid, diagram_format="@+1cm")) + \xymatrix@+1cm{ + A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\ + C & + } + + """ + # This method works in several steps. It starts by removing + # the masked morphisms, if necessary, and then maps objects to + # their positions in the grid (coordinate tuples). Remember + # that objects are unique in ``Diagram`` and in the layout + # produced by ``DiagramGrid``, so every object is mapped to a + # single coordinate pair. + # + # The next step is the central step and is concerned with + # analysing the morphisms of the diagram and deciding how to + # draw them. For example, how to curve the arrows is decided + # at this step. The bulk of the analysis is implemented in + # ``_process_morphism``, to the result of which the + # appropriate formatters are applied. + # + # The result of the previous step is a list of + # ``ArrowStringDescription``. After the analysis and + # application of formatters, some extra logic tries to assure + # better positioning of morphism labels (for example, an + # attempt is made to avoid the situations when arrows cross + # labels). This functionality constitutes the next step and + # is implemented in ``_push_labels_out``. Note that label + # positions which have been set via a formatter are not + # affected in this step. + # + # Finally, at the closing step, the array of + # ``ArrowStringDescription`` and the layout information + # incorporated in ``DiagramGrid`` are combined to produce the + # resulting Xy-pic picture. This part of code lies in + # ``_build_xypic_string``. + + if not masked: + morphisms_props = grid.morphisms + else: + morphisms_props = {} + for m, props in grid.morphisms.items(): + if m in masked: + continue + morphisms_props[m] = props + + # Build the mapping between objects and their position in the + # grid. + object_coords = {} + for i in range(grid.height): + for j in range(grid.width): + if grid[i, j]: + object_coords[grid[i, j]] = (i, j) + + morphisms = sorted(morphisms_props, + key=lambda m: XypicDiagramDrawer._morphism_sort_key( + m, object_coords)) + + # Build the tuples defining the string representations of + # morphisms. + morphisms_str_info = {} + for morphism in morphisms: + string_description = self._process_morphism( + diagram, grid, morphism, object_coords, morphisms, + morphisms_str_info) + + if self.default_arrow_formatter: + self.default_arrow_formatter(string_description) + + for prop in morphisms_props[morphism]: + # prop is a Symbol. TODO: Find out why. + if prop.name in self.arrow_formatters: + formatter = self.arrow_formatters[prop.name] + formatter(string_description) + + morphisms_str_info[morphism] = string_description + + # Reposition the labels a bit. + self._push_labels_out(morphisms_str_info, grid, object_coords) + + return XypicDiagramDrawer._build_xypic_string( + diagram, grid, morphisms, morphisms_str_info, diagram_format) + + +def xypic_draw_diagram(diagram, masked=None, diagram_format="", + groups=None, **hints): + r""" + Provides a shortcut combining :class:`DiagramGrid` and + :class:`XypicDiagramDrawer`. Returns an Xy-pic presentation of + ``diagram``. The argument ``masked`` is a list of morphisms which + will be not be drawn. The argument ``diagram_format`` is the + format string inserted after "\xymatrix". ``groups`` should be a + set of logical groups. The ``hints`` will be passed directly to + the constructor of :class:`DiagramGrid`. + + For more information about the arguments, see the docstrings of + :class:`DiagramGrid` and ``XypicDiagramDrawer.draw``. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy.categories import xypic_draw_diagram + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> diagram = Diagram([f, g], {g * f: "unique"}) + >>> print(xypic_draw_diagram(diagram)) + \xymatrix{ + A \ar[d]_{g\circ f} \ar[r]^{f} & B \ar[ld]^{g} \\ + C & + } + + See Also + ======== + + XypicDiagramDrawer, DiagramGrid + """ + grid = DiagramGrid(diagram, groups, **hints) + drawer = XypicDiagramDrawer() + return drawer.draw(diagram, grid, masked, diagram_format) + + +@doctest_depends_on(exe=('latex', 'dvipng'), modules=('pyglet',)) +def preview_diagram(diagram, masked=None, diagram_format="", groups=None, + output='png', viewer=None, euler=True, **hints): + """ + Combines the functionality of ``xypic_draw_diagram`` and + ``sympy.printing.preview``. The arguments ``masked``, + ``diagram_format``, ``groups``, and ``hints`` are passed to + ``xypic_draw_diagram``, while ``output``, ``viewer, and ``euler`` + are passed to ``preview``. + + Examples + ======== + + >>> from sympy.categories import Object, NamedMorphism, Diagram + >>> from sympy.categories import preview_diagram + >>> A = Object("A") + >>> B = Object("B") + >>> C = Object("C") + >>> f = NamedMorphism(A, B, "f") + >>> g = NamedMorphism(B, C, "g") + >>> d = Diagram([f, g], {g * f: "unique"}) + >>> preview_diagram(d) + + See Also + ======== + + XypicDiagramDrawer + """ + from sympy.printing import preview + latex_output = xypic_draw_diagram(diagram, masked, diagram_format, + groups, **hints) + preview(latex_output, output, viewer, euler, ("xypic",)) diff --git a/phivenv/Lib/site-packages/sympy/categories/tests/__init__.py b/phivenv/Lib/site-packages/sympy/categories/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d356f89cdb1edef824a0825a11a481808f0c89c3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/test_baseclasses.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/test_baseclasses.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be0703ab396563b34e34e5e327d19ad112ca5d37 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/test_baseclasses.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/test_drawing.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/test_drawing.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..607c7916a9e6cc1259221629ee013d3dcfb121f8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/categories/tests/__pycache__/test_drawing.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/categories/tests/test_baseclasses.py b/phivenv/Lib/site-packages/sympy/categories/tests/test_baseclasses.py new file mode 100644 index 0000000000000000000000000000000000000000..cfac32229768fb5903b23b11ffb236912c0b931e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/categories/tests/test_baseclasses.py @@ -0,0 +1,209 @@ +from sympy.categories import (Object, Morphism, IdentityMorphism, + NamedMorphism, CompositeMorphism, + Diagram, Category) +from sympy.categories.baseclasses import Class +from sympy.testing.pytest import raises +from sympy.core.containers import (Dict, Tuple) +from sympy.sets import EmptySet +from sympy.sets.sets import FiniteSet + + +def test_morphisms(): + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + + # Test the base morphism. + f = NamedMorphism(A, B, "f") + assert f.domain == A + assert f.codomain == B + assert f == NamedMorphism(A, B, "f") + + # Test identities. + id_A = IdentityMorphism(A) + id_B = IdentityMorphism(B) + assert id_A.domain == A + assert id_A.codomain == A + assert id_A == IdentityMorphism(A) + assert id_A != id_B + + # Test named morphisms. + g = NamedMorphism(B, C, "g") + assert g.name == "g" + assert g != f + assert g == NamedMorphism(B, C, "g") + assert g != NamedMorphism(B, C, "f") + + # Test composite morphisms. + assert f == CompositeMorphism(f) + + k = g.compose(f) + assert k.domain == A + assert k.codomain == C + assert k.components == Tuple(f, g) + assert g * f == k + assert CompositeMorphism(f, g) == k + + assert CompositeMorphism(g * f) == g * f + + # Test the associativity of composition. + h = NamedMorphism(C, D, "h") + + p = h * g + u = h * g * f + + assert h * k == u + assert p * f == u + assert CompositeMorphism(f, g, h) == u + + # Test flattening. + u2 = u.flatten("u") + assert isinstance(u2, NamedMorphism) + assert u2.name == "u" + assert u2.domain == A + assert u2.codomain == D + + # Test identities. + assert f * id_A == f + assert id_B * f == f + assert id_A * id_A == id_A + assert CompositeMorphism(id_A) == id_A + + # Test bad compositions. + raises(ValueError, lambda: f * g) + + raises(TypeError, lambda: f.compose(None)) + raises(TypeError, lambda: id_A.compose(None)) + raises(TypeError, lambda: f * None) + raises(TypeError, lambda: id_A * None) + + raises(TypeError, lambda: CompositeMorphism(f, None, 1)) + + raises(ValueError, lambda: NamedMorphism(A, B, "")) + raises(NotImplementedError, lambda: Morphism(A, B)) + + +def test_diagram(): + A = Object("A") + B = Object("B") + C = Object("C") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + id_A = IdentityMorphism(A) + id_B = IdentityMorphism(B) + + empty = EmptySet + + # Test the addition of identities. + d1 = Diagram([f]) + + assert d1.objects == FiniteSet(A, B) + assert d1.hom(A, B) == (FiniteSet(f), empty) + assert d1.hom(A, A) == (FiniteSet(id_A), empty) + assert d1.hom(B, B) == (FiniteSet(id_B), empty) + + assert d1 == Diagram([id_A, f]) + assert d1 == Diagram([f, f]) + + # Test the addition of composites. + d2 = Diagram([f, g]) + homAC = d2.hom(A, C)[0] + + assert d2.objects == FiniteSet(A, B, C) + assert g * f in d2.premises.keys() + assert homAC == FiniteSet(g * f) + + # Test equality, inequality and hash. + d11 = Diagram([f]) + + assert d1 == d11 + assert d1 != d2 + assert hash(d1) == hash(d11) + + d11 = Diagram({f: "unique"}) + assert d1 != d11 + + # Make sure that (re-)adding composites (with new properties) + # works as expected. + d = Diagram([f, g], {g * f: "unique"}) + assert d.conclusions == Dict({g * f: FiniteSet("unique")}) + + # Check the hom-sets when there are premises and conclusions. + assert d.hom(A, C) == (FiniteSet(g * f), FiniteSet(g * f)) + d = Diagram([f, g], [g * f]) + assert d.hom(A, C) == (FiniteSet(g * f), FiniteSet(g * f)) + + # Check how the properties of composite morphisms are computed. + d = Diagram({f: ["unique", "isomorphism"], g: "unique"}) + assert d.premises[g * f] == FiniteSet("unique") + + # Check that conclusion morphisms with new objects are not allowed. + d = Diagram([f], [g]) + assert d.conclusions == Dict({}) + + # Test an empty diagram. + d = Diagram() + assert d.premises == Dict({}) + assert d.conclusions == Dict({}) + assert d.objects == empty + + # Check a SymPy Dict object. + d = Diagram(Dict({f: FiniteSet("unique", "isomorphism"), g: "unique"})) + assert d.premises[g * f] == FiniteSet("unique") + + # Check the addition of components of composite morphisms. + d = Diagram([g * f]) + assert f in d.premises + assert g in d.premises + + # Check subdiagrams. + d = Diagram([f, g], {g * f: "unique"}) + + d1 = Diagram([f]) + assert d.is_subdiagram(d1) + assert not d1.is_subdiagram(d) + + d = Diagram([NamedMorphism(B, A, "f'")]) + assert not d.is_subdiagram(d1) + assert not d1.is_subdiagram(d) + + d1 = Diagram([f, g], {g * f: ["unique", "something"]}) + assert not d.is_subdiagram(d1) + assert not d1.is_subdiagram(d) + + d = Diagram({f: "blooh"}) + d1 = Diagram({f: "bleeh"}) + assert not d.is_subdiagram(d1) + assert not d1.is_subdiagram(d) + + d = Diagram([f, g], {f: "unique", g * f: "veryunique"}) + d1 = d.subdiagram_from_objects(FiniteSet(A, B)) + assert d1 == Diagram([f], {f: "unique"}) + raises(ValueError, lambda: d.subdiagram_from_objects(FiniteSet(A, + Object("D")))) + + raises(ValueError, lambda: Diagram({IdentityMorphism(A): "unique"})) + + +def test_category(): + A = Object("A") + B = Object("B") + C = Object("C") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + + d1 = Diagram([f, g]) + d2 = Diagram([f]) + + objects = d1.objects | d2.objects + + K = Category("K", objects, commutative_diagrams=[d1, d2]) + + assert K.name == "K" + assert K.objects == Class(objects) + assert K.commutative_diagrams == FiniteSet(d1, d2) + + raises(ValueError, lambda: Category("")) diff --git a/phivenv/Lib/site-packages/sympy/categories/tests/test_drawing.py b/phivenv/Lib/site-packages/sympy/categories/tests/test_drawing.py new file mode 100644 index 0000000000000000000000000000000000000000..63a13266cd6b58f6a85aad4af0813b395acbb5e1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/categories/tests/test_drawing.py @@ -0,0 +1,919 @@ +from sympy.categories.diagram_drawing import _GrowableGrid, ArrowStringDescription +from sympy.categories import (DiagramGrid, Object, NamedMorphism, + Diagram, XypicDiagramDrawer, xypic_draw_diagram) +from sympy.sets.sets import FiniteSet + + +def test_GrowableGrid(): + grid = _GrowableGrid(1, 2) + + # Check dimensions. + assert grid.width == 1 + assert grid.height == 2 + + # Check initialization of elements. + assert grid[0, 0] is None + assert grid[1, 0] is None + + # Check assignment to elements. + grid[0, 0] = 1 + grid[1, 0] = "two" + + assert grid[0, 0] == 1 + assert grid[1, 0] == "two" + + # Check appending a row. + grid.append_row() + + assert grid.width == 1 + assert grid.height == 3 + + assert grid[0, 0] == 1 + assert grid[1, 0] == "two" + assert grid[2, 0] is None + + # Check appending a column. + grid.append_column() + assert grid.width == 2 + assert grid.height == 3 + + assert grid[0, 0] == 1 + assert grid[1, 0] == "two" + assert grid[2, 0] is None + + assert grid[0, 1] is None + assert grid[1, 1] is None + assert grid[2, 1] is None + + grid = _GrowableGrid(1, 2) + grid[0, 0] = 1 + grid[1, 0] = "two" + + # Check prepending a row. + grid.prepend_row() + assert grid.width == 1 + assert grid.height == 3 + + assert grid[0, 0] is None + assert grid[1, 0] == 1 + assert grid[2, 0] == "two" + + # Check prepending a column. + grid.prepend_column() + assert grid.width == 2 + assert grid.height == 3 + + assert grid[0, 0] is None + assert grid[1, 0] is None + assert grid[2, 0] is None + + assert grid[0, 1] is None + assert grid[1, 1] == 1 + assert grid[2, 1] == "two" + + +def test_DiagramGrid(): + # Set up some objects and morphisms. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(D, A, "h") + k = NamedMorphism(D, B, "k") + + # A one-morphism diagram. + d = Diagram([f]) + grid = DiagramGrid(d) + + assert grid.width == 2 + assert grid.height == 1 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid.morphisms == {f: FiniteSet()} + + # A triangle. + d = Diagram([f, g], {g * f: "unique"}) + grid = DiagramGrid(d) + + assert grid.width == 2 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[1, 0] == C + assert grid[1, 1] is None + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), + g * f: FiniteSet("unique")} + + # A triangle with a "loop" morphism. + l_A = NamedMorphism(A, A, "l_A") + d = Diagram([f, g, l_A]) + grid = DiagramGrid(d) + + assert grid.width == 2 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[1, 0] is None + assert grid[1, 1] == C + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), l_A: FiniteSet()} + + # A simple diagram. + d = Diagram([f, g, h, k]) + grid = DiagramGrid(d) + + assert grid.width == 3 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] == D + assert grid[1, 0] is None + assert grid[1, 1] == C + assert grid[1, 2] is None + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(), + k: FiniteSet()} + + assert str(grid) == '[[Object("A"), Object("B"), Object("D")], ' \ + '[None, Object("C"), None]]' + + # A chain of morphisms. + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(C, D, "h") + k = NamedMorphism(D, E, "k") + d = Diagram([f, g, h, k]) + grid = DiagramGrid(d) + + assert grid.width == 3 + assert grid.height == 3 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] is None + assert grid[1, 0] is None + assert grid[1, 1] == C + assert grid[1, 2] == D + assert grid[2, 0] is None + assert grid[2, 1] is None + assert grid[2, 2] == E + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(), + k: FiniteSet()} + + # A square. + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, D, "g") + h = NamedMorphism(A, C, "h") + k = NamedMorphism(C, D, "k") + d = Diagram([f, g, h, k]) + grid = DiagramGrid(d) + + assert grid.width == 2 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[1, 0] == C + assert grid[1, 1] == D + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(), + k: FiniteSet()} + + # A strange diagram which resulted from a typo when creating a + # test for five lemma, but which allowed to stop one extra problem + # in the algorithm. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + A_ = Object("A'") + B_ = Object("B'") + C_ = Object("C'") + D_ = Object("D'") + E_ = Object("E'") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(C, D, "h") + i = NamedMorphism(D, E, "i") + + # These 4 morphisms should be between primed objects. + j = NamedMorphism(A, B, "j") + k = NamedMorphism(B, C, "k") + l = NamedMorphism(C, D, "l") + m = NamedMorphism(D, E, "m") + + o = NamedMorphism(A, A_, "o") + p = NamedMorphism(B, B_, "p") + q = NamedMorphism(C, C_, "q") + r = NamedMorphism(D, D_, "r") + s = NamedMorphism(E, E_, "s") + + d = Diagram([f, g, h, i, j, k, l, m, o, p, q, r, s]) + grid = DiagramGrid(d) + + assert grid.width == 3 + assert grid.height == 4 + assert grid[0, 0] is None + assert grid[0, 1] == A + assert grid[0, 2] == A_ + assert grid[1, 0] == C + assert grid[1, 1] == B + assert grid[1, 2] == B_ + assert grid[2, 0] == C_ + assert grid[2, 1] == D + assert grid[2, 2] == D_ + assert grid[3, 0] is None + assert grid[3, 1] == E + assert grid[3, 2] == E_ + + morphisms = {} + for m in [f, g, h, i, j, k, l, m, o, p, q, r, s]: + morphisms[m] = FiniteSet() + assert grid.morphisms == morphisms + + # A cube. + A1 = Object("A1") + A2 = Object("A2") + A3 = Object("A3") + A4 = Object("A4") + A5 = Object("A5") + A6 = Object("A6") + A7 = Object("A7") + A8 = Object("A8") + + # The top face of the cube. + f1 = NamedMorphism(A1, A2, "f1") + f2 = NamedMorphism(A1, A3, "f2") + f3 = NamedMorphism(A2, A4, "f3") + f4 = NamedMorphism(A3, A4, "f3") + + # The bottom face of the cube. + f5 = NamedMorphism(A5, A6, "f5") + f6 = NamedMorphism(A5, A7, "f6") + f7 = NamedMorphism(A6, A8, "f7") + f8 = NamedMorphism(A7, A8, "f8") + + # The remaining morphisms. + f9 = NamedMorphism(A1, A5, "f9") + f10 = NamedMorphism(A2, A6, "f10") + f11 = NamedMorphism(A3, A7, "f11") + f12 = NamedMorphism(A4, A8, "f11") + + d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12]) + grid = DiagramGrid(d) + + assert grid.width == 4 + assert grid.height == 3 + assert grid[0, 0] is None + assert grid[0, 1] == A5 + assert grid[0, 2] == A6 + assert grid[0, 3] is None + assert grid[1, 0] is None + assert grid[1, 1] == A1 + assert grid[1, 2] == A2 + assert grid[1, 3] is None + assert grid[2, 0] == A7 + assert grid[2, 1] == A3 + assert grid[2, 2] == A4 + assert grid[2, 3] == A8 + + morphisms = {} + for m in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12]: + morphisms[m] = FiniteSet() + assert grid.morphisms == morphisms + + # A line diagram. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(C, D, "h") + i = NamedMorphism(D, E, "i") + d = Diagram([f, g, h, i]) + grid = DiagramGrid(d, layout="sequential") + + assert grid.width == 5 + assert grid.height == 1 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] == C + assert grid[0, 3] == D + assert grid[0, 4] == E + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(), + i: FiniteSet()} + + # Test the transposed version. + grid = DiagramGrid(d, layout="sequential", transpose=True) + + assert grid.width == 1 + assert grid.height == 5 + assert grid[0, 0] == A + assert grid[1, 0] == B + assert grid[2, 0] == C + assert grid[3, 0] == D + assert grid[4, 0] == E + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(), + i: FiniteSet()} + + # A pullback. + m1 = NamedMorphism(A, B, "m1") + m2 = NamedMorphism(A, C, "m2") + s1 = NamedMorphism(B, D, "s1") + s2 = NamedMorphism(C, D, "s2") + f1 = NamedMorphism(E, B, "f1") + f2 = NamedMorphism(E, C, "f2") + g = NamedMorphism(E, A, "g") + + d = Diagram([m1, m2, s1, s2, f1, f2], {g: "unique"}) + grid = DiagramGrid(d) + + assert grid.width == 3 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] == E + assert grid[1, 0] == C + assert grid[1, 1] == D + assert grid[1, 2] is None + + morphisms = {g: FiniteSet("unique")} + for m in [m1, m2, s1, s2, f1, f2]: + morphisms[m] = FiniteSet() + assert grid.morphisms == morphisms + + # Test the pullback with sequential layout, just for stress + # testing. + grid = DiagramGrid(d, layout="sequential") + + assert grid.width == 5 + assert grid.height == 1 + assert grid[0, 0] == D + assert grid[0, 1] == B + assert grid[0, 2] == A + assert grid[0, 3] == C + assert grid[0, 4] == E + assert grid.morphisms == morphisms + + # Test a pullback with object grouping. + grid = DiagramGrid(d, groups=FiniteSet(E, FiniteSet(A, B, C, D))) + + assert grid.width == 3 + assert grid.height == 2 + assert grid[0, 0] == E + assert grid[0, 1] == A + assert grid[0, 2] == B + assert grid[1, 0] is None + assert grid[1, 1] == C + assert grid[1, 2] == D + assert grid.morphisms == morphisms + + # Five lemma, actually. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + A_ = Object("A'") + B_ = Object("B'") + C_ = Object("C'") + D_ = Object("D'") + E_ = Object("E'") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(C, D, "h") + i = NamedMorphism(D, E, "i") + + j = NamedMorphism(A_, B_, "j") + k = NamedMorphism(B_, C_, "k") + l = NamedMorphism(C_, D_, "l") + m = NamedMorphism(D_, E_, "m") + + o = NamedMorphism(A, A_, "o") + p = NamedMorphism(B, B_, "p") + q = NamedMorphism(C, C_, "q") + r = NamedMorphism(D, D_, "r") + s = NamedMorphism(E, E_, "s") + + d = Diagram([f, g, h, i, j, k, l, m, o, p, q, r, s]) + grid = DiagramGrid(d) + + assert grid.width == 5 + assert grid.height == 3 + assert grid[0, 0] is None + assert grid[0, 1] == A + assert grid[0, 2] == A_ + assert grid[0, 3] is None + assert grid[0, 4] is None + assert grid[1, 0] == C + assert grid[1, 1] == B + assert grid[1, 2] == B_ + assert grid[1, 3] == C_ + assert grid[1, 4] is None + assert grid[2, 0] == D + assert grid[2, 1] == E + assert grid[2, 2] is None + assert grid[2, 3] == D_ + assert grid[2, 4] == E_ + + morphisms = {} + for m in [f, g, h, i, j, k, l, m, o, p, q, r, s]: + morphisms[m] = FiniteSet() + assert grid.morphisms == morphisms + + # Test the five lemma with object grouping. + grid = DiagramGrid(d, FiniteSet( + FiniteSet(A, B, C, D, E), FiniteSet(A_, B_, C_, D_, E_))) + + assert grid.width == 6 + assert grid.height == 3 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] is None + assert grid[0, 3] == A_ + assert grid[0, 4] == B_ + assert grid[0, 5] is None + assert grid[1, 0] is None + assert grid[1, 1] == C + assert grid[1, 2] == D + assert grid[1, 3] is None + assert grid[1, 4] == C_ + assert grid[1, 5] == D_ + assert grid[2, 0] is None + assert grid[2, 1] is None + assert grid[2, 2] == E + assert grid[2, 3] is None + assert grid[2, 4] is None + assert grid[2, 5] == E_ + assert grid.morphisms == morphisms + + # Test the five lemma with object grouping, but mixing containers + # to represent groups. + grid = DiagramGrid(d, [(A, B, C, D, E), {A_, B_, C_, D_, E_}]) + + assert grid.width == 6 + assert grid.height == 3 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] is None + assert grid[0, 3] == A_ + assert grid[0, 4] == B_ + assert grid[0, 5] is None + assert grid[1, 0] is None + assert grid[1, 1] == C + assert grid[1, 2] == D + assert grid[1, 3] is None + assert grid[1, 4] == C_ + assert grid[1, 5] == D_ + assert grid[2, 0] is None + assert grid[2, 1] is None + assert grid[2, 2] == E + assert grid[2, 3] is None + assert grid[2, 4] is None + assert grid[2, 5] == E_ + assert grid.morphisms == morphisms + + # Test the five lemma with object grouping and hints. + grid = DiagramGrid(d, { + FiniteSet(A, B, C, D, E): {"layout": "sequential", + "transpose": True}, + FiniteSet(A_, B_, C_, D_, E_): {"layout": "sequential", + "transpose": True}}, + transpose=True) + + assert grid.width == 5 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] == C + assert grid[0, 3] == D + assert grid[0, 4] == E + assert grid[1, 0] == A_ + assert grid[1, 1] == B_ + assert grid[1, 2] == C_ + assert grid[1, 3] == D_ + assert grid[1, 4] == E_ + assert grid.morphisms == morphisms + + # A two-triangle disconnected diagram. + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + f_ = NamedMorphism(A_, B_, "f") + g_ = NamedMorphism(B_, C_, "g") + d = Diagram([f, g, f_, g_], {g * f: "unique", g_ * f_: "unique"}) + grid = DiagramGrid(d) + + assert grid.width == 4 + assert grid.height == 2 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] == A_ + assert grid[0, 3] == B_ + assert grid[1, 0] == C + assert grid[1, 1] is None + assert grid[1, 2] == C_ + assert grid[1, 3] is None + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), f_: FiniteSet(), + g_: FiniteSet(), g * f: FiniteSet("unique"), + g_ * f_: FiniteSet("unique")} + + # A two-morphism disconnected diagram. + f = NamedMorphism(A, B, "f") + g = NamedMorphism(C, D, "g") + d = Diagram([f, g]) + grid = DiagramGrid(d) + + assert grid.width == 4 + assert grid.height == 1 + assert grid[0, 0] == A + assert grid[0, 1] == B + assert grid[0, 2] == C + assert grid[0, 3] == D + assert grid.morphisms == {f: FiniteSet(), g: FiniteSet()} + + # Test a one-object diagram. + f = NamedMorphism(A, A, "f") + d = Diagram([f]) + grid = DiagramGrid(d) + + assert grid.width == 1 + assert grid.height == 1 + assert grid[0, 0] == A + + # Test a two-object disconnected diagram. + g = NamedMorphism(B, B, "g") + d = Diagram([f, g]) + grid = DiagramGrid(d) + + assert grid.width == 2 + assert grid.height == 1 + assert grid[0, 0] == A + assert grid[0, 1] == B + + +def test_DiagramGrid_pseudopod(): + # Test a diagram in which even growing a pseudopod does not + # eventually help. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + F = Object("F") + A_ = Object("A'") + B_ = Object("B'") + C_ = Object("C'") + D_ = Object("D'") + E_ = Object("E'") + + f1 = NamedMorphism(A, B, "f1") + f2 = NamedMorphism(A, C, "f2") + f3 = NamedMorphism(A, D, "f3") + f4 = NamedMorphism(A, E, "f4") + f5 = NamedMorphism(A, A_, "f5") + f6 = NamedMorphism(A, B_, "f6") + f7 = NamedMorphism(A, C_, "f7") + f8 = NamedMorphism(A, D_, "f8") + f9 = NamedMorphism(A, E_, "f9") + f10 = NamedMorphism(A, F, "f10") + d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10]) + grid = DiagramGrid(d) + + assert grid.width == 5 + assert grid.height == 3 + assert grid[0, 0] == E + assert grid[0, 1] == C + assert grid[0, 2] == C_ + assert grid[0, 3] == E_ + assert grid[0, 4] == F + assert grid[1, 0] == D + assert grid[1, 1] == A + assert grid[1, 2] == A_ + assert grid[1, 3] is None + assert grid[1, 4] is None + assert grid[2, 0] == D_ + assert grid[2, 1] == B + assert grid[2, 2] == B_ + assert grid[2, 3] is None + assert grid[2, 4] is None + + morphisms = {} + for f in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10]: + morphisms[f] = FiniteSet() + assert grid.morphisms == morphisms + + +def test_ArrowStringDescription(): + astr = ArrowStringDescription("cm", "", None, "", "", "d", "r", "_", "f") + assert str(astr) == "\\ar[dr]_{f}" + + astr = ArrowStringDescription("cm", "", 12, "", "", "d", "r", "_", "f") + assert str(astr) == "\\ar[dr]_{f}" + + astr = ArrowStringDescription("cm", "^", 12, "", "", "d", "r", "_", "f") + assert str(astr) == "\\ar@/^12cm/[dr]_{f}" + + astr = ArrowStringDescription("cm", "", 12, "r", "", "d", "r", "_", "f") + assert str(astr) == "\\ar[dr]_{f}" + + astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f") + assert str(astr) == "\\ar@(r,u)[dr]_{f}" + + astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f") + assert str(astr) == "\\ar@(r,u)[dr]_{f}" + + astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f") + astr.arrow_style = "{-->}" + assert str(astr) == "\\ar@(r,u)@{-->}[dr]_{f}" + + astr = ArrowStringDescription("cm", "_", 12, "", "", "d", "r", "_", "f") + astr.arrow_style = "{-->}" + assert str(astr) == "\\ar@/_12cm/@{-->}[dr]_{f}" + + +def test_XypicDiagramDrawer_line(): + # A linear diagram. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(C, D, "h") + i = NamedMorphism(D, E, "i") + d = Diagram([f, g, h, i]) + grid = DiagramGrid(d, layout="sequential") + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[r]^{f} & B \\ar[r]^{g} & C \\ar[r]^{h} & D \\ar[r]^{i} & E \n" \ + "}\n" + + # The same diagram, transposed. + grid = DiagramGrid(d, layout="sequential", transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[d]^{f} \\\\\n" \ + "B \\ar[d]^{g} \\\\\n" \ + "C \\ar[d]^{h} \\\\\n" \ + "D \\ar[d]^{i} \\\\\n" \ + "E \n" \ + "}\n" + + +def test_XypicDiagramDrawer_triangle(): + # A triangle diagram. + A = Object("A") + B = Object("B") + C = Object("C") + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + + d = Diagram([f, g], {g * f: "unique"}) + grid = DiagramGrid(d) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[d]_{g\\circ f} \\ar[r]^{f} & B \\ar[ld]^{g} \\\\\n" \ + "C & \n" \ + "}\n" + + # The same diagram, transposed. + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[r]^{g\\circ f} \\ar[d]_{f} & C \\\\\n" \ + "B \\ar[ru]_{g} & \n" \ + "}\n" + + # The same diagram, with a masked morphism. + assert drawer.draw(d, grid, masked=[g]) == "\\xymatrix{\n" \ + "A \\ar[r]^{g\\circ f} \\ar[d]_{f} & C \\\\\n" \ + "B & \n" \ + "}\n" + + # The same diagram with a formatter for "unique". + def formatter(astr): + astr.label = "\\exists !" + astr.label + astr.arrow_style = "{-->}" + + drawer.arrow_formatters["unique"] = formatter + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar@{-->}[r]^{\\exists !g\\circ f} \\ar[d]_{f} & C \\\\\n" \ + "B \\ar[ru]_{g} & \n" \ + "}\n" + + # The same diagram with a default formatter. + def default_formatter(astr): + astr.label_displacement = "(0.45)" + + drawer.default_arrow_formatter = default_formatter + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar@{-->}[r]^(0.45){\\exists !g\\circ f} \\ar[d]_(0.45){f} & C \\\\\n" \ + "B \\ar[ru]_(0.45){g} & \n" \ + "}\n" + + # A triangle diagram with a lot of morphisms between the same + # objects. + f1 = NamedMorphism(B, A, "f1") + f2 = NamedMorphism(A, B, "f2") + g1 = NamedMorphism(C, B, "g1") + g2 = NamedMorphism(B, C, "g2") + d = Diagram([f, f1, f2, g, g1, g2], {f1 * g1: "unique", g2 * f2: "unique"}) + + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid, masked=[f1*g1*g2*f2, g2*f2*f1*g1]) == \ + "\\xymatrix{\n" \ + "A \\ar[r]^{g_{2}\\circ f_{2}} \\ar[d]_{f} \\ar@/^3mm/[d]^{f_{2}} " \ + "& C \\ar@/^3mm/[l]^{f_{1}\\circ g_{1}} \\ar@/^3mm/[ld]^{g_{1}} \\\\\n" \ + "B \\ar@/^3mm/[u]^{f_{1}} \\ar[ru]_{g} \\ar@/^3mm/[ru]^{g_{2}} & \n" \ + "}\n" + + +def test_XypicDiagramDrawer_cube(): + # A cube diagram. + A1 = Object("A1") + A2 = Object("A2") + A3 = Object("A3") + A4 = Object("A4") + A5 = Object("A5") + A6 = Object("A6") + A7 = Object("A7") + A8 = Object("A8") + + # The top face of the cube. + f1 = NamedMorphism(A1, A2, "f1") + f2 = NamedMorphism(A1, A3, "f2") + f3 = NamedMorphism(A2, A4, "f3") + f4 = NamedMorphism(A3, A4, "f3") + + # The bottom face of the cube. + f5 = NamedMorphism(A5, A6, "f5") + f6 = NamedMorphism(A5, A7, "f6") + f7 = NamedMorphism(A6, A8, "f7") + f8 = NamedMorphism(A7, A8, "f8") + + # The remaining morphisms. + f9 = NamedMorphism(A1, A5, "f9") + f10 = NamedMorphism(A2, A6, "f10") + f11 = NamedMorphism(A3, A7, "f11") + f12 = NamedMorphism(A4, A8, "f11") + + d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12]) + grid = DiagramGrid(d) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "& A_{5} \\ar[r]^{f_{5}} \\ar[ldd]_{f_{6}} & A_{6} \\ar[rdd]^{f_{7}} " \ + "& \\\\\n" \ + "& A_{1} \\ar[r]^{f_{1}} \\ar[d]^{f_{2}} \\ar[u]^{f_{9}} & A_{2} " \ + "\\ar[d]^{f_{3}} \\ar[u]_{f_{10}} & \\\\\n" \ + "A_{7} \\ar@/_3mm/[rrr]_{f_{8}} & A_{3} \\ar[r]^{f_{3}} \\ar[l]_{f_{11}} " \ + "& A_{4} \\ar[r]^{f_{11}} & A_{8} \n" \ + "}\n" + + # The same diagram, transposed. + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "& & A_{7} \\ar@/^3mm/[ddd]^{f_{8}} \\\\\n" \ + "A_{5} \\ar[d]_{f_{5}} \\ar[rru]^{f_{6}} & A_{1} \\ar[d]^{f_{1}} " \ + "\\ar[r]^{f_{2}} \\ar[l]^{f_{9}} & A_{3} \\ar[d]_{f_{3}} " \ + "\\ar[u]^{f_{11}} \\\\\n" \ + "A_{6} \\ar[rrd]_{f_{7}} & A_{2} \\ar[r]^{f_{3}} \\ar[l]^{f_{10}} " \ + "& A_{4} \\ar[d]_{f_{11}} \\\\\n" \ + "& & A_{8} \n" \ + "}\n" + + +def test_XypicDiagramDrawer_curved_and_loops(): + # A simple diagram, with a curved arrow. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(D, A, "h") + k = NamedMorphism(D, B, "k") + d = Diagram([f, g, h, k]) + grid = DiagramGrid(d) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[r]_{f} & B \\ar[d]^{g} & D \\ar[l]^{k} \\ar@/_3mm/[ll]_{h} \\\\\n" \ + "& C & \n" \ + "}\n" + + # The same diagram, transposed. + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[d]^{f} & \\\\\n" \ + "B \\ar[r]^{g} & C \\\\\n" \ + "D \\ar[u]_{k} \\ar@/^3mm/[uu]^{h} & \n" \ + "}\n" + + # The same diagram, larger and rotated. + assert drawer.draw(d, grid, diagram_format="@+1cm@dr") == \ + "\\xymatrix@+1cm@dr{\n" \ + "A \\ar[d]^{f} & \\\\\n" \ + "B \\ar[r]^{g} & C \\\\\n" \ + "D \\ar[u]_{k} \\ar@/^3mm/[uu]^{h} & \n" \ + "}\n" + + # A simple diagram with three curved arrows. + h1 = NamedMorphism(D, A, "h1") + h2 = NamedMorphism(A, D, "h2") + k = NamedMorphism(D, B, "k") + d = Diagram([f, g, h, k, h1, h2]) + grid = DiagramGrid(d) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} & B \\ar[d]^{g} & D \\ar[l]^{k} " \ + "\\ar@/_7mm/[ll]_{h} \\ar@/_11mm/[ll]_{h_{1}} \\\\\n" \ + "& C & \n" \ + "}\n" + + # The same diagram, transposed. + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} & \\\\\n" \ + "B \\ar[r]^{g} & C \\\\\n" \ + "D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} & \n" \ + "}\n" + + # The same diagram, with "loop" morphisms. + l_A = NamedMorphism(A, A, "l_A") + l_D = NamedMorphism(D, D, "l_D") + l_C = NamedMorphism(C, C, "l_C") + d = Diagram([f, g, h, k, h1, h2, l_A, l_D, l_C]) + grid = DiagramGrid(d) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} \\ar@(u,l)[]^{l_{A}} " \ + "& B \\ar[d]^{g} & D \\ar[l]^{k} \\ar@/_7mm/[ll]_{h} " \ + "\\ar@/_11mm/[ll]_{h_{1}} \\ar@(r,u)[]^{l_{D}} \\\\\n" \ + "& C \\ar@(l,d)[]^{l_{C}} & \n" \ + "}\n" + + # The same diagram with "loop" morphisms, transposed. + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} \\ar@(r,u)[]^{l_{A}} & \\\\\n" \ + "B \\ar[r]^{g} & C \\ar@(r,u)[]^{l_{C}} \\\\\n" \ + "D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} " \ + "\\ar@(l,d)[]^{l_{D}} & \n" \ + "}\n" + + # The same diagram with two "loop" morphisms per object. + l_A_ = NamedMorphism(A, A, "n_A") + l_D_ = NamedMorphism(D, D, "n_D") + l_C_ = NamedMorphism(C, C, "n_C") + d = Diagram([f, g, h, k, h1, h2, l_A, l_D, l_C, l_A_, l_D_, l_C_]) + grid = DiagramGrid(d) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} \\ar@(u,l)[]^{l_{A}} " \ + "\\ar@/^3mm/@(l,d)[]^{n_{A}} & B \\ar[d]^{g} & D \\ar[l]^{k} " \ + "\\ar@/_7mm/[ll]_{h} \\ar@/_11mm/[ll]_{h_{1}} \\ar@(r,u)[]^{l_{D}} " \ + "\\ar@/^3mm/@(d,r)[]^{n_{D}} \\\\\n" \ + "& C \\ar@(l,d)[]^{l_{C}} \\ar@/^3mm/@(d,r)[]^{n_{C}} & \n" \ + "}\n" + + # The same diagram with two "loop" morphisms per object, transposed. + grid = DiagramGrid(d, transpose=True) + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == "\\xymatrix{\n" \ + "A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} \\ar@(r,u)[]^{l_{A}} " \ + "\\ar@/^3mm/@(u,l)[]^{n_{A}} & \\\\\n" \ + "B \\ar[r]^{g} & C \\ar@(r,u)[]^{l_{C}} \\ar@/^3mm/@(d,r)[]^{n_{C}} \\\\\n" \ + "D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} " \ + "\\ar@(l,d)[]^{l_{D}} \\ar@/^3mm/@(d,r)[]^{n_{D}} & \n" \ + "}\n" + + +def test_xypic_draw_diagram(): + # A linear diagram. + A = Object("A") + B = Object("B") + C = Object("C") + D = Object("D") + E = Object("E") + + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + h = NamedMorphism(C, D, "h") + i = NamedMorphism(D, E, "i") + d = Diagram([f, g, h, i]) + + grid = DiagramGrid(d, layout="sequential") + drawer = XypicDiagramDrawer() + assert drawer.draw(d, grid) == xypic_draw_diagram(d, layout="sequential") diff --git a/phivenv/Lib/site-packages/sympy/codegen/__init__.py b/phivenv/Lib/site-packages/sympy/codegen/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..62b195633bae28371bdf9e79317050d7fa7125ae --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/__init__.py @@ -0,0 +1,24 @@ +""" The ``sympy.codegen`` module contains classes and functions for building +abstract syntax trees of algorithms. These trees may then be printed by the +code-printers in ``sympy.printing``. + +There are several submodules available: +- ``sympy.codegen.ast``: AST nodes useful across multiple languages. +- ``sympy.codegen.cnodes``: AST nodes useful for the C family of languages. +- ``sympy.codegen.fnodes``: AST nodes useful for Fortran. +- ``sympy.codegen.cfunctions``: functions specific to C (C99 math functions) +- ``sympy.codegen.ffunctions``: functions specific to Fortran (e.g. ``kind``). + + + +""" +from .ast import ( + Assignment, aug_assign, CodeBlock, For, Attribute, Variable, Declaration, + While, Scope, Print, FunctionPrototype, FunctionDefinition, FunctionCall +) + +__all__ = [ + 'Assignment', 'aug_assign', 'CodeBlock', 'For', 'Attribute', 'Variable', + 'Declaration', 'While', 'Scope', 'Print', 'FunctionPrototype', + 'FunctionDefinition', 'FunctionCall', +] diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2c66d0358f0dc3abfc53bf85084e60f050eece7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/abstract_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/abstract_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0012746660828a43674d757273b0c9c8b816995e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/abstract_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/algorithms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/algorithms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d24eae4cc4b3a7a5966e86803c4c7c0ec1b70c14 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/algorithms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/approximations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/approximations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43a0f30966e2956cafbca6b4149d4071dc0a4a01 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/approximations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/ast.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/ast.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5de695ab71f77277de8d69243928be00322d0c7e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/ast.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cfunctions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cfunctions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8168de76cad6b19c523ca56b3d6af10f635912a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cfunctions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cnodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cnodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3311606366713e6c6b4bb7c705958a0355ba989d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cnodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cutils.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cutils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb73593dc1420c23ab4599210fe26655b21bcada Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cutils.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cxxnodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cxxnodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e6b2e4454905c4747b3ca1037472ec386990614 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/cxxnodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/fnodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/fnodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a3855b397097b87c7e48e6a73cb7bcdf314ff09 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/fnodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/futils.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/futils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..177792d2852b04c9c98fe96ff586609dc2131a79 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/futils.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/matrix_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/matrix_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fd5130f4c133cdce127efcd029a6aca89331eb1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/matrix_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/numpy_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/numpy_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83785ce1df0f11a2efdd58a66ae216714b03d1bf Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/numpy_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/pynodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/pynodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..405746b3f836dddfa9c9bf2c6312729527e05605 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/pynodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/pyutils.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/pyutils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c921ae02dae39fb909705bf1c454d1f573261704 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/pyutils.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/rewriting.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/rewriting.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b070f1eb4fb71aea958f94e58a2309225ccd422e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/rewriting.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/__pycache__/scipy_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/scipy_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa749152061557d255a69d325dd7b2f0524255e7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/__pycache__/scipy_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/abstract_nodes.py b/phivenv/Lib/site-packages/sympy/codegen/abstract_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..ae0a8b3e996a7112edf2568c00138e11c3f3327d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/abstract_nodes.py @@ -0,0 +1,18 @@ +"""This module provides containers for python objects that are valid +printing targets but are not a subclass of SymPy's Printable. +""" + + +from sympy.core.containers import Tuple + + +class List(Tuple): + """Represents a (frozen) (Python) list (for code printing purposes).""" + def __eq__(self, other): + if isinstance(other, list): + return self == List(*other) + else: + return self.args == other + + def __hash__(self): + return super().__hash__() diff --git a/phivenv/Lib/site-packages/sympy/codegen/algorithms.py b/phivenv/Lib/site-packages/sympy/codegen/algorithms.py new file mode 100644 index 0000000000000000000000000000000000000000..f4890eb8c25e565095600e2713c0de270aa0cf97 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/algorithms.py @@ -0,0 +1,180 @@ +from sympy.core.containers import Tuple +from sympy.core.numbers import oo +from sympy.core.relational import (Gt, Lt) +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import Min, Max +from sympy.logic.boolalg import And +from sympy.codegen.ast import ( + Assignment, AddAugmentedAssignment, break_, CodeBlock, Declaration, FunctionDefinition, + Print, Return, Scope, While, Variable, Pointer, real +) +from sympy.codegen.cfunctions import isnan + +""" This module collects functions for constructing ASTs representing algorithms. """ + +def newtons_method(expr, wrt, atol=1e-12, delta=None, *, rtol=4e-16, debug=False, + itermax=None, counter=None, delta_fn=lambda e, x: -e/e.diff(x), + cse=False, handle_nan=None, + bounds=None): + """ Generates an AST for Newton-Raphson method (a root-finding algorithm). + + Explanation + =========== + + Returns an abstract syntax tree (AST) based on ``sympy.codegen.ast`` for Netwon's + method of root-finding. + + Parameters + ========== + + expr : expression + wrt : Symbol + With respect to, i.e. what is the variable. + atol : number or expression + Absolute tolerance (stopping criterion) + rtol : number or expression + Relative tolerance (stopping criterion) + delta : Symbol + Will be a ``Dummy`` if ``None``. + debug : bool + Whether to print convergence information during iterations + itermax : number or expr + Maximum number of iterations. + counter : Symbol + Will be a ``Dummy`` if ``None``. + delta_fn: Callable[[Expr, Symbol], Expr] + computes the step, default is newtons method. For e.g. Halley's method + use delta_fn=lambda e, x: -2*e*e.diff(x)/(2*e.diff(x)**2 - e*e.diff(x, 2)) + cse: bool + Perform common sub-expression elimination on delta expression + handle_nan: Token + How to handle occurrence of not-a-number (NaN). + bounds: Optional[tuple[Expr, Expr]] + Perform optimization within bounds + + Examples + ======== + + >>> from sympy import symbols, cos + >>> from sympy.codegen.ast import Assignment + >>> from sympy.codegen.algorithms import newtons_method + >>> x, dx, atol = symbols('x dx atol') + >>> expr = cos(x) - x**3 + >>> algo = newtons_method(expr, x, atol=atol, delta=dx) + >>> algo.has(Assignment(dx, -expr/expr.diff(x))) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Newton%27s_method + + """ + + if delta is None: + delta = Dummy() + Wrapper = Scope + name_d = 'delta' + else: + Wrapper = lambda x: x + name_d = delta.name + + delta_expr = delta_fn(expr, wrt) + if cse: + from sympy.simplify.cse_main import cse + cses, (red,) = cse([delta_expr.factor()]) + whl_bdy = [Assignment(dum, sub_e) for dum, sub_e in cses] + whl_bdy += [Assignment(delta, red)] + else: + whl_bdy = [Assignment(delta, delta_expr)] + if handle_nan is not None: + whl_bdy += [While(isnan(delta), CodeBlock(handle_nan, break_))] + whl_bdy += [AddAugmentedAssignment(wrt, delta)] + if bounds is not None: + whl_bdy += [Assignment(wrt, Min(Max(wrt, bounds[0]), bounds[1]))] + if debug: + prnt = Print([wrt, delta], r"{}=%12.5g {}=%12.5g\n".format(wrt.name, name_d)) + whl_bdy += [prnt] + req = Gt(Abs(delta), atol + rtol*Abs(wrt)) + declars = [Declaration(Variable(delta, type=real, value=oo))] + if itermax is not None: + counter = counter or Dummy(integer=True) + v_counter = Variable.deduced(counter, 0) + declars.append(Declaration(v_counter)) + whl_bdy.append(AddAugmentedAssignment(counter, 1)) + req = And(req, Lt(counter, itermax)) + whl = While(req, CodeBlock(*whl_bdy)) + blck = declars + if debug: + blck.append(Print([wrt], r"{}=%12.5g\n".format(wrt.name))) + blck += [whl] + return Wrapper(CodeBlock(*blck)) + + +def _symbol_of(arg): + if isinstance(arg, Declaration): + arg = arg.variable.symbol + elif isinstance(arg, Variable): + arg = arg.symbol + return arg + + +def newtons_method_function(expr, wrt, params=None, func_name="newton", attrs=Tuple(), *, delta=None, **kwargs): + """ Generates an AST for a function implementing the Newton-Raphson method. + + Parameters + ========== + + expr : expression + wrt : Symbol + With respect to, i.e. what is the variable + params : iterable of symbols + Symbols appearing in expr that are taken as constants during the iterations + (these will be accepted as parameters to the generated function). + func_name : str + Name of the generated function. + attrs : Tuple + Attribute instances passed as ``attrs`` to ``FunctionDefinition``. + \\*\\*kwargs : + Keyword arguments passed to :func:`sympy.codegen.algorithms.newtons_method`. + + Examples + ======== + + >>> from sympy import symbols, cos + >>> from sympy.codegen.algorithms import newtons_method_function + >>> from sympy.codegen.pyutils import render_as_module + >>> x = symbols('x') + >>> expr = cos(x) - x**3 + >>> func = newtons_method_function(expr, x) + >>> py_mod = render_as_module(func) # source code as string + >>> namespace = {} + >>> exec(py_mod, namespace, namespace) + >>> res = eval('newton(0.5)', namespace) + >>> abs(res - 0.865474033102) < 1e-12 + True + + See Also + ======== + + sympy.codegen.algorithms.newtons_method + + """ + if params is None: + params = (wrt,) + pointer_subs = {p.symbol: Symbol('(*%s)' % p.symbol.name) + for p in params if isinstance(p, Pointer)} + if delta is None: + delta = Symbol('d_' + wrt.name) + if expr.has(delta): + delta = None # will use Dummy + algo = newtons_method(expr, wrt, delta=delta, **kwargs).xreplace(pointer_subs) + if isinstance(algo, Scope): + algo = algo.body + not_in_params = expr.free_symbols.difference({_symbol_of(p) for p in params}) + if not_in_params: + raise ValueError("Missing symbols in params: %s" % ', '.join(map(str, not_in_params))) + declars = tuple(Variable(p, real) for p in params) + body = CodeBlock(algo, Return(wrt)) + return FunctionDefinition(real, func_name, declars, body, attrs=attrs) diff --git a/phivenv/Lib/site-packages/sympy/codegen/approximations.py b/phivenv/Lib/site-packages/sympy/codegen/approximations.py new file mode 100644 index 0000000000000000000000000000000000000000..c6486926938224c5052dc5adfac29807e82eb4d8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/approximations.py @@ -0,0 +1,187 @@ +import math +from sympy.sets.sets import Interval +from sympy.calculus.singularities import is_increasing, is_decreasing +from sympy.codegen.rewriting import Optimization +from sympy.core.function import UndefinedFunction + +""" +This module collects classes useful for approximate rewriting of expressions. +This can be beneficial when generating numeric code for which performance is +of greater importance than precision (e.g. for preconditioners used in iterative +methods). +""" + +class SumApprox(Optimization): + """ + Approximates sum by neglecting small terms. + + Explanation + =========== + + If terms are expressions which can be determined to be monotonic, then + bounds for those expressions are added. + + Parameters + ========== + + bounds : dict + Mapping expressions to length 2 tuple of bounds (low, high). + reltol : number + Threshold for when to ignore a term. Taken relative to the largest + lower bound among bounds. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.abc import x, y, z + >>> from sympy.codegen.rewriting import optimize + >>> from sympy.codegen.approximations import SumApprox + >>> bounds = {x: (-1, 1), y: (1000, 2000), z: (-10, 3)} + >>> sum_approx3 = SumApprox(bounds, reltol=1e-3) + >>> sum_approx2 = SumApprox(bounds, reltol=1e-2) + >>> sum_approx1 = SumApprox(bounds, reltol=1e-1) + >>> expr = 3*(x + y + exp(z)) + >>> optimize(expr, [sum_approx3]) + 3*(x + y + exp(z)) + >>> optimize(expr, [sum_approx2]) + 3*y + 3*exp(z) + >>> optimize(expr, [sum_approx1]) + 3*y + + """ + + def __init__(self, bounds, reltol, **kwargs): + super().__init__(**kwargs) + self.bounds = bounds + self.reltol = reltol + + def __call__(self, expr): + return expr.factor().replace(self.query, lambda arg: self.value(arg)) + + def query(self, expr): + return expr.is_Add + + def value(self, add): + for term in add.args: + if term.is_number or term in self.bounds or len(term.free_symbols) != 1: + continue + fs, = term.free_symbols + if fs not in self.bounds: + continue + intrvl = Interval(*self.bounds[fs]) + if is_increasing(term, intrvl, fs): + self.bounds[term] = ( + term.subs({fs: self.bounds[fs][0]}), + term.subs({fs: self.bounds[fs][1]}) + ) + elif is_decreasing(term, intrvl, fs): + self.bounds[term] = ( + term.subs({fs: self.bounds[fs][1]}), + term.subs({fs: self.bounds[fs][0]}) + ) + else: + return add + + if all(term.is_number or term in self.bounds for term in add.args): + bounds = [(term, term) if term.is_number else self.bounds[term] for term in add.args] + largest_abs_guarantee = 0 + for lo, hi in bounds: + if lo <= 0 <= hi: + continue + largest_abs_guarantee = max(largest_abs_guarantee, + min(abs(lo), abs(hi))) + new_terms = [] + for term, (lo, hi) in zip(add.args, bounds): + if max(abs(lo), abs(hi)) >= largest_abs_guarantee*self.reltol: + new_terms.append(term) + return add.func(*new_terms) + else: + return add + + +class SeriesApprox(Optimization): + """ Approximates functions by expanding them as a series. + + Parameters + ========== + + bounds : dict + Mapping expressions to length 2 tuple of bounds (low, high). + reltol : number + Threshold for when to ignore a term. Taken relative to the largest + lower bound among bounds. + max_order : int + Largest order to include in series expansion + n_point_checks : int (even) + The validity of an expansion (with respect to reltol) is checked at + discrete points (linearly spaced over the bounds of the variable). The + number of points used in this numerical check is given by this number. + + Examples + ======== + + >>> from sympy import sin, pi + >>> from sympy.abc import x, y + >>> from sympy.codegen.rewriting import optimize + >>> from sympy.codegen.approximations import SeriesApprox + >>> bounds = {x: (-.1, .1), y: (pi-1, pi+1)} + >>> series_approx2 = SeriesApprox(bounds, reltol=1e-2) + >>> series_approx3 = SeriesApprox(bounds, reltol=1e-3) + >>> series_approx8 = SeriesApprox(bounds, reltol=1e-8) + >>> expr = sin(x)*sin(y) + >>> optimize(expr, [series_approx2]) + x*(-y + (y - pi)**3/6 + pi) + >>> optimize(expr, [series_approx3]) + (-x**3/6 + x)*sin(y) + >>> optimize(expr, [series_approx8]) + sin(x)*sin(y) + + """ + def __init__(self, bounds, reltol, max_order=4, n_point_checks=4, **kwargs): + super().__init__(**kwargs) + self.bounds = bounds + self.reltol = reltol + self.max_order = max_order + if n_point_checks % 2 == 1: + raise ValueError("Checking the solution at expansion point is not helpful") + self.n_point_checks = n_point_checks + self._prec = math.ceil(-math.log10(self.reltol)) + + def __call__(self, expr): + return expr.factor().replace(self.query, lambda arg: self.value(arg)) + + def query(self, expr): + return (expr.is_Function and not isinstance(expr, UndefinedFunction) + and len(expr.args) == 1) + + def value(self, fexpr): + free_symbols = fexpr.free_symbols + if len(free_symbols) != 1: + return fexpr + symb, = free_symbols + if symb not in self.bounds: + return fexpr + lo, hi = self.bounds[symb] + x0 = (lo + hi)/2 + cheapest = None + for n in range(self.max_order+1, 0, -1): + fseri = fexpr.series(symb, x0=x0, n=n).removeO() + n_ok = True + for idx in range(self.n_point_checks): + x = lo + idx*(hi - lo)/(self.n_point_checks - 1) + val = fseri.xreplace({symb: x}) + ref = fexpr.xreplace({symb: x}) + if abs((1 - val/ref).evalf(self._prec)) > self.reltol: + n_ok = False + break + + if n_ok: + cheapest = fseri + else: + break + + if cheapest is None: + return fexpr + else: + return cheapest diff --git a/phivenv/Lib/site-packages/sympy/codegen/ast.py b/phivenv/Lib/site-packages/sympy/codegen/ast.py new file mode 100644 index 0000000000000000000000000000000000000000..dd774ca87c5c9d4b55c8ea7a3b68837035b0d06d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/ast.py @@ -0,0 +1,1906 @@ +""" +Types used to represent a full function/module as an Abstract Syntax Tree. + +Most types are small, and are merely used as tokens in the AST. A tree diagram +has been included below to illustrate the relationships between the AST types. + + +AST Type Tree +------------- +:: + + *Basic* + | + | + CodegenAST + | + |--->AssignmentBase + | |--->Assignment + | |--->AugmentedAssignment + | |--->AddAugmentedAssignment + | |--->SubAugmentedAssignment + | |--->MulAugmentedAssignment + | |--->DivAugmentedAssignment + | |--->ModAugmentedAssignment + | + |--->CodeBlock + | + | + |--->Token + |--->Attribute + |--->For + |--->String + | |--->QuotedString + | |--->Comment + |--->Type + | |--->IntBaseType + | | |--->_SizedIntType + | | |--->SignedIntType + | | |--->UnsignedIntType + | |--->FloatBaseType + | |--->FloatType + | |--->ComplexBaseType + | |--->ComplexType + |--->Node + | |--->Variable + | | |---> Pointer + | |--->FunctionPrototype + | |--->FunctionDefinition + |--->Element + |--->Declaration + |--->While + |--->Scope + |--->Stream + |--->Print + |--->FunctionCall + |--->BreakToken + |--->ContinueToken + |--->NoneToken + |--->Return + + +Predefined types +---------------- + +A number of ``Type`` instances are provided in the ``sympy.codegen.ast`` module +for convenience. Perhaps the two most common ones for code-generation (of numeric +codes) are ``float32`` and ``float64`` (known as single and double precision respectively). +There are also precision generic versions of Types (for which the codeprinters selects the +underlying data type at time of printing): ``real``, ``integer``, ``complex_``, ``bool_``. + +The other ``Type`` instances defined are: + +- ``intc``: Integer type used by C's "int". +- ``intp``: Integer type used by C's "unsigned". +- ``int8``, ``int16``, ``int32``, ``int64``: n-bit integers. +- ``uint8``, ``uint16``, ``uint32``, ``uint64``: n-bit unsigned integers. +- ``float80``: known as "extended precision" on modern x86/amd64 hardware. +- ``complex64``: Complex number represented by two ``float32`` numbers +- ``complex128``: Complex number represented by two ``float64`` numbers + +Using the nodes +--------------- + +It is possible to construct simple algorithms using the AST nodes. Let's construct a loop applying +Newton's method:: + + >>> from sympy import symbols, cos + >>> from sympy.codegen.ast import While, Assignment, aug_assign, Print, QuotedString + >>> t, dx, x = symbols('tol delta val') + >>> expr = cos(x) - x**3 + >>> whl = While(abs(dx) > t, [ + ... Assignment(dx, -expr/expr.diff(x)), + ... aug_assign(x, '+', dx), + ... Print([x]) + ... ]) + >>> from sympy import pycode + >>> py_str = pycode(whl) + >>> print(py_str) + while (abs(delta) > tol): + delta = (val**3 - math.cos(val))/(-3*val**2 - math.sin(val)) + val += delta + print(val) + >>> import math + >>> tol, val, delta = 1e-5, 0.5, float('inf') + >>> exec(py_str) + 1.1121416371 + 0.909672693737 + 0.867263818209 + 0.865477135298 + 0.865474033111 + >>> print('%3.1g' % (math.cos(val) - val**3)) + -3e-11 + +If we want to generate Fortran code for the same while loop we simple call ``fcode``:: + + >>> from sympy import fcode + >>> print(fcode(whl, standard=2003, source_format='free')) + do while (abs(delta) > tol) + delta = (val**3 - cos(val))/(-3*val**2 - sin(val)) + val = val + delta + print *, val + end do + +There is a function constructing a loop (or a complete function) like this in +:mod:`sympy.codegen.algorithms`. + +""" + +from __future__ import annotations +from typing import Any + +from collections import defaultdict + +from sympy.core.relational import (Ge, Gt, Le, Lt) +from sympy.core import Symbol, Tuple, Dummy +from sympy.core.basic import Basic +from sympy.core.expr import Expr, Atom +from sympy.core.numbers import Float, Integer, oo +from sympy.core.sympify import _sympify, sympify, SympifyError +from sympy.utilities.iterables import (iterable, topological_sort, + numbered_symbols, filter_symbols) + + +def _mk_Tuple(args): + """ + Create a SymPy Tuple object from an iterable, converting Python strings to + AST strings. + + Parameters + ========== + + args: iterable + Arguments to :class:`sympy.Tuple`. + + Returns + ======= + + sympy.Tuple + """ + args = [String(arg) if isinstance(arg, str) else arg for arg in args] + return Tuple(*args) + + +class CodegenAST(Basic): + __slots__ = () + + +class Token(CodegenAST): + """ Base class for the AST types. + + Explanation + =========== + + Defining fields are set in ``_fields``. Attributes (defined in _fields) + are only allowed to contain instances of Basic (unless atomic, see + ``String``). The arguments to ``__new__()`` correspond to the attributes in + the order defined in ``_fields`. The ``defaults`` class attribute is a + dictionary mapping attribute names to their default values. + + Subclasses should not need to override the ``__new__()`` method. They may + define a class or static method named ``_construct_`` for each + attribute to process the value passed to ``__new__()``. Attributes listed + in the class attribute ``not_in_args`` are not passed to :class:`~.Basic`. + """ + + __slots__: tuple[str, ...] = () + _fields = __slots__ + defaults: dict[str, Any] = {} + not_in_args: list[str] = [] + indented_args = ['body'] + + @property + def is_Atom(self): + return len(self._fields) == 0 + + @classmethod + def _get_constructor(cls, attr): + """ Get the constructor function for an attribute by name. """ + return getattr(cls, '_construct_%s' % attr, lambda x: x) + + @classmethod + def _construct(cls, attr, arg): + """ Construct an attribute value from argument passed to ``__new__()``. """ + # arg may be ``NoneToken()``, so comparison is done using == instead of ``is`` operator + if arg == None: + return cls.defaults.get(attr, none) + else: + if isinstance(arg, Dummy): # SymPy's replace uses Dummy instances + return arg + else: + return cls._get_constructor(attr)(arg) + + def __new__(cls, *args, **kwargs): + # Pass through existing instances when given as sole argument + if len(args) == 1 and not kwargs and isinstance(args[0], cls): + return args[0] + + if len(args) > len(cls._fields): + raise ValueError("Too many arguments (%d), expected at most %d" % (len(args), len(cls._fields))) + + attrvals = [] + + # Process positional arguments + for attrname, argval in zip(cls._fields, args): + if attrname in kwargs: + raise TypeError('Got multiple values for attribute %r' % attrname) + + attrvals.append(cls._construct(attrname, argval)) + + # Process keyword arguments + for attrname in cls._fields[len(args):]: + if attrname in kwargs: + argval = kwargs.pop(attrname) + + elif attrname in cls.defaults: + argval = cls.defaults[attrname] + + else: + raise TypeError('No value for %r given and attribute has no default' % attrname) + + attrvals.append(cls._construct(attrname, argval)) + + if kwargs: + raise ValueError("Unknown keyword arguments: %s" % ' '.join(kwargs)) + + # Parent constructor + basic_args = [ + val for attr, val in zip(cls._fields, attrvals) + if attr not in cls.not_in_args + ] + obj = CodegenAST.__new__(cls, *basic_args) + + # Set attributes + for attr, arg in zip(cls._fields, attrvals): + setattr(obj, attr, arg) + + return obj + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + for attr in self._fields: + if getattr(self, attr) != getattr(other, attr): + return False + return True + + def _hashable_content(self): + return tuple([getattr(self, attr) for attr in self._fields]) + + def __hash__(self): + return super().__hash__() + + def _joiner(self, k, indent_level): + return (',\n' + ' '*indent_level) if k in self.indented_args else ', ' + + def _indented(self, printer, k, v, *args, **kwargs): + il = printer._context['indent_level'] + def _print(arg): + if isinstance(arg, Token): + return printer._print(arg, *args, joiner=self._joiner(k, il), **kwargs) + else: + return printer._print(arg, *args, **kwargs) + + if isinstance(v, Tuple): + joined = self._joiner(k, il).join([_print(arg) for arg in v.args]) + if k in self.indented_args: + return '(\n' + ' '*il + joined + ',\n' + ' '*(il - 4) + ')' + else: + return ('({0},)' if len(v.args) == 1 else '({0})').format(joined) + else: + return _print(v) + + def _sympyrepr(self, printer, *args, joiner=', ', **kwargs): + from sympy.printing.printer import printer_context + exclude = kwargs.get('exclude', ()) + values = [getattr(self, k) for k in self._fields] + indent_level = printer._context.get('indent_level', 0) + + arg_reprs = [] + + for i, (attr, value) in enumerate(zip(self._fields, values)): + if attr in exclude: + continue + + # Skip attributes which have the default value + if attr in self.defaults and value == self.defaults[attr]: + continue + + ilvl = indent_level + 4 if attr in self.indented_args else 0 + with printer_context(printer, indent_level=ilvl): + indented = self._indented(printer, attr, value, *args, **kwargs) + arg_reprs.append(('{1}' if i == 0 else '{0}={1}').format(attr, indented.lstrip())) + + return "{}({})".format(self.__class__.__name__, joiner.join(arg_reprs)) + + _sympystr = _sympyrepr + + def __repr__(self): # sympy.core.Basic.__repr__ uses sstr + from sympy.printing import srepr + return srepr(self) + + def kwargs(self, exclude=(), apply=None): + """ Get instance's attributes as dict of keyword arguments. + + Parameters + ========== + + exclude : collection of str + Collection of keywords to exclude. + + apply : callable, optional + Function to apply to all values. + """ + kwargs = {k: getattr(self, k) for k in self._fields if k not in exclude} + if apply is not None: + return {k: apply(v) for k, v in kwargs.items()} + else: + return kwargs + +class BreakToken(Token): + """ Represents 'break' in C/Python ('exit' in Fortran). + + Use the premade instance ``break_`` or instantiate manually. + + Examples + ======== + + >>> from sympy import ccode, fcode + >>> from sympy.codegen.ast import break_ + >>> ccode(break_) + 'break' + >>> fcode(break_, source_format='free') + 'exit' + """ + +break_ = BreakToken() + + +class ContinueToken(Token): + """ Represents 'continue' in C/Python ('cycle' in Fortran) + + Use the premade instance ``continue_`` or instantiate manually. + + Examples + ======== + + >>> from sympy import ccode, fcode + >>> from sympy.codegen.ast import continue_ + >>> ccode(continue_) + 'continue' + >>> fcode(continue_, source_format='free') + 'cycle' + """ + +continue_ = ContinueToken() + +class NoneToken(Token): + """ The AST equivalence of Python's NoneType + + The corresponding instance of Python's ``None`` is ``none``. + + Examples + ======== + + >>> from sympy.codegen.ast import none, Variable + >>> from sympy import pycode + >>> print(pycode(Variable('x').as_Declaration(value=none))) + x = None + + """ + def __eq__(self, other): + return other is None or isinstance(other, NoneToken) + + def _hashable_content(self): + return () + + def __hash__(self): + return super().__hash__() + + +none = NoneToken() + + +class AssignmentBase(CodegenAST): + """ Abstract base class for Assignment and AugmentedAssignment. + + Attributes: + =========== + + op : str + Symbol for assignment operator, e.g. "=", "+=", etc. + """ + + def __new__(cls, lhs, rhs): + lhs = _sympify(lhs) + rhs = _sympify(rhs) + + cls._check_args(lhs, rhs) + + return super().__new__(cls, lhs, rhs) + + @property + def lhs(self): + return self.args[0] + + @property + def rhs(self): + return self.args[1] + + @classmethod + def _check_args(cls, lhs, rhs): + """ Check arguments to __new__ and raise exception if any problems found. + + Derived classes may wish to override this. + """ + from sympy.matrices.expressions.matexpr import ( + MatrixElement, MatrixSymbol) + from sympy.tensor.indexed import Indexed + from sympy.tensor.array.expressions import ArrayElement + + # Tuple of things that can be on the lhs of an assignment + assignable = (Symbol, MatrixSymbol, MatrixElement, Indexed, Element, Variable, + ArrayElement) + if not isinstance(lhs, assignable): + raise TypeError("Cannot assign to lhs of type %s." % type(lhs)) + + # Indexed types implement shape, but don't define it until later. This + # causes issues in assignment validation. For now, matrices are defined + # as anything with a shape that is not an Indexed + lhs_is_mat = hasattr(lhs, 'shape') and not isinstance(lhs, Indexed) + rhs_is_mat = hasattr(rhs, 'shape') and not isinstance(rhs, Indexed) + + # If lhs and rhs have same structure, then this assignment is ok + if lhs_is_mat: + if not rhs_is_mat: + raise ValueError("Cannot assign a scalar to a matrix.") + elif lhs.shape != rhs.shape: + raise ValueError("Dimensions of lhs and rhs do not align.") + elif rhs_is_mat and not lhs_is_mat: + raise ValueError("Cannot assign a matrix to a scalar.") + + +class Assignment(AssignmentBase): + """ + Represents variable assignment for code generation. + + Parameters + ========== + + lhs : Expr + SymPy object representing the lhs of the expression. These should be + singular objects, such as one would use in writing code. Notable types + include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that + subclass these types are also supported. + + rhs : Expr + SymPy object representing the rhs of the expression. This can be any + type, provided its shape corresponds to that of the lhs. For example, + a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as + the dimensions will not align. + + Examples + ======== + + >>> from sympy import symbols, MatrixSymbol, Matrix + >>> from sympy.codegen.ast import Assignment + >>> x, y, z = symbols('x, y, z') + >>> Assignment(x, y) + Assignment(x, y) + >>> Assignment(x, 0) + Assignment(x, 0) + >>> A = MatrixSymbol('A', 1, 3) + >>> mat = Matrix([x, y, z]).T + >>> Assignment(A, mat) + Assignment(A, Matrix([[x, y, z]])) + >>> Assignment(A[0, 1], x) + Assignment(A[0, 1], x) + """ + + op = ':=' + + +class AugmentedAssignment(AssignmentBase): + """ + Base class for augmented assignments. + + Attributes: + =========== + + binop : str + Symbol for binary operation being applied in the assignment, such as "+", + "*", etc. + """ + binop: str | None + + @property + def op(self): + return self.binop + '=' + + +class AddAugmentedAssignment(AugmentedAssignment): + binop = '+' + + +class SubAugmentedAssignment(AugmentedAssignment): + binop = '-' + + +class MulAugmentedAssignment(AugmentedAssignment): + binop = '*' + + +class DivAugmentedAssignment(AugmentedAssignment): + binop = '/' + + +class ModAugmentedAssignment(AugmentedAssignment): + binop = '%' + + +# Mapping from binary op strings to AugmentedAssignment subclasses +augassign_classes = { + cls.binop: cls for cls in [ + AddAugmentedAssignment, SubAugmentedAssignment, MulAugmentedAssignment, + DivAugmentedAssignment, ModAugmentedAssignment + ] +} + + +def aug_assign(lhs, op, rhs): + """ + Create 'lhs op= rhs'. + + Explanation + =========== + + Represents augmented variable assignment for code generation. This is a + convenience function. You can also use the AugmentedAssignment classes + directly, like AddAugmentedAssignment(x, y). + + Parameters + ========== + + lhs : Expr + SymPy object representing the lhs of the expression. These should be + singular objects, such as one would use in writing code. Notable types + include Symbol, MatrixSymbol, MatrixElement, and Indexed. Types that + subclass these types are also supported. + + op : str + Operator (+, -, /, \\*, %). + + rhs : Expr + SymPy object representing the rhs of the expression. This can be any + type, provided its shape corresponds to that of the lhs. For example, + a Matrix type can be assigned to MatrixSymbol, but not to Symbol, as + the dimensions will not align. + + Examples + ======== + + >>> from sympy import symbols + >>> from sympy.codegen.ast import aug_assign + >>> x, y = symbols('x, y') + >>> aug_assign(x, '+', y) + AddAugmentedAssignment(x, y) + """ + if op not in augassign_classes: + raise ValueError("Unrecognized operator %s" % op) + return augassign_classes[op](lhs, rhs) + + +class CodeBlock(CodegenAST): + """ + Represents a block of code. + + Explanation + =========== + + For now only assignments are supported. This restriction will be lifted in + the future. + + Useful attributes on this object are: + + ``left_hand_sides``: + Tuple of left-hand sides of assignments, in order. + ``left_hand_sides``: + Tuple of right-hand sides of assignments, in order. + ``free_symbols``: Free symbols of the expressions in the right-hand sides + which do not appear in the left-hand side of an assignment. + + Useful methods on this object are: + + ``topological_sort``: + Class method. Return a CodeBlock with assignments + sorted so that variables are assigned before they + are used. + ``cse``: + Return a new CodeBlock with common subexpressions eliminated and + pulled out as assignments. + + Examples + ======== + + >>> from sympy import symbols, ccode + >>> from sympy.codegen.ast import CodeBlock, Assignment + >>> x, y = symbols('x y') + >>> c = CodeBlock(Assignment(x, 1), Assignment(y, x + 1)) + >>> print(ccode(c)) + x = 1; + y = x + 1; + + """ + def __new__(cls, *args): + left_hand_sides = [] + right_hand_sides = [] + for i in args: + if isinstance(i, Assignment): + lhs, rhs = i.args + left_hand_sides.append(lhs) + right_hand_sides.append(rhs) + + obj = CodegenAST.__new__(cls, *args) + + obj.left_hand_sides = Tuple(*left_hand_sides) + obj.right_hand_sides = Tuple(*right_hand_sides) + return obj + + def __iter__(self): + return iter(self.args) + + def _sympyrepr(self, printer, *args, **kwargs): + il = printer._context.get('indent_level', 0) + joiner = ',\n' + ' '*il + joined = joiner.join(map(printer._print, self.args)) + return ('{}(\n'.format(' '*(il-4) + self.__class__.__name__,) + + ' '*il + joined + '\n' + ' '*(il - 4) + ')') + + _sympystr = _sympyrepr + + @property + def free_symbols(self): + return super().free_symbols - set(self.left_hand_sides) + + @classmethod + def topological_sort(cls, assignments): + """ + Return a CodeBlock with topologically sorted assignments so that + variables are assigned before they are used. + + Examples + ======== + + The existing order of assignments is preserved as much as possible. + + This function assumes that variables are assigned to only once. + + This is a class constructor so that the default constructor for + CodeBlock can error when variables are used before they are assigned. + + >>> from sympy import symbols + >>> from sympy.codegen.ast import CodeBlock, Assignment + >>> x, y, z = symbols('x y z') + + >>> assignments = [ + ... Assignment(x, y + z), + ... Assignment(y, z + 1), + ... Assignment(z, 2), + ... ] + >>> CodeBlock.topological_sort(assignments) + CodeBlock( + Assignment(z, 2), + Assignment(y, z + 1), + Assignment(x, y + z) + ) + + """ + + if not all(isinstance(i, Assignment) for i in assignments): + # Will support more things later + raise NotImplementedError("CodeBlock.topological_sort only supports Assignments") + + if any(isinstance(i, AugmentedAssignment) for i in assignments): + raise NotImplementedError("CodeBlock.topological_sort does not yet work with AugmentedAssignments") + + # Create a graph where the nodes are assignments and there is a directed edge + # between nodes that use a variable and nodes that assign that + # variable, like + + # [(x := 1, y := x + 1), (x := 1, z := y + z), (y := x + 1, z := y + z)] + + # If we then topologically sort these nodes, they will be in + # assignment order, like + + # x := 1 + # y := x + 1 + # z := y + z + + # A = The nodes + # + # enumerate keeps nodes in the same order they are already in if + # possible. It will also allow us to handle duplicate assignments to + # the same variable when those are implemented. + A = list(enumerate(assignments)) + + # var_map = {variable: [nodes for which this variable is assigned to]} + # like {x: [(1, x := y + z), (4, x := 2 * w)], ...} + var_map = defaultdict(list) + for node in A: + i, a = node + var_map[a.lhs].append(node) + + # E = Edges in the graph + E = [] + for dst_node in A: + i, a = dst_node + for s in a.rhs.free_symbols: + for src_node in var_map[s]: + E.append((src_node, dst_node)) + + ordered_assignments = topological_sort([A, E]) + + # De-enumerate the result + return cls(*[a for i, a in ordered_assignments]) + + def cse(self, symbols=None, optimizations=None, postprocess=None, + order='canonical'): + """ + Return a new code block with common subexpressions eliminated. + + Explanation + =========== + + See the docstring of :func:`sympy.simplify.cse_main.cse` for more + information. + + Examples + ======== + + >>> from sympy import symbols, sin + >>> from sympy.codegen.ast import CodeBlock, Assignment + >>> x, y, z = symbols('x y z') + + >>> c = CodeBlock( + ... Assignment(x, 1), + ... Assignment(y, sin(x) + 1), + ... Assignment(z, sin(x) - 1), + ... ) + ... + >>> c.cse() + CodeBlock( + Assignment(x, 1), + Assignment(x0, sin(x)), + Assignment(y, x0 + 1), + Assignment(z, x0 - 1) + ) + + """ + from sympy.simplify.cse_main import cse + + # Check that the CodeBlock only contains assignments to unique variables + if not all(isinstance(i, Assignment) for i in self.args): + # Will support more things later + raise NotImplementedError("CodeBlock.cse only supports Assignments") + + if any(isinstance(i, AugmentedAssignment) for i in self.args): + raise NotImplementedError("CodeBlock.cse does not yet work with AugmentedAssignments") + + for i, lhs in enumerate(self.left_hand_sides): + if lhs in self.left_hand_sides[:i]: + raise NotImplementedError("Duplicate assignments to the same " + "variable are not yet supported (%s)" % lhs) + + # Ensure new symbols for subexpressions do not conflict with existing + existing_symbols = self.atoms(Symbol) + if symbols is None: + symbols = numbered_symbols() + symbols = filter_symbols(symbols, existing_symbols) + + replacements, reduced_exprs = cse(list(self.right_hand_sides), + symbols=symbols, optimizations=optimizations, postprocess=postprocess, + order=order) + + new_block = [Assignment(var, expr) for var, expr in + zip(self.left_hand_sides, reduced_exprs)] + new_assignments = [Assignment(var, expr) for var, expr in replacements] + return self.topological_sort(new_assignments + new_block) + + +class For(Token): + """Represents a 'for-loop' in the code. + + Expressions are of the form: + "for target in iter: + body..." + + Parameters + ========== + + target : symbol + iter : iterable + body : CodeBlock or iterable +! When passed an iterable it is used to instantiate a CodeBlock. + + Examples + ======== + + >>> from sympy import symbols, Range + >>> from sympy.codegen.ast import aug_assign, For + >>> x, i, j, k = symbols('x i j k') + >>> for_i = For(i, Range(10), [aug_assign(x, '+', i*j*k)]) + >>> for_i # doctest: -NORMALIZE_WHITESPACE + For(i, iterable=Range(0, 10, 1), body=CodeBlock( + AddAugmentedAssignment(x, i*j*k) + )) + >>> for_ji = For(j, Range(7), [for_i]) + >>> for_ji # doctest: -NORMALIZE_WHITESPACE + For(j, iterable=Range(0, 7, 1), body=CodeBlock( + For(i, iterable=Range(0, 10, 1), body=CodeBlock( + AddAugmentedAssignment(x, i*j*k) + )) + )) + >>> for_kji =For(k, Range(5), [for_ji]) + >>> for_kji # doctest: -NORMALIZE_WHITESPACE + For(k, iterable=Range(0, 5, 1), body=CodeBlock( + For(j, iterable=Range(0, 7, 1), body=CodeBlock( + For(i, iterable=Range(0, 10, 1), body=CodeBlock( + AddAugmentedAssignment(x, i*j*k) + )) + )) + )) + """ + __slots__ = _fields = ('target', 'iterable', 'body') + _construct_target = staticmethod(_sympify) + + @classmethod + def _construct_body(cls, itr): + if isinstance(itr, CodeBlock): + return itr + else: + return CodeBlock(*itr) + + @classmethod + def _construct_iterable(cls, itr): + if not iterable(itr): + raise TypeError("iterable must be an iterable") + if isinstance(itr, list): # _sympify errors on lists because they are mutable + itr = tuple(itr) + return _sympify(itr) + + +class String(Atom, Token): + """ SymPy object representing a string. + + Atomic object which is not an expression (as opposed to Symbol). + + Parameters + ========== + + text : str + + Examples + ======== + + >>> from sympy.codegen.ast import String + >>> f = String('foo') + >>> f + foo + >>> str(f) + 'foo' + >>> f.text + 'foo' + >>> print(repr(f)) + String('foo') + + """ + __slots__ = _fields = ('text',) + not_in_args = ['text'] + is_Atom = True + + @classmethod + def _construct_text(cls, text): + if not isinstance(text, str): + raise TypeError("Argument text is not a string type.") + return text + + def _sympystr(self, printer, *args, **kwargs): + return self.text + + def kwargs(self, exclude = (), apply = None): + return {} + + #to be removed when Atom is given a suitable func + @property + def func(self): + return lambda: self + + def _latex(self, printer): + from sympy.printing.latex import latex_escape + return r'\texttt{{"{}"}}'.format(latex_escape(self.text)) + +class QuotedString(String): + """ Represents a string which should be printed with quotes. """ + +class Comment(String): + """ Represents a comment. """ + +class Node(Token): + """ Subclass of Token, carrying the attribute 'attrs' (Tuple) + + Examples + ======== + + >>> from sympy.codegen.ast import Node, value_const, pointer_const + >>> n1 = Node([value_const]) + >>> n1.attr_params('value_const') # get the parameters of attribute (by name) + () + >>> from sympy.codegen.fnodes import dimension + >>> n2 = Node([value_const, dimension(5, 3)]) + >>> n2.attr_params(value_const) # get the parameters of attribute (by Attribute instance) + () + >>> n2.attr_params('dimension') # get the parameters of attribute (by name) + (5, 3) + >>> n2.attr_params(pointer_const) is None + True + + """ + + __slots__: tuple[str, ...] = ('attrs',) + _fields = __slots__ + + defaults: dict[str, Any] = {'attrs': Tuple()} + + _construct_attrs = staticmethod(_mk_Tuple) + + def attr_params(self, looking_for): + """ Returns the parameters of the Attribute with name ``looking_for`` in self.attrs """ + for attr in self.attrs: + if str(attr.name) == str(looking_for): + return attr.parameters + + +class Type(Token): + """ Represents a type. + + Explanation + =========== + + The naming is a super-set of NumPy naming. Type has a classmethod + ``from_expr`` which offer type deduction. It also has a method + ``cast_check`` which casts the argument to its type, possibly raising an + exception if rounding error is not within tolerances, or if the value is not + representable by the underlying data type (e.g. unsigned integers). + + Parameters + ========== + + name : str + Name of the type, e.g. ``object``, ``int16``, ``float16`` (where the latter two + would use the ``Type`` sub-classes ``IntType`` and ``FloatType`` respectively). + If a ``Type`` instance is given, the said instance is returned. + + Examples + ======== + + >>> from sympy.codegen.ast import Type + >>> t = Type.from_expr(42) + >>> t + integer + >>> print(repr(t)) + IntBaseType(String('integer')) + >>> from sympy.codegen.ast import uint8 + >>> uint8.cast_check(-1) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Minimum value for data type bigger than new value. + >>> from sympy.codegen.ast import float32 + >>> v6 = 0.123456 + >>> float32.cast_check(v6) + 0.123456 + >>> v10 = 12345.67894 + >>> float32.cast_check(v10) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Casting gives a significantly different value. + >>> boost_mp50 = Type('boost::multiprecision::cpp_dec_float_50') + >>> from sympy import cxxcode + >>> from sympy.codegen.ast import Declaration, Variable + >>> cxxcode(Declaration(Variable('x', type=boost_mp50))) + 'boost::multiprecision::cpp_dec_float_50 x' + + References + ========== + + .. [1] https://numpy.org/doc/stable/user/basics.types.html + + """ + __slots__: tuple[str, ...] = ('name',) + _fields = __slots__ + + _construct_name = String + + def _sympystr(self, printer, *args, **kwargs): + return str(self.name) + + @classmethod + def from_expr(cls, expr): + """ Deduces type from an expression or a ``Symbol``. + + Parameters + ========== + + expr : number or SymPy object + The type will be deduced from type or properties. + + Examples + ======== + + >>> from sympy.codegen.ast import Type, integer, complex_ + >>> Type.from_expr(2) == integer + True + >>> from sympy import Symbol + >>> Type.from_expr(Symbol('z', complex=True)) == complex_ + True + >>> Type.from_expr(sum) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Could not deduce type from expr. + + Raises + ====== + + ValueError when type deduction fails. + + """ + if isinstance(expr, (float, Float)): + return real + if isinstance(expr, (int, Integer)) or getattr(expr, 'is_integer', False): + return integer + if getattr(expr, 'is_real', False): + return real + if isinstance(expr, complex) or getattr(expr, 'is_complex', False): + return complex_ + if isinstance(expr, bool) or getattr(expr, 'is_Relational', False): + return bool_ + else: + raise ValueError("Could not deduce type from expr.") + + def _check(self, value): + pass + + def cast_check(self, value, rtol=None, atol=0, precision_targets=None): + """ Casts a value to the data type of the instance. + + Parameters + ========== + + value : number + rtol : floating point number + Relative tolerance. (will be deduced if not given). + atol : floating point number + Absolute tolerance (in addition to ``rtol``). + type_aliases : dict + Maps substitutions for Type, e.g. {integer: int64, real: float32} + + Examples + ======== + + >>> from sympy.codegen.ast import integer, float32, int8 + >>> integer.cast_check(3.0) == 3 + True + >>> float32.cast_check(1e-40) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Minimum value for data type bigger than new value. + >>> int8.cast_check(256) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Maximum value for data type smaller than new value. + >>> v10 = 12345.67894 + >>> float32.cast_check(v10) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Casting gives a significantly different value. + >>> from sympy.codegen.ast import float64 + >>> float64.cast_check(v10) + 12345.67894 + >>> from sympy import Float + >>> v18 = Float('0.123456789012345646') + >>> float64.cast_check(v18) + Traceback (most recent call last): + ... + ValueError: Casting gives a significantly different value. + >>> from sympy.codegen.ast import float80 + >>> float80.cast_check(v18) + 0.123456789012345649 + + """ + val = sympify(value) + + ten = Integer(10) + exp10 = getattr(self, 'decimal_dig', None) + + if rtol is None: + rtol = 1e-15 if exp10 is None else 2.0*ten**(-exp10) + + def tol(num): + return atol + rtol*abs(num) + + new_val = self.cast_nocheck(value) + self._check(new_val) + + delta = new_val - val + if abs(delta) > tol(val): # rounding, e.g. int(3.5) != 3.5 + raise ValueError("Casting gives a significantly different value.") + + return new_val + + def _latex(self, printer): + from sympy.printing.latex import latex_escape + type_name = latex_escape(self.__class__.__name__) + name = latex_escape(self.name.text) + return r"\text{{{}}}\left(\texttt{{{}}}\right)".format(type_name, name) + + +class IntBaseType(Type): + """ Integer base type, contains no size information. """ + __slots__ = () + cast_nocheck = lambda self, i: Integer(int(i)) + + +class _SizedIntType(IntBaseType): + __slots__ = ('nbits',) + _fields = Type._fields + __slots__ + + _construct_nbits = Integer + + def _check(self, value): + if value < self.min: + raise ValueError("Value is too small: %d < %d" % (value, self.min)) + if value > self.max: + raise ValueError("Value is too big: %d > %d" % (value, self.max)) + + +class SignedIntType(_SizedIntType): + """ Represents a signed integer type. """ + __slots__ = () + @property + def min(self): + return -2**(self.nbits-1) + + @property + def max(self): + return 2**(self.nbits-1) - 1 + + +class UnsignedIntType(_SizedIntType): + """ Represents an unsigned integer type. """ + __slots__ = () + @property + def min(self): + return 0 + + @property + def max(self): + return 2**self.nbits - 1 + +two = Integer(2) + +class FloatBaseType(Type): + """ Represents a floating point number type. """ + __slots__ = () + cast_nocheck = Float + +class FloatType(FloatBaseType): + """ Represents a floating point type with fixed bit width. + + Base 2 & one sign bit is assumed. + + Parameters + ========== + + name : str + Name of the type. + nbits : integer + Number of bits used (storage). + nmant : integer + Number of bits used to represent the mantissa. + nexp : integer + Number of bits used to represent the mantissa. + + Examples + ======== + + >>> from sympy import S + >>> from sympy.codegen.ast import FloatType + >>> half_precision = FloatType('f16', nbits=16, nmant=10, nexp=5) + >>> half_precision.max + 65504 + >>> half_precision.tiny == S(2)**-14 + True + >>> half_precision.eps == S(2)**-10 + True + >>> half_precision.dig == 3 + True + >>> half_precision.decimal_dig == 5 + True + >>> half_precision.cast_check(1.0) + 1.0 + >>> half_precision.cast_check(1e5) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: Maximum value for data type smaller than new value. + """ + + __slots__ = ('nbits', 'nmant', 'nexp',) + _fields = Type._fields + __slots__ + + _construct_nbits = _construct_nmant = _construct_nexp = Integer + + + @property + def max_exponent(self): + """ The largest positive number n, such that 2**(n - 1) is a representable finite value. """ + # cf. C++'s ``std::numeric_limits::max_exponent`` + return two**(self.nexp - 1) + + @property + def min_exponent(self): + """ The lowest negative number n, such that 2**(n - 1) is a valid normalized number. """ + # cf. C++'s ``std::numeric_limits::min_exponent`` + return 3 - self.max_exponent + + @property + def max(self): + """ Maximum value representable. """ + return (1 - two**-(self.nmant+1))*two**self.max_exponent + + @property + def tiny(self): + """ The minimum positive normalized value. """ + # See C macros: FLT_MIN, DBL_MIN, LDBL_MIN + # or C++'s ``std::numeric_limits::min`` + # or numpy.finfo(dtype).tiny + return two**(self.min_exponent - 1) + + + @property + def eps(self): + """ Difference between 1.0 and the next representable value. """ + return two**(-self.nmant) + + @property + def dig(self): + """ Number of decimal digits that are guaranteed to be preserved in text. + + When converting text -> float -> text, you are guaranteed that at least ``dig`` + number of digits are preserved with respect to rounding or overflow. + """ + from sympy.functions import floor, log + return floor(self.nmant * log(2)/log(10)) + + @property + def decimal_dig(self): + """ Number of digits needed to store & load without loss. + + Explanation + =========== + + Number of decimal digits needed to guarantee that two consecutive conversions + (float -> text -> float) to be idempotent. This is useful when one do not want + to loose precision due to rounding errors when storing a floating point value + as text. + """ + from sympy.functions import ceiling, log + return ceiling((self.nmant + 1) * log(2)/log(10) + 1) + + def cast_nocheck(self, value): + """ Casts without checking if out of bounds or subnormal. """ + if value == oo: # float(oo) or oo + return float(oo) + elif value == -oo: # float(-oo) or -oo + return float(-oo) + return Float(str(sympify(value).evalf(self.decimal_dig)), self.decimal_dig) + + def _check(self, value): + if value < -self.max: + raise ValueError("Value is too small: %d < %d" % (value, -self.max)) + if value > self.max: + raise ValueError("Value is too big: %d > %d" % (value, self.max)) + if abs(value) < self.tiny: + raise ValueError("Smallest (absolute) value for data type bigger than new value.") + +class ComplexBaseType(FloatBaseType): + + __slots__ = () + + def cast_nocheck(self, value): + """ Casts without checking if out of bounds or subnormal. """ + from sympy.functions import re, im + return ( + super().cast_nocheck(re(value)) + + super().cast_nocheck(im(value))*1j + ) + + def _check(self, value): + from sympy.functions import re, im + super()._check(re(value)) + super()._check(im(value)) + + +class ComplexType(ComplexBaseType, FloatType): + """ Represents a complex floating point number. """ + __slots__ = () + + +# NumPy types: +intc = IntBaseType('intc') +intp = IntBaseType('intp') +int8 = SignedIntType('int8', 8) +int16 = SignedIntType('int16', 16) +int32 = SignedIntType('int32', 32) +int64 = SignedIntType('int64', 64) +uint8 = UnsignedIntType('uint8', 8) +uint16 = UnsignedIntType('uint16', 16) +uint32 = UnsignedIntType('uint32', 32) +uint64 = UnsignedIntType('uint64', 64) +float16 = FloatType('float16', 16, nexp=5, nmant=10) # IEEE 754 binary16, Half precision +float32 = FloatType('float32', 32, nexp=8, nmant=23) # IEEE 754 binary32, Single precision +float64 = FloatType('float64', 64, nexp=11, nmant=52) # IEEE 754 binary64, Double precision +float80 = FloatType('float80', 80, nexp=15, nmant=63) # x86 extended precision (1 integer part bit), "long double" +float128 = FloatType('float128', 128, nexp=15, nmant=112) # IEEE 754 binary128, Quadruple precision +float256 = FloatType('float256', 256, nexp=19, nmant=236) # IEEE 754 binary256, Octuple precision + +complex64 = ComplexType('complex64', nbits=64, **float32.kwargs(exclude=('name', 'nbits'))) +complex128 = ComplexType('complex128', nbits=128, **float64.kwargs(exclude=('name', 'nbits'))) + +# Generic types (precision may be chosen by code printers): +untyped = Type('untyped') +real = FloatBaseType('real') +integer = IntBaseType('integer') +complex_ = ComplexBaseType('complex') +bool_ = Type('bool') + + +class Attribute(Token): + """ Attribute (possibly parametrized) + + For use with :class:`sympy.codegen.ast.Node` (which takes instances of + ``Attribute`` as ``attrs``). + + Parameters + ========== + + name : str + parameters : Tuple + + Examples + ======== + + >>> from sympy.codegen.ast import Attribute + >>> volatile = Attribute('volatile') + >>> volatile + volatile + >>> print(repr(volatile)) + Attribute(String('volatile')) + >>> a = Attribute('foo', [1, 2, 3]) + >>> a + foo(1, 2, 3) + >>> a.parameters == (1, 2, 3) + True + """ + __slots__ = _fields = ('name', 'parameters') + defaults = {'parameters': Tuple()} + + _construct_name = String + _construct_parameters = staticmethod(_mk_Tuple) + + def _sympystr(self, printer, *args, **kwargs): + result = str(self.name) + if self.parameters: + result += '(%s)' % ', '.join((printer._print( + arg, *args, **kwargs) for arg in self.parameters)) + return result + +value_const = Attribute('value_const') +pointer_const = Attribute('pointer_const') + + +class Variable(Node): + """ Represents a variable. + + Parameters + ========== + + symbol : Symbol + type : Type (optional) + Type of the variable. + attrs : iterable of Attribute instances + Will be stored as a Tuple. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.codegen.ast import Variable, float32, integer + >>> x = Symbol('x') + >>> v = Variable(x, type=float32) + >>> v.attrs + () + >>> v == Variable('x') + False + >>> v == Variable('x', type=float32) + True + >>> v + Variable(x, type=float32) + + One may also construct a ``Variable`` instance with the type deduced from + assumptions about the symbol using the ``deduced`` classmethod: + + >>> i = Symbol('i', integer=True) + >>> v = Variable.deduced(i) + >>> v.type == integer + True + >>> v == Variable('i') + False + >>> from sympy.codegen.ast import value_const + >>> value_const in v.attrs + False + >>> w = Variable('w', attrs=[value_const]) + >>> w + Variable(w, attrs=(value_const,)) + >>> value_const in w.attrs + True + >>> w.as_Declaration(value=42) + Declaration(Variable(w, value=42, attrs=(value_const,))) + + """ + + __slots__ = ('symbol', 'type', 'value') + _fields = __slots__ + Node._fields + + defaults = Node.defaults.copy() + defaults.update({'type': untyped, 'value': none}) + + _construct_symbol = staticmethod(sympify) + _construct_value = staticmethod(sympify) + + @classmethod + def deduced(cls, symbol, value=None, attrs=Tuple(), cast_check=True): + """ Alt. constructor with type deduction from ``Type.from_expr``. + + Deduces type primarily from ``symbol``, secondarily from ``value``. + + Parameters + ========== + + symbol : Symbol + value : expr + (optional) value of the variable. + attrs : iterable of Attribute instances + cast_check : bool + Whether to apply ``Type.cast_check`` on ``value``. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.codegen.ast import Variable, complex_ + >>> n = Symbol('n', integer=True) + >>> str(Variable.deduced(n).type) + 'integer' + >>> x = Symbol('x', real=True) + >>> v = Variable.deduced(x) + >>> v.type + real + >>> z = Symbol('z', complex=True) + >>> Variable.deduced(z).type == complex_ + True + + """ + if isinstance(symbol, Variable): + return symbol + + try: + type_ = Type.from_expr(symbol) + except ValueError: + type_ = Type.from_expr(value) + + if value is not None and cast_check: + value = type_.cast_check(value) + return cls(symbol, type=type_, value=value, attrs=attrs) + + def as_Declaration(self, **kwargs): + """ Convenience method for creating a Declaration instance. + + Explanation + =========== + + If the variable of the Declaration need to wrap a modified + variable keyword arguments may be passed (overriding e.g. + the ``value`` of the Variable instance). + + Examples + ======== + + >>> from sympy.codegen.ast import Variable, NoneToken + >>> x = Variable('x') + >>> decl1 = x.as_Declaration() + >>> # value is special NoneToken() which must be tested with == operator + >>> decl1.variable.value is None # won't work + False + >>> decl1.variable.value == None # not PEP-8 compliant + True + >>> decl1.variable.value == NoneToken() # OK + True + >>> decl2 = x.as_Declaration(value=42.0) + >>> decl2.variable.value == 42.0 + True + + """ + kw = self.kwargs() + kw.update(kwargs) + return Declaration(self.func(**kw)) + + def _relation(self, rhs, op): + try: + rhs = _sympify(rhs) + except SympifyError: + raise TypeError("Invalid comparison %s < %s" % (self, rhs)) + return op(self, rhs, evaluate=False) + + __lt__ = lambda self, other: self._relation(other, Lt) + __le__ = lambda self, other: self._relation(other, Le) + __ge__ = lambda self, other: self._relation(other, Ge) + __gt__ = lambda self, other: self._relation(other, Gt) + +class Pointer(Variable): + """ Represents a pointer. See ``Variable``. + + Examples + ======== + + Can create instances of ``Element``: + + >>> from sympy import Symbol + >>> from sympy.codegen.ast import Pointer + >>> i = Symbol('i', integer=True) + >>> p = Pointer('x') + >>> p[i+1] + Element(x, indices=(i + 1,)) + + """ + __slots__ = () + + def __getitem__(self, key): + try: + return Element(self.symbol, key) + except TypeError: + return Element(self.symbol, (key,)) + + +class Element(Token): + """ Element in (a possibly N-dimensional) array. + + Examples + ======== + + >>> from sympy.codegen.ast import Element + >>> elem = Element('x', 'ijk') + >>> elem.symbol.name == 'x' + True + >>> elem.indices + (i, j, k) + >>> from sympy import ccode + >>> ccode(elem) + 'x[i][j][k]' + >>> ccode(Element('x', 'ijk', strides='lmn', offset='o')) + 'x[i*l + j*m + k*n + o]' + + """ + __slots__ = _fields = ('symbol', 'indices', 'strides', 'offset') + defaults = {'strides': none, 'offset': none} + _construct_symbol = staticmethod(sympify) + _construct_indices = staticmethod(lambda arg: Tuple(*arg)) + _construct_strides = staticmethod(lambda arg: Tuple(*arg)) + _construct_offset = staticmethod(sympify) + + +class Declaration(Token): + """ Represents a variable declaration + + Parameters + ========== + + variable : Variable + + Examples + ======== + + >>> from sympy.codegen.ast import Declaration, NoneToken, untyped + >>> z = Declaration('z') + >>> z.variable.type == untyped + True + >>> # value is special NoneToken() which must be tested with == operator + >>> z.variable.value is None # won't work + False + >>> z.variable.value == None # not PEP-8 compliant + True + >>> z.variable.value == NoneToken() # OK + True + """ + __slots__ = _fields = ('variable',) + _construct_variable = Variable + + +class While(Token): + """ Represents a 'for-loop' in the code. + + Expressions are of the form: + "while condition: + body..." + + Parameters + ========== + + condition : expression convertible to Boolean + body : CodeBlock or iterable + When passed an iterable it is used to instantiate a CodeBlock. + + Examples + ======== + + >>> from sympy import symbols, Gt, Abs + >>> from sympy.codegen import aug_assign, Assignment, While + >>> x, dx = symbols('x dx') + >>> expr = 1 - x**2 + >>> whl = While(Gt(Abs(dx), 1e-9), [ + ... Assignment(dx, -expr/expr.diff(x)), + ... aug_assign(x, '+', dx) + ... ]) + + """ + __slots__ = _fields = ('condition', 'body') + _construct_condition = staticmethod(lambda cond: _sympify(cond)) + + @classmethod + def _construct_body(cls, itr): + if isinstance(itr, CodeBlock): + return itr + else: + return CodeBlock(*itr) + + +class Scope(Token): + """ Represents a scope in the code. + + Parameters + ========== + + body : CodeBlock or iterable + When passed an iterable it is used to instantiate a CodeBlock. + + """ + __slots__ = _fields = ('body',) + + @classmethod + def _construct_body(cls, itr): + if isinstance(itr, CodeBlock): + return itr + else: + return CodeBlock(*itr) + + +class Stream(Token): + """ Represents a stream. + + There are two predefined Stream instances ``stdout`` & ``stderr``. + + Parameters + ========== + + name : str + + Examples + ======== + + >>> from sympy import pycode, Symbol + >>> from sympy.codegen.ast import Print, stderr, QuotedString + >>> print(pycode(Print(['x'], file=stderr))) + print(x, file=sys.stderr) + >>> x = Symbol('x') + >>> print(pycode(Print([QuotedString('x')], file=stderr))) # print literally "x" + print("x", file=sys.stderr) + + """ + __slots__ = _fields = ('name',) + _construct_name = String + +stdout = Stream('stdout') +stderr = Stream('stderr') + + +class Print(Token): + r""" Represents print command in the code. + + Parameters + ========== + + formatstring : str + *args : Basic instances (or convertible to such through sympify) + + Examples + ======== + + >>> from sympy.codegen.ast import Print + >>> from sympy import pycode + >>> print(pycode(Print('x y'.split(), "coordinate: %12.5g %12.5g\\n"))) + print("coordinate: %12.5g %12.5g\n" % (x, y), end="") + + """ + + __slots__ = _fields = ('print_args', 'format_string', 'file') + defaults = {'format_string': none, 'file': none} + + _construct_print_args = staticmethod(_mk_Tuple) + _construct_format_string = QuotedString + _construct_file = Stream + + +class FunctionPrototype(Node): + """ Represents a function prototype + + Allows the user to generate forward declaration in e.g. C/C++. + + Parameters + ========== + + return_type : Type + name : str + parameters: iterable of Variable instances + attrs : iterable of Attribute instances + + Examples + ======== + + >>> from sympy import ccode, symbols + >>> from sympy.codegen.ast import real, FunctionPrototype + >>> x, y = symbols('x y', real=True) + >>> fp = FunctionPrototype(real, 'foo', [x, y]) + >>> ccode(fp) + 'double foo(double x, double y)' + + """ + + __slots__ = ('return_type', 'name', 'parameters') + _fields: tuple[str, ...] = __slots__ + Node._fields + + _construct_return_type = Type + _construct_name = String + + @staticmethod + def _construct_parameters(args): + def _var(arg): + if isinstance(arg, Declaration): + return arg.variable + elif isinstance(arg, Variable): + return arg + else: + return Variable.deduced(arg) + return Tuple(*map(_var, args)) + + @classmethod + def from_FunctionDefinition(cls, func_def): + if not isinstance(func_def, FunctionDefinition): + raise TypeError("func_def is not an instance of FunctionDefinition") + return cls(**func_def.kwargs(exclude=('body',))) + + +class FunctionDefinition(FunctionPrototype): + """ Represents a function definition in the code. + + Parameters + ========== + + return_type : Type + name : str + parameters: iterable of Variable instances + body : CodeBlock or iterable + attrs : iterable of Attribute instances + + Examples + ======== + + >>> from sympy import ccode, symbols + >>> from sympy.codegen.ast import real, FunctionPrototype + >>> x, y = symbols('x y', real=True) + >>> fp = FunctionPrototype(real, 'foo', [x, y]) + >>> ccode(fp) + 'double foo(double x, double y)' + >>> from sympy.codegen.ast import FunctionDefinition, Return + >>> body = [Return(x*y)] + >>> fd = FunctionDefinition.from_FunctionPrototype(fp, body) + >>> print(ccode(fd)) + double foo(double x, double y){ + return x*y; + } + """ + + __slots__ = ('body', ) + _fields = FunctionPrototype._fields[:-1] + __slots__ + Node._fields + + @classmethod + def _construct_body(cls, itr): + if isinstance(itr, CodeBlock): + return itr + else: + return CodeBlock(*itr) + + @classmethod + def from_FunctionPrototype(cls, func_proto, body): + if not isinstance(func_proto, FunctionPrototype): + raise TypeError("func_proto is not an instance of FunctionPrototype") + return cls(body=body, **func_proto.kwargs()) + + +class Return(Token): + """ Represents a return command in the code. + + Parameters + ========== + + return : Basic + + Examples + ======== + + >>> from sympy.codegen.ast import Return + >>> from sympy.printing.pycode import pycode + >>> from sympy import Symbol + >>> x = Symbol('x') + >>> print(pycode(Return(x))) + return x + + """ + __slots__ = _fields = ('return',) + _construct_return=staticmethod(_sympify) + + +class FunctionCall(Token, Expr): + """ Represents a call to a function in the code. + + Parameters + ========== + + name : str + function_args : Tuple + + Examples + ======== + + >>> from sympy.codegen.ast import FunctionCall + >>> from sympy import pycode + >>> fcall = FunctionCall('foo', 'bar baz'.split()) + >>> print(pycode(fcall)) + foo(bar, baz) + + """ + __slots__ = _fields = ('name', 'function_args') + + _construct_name = String + _construct_function_args = staticmethod(lambda args: Tuple(*args)) + + +class Raise(Token): + """ Prints as 'raise ...' in Python, 'throw ...' in C++""" + __slots__ = _fields = ('exception',) + + +class RuntimeError_(Token): + """ Represents 'std::runtime_error' in C++ and 'RuntimeError' in Python. + + Note that the latter is uncommon, and you might want to use e.g. ValueError. + """ + __slots__ = _fields = ('message',) + _construct_message = String diff --git a/phivenv/Lib/site-packages/sympy/codegen/cfunctions.py b/phivenv/Lib/site-packages/sympy/codegen/cfunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..7b79291f128aef1cb83b327782840508e59a9cc8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/cfunctions.py @@ -0,0 +1,558 @@ +""" +This module contains SymPy functions mathcin corresponding to special math functions in the +C standard library (since C99, also available in C++11). + +The functions defined in this module allows the user to express functions such as ``expm1`` +as a SymPy function for symbolic manipulation. + +""" +from sympy.core.function import ArgumentIndexError, Function +from sympy.core.numbers import Rational +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.logic.boolalg import BooleanFunction, true, false + +def _expm1(x): + return exp(x) - S.One + + +class expm1(Function): + """ + Represents the exponential function minus one. + + Explanation + =========== + + The benefit of using ``expm1(x)`` over ``exp(x) - 1`` + is that the latter is prone to cancellation under finite precision + arithmetic when x is close to zero. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import expm1 + >>> '%.0e' % expm1(1e-99).evalf() + '1e-99' + >>> from math import exp + >>> exp(1e-99) - 1 + 0.0 + >>> expm1(x).diff(x) + exp(x) + + See Also + ======== + + log1p + """ + nargs = 1 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return exp(*self.args) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_expand_func(self, **hints): + return _expm1(*self.args) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + return exp(arg) - S.One + + _eval_rewrite_as_tractable = _eval_rewrite_as_exp + + @classmethod + def eval(cls, arg): + exp_arg = exp.eval(arg) + if exp_arg is not None: + return exp_arg - S.One + + def _eval_is_real(self): + return self.args[0].is_real + + def _eval_is_finite(self): + return self.args[0].is_finite + + +def _log1p(x): + return log(x + S.One) + + +class log1p(Function): + """ + Represents the natural logarithm of a number plus one. + + Explanation + =========== + + The benefit of using ``log1p(x)`` over ``log(x + 1)`` + is that the latter is prone to cancellation under finite precision + arithmetic when x is close to zero. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import log1p + >>> from sympy import expand_log + >>> '%.0e' % expand_log(log1p(1e-99)).evalf() + '1e-99' + >>> from math import log + >>> log(1 + 1e-99) + 0.0 + >>> log1p(x).diff(x) + 1/(x + 1) + + See Also + ======== + + expm1 + """ + nargs = 1 + + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return S.One/(self.args[0] + S.One) + else: + raise ArgumentIndexError(self, argindex) + + + def _eval_expand_func(self, **hints): + return _log1p(*self.args) + + def _eval_rewrite_as_log(self, arg, **kwargs): + return _log1p(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + @classmethod + def eval(cls, arg): + if arg.is_Rational: + return log(arg + S.One) + elif not arg.is_Float: # not safe to add 1 to Float + return log.eval(arg + S.One) + elif arg.is_number: + return log(Rational(arg) + S.One) + + def _eval_is_real(self): + return (self.args[0] + S.One).is_nonnegative + + def _eval_is_finite(self): + if (self.args[0] + S.One).is_zero: + return False + return self.args[0].is_finite + + def _eval_is_positive(self): + return self.args[0].is_positive + + def _eval_is_zero(self): + return self.args[0].is_zero + + def _eval_is_nonnegative(self): + return self.args[0].is_nonnegative + +_Two = S(2) + +def _exp2(x): + return Pow(_Two, x) + +class exp2(Function): + """ + Represents the exponential function with base two. + + Explanation + =========== + + The benefit of using ``exp2(x)`` over ``2**x`` + is that the latter is not as efficient under finite precision + arithmetic. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import exp2 + >>> exp2(2).evalf() == 4.0 + True + >>> exp2(x).diff(x) + log(2)*exp2(x) + + See Also + ======== + + log2 + """ + nargs = 1 + + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return self*log(_Two) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + return _exp2(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_Pow + + def _eval_expand_func(self, **hints): + return _exp2(*self.args) + + @classmethod + def eval(cls, arg): + if arg.is_number: + return _exp2(arg) + + +def _log2(x): + return log(x)/log(_Two) + + +class log2(Function): + """ + Represents the logarithm function with base two. + + Explanation + =========== + + The benefit of using ``log2(x)`` over ``log(x)/log(2)`` + is that the latter is not as efficient under finite precision + arithmetic. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import log2 + >>> log2(4).evalf() == 2.0 + True + >>> log2(x).diff(x) + 1/(x*log(2)) + + See Also + ======== + + exp2 + log10 + """ + nargs = 1 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return S.One/(log(_Two)*self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + + @classmethod + def eval(cls, arg): + if arg.is_number: + result = log.eval(arg, base=_Two) + if result.is_Atom: + return result + elif arg.is_Pow and arg.base == _Two: + return arg.exp + + def _eval_evalf(self, *args, **kwargs): + return self.rewrite(log).evalf(*args, **kwargs) + + def _eval_expand_func(self, **hints): + return _log2(*self.args) + + def _eval_rewrite_as_log(self, arg, **kwargs): + return _log2(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + +def _fma(x, y, z): + return x*y + z + + +class fma(Function): + """ + Represents "fused multiply add". + + Explanation + =========== + + The benefit of using ``fma(x, y, z)`` over ``x*y + z`` + is that, under finite precision arithmetic, the former is + supported by special instructions on some CPUs. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.codegen.cfunctions import fma + >>> fma(x, y, z).diff(x) + y + + """ + nargs = 3 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex in (1, 2): + return self.args[2 - argindex] + elif argindex == 3: + return S.One + else: + raise ArgumentIndexError(self, argindex) + + + def _eval_expand_func(self, **hints): + return _fma(*self.args) + + def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs): + return _fma(arg) + + +_Ten = S(10) + + +def _log10(x): + return log(x)/log(_Ten) + + +class log10(Function): + """ + Represents the logarithm function with base ten. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import log10 + >>> log10(100).evalf() == 2.0 + True + >>> log10(x).diff(x) + 1/(x*log(10)) + + See Also + ======== + + log2 + """ + nargs = 1 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return S.One/(log(_Ten)*self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + + @classmethod + def eval(cls, arg): + if arg.is_number: + result = log.eval(arg, base=_Ten) + if result.is_Atom: + return result + elif arg.is_Pow and arg.base == _Ten: + return arg.exp + + def _eval_expand_func(self, **hints): + return _log10(*self.args) + + def _eval_rewrite_as_log(self, arg, **kwargs): + return _log10(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + +def _Sqrt(x): + return Pow(x, S.Half) + + +class Sqrt(Function): # 'sqrt' already defined in sympy.functions.elementary.miscellaneous + """ + Represents the square root function. + + Explanation + =========== + + The reason why one would use ``Sqrt(x)`` over ``sqrt(x)`` + is that the latter is internally represented as ``Pow(x, S.Half)`` which + may not be what one wants when doing code-generation. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import Sqrt + >>> Sqrt(x) + Sqrt(x) + >>> Sqrt(x).diff(x) + 1/(2*sqrt(x)) + + See Also + ======== + + Cbrt + """ + nargs = 1 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return Pow(self.args[0], Rational(-1, 2))/_Two + else: + raise ArgumentIndexError(self, argindex) + + def _eval_expand_func(self, **hints): + return _Sqrt(*self.args) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + return _Sqrt(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_Pow + + +def _Cbrt(x): + return Pow(x, Rational(1, 3)) + + +class Cbrt(Function): # 'cbrt' already defined in sympy.functions.elementary.miscellaneous + """ + Represents the cube root function. + + Explanation + =========== + + The reason why one would use ``Cbrt(x)`` over ``cbrt(x)`` + is that the latter is internally represented as ``Pow(x, Rational(1, 3))`` which + may not be what one wants when doing code-generation. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cfunctions import Cbrt + >>> Cbrt(x) + Cbrt(x) + >>> Cbrt(x).diff(x) + 1/(3*x**(2/3)) + + See Also + ======== + + Sqrt + """ + nargs = 1 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return Pow(self.args[0], Rational(-_Two/3))/3 + else: + raise ArgumentIndexError(self, argindex) + + + def _eval_expand_func(self, **hints): + return _Cbrt(*self.args) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + return _Cbrt(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_Pow + + +def _hypot(x, y): + return sqrt(Pow(x, 2) + Pow(y, 2)) + + +class hypot(Function): + """ + Represents the hypotenuse function. + + Explanation + =========== + + The hypotenuse function is provided by e.g. the math library + in the C99 standard, hence one may want to represent the function + symbolically when doing code-generation. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.codegen.cfunctions import hypot + >>> hypot(3, 4).evalf() == 5.0 + True + >>> hypot(x, y) + hypot(x, y) + >>> hypot(x, y).diff(x) + x/hypot(x, y) + + """ + nargs = 2 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex in (1, 2): + return 2*self.args[argindex-1]/(_Two*self.func(*self.args)) + else: + raise ArgumentIndexError(self, argindex) + + + def _eval_expand_func(self, **hints): + return _hypot(*self.args) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + return _hypot(arg) + + _eval_rewrite_as_tractable = _eval_rewrite_as_Pow + + +class isnan(BooleanFunction): + nargs = 1 + + @classmethod + def eval(cls, arg): + if arg is S.NaN: + return true + elif arg.is_number: + return false + else: + return None + + +class isinf(BooleanFunction): + nargs = 1 + + @classmethod + def eval(cls, arg): + if arg.is_infinite: + return true + elif arg.is_finite: + return false + else: + return None diff --git a/phivenv/Lib/site-packages/sympy/codegen/cnodes.py b/phivenv/Lib/site-packages/sympy/codegen/cnodes.py new file mode 100644 index 0000000000000000000000000000000000000000..dd2a324ee49cabcd42e99ca5d8e5379cc9262c4e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/cnodes.py @@ -0,0 +1,156 @@ +""" +AST nodes specific to the C family of languages +""" + +from sympy.codegen.ast import ( + Attribute, Declaration, Node, String, Token, Type, none, + FunctionCall, CodeBlock + ) +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.sympify import sympify + +void = Type('void') + +restrict = Attribute('restrict') # guarantees no pointer aliasing +volatile = Attribute('volatile') +static = Attribute('static') + + +def alignof(arg): + """ Generate of FunctionCall instance for calling 'alignof' """ + return FunctionCall('alignof', [String(arg) if isinstance(arg, str) else arg]) + + +def sizeof(arg): + """ Generate of FunctionCall instance for calling 'sizeof' + + Examples + ======== + + >>> from sympy.codegen.ast import real + >>> from sympy.codegen.cnodes import sizeof + >>> from sympy import ccode + >>> ccode(sizeof(real)) + 'sizeof(double)' + """ + return FunctionCall('sizeof', [String(arg) if isinstance(arg, str) else arg]) + + +class CommaOperator(Basic): + """ Represents the comma operator in C """ + def __new__(cls, *args): + return Basic.__new__(cls, *[sympify(arg) for arg in args]) + + +class Label(Node): + """ Label for use with e.g. goto statement. + + Examples + ======== + + >>> from sympy import ccode, Symbol + >>> from sympy.codegen.cnodes import Label, PreIncrement + >>> print(ccode(Label('foo'))) + foo: + >>> print(ccode(Label('bar', [PreIncrement(Symbol('a'))]))) + bar: + ++(a); + + """ + __slots__ = _fields = ('name', 'body') + defaults = {'body': none} + _construct_name = String + + @classmethod + def _construct_body(cls, itr): + if isinstance(itr, CodeBlock): + return itr + else: + return CodeBlock(*itr) + + +class goto(Token): + """ Represents goto in C """ + __slots__ = _fields = ('label',) + _construct_label = Label + + +class PreDecrement(Basic): + """ Represents the pre-decrement operator + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cnodes import PreDecrement + >>> from sympy import ccode + >>> ccode(PreDecrement(x)) + '--(x)' + + """ + nargs = 1 + + +class PostDecrement(Basic): + """ Represents the post-decrement operator + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cnodes import PostDecrement + >>> from sympy import ccode + >>> ccode(PostDecrement(x)) + '(x)--' + + """ + nargs = 1 + + +class PreIncrement(Basic): + """ Represents the pre-increment operator + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cnodes import PreIncrement + >>> from sympy import ccode + >>> ccode(PreIncrement(x)) + '++(x)' + + """ + nargs = 1 + + +class PostIncrement(Basic): + """ Represents the post-increment operator + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.codegen.cnodes import PostIncrement + >>> from sympy import ccode + >>> ccode(PostIncrement(x)) + '(x)++' + + """ + nargs = 1 + + +class struct(Node): + """ Represents a struct in C """ + __slots__ = _fields = ('name', 'declarations') + defaults = {'name': none} + _construct_name = String + + @classmethod + def _construct_declarations(cls, args): + return Tuple(*[Declaration(arg) for arg in args]) + + +class union(struct): + """ Represents a union in C """ + __slots__ = () diff --git a/phivenv/Lib/site-packages/sympy/codegen/cutils.py b/phivenv/Lib/site-packages/sympy/codegen/cutils.py new file mode 100644 index 0000000000000000000000000000000000000000..2182ac1f3455da490a0bb57f8d6731fe8a29a232 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/cutils.py @@ -0,0 +1,8 @@ +from sympy.printing.c import C99CodePrinter + +def render_as_source_file(content, Printer=C99CodePrinter, settings=None): + """ Renders a C source file (with required #include statements) """ + printer = Printer(settings or {}) + code_str = printer.doprint(content) + includes = '\n'.join(['#include <%s>' % h for h in printer.headers]) + return includes + '\n\n' + code_str diff --git a/phivenv/Lib/site-packages/sympy/codegen/cxxnodes.py b/phivenv/Lib/site-packages/sympy/codegen/cxxnodes.py new file mode 100644 index 0000000000000000000000000000000000000000..7f7aafd01ab2de99ad0f668275889863fc73f5aa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/cxxnodes.py @@ -0,0 +1,14 @@ +""" +AST nodes specific to C++. +""" + +from sympy.codegen.ast import Attribute, String, Token, Type, none + +class using(Token): + """ Represents a 'using' statement in C++ """ + __slots__ = _fields = ('type', 'alias') + defaults = {'alias': none} + _construct_type = Type + _construct_alias = String + +constexpr = Attribute('constexpr') diff --git a/phivenv/Lib/site-packages/sympy/codegen/fnodes.py b/phivenv/Lib/site-packages/sympy/codegen/fnodes.py new file mode 100644 index 0000000000000000000000000000000000000000..8b972fcfe4d4de4cfe74d75705f42c5e112a9b43 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/fnodes.py @@ -0,0 +1,658 @@ +""" +AST nodes specific to Fortran. + +The functions defined in this module allows the user to express functions such as ``dsign`` +as a SymPy function for symbolic manipulation. +""" + +from __future__ import annotations +from sympy.codegen.ast import ( + Attribute, CodeBlock, FunctionCall, Node, none, String, + Token, _mk_Tuple, Variable +) +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.function import Function +from sympy.core.numbers import Float, Integer +from sympy.core.symbol import Str +from sympy.core.sympify import sympify +from sympy.logic import true, false +from sympy.utilities.iterables import iterable + + + +pure = Attribute('pure') +elemental = Attribute('elemental') # (all elemental procedures are also pure) + +intent_in = Attribute('intent_in') +intent_out = Attribute('intent_out') +intent_inout = Attribute('intent_inout') + +allocatable = Attribute('allocatable') + +class Program(Token): + """ Represents a 'program' block in Fortran. + + Examples + ======== + + >>> from sympy.codegen.ast import Print + >>> from sympy.codegen.fnodes import Program + >>> prog = Program('myprogram', [Print([42])]) + >>> from sympy import fcode + >>> print(fcode(prog, source_format='free')) + program myprogram + print *, 42 + end program + + """ + __slots__ = _fields = ('name', 'body') + _construct_name = String + _construct_body = staticmethod(lambda body: CodeBlock(*body)) + + +class use_rename(Token): + """ Represents a renaming in a use statement in Fortran. + + Examples + ======== + + >>> from sympy.codegen.fnodes import use_rename, use + >>> from sympy import fcode + >>> ren = use_rename("thingy", "convolution2d") + >>> print(fcode(ren, source_format='free')) + thingy => convolution2d + >>> full = use('signallib', only=['snr', ren]) + >>> print(fcode(full, source_format='free')) + use signallib, only: snr, thingy => convolution2d + + """ + __slots__ = _fields = ('local', 'original') + _construct_local = String + _construct_original = String + +def _name(arg): + if hasattr(arg, 'name'): + return arg.name + else: + return String(arg) + +class use(Token): + """ Represents a use statement in Fortran. + + Examples + ======== + + >>> from sympy.codegen.fnodes import use + >>> from sympy import fcode + >>> fcode(use('signallib'), source_format='free') + 'use signallib' + >>> fcode(use('signallib', [('metric', 'snr')]), source_format='free') + 'use signallib, metric => snr' + >>> fcode(use('signallib', only=['snr', 'convolution2d']), source_format='free') + 'use signallib, only: snr, convolution2d' + + """ + __slots__ = _fields = ('namespace', 'rename', 'only') + defaults = {'rename': none, 'only': none} + _construct_namespace = staticmethod(_name) + _construct_rename = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else use_rename(*arg) for arg in args])) + _construct_only = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else _name(arg) for arg in args])) + + +class Module(Token): + """ Represents a module in Fortran. + + Examples + ======== + + >>> from sympy.codegen.fnodes import Module + >>> from sympy import fcode + >>> print(fcode(Module('signallib', ['implicit none'], []), source_format='free')) + module signallib + implicit none + + contains + + + end module + + """ + __slots__ = _fields = ('name', 'declarations', 'definitions') + defaults = {'declarations': Tuple()} + _construct_name = String + + @classmethod + def _construct_declarations(cls, args): + args = [Str(arg) if isinstance(arg, str) else arg for arg in args] + return CodeBlock(*args) + + _construct_definitions = staticmethod(lambda arg: CodeBlock(*arg)) + + +class Subroutine(Node): + """ Represents a subroutine in Fortran. + + Examples + ======== + + >>> from sympy import fcode, symbols + >>> from sympy.codegen.ast import Print + >>> from sympy.codegen.fnodes import Subroutine + >>> x, y = symbols('x y', real=True) + >>> sub = Subroutine('mysub', [x, y], [Print([x**2 + y**2, x*y])]) + >>> print(fcode(sub, source_format='free', standard=2003)) + subroutine mysub(x, y) + real*8 :: x + real*8 :: y + print *, x**2 + y**2, x*y + end subroutine + + """ + __slots__ = ('name', 'parameters', 'body') + _fields = __slots__ + Node._fields + _construct_name = String + _construct_parameters = staticmethod(lambda params: Tuple(*map(Variable.deduced, params))) + + @classmethod + def _construct_body(cls, itr): + if isinstance(itr, CodeBlock): + return itr + else: + return CodeBlock(*itr) + +class SubroutineCall(Token): + """ Represents a call to a subroutine in Fortran. + + Examples + ======== + + >>> from sympy.codegen.fnodes import SubroutineCall + >>> from sympy import fcode + >>> fcode(SubroutineCall('mysub', 'x y'.split())) + ' call mysub(x, y)' + + """ + __slots__ = _fields = ('name', 'subroutine_args') + _construct_name = staticmethod(_name) + _construct_subroutine_args = staticmethod(_mk_Tuple) + + +class Do(Token): + """ Represents a Do loop in in Fortran. + + Examples + ======== + + >>> from sympy import fcode, symbols + >>> from sympy.codegen.ast import aug_assign, Print + >>> from sympy.codegen.fnodes import Do + >>> i, n = symbols('i n', integer=True) + >>> r = symbols('r', real=True) + >>> body = [aug_assign(r, '+', 1/i), Print([i, r])] + >>> do1 = Do(body, i, 1, n) + >>> print(fcode(do1, source_format='free')) + do i = 1, n + r = r + 1d0/i + print *, i, r + end do + >>> do2 = Do(body, i, 1, n, 2) + >>> print(fcode(do2, source_format='free')) + do i = 1, n, 2 + r = r + 1d0/i + print *, i, r + end do + + """ + + __slots__ = _fields = ('body', 'counter', 'first', 'last', 'step', 'concurrent') + defaults = {'step': Integer(1), 'concurrent': false} + _construct_body = staticmethod(lambda body: CodeBlock(*body)) + _construct_counter = staticmethod(sympify) + _construct_first = staticmethod(sympify) + _construct_last = staticmethod(sympify) + _construct_step = staticmethod(sympify) + _construct_concurrent = staticmethod(lambda arg: true if arg else false) + + +class ArrayConstructor(Token): + """ Represents an array constructor. + + Examples + ======== + + >>> from sympy import fcode + >>> from sympy.codegen.fnodes import ArrayConstructor + >>> ac = ArrayConstructor([1, 2, 3]) + >>> fcode(ac, standard=95, source_format='free') + '(/1, 2, 3/)' + >>> fcode(ac, standard=2003, source_format='free') + '[1, 2, 3]' + + """ + __slots__ = _fields = ('elements',) + _construct_elements = staticmethod(_mk_Tuple) + + +class ImpliedDoLoop(Token): + """ Represents an implied do loop in Fortran. + + Examples + ======== + + >>> from sympy import Symbol, fcode + >>> from sympy.codegen.fnodes import ImpliedDoLoop, ArrayConstructor + >>> i = Symbol('i', integer=True) + >>> idl = ImpliedDoLoop(i**3, i, -3, 3, 2) # -27, -1, 1, 27 + >>> ac = ArrayConstructor([-28, idl, 28]) # -28, -27, -1, 1, 27, 28 + >>> fcode(ac, standard=2003, source_format='free') + '[-28, (i**3, i = -3, 3, 2), 28]' + + """ + __slots__ = _fields = ('expr', 'counter', 'first', 'last', 'step') + defaults = {'step': Integer(1)} + _construct_expr = staticmethod(sympify) + _construct_counter = staticmethod(sympify) + _construct_first = staticmethod(sympify) + _construct_last = staticmethod(sympify) + _construct_step = staticmethod(sympify) + + +class Extent(Basic): + """ Represents a dimension extent. + + Examples + ======== + + >>> from sympy.codegen.fnodes import Extent + >>> e = Extent(-3, 3) # -3, -2, -1, 0, 1, 2, 3 + >>> from sympy import fcode + >>> fcode(e, source_format='free') + '-3:3' + >>> from sympy.codegen.ast import Variable, real + >>> from sympy.codegen.fnodes import dimension, intent_out + >>> dim = dimension(e, e) + >>> arr = Variable('x', real, attrs=[dim, intent_out]) + >>> fcode(arr.as_Declaration(), source_format='free', standard=2003) + 'real*8, dimension(-3:3, -3:3), intent(out) :: x' + + """ + def __new__(cls, *args): + if len(args) == 2: + low, high = args + return Basic.__new__(cls, sympify(low), sympify(high)) + elif len(args) == 0 or (len(args) == 1 and args[0] in (':', None)): + return Basic.__new__(cls) # assumed shape + else: + raise ValueError("Expected 0 or 2 args (or one argument == None or ':')") + + def _sympystr(self, printer): + if len(self.args) == 0: + return ':' + return ":".join(str(arg) for arg in self.args) + +assumed_extent = Extent() # or Extent(':'), Extent(None) + + +def dimension(*args): + """ Creates a 'dimension' Attribute with (up to 7) extents. + + Examples + ======== + + >>> from sympy import fcode + >>> from sympy.codegen.fnodes import dimension, intent_in + >>> dim = dimension('2', ':') # 2 rows, runtime determined number of columns + >>> from sympy.codegen.ast import Variable, integer + >>> arr = Variable('a', integer, attrs=[dim, intent_in]) + >>> fcode(arr.as_Declaration(), source_format='free', standard=2003) + 'integer*4, dimension(2, :), intent(in) :: a' + + """ + if len(args) > 7: + raise ValueError("Fortran only supports up to 7 dimensional arrays") + parameters = [] + for arg in args: + if isinstance(arg, Extent): + parameters.append(arg) + elif isinstance(arg, str): + if arg == ':': + parameters.append(Extent()) + else: + parameters.append(String(arg)) + elif iterable(arg): + parameters.append(Extent(*arg)) + else: + parameters.append(sympify(arg)) + if len(args) == 0: + raise ValueError("Need at least one dimension") + return Attribute('dimension', parameters) + + +assumed_size = dimension('*') + +def array(symbol, dim, intent=None, *, attrs=(), value=None, type=None): + """ Convenience function for creating a Variable instance for a Fortran array. + + Parameters + ========== + + symbol : symbol + dim : Attribute or iterable + If dim is an ``Attribute`` it need to have the name 'dimension'. If it is + not an ``Attribute``, then it is passed to :func:`dimension` as ``*dim`` + intent : str + One of: 'in', 'out', 'inout' or None + \\*\\*kwargs: + Keyword arguments for ``Variable`` ('type' & 'value') + + Examples + ======== + + >>> from sympy import fcode + >>> from sympy.codegen.ast import integer, real + >>> from sympy.codegen.fnodes import array + >>> arr = array('a', '*', 'in', type=integer) + >>> print(fcode(arr.as_Declaration(), source_format='free', standard=2003)) + integer*4, dimension(*), intent(in) :: a + >>> x = array('x', [3, ':', ':'], intent='out', type=real) + >>> print(fcode(x.as_Declaration(value=1), source_format='free', standard=2003)) + real*8, dimension(3, :, :), intent(out) :: x = 1 + + """ + if isinstance(dim, Attribute): + if str(dim.name) != 'dimension': + raise ValueError("Got an unexpected Attribute argument as dim: %s" % str(dim)) + else: + dim = dimension(*dim) + + attrs = list(attrs) + [dim] + if intent is not None: + if intent not in (intent_in, intent_out, intent_inout): + intent = {'in': intent_in, 'out': intent_out, 'inout': intent_inout}[intent] + attrs.append(intent) + if type is None: + return Variable.deduced(symbol, value=value, attrs=attrs) + else: + return Variable(symbol, type, value=value, attrs=attrs) + +def _printable(arg): + return String(arg) if isinstance(arg, str) else sympify(arg) + + +def allocated(array): + """ Creates an AST node for a function call to Fortran's "allocated(...)" + + Examples + ======== + + >>> from sympy import fcode + >>> from sympy.codegen.fnodes import allocated + >>> alloc = allocated('x') + >>> fcode(alloc, source_format='free') + 'allocated(x)' + + """ + return FunctionCall('allocated', [_printable(array)]) + + +def lbound(array, dim=None, kind=None): + """ Creates an AST node for a function call to Fortran's "lbound(...)" + + Parameters + ========== + + array : Symbol or String + dim : expr + kind : expr + + Examples + ======== + + >>> from sympy import fcode + >>> from sympy.codegen.fnodes import lbound + >>> lb = lbound('arr', dim=2) + >>> fcode(lb, source_format='free') + 'lbound(arr, 2)' + + """ + return FunctionCall( + 'lbound', + [_printable(array)] + + ([_printable(dim)] if dim else []) + + ([_printable(kind)] if kind else []) + ) + + +def ubound(array, dim=None, kind=None): + return FunctionCall( + 'ubound', + [_printable(array)] + + ([_printable(dim)] if dim else []) + + ([_printable(kind)] if kind else []) + ) + + +def shape(source, kind=None): + """ Creates an AST node for a function call to Fortran's "shape(...)" + + Parameters + ========== + + source : Symbol or String + kind : expr + + Examples + ======== + + >>> from sympy import fcode + >>> from sympy.codegen.fnodes import shape + >>> shp = shape('x') + >>> fcode(shp, source_format='free') + 'shape(x)' + + """ + return FunctionCall( + 'shape', + [_printable(source)] + + ([_printable(kind)] if kind else []) + ) + + +def size(array, dim=None, kind=None): + """ Creates an AST node for a function call to Fortran's "size(...)" + + Examples + ======== + + >>> from sympy import fcode, Symbol + >>> from sympy.codegen.ast import FunctionDefinition, real, Return + >>> from sympy.codegen.fnodes import array, sum_, size + >>> a = Symbol('a', real=True) + >>> body = [Return((sum_(a**2)/size(a))**.5)] + >>> arr = array(a, dim=[':'], intent='in') + >>> fd = FunctionDefinition(real, 'rms', [arr], body) + >>> print(fcode(fd, source_format='free', standard=2003)) + real*8 function rms(a) + real*8, dimension(:), intent(in) :: a + rms = sqrt(sum(a**2)*1d0/size(a)) + end function + + """ + return FunctionCall( + 'size', + [_printable(array)] + + ([_printable(dim)] if dim else []) + + ([_printable(kind)] if kind else []) + ) + + +def reshape(source, shape, pad=None, order=None): + """ Creates an AST node for a function call to Fortran's "reshape(...)" + + Parameters + ========== + + source : Symbol or String + shape : ArrayExpr + + """ + return FunctionCall( + 'reshape', + [_printable(source), _printable(shape)] + + ([_printable(pad)] if pad else []) + + ([_printable(order)] if pad else []) + ) + + +def bind_C(name=None): + """ Creates an Attribute ``bind_C`` with a name. + + Parameters + ========== + + name : str + + Examples + ======== + + >>> from sympy import fcode, Symbol + >>> from sympy.codegen.ast import FunctionDefinition, real, Return + >>> from sympy.codegen.fnodes import array, sum_, bind_C + >>> a = Symbol('a', real=True) + >>> s = Symbol('s', integer=True) + >>> arr = array(a, dim=[s], intent='in') + >>> body = [Return((sum_(a**2)/s)**.5)] + >>> fd = FunctionDefinition(real, 'rms', [arr, s], body, attrs=[bind_C('rms')]) + >>> print(fcode(fd, source_format='free', standard=2003)) + real*8 function rms(a, s) bind(C, name="rms") + real*8, dimension(s), intent(in) :: a + integer*4 :: s + rms = sqrt(sum(a**2)/s) + end function + + """ + return Attribute('bind_C', [String(name)] if name else []) + +class GoTo(Token): + """ Represents a goto statement in Fortran + + Examples + ======== + + >>> from sympy.codegen.fnodes import GoTo + >>> go = GoTo([10, 20, 30], 'i') + >>> from sympy import fcode + >>> fcode(go, source_format='free') + 'go to (10, 20, 30), i' + + """ + __slots__ = _fields = ('labels', 'expr') + defaults = {'expr': none} + _construct_labels = staticmethod(_mk_Tuple) + _construct_expr = staticmethod(sympify) + + +class FortranReturn(Token): + """ AST node explicitly mapped to a fortran "return". + + Explanation + =========== + + Because a return statement in fortran is different from C, and + in order to aid reuse of our codegen ASTs the ordinary + ``.codegen.ast.Return`` is interpreted as assignment to + the result variable of the function. If one for some reason needs + to generate a fortran RETURN statement, this node should be used. + + Examples + ======== + + >>> from sympy.codegen.fnodes import FortranReturn + >>> from sympy import fcode + >>> fcode(FortranReturn('x')) + ' return x' + + """ + __slots__ = _fields = ('return_value',) + defaults = {'return_value': none} + _construct_return_value = staticmethod(sympify) + + +class FFunction(Function): + _required_standard = 77 + + def _fcode(self, printer): + name = self.__class__.__name__ + if printer._settings['standard'] < self._required_standard: + raise NotImplementedError("%s requires Fortran %d or newer" % + (name, self._required_standard)) + return '{}({})'.format(name, ', '.join(map(printer._print, self.args))) + + +class F95Function(FFunction): + _required_standard = 95 + + +class isign(FFunction): + """ Fortran sign intrinsic for integer arguments. """ + nargs = 2 + + +class dsign(FFunction): + """ Fortran sign intrinsic for double precision arguments. """ + nargs = 2 + + +class cmplx(FFunction): + """ Fortran complex conversion function. """ + nargs = 2 # may be extended to (2, 3) at a later point + + +class kind(FFunction): + """ Fortran kind function. """ + nargs = 1 + + +class merge(F95Function): + """ Fortran merge function """ + nargs = 3 + + +class _literal(Float): + _token: str + _decimals: int + + def _fcode(self, printer, *args, **kwargs): + mantissa, sgnd_ex = ('%.{}e'.format(self._decimals) % self).split('e') + mantissa = mantissa.strip('0').rstrip('.') + ex_sgn, ex_num = sgnd_ex[0], sgnd_ex[1:].lstrip('0') + ex_sgn = '' if ex_sgn == '+' else ex_sgn + return (mantissa or '0') + self._token + ex_sgn + (ex_num or '0') + + +class literal_sp(_literal): + """ Fortran single precision real literal """ + _token = 'e' + _decimals = 9 + + +class literal_dp(_literal): + """ Fortran double precision real literal """ + _token = 'd' + _decimals = 17 + + +class sum_(Token, Expr): + __slots__ = _fields = ('array', 'dim', 'mask') + defaults = {'dim': none, 'mask': none} + _construct_array = staticmethod(sympify) + _construct_dim = staticmethod(sympify) + + +class product_(Token, Expr): + __slots__ = _fields = ('array', 'dim', 'mask') + defaults = {'dim': none, 'mask': none} + _construct_array = staticmethod(sympify) + _construct_dim = staticmethod(sympify) diff --git a/phivenv/Lib/site-packages/sympy/codegen/futils.py b/phivenv/Lib/site-packages/sympy/codegen/futils.py new file mode 100644 index 0000000000000000000000000000000000000000..4a1f5751fbd4d6b44d99c69a74ad89a8496f8648 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/futils.py @@ -0,0 +1,40 @@ +from itertools import chain +from sympy.codegen.fnodes import Module +from sympy.core.symbol import Dummy +from sympy.printing.fortran import FCodePrinter + +""" This module collects utilities for rendering Fortran code. """ + + +def render_as_module(definitions, name, declarations=(), printer_settings=None): + """ Creates a ``Module`` instance and renders it as a string. + + This generates Fortran source code for a module with the correct ``use`` statements. + + Parameters + ========== + + definitions : iterable + Passed to :class:`sympy.codegen.fnodes.Module`. + name : str + Passed to :class:`sympy.codegen.fnodes.Module`. + declarations : iterable + Passed to :class:`sympy.codegen.fnodes.Module`. It will be extended with + use statements, 'implicit none' and public list generated from ``definitions``. + printer_settings : dict + Passed to ``FCodePrinter`` (default: ``{'standard': 2003, 'source_format': 'free'}``). + + """ + printer_settings = printer_settings or {'standard': 2003, 'source_format': 'free'} + printer = FCodePrinter(printer_settings) + dummy = Dummy() + if isinstance(definitions, Module): + raise ValueError("This function expects to construct a module on its own.") + mod = Module(name, chain(declarations, [dummy]), definitions) + fstr = printer.doprint(mod) + module_use_str = ' %s\n' % ' \n'.join(['use %s, only: %s' % (k, ', '.join(v)) for + k, v in printer.module_uses.items()]) + module_use_str += ' implicit none\n' + module_use_str += ' private\n' + module_use_str += ' public %s\n' % ', '.join([str(node.name) for node in definitions if getattr(node, 'name', None)]) + return fstr.replace(printer.doprint(dummy), module_use_str) diff --git a/phivenv/Lib/site-packages/sympy/codegen/matrix_nodes.py b/phivenv/Lib/site-packages/sympy/codegen/matrix_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..cf0a13a81c963e2c9e2b885dabd0ff3e2d2b3eb9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/matrix_nodes.py @@ -0,0 +1,71 @@ +""" +Additional AST nodes for operations on matrices. The nodes in this module +are meant to represent optimization of matrix expressions within codegen's +target languages that cannot be represented by SymPy expressions. + +As an example, we can use :meth:`sympy.codegen.rewriting.optimize` and the +``matin_opt`` optimization provided in :mod:`sympy.codegen.rewriting` to +transform matrix multiplication under certain assumptions: + + >>> from sympy import symbols, MatrixSymbol + >>> n = symbols('n', integer=True) + >>> A = MatrixSymbol('A', n, n) + >>> x = MatrixSymbol('x', n, 1) + >>> expr = A**(-1) * x + >>> from sympy import assuming, Q + >>> from sympy.codegen.rewriting import matinv_opt, optimize + >>> with assuming(Q.fullrank(A)): + ... optimize(expr, [matinv_opt]) + MatrixSolve(A, vector=x) +""" + +from .ast import Token +from sympy.matrices import MatrixExpr +from sympy.core.sympify import sympify + + +class MatrixSolve(Token, MatrixExpr): + """Represents an operation to solve a linear matrix equation. + + Parameters + ========== + + matrix : MatrixSymbol + + Matrix representing the coefficients of variables in the linear + equation. This matrix must be square and full-rank (i.e. all columns must + be linearly independent) for the solving operation to be valid. + + vector : MatrixSymbol + + One-column matrix representing the solutions to the equations + represented in ``matrix``. + + Examples + ======== + + >>> from sympy import symbols, MatrixSymbol + >>> from sympy.codegen.matrix_nodes import MatrixSolve + >>> n = symbols('n', integer=True) + >>> A = MatrixSymbol('A', n, n) + >>> x = MatrixSymbol('x', n, 1) + >>> from sympy.printing.numpy import NumPyPrinter + >>> NumPyPrinter().doprint(MatrixSolve(A, x)) + 'numpy.linalg.solve(A, x)' + >>> from sympy import octave_code + >>> octave_code(MatrixSolve(A, x)) + 'A \\\\ x' + + """ + __slots__ = _fields = ('matrix', 'vector') + + _construct_matrix = staticmethod(sympify) + _construct_vector = staticmethod(sympify) + + @property + def shape(self): + return self.vector.shape + + def _eval_derivative(self, x): + A, b = self.matrix, self.vector + return MatrixSolve(A, b.diff(x) - A.diff(x) * MatrixSolve(A, b)) diff --git a/phivenv/Lib/site-packages/sympy/codegen/numpy_nodes.py b/phivenv/Lib/site-packages/sympy/codegen/numpy_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..c47c87f0e1df74cd314bb70674ebff732fe500aa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/numpy_nodes.py @@ -0,0 +1,177 @@ +from sympy.core.function import Add, ArgumentIndexError, Function +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.core.sympify import sympify +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.miscellaneous import Max, Min +from .ast import Token, none + + +def _logaddexp(x1, x2, *, evaluate=True): + return log(Add(exp(x1, evaluate=evaluate), exp(x2, evaluate=evaluate), evaluate=evaluate)) + + +_two = S.One*2 +_ln2 = log(_two) + + +def _lb(x, *, evaluate=True): + return log(x, evaluate=evaluate)/_ln2 + + +def _exp2(x, *, evaluate=True): + return Pow(_two, x, evaluate=evaluate) + + +def _logaddexp2(x1, x2, *, evaluate=True): + return _lb(Add(_exp2(x1, evaluate=evaluate), + _exp2(x2, evaluate=evaluate), evaluate=evaluate)) + + +class logaddexp(Function): + """ Logarithm of the sum of exponentiations of the inputs. + + Helper class for use with e.g. numpy.logaddexp + + See Also + ======== + + https://numpy.org/doc/stable/reference/generated/numpy.logaddexp.html + """ + nargs = 2 + + def __new__(cls, *args): + return Function.__new__(cls, *sorted(args, key=default_sort_key)) + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + wrt, other = self.args + elif argindex == 2: + other, wrt = self.args + else: + raise ArgumentIndexError(self, argindex) + return S.One/(S.One + exp(other-wrt)) + + def _eval_rewrite_as_log(self, x1, x2, **kwargs): + return _logaddexp(x1, x2) + + def _eval_evalf(self, *args, **kwargs): + return self.rewrite(log).evalf(*args, **kwargs) + + def _eval_simplify(self, *args, **kwargs): + a, b = (x.simplify(**kwargs) for x in self.args) + candidate = _logaddexp(a, b) + if candidate != _logaddexp(a, b, evaluate=False): + return candidate + else: + return logaddexp(a, b) + + +class logaddexp2(Function): + """ Logarithm of the sum of exponentiations of the inputs in base-2. + + Helper class for use with e.g. numpy.logaddexp2 + + See Also + ======== + + https://numpy.org/doc/stable/reference/generated/numpy.logaddexp2.html + """ + nargs = 2 + + def __new__(cls, *args): + return Function.__new__(cls, *sorted(args, key=default_sort_key)) + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + wrt, other = self.args + elif argindex == 2: + other, wrt = self.args + else: + raise ArgumentIndexError(self, argindex) + return S.One/(S.One + _exp2(other-wrt)) + + def _eval_rewrite_as_log(self, x1, x2, **kwargs): + return _logaddexp2(x1, x2) + + def _eval_evalf(self, *args, **kwargs): + return self.rewrite(log).evalf(*args, **kwargs) + + def _eval_simplify(self, *args, **kwargs): + a, b = (x.simplify(**kwargs).factor() for x in self.args) + candidate = _logaddexp2(a, b) + if candidate != _logaddexp2(a, b, evaluate=False): + return candidate + else: + return logaddexp2(a, b) + + +class amin(Token): + """ Minimum value along an axis. + + Helper class for use with e.g. numpy.amin + + + See Also + ======== + + https://numpy.org/doc/stable/reference/generated/numpy.amin.html + """ + __slots__ = _fields = ('array', 'axis') + defaults = {'axis': none} + _construct_axis = staticmethod(sympify) + + +class amax(Token): + """ Maximum value along an axis. + + Helper class for use with e.g. numpy.amax + + + See Also + ======== + + https://numpy.org/doc/stable/reference/generated/numpy.amax.html + """ + __slots__ = _fields = ('array', 'axis') + defaults = {'axis': none} + _construct_axis = staticmethod(sympify) + + +class maximum(Function): + """ Element-wise maximum of array elements. + + Helper class for use with e.g. numpy.maximum + + + See Also + ======== + + https://numpy.org/doc/stable/reference/generated/numpy.maximum.html + """ + + def _eval_rewrite_as_Max(self, *args): + return Max(*self.args) + + +class minimum(Function): + """ Element-wise minimum of array elements. + + Helper class for use with e.g. numpy.minimum + + + See Also + ======== + + https://numpy.org/doc/stable/reference/generated/numpy.minimum.html + """ + + def _eval_rewrite_as_Min(self, *args): + return Min(*self.args) diff --git a/phivenv/Lib/site-packages/sympy/codegen/pynodes.py b/phivenv/Lib/site-packages/sympy/codegen/pynodes.py new file mode 100644 index 0000000000000000000000000000000000000000..f0a08b4a79d32f63d345947d6be310b44504dbf5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/pynodes.py @@ -0,0 +1,11 @@ +from .abstract_nodes import List as AbstractList +from .ast import Token + + +class List(AbstractList): + pass + + +class NumExprEvaluate(Token): + """represents a call to :class:`numexpr`s :func:`evaluate`""" + __slots__ = _fields = ('expr',) diff --git a/phivenv/Lib/site-packages/sympy/codegen/pyutils.py b/phivenv/Lib/site-packages/sympy/codegen/pyutils.py new file mode 100644 index 0000000000000000000000000000000000000000..e14eabe92ce50105a4055b71a49767aae04610b9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/pyutils.py @@ -0,0 +1,24 @@ +from sympy.printing.pycode import PythonCodePrinter + +""" This module collects utilities for rendering Python code. """ + + +def render_as_module(content, standard='python3'): + """Renders Python code as a module (with the required imports). + + Parameters + ========== + + standard : + See the parameter ``standard`` in + :meth:`sympy.printing.pycode.pycode` + """ + + printer = PythonCodePrinter({'standard':standard}) + pystr = printer.doprint(content) + if printer._settings['fully_qualified_modules']: + module_imports_str = '\n'.join('import %s' % k for k in printer.module_imports) + else: + module_imports_str = '\n'.join(['from %s import %s' % (k, ', '.join(v)) for + k, v in printer.module_imports.items()]) + return module_imports_str + '\n\n' + pystr diff --git a/phivenv/Lib/site-packages/sympy/codegen/rewriting.py b/phivenv/Lib/site-packages/sympy/codegen/rewriting.py new file mode 100644 index 0000000000000000000000000000000000000000..274b7770b46ded6711468ab2a01db3a53d6fde87 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/rewriting.py @@ -0,0 +1,357 @@ +""" +Classes and functions useful for rewriting expressions for optimized code +generation. Some languages (or standards thereof), e.g. C99, offer specialized +math functions for better performance and/or precision. + +Using the ``optimize`` function in this module, together with a collection of +rules (represented as instances of ``Optimization``), one can rewrite the +expressions for this purpose:: + + >>> from sympy import Symbol, exp, log + >>> from sympy.codegen.rewriting import optimize, optims_c99 + >>> x = Symbol('x') + >>> optimize(3*exp(2*x) - 3, optims_c99) + 3*expm1(2*x) + >>> optimize(exp(2*x) - 1 - exp(-33), optims_c99) + expm1(2*x) - exp(-33) + >>> optimize(log(3*x + 3), optims_c99) + log1p(x) + log(3) + >>> optimize(log(2*x + 3), optims_c99) + log(2*x + 3) + +The ``optims_c99`` imported above is tuple containing the following instances +(which may be imported from ``sympy.codegen.rewriting``): + +- ``expm1_opt`` +- ``log1p_opt`` +- ``exp2_opt`` +- ``log2_opt`` +- ``log2const_opt`` + + +""" +from sympy.core.function import expand_log +from sympy.core.singleton import S +from sympy.core.symbol import Wild +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import (Max, Min) +from sympy.functions.elementary.trigonometric import (cos, sin, sinc) +from sympy.assumptions import Q, ask +from sympy.codegen.cfunctions import log1p, log2, exp2, expm1 +from sympy.codegen.matrix_nodes import MatrixSolve +from sympy.core.expr import UnevaluatedExpr +from sympy.core.power import Pow +from sympy.codegen.numpy_nodes import logaddexp, logaddexp2 +from sympy.codegen.scipy_nodes import cosm1, powm1 +from sympy.core.mul import Mul +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.utilities.iterables import sift + + +class Optimization: + """ Abstract base class for rewriting optimization. + + Subclasses should implement ``__call__`` taking an expression + as argument. + + Parameters + ========== + cost_function : callable returning number + priority : number + + """ + def __init__(self, cost_function=None, priority=1): + self.cost_function = cost_function + self.priority=priority + + def cheapest(self, *args): + return min(args, key=self.cost_function) + + +class ReplaceOptim(Optimization): + """ Rewriting optimization calling replace on expressions. + + Explanation + =========== + + The instance can be used as a function on expressions for which + it will apply the ``replace`` method (see + :meth:`sympy.core.basic.Basic.replace`). + + Parameters + ========== + + query : + First argument passed to replace. + value : + Second argument passed to replace. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.codegen.rewriting import ReplaceOptim + >>> from sympy.codegen.cfunctions import exp2 + >>> x = Symbol('x') + >>> exp2_opt = ReplaceOptim(lambda p: p.is_Pow and p.base == 2, + ... lambda p: exp2(p.exp)) + >>> exp2_opt(2**x) + exp2(x) + + """ + + def __init__(self, query, value, **kwargs): + super().__init__(**kwargs) + self.query = query + self.value = value + + def __call__(self, expr): + return expr.replace(self.query, self.value) + + +def optimize(expr, optimizations): + """ Apply optimizations to an expression. + + Parameters + ========== + + expr : expression + optimizations : iterable of ``Optimization`` instances + The optimizations will be sorted with respect to ``priority`` (highest first). + + Examples + ======== + + >>> from sympy import log, Symbol + >>> from sympy.codegen.rewriting import optims_c99, optimize + >>> x = Symbol('x') + >>> optimize(log(x+3)/log(2) + log(x**2 + 1), optims_c99) + log1p(x**2) + log2(x + 3) + + """ + + for optim in sorted(optimizations, key=lambda opt: opt.priority, reverse=True): + new_expr = optim(expr) + if optim.cost_function is None: + expr = new_expr + else: + expr = optim.cheapest(expr, new_expr) + return expr + + +exp2_opt = ReplaceOptim( + lambda p: p.is_Pow and p.base == 2, + lambda p: exp2(p.exp) +) + + +_d = Wild('d', properties=[lambda x: x.is_Dummy]) +_u = Wild('u', properties=[lambda x: not x.is_number and not x.is_Add]) +_v = Wild('v') +_w = Wild('w') +_n = Wild('n', properties=[lambda x: x.is_number]) + +sinc_opt1 = ReplaceOptim( + sin(_w)/_w, sinc(_w) +) +sinc_opt2 = ReplaceOptim( + sin(_n*_w)/_w, _n*sinc(_n*_w) +) +sinc_opts = (sinc_opt1, sinc_opt2) + +log2_opt = ReplaceOptim(_v*log(_w)/log(2), _v*log2(_w), cost_function=lambda expr: expr.count( + lambda e: ( # division & eval of transcendentals are expensive floating point operations... + e.is_Pow and e.exp.is_negative # division + or (isinstance(e, (log, log2)) and not e.args[0].is_number)) # transcendental + ) +) + +log2const_opt = ReplaceOptim(log(2)*log2(_w), log(_w)) + +logsumexp_2terms_opt = ReplaceOptim( + lambda l: (isinstance(l, log) + and l.args[0].is_Add + and len(l.args[0].args) == 2 + and all(isinstance(t, exp) for t in l.args[0].args)), + lambda l: ( + Max(*[e.args[0] for e in l.args[0].args]) + + log1p(exp(Min(*[e.args[0] for e in l.args[0].args]))) + ) +) + + +class FuncMinusOneOptim(ReplaceOptim): + """Specialization of ReplaceOptim for functions evaluating "f(x) - 1". + + Explanation + =========== + + Numerical functions which go toward one as x go toward zero is often best + implemented by a dedicated function in order to avoid catastrophic + cancellation. One such example is ``expm1(x)`` in the C standard library + which evaluates ``exp(x) - 1``. Such functions preserves many more + significant digits when its argument is much smaller than one, compared + to subtracting one afterwards. + + Parameters + ========== + + func : + The function which is subtracted by one. + func_m_1 : + The specialized function evaluating ``func(x) - 1``. + opportunistic : bool + When ``True``, apply the transformation as long as the magnitude of the + remaining number terms decreases. When ``False``, only apply the + transformation if it completely eliminates the number term. + + Examples + ======== + + >>> from sympy import symbols, exp + >>> from sympy.codegen.rewriting import FuncMinusOneOptim + >>> from sympy.codegen.cfunctions import expm1 + >>> x, y = symbols('x y') + >>> expm1_opt = FuncMinusOneOptim(exp, expm1) + >>> expm1_opt(exp(x) + 2*exp(5*y) - 3) + expm1(x) + 2*expm1(5*y) + + + """ + + def __init__(self, func, func_m_1, opportunistic=True): + weight = 10 # <-- this is an arbitrary number (heuristic) + super().__init__(lambda e: e.is_Add, self.replace_in_Add, + cost_function=lambda expr: expr.count_ops() - weight*expr.count(func_m_1)) + self.func = func + self.func_m_1 = func_m_1 + self.opportunistic = opportunistic + + def _group_Add_terms(self, add): + numbers, non_num = sift(add.args, lambda arg: arg.is_number, binary=True) + numsum = sum(numbers) + terms_with_func, other = sift(non_num, lambda arg: arg.has(self.func), binary=True) + return numsum, terms_with_func, other + + def replace_in_Add(self, e): + """ passed as second argument to Basic.replace(...) """ + numsum, terms_with_func, other_non_num_terms = self._group_Add_terms(e) + if numsum == 0: + return e + substituted, untouched = [], [] + for with_func in terms_with_func: + if with_func.is_Mul: + func, coeff = sift(with_func.args, lambda arg: arg.func == self.func, binary=True) + if len(func) == 1 and len(coeff) == 1: + func, coeff = func[0], coeff[0] + else: + coeff = None + elif with_func.func == self.func: + func, coeff = with_func, S.One + else: + coeff = None + + if coeff is not None and coeff.is_number and sign(coeff) == -sign(numsum): + if self.opportunistic: + do_substitute = abs(coeff+numsum) < abs(numsum) + else: + do_substitute = coeff+numsum == 0 + + if do_substitute: # advantageous substitution + numsum += coeff + substituted.append(coeff*self.func_m_1(*func.args)) + continue + untouched.append(with_func) + + return e.func(numsum, *substituted, *untouched, *other_non_num_terms) + + def __call__(self, expr): + alt1 = super().__call__(expr) + alt2 = super().__call__(expr.factor()) + return self.cheapest(alt1, alt2) + + +expm1_opt = FuncMinusOneOptim(exp, expm1) +cosm1_opt = FuncMinusOneOptim(cos, cosm1) +powm1_opt = FuncMinusOneOptim(Pow, powm1) + +log1p_opt = ReplaceOptim( + lambda e: isinstance(e, log), + lambda l: expand_log(l.replace( + log, lambda arg: log(arg.factor()) + )).replace(log(_u+1), log1p(_u)) +) + +def create_expand_pow_optimization(limit, *, base_req=lambda b: b.is_symbol): + """ Creates an instance of :class:`ReplaceOptim` for expanding ``Pow``. + + Explanation + =========== + + The requirements for expansions are that the base needs to be a symbol + and the exponent needs to be an Integer (and be less than or equal to + ``limit``). + + Parameters + ========== + + limit : int + The highest power which is expanded into multiplication. + base_req : function returning bool + Requirement on base for expansion to happen, default is to return + the ``is_symbol`` attribute of the base. + + Examples + ======== + + >>> from sympy import Symbol, sin + >>> from sympy.codegen.rewriting import create_expand_pow_optimization + >>> x = Symbol('x') + >>> expand_opt = create_expand_pow_optimization(3) + >>> expand_opt(x**5 + x**3) + x**5 + x*x*x + >>> expand_opt(x**5 + x**3 + sin(x)**3) + x**5 + sin(x)**3 + x*x*x + >>> opt2 = create_expand_pow_optimization(3, base_req=lambda b: not b.is_Function) + >>> opt2((x+1)**2 + sin(x)**2) + sin(x)**2 + (x + 1)*(x + 1) + + """ + return ReplaceOptim( + lambda e: e.is_Pow and base_req(e.base) and e.exp.is_Integer and abs(e.exp) <= limit, + lambda p: ( + UnevaluatedExpr(Mul(*([p.base]*+p.exp), evaluate=False)) if p.exp > 0 else + 1/UnevaluatedExpr(Mul(*([p.base]*-p.exp), evaluate=False)) + )) + +# Optimization procedures for turning A**(-1) * x into MatrixSolve(A, x) +def _matinv_predicate(expr): + # TODO: We should be able to support more than 2 elements + if expr.is_MatMul and len(expr.args) == 2: + left, right = expr.args + if left.is_Inverse and right.shape[1] == 1: + inv_arg = left.arg + if isinstance(inv_arg, MatrixSymbol): + return bool(ask(Q.fullrank(left.arg))) + + return False + +def _matinv_transform(expr): + left, right = expr.args + inv_arg = left.arg + return MatrixSolve(inv_arg, right) + + +matinv_opt = ReplaceOptim(_matinv_predicate, _matinv_transform) + + +logaddexp_opt = ReplaceOptim(log(exp(_v)+exp(_w)), logaddexp(_v, _w)) +logaddexp2_opt = ReplaceOptim(log(Pow(2, _v)+Pow(2, _w)), logaddexp2(_v, _w)*log(2)) + +# Collections of optimizations: +optims_c99 = (expm1_opt, log1p_opt, exp2_opt, log2_opt, log2const_opt) + +optims_numpy = optims_c99 + (logaddexp_opt, logaddexp2_opt,) + sinc_opts + +optims_scipy = (cosm1_opt, powm1_opt) diff --git a/phivenv/Lib/site-packages/sympy/codegen/scipy_nodes.py b/phivenv/Lib/site-packages/sympy/codegen/scipy_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..059a853fc8cac6e0b9a1a3c7395dd3a15384dcba --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/scipy_nodes.py @@ -0,0 +1,79 @@ +from sympy.core.function import Add, ArgumentIndexError, Function +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.trigonometric import cos, sin + + +def _cosm1(x, *, evaluate=True): + return Add(cos(x, evaluate=evaluate), -S.One, evaluate=evaluate) + + +class cosm1(Function): + """ Minus one plus cosine of x, i.e. cos(x) - 1. For use when x is close to zero. + + Helper class for use with e.g. scipy.special.cosm1 + See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.cosm1.html + """ + nargs = 1 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return -sin(*self.args) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_cos(self, x, **kwargs): + return _cosm1(x) + + def _eval_evalf(self, *args, **kwargs): + return self.rewrite(cos).evalf(*args, **kwargs) + + def _eval_simplify(self, **kwargs): + x, = self.args + candidate = _cosm1(x.simplify(**kwargs)) + if candidate != _cosm1(x, evaluate=False): + return candidate + else: + return cosm1(x) + + +def _powm1(x, y, *, evaluate=True): + return Add(Pow(x, y, evaluate=evaluate), -S.One, evaluate=evaluate) + + +class powm1(Function): + """ Minus one plus x to the power of y, i.e. x**y - 1. For use when x is close to one or y is close to zero. + + Helper class for use with e.g. scipy.special.powm1 + See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.powm1.html + """ + nargs = 2 + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return Pow(self.args[0], self.args[1])*self.args[1]/self.args[0] + elif argindex == 2: + return log(self.args[0])*Pow(*self.args) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Pow(self, x, y, **kwargs): + return _powm1(x, y) + + def _eval_evalf(self, *args, **kwargs): + return self.rewrite(Pow).evalf(*args, **kwargs) + + def _eval_simplify(self, **kwargs): + x, y = self.args + candidate = _powm1(x.simplify(**kwargs), y.simplify(**kwargs)) + if candidate != _powm1(x, y, evaluate=False): + return candidate + else: + return powm1(x, y) diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__init__.py b/phivenv/Lib/site-packages/sympy/codegen/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..101dafefbffd4888345f8ed2893b18c37d9e4369 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_abstract_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_abstract_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62abeca4389de64e2b563dddb4a688b65e366b45 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_abstract_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_algorithms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_algorithms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eef166b45435b15ad4ad8c87a5db01e6297c30a5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_algorithms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_applications.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_applications.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f05752a91c9ba0e08776715930daa569a5440654 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_applications.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_approximations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_approximations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..964ea1d72c27ff9bc8d5697a2f258623dfcd775d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_approximations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_ast.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_ast.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fe6a41a0fbc9b1fa31b9b47289db16e82c1a39d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_ast.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cfunctions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cfunctions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..342365a3d69350c84eb90c1ecd4e6c22cda89dd3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cfunctions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cnodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cnodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5736ff73a4facf2705b7e8ea18d3c641dc9e8cd3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cnodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cxxnodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cxxnodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..373f25c65f390b8d835b2b9d009f1ead113db9f0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_cxxnodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_fnodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_fnodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f20a1dbe838361a106ed91612872c494f9d28665 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_fnodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_matrix_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_matrix_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fd5181f15543833ace255454da5aa9db8e1c2a7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_matrix_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_numpy_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_numpy_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a070ea41993ceaa27e63b511a8eeeac841388ee9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_numpy_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_pynodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_pynodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56fa4e9eee31dee339ce4f87f77bfbee610af83d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_pynodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_pyutils.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_pyutils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9847d2f3b99e12a2c91ed0cdd80c2e46f4243b8b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_pyutils.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_rewriting.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_rewriting.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ff9515702c687a5c0f01d1b07e22c69d7efd53a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_rewriting.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_scipy_nodes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_scipy_nodes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69ede9d95dd4a0ebba4167ec214c8401628f87da Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/codegen/tests/__pycache__/test_scipy_nodes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/test_abstract_nodes.py b/phivenv/Lib/site-packages/sympy/codegen/tests/test_abstract_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..89e1f73ff8cb24a4a865aa51304ec66e9901e3cb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/tests/test_abstract_nodes.py @@ -0,0 +1,14 @@ +from sympy.core.symbol import symbols +from sympy.codegen.abstract_nodes import List + + +def test_List(): + l = List(2, 3, 4) + assert l == List(2, 3, 4) + assert str(l) == "[2, 3, 4]" + x, y, z = symbols('x y z') + l = List(x**2,y**3,z**4) + # contrary to python's built-in list, we can call e.g. "replace" on List. + m = l.replace(lambda arg: arg.is_Pow and arg.exp>2, lambda p: p.base-p.exp) + assert m == [x**2, y-3, z-4] + hash(m) diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/test_algorithms.py b/phivenv/Lib/site-packages/sympy/codegen/tests/test_algorithms.py new file mode 100644 index 0000000000000000000000000000000000000000..c684229ec18a1e02a97eee6db8537b8d12af0582 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/tests/test_algorithms.py @@ -0,0 +1,180 @@ +import tempfile +from sympy import log, Min, Max, sqrt +from sympy.core.numbers import Float +from sympy.core.symbol import Symbol, symbols +from sympy.functions.elementary.trigonometric import cos +from sympy.codegen.ast import Assignment, Raise, RuntimeError_, QuotedString +from sympy.codegen.algorithms import newtons_method, newtons_method_function +from sympy.codegen.cfunctions import expm1 +from sympy.codegen.fnodes import bind_C +from sympy.codegen.futils import render_as_module as f_module +from sympy.codegen.pyutils import render_as_module as py_module +from sympy.external import import_module +from sympy.printing.codeprinter import ccode +from sympy.utilities._compilation import compile_link_import_strings, has_c, has_fortran +from sympy.utilities._compilation.util import may_xfail +from sympy.testing.pytest import skip, raises, skip_under_pyodide + +cython = import_module('cython') +wurlitzer = import_module('wurlitzer') + +def test_newtons_method(): + x, dx, atol = symbols('x dx atol') + expr = cos(x) - x**3 + algo = newtons_method(expr, x, atol, dx) + assert algo.has(Assignment(dx, -expr/expr.diff(x))) + + +@may_xfail +def test_newtons_method_function__ccode(): + x = Symbol('x', real=True) + expr = cos(x) - x**3 + func = newtons_method_function(expr, x) + + if not cython: + skip("cython not installed.") + if not has_c(): + skip("No C compiler found.") + + compile_kw = {"std": 'c99'} + with tempfile.TemporaryDirectory() as folder: + mod, info = compile_link_import_strings([ + ('newton.c', ('#include \n' + '#include \n') + ccode(func)), + ('_newton.pyx', ("#cython: language_level={}\n".format("3") + + "cdef extern double newton(double)\n" + "def py_newton(x):\n" + " return newton(x)\n")) + ], build_dir=folder, compile_kwargs=compile_kw) + assert abs(mod.py_newton(0.5) - 0.865474033102) < 1e-12 + + +@may_xfail +def test_newtons_method_function__fcode(): + x = Symbol('x', real=True) + expr = cos(x) - x**3 + func = newtons_method_function(expr, x, attrs=[bind_C(name='newton')]) + + if not cython: + skip("cython not installed.") + if not has_fortran(): + skip("No Fortran compiler found.") + + f_mod = f_module([func], 'mod_newton') + with tempfile.TemporaryDirectory() as folder: + mod, info = compile_link_import_strings([ + ('newton.f90', f_mod), + ('_newton.pyx', ("#cython: language_level={}\n".format("3") + + "cdef extern double newton(double*)\n" + "def py_newton(double x):\n" + " return newton(&x)\n")) + ], build_dir=folder) + assert abs(mod.py_newton(0.5) - 0.865474033102) < 1e-12 + + +def test_newtons_method_function__pycode(): + x = Symbol('x', real=True) + expr = cos(x) - x**3 + func = newtons_method_function(expr, x) + py_mod = py_module(func) + namespace = {} + exec(py_mod, namespace, namespace) + res = eval('newton(0.5)', namespace) + assert abs(res - 0.865474033102) < 1e-12 + + +@may_xfail +@skip_under_pyodide("Emscripten does not support process spawning") +def test_newtons_method_function__ccode_parameters(): + args = x, A, k, p = symbols('x A k p') + expr = A*cos(k*x) - p*x**3 + raises(ValueError, lambda: newtons_method_function(expr, x)) + use_wurlitzer = wurlitzer + + func = newtons_method_function(expr, x, args, debug=use_wurlitzer) + + if not has_c(): + skip("No C compiler found.") + if not cython: + skip("cython not installed.") + + compile_kw = {"std": 'c99'} + with tempfile.TemporaryDirectory() as folder: + mod, info = compile_link_import_strings([ + ('newton_par.c', ('#include \n' + '#include \n') + ccode(func)), + ('_newton_par.pyx', ("#cython: language_level={}\n".format("3") + + "cdef extern double newton(double, double, double, double)\n" + "def py_newton(x, A=1, k=1, p=1):\n" + " return newton(x, A, k, p)\n")) + ], compile_kwargs=compile_kw, build_dir=folder) + + if use_wurlitzer: + with wurlitzer.pipes() as (out, err): + result = mod.py_newton(0.5) + else: + result = mod.py_newton(0.5) + + assert abs(result - 0.865474033102) < 1e-12 + + if not use_wurlitzer: + skip("C-level output only tested when package 'wurlitzer' is available.") + + out, err = out.read(), err.read() + assert err == '' + assert out == """\ +x= 0.5 +x= 1.1121 d_x= 0.61214 +x= 0.90967 d_x= -0.20247 +x= 0.86726 d_x= -0.042409 +x= 0.86548 d_x= -0.0017867 +x= 0.86547 d_x= -3.1022e-06 +x= 0.86547 d_x= -9.3421e-12 +x= 0.86547 d_x= 3.6902e-17 +""" # try to run tests with LC_ALL=C if this assertion fails + + +def test_newtons_method_function__rtol_cse_nan(): + a, b, c, N_geo, N_tot = symbols('a b c N_geo N_tot', real=True, nonnegative=True) + i = Symbol('i', integer=True, nonnegative=True) + N_ari = N_tot - N_geo - 1 + delta_ari = (c-b)/N_ari + ln_delta_geo = log(b) + log(-expm1((log(a)-log(b))/N_geo)) + eqb_log = ln_delta_geo - log(delta_ari) + + def _clamp(low, expr, high): + return Min(Max(low, expr), high) + + meth_kw = { + 'clamped_newton': {'delta_fn': lambda e, x: _clamp( + (sqrt(a*x)-x)*0.99, + -e/e.diff(x), + (sqrt(c*x)-x)*0.99 + )}, + 'halley': {'delta_fn': lambda e, x: (-2*(e*e.diff(x))/(2*e.diff(x)**2 - e*e.diff(x, 2)))}, + 'halley_alt': {'delta_fn': lambda e, x: (-e/e.diff(x)/(1-e/e.diff(x)*e.diff(x,2)/2/e.diff(x)))}, + } + args = eqb_log, b + for use_cse in [False, True]: + kwargs = { + 'params': (b, a, c, N_geo, N_tot), 'itermax': 60, 'debug': True, 'cse': use_cse, + 'counter': i, 'atol': 1e-100, 'rtol': 2e-16, 'bounds': (a,c), + 'handle_nan': Raise(RuntimeError_(QuotedString("encountered NaN."))) + } + func = {k: newtons_method_function(*args, func_name=f"{k}_b", **dict(kwargs, **kw)) for k, kw in meth_kw.items()} + py_mod = {k: py_module(v) for k, v in func.items()} + namespace = {} + root_find_b = {} + for k, v in py_mod.items(): + ns = namespace[k] = {} + exec(v, ns, ns) + root_find_b[k] = ns[f'{k}_b'] + ref = Float('13.2261515064168768938151923226496') + reftol = {'clamped_newton': 2e-16, 'halley': 2e-16, 'halley_alt': 3e-16} + guess = 4.0 + for meth, func in root_find_b.items(): + result = func(guess, 1e-2, 1e2, 50, 100) + req = ref*reftol[meth] + if use_cse: + req *= 2 + assert abs(result - ref) < req diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/test_applications.py b/phivenv/Lib/site-packages/sympy/codegen/tests/test_applications.py new file mode 100644 index 0000000000000000000000000000000000000000..9519c06b96042b383314ef928d2ad0c1a2f92650 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/tests/test_applications.py @@ -0,0 +1,58 @@ +# This file contains tests that exercise multiple AST nodes + +import tempfile + +from sympy.external import import_module +from sympy.printing.codeprinter import ccode +from sympy.utilities._compilation import compile_link_import_strings, has_c +from sympy.utilities._compilation.util import may_xfail +from sympy.testing.pytest import skip, skip_under_pyodide +from sympy.codegen.ast import ( + FunctionDefinition, FunctionPrototype, Variable, Pointer, real, Assignment, + integer, CodeBlock, While +) +from sympy.codegen.cnodes import void, PreIncrement +from sympy.codegen.cutils import render_as_source_file + +cython = import_module('cython') +np = import_module('numpy') + +def _mk_func1(): + declars = n, inp, out = Variable('n', integer), Pointer('inp', real), Pointer('out', real) + i = Variable('i', integer) + whl = While(i2, lambda p: p.base-p.exp) + assert m == [x**2, y-3, z-4] diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/test_pyutils.py b/phivenv/Lib/site-packages/sympy/codegen/tests/test_pyutils.py new file mode 100644 index 0000000000000000000000000000000000000000..0a2f0ff358f333635c8d44195a5c39d63ac8f16f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/tests/test_pyutils.py @@ -0,0 +1,7 @@ +from sympy.codegen.ast import Print +from sympy.codegen.pyutils import render_as_module + +def test_standard(): + ast = Print('x y'.split(), r"coordinate: %12.5g %12.5g\n") + assert render_as_module(ast, standard='python3') == \ + '\n\nprint("coordinate: %12.5g %12.5g\\n" % (x, y), end="")' diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/test_rewriting.py b/phivenv/Lib/site-packages/sympy/codegen/tests/test_rewriting.py new file mode 100644 index 0000000000000000000000000000000000000000..51e0c9ecc940f60186cc04d4bf15650281d31cd8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/tests/test_rewriting.py @@ -0,0 +1,479 @@ +import tempfile +from sympy.core.numbers import pi, Rational +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.trigonometric import (cos, sin, sinc) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.assumptions import assuming, Q +from sympy.external import import_module +from sympy.printing.codeprinter import ccode +from sympy.codegen.matrix_nodes import MatrixSolve +from sympy.codegen.cfunctions import log2, exp2, expm1, log1p +from sympy.codegen.numpy_nodes import logaddexp, logaddexp2 +from sympy.codegen.scipy_nodes import cosm1, powm1 +from sympy.codegen.rewriting import ( + optimize, cosm1_opt, log2_opt, exp2_opt, expm1_opt, log1p_opt, powm1_opt, optims_c99, + create_expand_pow_optimization, matinv_opt, logaddexp_opt, logaddexp2_opt, + optims_numpy, optims_scipy, sinc_opts, FuncMinusOneOptim +) +from sympy.testing.pytest import XFAIL, skip +from sympy.utilities import lambdify +from sympy.utilities._compilation import compile_link_import_strings, has_c +from sympy.utilities._compilation.util import may_xfail + +cython = import_module('cython') +numpy = import_module('numpy') +scipy = import_module('scipy') + + +def test_log2_opt(): + x = Symbol('x') + expr1 = 7*log(3*x + 5)/(log(2)) + opt1 = optimize(expr1, [log2_opt]) + assert opt1 == 7*log2(3*x + 5) + assert opt1.rewrite(log) == expr1 + + expr2 = 3*log(5*x + 7)/(13*log(2)) + opt2 = optimize(expr2, [log2_opt]) + assert opt2 == 3*log2(5*x + 7)/13 + assert opt2.rewrite(log) == expr2 + + expr3 = log(x)/log(2) + opt3 = optimize(expr3, [log2_opt]) + assert opt3 == log2(x) + assert opt3.rewrite(log) == expr3 + + expr4 = log(x)/log(2) + log(x+1) + opt4 = optimize(expr4, [log2_opt]) + assert opt4 == log2(x) + log(2)*log2(x+1) + assert opt4.rewrite(log) == expr4 + + expr5 = log(17) + opt5 = optimize(expr5, [log2_opt]) + assert opt5 == expr5 + + expr6 = log(x + 3)/log(2) + opt6 = optimize(expr6, [log2_opt]) + assert str(opt6) == 'log2(x + 3)' + assert opt6.rewrite(log) == expr6 + + +def test_exp2_opt(): + x = Symbol('x') + expr1 = 1 + 2**x + opt1 = optimize(expr1, [exp2_opt]) + assert opt1 == 1 + exp2(x) + assert opt1.rewrite(Pow) == expr1 + + expr2 = 1 + 3**x + assert expr2 == optimize(expr2, [exp2_opt]) + + +def test_expm1_opt(): + x = Symbol('x') + + expr1 = exp(x) - 1 + opt1 = optimize(expr1, [expm1_opt]) + assert expm1(x) - opt1 == 0 + assert opt1.rewrite(exp) == expr1 + + expr2 = 3*exp(x) - 3 + opt2 = optimize(expr2, [expm1_opt]) + assert 3*expm1(x) == opt2 + assert opt2.rewrite(exp) == expr2 + + expr3 = 3*exp(x) - 5 + opt3 = optimize(expr3, [expm1_opt]) + assert 3*expm1(x) - 2 == opt3 + assert opt3.rewrite(exp) == expr3 + expm1_opt_non_opportunistic = FuncMinusOneOptim(exp, expm1, opportunistic=False) + assert expr3 == optimize(expr3, [expm1_opt_non_opportunistic]) + assert opt1 == optimize(expr1, [expm1_opt_non_opportunistic]) + assert opt2 == optimize(expr2, [expm1_opt_non_opportunistic]) + + expr4 = 3*exp(x) + log(x) - 3 + opt4 = optimize(expr4, [expm1_opt]) + assert 3*expm1(x) + log(x) == opt4 + assert opt4.rewrite(exp) == expr4 + + expr5 = 3*exp(2*x) - 3 + opt5 = optimize(expr5, [expm1_opt]) + assert 3*expm1(2*x) == opt5 + assert opt5.rewrite(exp) == expr5 + + expr6 = (2*exp(x) + 1)/(exp(x) + 1) + 1 + opt6 = optimize(expr6, [expm1_opt]) + assert opt6.count_ops() <= expr6.count_ops() + + def ev(e): + return e.subs(x, 3).evalf() + assert abs(ev(expr6) - ev(opt6)) < 1e-15 + + y = Symbol('y') + expr7 = (2*exp(x) - 1)/(1 - exp(y)) - 1/(1-exp(y)) + opt7 = optimize(expr7, [expm1_opt]) + assert -2*expm1(x)/expm1(y) == opt7 + assert (opt7.rewrite(exp) - expr7).factor() == 0 + + expr8 = (1+exp(x))**2 - 4 + opt8 = optimize(expr8, [expm1_opt]) + tgt8a = (exp(x) + 3)*expm1(x) + tgt8b = 2*expm1(x) + expm1(2*x) + # Both tgt8a & tgt8b seem to give full precision (~16 digits for double) + # for x=1e-7 (compare with expr8 which only achieves ~8 significant digits). + # If we can show that either tgt8a or tgt8b is preferable, we can + # change this test to ensure the preferable version is returned. + assert (tgt8a - tgt8b).rewrite(exp).factor() == 0 + assert opt8 in (tgt8a, tgt8b) + assert (opt8.rewrite(exp) - expr8).factor() == 0 + + expr9 = sin(expr8) + opt9 = optimize(expr9, [expm1_opt]) + tgt9a = sin(tgt8a) + tgt9b = sin(tgt8b) + assert opt9 in (tgt9a, tgt9b) + assert (opt9.rewrite(exp) - expr9.rewrite(exp)).factor().is_zero + + +def test_expm1_two_exp_terms(): + x, y = map(Symbol, 'x y'.split()) + expr1 = exp(x) + exp(y) - 2 + opt1 = optimize(expr1, [expm1_opt]) + assert opt1 == expm1(x) + expm1(y) + + +def test_cosm1_opt(): + x = Symbol('x') + + expr1 = cos(x) - 1 + opt1 = optimize(expr1, [cosm1_opt]) + assert cosm1(x) - opt1 == 0 + assert opt1.rewrite(cos) == expr1 + + expr2 = 3*cos(x) - 3 + opt2 = optimize(expr2, [cosm1_opt]) + assert 3*cosm1(x) == opt2 + assert opt2.rewrite(cos) == expr2 + + expr3 = 3*cos(x) - 5 + opt3 = optimize(expr3, [cosm1_opt]) + assert 3*cosm1(x) - 2 == opt3 + assert opt3.rewrite(cos) == expr3 + cosm1_opt_non_opportunistic = FuncMinusOneOptim(cos, cosm1, opportunistic=False) + assert expr3 == optimize(expr3, [cosm1_opt_non_opportunistic]) + assert opt1 == optimize(expr1, [cosm1_opt_non_opportunistic]) + assert opt2 == optimize(expr2, [cosm1_opt_non_opportunistic]) + + expr4 = 3*cos(x) + log(x) - 3 + opt4 = optimize(expr4, [cosm1_opt]) + assert 3*cosm1(x) + log(x) == opt4 + assert opt4.rewrite(cos) == expr4 + + expr5 = 3*cos(2*x) - 3 + opt5 = optimize(expr5, [cosm1_opt]) + assert 3*cosm1(2*x) == opt5 + assert opt5.rewrite(cos) == expr5 + + expr6 = 2 - 2*cos(x) + opt6 = optimize(expr6, [cosm1_opt]) + assert -2*cosm1(x) == opt6 + assert opt6.rewrite(cos) == expr6 + + +def test_cosm1_two_cos_terms(): + x, y = map(Symbol, 'x y'.split()) + expr1 = cos(x) + cos(y) - 2 + opt1 = optimize(expr1, [cosm1_opt]) + assert opt1 == cosm1(x) + cosm1(y) + + +def test_expm1_cosm1_mixed(): + x = Symbol('x') + expr1 = exp(x) + cos(x) - 2 + opt1 = optimize(expr1, [expm1_opt, cosm1_opt]) + assert opt1 == cosm1(x) + expm1(x) + + +def _check_num_lambdify(expr, opt, val_subs, approx_ref, lambdify_kw=None, poorness=1e10): + """ poorness=1e10 signifies that `expr` loses precision of at least ten decimal digits. """ + num_ref = expr.subs(val_subs).evalf() + eps = numpy.finfo(numpy.float64).eps + assert abs(num_ref - approx_ref) < approx_ref*eps + f1 = lambdify(list(val_subs.keys()), opt, **(lambdify_kw or {})) + args_float = tuple(map(float, val_subs.values())) + num_err1 = abs(f1(*args_float) - approx_ref) + assert num_err1 < abs(num_ref*eps) + f2 = lambdify(list(val_subs.keys()), expr, **(lambdify_kw or {})) + num_err2 = abs(f2(*args_float) - approx_ref) + assert num_err2 > abs(num_ref*eps*poorness) # this only ensures that the *test* works as intended + + +def test_cosm1_apart(): + x = Symbol('x') + + expr1 = 1/cos(x) - 1 + opt1 = optimize(expr1, [cosm1_opt]) + assert opt1 == -cosm1(x)/cos(x) + if scipy: + _check_num_lambdify(expr1, opt1, {x: S(10)**-30}, 5e-61, lambdify_kw={"modules": 'scipy'}) + + expr2 = 2/cos(x) - 2 + opt2 = optimize(expr2, optims_scipy) + assert opt2 == -2*cosm1(x)/cos(x) + if scipy: + _check_num_lambdify(expr2, opt2, {x: S(10)**-30}, 1e-60, lambdify_kw={"modules": 'scipy'}) + + expr3 = pi/cos(3*x) - pi + opt3 = optimize(expr3, [cosm1_opt]) + assert opt3 == -pi*cosm1(3*x)/cos(3*x) + if scipy: + _check_num_lambdify(expr3, opt3, {x: S(10)**-30/3}, float(5e-61*pi), lambdify_kw={"modules": 'scipy'}) + + +def test_powm1(): + args = x, y = map(Symbol, "xy") + + expr1 = x**y - 1 + opt1 = optimize(expr1, [powm1_opt]) + assert opt1 == powm1(x, y) + for arg in args: + assert expr1.diff(arg) == opt1.diff(arg) + if scipy and tuple(map(int, scipy.version.version.split('.')[:3])) >= (1, 10, 0): + subs1_a = {x: Rational(*(1.0+1e-13).as_integer_ratio()), y: pi} + ref1_f64_a = 3.139081648208105e-13 + _check_num_lambdify(expr1, opt1, subs1_a, ref1_f64_a, lambdify_kw={"modules": 'scipy'}, poorness=10**11) + + subs1_b = {x: pi, y: Rational(*(1e-10).as_integer_ratio())} + ref1_f64_b = 1.1447298859149205e-10 + _check_num_lambdify(expr1, opt1, subs1_b, ref1_f64_b, lambdify_kw={"modules": 'scipy'}, poorness=10**9) + + +def test_log1p_opt(): + x = Symbol('x') + expr1 = log(x + 1) + opt1 = optimize(expr1, [log1p_opt]) + assert log1p(x) - opt1 == 0 + assert opt1.rewrite(log) == expr1 + + expr2 = log(3*x + 3) + opt2 = optimize(expr2, [log1p_opt]) + assert log1p(x) + log(3) == opt2 + assert (opt2.rewrite(log) - expr2).simplify() == 0 + + expr3 = log(2*x + 1) + opt3 = optimize(expr3, [log1p_opt]) + assert log1p(2*x) - opt3 == 0 + assert opt3.rewrite(log) == expr3 + + expr4 = log(x+3) + opt4 = optimize(expr4, [log1p_opt]) + assert str(opt4) == 'log(x + 3)' + + +def test_optims_c99(): + x = Symbol('x') + + expr1 = 2**x + log(x)/log(2) + log(x + 1) + exp(x) - 1 + opt1 = optimize(expr1, optims_c99).simplify() + assert opt1 == exp2(x) + log2(x) + log1p(x) + expm1(x) + assert opt1.rewrite(exp).rewrite(log).rewrite(Pow) == expr1 + + expr2 = log(x)/log(2) + log(x + 1) + opt2 = optimize(expr2, optims_c99) + assert opt2 == log2(x) + log1p(x) + assert opt2.rewrite(log) == expr2 + + expr3 = log(x)/log(2) + log(17*x + 17) + opt3 = optimize(expr3, optims_c99) + delta3 = opt3 - (log2(x) + log(17) + log1p(x)) + assert delta3 == 0 + assert (opt3.rewrite(log) - expr3).simplify() == 0 + + expr4 = 2**x + 3*log(5*x + 7)/(13*log(2)) + 11*exp(x) - 11 + log(17*x + 17) + opt4 = optimize(expr4, optims_c99).simplify() + delta4 = opt4 - (exp2(x) + 3*log2(5*x + 7)/13 + 11*expm1(x) + log(17) + log1p(x)) + assert delta4 == 0 + assert (opt4.rewrite(exp).rewrite(log).rewrite(Pow) - expr4).simplify() == 0 + + expr5 = 3*exp(2*x) - 3 + opt5 = optimize(expr5, optims_c99) + delta5 = opt5 - 3*expm1(2*x) + assert delta5 == 0 + assert opt5.rewrite(exp) == expr5 + + expr6 = exp(2*x) - 3 + opt6 = optimize(expr6, optims_c99) + assert opt6 in (expm1(2*x) - 2, expr6) # expm1(2*x) - 2 is not better or worse + + expr7 = log(3*x + 3) + opt7 = optimize(expr7, optims_c99) + delta7 = opt7 - (log(3) + log1p(x)) + assert delta7 == 0 + assert (opt7.rewrite(log) - expr7).simplify() == 0 + + expr8 = log(2*x + 3) + opt8 = optimize(expr8, optims_c99) + assert opt8 == expr8 + + +def test_create_expand_pow_optimization(): + cc = lambda x: ccode( + optimize(x, [create_expand_pow_optimization(4)])) + x = Symbol('x') + assert cc(x**4) == 'x*x*x*x' + assert cc(x**4 + x**2) == 'x*x + x*x*x*x' + assert cc(x**5 + x**4) == 'pow(x, 5) + x*x*x*x' + assert cc(sin(x)**4) == 'pow(sin(x), 4)' + # gh issue 15335 + assert cc(x**(-4)) == '1.0/(x*x*x*x)' + assert cc(x**(-5)) == 'pow(x, -5)' + assert cc(-x**4) == '-(x*x*x*x)' + assert cc(x**4 - x**2) == '-(x*x) + x*x*x*x' + i = Symbol('i', integer=True) + assert cc(x**i - x**2) == 'pow(x, i) - (x*x)' + y = Symbol('y', real=True) + assert cc(Abs(exp(y**4))) == "exp(y*y*y*y)" + + # gh issue 20753 + cc2 = lambda x: ccode(optimize(x, [create_expand_pow_optimization( + 4, base_req=lambda b: b.is_Function)])) + assert cc2(x**3 + sin(x)**3) == "pow(x, 3) + sin(x)*sin(x)*sin(x)" + + +def test_matsolve(): + n = Symbol('n', integer=True) + A = MatrixSymbol('A', n, n) + x = MatrixSymbol('x', n, 1) + + with assuming(Q.fullrank(A)): + assert optimize(A**(-1) * x, [matinv_opt]) == MatrixSolve(A, x) + assert optimize(A**(-1) * x + x, [matinv_opt]) == MatrixSolve(A, x) + x + + +def test_logaddexp_opt(): + x, y = map(Symbol, 'x y'.split()) + expr1 = log(exp(x) + exp(y)) + opt1 = optimize(expr1, [logaddexp_opt]) + assert logaddexp(x, y) - opt1 == 0 + assert logaddexp(y, x) - opt1 == 0 + assert opt1.rewrite(log) == expr1 + + +def test_logaddexp2_opt(): + x, y = map(Symbol, 'x y'.split()) + expr1 = log(2**x + 2**y)/log(2) + opt1 = optimize(expr1, [logaddexp2_opt]) + assert logaddexp2(x, y) - opt1 == 0 + assert logaddexp2(y, x) - opt1 == 0 + assert opt1.rewrite(log) == expr1 + + +def test_sinc_opts(): + def check(d): + for k, v in d.items(): + assert optimize(k, sinc_opts) == v + + x = Symbol('x') + check({ + sin(x)/x : sinc(x), + sin(2*x)/(2*x) : sinc(2*x), + sin(3*x)/x : 3*sinc(3*x), + x*sin(x) : x*sin(x) + }) + + y = Symbol('y') + check({ + sin(x*y)/(x*y) : sinc(x*y), + y*sin(x/y)/x : sinc(x/y), + sin(sin(x))/sin(x) : sinc(sin(x)), + sin(3*sin(x))/sin(x) : 3*sinc(3*sin(x)), + sin(x)/y : sin(x)/y + }) + + +def test_optims_numpy(): + def check(d): + for k, v in d.items(): + assert optimize(k, optims_numpy) == v + + x = Symbol('x') + check({ + sin(2*x)/(2*x) + exp(2*x) - 1: sinc(2*x) + expm1(2*x), + log(x+3)/log(2) + log(x**2 + 1): log1p(x**2) + log2(x+3) + }) + + +@XFAIL # room for improvement, ideally this test case should pass. +def test_optims_numpy_TODO(): + def check(d): + for k, v in d.items(): + assert optimize(k, optims_numpy) == v + + x, y = map(Symbol, 'x y'.split()) + check({ + log(x*y)*sin(x*y)*log(x*y+1)/(log(2)*x*y): log2(x*y)*sinc(x*y)*log1p(x*y), + exp(x*sin(y)/y) - 1: expm1(x*sinc(y)) + }) + + +@may_xfail +def test_compiled_ccode_with_rewriting(): + if not cython: + skip("cython not installed.") + if not has_c(): + skip("No C compiler found.") + + x = Symbol('x') + about_two = 2**(58/S(117))*3**(97/S(117))*5**(4/S(39))*7**(92/S(117))/S(30)*pi + # about_two: 1.999999999999581826 + unchanged = 2*exp(x) - about_two + xval = S(10)**-11 + ref = unchanged.subs(x, xval).n(19) # 2.0418173913673213e-11 + + rewritten = optimize(2*exp(x) - about_two, [expm1_opt]) + + # Unfortunately, we need to call ``.n()`` on our expressions before we hand them + # to ``ccode``, and we need to request a large number of significant digits. + # In this test, results converged for double precision when the following number + # of significant digits were chosen: + NUMBER_OF_DIGITS = 25 # TODO: this should ideally be automatically handled. + + func_c = ''' +#include + +double func_unchanged(double x) { + return %(unchanged)s; +} +double func_rewritten(double x) { + return %(rewritten)s; +} +''' % {"unchanged": ccode(unchanged.n(NUMBER_OF_DIGITS)), + "rewritten": ccode(rewritten.n(NUMBER_OF_DIGITS))} + + func_pyx = ''' +#cython: language_level=3 +cdef extern double func_unchanged(double) +cdef extern double func_rewritten(double) +def py_unchanged(x): + return func_unchanged(x) +def py_rewritten(x): + return func_rewritten(x) +''' + with tempfile.TemporaryDirectory() as folder: + mod, info = compile_link_import_strings( + [('func.c', func_c), ('_func.pyx', func_pyx)], + build_dir=folder, compile_kwargs={"std": 'c99'} + ) + err_rewritten = abs(mod.py_rewritten(1e-11) - ref) + err_unchanged = abs(mod.py_unchanged(1e-11) - ref) + assert 1e-27 < err_rewritten < 1e-25 # highly accurate. + assert 1e-19 < err_unchanged < 1e-16 # quite poor. + + # Tolerances used above were determined as follows: + # >>> no_opt = unchanged.subs(x, xval.evalf()).evalf() + # >>> with_opt = rewritten.n(25).subs(x, 1e-11).evalf() + # >>> with_opt - ref, no_opt - ref + # (1.1536301877952077e-26, 1.6547074214222335e-18) diff --git a/phivenv/Lib/site-packages/sympy/codegen/tests/test_scipy_nodes.py b/phivenv/Lib/site-packages/sympy/codegen/tests/test_scipy_nodes.py new file mode 100644 index 0000000000000000000000000000000000000000..c0d1461037eec81ade0c99b18fbbf5a4517ce0b7 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/codegen/tests/test_scipy_nodes.py @@ -0,0 +1,44 @@ +from itertools import product +from sympy.core.power import Pow +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.trigonometric import cos +from sympy.core.numbers import pi +from sympy.codegen.scipy_nodes import cosm1, powm1 + +x, y, z = symbols('x y z') + + +def test_cosm1(): + cm1_xy = cosm1(x*y) + ref_xy = cos(x*y) - 1 + for wrt, deriv_order in product([x, y, z], range(3)): + assert ( + cm1_xy.diff(wrt, deriv_order) - + ref_xy.diff(wrt, deriv_order) + ).rewrite(cos).simplify() == 0 + + expr_minus2 = cosm1(pi) + assert expr_minus2.rewrite(cos) == -2 + assert cosm1(3.14).simplify() == cosm1(3.14) # cannot simplify with 3.14 + assert cosm1(pi/2).simplify() == -1 + assert (1/cos(x) - 1 + cosm1(x)/cos(x)).simplify() == 0 + + +def test_powm1(): + cases = { + powm1(x, y): x**y - 1, + powm1(x*y, z): (x*y)**z - 1, + powm1(x, y*z): x**(y*z)-1, + powm1(x*y*z, x*y*z): (x*y*z)**(x*y*z)-1 + } + for pm1_e, ref_e in cases.items(): + for wrt, deriv_order in product([x, y, z], range(3)): + der = pm1_e.diff(wrt, deriv_order) + ref = ref_e.diff(wrt, deriv_order) + delta = (der - ref).rewrite(Pow) + assert delta.simplify() == 0 + + eulers_constant_m1 = powm1(x, 1/log(x)) + assert eulers_constant_m1.rewrite(Pow) == exp(1) - 1 + assert eulers_constant_m1.simplify() == exp(1) - 1 diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__init__.py b/phivenv/Lib/site-packages/sympy/combinatorics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..220c27a42975555f8fe7770fcef0dd18475c89e1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/__init__.py @@ -0,0 +1,43 @@ +from sympy.combinatorics.permutations import Permutation, Cycle +from sympy.combinatorics.prufer import Prufer +from sympy.combinatorics.generators import cyclic, alternating, symmetric, dihedral +from sympy.combinatorics.subsets import Subset +from sympy.combinatorics.partitions import (Partition, IntegerPartition, + RGS_rank, RGS_unrank, RGS_enum) +from sympy.combinatorics.polyhedron import (Polyhedron, tetrahedron, cube, + octahedron, dodecahedron, icosahedron) +from sympy.combinatorics.perm_groups import PermutationGroup, Coset, SymmetricPermutationGroup +from sympy.combinatorics.group_constructs import DirectProduct +from sympy.combinatorics.graycode import GrayCode +from sympy.combinatorics.named_groups import (SymmetricGroup, DihedralGroup, + CyclicGroup, AlternatingGroup, AbelianGroup, RubikGroup) +from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector +from sympy.combinatorics.free_groups import free_group + +__all__ = [ + 'Permutation', 'Cycle', + + 'Prufer', + + 'cyclic', 'alternating', 'symmetric', 'dihedral', + + 'Subset', + + 'Partition', 'IntegerPartition', 'RGS_rank', 'RGS_unrank', 'RGS_enum', + + 'Polyhedron', 'tetrahedron', 'cube', 'octahedron', 'dodecahedron', + 'icosahedron', + + 'PermutationGroup', 'Coset', 'SymmetricPermutationGroup', + + 'DirectProduct', + + 'GrayCode', + + 'SymmetricGroup', 'DihedralGroup', 'CyclicGroup', 'AlternatingGroup', + 'AbelianGroup', 'RubikGroup', + + 'PolycyclicGroup', 'Collector', + + 'free_group', +] diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05ec86f4a4dd1767a3de1ef1f51e92acc8f1404b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/coset_table.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/coset_table.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d9ff93db5d8905504fc98a634e3ec281bd56706 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/coset_table.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/fp_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/fp_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11dd16f03c3dac6ef9dbbdacc07ad06ad54c58dc Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/fp_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/free_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/free_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3d53cf005ccbc1d26e4bed53bf69b2db1299967 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/free_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/galois.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/galois.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f2e9eb14de10fcd6dfe5a07d5a4f2a19f7ef4b8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/galois.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/generators.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/generators.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d856807957ef888a5ab85e7b0c53422e71ce4b92 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/generators.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/graycode.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/graycode.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e751d3e1530bf458fc27b71723f6e56f9fcacdea Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/graycode.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/group_constructs.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/group_constructs.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..278e72cd2434c24bc1176a926e9cb8b5e37e87f5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/group_constructs.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/group_numbers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/group_numbers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..243870e1f82c7ea0b06d9276e0affa0b66bf40d8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/group_numbers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/homomorphisms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/homomorphisms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb6ff083181b55dc21187609d8f282f5e7d7d328 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/homomorphisms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/named_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/named_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8364c5623e3d20d46f37306e6ebc32740d9b16ce Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/named_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/partitions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/partitions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bffc7ae663e3ade318ac47dd61056fe65854a09 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/partitions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/pc_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/pc_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..429dfc062f7b02bd55c619eb6a533275485816d9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/pc_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/permutations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/permutations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..805b0a7f0afff66fe7cf240e71c08e00209b37c1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/permutations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/polyhedron.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/polyhedron.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee26880284535dcb27c90e3ed1ff6fb371c0d840 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/polyhedron.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/prufer.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/prufer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bdc75c4ea3275507f36e4b53b6183cd5cb1e0ee Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/prufer.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/rewritingsystem.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/rewritingsystem.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03a0bb175d3bd94915766fbe03b4b6270927c6ae Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/rewritingsystem.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/rewritingsystem_fsm.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/rewritingsystem_fsm.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53a703129bea483e74fea8522a5eee4900230d7d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/rewritingsystem_fsm.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/schur_number.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/schur_number.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb993333a707cd5b12fffb9143c28d7299ccbdaa Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/schur_number.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/subsets.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/subsets.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71630f1488bb621da3aeb0b9a5290ec161a9d1b0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/subsets.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/tensor_can.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/tensor_can.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92ec0a05487cd2f7e79509149236af2ea2ab75be Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/tensor_can.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/testutil.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/testutil.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bde4b8d39d5fa82dc2d8e4731c955b63b808f674 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/testutil.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/util.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5e688febfd31b6081429a53f07b1605c8549ef7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/__pycache__/util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/coset_table.py b/phivenv/Lib/site-packages/sympy/combinatorics/coset_table.py new file mode 100644 index 0000000000000000000000000000000000000000..0687aa34ec70e9062f65ff6413213848cf03e51b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/coset_table.py @@ -0,0 +1,1259 @@ +from sympy.combinatorics.free_groups import free_group +from sympy.printing.defaults import DefaultPrinting + +from itertools import chain, product +from bisect import bisect_left + + +############################################################################### +# COSET TABLE # +############################################################################### + +class CosetTable(DefaultPrinting): + # coset_table: Mathematically a coset table + # represented using a list of lists + # alpha: Mathematically a coset (precisely, a live coset) + # represented by an integer between i with 1 <= i <= n + # alpha in c + # x: Mathematically an element of "A" (set of generators and + # their inverses), represented using "FpGroupElement" + # fp_grp: Finitely Presented Group with < X|R > as presentation. + # H: subgroup of fp_grp. + # NOTE: We start with H as being only a list of words in generators + # of "fp_grp". Since `.subgroup` method has not been implemented. + + r""" + + Properties + ========== + + [1] `0 \in \Omega` and `\tau(1) = \epsilon` + [2] `\alpha^x = \beta \Leftrightarrow \beta^{x^{-1}} = \alpha` + [3] If `\alpha^x = \beta`, then `H \tau(\alpha)x = H \tau(\beta)` + [4] `\forall \alpha \in \Omega, 1^{\tau(\alpha)} = \alpha` + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of Computational Group Theory" + + .. [2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson + Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490. + "Implementation and Analysis of the Todd-Coxeter Algorithm" + + """ + # default limit for the number of cosets allowed in a + # coset enumeration. + coset_table_max_limit = 4096000 + # limit for the current instance + coset_table_limit = None + # maximum size of deduction stack above or equal to + # which it is emptied + max_stack_size = 100 + + def __init__(self, fp_grp, subgroup, max_cosets=None): + if not max_cosets: + max_cosets = CosetTable.coset_table_max_limit + self.fp_group = fp_grp + self.subgroup = subgroup + self.coset_table_limit = max_cosets + # "p" is setup independent of Omega and n + self.p = [0] + # a list of the form `[gen_1, gen_1^{-1}, ... , gen_k, gen_k^{-1}]` + self.A = list(chain.from_iterable((gen, gen**-1) \ + for gen in self.fp_group.generators)) + #P[alpha, x] Only defined when alpha^x is defined. + self.P = [[None]*len(self.A)] + # the mathematical coset table which is a list of lists + self.table = [[None]*len(self.A)] + self.A_dict = {x: self.A.index(x) for x in self.A} + self.A_dict_inv = {} + for x, index in self.A_dict.items(): + if index % 2 == 0: + self.A_dict_inv[x] = self.A_dict[x] + 1 + else: + self.A_dict_inv[x] = self.A_dict[x] - 1 + # used in the coset-table based method of coset enumeration. Each of + # the element is called a "deduction" which is the form (alpha, x) whenever + # a value is assigned to alpha^x during a definition or "deduction process" + self.deduction_stack = [] + # Attributes for modified methods. + H = self.subgroup + self._grp = free_group(', ' .join(["a_%d" % i for i in range(len(H))]))[0] + self.P = [[None]*len(self.A)] + self.p_p = {} + + @property + def omega(self): + """Set of live cosets. """ + return [coset for coset in range(len(self.p)) if self.p[coset] == coset] + + def copy(self): + """ + Return a shallow copy of Coset Table instance ``self``. + + """ + self_copy = self.__class__(self.fp_group, self.subgroup) + self_copy.table = [list(perm_rep) for perm_rep in self.table] + self_copy.p = list(self.p) + self_copy.deduction_stack = list(self.deduction_stack) + return self_copy + + def __str__(self): + return "Coset Table on %s with %s as subgroup generators" \ + % (self.fp_group, self.subgroup) + + __repr__ = __str__ + + @property + def n(self): + """The number `n` represents the length of the sublist containing the + live cosets. + + """ + if not self.table: + return 0 + return max(self.omega) + 1 + + # Pg. 152 [1] + def is_complete(self): + r""" + The coset table is called complete if it has no undefined entries + on the live cosets; that is, `\alpha^x` is defined for all + `\alpha \in \Omega` and `x \in A`. + + """ + return not any(None in self.table[coset] for coset in self.omega) + + # Pg. 153 [1] + def define(self, alpha, x, modified=False): + r""" + This routine is used in the relator-based strategy of Todd-Coxeter + algorithm if some `\alpha^x` is undefined. We check whether there is + space available for defining a new coset. If there is enough space + then we remedy this by adjoining a new coset `\beta` to `\Omega` + (i.e to set of live cosets) and put that equal to `\alpha^x`, then + make an assignment satisfying Property[1]. If there is not enough space + then we halt the Coset Table creation. The maximum amount of space that + can be used by Coset Table can be manipulated using the class variable + ``CosetTable.coset_table_max_limit``. + + See Also + ======== + + define_c + + """ + A = self.A + table = self.table + len_table = len(table) + if len_table >= self.coset_table_limit: + # abort the further generation of cosets + raise ValueError("the coset enumeration has defined more than " + "%s cosets. Try with a greater value max number of cosets " + % self.coset_table_limit) + table.append([None]*len(A)) + self.P.append([None]*len(self.A)) + # beta is the new coset generated + beta = len_table + self.p.append(beta) + table[alpha][self.A_dict[x]] = beta + table[beta][self.A_dict_inv[x]] = alpha + # P[alpha][x] = epsilon, P[beta][x**-1] = epsilon + if modified: + self.P[alpha][self.A_dict[x]] = self._grp.identity + self.P[beta][self.A_dict_inv[x]] = self._grp.identity + self.p_p[beta] = self._grp.identity + + def define_c(self, alpha, x): + r""" + A variation of ``define`` routine, described on Pg. 165 [1], used in + the coset table-based strategy of Todd-Coxeter algorithm. It differs + from ``define`` routine in that for each definition it also adds the + tuple `(\alpha, x)` to the deduction stack. + + See Also + ======== + + define + + """ + A = self.A + table = self.table + len_table = len(table) + if len_table >= self.coset_table_limit: + # abort the further generation of cosets + raise ValueError("the coset enumeration has defined more than " + "%s cosets. Try with a greater value max number of cosets " + % self.coset_table_limit) + table.append([None]*len(A)) + # beta is the new coset generated + beta = len_table + self.p.append(beta) + table[alpha][self.A_dict[x]] = beta + table[beta][self.A_dict_inv[x]] = alpha + # append to deduction stack + self.deduction_stack.append((alpha, x)) + + def scan_c(self, alpha, word): + """ + A variation of ``scan`` routine, described on pg. 165 of [1], which + puts at tuple, whenever a deduction occurs, to deduction stack. + + See Also + ======== + + scan, scan_check, scan_and_fill, scan_and_fill_c + + """ + # alpha is an integer representing a "coset" + # since scanning can be in two cases + # 1. for alpha=0 and w in Y (i.e generating set of H) + # 2. alpha in Omega (set of live cosets), w in R (relators) + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + f = alpha + i = 0 + r = len(word) + b = alpha + j = r - 1 + # list of union of generators and their inverses + while i <= j and table[f][A_dict[word[i]]] is not None: + f = table[f][A_dict[word[i]]] + i += 1 + if i > j: + if f != b: + self.coincidence_c(f, b) + return + while j >= i and table[b][A_dict_inv[word[j]]] is not None: + b = table[b][A_dict_inv[word[j]]] + j -= 1 + if j < i: + # we have an incorrect completed scan with coincidence f ~ b + # run the "coincidence" routine + self.coincidence_c(f, b) + elif j == i: + # deduction process + table[f][A_dict[word[i]]] = b + table[b][A_dict_inv[word[i]]] = f + self.deduction_stack.append((f, word[i])) + # otherwise scan is incomplete and yields no information + + # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets where + # coincidence occurs + def coincidence_c(self, alpha, beta): + """ + A variation of ``coincidence`` routine used in the coset-table based + method of coset enumeration. The only difference being on addition of + a new coset in coset table(i.e new coset introduction), then it is + appended to ``deduction_stack``. + + See Also + ======== + + coincidence + + """ + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + # behaves as a queue + q = [] + self.merge(alpha, beta, q) + while len(q) > 0: + gamma = q.pop(0) + for x in A_dict: + delta = table[gamma][A_dict[x]] + if delta is not None: + table[delta][A_dict_inv[x]] = None + # only line of difference from ``coincidence`` routine + self.deduction_stack.append((delta, x**-1)) + mu = self.rep(gamma) + nu = self.rep(delta) + if table[mu][A_dict[x]] is not None: + self.merge(nu, table[mu][A_dict[x]], q) + elif table[nu][A_dict_inv[x]] is not None: + self.merge(mu, table[nu][A_dict_inv[x]], q) + else: + table[mu][A_dict[x]] = nu + table[nu][A_dict_inv[x]] = mu + + def scan(self, alpha, word, y=None, fill=False, modified=False): + r""" + ``scan`` performs a scanning process on the input ``word``. + It first locates the largest prefix ``s`` of ``word`` for which + `\alpha^s` is defined (i.e is not ``None``), ``s`` may be empty. Let + ``word=sv``, let ``t`` be the longest suffix of ``v`` for which + `\alpha^{t^{-1}}` is defined, and let ``v=ut``. Then three + possibilities are there: + + 1. If ``t=v``, then we say that the scan completes, and if, in addition + `\alpha^s = \alpha^{t^{-1}}`, then we say that the scan completes + correctly. + + 2. It can also happen that scan does not complete, but `|u|=1`; that + is, the word ``u`` consists of a single generator `x \in A`. In that + case, if `\alpha^s = \beta` and `\alpha^{t^{-1}} = \gamma`, then we can + set `\beta^x = \gamma` and `\gamma^{x^{-1}} = \beta`. These assignments + are known as deductions and enable the scan to complete correctly. + + 3. See ``coicidence`` routine for explanation of third condition. + + Notes + ===== + + The code for the procedure of scanning `\alpha \in \Omega` + under `w \in A*` is defined on pg. 155 [1] + + See Also + ======== + + scan_c, scan_check, scan_and_fill, scan_and_fill_c + + Scan and Fill + ============= + + Performed when the default argument fill=True. + + Modified Scan + ============= + + Performed when the default argument modified=True + + """ + # alpha is an integer representing a "coset" + # since scanning can be in two cases + # 1. for alpha=0 and w in Y (i.e generating set of H) + # 2. alpha in Omega (set of live cosets), w in R (relators) + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + f = alpha + i = 0 + r = len(word) + b = alpha + j = r - 1 + b_p = y + if modified: + f_p = self._grp.identity + flag = 0 + while fill or flag == 0: + flag = 1 + while i <= j and table[f][A_dict[word[i]]] is not None: + if modified: + f_p = f_p*self.P[f][A_dict[word[i]]] + f = table[f][A_dict[word[i]]] + i += 1 + if i > j: + if f != b: + if modified: + self.modified_coincidence(f, b, f_p**-1*y) + else: + self.coincidence(f, b) + return + while j >= i and table[b][A_dict_inv[word[j]]] is not None: + if modified: + b_p = b_p*self.P[b][self.A_dict_inv[word[j]]] + b = table[b][A_dict_inv[word[j]]] + j -= 1 + if j < i: + # we have an incorrect completed scan with coincidence f ~ b + # run the "coincidence" routine + if modified: + self.modified_coincidence(f, b, f_p**-1*b_p) + else: + self.coincidence(f, b) + elif j == i: + # deduction process + table[f][A_dict[word[i]]] = b + table[b][A_dict_inv[word[i]]] = f + if modified: + self.P[f][self.A_dict[word[i]]] = f_p**-1*b_p + self.P[b][self.A_dict_inv[word[i]]] = b_p**-1*f_p + return + elif fill: + self.define(f, word[i], modified=modified) + # otherwise scan is incomplete and yields no information + + # used in the low-index subgroups algorithm + def scan_check(self, alpha, word): + r""" + Another version of ``scan`` routine, described on, it checks whether + `\alpha` scans correctly under `word`, it is a straightforward + modification of ``scan``. ``scan_check`` returns ``False`` (rather than + calling ``coincidence``) if the scan completes incorrectly; otherwise + it returns ``True``. + + See Also + ======== + + scan, scan_c, scan_and_fill, scan_and_fill_c + + """ + # alpha is an integer representing a "coset" + # since scanning can be in two cases + # 1. for alpha=0 and w in Y (i.e generating set of H) + # 2. alpha in Omega (set of live cosets), w in R (relators) + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + f = alpha + i = 0 + r = len(word) + b = alpha + j = r - 1 + while i <= j and table[f][A_dict[word[i]]] is not None: + f = table[f][A_dict[word[i]]] + i += 1 + if i > j: + return f == b + while j >= i and table[b][A_dict_inv[word[j]]] is not None: + b = table[b][A_dict_inv[word[j]]] + j -= 1 + if j < i: + # we have an incorrect completed scan with coincidence f ~ b + # return False, instead of calling coincidence routine + return False + elif j == i: + # deduction process + table[f][A_dict[word[i]]] = b + table[b][A_dict_inv[word[i]]] = f + return True + + def merge(self, k, lamda, q, w=None, modified=False): + """ + Merge two classes with representatives ``k`` and ``lamda``, described + on Pg. 157 [1] (for pseudocode), start by putting ``p[k] = lamda``. + It is more efficient to choose the new representative from the larger + of the two classes being merged, i.e larger among ``k`` and ``lamda``. + procedure ``merge`` performs the merging operation, adds the deleted + class representative to the queue ``q``. + + Parameters + ========== + + 'k', 'lamda' being the two class representatives to be merged. + + Notes + ===== + + Pg. 86-87 [1] contains a description of this method. + + See Also + ======== + + coincidence, rep + + """ + p = self.p + rep = self.rep + phi = rep(k, modified=modified) + psi = rep(lamda, modified=modified) + if phi != psi: + mu = min(phi, psi) + v = max(phi, psi) + p[v] = mu + if modified: + if v == phi: + self.p_p[phi] = self.p_p[k]**-1*w*self.p_p[lamda] + else: + self.p_p[psi] = self.p_p[lamda]**-1*w**-1*self.p_p[k] + q.append(v) + + def rep(self, k, modified=False): + r""" + Parameters + ========== + + `k \in [0 \ldots n-1]`, as for ``self`` only array ``p`` is used + + Returns + ======= + + Representative of the class containing ``k``. + + Returns the representative of `\sim` class containing ``k``, it also + makes some modification to array ``p`` of ``self`` to ease further + computations, described on Pg. 157 [1]. + + The information on classes under `\sim` is stored in array `p` of + ``self`` argument, which will always satisfy the property: + + `p[\alpha] \sim \alpha` and `p[\alpha]=\alpha \iff \alpha=rep(\alpha)` + `\forall \in [0 \ldots n-1]`. + + So, for `\alpha \in [0 \ldots n-1]`, we find `rep(self, \alpha)` by + continually replacing `\alpha` by `p[\alpha]` until it becomes + constant (i.e satisfies `p[\alpha] = \alpha`):w + + To increase the efficiency of later ``rep`` calculations, whenever we + find `rep(self, \alpha)=\beta`, we set + `p[\gamma] = \beta \forall \gamma \in p-chain` from `\alpha` to `\beta` + + Notes + ===== + + ``rep`` routine is also described on Pg. 85-87 [1] in Atkinson's + algorithm, this results from the fact that ``coincidence`` routine + introduces functionality similar to that introduced by the + ``minimal_block`` routine on Pg. 85-87 [1]. + + See Also + ======== + + coincidence, merge + + """ + p = self.p + lamda = k + rho = p[lamda] + if modified: + s = p[:] + while rho != lamda: + if modified: + s[rho] = lamda + lamda = rho + rho = p[lamda] + if modified: + rho = s[lamda] + while rho != k: + mu = rho + rho = s[mu] + p[rho] = lamda + self.p_p[rho] = self.p_p[rho]*self.p_p[mu] + else: + mu = k + rho = p[mu] + while rho != lamda: + p[mu] = lamda + mu = rho + rho = p[mu] + return lamda + + # alpha, beta coincide, i.e. alpha, beta represent the pair of cosets + # where coincidence occurs + def coincidence(self, alpha, beta, w=None, modified=False): + r""" + The third situation described in ``scan`` routine is handled by this + routine, described on Pg. 156-161 [1]. + + The unfortunate situation when the scan completes but not correctly, + then ``coincidence`` routine is run. i.e when for some `i` with + `1 \le i \le r+1`, we have `w=st` with `s = x_1 x_2 \dots x_{i-1}`, + `t = x_i x_{i+1} \dots x_r`, and `\beta = \alpha^s` and + `\gamma = \alpha^{t-1}` are defined but unequal. This means that + `\beta` and `\gamma` represent the same coset of `H` in `G`. Described + on Pg. 156 [1]. ``rep`` + + See Also + ======== + + scan + + """ + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + # behaves as a queue + q = [] + if modified: + self.modified_merge(alpha, beta, w, q) + else: + self.merge(alpha, beta, q) + while len(q) > 0: + gamma = q.pop(0) + for x in A_dict: + delta = table[gamma][A_dict[x]] + if delta is not None: + table[delta][A_dict_inv[x]] = None + mu = self.rep(gamma, modified=modified) + nu = self.rep(delta, modified=modified) + if table[mu][A_dict[x]] is not None: + if modified: + v = self.p_p[delta]**-1*self.P[gamma][self.A_dict[x]]**-1 + v = v*self.p_p[gamma]*self.P[mu][self.A_dict[x]] + self.modified_merge(nu, table[mu][self.A_dict[x]], v, q) + else: + self.merge(nu, table[mu][A_dict[x]], q) + elif table[nu][A_dict_inv[x]] is not None: + if modified: + v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]] + v = v*self.p_p[delta]*self.P[mu][self.A_dict_inv[x]] + self.modified_merge(mu, table[nu][self.A_dict_inv[x]], v, q) + else: + self.merge(mu, table[nu][A_dict_inv[x]], q) + else: + table[mu][A_dict[x]] = nu + table[nu][A_dict_inv[x]] = mu + if modified: + v = self.p_p[gamma]**-1*self.P[gamma][self.A_dict[x]]*self.p_p[delta] + self.P[mu][self.A_dict[x]] = v + self.P[nu][self.A_dict_inv[x]] = v**-1 + + # method used in the HLT strategy + def scan_and_fill(self, alpha, word): + """ + A modified version of ``scan`` routine used in the relator-based + method of coset enumeration, described on pg. 162-163 [1], which + follows the idea that whenever the procedure is called and the scan + is incomplete then it makes new definitions to enable the scan to + complete; i.e it fills in the gaps in the scan of the relator or + subgroup generator. + + """ + self.scan(alpha, word, fill=True) + + def scan_and_fill_c(self, alpha, word): + """ + A modified version of ``scan`` routine, described on Pg. 165 second + para. [1], with modification similar to that of ``scan_anf_fill`` the + only difference being it calls the coincidence procedure used in the + coset-table based method i.e. the routine ``coincidence_c`` is used. + + See Also + ======== + + scan, scan_and_fill + + """ + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + r = len(word) + f = alpha + i = 0 + b = alpha + j = r - 1 + # loop until it has filled the alpha row in the table. + while True: + # do the forward scanning + while i <= j and table[f][A_dict[word[i]]] is not None: + f = table[f][A_dict[word[i]]] + i += 1 + if i > j: + if f != b: + self.coincidence_c(f, b) + return + # forward scan was incomplete, scan backwards + while j >= i and table[b][A_dict_inv[word[j]]] is not None: + b = table[b][A_dict_inv[word[j]]] + j -= 1 + if j < i: + self.coincidence_c(f, b) + elif j == i: + table[f][A_dict[word[i]]] = b + table[b][A_dict_inv[word[i]]] = f + self.deduction_stack.append((f, word[i])) + else: + self.define_c(f, word[i]) + + # method used in the HLT strategy + def look_ahead(self): + """ + When combined with the HLT method this is known as HLT+Lookahead + method of coset enumeration, described on pg. 164 [1]. Whenever + ``define`` aborts due to lack of space available this procedure is + executed. This routine helps in recovering space resulting from + "coincidence" of cosets. + + """ + R = self.fp_group.relators + p = self.p + # complete scan all relators under all cosets(obviously live) + # without making new definitions + for beta in self.omega: + for w in R: + self.scan(beta, w) + if p[beta] < beta: + break + + # Pg. 166 + def process_deductions(self, R_c_x, R_c_x_inv): + """ + Processes the deductions that have been pushed onto ``deduction_stack``, + described on Pg. 166 [1] and is used in coset-table based enumeration. + + See Also + ======== + + deduction_stack + + """ + p = self.p + table = self.table + while len(self.deduction_stack) > 0: + if len(self.deduction_stack) >= CosetTable.max_stack_size: + self.look_ahead() + del self.deduction_stack[:] + continue + else: + alpha, x = self.deduction_stack.pop() + if p[alpha] == alpha: + for w in R_c_x: + self.scan_c(alpha, w) + if p[alpha] < alpha: + break + beta = table[alpha][self.A_dict[x]] + if beta is not None and p[beta] == beta: + for w in R_c_x_inv: + self.scan_c(beta, w) + if p[beta] < beta: + break + + def process_deductions_check(self, R_c_x, R_c_x_inv): + """ + A variation of ``process_deductions``, this calls ``scan_check`` + wherever ``process_deductions`` calls ``scan``, described on Pg. [1]. + + See Also + ======== + + process_deductions + + """ + table = self.table + while len(self.deduction_stack) > 0: + alpha, x = self.deduction_stack.pop() + if not all(self.scan_check(alpha, w) for w in R_c_x): + return False + beta = table[alpha][self.A_dict[x]] + if beta is not None: + if not all(self.scan_check(beta, w) for w in R_c_x_inv): + return False + return True + + def switch(self, beta, gamma): + r"""Switch the elements `\beta, \gamma \in \Omega` of ``self``, used + by the ``standardize`` procedure, described on Pg. 167 [1]. + + See Also + ======== + + standardize + + """ + A = self.A + A_dict = self.A_dict + table = self.table + for x in A: + z = table[gamma][A_dict[x]] + table[gamma][A_dict[x]] = table[beta][A_dict[x]] + table[beta][A_dict[x]] = z + for alpha in range(len(self.p)): + if self.p[alpha] == alpha: + if table[alpha][A_dict[x]] == beta: + table[alpha][A_dict[x]] = gamma + elif table[alpha][A_dict[x]] == gamma: + table[alpha][A_dict[x]] = beta + + def standardize(self): + r""" + A coset table is standardized if when running through the cosets and + within each coset through the generator images (ignoring generator + inverses), the cosets appear in order of the integers + `0, 1, \dots, n`. "Standardize" reorders the elements of `\Omega` + such that, if we scan the coset table first by elements of `\Omega` + and then by elements of A, then the cosets occur in ascending order. + ``standardize()`` is used at the end of an enumeration to permute the + cosets so that they occur in some sort of standard order. + + Notes + ===== + + procedure is described on pg. 167-168 [1], it also makes use of the + ``switch`` routine to replace by smaller integer value. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r + >>> F, x, y = free_group("x, y") + + # Example 5.3 from [1] + >>> f = FpGroup(F, [x**2*y**2, x**3*y**5]) + >>> C = coset_enumeration_r(f, []) + >>> C.compress() + >>> C.table + [[1, 3, 1, 3], [2, 0, 2, 0], [3, 1, 3, 1], [0, 2, 0, 2]] + >>> C.standardize() + >>> C.table + [[1, 2, 1, 2], [3, 0, 3, 0], [0, 3, 0, 3], [2, 1, 2, 1]] + + """ + A = self.A + A_dict = self.A_dict + gamma = 1 + for alpha, x in product(range(self.n), A): + beta = self.table[alpha][A_dict[x]] + if beta >= gamma: + if beta > gamma: + self.switch(gamma, beta) + gamma += 1 + if gamma == self.n: + return + + # Compression of a Coset Table + def compress(self): + """Removes the non-live cosets from the coset table, described on + pg. 167 [1]. + + """ + gamma = -1 + A = self.A + A_dict = self.A_dict + A_dict_inv = self.A_dict_inv + table = self.table + chi = tuple([i for i in range(len(self.p)) if self.p[i] != i]) + for alpha in self.omega: + gamma += 1 + if gamma != alpha: + # replace alpha by gamma in coset table + for x in A: + beta = table[alpha][A_dict[x]] + table[gamma][A_dict[x]] = beta + # XXX: The line below uses == rather than = which means + # that it has no effect. It is not clear though if it is + # correct simply to delete the line or to change it to + # use =. Changing it causes some tests to fail. + # + # https://github.com/sympy/sympy/issues/27633 + table[beta][A_dict_inv[x]] == gamma # noqa: B015 + # all the cosets in the table are live cosets + self.p = list(range(gamma + 1)) + # delete the useless columns + del table[len(self.p):] + # re-define values + for row in table: + for j in range(len(self.A)): + row[j] -= bisect_left(chi, row[j]) + + def conjugates(self, R): + R_c = list(chain.from_iterable((rel.cyclic_conjugates(), \ + (rel**-1).cyclic_conjugates()) for rel in R)) + R_set = set() + for conjugate in R_c: + R_set = R_set.union(conjugate) + R_c_list = [] + for x in self.A: + r = {word for word in R_set if word[0] == x} + R_c_list.append(r) + R_set.difference_update(r) + return R_c_list + + def coset_representative(self, coset): + ''' + Compute the coset representative of a given coset. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + >>> C = coset_enumeration_r(f, [x]) + >>> C.compress() + >>> C.table + [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]] + >>> C.coset_representative(0) + + >>> C.coset_representative(1) + y + >>> C.coset_representative(2) + y**-1 + + ''' + for x in self.A: + gamma = self.table[coset][self.A_dict[x]] + if coset == 0: + return self.fp_group.identity + if gamma < coset: + return self.coset_representative(gamma)*x**-1 + + ############################## + # Modified Methods # + ############################## + + def modified_define(self, alpha, x): + r""" + Define a function p_p from from [1..n] to A* as + an additional component of the modified coset table. + + Parameters + ========== + + \alpha \in \Omega + x \in A* + + See Also + ======== + + define + + """ + self.define(alpha, x, modified=True) + + def modified_scan(self, alpha, w, y, fill=False): + r""" + Parameters + ========== + \alpha \in \Omega + w \in A* + y \in (YUY^-1) + fill -- `modified_scan_and_fill` when set to True. + + See Also + ======== + + scan + """ + self.scan(alpha, w, y=y, fill=fill, modified=True) + + def modified_scan_and_fill(self, alpha, w, y): + self.modified_scan(alpha, w, y, fill=True) + + def modified_merge(self, k, lamda, w, q): + r""" + Parameters + ========== + + 'k', 'lamda' -- the two class representatives to be merged. + q -- queue of length l of elements to be deleted from `\Omega` *. + w -- Word in (YUY^-1) + + See Also + ======== + + merge + """ + self.merge(k, lamda, q, w=w, modified=True) + + def modified_rep(self, k): + r""" + Parameters + ========== + + `k \in [0 \ldots n-1]` + + See Also + ======== + + rep + """ + self.rep(k, modified=True) + + def modified_coincidence(self, alpha, beta, w): + r""" + Parameters + ========== + + A coincident pair `\alpha, \beta \in \Omega, w \in Y \cup Y^{-1}` + + See Also + ======== + + coincidence + + """ + self.coincidence(alpha, beta, w=w, modified=True) + +############################################################################### +# COSET ENUMERATION # +############################################################################### + +# relator-based method +def coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None, + incomplete=False, modified=False): + """ + This is easier of the two implemented methods of coset enumeration. + and is often called the HLT method, after Hazelgrove, Leech, Trotter + The idea is that we make use of ``scan_and_fill`` makes new definitions + whenever the scan is incomplete to enable the scan to complete; this way + we fill in the gaps in the scan of the relator or subgroup generator, + that's why the name relator-based method. + + An instance of `CosetTable` for `fp_grp` can be passed as the keyword + argument `draft` in which case the coset enumeration will start with + that instance and attempt to complete it. + + When `incomplete` is `True` and the function is unable to complete for + some reason, the partially complete table will be returned. + + # TODO: complete the docstring + + See Also + ======== + + scan_and_fill, + + Examples + ======== + + >>> from sympy.combinatorics.free_groups import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r + >>> F, x, y = free_group("x, y") + + # Example 5.1 from [1] + >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + >>> C = coset_enumeration_r(f, [x]) + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... print(C.table[i]) + [0, 0, 1, 2] + [1, 1, 2, 0] + [2, 2, 0, 1] + >>> C.p + [0, 1, 2, 1, 1] + + # Example from exercises Q2 [1] + >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3]) + >>> C = coset_enumeration_r(f, []) + >>> C.compress(); C.standardize() + >>> C.table + [[1, 2, 3, 4], + [5, 0, 6, 7], + [0, 5, 7, 6], + [7, 6, 5, 0], + [6, 7, 0, 5], + [2, 1, 4, 3], + [3, 4, 2, 1], + [4, 3, 1, 2]] + + # Example 5.2 + >>> f = FpGroup(F, [x**2, y**3, (x*y)**3]) + >>> Y = [x*y] + >>> C = coset_enumeration_r(f, Y) + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... print(C.table[i]) + [1, 1, 2, 1] + [0, 0, 0, 2] + [3, 3, 1, 0] + [2, 2, 3, 3] + + # Example 5.3 + >>> f = FpGroup(F, [x**2*y**2, x**3*y**5]) + >>> Y = [] + >>> C = coset_enumeration_r(f, Y) + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... print(C.table[i]) + [1, 3, 1, 3] + [2, 0, 2, 0] + [3, 1, 3, 1] + [0, 2, 0, 2] + + # Example 5.4 + >>> F, a, b, c, d, e = free_group("a, b, c, d, e") + >>> f = FpGroup(F, [a*b*c**-1, b*c*d**-1, c*d*e**-1, d*e*a**-1, e*a*b**-1]) + >>> Y = [a] + >>> C = coset_enumeration_r(f, Y) + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... print(C.table[i]) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + # example of "compress" method + >>> C.compress() + >>> C.table + [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] + + # Exercises Pg. 161, Q2. + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3]) + >>> Y = [] + >>> C = coset_enumeration_r(f, Y) + >>> C.compress() + >>> C.standardize() + >>> C.table + [[1, 2, 3, 4], + [5, 0, 6, 7], + [0, 5, 7, 6], + [7, 6, 5, 0], + [6, 7, 0, 5], + [2, 1, 4, 3], + [3, 4, 2, 1], + [4, 3, 1, 2]] + + # John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson + # Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490 + # from 1973chwd.pdf + # Table 1. Ex. 1 + >>> F, r, s, t = free_group("r, s, t") + >>> E1 = FpGroup(F, [t**-1*r*t*r**-2, r**-1*s*r*s**-2, s**-1*t*s*t**-2]) + >>> C = coset_enumeration_r(E1, [r]) + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... print(C.table[i]) + [0, 0, 0, 0, 0, 0] + + Ex. 2 + >>> F, a, b = free_group("a, b") + >>> Cox = FpGroup(F, [a**6, b**6, (a*b)**2, (a**2*b**2)**2, (a**3*b**3)**5]) + >>> C = coset_enumeration_r(Cox, [a]) + >>> index = 0 + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... index += 1 + >>> index + 500 + + # Ex. 3 + >>> F, a, b = free_group("a, b") + >>> B_2_4 = FpGroup(F, [a**4, b**4, (a*b)**4, (a**-1*b)**4, (a**2*b)**4, \ + (a*b**2)**4, (a**2*b**2)**4, (a**-1*b*a*b)**4, (a*b**-1*a*b)**4]) + >>> C = coset_enumeration_r(B_2_4, [a]) + >>> index = 0 + >>> for i in range(len(C.p)): + ... if C.p[i] == i: + ... index += 1 + >>> index + 1024 + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of computational group theory" + + """ + # 1. Initialize a coset table C for < X|R > + C = CosetTable(fp_grp, Y, max_cosets=max_cosets) + # Define coset table methods. + if modified: + _scan_and_fill = C.modified_scan_and_fill + _define = C.modified_define + else: + _scan_and_fill = C.scan_and_fill + _define = C.define + if draft: + C.table = draft.table[:] + C.p = draft.p[:] + R = fp_grp.relators + A_dict = C.A_dict + p = C.p + for i in range(len(Y)): + if modified: + _scan_and_fill(0, Y[i], C._grp.generators[i]) + else: + _scan_and_fill(0, Y[i]) + alpha = 0 + while alpha < C.n: + if p[alpha] == alpha: + try: + for w in R: + if modified: + _scan_and_fill(alpha, w, C._grp.identity) + else: + _scan_and_fill(alpha, w) + # if alpha was eliminated during the scan then break + if p[alpha] < alpha: + break + if p[alpha] == alpha: + for x in A_dict: + if C.table[alpha][A_dict[x]] is None: + _define(alpha, x) + except ValueError as e: + if incomplete: + return C + raise e + alpha += 1 + return C + +def modified_coset_enumeration_r(fp_grp, Y, max_cosets=None, draft=None, + incomplete=False): + r""" + Introduce a new set of symbols y \in Y that correspond to the + generators of the subgroup. Store the elements of Y as a + word P[\alpha, x] and compute the coset table similar to that of + the regular coset enumeration methods. + + Examples + ======== + + >>> from sympy.combinatorics.free_groups import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup + >>> from sympy.combinatorics.coset_table import modified_coset_enumeration_r + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + >>> C = modified_coset_enumeration_r(f, [x]) + >>> C.table + [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1], [None, 1, None, None], [1, 3, None, None]] + + See Also + ======== + + coset_enumertation_r + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E., + "Handbook of Computational Group Theory", + Section 5.3.2 + """ + return coset_enumeration_r(fp_grp, Y, max_cosets=max_cosets, draft=draft, + incomplete=incomplete, modified=True) + +# Pg. 166 +# coset-table based method +def coset_enumeration_c(fp_grp, Y, max_cosets=None, draft=None, + incomplete=False): + """ + >>> from sympy.combinatorics.free_groups import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_c + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + >>> C = coset_enumeration_c(f, [x]) + >>> C.table + [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]] + + """ + # Initialize a coset table C for < X|R > + X = fp_grp.generators + R = fp_grp.relators + C = CosetTable(fp_grp, Y, max_cosets=max_cosets) + if draft: + C.table = draft.table[:] + C.p = draft.p[:] + C.deduction_stack = draft.deduction_stack + for alpha, x in product(range(len(C.table)), X): + if C.table[alpha][C.A_dict[x]] is not None: + C.deduction_stack.append((alpha, x)) + A = C.A + # replace all the elements by cyclic reductions + R_cyc_red = [rel.identity_cyclic_reduction() for rel in R] + R_c = list(chain.from_iterable((rel.cyclic_conjugates(), (rel**-1).cyclic_conjugates()) \ + for rel in R_cyc_red)) + R_set = set() + for conjugate in R_c: + R_set = R_set.union(conjugate) + # a list of subsets of R_c whose words start with "x". + R_c_list = [] + for x in C.A: + r = {word for word in R_set if word[0] == x} + R_c_list.append(r) + R_set.difference_update(r) + for w in Y: + C.scan_and_fill_c(0, w) + for x in A: + C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]]) + alpha = 0 + while alpha < len(C.table): + if C.p[alpha] == alpha: + try: + for x in C.A: + if C.p[alpha] != alpha: + break + if C.table[alpha][C.A_dict[x]] is None: + C.define_c(alpha, x) + C.process_deductions(R_c_list[C.A_dict[x]], R_c_list[C.A_dict_inv[x]]) + except ValueError as e: + if incomplete: + return C + raise e + alpha += 1 + return C diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/fp_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/fp_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..95530ccd44f025eca5f029c6ba60d3727cbcb29d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/fp_groups.py @@ -0,0 +1,1352 @@ +"""Finitely Presented Groups and its algorithms. """ + +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.combinatorics.free_groups import (FreeGroup, FreeGroupElement, + free_group) +from sympy.combinatorics.rewritingsystem import RewritingSystem +from sympy.combinatorics.coset_table import (CosetTable, + coset_enumeration_r, + coset_enumeration_c) +from sympy.combinatorics import PermutationGroup +from sympy.matrices.normalforms import invariant_factors +from sympy.matrices import Matrix +from sympy.polys.polytools import gcd +from sympy.printing.defaults import DefaultPrinting +from sympy.utilities import public +from sympy.utilities.magic import pollute + +from itertools import product + + +@public +def fp_group(fr_grp, relators=()): + _fp_group = FpGroup(fr_grp, relators) + return (_fp_group,) + tuple(_fp_group._generators) + +@public +def xfp_group(fr_grp, relators=()): + _fp_group = FpGroup(fr_grp, relators) + return (_fp_group, _fp_group._generators) + +# Does not work. Both symbols and pollute are undefined. Never tested. +@public +def vfp_group(fr_grpm, relators): + _fp_group = FpGroup(symbols, relators) + pollute([sym.name for sym in _fp_group.symbols], _fp_group.generators) + return _fp_group + + +def _parse_relators(rels): + """Parse the passed relators.""" + return rels + + +############################################################################### +# FINITELY PRESENTED GROUPS # +############################################################################### + + +class FpGroup(DefaultPrinting): + """ + The FpGroup would take a FreeGroup and a list/tuple of relators, the + relators would be specified in such a way that each of them be equal to the + identity of the provided free group. + + """ + is_group = True + is_FpGroup = True + is_PermutationGroup = False + + def __init__(self, fr_grp, relators): + relators = _parse_relators(relators) + self.free_group = fr_grp + self.relators = relators + self.generators = self._generators() + self.dtype = type("FpGroupElement", (FpGroupElement,), {"group": self}) + + # CosetTable instance on identity subgroup + self._coset_table = None + # returns whether coset table on identity subgroup + # has been standardized + self._is_standardized = False + + self._order = None + self._center = None + + self._rewriting_system = RewritingSystem(self) + self._perm_isomorphism = None + return + + def _generators(self): + return self.free_group.generators + + def make_confluent(self): + ''' + Try to make the group's rewriting system confluent + + ''' + self._rewriting_system.make_confluent() + return + + def reduce(self, word): + ''' + Return the reduced form of `word` in `self` according to the group's + rewriting system. If it's confluent, the reduced form is the unique normal + form of the word in the group. + + ''' + return self._rewriting_system.reduce(word) + + def equals(self, word1, word2): + ''' + Compare `word1` and `word2` for equality in the group + using the group's rewriting system. If the system is + confluent, the returned answer is necessarily correct. + (If it is not, `False` could be returned in some cases + where in fact `word1 == word2`) + + ''' + if self.reduce(word1*word2**-1) == self.identity: + return True + elif self._rewriting_system.is_confluent: + return False + return None + + @property + def identity(self): + return self.free_group.identity + + def __contains__(self, g): + return g in self.free_group + + def subgroup(self, gens, C=None, homomorphism=False): + ''' + Return the subgroup generated by `gens` using the + Reidemeister-Schreier algorithm + homomorphism -- When set to True, return a dictionary containing the images + of the presentation generators in the original group. + + Examples + ======== + + >>> from sympy.combinatorics.fp_groups import FpGroup + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**3, y**5, (x*y)**2]) + >>> H = [x*y, x**-1*y**-1*x*y*x] + >>> K, T = f.subgroup(H, homomorphism=True) + >>> T(K.generators) + [x*y, x**-1*y**2*x**-1] + + ''' + + if not all(isinstance(g, FreeGroupElement) for g in gens): + raise ValueError("Generators must be `FreeGroupElement`s") + if not all(g.group == self.free_group for g in gens): + raise ValueError("Given generators are not members of the group") + if homomorphism: + g, rels, _gens = reidemeister_presentation(self, gens, C=C, homomorphism=True) + else: + g, rels = reidemeister_presentation(self, gens, C=C) + if g: + g = FpGroup(g[0].group, rels) + else: + g = FpGroup(free_group('')[0], []) + if homomorphism: + from sympy.combinatorics.homomorphisms import homomorphism + return g, homomorphism(g, self, g.generators, _gens, check=False) + return g + + def coset_enumeration(self, H, strategy="relator_based", max_cosets=None, + draft=None, incomplete=False): + """ + Return an instance of ``coset table``, when Todd-Coxeter algorithm is + run over the ``self`` with ``H`` as subgroup, using ``strategy`` + argument as strategy. The returned coset table is compressed but not + standardized. + + An instance of `CosetTable` for `fp_grp` can be passed as the keyword + argument `draft` in which case the coset enumeration will start with + that instance and attempt to complete it. + + When `incomplete` is `True` and the function is unable to complete for + some reason, the partially complete table will be returned. + + """ + if not max_cosets: + max_cosets = CosetTable.coset_table_max_limit + if strategy == 'relator_based': + C = coset_enumeration_r(self, H, max_cosets=max_cosets, + draft=draft, incomplete=incomplete) + else: + C = coset_enumeration_c(self, H, max_cosets=max_cosets, + draft=draft, incomplete=incomplete) + if C.is_complete(): + C.compress() + return C + + def standardize_coset_table(self): + """ + Standardized the coset table ``self`` and makes the internal variable + ``_is_standardized`` equal to ``True``. + + """ + self._coset_table.standardize() + self._is_standardized = True + + def coset_table(self, H, strategy="relator_based", max_cosets=None, + draft=None, incomplete=False): + """ + Return the mathematical coset table of ``self`` in ``H``. + + """ + if not H: + if self._coset_table is not None: + if not self._is_standardized: + self.standardize_coset_table() + else: + C = self.coset_enumeration([], strategy, max_cosets=max_cosets, + draft=draft, incomplete=incomplete) + self._coset_table = C + self.standardize_coset_table() + return self._coset_table.table + else: + C = self.coset_enumeration(H, strategy, max_cosets=max_cosets, + draft=draft, incomplete=incomplete) + C.standardize() + return C.table + + def order(self, strategy="relator_based"): + """ + Returns the order of the finitely presented group ``self``. It uses + the coset enumeration with identity group as subgroup, i.e ``H=[]``. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x, y**2]) + >>> f.order(strategy="coset_table_based") + 2 + + """ + if self._order is not None: + return self._order + if self._coset_table is not None: + self._order = len(self._coset_table.table) + elif len(self.relators) == 0: + self._order = self.free_group.order() + elif len(self.generators) == 1: + self._order = abs(gcd([r.array_form[0][1] for r in self.relators])) + elif self._is_infinite(): + self._order = S.Infinity + else: + gens, C = self._finite_index_subgroup() + if C: + ind = len(C.table) + self._order = ind*self.subgroup(gens, C=C).order() + else: + self._order = self.index([]) + return self._order + + def _is_infinite(self): + ''' + Test if the group is infinite. Return `True` if the test succeeds + and `None` otherwise + + ''' + used_gens = set() + for r in self.relators: + used_gens.update(r.contains_generators()) + if not set(self.generators) <= used_gens: + return True + # Abelianisation test: check is the abelianisation is infinite + abelian_rels = [] + for rel in self.relators: + abelian_rels.append([rel.exponent_sum(g) for g in self.generators]) + m = Matrix(Matrix(abelian_rels)) + if 0 in invariant_factors(m): + return True + else: + return None + + + def _finite_index_subgroup(self, s=None): + ''' + Find the elements of `self` that generate a finite index subgroup + and, if found, return the list of elements and the coset table of `self` by + the subgroup, otherwise return `(None, None)` + + ''' + gen = self.most_frequent_generator() + rels = list(self.generators) + rels.extend(self.relators) + if not s: + if len(self.generators) == 2: + s = [gen] + [g for g in self.generators if g != gen] + else: + rand = self.free_group.identity + i = 0 + while ((rand in rels or rand**-1 in rels or rand.is_identity) + and i<10): + rand = self.random() + i += 1 + s = [gen, rand] + [g for g in self.generators if g != gen] + mid = (len(s)+1)//2 + half1 = s[:mid] + half2 = s[mid:] + draft1 = None + draft2 = None + m = 200 + C = None + while not C and (m/2 < CosetTable.coset_table_max_limit): + m = min(m, CosetTable.coset_table_max_limit) + draft1 = self.coset_enumeration(half1, max_cosets=m, + draft=draft1, incomplete=True) + if draft1.is_complete(): + C = draft1 + half = half1 + else: + draft2 = self.coset_enumeration(half2, max_cosets=m, + draft=draft2, incomplete=True) + if draft2.is_complete(): + C = draft2 + half = half2 + if not C: + m *= 2 + if not C: + return None, None + C.compress() + return half, C + + def most_frequent_generator(self): + gens = self.generators + rels = self.relators + freqs = [sum(r.generator_count(g) for r in rels) for g in gens] + return gens[freqs.index(max(freqs))] + + def random(self): + import random + r = self.free_group.identity + for i in range(random.randint(2,3)): + r = r*random.choice(self.generators)**random.choice([1,-1]) + return r + + def index(self, H, strategy="relator_based"): + """ + Return the index of subgroup ``H`` in group ``self``. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**5, y**4, y*x*y**3*x**3]) + >>> f.index([x]) + 4 + + """ + # TODO: use |G:H| = |G|/|H| (currently H can't be made into a group) + # when we know |G| and |H| + + if H == []: + return self.order() + else: + C = self.coset_enumeration(H, strategy) + return len(C.table) + + def __str__(self): + if self.free_group.rank > 30: + str_form = "" % self.free_group.rank + else: + str_form = "" % str(self.generators) + return str_form + + __repr__ = __str__ + +#============================================================================== +# PERMUTATION GROUP METHODS +#============================================================================== + + def _to_perm_group(self): + ''' + Return an isomorphic permutation group and the isomorphism. + The implementation is dependent on coset enumeration so + will only terminate for finite groups. + + ''' + from sympy.combinatorics import Permutation + from sympy.combinatorics.homomorphisms import homomorphism + if self.order() is S.Infinity: + raise NotImplementedError("Permutation presentation of infinite " + "groups is not implemented") + if self._perm_isomorphism: + T = self._perm_isomorphism + P = T.image() + else: + C = self.coset_table([]) + gens = self.generators + images = [[C[i][2*gens.index(g)] for i in range(len(C))] for g in gens] + images = [Permutation(i) for i in images] + P = PermutationGroup(images) + T = homomorphism(self, P, gens, images, check=False) + self._perm_isomorphism = T + return P, T + + def _perm_group_list(self, method_name, *args): + ''' + Given the name of a `PermutationGroup` method (returning a subgroup + or a list of subgroups) and (optionally) additional arguments it takes, + return a list or a list of lists containing the generators of this (or + these) subgroups in terms of the generators of `self`. + + ''' + P, T = self._to_perm_group() + perm_result = getattr(P, method_name)(*args) + single = False + if isinstance(perm_result, PermutationGroup): + perm_result, single = [perm_result], True + result = [] + for group in perm_result: + gens = group.generators + result.append(T.invert(gens)) + return result[0] if single else result + + def derived_series(self): + ''' + Return the list of lists containing the generators + of the subgroups in the derived series of `self`. + + ''' + return self._perm_group_list('derived_series') + + def lower_central_series(self): + ''' + Return the list of lists containing the generators + of the subgroups in the lower central series of `self`. + + ''' + return self._perm_group_list('lower_central_series') + + def center(self): + ''' + Return the list of generators of the center of `self`. + + ''' + return self._perm_group_list('center') + + + def derived_subgroup(self): + ''' + Return the list of generators of the derived subgroup of `self`. + + ''' + return self._perm_group_list('derived_subgroup') + + + def centralizer(self, other): + ''' + Return the list of generators of the centralizer of `other` + (a list of elements of `self`) in `self`. + + ''' + T = self._to_perm_group()[1] + other = T(other) + return self._perm_group_list('centralizer', other) + + def normal_closure(self, other): + ''' + Return the list of generators of the normal closure of `other` + (a list of elements of `self`) in `self`. + + ''' + T = self._to_perm_group()[1] + other = T(other) + return self._perm_group_list('normal_closure', other) + + def _perm_property(self, attr): + ''' + Given an attribute of a `PermutationGroup`, return + its value for a permutation group isomorphic to `self`. + + ''' + P = self._to_perm_group()[0] + return getattr(P, attr) + + @property + def is_abelian(self): + ''' + Check if `self` is abelian. + + ''' + return self._perm_property("is_abelian") + + @property + def is_nilpotent(self): + ''' + Check if `self` is nilpotent. + + ''' + return self._perm_property("is_nilpotent") + + @property + def is_solvable(self): + ''' + Check if `self` is solvable. + + ''' + return self._perm_property("is_solvable") + + @property + def elements(self): + ''' + List the elements of `self`. + + ''' + P, T = self._to_perm_group() + return T.invert(P.elements) + + @property + def is_cyclic(self): + """ + Return ``True`` if group is Cyclic. + + """ + if len(self.generators) <= 1: + return True + try: + P, T = self._to_perm_group() + except NotImplementedError: + raise NotImplementedError("Check for infinite Cyclic group " + "is not implemented") + return P.is_cyclic + + def abelian_invariants(self): + """ + Return Abelian Invariants of a group. + """ + try: + P, T = self._to_perm_group() + except NotImplementedError: + raise NotImplementedError("abelian invariants is not implemented" + "for infinite group") + return P.abelian_invariants() + + def composition_series(self): + """ + Return subnormal series of maximum length for a group. + """ + try: + P, T = self._to_perm_group() + except NotImplementedError: + raise NotImplementedError("composition series is not implemented" + "for infinite group") + return P.composition_series() + + +class FpSubgroup(DefaultPrinting): + ''' + The class implementing a subgroup of an FpGroup or a FreeGroup + (only finite index subgroups are supported at this point). This + is to be used if one wishes to check if an element of the original + group belongs to the subgroup + + ''' + def __init__(self, G, gens, normal=False): + super().__init__() + self.parent = G + self.generators = list({g for g in gens if g != G.identity}) + self._min_words = None #for use in __contains__ + self.C = None + self.normal = normal + + def __contains__(self, g): + + if isinstance(self.parent, FreeGroup): + if self._min_words is None: + # make _min_words - a list of subwords such that + # g is in the subgroup if and only if it can be + # partitioned into these subwords. Infinite families of + # subwords are presented by tuples, e.g. (r, w) + # stands for the family of subwords r*w**n*r**-1 + + def _process(w): + # this is to be used before adding new words + # into _min_words; if the word w is not cyclically + # reduced, it will generate an infinite family of + # subwords so should be written as a tuple; + # if it is, w**-1 should be added to the list + # as well + p, r = w.cyclic_reduction(removed=True) + if not r.is_identity: + return [(r, p)] + else: + return [w, w**-1] + + # make the initial list + gens = [] + for w in self.generators: + if self.normal: + w = w.cyclic_reduction() + gens.extend(_process(w)) + + for w1 in gens: + for w2 in gens: + # if w1 and w2 are equal or are inverses, continue + if w1 == w2 or (not isinstance(w1, tuple) + and w1**-1 == w2): + continue + + # if the start of one word is the inverse of the + # end of the other, their multiple should be added + # to _min_words because of cancellation + if isinstance(w1, tuple): + # start, end + s1, s2 = w1[0][0], w1[0][0]**-1 + else: + s1, s2 = w1[0], w1[len(w1)-1] + + if isinstance(w2, tuple): + # start, end + r1, r2 = w2[0][0], w2[0][0]**-1 + else: + r1, r2 = w2[0], w2[len(w1)-1] + + # p1 and p2 are w1 and w2 or, in case when + # w1 or w2 is an infinite family, a representative + p1, p2 = w1, w2 + if isinstance(w1, tuple): + p1 = w1[0]*w1[1]*w1[0]**-1 + if isinstance(w2, tuple): + p2 = w2[0]*w2[1]*w2[0]**-1 + + # add the product of the words to the list is necessary + if r1**-1 == s2 and not (p1*p2).is_identity: + new = _process(p1*p2) + if new not in gens: + gens.extend(new) + + if r2**-1 == s1 and not (p2*p1).is_identity: + new = _process(p2*p1) + if new not in gens: + gens.extend(new) + + self._min_words = gens + + min_words = self._min_words + + def _is_subword(w): + # check if w is a word in _min_words or one of + # the infinite families in it + w, r = w.cyclic_reduction(removed=True) + if r.is_identity or self.normal: + return w in min_words + else: + t = [s[1] for s in min_words if isinstance(s, tuple) + and s[0] == r] + return [s for s in t if w.power_of(s)] != [] + + # store the solution of words for which the result of + # _word_break (below) is known + known = {} + + def _word_break(w): + # check if w can be written as a product of words + # in min_words + if len(w) == 0: + return True + i = 0 + while i < len(w): + i += 1 + prefix = w.subword(0, i) + if not _is_subword(prefix): + continue + rest = w.subword(i, len(w)) + if rest not in known: + known[rest] = _word_break(rest) + if known[rest]: + return True + return False + + if self.normal: + g = g.cyclic_reduction() + return _word_break(g) + else: + if self.C is None: + C = self.parent.coset_enumeration(self.generators) + self.C = C + i = 0 + C = self.C + for j in range(len(g)): + i = C.table[i][C.A_dict[g[j]]] + return i == 0 + + def order(self): + if not self.generators: + return S.One + if isinstance(self.parent, FreeGroup): + return S.Infinity + if self.C is None: + C = self.parent.coset_enumeration(self.generators) + self.C = C + # This is valid because `len(self.C.table)` (the index of the subgroup) + # will always be finite - otherwise coset enumeration doesn't terminate + return self.parent.order()/len(self.C.table) + + def to_FpGroup(self): + if isinstance(self.parent, FreeGroup): + gen_syms = [('x_%d'%i) for i in range(len(self.generators))] + return free_group(', '.join(gen_syms))[0] + return self.parent.subgroup(C=self.C) + + def __str__(self): + if len(self.generators) > 30: + str_form = "" % len(self.generators) + else: + str_form = "" % str(self.generators) + return str_form + + __repr__ = __str__ + + +############################################################################### +# LOW INDEX SUBGROUPS # +############################################################################### + +def low_index_subgroups(G, N, Y=()): + """ + Implements the Low Index Subgroups algorithm, i.e find all subgroups of + ``G`` upto a given index ``N``. This implements the method described in + [Sim94]. This procedure involves a backtrack search over incomplete Coset + Tables, rather than over forced coincidences. + + Parameters + ========== + + G: An FpGroup < X|R > + N: positive integer, representing the maximum index value for subgroups + Y: (an optional argument) specifying a list of subgroup generators, such + that each of the resulting subgroup contains the subgroup generated by Y. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, low_index_subgroups + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**2, y**3, (x*y)**4]) + >>> L = low_index_subgroups(f, 4) + >>> for coset_table in L: + ... print(coset_table.table) + [[0, 0, 0, 0]] + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]] + [[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]] + [[1, 1, 0, 0], [0, 0, 1, 1]] + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of Computational Group Theory" + Section 5.4 + + .. [2] Marston Conder and Peter Dobcsanyi + "Applications and Adaptions of the Low Index Subgroups Procedure" + + """ + C = CosetTable(G, []) + R = G.relators + # length chosen for the length of the short relators + len_short_rel = 5 + # elements of R2 only checked at the last step for complete + # coset tables + R2 = {rel for rel in R if len(rel) > len_short_rel} + # elements of R1 are used in inner parts of the process to prune + # branches of the search tree, + R1 = {rel.identity_cyclic_reduction() for rel in set(R) - R2} + R1_c_list = C.conjugates(R1) + S = [] + descendant_subgroups(S, C, R1_c_list, C.A[0], R2, N, Y) + return S + + +def descendant_subgroups(S, C, R1_c_list, x, R2, N, Y): + A_dict = C.A_dict + A_dict_inv = C.A_dict_inv + if C.is_complete(): + # if C is complete then it only needs to test + # whether the relators in R2 are satisfied + for w, alpha in product(R2, C.omega): + if not C.scan_check(alpha, w): + return + # relators in R2 are satisfied, append the table to list + S.append(C) + else: + # find the first undefined entry in Coset Table + for alpha, x in product(range(len(C.table)), C.A): + if C.table[alpha][A_dict[x]] is None: + # this is "x" in pseudo-code (using "y" makes it clear) + undefined_coset, undefined_gen = alpha, x + break + # for filling up the undefine entry we try all possible values + # of beta in Omega or beta = n where beta^(undefined_gen^-1) is undefined + reach = C.omega + [C.n] + for beta in reach: + if beta < N: + if beta == C.n or C.table[beta][A_dict_inv[undefined_gen]] is None: + try_descendant(S, C, R1_c_list, R2, N, undefined_coset, \ + undefined_gen, beta, Y) + + +def try_descendant(S, C, R1_c_list, R2, N, alpha, x, beta, Y): + r""" + Solves the problem of trying out each individual possibility + for `\alpha^x. + + """ + D = C.copy() + if beta == D.n and beta < N: + D.table.append([None]*len(D.A)) + D.p.append(beta) + D.table[alpha][D.A_dict[x]] = beta + D.table[beta][D.A_dict_inv[x]] = alpha + D.deduction_stack.append((alpha, x)) + if not D.process_deductions_check(R1_c_list[D.A_dict[x]], \ + R1_c_list[D.A_dict_inv[x]]): + return + for w in Y: + if not D.scan_check(0, w): + return + if first_in_class(D, Y): + descendant_subgroups(S, D, R1_c_list, x, R2, N, Y) + + +def first_in_class(C, Y=()): + """ + Checks whether the subgroup ``H=G1`` corresponding to the Coset Table + could possibly be the canonical representative of its conjugacy class. + + Parameters + ========== + + C: CosetTable + + Returns + ======= + + bool: True/False + + If this returns False, then no descendant of C can have that property, and + so we can abandon C. If it returns True, then we need to process further + the node of the search tree corresponding to C, and so we call + ``descendant_subgroups`` recursively on C. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, first_in_class + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**2, y**3, (x*y)**4]) + >>> C = CosetTable(f, []) + >>> C.table = [[0, 0, None, None]] + >>> first_in_class(C) + True + >>> C.table = [[1, 1, 1, None], [0, 0, None, 1]]; C.p = [0, 1] + >>> first_in_class(C) + True + >>> C.table = [[1, 1, 2, 1], [0, 0, 0, None], [None, None, None, 0]] + >>> C.p = [0, 1, 2] + >>> first_in_class(C) + False + >>> C.table = [[1, 1, 1, 2], [0, 0, 2, 0], [2, None, 0, 1]] + >>> first_in_class(C) + False + + # TODO:: Sims points out in [Sim94] that performance can be improved by + # remembering some of the information computed by ``first_in_class``. If + # the ``continue alpha`` statement is executed at line 14, then the same thing + # will happen for that value of alpha in any descendant of the table C, and so + # the values the values of alpha for which this occurs could profitably be + # stored and passed through to the descendants of C. Of course this would + # make the code more complicated. + + # The code below is taken directly from the function on page 208 of [Sim94] + # nu[alpha] + + """ + n = C.n + # lamda is the largest numbered point in Omega_c_alpha which is currently defined + lamda = -1 + # for alpha in Omega_c, nu[alpha] is the point in Omega_c_alpha corresponding to alpha + nu = [None]*n + # for alpha in Omega_c_alpha, mu[alpha] is the point in Omega_c corresponding to alpha + mu = [None]*n + # mutually nu and mu are the mutually-inverse equivalence maps between + # Omega_c_alpha and Omega_c + next_alpha = False + # For each 0!=alpha in [0 .. nc-1], we start by constructing the equivalent + # standardized coset table C_alpha corresponding to H_alpha + for alpha in range(1, n): + # reset nu to "None" after previous value of alpha + for beta in range(lamda+1): + nu[mu[beta]] = None + # we only want to reject our current table in favour of a preceding + # table in the ordering in which 1 is replaced by alpha, if the subgroup + # G_alpha corresponding to this preceding table definitely contains the + # given subgroup + for w in Y: + # TODO: this should support input of a list of general words + # not just the words which are in "A" (i.e gen and gen^-1) + if C.table[alpha][C.A_dict[w]] != alpha: + # continue with alpha + next_alpha = True + break + if next_alpha: + next_alpha = False + continue + # try alpha as the new point 0 in Omega_C_alpha + mu[0] = alpha + nu[alpha] = 0 + # compare corresponding entries in C and C_alpha + lamda = 0 + for beta in range(n): + for x in C.A: + gamma = C.table[beta][C.A_dict[x]] + delta = C.table[mu[beta]][C.A_dict[x]] + # if either of the entries is undefined, + # we move with next alpha + if gamma is None or delta is None: + # continue with alpha + next_alpha = True + break + if nu[delta] is None: + # delta becomes the next point in Omega_C_alpha + lamda += 1 + nu[delta] = lamda + mu[lamda] = delta + if nu[delta] < gamma: + return False + if nu[delta] > gamma: + # continue with alpha + next_alpha = True + break + if next_alpha: + next_alpha = False + break + return True + +#======================================================================== +# Simplifying Presentation +#======================================================================== + +def simplify_presentation(*args, change_gens=False): + ''' + For an instance of `FpGroup`, return a simplified isomorphic copy of + the group (e.g. remove redundant generators or relators). Alternatively, + a list of generators and relators can be passed in which case the + simplified lists will be returned. + + By default, the generators of the group are unchanged. If you would + like to remove redundant generators, set the keyword argument + `change_gens = True`. + + ''' + if len(args) == 1: + if not isinstance(args[0], FpGroup): + raise TypeError("The argument must be an instance of FpGroup") + G = args[0] + gens, rels = simplify_presentation(G.generators, G.relators, + change_gens=change_gens) + if gens: + return FpGroup(gens[0].group, rels) + return FpGroup(FreeGroup([]), []) + elif len(args) == 2: + gens, rels = args[0][:], args[1][:] + if not gens: + return gens, rels + identity = gens[0].group.identity + else: + if len(args) == 0: + m = "Not enough arguments" + else: + m = "Too many arguments" + raise RuntimeError(m) + + prev_gens = [] + prev_rels = [] + while not set(prev_rels) == set(rels): + prev_rels = rels + while change_gens and not set(prev_gens) == set(gens): + prev_gens = gens + gens, rels = elimination_technique_1(gens, rels, identity) + rels = _simplify_relators(rels) + + if change_gens: + syms = [g.array_form[0][0] for g in gens] + F = free_group(syms)[0] + identity = F.identity + gens = F.generators + subs = dict(zip(syms, gens)) + for j, r in enumerate(rels): + a = r.array_form + rel = identity + for sym, p in a: + rel = rel*subs[sym]**p + rels[j] = rel + return gens, rels + +def _simplify_relators(rels): + """ + Simplifies a set of relators. All relators are checked to see if they are + of the form `gen^n`. If any such relators are found then all other relators + are processed for strings in the `gen` known order. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import _simplify_relators + >>> F, x, y = free_group("x, y") + >>> w1 = [x**2*y**4, x**3] + >>> _simplify_relators(w1) + [x**3, x**-1*y**4] + + >>> w2 = [x**2*y**-4*x**5, x**3, x**2*y**8, y**5] + >>> _simplify_relators(w2) + [x**-1*y**-2, x**-1*y*x**-1, x**3, y**5] + + >>> w3 = [x**6*y**4, x**4] + >>> _simplify_relators(w3) + [x**4, x**2*y**4] + + >>> w4 = [x**2, x**5, y**3] + >>> _simplify_relators(w4) + [x, y**3] + + """ + rels = rels[:] + + if not rels: + return [] + + identity = rels[0].group.identity + + # build dictionary with "gen: n" where gen^n is one of the relators + exps = {} + for i in range(len(rels)): + rel = rels[i] + if rel.number_syllables() == 1: + g = rel[0] + exp = abs(rel.array_form[0][1]) + if rel.array_form[0][1] < 0: + rels[i] = rels[i]**-1 + g = g**-1 + if g in exps: + exp = gcd(exp, exps[g].array_form[0][1]) + exps[g] = g**exp + + one_syllables_words = list(exps.values()) + # decrease some of the exponents in relators, making use of the single + # syllable relators + for i, rel in enumerate(rels): + if rel in one_syllables_words: + continue + rel = rel.eliminate_words(one_syllables_words, _all = True) + # if rels[i] contains g**n where abs(n) is greater than half of the power p + # of g in exps, g**n can be replaced by g**(n-p) (or g**(p-n) if n<0) + for g in rel.contains_generators(): + if g in exps: + exp = exps[g].array_form[0][1] + max_exp = (exp + 1)//2 + rel = rel.eliminate_word(g**(max_exp), g**(max_exp-exp), _all = True) + rel = rel.eliminate_word(g**(-max_exp), g**(-(max_exp-exp)), _all = True) + rels[i] = rel + + rels = [r.identity_cyclic_reduction() for r in rels] + + rels += one_syllables_words # include one_syllable_words in the list of relators + rels = list(set(rels)) # get unique values in rels + rels.sort() + + # remove entries in rels + try: + rels.remove(identity) + except ValueError: + pass + return rels + +# Pg 350, section 2.5.1 from [2] +def elimination_technique_1(gens, rels, identity): + rels = rels[:] + # the shorter relators are examined first so that generators selected for + # elimination will have shorter strings as equivalent + rels.sort() + gens = gens[:] + redundant_gens = {} + redundant_rels = [] + used_gens = set() + # examine each relator in relator list for any generator occurring exactly + # once + for rel in rels: + # don't look for a redundant generator in a relator which + # depends on previously found ones + contained_gens = rel.contains_generators() + if any(g in contained_gens for g in redundant_gens): + continue + contained_gens = list(contained_gens) + contained_gens.sort(reverse = True) + for gen in contained_gens: + if rel.generator_count(gen) == 1 and gen not in used_gens: + k = rel.exponent_sum(gen) + gen_index = rel.index(gen**k) + bk = rel.subword(gen_index + 1, len(rel)) + fw = rel.subword(0, gen_index) + chi = bk*fw + redundant_gens[gen] = chi**(-1*k) + used_gens.update(chi.contains_generators()) + redundant_rels.append(rel) + break + rels = [r for r in rels if r not in redundant_rels] + # eliminate the redundant generators from remaining relators + rels = [r.eliminate_words(redundant_gens, _all = True).identity_cyclic_reduction() for r in rels] + rels = list(set(rels)) + try: + rels.remove(identity) + except ValueError: + pass + gens = [g for g in gens if g not in redundant_gens] + return gens, rels + +############################################################################### +# SUBGROUP PRESENTATIONS # +############################################################################### + +# Pg 175 [1] +def define_schreier_generators(C, homomorphism=False): + ''' + Parameters + ========== + + C -- Coset table. + homomorphism -- When set to True, return a dictionary containing the images + of the presentation generators in the original group. + ''' + y = [] + gamma = 1 + f = C.fp_group + X = f.generators + if homomorphism: + # `_gens` stores the elements of the parent group to + # to which the schreier generators correspond to. + _gens = {} + # compute the schreier Traversal + tau = {} + tau[0] = f.identity + C.P = [[None]*len(C.A) for i in range(C.n)] + for alpha, x in product(C.omega, C.A): + beta = C.table[alpha][C.A_dict[x]] + if beta == gamma: + C.P[alpha][C.A_dict[x]] = "" + C.P[beta][C.A_dict_inv[x]] = "" + gamma += 1 + if homomorphism: + tau[beta] = tau[alpha]*x + elif x in X and C.P[alpha][C.A_dict[x]] is None: + y_alpha_x = '%s_%s' % (x, alpha) + y.append(y_alpha_x) + C.P[alpha][C.A_dict[x]] = y_alpha_x + if homomorphism: + _gens[y_alpha_x] = tau[alpha]*x*tau[beta]**-1 + grp_gens = list(free_group(', '.join(y))) + C._schreier_free_group = grp_gens.pop(0) + C._schreier_generators = grp_gens + if homomorphism: + C._schreier_gen_elem = _gens + # replace all elements of P by, free group elements + for i, j in product(range(len(C.P)), range(len(C.A))): + # if equals "", replace by identity element + if C.P[i][j] == "": + C.P[i][j] = C._schreier_free_group.identity + elif isinstance(C.P[i][j], str): + r = C._schreier_generators[y.index(C.P[i][j])] + C.P[i][j] = r + beta = C.table[i][j] + C.P[beta][j + 1] = r**-1 + +def reidemeister_relators(C): + R = C.fp_group.relators + rels = [rewrite(C, coset, word) for word in R for coset in range(C.n)] + order_1_gens = {i for i in rels if len(i) == 1} + + # remove all the order 1 generators from relators + rels = list(filter(lambda rel: rel not in order_1_gens, rels)) + + # replace order 1 generators by identity element in reidemeister relators + for i in range(len(rels)): + w = rels[i] + w = w.eliminate_words(order_1_gens, _all=True) + rels[i] = w + + C._schreier_generators = [i for i in C._schreier_generators + if not (i in order_1_gens or i**-1 in order_1_gens)] + + # Tietze transformation 1 i.e TT_1 + # remove cyclic conjugate elements from relators + i = 0 + while i < len(rels): + w = rels[i] + j = i + 1 + while j < len(rels): + if w.is_cyclic_conjugate(rels[j]): + del rels[j] + else: + j += 1 + i += 1 + + C._reidemeister_relators = rels + + +def rewrite(C, alpha, w): + """ + Parameters + ========== + + C: CosetTable + alpha: A live coset + w: A word in `A*` + + Returns + ======= + + rho(tau(alpha), w) + + Examples + ======== + + >>> from sympy.combinatorics.fp_groups import FpGroup, CosetTable, define_schreier_generators, rewrite + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**2, y**3, (x*y)**6]) + >>> C = CosetTable(f, []) + >>> C.table = [[1, 1, 2, 3], [0, 0, 4, 5], [4, 4, 3, 0], [5, 5, 0, 2], [2, 2, 5, 1], [3, 3, 1, 4]] + >>> C.p = [0, 1, 2, 3, 4, 5] + >>> define_schreier_generators(C) + >>> rewrite(C, 0, (x*y)**6) + x_4*y_2*x_3*x_1*x_2*y_4*x_5 + + """ + v = C._schreier_free_group.identity + for i in range(len(w)): + x_i = w[i] + v = v*C.P[alpha][C.A_dict[x_i]] + alpha = C.table[alpha][C.A_dict[x_i]] + return v + +# Pg 350, section 2.5.2 from [2] +def elimination_technique_2(C): + """ + This technique eliminates one generator at a time. Heuristically this + seems superior in that we may select for elimination the generator with + shortest equivalent string at each stage. + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r, \ + reidemeister_relators, define_schreier_generators, elimination_technique_2 + >>> F, x, y = free_group("x, y") + >>> f = FpGroup(F, [x**3, y**5, (x*y)**2]); H = [x*y, x**-1*y**-1*x*y*x] + >>> C = coset_enumeration_r(f, H) + >>> C.compress(); C.standardize() + >>> define_schreier_generators(C) + >>> reidemeister_relators(C) + >>> elimination_technique_2(C) + ([y_1, y_2], [y_2**-3, y_2*y_1*y_2*y_1*y_2*y_1, y_1**2]) + + """ + rels = C._reidemeister_relators + rels.sort(reverse=True) + gens = C._schreier_generators + for i in range(len(gens) - 1, -1, -1): + rel = rels[i] + for j in range(len(gens) - 1, -1, -1): + gen = gens[j] + if rel.generator_count(gen) == 1: + k = rel.exponent_sum(gen) + gen_index = rel.index(gen**k) + bk = rel.subword(gen_index + 1, len(rel)) + fw = rel.subword(0, gen_index) + rep_by = (bk*fw)**(-1*k) + del rels[i] + del gens[j] + rels = [rel.eliminate_word(gen, rep_by) for rel in rels] + break + C._reidemeister_relators = rels + C._schreier_generators = gens + return C._schreier_generators, C._reidemeister_relators + +def reidemeister_presentation(fp_grp, H, C=None, homomorphism=False): + """ + Parameters + ========== + + fp_group: A finitely presented group, an instance of FpGroup + H: A subgroup whose presentation is to be found, given as a list + of words in generators of `fp_grp` + homomorphism: When set to True, return a homomorphism from the subgroup + to the parent group + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> from sympy.combinatorics.fp_groups import FpGroup, reidemeister_presentation + >>> F, x, y = free_group("x, y") + + Example 5.6 Pg. 177 from [1] + >>> f = FpGroup(F, [x**3, y**5, (x*y)**2]) + >>> H = [x*y, x**-1*y**-1*x*y*x] + >>> reidemeister_presentation(f, H) + ((y_1, y_2), (y_1**2, y_2**3, y_2*y_1*y_2*y_1*y_2*y_1)) + + Example 5.8 Pg. 183 from [1] + >>> f = FpGroup(F, [x**3, y**3, (x*y)**3]) + >>> H = [x*y, x*y**-1] + >>> reidemeister_presentation(f, H) + ((x_0, y_0), (x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0)) + + Exercises Q2. Pg 187 from [1] + >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3]) + >>> H = [x] + >>> reidemeister_presentation(f, H) + ((x_0,), (x_0**4,)) + + Example 5.9 Pg. 183 from [1] + >>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2]) + >>> H = [x] + >>> reidemeister_presentation(f, H) + ((x_0,), (x_0**6,)) + + """ + if not C: + C = coset_enumeration_r(fp_grp, H) + C.compress(); C.standardize() + define_schreier_generators(C, homomorphism=homomorphism) + reidemeister_relators(C) + gens, rels = C._schreier_generators, C._reidemeister_relators + gens, rels = simplify_presentation(gens, rels, change_gens=True) + + C.schreier_generators = tuple(gens) + C.reidemeister_relators = tuple(rels) + + if homomorphism: + _gens = [C._schreier_gen_elem[str(gen)] for gen in gens] + return C.schreier_generators, C.reidemeister_relators, _gens + + return C.schreier_generators, C.reidemeister_relators + + +FpGroupElement = FreeGroupElement diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/free_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/free_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..2ec85f4fac73fba95d4644a37ecb27140e45a4c5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/free_groups.py @@ -0,0 +1,1360 @@ +from __future__ import annotations + +from sympy.core import S +from sympy.core.expr import Expr +from sympy.core.symbol import Symbol, symbols as _symbols +from sympy.core.sympify import CantSympify +from sympy.printing.defaults import DefaultPrinting +from sympy.utilities import public +from sympy.utilities.iterables import flatten, is_sequence +from sympy.utilities.magic import pollute +from sympy.utilities.misc import as_int + + +@public +def free_group(symbols): + """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1))``. + + Parameters + ========== + + symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty) + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y, z = free_group("x, y, z") + >>> F + + >>> x**2*y**-1 + x**2*y**-1 + >>> type(_) + + + """ + _free_group = FreeGroup(symbols) + return (_free_group,) + tuple(_free_group.generators) + +@public +def xfree_group(symbols): + """Construct a free group returning ``(FreeGroup, (f_0, f_1, ..., f_(n-1)))``. + + Parameters + ========== + + symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty) + + Examples + ======== + + >>> from sympy.combinatorics.free_groups import xfree_group + >>> F, (x, y, z) = xfree_group("x, y, z") + >>> F + + >>> y**2*x**-2*z**-1 + y**2*x**-2*z**-1 + >>> type(_) + + + """ + _free_group = FreeGroup(symbols) + return (_free_group, _free_group.generators) + +@public +def vfree_group(symbols): + """Construct a free group and inject ``f_0, f_1, ..., f_(n-1)`` as symbols + into the global namespace. + + Parameters + ========== + + symbols : str, Symbol/Expr or sequence of str, Symbol/Expr (may be empty) + + Examples + ======== + + >>> from sympy.combinatorics.free_groups import vfree_group + >>> vfree_group("x, y, z") + + >>> x**2*y**-2*z # noqa: F821 + x**2*y**-2*z + >>> type(_) + + + """ + _free_group = FreeGroup(symbols) + pollute([sym.name for sym in _free_group.symbols], _free_group.generators) + return _free_group + + +def _parse_symbols(symbols): + if not symbols: + return () + if isinstance(symbols, str): + return _symbols(symbols, seq=True) + elif isinstance(symbols, (Expr, FreeGroupElement)): + return (symbols,) + elif is_sequence(symbols): + if all(isinstance(s, str) for s in symbols): + return _symbols(symbols) + elif all(isinstance(s, Expr) for s in symbols): + return symbols + raise ValueError("The type of `symbols` must be one of the following: " + "a str, Symbol/Expr or a sequence of " + "one of these types") + + +############################################################################## +# FREE GROUP # +############################################################################## + +_free_group_cache: dict[int, FreeGroup] = {} + +class FreeGroup(DefaultPrinting): + """ + Free group with finite or infinite number of generators. Its input API + is that of a str, Symbol/Expr or a sequence of one of + these types (which may be empty) + + See Also + ======== + + sympy.polys.rings.PolyRing + + References + ========== + + .. [1] https://www.gap-system.org/Manuals/doc/ref/chap37.html + + .. [2] https://en.wikipedia.org/wiki/Free_group + + """ + is_associative = True + is_group = True + is_FreeGroup = True + is_PermutationGroup = False + relators: list[Expr] = [] + + def __new__(cls, symbols): + symbols = tuple(_parse_symbols(symbols)) + rank = len(symbols) + _hash = hash((cls.__name__, symbols, rank)) + obj = _free_group_cache.get(_hash) + + if obj is None: + obj = object.__new__(cls) + obj._hash = _hash + obj._rank = rank + # dtype method is used to create new instances of FreeGroupElement + obj.dtype = type("FreeGroupElement", (FreeGroupElement,), {"group": obj}) + obj.symbols = symbols + obj.generators = obj._generators() + obj._gens_set = set(obj.generators) + for symbol, generator in zip(obj.symbols, obj.generators): + if isinstance(symbol, Symbol): + name = symbol.name + if hasattr(obj, name): + setattr(obj, name, generator) + + _free_group_cache[_hash] = obj + + return obj + + def __getnewargs__(self): + """Return a tuple of arguments that must be passed to __new__ in order to support pickling this object.""" + return (self.symbols,) + + def __getstate__(self): + # Don't pickle any fields because they are regenerated within __new__ + return None + + def _generators(group): + """Returns the generators of the FreeGroup. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y, z = free_group("x, y, z") + >>> F.generators + (x, y, z) + + """ + gens = [] + for sym in group.symbols: + elm = ((sym, 1),) + gens.append(group.dtype(elm)) + return tuple(gens) + + def clone(self, symbols=None): + return self.__class__(symbols or self.symbols) + + def __contains__(self, i): + """Return True if ``i`` is contained in FreeGroup.""" + if not isinstance(i, FreeGroupElement): + return False + group = i.group + return self == group + + def __hash__(self): + return self._hash + + def __len__(self): + return self.rank + + def __str__(self): + if self.rank > 30: + str_form = "" % self.rank + else: + str_form = "" + return str_form + + __repr__ = __str__ + + def __getitem__(self, index): + symbols = self.symbols[index] + return self.clone(symbols=symbols) + + def __eq__(self, other): + """No ``FreeGroup`` is equal to any "other" ``FreeGroup``. + """ + return self is other + + def index(self, gen): + """Return the index of the generator `gen` from ``(f_0, ..., f_(n-1))``. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> F.index(y) + 1 + >>> F.index(x) + 0 + + """ + if isinstance(gen, self.dtype): + return self.generators.index(gen) + else: + raise ValueError("expected a generator of Free Group %s, got %s" % (self, gen)) + + def order(self): + """Return the order of the free group. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> F.order() + oo + + >>> free_group("")[0].order() + 1 + + """ + if self.rank == 0: + return S.One + else: + return S.Infinity + + @property + def elements(self): + """ + Return the elements of the free group. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> (z,) = free_group("") + >>> z.elements + {} + + """ + if self.rank == 0: + # A set containing Identity element of `FreeGroup` self is returned + return {self.identity} + else: + raise ValueError("Group contains infinitely many elements" + ", hence cannot be represented") + + @property + def rank(self): + r""" + In group theory, the `rank` of a group `G`, denoted `G.rank`, + can refer to the smallest cardinality of a generating set + for G, that is + + \operatorname{rank}(G)=\min\{ |X|: X\subseteq G, \left\langle X\right\rangle =G\}. + + """ + return self._rank + + @property + def is_abelian(self): + """Returns if the group is Abelian. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y, z = free_group("x y z") + >>> f.is_abelian + False + + """ + return self.rank in (0, 1) + + @property + def identity(self): + """Returns the identity element of free group.""" + return self.dtype() + + def contains(self, g): + """Tests if Free Group element ``g`` belong to self, ``G``. + + In mathematical terms any linear combination of generators + of a Free Group is contained in it. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y, z = free_group("x y z") + >>> f.contains(x**3*y**2) + True + + """ + if not isinstance(g, FreeGroupElement): + return False + elif self != g.group: + return False + else: + return True + + def center(self): + """Returns the center of the free group `self`.""" + return {self.identity} + + +############################################################################ +# FreeGroupElement # +############################################################################ + + +class FreeGroupElement(CantSympify, DefaultPrinting, tuple): + """Used to create elements of FreeGroup. It cannot be used directly to + create a free group element. It is called by the `dtype` method of the + `FreeGroup` class. + + """ + __slots__ = () + is_assoc_word = True + + def new(self, init): + return self.__class__(init) + + _hash = None + + def __hash__(self): + _hash = self._hash + if _hash is None: + self._hash = _hash = hash((self.group, frozenset(tuple(self)))) + return _hash + + def copy(self): + return self.new(self) + + @property + def is_identity(self): + return not self.array_form + + @property + def array_form(self): + """ + SymPy provides two different internal kinds of representation + of associative words. The first one is called the `array_form` + which is a tuple containing `tuples` as its elements, where the + size of each tuple is two. At the first position the tuple + contains the `symbol-generator`, while at the second position + of tuple contains the exponent of that generator at the position. + Since elements (i.e. words) do not commute, the indexing of tuple + makes that property to stay. + + The structure in ``array_form`` of ``FreeGroupElement`` is of form: + + ``( ( symbol_of_gen, exponent ), ( , ), ... ( , ) )`` + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y, z = free_group("x y z") + >>> (x*z).array_form + ((x, 1), (z, 1)) + >>> (x**2*z*y*x**2).array_form + ((x, 2), (z, 1), (y, 1), (x, 2)) + + See Also + ======== + + letter_repr + + """ + return tuple(self) + + @property + def letter_form(self): + """ + The letter representation of a ``FreeGroupElement`` is a tuple + of generator symbols, with each entry corresponding to a group + generator. Inverses of the generators are represented by + negative generator symbols. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b, c, d = free_group("a b c d") + >>> (a**3).letter_form + (a, a, a) + >>> (a**2*d**-2*a*b**-4).letter_form + (a, a, -d, -d, a, -b, -b, -b, -b) + >>> (a**-2*b**3*d).letter_form + (-a, -a, b, b, b, d) + + See Also + ======== + + array_form + + """ + return tuple(flatten([(i,)*j if j > 0 else (-i,)*(-j) + for i, j in self.array_form])) + + def __getitem__(self, i): + group = self.group + r = self.letter_form[i] + if r.is_Symbol: + return group.dtype(((r, 1),)) + else: + return group.dtype(((-r, -1),)) + + def index(self, gen): + if len(gen) != 1: + raise ValueError() + return (self.letter_form).index(gen.letter_form[0]) + + @property + def letter_form_elm(self): + """ + """ + group = self.group + r = self.letter_form + return [group.dtype(((elm,1),)) if elm.is_Symbol \ + else group.dtype(((-elm,-1),)) for elm in r] + + @property + def ext_rep(self): + """This is called the External Representation of ``FreeGroupElement`` + """ + return tuple(flatten(self.array_form)) + + def __contains__(self, gen): + return gen.array_form[0][0] in tuple([r[0] for r in self.array_form]) + + def __str__(self): + if self.is_identity: + return "" + + str_form = "" + array_form = self.array_form + for i in range(len(array_form)): + if i == len(array_form) - 1: + if array_form[i][1] == 1: + str_form += str(array_form[i][0]) + else: + str_form += str(array_form[i][0]) + \ + "**" + str(array_form[i][1]) + else: + if array_form[i][1] == 1: + str_form += str(array_form[i][0]) + "*" + else: + str_form += str(array_form[i][0]) + \ + "**" + str(array_form[i][1]) + "*" + return str_form + + __repr__ = __str__ + + def __pow__(self, n): + n = as_int(n) + result = self.group.identity + if n == 0: + return result + if n < 0: + n = -n + x = self.inverse() + else: + x = self + while True: + if n % 2: + result *= x + n >>= 1 + if not n: + break + x *= x + return result + + def __mul__(self, other): + """Returns the product of elements belonging to the same ``FreeGroup``. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y, z = free_group("x y z") + >>> x*y**2*y**-4 + x*y**-2 + >>> z*y**-2 + z*y**-2 + >>> x**2*y*y**-1*x**-2 + + + """ + group = self.group + if not isinstance(other, group.dtype): + raise TypeError("only FreeGroup elements of same FreeGroup can " + "be multiplied") + if self.is_identity: + return other + if other.is_identity: + return self + r = list(self.array_form + other.array_form) + zero_mul_simp(r, len(self.array_form) - 1) + return group.dtype(tuple(r)) + + def __truediv__(self, other): + group = self.group + if not isinstance(other, group.dtype): + raise TypeError("only FreeGroup elements of same FreeGroup can " + "be multiplied") + return self*(other.inverse()) + + def __rtruediv__(self, other): + group = self.group + if not isinstance(other, group.dtype): + raise TypeError("only FreeGroup elements of same FreeGroup can " + "be multiplied") + return other*(self.inverse()) + + def __add__(self, other): + return NotImplemented + + def inverse(self): + """ + Returns the inverse of a ``FreeGroupElement`` element + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y, z = free_group("x y z") + >>> x.inverse() + x**-1 + >>> (x*y).inverse() + y**-1*x**-1 + + """ + group = self.group + r = tuple([(i, -j) for i, j in self.array_form[::-1]]) + return group.dtype(r) + + def order(self): + """Find the order of a ``FreeGroupElement``. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y = free_group("x y") + >>> (x**2*y*y**-1*x**-2).order() + 1 + + """ + if self.is_identity: + return S.One + else: + return S.Infinity + + def commutator(self, other): + """ + Return the commutator of `self` and `x`: ``~x*~self*x*self`` + + """ + group = self.group + if not isinstance(other, group.dtype): + raise ValueError("commutator of only FreeGroupElement of the same " + "FreeGroup exists") + else: + return self.inverse()*other.inverse()*self*other + + def eliminate_words(self, words, _all=False, inverse=True): + ''' + Replace each subword from the dictionary `words` by words[subword]. + If words is a list, replace the words by the identity. + + ''' + again = True + new = self + if isinstance(words, dict): + while again: + again = False + for sub in words: + prev = new + new = new.eliminate_word(sub, words[sub], _all=_all, inverse=inverse) + if new != prev: + again = True + else: + while again: + again = False + for sub in words: + prev = new + new = new.eliminate_word(sub, _all=_all, inverse=inverse) + if new != prev: + again = True + return new + + def eliminate_word(self, gen, by=None, _all=False, inverse=True): + """ + For an associative word `self`, a subword `gen`, and an associative + word `by` (identity by default), return the associative word obtained by + replacing each occurrence of `gen` in `self` by `by`. If `_all = True`, + the occurrences of `gen` that may appear after the first substitution will + also be replaced and so on until no occurrences are found. This might not + always terminate (e.g. `(x).eliminate_word(x, x**2, _all=True)`). + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y = free_group("x y") + >>> w = x**5*y*x**2*y**-4*x + >>> w.eliminate_word( x, x**2 ) + x**10*y*x**4*y**-4*x**2 + >>> w.eliminate_word( x, y**-1 ) + y**-11 + >>> w.eliminate_word(x**5) + y*x**2*y**-4*x + >>> w.eliminate_word(x*y, y) + x**4*y*x**2*y**-4*x + + See Also + ======== + substituted_word + + """ + if by is None: + by = self.group.identity + if self.is_independent(gen) or gen == by: + return self + if gen == self: + return by + if gen**-1 == by: + _all = False + word = self + l = len(gen) + + try: + i = word.subword_index(gen) + k = 1 + except ValueError: + if not inverse: + return word + try: + i = word.subword_index(gen**-1) + k = -1 + except ValueError: + return word + + word = word.subword(0, i)*by**k*word.subword(i+l, len(word)).eliminate_word(gen, by) + + if _all: + return word.eliminate_word(gen, by, _all=True, inverse=inverse) + else: + return word + + def __len__(self): + """ + For an associative word `self`, returns the number of letters in it. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a b") + >>> w = a**5*b*a**2*b**-4*a + >>> len(w) + 13 + >>> len(a**17) + 17 + >>> len(w**0) + 0 + + """ + return sum(abs(j) for (i, j) in self) + + def __eq__(self, other): + """ + Two associative words are equal if they are words over the + same alphabet and if they are sequences of the same letters. + This is equivalent to saying that the external representations + of the words are equal. + There is no "universal" empty word, every alphabet has its own + empty word. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1") + >>> f + + >>> g, swap0, swap1 = free_group("swap0 swap1") + >>> g + + + >>> swapnil0 == swapnil1 + False + >>> swapnil0*swapnil1 == swapnil1/swapnil1*swapnil0*swapnil1 + True + >>> swapnil0*swapnil1 == swapnil1*swapnil0 + False + >>> swapnil1**0 == swap0**0 + False + + """ + group = self.group + if not isinstance(other, group.dtype): + return False + return tuple.__eq__(self, other) + + def __lt__(self, other): + """ + The ordering of associative words is defined by length and + lexicography (this ordering is called short-lex ordering), that + is, shorter words are smaller than longer words, and words of the + same length are compared w.r.t. the lexicographical ordering induced + by the ordering of generators. Generators are sorted according + to the order in which they were created. If the generators are + invertible then each generator `g` is larger than its inverse `g^{-1}`, + and `g^{-1}` is larger than every generator that is smaller than `g`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a b") + >>> b < a + False + >>> a < a.inverse() + False + + """ + group = self.group + if not isinstance(other, group.dtype): + raise TypeError("only FreeGroup elements of same FreeGroup can " + "be compared") + l = len(self) + m = len(other) + # implement lenlex order + if l < m: + return True + elif l > m: + return False + for i in range(l): + a = self[i].array_form[0] + b = other[i].array_form[0] + p = group.symbols.index(a[0]) + q = group.symbols.index(b[0]) + if p < q: + return True + elif p > q: + return False + elif a[1] < b[1]: + return True + elif a[1] > b[1]: + return False + return False + + def __le__(self, other): + return (self == other or self < other) + + def __gt__(self, other): + """ + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, x, y, z = free_group("x y z") + >>> y**2 > x**2 + True + >>> y*z > z*y + False + >>> x > x.inverse() + True + + """ + group = self.group + if not isinstance(other, group.dtype): + raise TypeError("only FreeGroup elements of same FreeGroup can " + "be compared") + return not self <= other + + def __ge__(self, other): + return not self < other + + def exponent_sum(self, gen): + """ + For an associative word `self` and a generator or inverse of generator + `gen`, ``exponent_sum`` returns the number of times `gen` appears in + `self` minus the number of times its inverse appears in `self`. If + neither `gen` nor its inverse occur in `self` then 0 is returned. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> w = x**2*y**3 + >>> w.exponent_sum(x) + 2 + >>> w.exponent_sum(x**-1) + -2 + >>> w = x**2*y**4*x**-3 + >>> w.exponent_sum(x) + -1 + + See Also + ======== + + generator_count + + """ + if len(gen) != 1: + raise ValueError("gen must be a generator or inverse of a generator") + s = gen.array_form[0] + return s[1]*sum(i[1] for i in self.array_form if i[0] == s[0]) + + def generator_count(self, gen): + """ + For an associative word `self` and a generator `gen`, + ``generator_count`` returns the multiplicity of generator + `gen` in `self`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> w = x**2*y**3 + >>> w.generator_count(x) + 2 + >>> w = x**2*y**4*x**-3 + >>> w.generator_count(x) + 5 + + See Also + ======== + + exponent_sum + + """ + if len(gen) != 1 or gen.array_form[0][1] < 0: + raise ValueError("gen must be a generator") + s = gen.array_form[0] + return s[1]*sum(abs(i[1]) for i in self.array_form if i[0] == s[0]) + + def subword(self, from_i, to_j, strict=True): + """ + For an associative word `self` and two positive integers `from_i` and + `to_j`, `subword` returns the subword of `self` that begins at position + `from_i` and ends at `to_j - 1`, indexing is done with origin 0. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a b") + >>> w = a**5*b*a**2*b**-4*a + >>> w.subword(2, 6) + a**3*b + + """ + group = self.group + if not strict: + from_i = max(from_i, 0) + to_j = min(len(self), to_j) + if from_i < 0 or to_j > len(self): + raise ValueError("`from_i`, `to_j` must be positive and no greater than " + "the length of associative word") + if to_j <= from_i: + return group.identity + else: + letter_form = self.letter_form[from_i: to_j] + array_form = letter_form_to_array_form(letter_form, group) + return group.dtype(array_form) + + def subword_index(self, word, start = 0): + ''' + Find the index of `word` in `self`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a b") + >>> w = a**2*b*a*b**3 + >>> w.subword_index(a*b*a*b) + 1 + + ''' + l = len(word) + self_lf = self.letter_form + word_lf = word.letter_form + index = None + for i in range(start,len(self_lf)-l+1): + if self_lf[i:i+l] == word_lf: + index = i + break + if index is not None: + return index + else: + raise ValueError("The given word is not a subword of self") + + def is_dependent(self, word): + """ + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> (x**4*y**-3).is_dependent(x**4*y**-2) + True + >>> (x**2*y**-1).is_dependent(x*y) + False + >>> (x*y**2*x*y**2).is_dependent(x*y**2) + True + >>> (x**12).is_dependent(x**-4) + True + + See Also + ======== + + is_independent + + """ + try: + return self.subword_index(word) is not None + except ValueError: + pass + try: + return self.subword_index(word**-1) is not None + except ValueError: + return False + + def is_independent(self, word): + """ + + See Also + ======== + + is_dependent + + """ + return not self.is_dependent(word) + + def contains_generators(self): + """ + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y, z = free_group("x, y, z") + >>> (x**2*y**-1).contains_generators() + {x, y} + >>> (x**3*z).contains_generators() + {x, z} + + """ + group = self.group + gens = {group.dtype(((syllable[0], 1),)) for syllable in self.array_form} + return gens + + def cyclic_subword(self, from_i, to_j): + group = self.group + l = len(self) + letter_form = self.letter_form + period1 = int(from_i/l) + if from_i >= l: + from_i -= l*period1 + to_j -= l*period1 + diff = to_j - from_i + word = letter_form[from_i: to_j] + period2 = int(to_j/l) - 1 + word += letter_form*period2 + letter_form[:diff-l+from_i-l*period2] + word = letter_form_to_array_form(word, group) + return group.dtype(word) + + def cyclic_conjugates(self): + """Returns a words which are cyclic to the word `self`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> w = x*y*x*y*x + >>> w.cyclic_conjugates() + {x*y*x**2*y, x**2*y*x*y, y*x*y*x**2, y*x**2*y*x, x*y*x*y*x} + >>> s = x*y*x**2*y*x + >>> s.cyclic_conjugates() + {x**2*y*x**2*y, y*x**2*y*x**2, x*y*x**2*y*x} + + References + ========== + + .. [1] https://planetmath.org/cyclicpermutation + + """ + return {self.cyclic_subword(i, i+len(self)) for i in range(len(self))} + + def is_cyclic_conjugate(self, w): + """ + Checks whether words ``self``, ``w`` are cyclic conjugates. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> w1 = x**2*y**5 + >>> w2 = x*y**5*x + >>> w1.is_cyclic_conjugate(w2) + True + >>> w3 = x**-1*y**5*x**-1 + >>> w3.is_cyclic_conjugate(w2) + False + + """ + l1 = len(self) + l2 = len(w) + if l1 != l2: + return False + w1 = self.identity_cyclic_reduction() + w2 = w.identity_cyclic_reduction() + letter1 = w1.letter_form + letter2 = w2.letter_form + str1 = ' '.join(map(str, letter1)) + str2 = ' '.join(map(str, letter2)) + if len(str1) != len(str2): + return False + + return str1 in str2 + ' ' + str2 + + def number_syllables(self): + """Returns the number of syllables of the associative word `self`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, swapnil0, swapnil1 = free_group("swapnil0 swapnil1") + >>> (swapnil1**3*swapnil0*swapnil1**-1).number_syllables() + 3 + + """ + return len(self.array_form) + + def exponent_syllable(self, i): + """ + Returns the exponent of the `i`-th syllable of the associative word + `self`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a b") + >>> w = a**5*b*a**2*b**-4*a + >>> w.exponent_syllable( 2 ) + 2 + + """ + return self.array_form[i][1] + + def generator_syllable(self, i): + """ + Returns the symbol of the generator that is involved in the + i-th syllable of the associative word `self`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a b") + >>> w = a**5*b*a**2*b**-4*a + >>> w.generator_syllable( 3 ) + b + + """ + return self.array_form[i][0] + + def sub_syllables(self, from_i, to_j): + """ + `sub_syllables` returns the subword of the associative word `self` that + consists of syllables from positions `from_to` to `to_j`, where + `from_to` and `to_j` must be positive integers and indexing is done + with origin 0. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> f, a, b = free_group("a, b") + >>> w = a**5*b*a**2*b**-4*a + >>> w.sub_syllables(1, 2) + b + >>> w.sub_syllables(3, 3) + + + """ + if not isinstance(from_i, int) or not isinstance(to_j, int): + raise ValueError("both arguments should be integers") + group = self.group + if to_j <= from_i: + return group.identity + else: + r = tuple(self.array_form[from_i: to_j]) + return group.dtype(r) + + def substituted_word(self, from_i, to_j, by): + """ + Returns the associative word obtained by replacing the subword of + `self` that begins at position `from_i` and ends at position `to_j - 1` + by the associative word `by`. `from_i` and `to_j` must be positive + integers, indexing is done with origin 0. In other words, + `w.substituted_word(w, from_i, to_j, by)` is the product of the three + words: `w.subword(0, from_i)`, `by`, and + `w.subword(to_j len(w))`. + + See Also + ======== + + eliminate_word + + """ + lw = len(self) + if from_i >= to_j or from_i > lw or to_j > lw: + raise ValueError("values should be within bounds") + + # otherwise there are four possibilities + + # first if from=1 and to=lw then + if from_i == 0 and to_j == lw: + return by + elif from_i == 0: # second if from_i=1 (and to_j < lw) then + return by*self.subword(to_j, lw) + elif to_j == lw: # third if to_j=1 (and from_i > 1) then + return self.subword(0, from_i)*by + else: # finally + return self.subword(0, from_i)*by*self.subword(to_j, lw) + + def is_cyclically_reduced(self): + r"""Returns whether the word is cyclically reduced or not. + A word is cyclically reduced if by forming the cycle of the + word, the word is not reduced, i.e a word w = `a_1 ... a_n` + is called cyclically reduced if `a_1 \ne a_n^{-1}`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> (x**2*y**-1*x**-1).is_cyclically_reduced() + False + >>> (y*x**2*y**2).is_cyclically_reduced() + True + + """ + if not self: + return True + return self[0] != self[-1]**-1 + + def identity_cyclic_reduction(self): + """Return a unique cyclically reduced version of the word. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> (x**2*y**2*x**-1).identity_cyclic_reduction() + x*y**2 + >>> (x**-3*y**-1*x**5).identity_cyclic_reduction() + x**2*y**-1 + + References + ========== + + .. [1] https://planetmath.org/cyclicallyreduced + + """ + word = self.copy() + group = self.group + while not word.is_cyclically_reduced(): + exp1 = word.exponent_syllable(0) + exp2 = word.exponent_syllable(-1) + r = exp1 + exp2 + if r == 0: + rep = word.array_form[1: word.number_syllables() - 1] + else: + rep = ((word.generator_syllable(0), exp1 + exp2),) + \ + word.array_form[1: word.number_syllables() - 1] + word = group.dtype(rep) + return word + + def cyclic_reduction(self, removed=False): + """Return a cyclically reduced version of the word. Unlike + `identity_cyclic_reduction`, this will not cyclically permute + the reduced word - just remove the "unreduced" bits on either + side of it. Compare the examples with those of + `identity_cyclic_reduction`. + + When `removed` is `True`, return a tuple `(word, r)` where + self `r` is such that before the reduction the word was either + `r*word*r**-1`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> (x**2*y**2*x**-1).cyclic_reduction() + x*y**2 + >>> (x**-3*y**-1*x**5).cyclic_reduction() + y**-1*x**2 + >>> (x**-3*y**-1*x**5).cyclic_reduction(removed=True) + (y**-1*x**2, x**-3) + + """ + word = self.copy() + g = self.group.identity + while not word.is_cyclically_reduced(): + exp1 = abs(word.exponent_syllable(0)) + exp2 = abs(word.exponent_syllable(-1)) + exp = min(exp1, exp2) + start = word[0]**abs(exp) + end = word[-1]**abs(exp) + word = start**-1*word*end**-1 + g = g*start + if removed: + return word, g + return word + + def power_of(self, other): + ''' + Check if `self == other**n` for some integer n. + + Examples + ======== + + >>> from sympy.combinatorics import free_group + >>> F, x, y = free_group("x, y") + >>> ((x*y)**2).power_of(x*y) + True + >>> (x**-3*y**-2*x**3).power_of(x**-3*y*x**3) + True + + ''' + if self.is_identity: + return True + + l = len(other) + if l == 1: + # self has to be a power of one generator + gens = self.contains_generators() + s = other in gens or other**-1 in gens + return len(gens) == 1 and s + + # if self is not cyclically reduced and it is a power of other, + # other isn't cyclically reduced and the parts removed during + # their reduction must be equal + reduced, r1 = self.cyclic_reduction(removed=True) + if not r1.is_identity: + other, r2 = other.cyclic_reduction(removed=True) + if r1 == r2: + return reduced.power_of(other) + return False + + if len(self) < l or len(self) % l: + return False + + prefix = self.subword(0, l) + if prefix == other or prefix**-1 == other: + rest = self.subword(l, len(self)) + return rest.power_of(other) + return False + + +def letter_form_to_array_form(array_form, group): + """ + This method converts a list given with possible repetitions of elements in + it. It returns a new list such that repetitions of consecutive elements is + removed and replace with a tuple element of size two such that the first + index contains `value` and the second index contains the number of + consecutive repetitions of `value`. + + """ + a = list(array_form[:]) + new_array = [] + n = 1 + symbols = group.symbols + for i in range(len(a)): + if i == len(a) - 1: + if a[i] == a[i - 1]: + if (-a[i]) in symbols: + new_array.append((-a[i], -n)) + else: + new_array.append((a[i], n)) + else: + if (-a[i]) in symbols: + new_array.append((-a[i], -1)) + else: + new_array.append((a[i], 1)) + return new_array + elif a[i] == a[i + 1]: + n += 1 + else: + if (-a[i]) in symbols: + new_array.append((-a[i], -n)) + else: + new_array.append((a[i], n)) + n = 1 + + +def zero_mul_simp(l, index): + """Used to combine two reduced words.""" + while index >=0 and index < len(l) - 1 and l[index][0] == l[index + 1][0]: + exp = l[index][1] + l[index + 1][1] + base = l[index][0] + l[index] = (base, exp) + del l[index + 1] + if l[index][1] == 0: + del l[index] + index -= 1 diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/galois.py b/phivenv/Lib/site-packages/sympy/combinatorics/galois.py new file mode 100644 index 0000000000000000000000000000000000000000..f8ff89886283903e116bf40e1be6f6131386e802 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/galois.py @@ -0,0 +1,611 @@ +r""" +Construct transitive subgroups of symmetric groups, useful in Galois theory. + +Besides constructing instances of the :py:class:`~.PermutationGroup` class to +represent the transitive subgroups of $S_n$ for small $n$, this module provides +*names* for these groups. + +In some applications, it may be preferable to know the name of a group, +rather than receive an instance of the :py:class:`~.PermutationGroup` +class, and then have to do extra work to determine which group it is, by +checking various properties. + +Names are instances of ``Enum`` classes defined in this module. With a name in +hand, the name's ``get_perm_group`` method can then be used to retrieve a +:py:class:`~.PermutationGroup`. + +The names used for groups in this module are taken from [1]. + +References +========== + +.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*. + +""" + +from collections import defaultdict +from enum import Enum +import itertools + +from sympy.combinatorics.named_groups import ( + SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup, + set_symmetric_group_properties, set_alternating_group_properties, +) +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.combinatorics.permutations import Permutation + + +class S1TransitiveSubgroups(Enum): + """ + Names for the transitive subgroups of S1. + """ + S1 = "S1" + + def get_perm_group(self): + return SymmetricGroup(1) + + +class S2TransitiveSubgroups(Enum): + """ + Names for the transitive subgroups of S2. + """ + S2 = "S2" + + def get_perm_group(self): + return SymmetricGroup(2) + + +class S3TransitiveSubgroups(Enum): + """ + Names for the transitive subgroups of S3. + """ + A3 = "A3" + S3 = "S3" + + def get_perm_group(self): + if self == S3TransitiveSubgroups.A3: + return AlternatingGroup(3) + elif self == S3TransitiveSubgroups.S3: + return SymmetricGroup(3) + + +class S4TransitiveSubgroups(Enum): + """ + Names for the transitive subgroups of S4. + """ + C4 = "C4" + V = "V" + D4 = "D4" + A4 = "A4" + S4 = "S4" + + def get_perm_group(self): + if self == S4TransitiveSubgroups.C4: + return CyclicGroup(4) + elif self == S4TransitiveSubgroups.V: + return four_group() + elif self == S4TransitiveSubgroups.D4: + return DihedralGroup(4) + elif self == S4TransitiveSubgroups.A4: + return AlternatingGroup(4) + elif self == S4TransitiveSubgroups.S4: + return SymmetricGroup(4) + + +class S5TransitiveSubgroups(Enum): + """ + Names for the transitive subgroups of S5. + """ + C5 = "C5" + D5 = "D5" + M20 = "M20" + A5 = "A5" + S5 = "S5" + + def get_perm_group(self): + if self == S5TransitiveSubgroups.C5: + return CyclicGroup(5) + elif self == S5TransitiveSubgroups.D5: + return DihedralGroup(5) + elif self == S5TransitiveSubgroups.M20: + return M20() + elif self == S5TransitiveSubgroups.A5: + return AlternatingGroup(5) + elif self == S5TransitiveSubgroups.S5: + return SymmetricGroup(5) + + +class S6TransitiveSubgroups(Enum): + """ + Names for the transitive subgroups of S6. + """ + C6 = "C6" + S3 = "S3" + D6 = "D6" + A4 = "A4" + G18 = "G18" + A4xC2 = "A4 x C2" + S4m = "S4-" + S4p = "S4+" + G36m = "G36-" + G36p = "G36+" + S4xC2 = "S4 x C2" + PSL2F5 = "PSL2(F5)" + G72 = "G72" + PGL2F5 = "PGL2(F5)" + A6 = "A6" + S6 = "S6" + + def get_perm_group(self): + if self == S6TransitiveSubgroups.C6: + return CyclicGroup(6) + elif self == S6TransitiveSubgroups.S3: + return S3_in_S6() + elif self == S6TransitiveSubgroups.D6: + return DihedralGroup(6) + elif self == S6TransitiveSubgroups.A4: + return A4_in_S6() + elif self == S6TransitiveSubgroups.G18: + return G18() + elif self == S6TransitiveSubgroups.A4xC2: + return A4xC2() + elif self == S6TransitiveSubgroups.S4m: + return S4m() + elif self == S6TransitiveSubgroups.S4p: + return S4p() + elif self == S6TransitiveSubgroups.G36m: + return G36m() + elif self == S6TransitiveSubgroups.G36p: + return G36p() + elif self == S6TransitiveSubgroups.S4xC2: + return S4xC2() + elif self == S6TransitiveSubgroups.PSL2F5: + return PSL2F5() + elif self == S6TransitiveSubgroups.G72: + return G72() + elif self == S6TransitiveSubgroups.PGL2F5: + return PGL2F5() + elif self == S6TransitiveSubgroups.A6: + return AlternatingGroup(6) + elif self == S6TransitiveSubgroups.S6: + return SymmetricGroup(6) + + +def four_group(): + """ + Return a representation of the Klein four-group as a transitive subgroup + of S4. + """ + return PermutationGroup( + Permutation(0, 1)(2, 3), + Permutation(0, 2)(1, 3) + ) + + +def M20(): + """ + Return a representation of the metacyclic group M20, a transitive subgroup + of S5 that is one of the possible Galois groups for polys of degree 5. + + Notes + ===== + + See [1], Page 323. + + """ + G = PermutationGroup(Permutation(0, 1, 2, 3, 4), Permutation(1, 2, 4, 3)) + G._degree = 5 + G._order = 20 + G._is_transitive = True + G._is_sym = False + G._is_alt = False + G._is_cyclic = False + G._is_dihedral = False + return G + + +def S3_in_S6(): + """ + Return a representation of S3 as a transitive subgroup of S6. + + Notes + ===== + + The representation is found by viewing the group as the symmetries of a + triangular prism. + + """ + G = PermutationGroup(Permutation(0, 1, 2)(3, 4, 5), Permutation(0, 3)(2, 4)(1, 5)) + set_symmetric_group_properties(G, 3, 6) + return G + + +def A4_in_S6(): + """ + Return a representation of A4 as a transitive subgroup of S6. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + G = PermutationGroup(Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4)) + set_alternating_group_properties(G, 4, 6) + return G + + +def S4m(): + """ + Return a representation of the S4- transitive subgroup of S6. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + G = PermutationGroup(Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3)) + set_symmetric_group_properties(G, 4, 6) + return G + + +def S4p(): + """ + Return a representation of the S4+ transitive subgroup of S6. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + G = PermutationGroup(Permutation(0, 2, 4, 1)(3, 5), Permutation(0, 3)(4, 5)) + set_symmetric_group_properties(G, 4, 6) + return G + + +def A4xC2(): + """ + Return a representation of the (A4 x C2) transitive subgroup of S6. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + return PermutationGroup( + Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4), + Permutation(5)(2, 4)) + + +def S4xC2(): + """ + Return a representation of the (S4 x C2) transitive subgroup of S6. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + return PermutationGroup( + Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3), + Permutation(1, 4)(3, 5)) + + +def G18(): + """ + Return a representation of the group G18, a transitive subgroup of S6 + isomorphic to the semidirect product of C3^2 with C2. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + return PermutationGroup( + Permutation(5)(0, 1, 2), Permutation(3, 4, 5), + Permutation(0, 4)(1, 5)(2, 3)) + + +def G36m(): + """ + Return a representation of the group G36-, a transitive subgroup of S6 + isomorphic to the semidirect product of C3^2 with C2^2. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + return PermutationGroup( + Permutation(5)(0, 1, 2), Permutation(3, 4, 5), + Permutation(1, 2)(3, 5), Permutation(0, 4)(1, 5)(2, 3)) + + +def G36p(): + """ + Return a representation of the group G36+, a transitive subgroup of S6 + isomorphic to the semidirect product of C3^2 with C4. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + return PermutationGroup( + Permutation(5)(0, 1, 2), Permutation(3, 4, 5), + Permutation(0, 5, 2, 3)(1, 4)) + + +def G72(): + """ + Return a representation of the group G72, a transitive subgroup of S6 + isomorphic to the semidirect product of C3^2 with D4. + + Notes + ===== + + See [1], Page 325. + + """ + return PermutationGroup( + Permutation(5)(0, 1, 2), + Permutation(0, 4, 1, 3)(2, 5), Permutation(0, 3)(1, 4)(2, 5)) + + +def PSL2F5(): + r""" + Return a representation of the group $PSL_2(\mathbb{F}_5)$, as a transitive + subgroup of S6, isomorphic to $A_5$. + + Notes + ===== + + This was computed using :py:func:`~.find_transitive_subgroups_of_S6`. + + """ + G = PermutationGroup( + Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 4, 3, 1, 5)) + set_alternating_group_properties(G, 5, 6) + return G + + +def PGL2F5(): + r""" + Return a representation of the group $PGL_2(\mathbb{F}_5)$, as a transitive + subgroup of S6, isomorphic to $S_5$. + + Notes + ===== + + See [1], Page 325. + + """ + G = PermutationGroup( + Permutation(0, 1, 2, 3, 4), Permutation(0, 5)(1, 2)(3, 4)) + set_symmetric_group_properties(G, 5, 6) + return G + + +def find_transitive_subgroups_of_S6(*targets, print_report=False): + r""" + Search for certain transitive subgroups of $S_6$. + + The symmetric group $S_6$ has 16 different transitive subgroups, up to + conjugacy. Some are more easily constructed than others. For example, the + dihedral group $D_6$ is immediately found, but it is not at all obvious how + to realize $S_4$ or $S_5$ *transitively* within $S_6$. + + In some cases there are well-known constructions that can be used. For + example, $S_5$ is isomorphic to $PGL_2(\mathbb{F}_5)$, which acts in a + natural way on the projective line $P^1(\mathbb{F}_5)$, a set of order 6. + + In absence of such special constructions however, we can simply search for + generators. For example, transitive instances of $A_4$ and $S_4$ can be + found within $S_6$ in this way. + + Once we are engaged in such searches, it may then be easier (if less + elegant) to find even those groups like $S_5$ that do have special + constructions, by mere search. + + This function locates generators for transitive instances in $S_6$ of the + following subgroups: + + * $A_4$ + * $S_4^-$ ($S_4$ not contained within $A_6$) + * $S_4^+$ ($S_4$ contained within $A_6$) + * $A_4 \times C_2$ + * $S_4 \times C_2$ + * $G_{18} = C_3^2 \rtimes C_2$ + * $G_{36}^- = C_3^2 \rtimes C_2^2$ + * $G_{36}^+ = C_3^2 \rtimes C_4$ + * $G_{72} = C_3^2 \rtimes D_4$ + * $A_5$ + * $S_5$ + + Note: Each of these groups also has a dedicated function in this module + that returns the group immediately, using generators that were found by + this search procedure. + + The search procedure serves as a record of how these generators were + found. Also, due to randomness in the generation of the elements of + permutation groups, it can be called again, in order to (probably) get + different generators for the same groups. + + Parameters + ========== + + targets : list of :py:class:`~.S6TransitiveSubgroups` values + The groups you want to find. + + print_report : bool (default False) + If True, print to stdout the generators found for each group. + + Returns + ======= + + dict + mapping each name in *targets* to the :py:class:`~.PermutationGroup` + that was found + + References + ========== + + .. [2] https://en.wikipedia.org/wiki/Projective_linear_group#Exceptional_isomorphisms + .. [3] https://en.wikipedia.org/wiki/Automorphisms_of_the_symmetric_and_alternating_groups#PGL%282,5%29 + + """ + def elts_by_order(G): + """Sort the elements of a group by their order. """ + elts = defaultdict(list) + for g in G.elements: + elts[g.order()].append(g) + return elts + + def order_profile(G, name=None): + """Determine how many elements a group has, of each order. """ + elts = elts_by_order(G) + profile = {o:len(e) for o, e in elts.items()} + if name: + print(f'{name}: ' + ' '.join(f'{len(profile[r])}@{r}' for r in sorted(profile.keys()))) + return profile + + S6 = SymmetricGroup(6) + A6 = AlternatingGroup(6) + S6_by_order = elts_by_order(S6) + + def search(existing_gens, needed_gen_orders, order, alt=None, profile=None, anti_profile=None): + """ + Find a transitive subgroup of S6. + + Parameters + ========== + + existing_gens : list of Permutation + Optionally empty list of generators that must be in the group. + + needed_gen_orders : list of positive int + Nonempty list of the orders of the additional generators that are + to be found. + + order: int + The order of the group being sought. + + alt: bool, None + If True, require the group to be contained in A6. + If False, require the group not to be contained in A6. + + profile : dict + If given, the group's order profile must equal this. + + anti_profile : dict + If given, the group's order profile must *not* equal this. + + """ + for gens in itertools.product(*[S6_by_order[n] for n in needed_gen_orders]): + if len(set(gens)) < len(gens): + continue + G = PermutationGroup(existing_gens + list(gens)) + if G.order() == order and G.is_transitive(): + if alt is not None and G.is_subgroup(A6) != alt: + continue + if profile and order_profile(G) != profile: + continue + if anti_profile and order_profile(G) == anti_profile: + continue + return G + + def match_known_group(G, alt=None): + needed = [g.order() for g in G.generators] + return search([], needed, G.order(), alt=alt, profile=order_profile(G)) + + found = {} + + def finish_up(name, G): + found[name] = G + if print_report: + print("=" * 40) + print(f"{name}:") + print(G.generators) + + if S6TransitiveSubgroups.A4 in targets or S6TransitiveSubgroups.A4xC2 in targets: + A4_in_S6 = match_known_group(AlternatingGroup(4)) + finish_up(S6TransitiveSubgroups.A4, A4_in_S6) + + if S6TransitiveSubgroups.S4m in targets or S6TransitiveSubgroups.S4xC2 in targets: + S4m_in_S6 = match_known_group(SymmetricGroup(4), alt=False) + finish_up(S6TransitiveSubgroups.S4m, S4m_in_S6) + + if S6TransitiveSubgroups.S4p in targets: + S4p_in_S6 = match_known_group(SymmetricGroup(4), alt=True) + finish_up(S6TransitiveSubgroups.S4p, S4p_in_S6) + + if S6TransitiveSubgroups.A4xC2 in targets: + A4xC2_in_S6 = search(A4_in_S6.generators, [2], 24, anti_profile=order_profile(SymmetricGroup(4))) + finish_up(S6TransitiveSubgroups.A4xC2, A4xC2_in_S6) + + if S6TransitiveSubgroups.S4xC2 in targets: + S4xC2_in_S6 = search(S4m_in_S6.generators, [2], 48) + finish_up(S6TransitiveSubgroups.S4xC2, S4xC2_in_S6) + + # For the normal factor N = C3^2 in any of the G_n subgroups, we take one + # obvious instance of C3^2 in S6: + N_gens = [Permutation(5)(0, 1, 2), Permutation(5)(3, 4, 5)] + + if S6TransitiveSubgroups.G18 in targets: + G18_in_S6 = search(N_gens, [2], 18) + finish_up(S6TransitiveSubgroups.G18, G18_in_S6) + + if S6TransitiveSubgroups.G36m in targets: + G36m_in_S6 = search(N_gens, [2, 2], 36, alt=False) + finish_up(S6TransitiveSubgroups.G36m, G36m_in_S6) + + if S6TransitiveSubgroups.G36p in targets: + G36p_in_S6 = search(N_gens, [4], 36, alt=True) + finish_up(S6TransitiveSubgroups.G36p, G36p_in_S6) + + if S6TransitiveSubgroups.G72 in targets: + G72_in_S6 = search(N_gens, [4, 2], 72) + finish_up(S6TransitiveSubgroups.G72, G72_in_S6) + + # The PSL2(F5) and PGL2(F5) subgroups are isomorphic to A5 and S5, resp. + + if S6TransitiveSubgroups.PSL2F5 in targets: + PSL2F5_in_S6 = match_known_group(AlternatingGroup(5)) + finish_up(S6TransitiveSubgroups.PSL2F5, PSL2F5_in_S6) + + if S6TransitiveSubgroups.PGL2F5 in targets: + PGL2F5_in_S6 = match_known_group(SymmetricGroup(5)) + finish_up(S6TransitiveSubgroups.PGL2F5, PGL2F5_in_S6) + + # There is little need to "search" for any of the groups C6, S3, D6, A6, + # or S6, since they all have obvious realizations within S6. However, we + # support them here just in case a random representation is desired. + + if S6TransitiveSubgroups.C6 in targets: + C6 = match_known_group(CyclicGroup(6)) + finish_up(S6TransitiveSubgroups.C6, C6) + + if S6TransitiveSubgroups.S3 in targets: + S3 = match_known_group(SymmetricGroup(3)) + finish_up(S6TransitiveSubgroups.S3, S3) + + if S6TransitiveSubgroups.D6 in targets: + D6 = match_known_group(DihedralGroup(6)) + finish_up(S6TransitiveSubgroups.D6, D6) + + if S6TransitiveSubgroups.A6 in targets: + A6 = match_known_group(A6) + finish_up(S6TransitiveSubgroups.A6, A6) + + if S6TransitiveSubgroups.S6 in targets: + S6 = match_known_group(S6) + finish_up(S6TransitiveSubgroups.S6, S6) + + return found diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/generators.py b/phivenv/Lib/site-packages/sympy/combinatorics/generators.py new file mode 100644 index 0000000000000000000000000000000000000000..19e274a4c5b4bf0781855db6bc6bf499349fff31 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/generators.py @@ -0,0 +1,301 @@ +from sympy.combinatorics.permutations import Permutation +from sympy.core.symbol import symbols +from sympy.matrices import Matrix +from sympy.utilities.iterables import variations, rotate_left + + +def symmetric(n): + """ + Generates the symmetric group of order n, Sn. + + Examples + ======== + + >>> from sympy.combinatorics.generators import symmetric + >>> list(symmetric(3)) + [(2), (1 2), (2)(0 1), (0 1 2), (0 2 1), (0 2)] + """ + yield from (Permutation(perm) for perm in variations(range(n), n)) + + +def cyclic(n): + """ + Generates the cyclic group of order n, Cn. + + Examples + ======== + + >>> from sympy.combinatorics.generators import cyclic + >>> list(cyclic(5)) + [(4), (0 1 2 3 4), (0 2 4 1 3), + (0 3 1 4 2), (0 4 3 2 1)] + + See Also + ======== + + dihedral + """ + gen = list(range(n)) + for i in range(n): + yield Permutation(gen) + gen = rotate_left(gen, 1) + + +def alternating(n): + """ + Generates the alternating group of order n, An. + + Examples + ======== + + >>> from sympy.combinatorics.generators import alternating + >>> list(alternating(3)) + [(2), (0 1 2), (0 2 1)] + """ + for perm in variations(range(n), n): + p = Permutation(perm) + if p.is_even: + yield p + + +def dihedral(n): + """ + Generates the dihedral group of order 2n, Dn. + + The result is given as a subgroup of Sn, except for the special cases n=1 + (the group S2) and n=2 (the Klein 4-group) where that's not possible + and embeddings in S2 and S4 respectively are given. + + Examples + ======== + + >>> from sympy.combinatorics.generators import dihedral + >>> list(dihedral(3)) + [(2), (0 2), (0 1 2), (1 2), (0 2 1), (2)(0 1)] + + See Also + ======== + + cyclic + """ + if n == 1: + yield Permutation([0, 1]) + yield Permutation([1, 0]) + elif n == 2: + yield Permutation([0, 1, 2, 3]) + yield Permutation([1, 0, 3, 2]) + yield Permutation([2, 3, 0, 1]) + yield Permutation([3, 2, 1, 0]) + else: + gen = list(range(n)) + for i in range(n): + yield Permutation(gen) + yield Permutation(gen[::-1]) + gen = rotate_left(gen, 1) + + +def rubik_cube_generators(): + """Return the permutations of the 3x3 Rubik's cube, see + https://www.gap-system.org/Doc/Examples/rubik.html + """ + a = [ + [(1, 3, 8, 6), (2, 5, 7, 4), (9, 33, 25, 17), (10, 34, 26, 18), + (11, 35, 27, 19)], + [(9, 11, 16, 14), (10, 13, 15, 12), (1, 17, 41, 40), (4, 20, 44, 37), + (6, 22, 46, 35)], + [(17, 19, 24, 22), (18, 21, 23, 20), (6, 25, 43, 16), (7, 28, 42, 13), + (8, 30, 41, 11)], + [(25, 27, 32, 30), (26, 29, 31, 28), (3, 38, 43, 19), (5, 36, 45, 21), + (8, 33, 48, 24)], + [(33, 35, 40, 38), (34, 37, 39, 36), (3, 9, 46, 32), (2, 12, 47, 29), + (1, 14, 48, 27)], + [(41, 43, 48, 46), (42, 45, 47, 44), (14, 22, 30, 38), + (15, 23, 31, 39), (16, 24, 32, 40)] + ] + return [Permutation([[i - 1 for i in xi] for xi in x], size=48) for x in a] + + +def rubik(n): + """Return permutations for an nxn Rubik's cube. + + Permutations returned are for rotation of each of the slice + from the face up to the last face for each of the 3 sides (in this order): + front, right and bottom. Hence, the first n - 1 permutations are for the + slices from the front. + """ + + if n < 2: + raise ValueError('dimension of cube must be > 1') + + # 1-based reference to rows and columns in Matrix + def getr(f, i): + return faces[f].col(n - i) + + def getl(f, i): + return faces[f].col(i - 1) + + def getu(f, i): + return faces[f].row(i - 1) + + def getd(f, i): + return faces[f].row(n - i) + + def setr(f, i, s): + faces[f][:, n - i] = Matrix(n, 1, s) + + def setl(f, i, s): + faces[f][:, i - 1] = Matrix(n, 1, s) + + def setu(f, i, s): + faces[f][i - 1, :] = Matrix(1, n, s) + + def setd(f, i, s): + faces[f][n - i, :] = Matrix(1, n, s) + + # motion of a single face + def cw(F, r=1): + for _ in range(r): + face = faces[F] + rv = [] + for c in range(n): + for r in range(n - 1, -1, -1): + rv.append(face[r, c]) + faces[F] = Matrix(n, n, rv) + + def ccw(F): + cw(F, 3) + + # motion of plane i from the F side; + # fcw(0) moves the F face, fcw(1) moves the plane + # just behind the front face, etc... + def fcw(i, r=1): + for _ in range(r): + if i == 0: + cw(F) + i += 1 + temp = getr(L, i) + setr(L, i, list(getu(D, i))) + setu(D, i, list(reversed(getl(R, i)))) + setl(R, i, list(getd(U, i))) + setd(U, i, list(reversed(temp))) + i -= 1 + + def fccw(i): + fcw(i, 3) + + # motion of the entire cube from the F side + def FCW(r=1): + for _ in range(r): + cw(F) + ccw(B) + cw(U) + t = faces[U] + cw(L) + faces[U] = faces[L] + cw(D) + faces[L] = faces[D] + cw(R) + faces[D] = faces[R] + faces[R] = t + + def FCCW(): + FCW(3) + + # motion of the entire cube from the U side + def UCW(r=1): + for _ in range(r): + cw(U) + ccw(D) + t = faces[F] + faces[F] = faces[R] + faces[R] = faces[B] + faces[B] = faces[L] + faces[L] = t + + def UCCW(): + UCW(3) + + # defining the permutations for the cube + + U, F, R, B, L, D = names = symbols('U, F, R, B, L, D') + + # the faces are represented by nxn matrices + faces = {} + count = 0 + for fi in range(6): + f = [] + for a in range(n**2): + f.append(count) + count += 1 + faces[names[fi]] = Matrix(n, n, f) + + # this will either return the value of the current permutation + # (show != 1) or else append the permutation to the group, g + def perm(show=0): + # add perm to the list of perms + p = [] + for f in names: + p.extend(faces[f]) + if show: + return p + g.append(Permutation(p)) + + g = [] # container for the group's permutations + I = list(range(6*n**2)) # the identity permutation used for checking + + # define permutations corresponding to cw rotations of the planes + # up TO the last plane from that direction; by not including the + # last plane, the orientation of the cube is maintained. + + # F slices + for i in range(n - 1): + fcw(i) + perm() + fccw(i) # restore + assert perm(1) == I + + # R slices + # bring R to front + UCW() + for i in range(n - 1): + fcw(i) + # put it back in place + UCCW() + # record + perm() + # restore + # bring face to front + UCW() + fccw(i) + # restore + UCCW() + assert perm(1) == I + + # D slices + # bring up bottom + FCW() + UCCW() + FCCW() + for i in range(n - 1): + # turn strip + fcw(i) + # put bottom back on the bottom + FCW() + UCW() + FCCW() + # record + perm() + # restore + # bring up bottom + FCW() + UCCW() + FCCW() + # turn strip + fccw(i) + # put bottom back on the bottom + FCW() + UCW() + FCCW() + assert perm(1) == I + + return g diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/graycode.py b/phivenv/Lib/site-packages/sympy/combinatorics/graycode.py new file mode 100644 index 0000000000000000000000000000000000000000..930fd337862a70e920a985947d74375b27741293 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/graycode.py @@ -0,0 +1,430 @@ +from sympy.core import Basic, Integer + +import random + + +class GrayCode(Basic): + """ + A Gray code is essentially a Hamiltonian walk on + a n-dimensional cube with edge length of one. + The vertices of the cube are represented by vectors + whose values are binary. The Hamilton walk visits + each vertex exactly once. The Gray code for a 3d + cube is ['000','100','110','010','011','111','101', + '001']. + + A Gray code solves the problem of sequentially + generating all possible subsets of n objects in such + a way that each subset is obtained from the previous + one by either deleting or adding a single object. + In the above example, 1 indicates that the object is + present, and 0 indicates that its absent. + + Gray codes have applications in statistics as well when + we want to compute various statistics related to subsets + in an efficient manner. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3) + >>> list(a.generate_gray()) + ['000', '001', '011', '010', '110', '111', '101', '100'] + >>> a = GrayCode(4) + >>> list(a.generate_gray()) + ['0000', '0001', '0011', '0010', '0110', '0111', '0101', '0100', \ + '1100', '1101', '1111', '1110', '1010', '1011', '1001', '1000'] + + References + ========== + + .. [1] Nijenhuis,A. and Wilf,H.S.(1978). + Combinatorial Algorithms. Academic Press. + .. [2] Knuth, D. (2011). The Art of Computer Programming, Vol 4 + Addison Wesley + + + """ + + _skip = False + _current = 0 + _rank = None + + def __new__(cls, n, *args, **kw_args): + """ + Default constructor. + + It takes a single argument ``n`` which gives the dimension of the Gray + code. The starting Gray code string (``start``) or the starting ``rank`` + may also be given; the default is to start at rank = 0 ('0...0'). + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3) + >>> a + GrayCode(3) + >>> a.n + 3 + + >>> a = GrayCode(3, start='100') + >>> a.current + '100' + + >>> a = GrayCode(4, rank=4) + >>> a.current + '0110' + >>> a.rank + 4 + + """ + if n < 1 or int(n) != n: + raise ValueError( + 'Gray code dimension must be a positive integer, not %i' % n) + n = Integer(n) + args = (n,) + args + obj = Basic.__new__(cls, *args) + if 'start' in kw_args: + obj._current = kw_args["start"] + if len(obj._current) > n: + raise ValueError('Gray code start has length %i but ' + 'should not be greater than %i' % (len(obj._current), n)) + elif 'rank' in kw_args: + if int(kw_args["rank"]) != kw_args["rank"]: + raise ValueError('Gray code rank must be a positive integer, ' + 'not %i' % kw_args["rank"]) + obj._rank = int(kw_args["rank"]) % obj.selections + obj._current = obj.unrank(n, obj._rank) + return obj + + def next(self, delta=1): + """ + Returns the Gray code a distance ``delta`` (default = 1) from the + current value in canonical order. + + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3, start='110') + >>> a.next().current + '111' + >>> a.next(-1).current + '010' + """ + return GrayCode(self.n, rank=(self.rank + delta) % self.selections) + + @property + def selections(self): + """ + Returns the number of bit vectors in the Gray code. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3) + >>> a.selections + 8 + """ + return 2**self.n + + @property + def n(self): + """ + Returns the dimension of the Gray code. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(5) + >>> a.n + 5 + """ + return self.args[0] + + def generate_gray(self, **hints): + """ + Generates the sequence of bit vectors of a Gray Code. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3) + >>> list(a.generate_gray()) + ['000', '001', '011', '010', '110', '111', '101', '100'] + >>> list(a.generate_gray(start='011')) + ['011', '010', '110', '111', '101', '100'] + >>> list(a.generate_gray(rank=4)) + ['110', '111', '101', '100'] + + See Also + ======== + + skip + + References + ========== + + .. [1] Knuth, D. (2011). The Art of Computer Programming, + Vol 4, Addison Wesley + + """ + bits = self.n + start = None + if "start" in hints: + start = hints["start"] + elif "rank" in hints: + start = GrayCode.unrank(self.n, hints["rank"]) + if start is not None: + self._current = start + current = self.current + graycode_bin = gray_to_bin(current) + if len(graycode_bin) > self.n: + raise ValueError('Gray code start has length %i but should ' + 'not be greater than %i' % (len(graycode_bin), bits)) + self._current = int(current, 2) + graycode_int = int(''.join(graycode_bin), 2) + for i in range(graycode_int, 1 << bits): + if self._skip: + self._skip = False + else: + yield self.current + bbtc = (i ^ (i + 1)) + gbtc = (bbtc ^ (bbtc >> 1)) + self._current = (self._current ^ gbtc) + self._current = 0 + + def skip(self): + """ + Skips the bit generation. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3) + >>> for i in a.generate_gray(): + ... if i == '010': + ... a.skip() + ... print(i) + ... + 000 + 001 + 011 + 010 + 111 + 101 + 100 + + See Also + ======== + + generate_gray + """ + self._skip = True + + @property + def rank(self): + """ + Ranks the Gray code. + + A ranking algorithm determines the position (or rank) + of a combinatorial object among all the objects w.r.t. + a given order. For example, the 4 bit binary reflected + Gray code (BRGC) '0101' has a rank of 6 as it appears in + the 6th position in the canonical ordering of the family + of 4 bit Gray codes. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> a = GrayCode(3) + >>> list(a.generate_gray()) + ['000', '001', '011', '010', '110', '111', '101', '100'] + >>> GrayCode(3, start='100').rank + 7 + >>> GrayCode(3, rank=7).current + '100' + + See Also + ======== + + unrank + + References + ========== + + .. [1] https://web.archive.org/web/20200224064753/http://statweb.stanford.edu/~susan/courses/s208/node12.html + + """ + if self._rank is None: + self._rank = int(gray_to_bin(self.current), 2) + return self._rank + + @property + def current(self): + """ + Returns the currently referenced Gray code as a bit string. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> GrayCode(3, start='100').current + '100' + """ + rv = self._current or '0' + if not isinstance(rv, str): + rv = bin(rv)[2:] + return rv.rjust(self.n, '0') + + @classmethod + def unrank(self, n, rank): + """ + Unranks an n-bit sized Gray code of rank k. This method exists + so that a derivative GrayCode class can define its own code of + a given rank. + + The string here is generated in reverse order to allow for tail-call + optimization. + + Examples + ======== + + >>> from sympy.combinatorics import GrayCode + >>> GrayCode(5, rank=3).current + '00010' + >>> GrayCode.unrank(5, 3) + '00010' + + See Also + ======== + + rank + """ + def _unrank(k, n): + if n == 1: + return str(k % 2) + m = 2**(n - 1) + if k < m: + return '0' + _unrank(k, n - 1) + return '1' + _unrank(m - (k % m) - 1, n - 1) + return _unrank(rank, n) + + +def random_bitstring(n): + """ + Generates a random bitlist of length n. + + Examples + ======== + + >>> from sympy.combinatorics.graycode import random_bitstring + >>> random_bitstring(3) # doctest: +SKIP + 100 + """ + return ''.join([random.choice('01') for i in range(n)]) + + +def gray_to_bin(bin_list): + """ + Convert from Gray coding to binary coding. + + We assume big endian encoding. + + Examples + ======== + + >>> from sympy.combinatorics.graycode import gray_to_bin + >>> gray_to_bin('100') + '111' + + See Also + ======== + + bin_to_gray + """ + b = [bin_list[0]] + for i in range(1, len(bin_list)): + b += str(int(b[i - 1] != bin_list[i])) + return ''.join(b) + + +def bin_to_gray(bin_list): + """ + Convert from binary coding to gray coding. + + We assume big endian encoding. + + Examples + ======== + + >>> from sympy.combinatorics.graycode import bin_to_gray + >>> bin_to_gray('111') + '100' + + See Also + ======== + + gray_to_bin + """ + b = [bin_list[0]] + for i in range(1, len(bin_list)): + b += str(int(bin_list[i]) ^ int(bin_list[i - 1])) + return ''.join(b) + + +def get_subset_from_bitstring(super_set, bitstring): + """ + Gets the subset defined by the bitstring. + + Examples + ======== + + >>> from sympy.combinatorics.graycode import get_subset_from_bitstring + >>> get_subset_from_bitstring(['a', 'b', 'c', 'd'], '0011') + ['c', 'd'] + >>> get_subset_from_bitstring(['c', 'a', 'c', 'c'], '1100') + ['c', 'a'] + + See Also + ======== + + graycode_subsets + """ + if len(super_set) != len(bitstring): + raise ValueError("The sizes of the lists are not equal") + return [super_set[i] for i, j in enumerate(bitstring) + if bitstring[i] == '1'] + + +def graycode_subsets(gray_code_set): + """ + Generates the subsets as enumerated by a Gray code. + + Examples + ======== + + >>> from sympy.combinatorics.graycode import graycode_subsets + >>> list(graycode_subsets(['a', 'b', 'c'])) + [[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], \ + ['a', 'c'], ['a']] + >>> list(graycode_subsets(['a', 'b', 'c', 'c'])) + [[], ['c'], ['c', 'c'], ['c'], ['b', 'c'], ['b', 'c', 'c'], \ + ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'c'], \ + ['a', 'b', 'c'], ['a', 'c'], ['a', 'c', 'c'], ['a', 'c'], ['a']] + + See Also + ======== + + get_subset_from_bitstring + """ + for bitstring in list(GrayCode(len(gray_code_set)).generate_gray()): + yield get_subset_from_bitstring(gray_code_set, bitstring) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/group_constructs.py b/phivenv/Lib/site-packages/sympy/combinatorics/group_constructs.py new file mode 100644 index 0000000000000000000000000000000000000000..a5c16ec254191646b26eee869763e2926e187da5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/group_constructs.py @@ -0,0 +1,61 @@ +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.combinatorics.permutations import Permutation +from sympy.utilities.iterables import uniq + +_af_new = Permutation._af_new + + +def DirectProduct(*groups): + """ + Returns the direct product of several groups as a permutation group. + + Explanation + =========== + + This is implemented much like the __mul__ procedure for taking the direct + product of two permutation groups, but the idea of shifting the + generators is realized in the case of an arbitrary number of groups. + A call to DirectProduct(G1, G2, ..., Gn) is generally expected to be faster + than a call to G1*G2*...*Gn (and thus the need for this algorithm). + + Examples + ======== + + >>> from sympy.combinatorics.group_constructs import DirectProduct + >>> from sympy.combinatorics.named_groups import CyclicGroup + >>> C = CyclicGroup(4) + >>> G = DirectProduct(C, C, C) + >>> G.order() + 64 + + See Also + ======== + + sympy.combinatorics.perm_groups.PermutationGroup.__mul__ + + """ + degrees = [] + gens_count = [] + total_degree = 0 + total_gens = 0 + for group in groups: + current_deg = group.degree + current_num_gens = len(group.generators) + degrees.append(current_deg) + total_degree += current_deg + gens_count.append(current_num_gens) + total_gens += current_num_gens + array_gens = [] + for i in range(total_gens): + array_gens.append(list(range(total_degree))) + current_gen = 0 + current_deg = 0 + for i in range(len(gens_count)): + for j in range(current_gen, current_gen + gens_count[i]): + gen = ((groups[i].generators)[j - current_gen]).array_form + array_gens[j][current_deg:current_deg + degrees[i]] = \ + [x + current_deg for x in gen] + current_gen += gens_count[i] + current_deg += degrees[i] + perm_gens = list(uniq([_af_new(list(a)) for a in array_gens])) + return PermutationGroup(perm_gens, dups=False) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/group_numbers.py b/phivenv/Lib/site-packages/sympy/combinatorics/group_numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..8099dfc2ed8a6943aa1261f9374ed84f0ad3c522 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/group_numbers.py @@ -0,0 +1,294 @@ +from itertools import chain, combinations + +from sympy.external.gmpy import gcd +from sympy.ntheory.factor_ import factorint +from sympy.utilities.misc import as_int + + +def _is_nilpotent_number(factors: dict) -> bool: + """ Check whether `n` is a nilpotent number. + Note that ``factors`` is a prime factorization of `n`. + + This is a low-level helper for ``is_nilpotent_number``, for internal use. + """ + for p in factors.keys(): + for q, e in factors.items(): + # We want to calculate + # any(pow(q, k, p) == 1 for k in range(1, e + 1)) + m = 1 + for _ in range(e): + m = m*q % p + if m == 1: + return False + return True + + +def is_nilpotent_number(n) -> bool: + """ + Check whether `n` is a nilpotent number. A number `n` is said to be + nilpotent if and only if every finite group of order `n` is nilpotent. + For more information see [1]_. + + Examples + ======== + + >>> from sympy.combinatorics.group_numbers import is_nilpotent_number + >>> from sympy import randprime + >>> is_nilpotent_number(21) + False + >>> is_nilpotent_number(randprime(1, 30)**12) + True + + References + ========== + + .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers, + The American Mathematical Monthly, 107(7), 631-634. + .. [2] https://oeis.org/A056867 + + """ + n = as_int(n) + if n <= 0: + raise ValueError("n must be a positive integer, not %i" % n) + return _is_nilpotent_number(factorint(n)) + + +def is_abelian_number(n) -> bool: + """ + Check whether `n` is an abelian number. A number `n` is said to be abelian + if and only if every finite group of order `n` is abelian. For more + information see [1]_. + + Examples + ======== + + >>> from sympy.combinatorics.group_numbers import is_abelian_number + >>> from sympy import randprime + >>> is_abelian_number(4) + True + >>> is_abelian_number(randprime(1, 2000)**2) + True + >>> is_abelian_number(60) + False + + References + ========== + + .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers, + The American Mathematical Monthly, 107(7), 631-634. + .. [2] https://oeis.org/A051532 + + """ + n = as_int(n) + if n <= 0: + raise ValueError("n must be a positive integer, not %i" % n) + factors = factorint(n) + return all(e < 3 for e in factors.values()) and _is_nilpotent_number(factors) + + +def is_cyclic_number(n) -> bool: + """ + Check whether `n` is a cyclic number. A number `n` is said to be cyclic + if and only if every finite group of order `n` is cyclic. For more + information see [1]_. + + Examples + ======== + + >>> from sympy.combinatorics.group_numbers import is_cyclic_number + >>> from sympy import randprime + >>> is_cyclic_number(15) + True + >>> is_cyclic_number(randprime(1, 2000)**2) + False + >>> is_cyclic_number(4) + False + + References + ========== + + .. [1] Pakianathan, J., Shankar, K., Nilpotent Numbers, + The American Mathematical Monthly, 107(7), 631-634. + .. [2] https://oeis.org/A003277 + + """ + n = as_int(n) + if n <= 0: + raise ValueError("n must be a positive integer, not %i" % n) + factors = factorint(n) + return all(e == 1 for e in factors.values()) and _is_nilpotent_number(factors) + + +def _holder_formula(prime_factors): + r""" Number of groups of order `n`. + where `n` is squarefree and its prime factors are ``prime_factors``. + i.e., ``n == math.prod(prime_factors)`` + + Explanation + =========== + + When `n` is squarefree, the number of groups of order `n` is expressed by + + .. math :: + \sum_{d \mid n} \prod_p \frac{p^{c(p, d)} - 1}{p - 1} + + where `n=de`, `p` is the prime factor of `e`, + and `c(p, d)` is the number of prime factors `q` of `d` such that `q \equiv 1 \pmod{p}` [2]_. + + The formula is elegant, but can be improved when implemented as an algorithm. + Since `n` is assumed to be squarefree, the divisor `d` of `n` can be identified with the power set of prime factors. + We let `N` be the set of prime factors of `n`. + `F = \{p \in N : \forall q \in N, q \not\equiv 1 \pmod{p} \}, M = N \setminus F`, we have the following. + + .. math :: + \sum_{d \in 2^{M}} \prod_{p \in M \setminus d} \frac{p^{c(p, F \cup d)} - 1}{p - 1} + + Practically, many prime factors are expected to be members of `F`, thus reducing computation time. + + Parameters + ========== + + prime_factors : set + The set of prime factors of ``n``. where `n` is squarefree. + + Returns + ======= + + int : Number of groups of order ``n`` + + Examples + ======== + + >>> from sympy.combinatorics.group_numbers import _holder_formula + >>> _holder_formula({2}) # n = 2 + 1 + >>> _holder_formula({2, 3}) # n = 2*3 = 6 + 2 + + See Also + ======== + + groups_count + + References + ========== + + .. [1] Otto Holder, Die Gruppen der Ordnungen p^3, pq^2, pqr, p^4, + Math. Ann. 43 pp. 301-412 (1893). + http://dx.doi.org/10.1007/BF01443651 + .. [2] John H. Conway, Heiko Dietrich and E.A. O'Brien, + Counting groups: gnus, moas and other exotica + The Mathematical Intelligencer 30, 6-15 (2008) + https://doi.org/10.1007/BF02985731 + + """ + F = {p for p in prime_factors if all(q % p != 1 for q in prime_factors)} + M = prime_factors - F + + s = 0 + powerset = chain.from_iterable(combinations(M, r) for r in range(len(M)+1)) + for ps in powerset: + ps = set(ps) + prod = 1 + for p in M - ps: + c = len([q for q in F | ps if q % p == 1]) + prod *= (p**c - 1) // (p - 1) + if not prod: + break + s += prod + return s + + +def groups_count(n): + r""" Number of groups of order `n`. + In [1]_, ``gnu(n)`` is given, so we follow this notation here as well. + + Parameters + ========== + + n : Integer + ``n`` is a positive integer + + Returns + ======= + + int : ``gnu(n)`` + + Raises + ====== + + ValueError + Number of groups of order ``n`` is unknown or not implemented. + For example, gnu(`2^{11}`) is not yet known. + On the other hand, gnu(99) is known to be 2, + but this has not yet been implemented in this function. + + Examples + ======== + + >>> from sympy.combinatorics.group_numbers import groups_count + >>> groups_count(3) # There is only one cyclic group of order 3 + 1 + >>> # There are two groups of order 10: the cyclic group and the dihedral group + >>> groups_count(10) + 2 + + See Also + ======== + + is_cyclic_number + `n` is cyclic iff gnu(n) = 1 + + References + ========== + + .. [1] John H. Conway, Heiko Dietrich and E.A. O'Brien, + Counting groups: gnus, moas and other exotica + The Mathematical Intelligencer 30, 6-15 (2008) + https://doi.org/10.1007/BF02985731 + .. [2] https://oeis.org/A000001 + + """ + n = as_int(n) + if n <= 0: + raise ValueError("n must be a positive integer, not %i" % n) + factors = factorint(n) + if len(factors) == 1: + (p, e) = list(factors.items())[0] + if p == 2: + A000679 = [1, 1, 2, 5, 14, 51, 267, 2328, 56092, 10494213, 49487367289] + if e < len(A000679): + return A000679[e] + if p == 3: + A090091 = [1, 1, 2, 5, 15, 67, 504, 9310, 1396077, 5937876645] + if e < len(A090091): + return A090091[e] + if e <= 2: # gnu(p) = 1, gnu(p**2) = 2 + return e + if e == 3: # gnu(p**3) = 5 + return 5 + if e == 4: # if p is an odd prime, gnu(p**4) = 15 + return 15 + if e == 5: # if p >= 5, gnu(p**5) is expressed by the following equation + return 61 + 2*p + 2*gcd(p-1, 3) + gcd(p-1, 4) + if e == 6: # if p >= 6, gnu(p**6) is expressed by the following equation + return 3*p**2 + 39*p + 344 +\ + 24*gcd(p-1, 3) + 11*gcd(p-1, 4) + 2*gcd(p-1, 5) + if e == 7: # if p >= 7, gnu(p**7) is expressed by the following equation + if p == 5: + return 34297 + return 3*p**5 + 12*p**4 + 44*p**3 + 170*p**2 + 707*p + 2455 +\ + (4*p**2 + 44*p + 291)*gcd(p-1, 3) + (p**2 + 19*p + 135)*gcd(p-1, 4) + \ + (3*p + 31)*gcd(p-1, 5) + 4*gcd(p-1, 7) + 5*gcd(p-1, 8) + gcd(p-1, 9) + if any(e > 1 for e in factors.values()): # n is not squarefree + # some known values for small n that have more than 1 factor and are not square free (https://oeis.org/A000001) + small = {12: 5, 18: 5, 20: 5, 24: 15, 28: 4, 36: 14, 40: 14, 44: 4, 45: 2, 48: 52, + 50: 5, 52: 5, 54: 15, 56: 13, 60: 13, 63: 4, 68: 5, 72: 50, 75: 3, 76: 4, + 80: 52, 84: 15, 88: 12, 90: 10, 92: 4} + if n in small: + return small[n] + raise ValueError("Number of groups of order n is unknown or not implemented") + if len(factors) == 2: # n is squarefree semiprime + p, q = sorted(factors.keys()) + return 2 if q % p == 1 else 1 + return _holder_formula(set(factors.keys())) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/homomorphisms.py b/phivenv/Lib/site-packages/sympy/combinatorics/homomorphisms.py new file mode 100644 index 0000000000000000000000000000000000000000..256f56b3aa7c5404d332f42e37d4f1117ea81db7 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/homomorphisms.py @@ -0,0 +1,549 @@ +import itertools +from sympy.combinatorics.fp_groups import FpGroup, FpSubgroup, simplify_presentation +from sympy.combinatorics.free_groups import FreeGroup +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.core.intfunc import igcd +from sympy.functions.combinatorial.numbers import totient +from sympy.core.singleton import S + +class GroupHomomorphism: + ''' + A class representing group homomorphisms. Instantiate using `homomorphism()`. + + References + ========== + + .. [1] Holt, D., Eick, B. and O'Brien, E. (2005). Handbook of computational group theory. + + ''' + + def __init__(self, domain, codomain, images): + self.domain = domain + self.codomain = codomain + self.images = images + self._inverses = None + self._kernel = None + self._image = None + + def _invs(self): + ''' + Return a dictionary with `{gen: inverse}` where `gen` is a rewriting + generator of `codomain` (e.g. strong generator for permutation groups) + and `inverse` is an element of its preimage + + ''' + image = self.image() + inverses = {} + for k in list(self.images.keys()): + v = self.images[k] + if not (v in inverses + or v.is_identity): + inverses[v] = k + if isinstance(self.codomain, PermutationGroup): + gens = image.strong_gens + else: + gens = image.generators + for g in gens: + if g in inverses or g.is_identity: + continue + w = self.domain.identity + if isinstance(self.codomain, PermutationGroup): + parts = image._strong_gens_slp[g][::-1] + else: + parts = g + for s in parts: + if s in inverses: + w = w*inverses[s] + else: + w = w*inverses[s**-1]**-1 + inverses[g] = w + + return inverses + + def invert(self, g): + ''' + Return an element of the preimage of ``g`` or of each element + of ``g`` if ``g`` is a list. + + Explanation + =========== + + If the codomain is an FpGroup, the inverse for equal + elements might not always be the same unless the FpGroup's + rewriting system is confluent. However, making a system + confluent can be time-consuming. If it's important, try + `self.codomain.make_confluent()` first. + + ''' + from sympy.combinatorics import Permutation + from sympy.combinatorics.free_groups import FreeGroupElement + if isinstance(g, (Permutation, FreeGroupElement)): + if isinstance(self.codomain, FpGroup): + g = self.codomain.reduce(g) + if self._inverses is None: + self._inverses = self._invs() + image = self.image() + w = self.domain.identity + if isinstance(self.codomain, PermutationGroup): + gens = image.generator_product(g)[::-1] + else: + gens = g + # the following can't be "for s in gens:" + # because that would be equivalent to + # "for s in gens.array_form:" when g is + # a FreeGroupElement. On the other hand, + # when you call gens by index, the generator + # (or inverse) at position i is returned. + for i in range(len(gens)): + s = gens[i] + if s.is_identity: + continue + if s in self._inverses: + w = w*self._inverses[s] + else: + w = w*self._inverses[s**-1]**-1 + return w + elif isinstance(g, list): + return [self.invert(e) for e in g] + + def kernel(self): + ''' + Compute the kernel of `self`. + + ''' + if self._kernel is None: + self._kernel = self._compute_kernel() + return self._kernel + + def _compute_kernel(self): + G = self.domain + G_order = G.order() + if G_order is S.Infinity: + raise NotImplementedError( + "Kernel computation is not implemented for infinite groups") + gens = [] + if isinstance(G, PermutationGroup): + K = PermutationGroup(G.identity) + else: + K = FpSubgroup(G, gens, normal=True) + i = self.image().order() + while K.order()*i != G_order: + r = G.random() + k = r*self.invert(self(r))**-1 + if k not in K: + gens.append(k) + if isinstance(G, PermutationGroup): + K = PermutationGroup(gens) + else: + K = FpSubgroup(G, gens, normal=True) + return K + + def image(self): + ''' + Compute the image of `self`. + + ''' + if self._image is None: + values = list(set(self.images.values())) + if isinstance(self.codomain, PermutationGroup): + self._image = self.codomain.subgroup(values) + else: + self._image = FpSubgroup(self.codomain, values) + return self._image + + def _apply(self, elem): + ''' + Apply `self` to `elem`. + + ''' + if elem not in self.domain: + if isinstance(elem, (list, tuple)): + return [self._apply(e) for e in elem] + raise ValueError("The supplied element does not belong to the domain") + if elem.is_identity: + return self.codomain.identity + else: + images = self.images + value = self.codomain.identity + if isinstance(self.domain, PermutationGroup): + gens = self.domain.generator_product(elem, original=True) + for g in gens: + if g in self.images: + value = images[g]*value + else: + value = images[g**-1]**-1*value + else: + i = 0 + for _, p in elem.array_form: + if p < 0: + g = elem[i]**-1 + else: + g = elem[i] + value = value*images[g]**p + i += abs(p) + return value + + def __call__(self, elem): + return self._apply(elem) + + def is_injective(self): + ''' + Check if the homomorphism is injective + + ''' + return self.kernel().order() == 1 + + def is_surjective(self): + ''' + Check if the homomorphism is surjective + + ''' + im = self.image().order() + oth = self.codomain.order() + if im is S.Infinity and oth is S.Infinity: + return None + else: + return im == oth + + def is_isomorphism(self): + ''' + Check if `self` is an isomorphism. + + ''' + return self.is_injective() and self.is_surjective() + + def is_trivial(self): + ''' + Check is `self` is a trivial homomorphism, i.e. all elements + are mapped to the identity. + + ''' + return self.image().order() == 1 + + def compose(self, other): + ''' + Return the composition of `self` and `other`, i.e. + the homomorphism phi such that for all g in the domain + of `other`, phi(g) = self(other(g)) + + ''' + if not other.image().is_subgroup(self.domain): + raise ValueError("The image of `other` must be a subgroup of " + "the domain of `self`") + images = {g: self(other(g)) for g in other.images} + return GroupHomomorphism(other.domain, self.codomain, images) + + def restrict_to(self, H): + ''' + Return the restriction of the homomorphism to the subgroup `H` + of the domain. + + ''' + if not isinstance(H, PermutationGroup) or not H.is_subgroup(self.domain): + raise ValueError("Given H is not a subgroup of the domain") + domain = H + images = {g: self(g) for g in H.generators} + return GroupHomomorphism(domain, self.codomain, images) + + def invert_subgroup(self, H): + ''' + Return the subgroup of the domain that is the inverse image + of the subgroup ``H`` of the homomorphism image + + ''' + if not H.is_subgroup(self.image()): + raise ValueError("Given H is not a subgroup of the image") + gens = [] + P = PermutationGroup(self.image().identity) + for h in H.generators: + h_i = self.invert(h) + if h_i not in P: + gens.append(h_i) + P = PermutationGroup(gens) + for k in self.kernel().generators: + if k*h_i not in P: + gens.append(k*h_i) + P = PermutationGroup(gens) + return P + +def homomorphism(domain, codomain, gens, images=(), check=True): + ''' + Create (if possible) a group homomorphism from the group ``domain`` + to the group ``codomain`` defined by the images of the domain's + generators ``gens``. ``gens`` and ``images`` can be either lists or tuples + of equal sizes. If ``gens`` is a proper subset of the group's generators, + the unspecified generators will be mapped to the identity. If the + images are not specified, a trivial homomorphism will be created. + + If the given images of the generators do not define a homomorphism, + an exception is raised. + + If ``check`` is ``False``, do not check whether the given images actually + define a homomorphism. + + ''' + if not isinstance(domain, (PermutationGroup, FpGroup, FreeGroup)): + raise TypeError("The domain must be a group") + if not isinstance(codomain, (PermutationGroup, FpGroup, FreeGroup)): + raise TypeError("The codomain must be a group") + + generators = domain.generators + if not all(g in generators for g in gens): + raise ValueError("The supplied generators must be a subset of the domain's generators") + if not all(g in codomain for g in images): + raise ValueError("The images must be elements of the codomain") + + if images and len(images) != len(gens): + raise ValueError("The number of images must be equal to the number of generators") + + gens = list(gens) + images = list(images) + + images.extend([codomain.identity]*(len(generators)-len(images))) + gens.extend([g for g in generators if g not in gens]) + images = dict(zip(gens,images)) + + if check and not _check_homomorphism(domain, codomain, images): + raise ValueError("The given images do not define a homomorphism") + return GroupHomomorphism(domain, codomain, images) + +def _check_homomorphism(domain, codomain, images): + """ + Check that a given mapping of generators to images defines a homomorphism. + + Parameters + ========== + domain : PermutationGroup, FpGroup, FreeGroup + codomain : PermutationGroup, FpGroup, FreeGroup + images : dict + The set of keys must be equal to domain.generators. + The values must be elements of the codomain. + + """ + pres = domain if hasattr(domain, 'relators') else domain.presentation() + rels = pres.relators + gens = pres.generators + symbols = [g.ext_rep[0] for g in gens] + symbols_to_domain_generators = dict(zip(symbols, domain.generators)) + identity = codomain.identity + + def _image(r): + w = identity + for symbol, power in r.array_form: + g = symbols_to_domain_generators[symbol] + w *= images[g]**power + return w + + for r in rels: + if isinstance(codomain, FpGroup): + s = codomain.equals(_image(r), identity) + if s is None: + # only try to make the rewriting system + # confluent when it can't determine the + # truth of equality otherwise + success = codomain.make_confluent() + s = codomain.equals(_image(r), identity) + if s is None and not success: + raise RuntimeError("Can't determine if the images " + "define a homomorphism. Try increasing " + "the maximum number of rewriting rules " + "(group._rewriting_system.set_max(new_value); " + "the current value is stored in group._rewriting" + "_system.maxeqns)") + else: + s = _image(r).is_identity + if not s: + return False + return True + +def orbit_homomorphism(group, omega): + ''' + Return the homomorphism induced by the action of the permutation + group ``group`` on the set ``omega`` that is closed under the action. + + ''' + from sympy.combinatorics import Permutation + from sympy.combinatorics.named_groups import SymmetricGroup + codomain = SymmetricGroup(len(omega)) + identity = codomain.identity + omega = list(omega) + images = {g: identity*Permutation([omega.index(o^g) for o in omega]) for g in group.generators} + group._schreier_sims(base=omega) + H = GroupHomomorphism(group, codomain, images) + if len(group.basic_stabilizers) > len(omega): + H._kernel = group.basic_stabilizers[len(omega)] + else: + H._kernel = PermutationGroup([group.identity]) + return H + +def block_homomorphism(group, blocks): + ''' + Return the homomorphism induced by the action of the permutation + group ``group`` on the block system ``blocks``. The latter should be + of the same form as returned by the ``minimal_block`` method for + permutation groups, namely a list of length ``group.degree`` where + the i-th entry is a representative of the block i belongs to. + + ''' + from sympy.combinatorics import Permutation + from sympy.combinatorics.named_groups import SymmetricGroup + + n = len(blocks) + + # number the blocks; m is the total number, + # b is such that b[i] is the number of the block i belongs to, + # p is the list of length m such that p[i] is the representative + # of the i-th block + m = 0 + p = [] + b = [None]*n + for i in range(n): + if blocks[i] == i: + p.append(i) + b[i] = m + m += 1 + for i in range(n): + b[i] = b[blocks[i]] + + codomain = SymmetricGroup(m) + # the list corresponding to the identity permutation in codomain + identity = range(m) + images = {g: Permutation([b[p[i]^g] for i in identity]) for g in group.generators} + H = GroupHomomorphism(group, codomain, images) + return H + +def group_isomorphism(G, H, isomorphism=True): + ''' + Compute an isomorphism between 2 given groups. + + Parameters + ========== + + G : A finite ``FpGroup`` or a ``PermutationGroup``. + First group. + + H : A finite ``FpGroup`` or a ``PermutationGroup`` + Second group. + + isomorphism : bool + This is used to avoid the computation of homomorphism + when the user only wants to check if there exists + an isomorphism between the groups. + + Returns + ======= + + If isomorphism = False -- Returns a boolean. + If isomorphism = True -- Returns a boolean and an isomorphism between `G` and `H`. + + Examples + ======== + + >>> from sympy.combinatorics import free_group, Permutation + >>> from sympy.combinatorics.perm_groups import PermutationGroup + >>> from sympy.combinatorics.fp_groups import FpGroup + >>> from sympy.combinatorics.homomorphisms import group_isomorphism + >>> from sympy.combinatorics.named_groups import DihedralGroup, AlternatingGroup + + >>> D = DihedralGroup(8) + >>> p = Permutation(0, 1, 2, 3, 4, 5, 6, 7) + >>> P = PermutationGroup(p) + >>> group_isomorphism(D, P) + (False, None) + + >>> F, a, b = free_group("a, b") + >>> G = FpGroup(F, [a**3, b**3, (a*b)**2]) + >>> H = AlternatingGroup(4) + >>> (check, T) = group_isomorphism(G, H) + >>> check + True + >>> T(b*a*b**-1*a**-1*b**-1) + (0 2 3) + + Notes + ===== + + Uses the approach suggested by Robert Tarjan to compute the isomorphism between two groups. + First, the generators of ``G`` are mapped to the elements of ``H`` and + we check if the mapping induces an isomorphism. + + ''' + if not isinstance(G, (PermutationGroup, FpGroup)): + raise TypeError("The group must be a PermutationGroup or an FpGroup") + if not isinstance(H, (PermutationGroup, FpGroup)): + raise TypeError("The group must be a PermutationGroup or an FpGroup") + + if isinstance(G, FpGroup) and isinstance(H, FpGroup): + G = simplify_presentation(G) + H = simplify_presentation(H) + # Two infinite FpGroups with the same generators are isomorphic + # when the relators are same but are ordered differently. + if G.generators == H.generators and (G.relators).sort() == (H.relators).sort(): + if not isomorphism: + return True + return (True, homomorphism(G, H, G.generators, H.generators)) + + # `_H` is the permutation group isomorphic to `H`. + _H = H + g_order = G.order() + h_order = H.order() + + if g_order is S.Infinity: + raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.") + + if isinstance(H, FpGroup): + if h_order is S.Infinity: + raise NotImplementedError("Isomorphism methods are not implemented for infinite groups.") + _H, h_isomorphism = H._to_perm_group() + + if (g_order != h_order) or (G.is_abelian != H.is_abelian): + if not isomorphism: + return False + return (False, None) + + if not isomorphism: + # Two groups of the same cyclic numbered order + # are isomorphic to each other. + n = g_order + if (igcd(n, totient(n))) == 1: + return True + + # Match the generators of `G` with subsets of `_H` + gens = list(G.generators) + for subset in itertools.permutations(_H, len(gens)): + images = list(subset) + images.extend([_H.identity]*(len(G.generators)-len(images))) + _images = dict(zip(gens,images)) + if _check_homomorphism(G, _H, _images): + if isinstance(H, FpGroup): + images = h_isomorphism.invert(images) + T = homomorphism(G, H, G.generators, images, check=False) + if T.is_isomorphism(): + # It is a valid isomorphism + if not isomorphism: + return True + return (True, T) + + if not isomorphism: + return False + return (False, None) + +def is_isomorphic(G, H): + ''' + Check if the groups are isomorphic to each other + + Parameters + ========== + + G : A finite ``FpGroup`` or a ``PermutationGroup`` + First group. + + H : A finite ``FpGroup`` or a ``PermutationGroup`` + Second group. + + Returns + ======= + + boolean + ''' + return group_isomorphism(G, H, isomorphism=False) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/named_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/named_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..59f10c40ef716e3b644e00f936323e9f6936eb88 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/named_groups.py @@ -0,0 +1,332 @@ +from sympy.combinatorics.group_constructs import DirectProduct +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.combinatorics.permutations import Permutation + +_af_new = Permutation._af_new + + +def AbelianGroup(*cyclic_orders): + """ + Returns the direct product of cyclic groups with the given orders. + + Explanation + =========== + + According to the structure theorem for finite abelian groups ([1]), + every finite abelian group can be written as the direct product of + finitely many cyclic groups. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AbelianGroup + >>> AbelianGroup(3, 4) + PermutationGroup([ + (6)(0 1 2), + (3 4 5 6)]) + >>> _.is_group + True + + See Also + ======== + + DirectProduct + + References + ========== + + .. [1] https://groupprops.subwiki.org/wiki/Structure_theorem_for_finitely_generated_abelian_groups + + """ + groups = [] + degree = 0 + order = 1 + for size in cyclic_orders: + degree += size + order *= size + groups.append(CyclicGroup(size)) + G = DirectProduct(*groups) + G._is_abelian = True + G._degree = degree + G._order = order + + return G + + +def AlternatingGroup(n): + """ + Generates the alternating group on ``n`` elements as a permutation group. + + Explanation + =========== + + For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for + ``n`` odd + and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.). + After the group is generated, some of its basic properties are set. + The cases ``n = 1, 2`` are handled separately. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AlternatingGroup + >>> G = AlternatingGroup(4) + >>> G.is_group + True + >>> a = list(G.generate_dimino()) + >>> len(a) + 12 + >>> all(perm.is_even for perm in a) + True + + See Also + ======== + + SymmetricGroup, CyclicGroup, DihedralGroup + + References + ========== + + .. [1] Armstrong, M. "Groups and Symmetry" + + """ + # small cases are special + if n in (1, 2): + return PermutationGroup([Permutation([0])]) + + a = list(range(n)) + a[0], a[1], a[2] = a[1], a[2], a[0] + gen1 = a + if n % 2: + a = list(range(1, n)) + a.append(0) + gen2 = a + else: + a = list(range(2, n)) + a.append(1) + a.insert(0, 0) + gen2 = a + gens = [gen1, gen2] + if gen1 == gen2: + gens = gens[:1] + G = PermutationGroup([_af_new(a) for a in gens], dups=False) + + set_alternating_group_properties(G, n, n) + G._is_alt = True + return G + + +def set_alternating_group_properties(G, n, degree): + """Set known properties of an alternating group. """ + if n < 4: + G._is_abelian = True + G._is_nilpotent = True + else: + G._is_abelian = False + G._is_nilpotent = False + if n < 5: + G._is_solvable = True + else: + G._is_solvable = False + G._degree = degree + G._is_transitive = True + G._is_dihedral = False + + +def CyclicGroup(n): + """ + Generates the cyclic group of order ``n`` as a permutation group. + + Explanation + =========== + + The generator taken is the ``n``-cycle ``(0 1 2 ... n-1)`` + (in cycle notation). After the group is generated, some of its basic + properties are set. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import CyclicGroup + >>> G = CyclicGroup(6) + >>> G.is_group + True + >>> G.order() + 6 + >>> list(G.generate_schreier_sims(af=True)) + [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 0], [2, 3, 4, 5, 0, 1], + [3, 4, 5, 0, 1, 2], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]] + + See Also + ======== + + SymmetricGroup, DihedralGroup, AlternatingGroup + + """ + a = list(range(1, n)) + a.append(0) + gen = _af_new(a) + G = PermutationGroup([gen]) + + G._is_abelian = True + G._is_nilpotent = True + G._is_solvable = True + G._degree = n + G._is_transitive = True + G._order = n + G._is_dihedral = (n == 2) + return G + + +def DihedralGroup(n): + r""" + Generates the dihedral group `D_n` as a permutation group. + + Explanation + =========== + + The dihedral group `D_n` is the group of symmetries of the regular + ``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)`` + (a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...`` + (a reflection of the ``n``-gon) in cycle rotation. It is easy to see that + these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate + `D_n` (See [1]). After the group is generated, some of its basic properties + are set. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> G = DihedralGroup(5) + >>> G.is_group + True + >>> a = list(G.generate_dimino()) + >>> [perm.cyclic_form for perm in a] + [[], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]], + [[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[0, 4], [1, 3]], + [[1, 4], [2, 3]], [[0, 1], [2, 4]], [[0, 2], [3, 4]], + [[0, 3], [1, 2]]] + + See Also + ======== + + SymmetricGroup, CyclicGroup, AlternatingGroup + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Dihedral_group + + """ + # small cases are special + if n == 1: + return PermutationGroup([Permutation([1, 0])]) + if n == 2: + return PermutationGroup([Permutation([1, 0, 3, 2]), + Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0])]) + + a = list(range(1, n)) + a.append(0) + gen1 = _af_new(a) + a = list(range(n)) + a.reverse() + gen2 = _af_new(a) + G = PermutationGroup([gen1, gen2]) + # if n is a power of 2, group is nilpotent + if n & (n-1) == 0: + G._is_nilpotent = True + else: + G._is_nilpotent = False + G._is_dihedral = True + G._is_abelian = False + G._is_solvable = True + G._degree = n + G._is_transitive = True + G._order = 2*n + return G + + +def SymmetricGroup(n): + """ + Generates the symmetric group on ``n`` elements as a permutation group. + + Explanation + =========== + + The generators taken are the ``n``-cycle + ``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation). + (See [1]). After the group is generated, some of its basic properties + are set. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> G = SymmetricGroup(4) + >>> G.is_group + True + >>> G.order() + 24 + >>> list(G.generate_schreier_sims(af=True)) + [[0, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 1], [3, 1, 2, 0], [0, 2, 3, 1], + [1, 3, 0, 2], [2, 0, 1, 3], [3, 2, 0, 1], [0, 3, 1, 2], [1, 0, 2, 3], + [2, 1, 3, 0], [3, 0, 1, 2], [0, 1, 3, 2], [1, 2, 0, 3], [2, 3, 1, 0], + [3, 1, 0, 2], [0, 2, 1, 3], [1, 3, 2, 0], [2, 0, 3, 1], [3, 2, 1, 0], + [0, 3, 2, 1], [1, 0, 3, 2], [2, 1, 0, 3], [3, 0, 2, 1]] + + See Also + ======== + + CyclicGroup, DihedralGroup, AlternatingGroup + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations + + """ + if n == 1: + G = PermutationGroup([Permutation([0])]) + elif n == 2: + G = PermutationGroup([Permutation([1, 0])]) + else: + a = list(range(1, n)) + a.append(0) + gen1 = _af_new(a) + a = list(range(n)) + a[0], a[1] = a[1], a[0] + gen2 = _af_new(a) + G = PermutationGroup([gen1, gen2]) + set_symmetric_group_properties(G, n, n) + G._is_sym = True + return G + + +def set_symmetric_group_properties(G, n, degree): + """Set known properties of a symmetric group. """ + if n < 3: + G._is_abelian = True + G._is_nilpotent = True + else: + G._is_abelian = False + G._is_nilpotent = False + if n < 5: + G._is_solvable = True + else: + G._is_solvable = False + G._degree = degree + G._is_transitive = True + G._is_dihedral = (n in [2, 3]) # cf Landau's func and Stirling's approx + + +def RubikGroup(n): + """Return a group of Rubik's cube generators + + >>> from sympy.combinatorics.named_groups import RubikGroup + >>> RubikGroup(2).is_group + True + """ + from sympy.combinatorics.generators import rubik + if n <= 1: + raise ValueError("Invalid cube. n has to be greater than 1") + return PermutationGroup(rubik(n)) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/partitions.py b/phivenv/Lib/site-packages/sympy/combinatorics/partitions.py new file mode 100644 index 0000000000000000000000000000000000000000..dfe646baabbb5bf2350cba859a265ac32bbfaf53 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/partitions.py @@ -0,0 +1,745 @@ +from sympy.core import Basic, Dict, sympify, Tuple +from sympy.core.numbers import Integer +from sympy.core.sorting import default_sort_key +from sympy.core.sympify import _sympify +from sympy.functions.combinatorial.numbers import bell +from sympy.matrices import zeros +from sympy.sets.sets import FiniteSet, Union +from sympy.utilities.iterables import flatten, group +from sympy.utilities.misc import as_int + + +from collections import defaultdict + + +class Partition(FiniteSet): + """ + This class represents an abstract partition. + + A partition is a set of disjoint sets whose union equals a given set. + + See Also + ======== + + sympy.utilities.iterables.partitions, + sympy.utilities.iterables.multiset_partitions + """ + + _rank = None + _partition = None + + def __new__(cls, *partition): + """ + Generates a new partition object. + + This method also verifies if the arguments passed are + valid and raises a ValueError if they are not. + + Examples + ======== + + Creating Partition from Python lists: + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3]) + >>> a + Partition({3}, {1, 2}) + >>> a.partition + [[1, 2], [3]] + >>> len(a) + 2 + >>> a.members + (1, 2, 3) + + Creating Partition from Python sets: + + >>> Partition({1, 2, 3}, {4, 5}) + Partition({4, 5}, {1, 2, 3}) + + Creating Partition from SymPy finite sets: + + >>> from sympy import FiniteSet + >>> a = FiniteSet(1, 2, 3) + >>> b = FiniteSet(4, 5) + >>> Partition(a, b) + Partition({4, 5}, {1, 2, 3}) + """ + args = [] + dups = False + for arg in partition: + if isinstance(arg, list): + as_set = set(arg) + if len(as_set) < len(arg): + dups = True + break # error below + arg = as_set + args.append(_sympify(arg)) + + if not all(isinstance(part, FiniteSet) for part in args): + raise ValueError( + "Each argument to Partition should be " \ + "a list, set, or a FiniteSet") + + # sort so we have a canonical reference for RGS + U = Union(*args) + if dups or len(U) < sum(len(arg) for arg in args): + raise ValueError("Partition contained duplicate elements.") + + obj = FiniteSet.__new__(cls, *args) + obj.members = tuple(U) + obj.size = len(U) + return obj + + def sort_key(self, order=None): + """Return a canonical key that can be used for sorting. + + Ordering is based on the size and sorted elements of the partition + and ties are broken with the rank. + + Examples + ======== + + >>> from sympy import default_sort_key + >>> from sympy.combinatorics import Partition + >>> from sympy.abc import x + >>> a = Partition([1, 2]) + >>> b = Partition([3, 4]) + >>> c = Partition([1, x]) + >>> d = Partition(list(range(4))) + >>> l = [d, b, a + 1, a, c] + >>> l.sort(key=default_sort_key); l + [Partition({1, 2}), Partition({1}, {2}), Partition({1, x}), Partition({3, 4}), Partition({0, 1, 2, 3})] + """ + if order is None: + members = self.members + else: + members = tuple(sorted(self.members, + key=lambda w: default_sort_key(w, order))) + return tuple(map(default_sort_key, (self.size, members, self.rank))) + + @property + def partition(self): + """Return partition as a sorted list of lists. + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> Partition([1], [2, 3]).partition + [[1], [2, 3]] + """ + if self._partition is None: + self._partition = sorted([sorted(p, key=default_sort_key) + for p in self.args]) + return self._partition + + def __add__(self, other): + """ + Return permutation whose rank is ``other`` greater than current rank, + (mod the maximum rank for the set). + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3]) + >>> a.rank + 1 + >>> (a + 1).rank + 2 + >>> (a + 100).rank + 1 + """ + other = as_int(other) + offset = self.rank + other + result = RGS_unrank((offset) % + RGS_enum(self.size), + self.size) + return Partition.from_rgs(result, self.members) + + def __sub__(self, other): + """ + Return permutation whose rank is ``other`` less than current rank, + (mod the maximum rank for the set). + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3]) + >>> a.rank + 1 + >>> (a - 1).rank + 0 + >>> (a - 100).rank + 1 + """ + return self.__add__(-other) + + def __le__(self, other): + """ + Checks if a partition is less than or equal to + the other based on rank. + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3, 4, 5]) + >>> b = Partition([1], [2, 3], [4], [5]) + >>> a.rank, b.rank + (9, 34) + >>> a <= a + True + >>> a <= b + True + """ + return self.sort_key() <= sympify(other).sort_key() + + def __lt__(self, other): + """ + Checks if a partition is less than the other. + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3, 4, 5]) + >>> b = Partition([1], [2, 3], [4], [5]) + >>> a.rank, b.rank + (9, 34) + >>> a < b + True + """ + return self.sort_key() < sympify(other).sort_key() + + @property + def rank(self): + """ + Gets the rank of a partition. + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3], [4, 5]) + >>> a.rank + 13 + """ + if self._rank is not None: + return self._rank + self._rank = RGS_rank(self.RGS) + return self._rank + + @property + def RGS(self): + """ + Returns the "restricted growth string" of the partition. + + Explanation + =========== + + The RGS is returned as a list of indices, L, where L[i] indicates + the block in which element i appears. For example, in a partition + of 3 elements (a, b, c) into 2 blocks ([c], [a, b]) the RGS is + [1, 1, 0]: "a" is in block 1, "b" is in block 1 and "c" is in block 0. + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> a = Partition([1, 2], [3], [4, 5]) + >>> a.members + (1, 2, 3, 4, 5) + >>> a.RGS + (0, 0, 1, 2, 2) + >>> a + 1 + Partition({3}, {4}, {5}, {1, 2}) + >>> _.RGS + (0, 0, 1, 2, 3) + """ + rgs = {} + partition = self.partition + for i, part in enumerate(partition): + for j in part: + rgs[j] = i + return tuple([rgs[i] for i in sorted( + [i for p in partition for i in p], key=default_sort_key)]) + + @classmethod + def from_rgs(self, rgs, elements): + """ + Creates a set partition from a restricted growth string. + + Explanation + =========== + + The indices given in rgs are assumed to be the index + of the element as given in elements *as provided* (the + elements are not sorted by this routine). Block numbering + starts from 0. If any block was not referenced in ``rgs`` + an error will be raised. + + Examples + ======== + + >>> from sympy.combinatorics import Partition + >>> Partition.from_rgs([0, 1, 2, 0, 1], list('abcde')) + Partition({c}, {a, d}, {b, e}) + >>> Partition.from_rgs([0, 1, 2, 0, 1], list('cbead')) + Partition({e}, {a, c}, {b, d}) + >>> a = Partition([1, 4], [2], [3, 5]) + >>> Partition.from_rgs(a.RGS, a.members) + Partition({2}, {1, 4}, {3, 5}) + """ + if len(rgs) != len(elements): + raise ValueError('mismatch in rgs and element lengths') + max_elem = max(rgs) + 1 + partition = [[] for i in range(max_elem)] + j = 0 + for i in rgs: + partition[i].append(elements[j]) + j += 1 + if not all(p for p in partition): + raise ValueError('some blocks of the partition were empty.') + return Partition(*partition) + + +class IntegerPartition(Basic): + """ + This class represents an integer partition. + + Explanation + =========== + + In number theory and combinatorics, a partition of a positive integer, + ``n``, also called an integer partition, is a way of writing ``n`` as a + list of positive integers that sum to n. Two partitions that differ only + in the order of summands are considered to be the same partition; if order + matters then the partitions are referred to as compositions. For example, + 4 has five partitions: [4], [3, 1], [2, 2], [2, 1, 1], and [1, 1, 1, 1]; + the compositions [1, 2, 1] and [1, 1, 2] are the same as partition + [2, 1, 1]. + + See Also + ======== + + sympy.utilities.iterables.partitions, + sympy.utilities.iterables.multiset_partitions + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Partition_%28number_theory%29 + """ + + _dict = None + _keys = None + + def __new__(cls, partition, integer=None): + """ + Generates a new IntegerPartition object from a list or dictionary. + + Explanation + =========== + + The partition can be given as a list of positive integers or a + dictionary of (integer, multiplicity) items. If the partition is + preceded by an integer an error will be raised if the partition + does not sum to that given integer. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> a = IntegerPartition([5, 4, 3, 1, 1]) + >>> a + IntegerPartition(14, (5, 4, 3, 1, 1)) + >>> print(a) + [5, 4, 3, 1, 1] + >>> IntegerPartition({1:3, 2:1}) + IntegerPartition(5, (2, 1, 1, 1)) + + If the value that the partition should sum to is given first, a check + will be made to see n error will be raised if there is a discrepancy: + + >>> IntegerPartition(10, [5, 4, 3, 1]) + Traceback (most recent call last): + ... + ValueError: The partition is not valid + + """ + if integer is not None: + integer, partition = partition, integer + if isinstance(partition, (dict, Dict)): + _ = [] + for k, v in sorted(partition.items(), reverse=True): + if not v: + continue + k, v = as_int(k), as_int(v) + _.extend([k]*v) + partition = tuple(_) + else: + partition = tuple(sorted(map(as_int, partition), reverse=True)) + sum_ok = False + if integer is None: + integer = sum(partition) + sum_ok = True + else: + integer = as_int(integer) + + if not sum_ok and sum(partition) != integer: + raise ValueError("Partition did not add to %s" % integer) + if any(i < 1 for i in partition): + raise ValueError("All integer summands must be greater than one") + + obj = Basic.__new__(cls, Integer(integer), Tuple(*partition)) + obj.partition = list(partition) + obj.integer = integer + return obj + + def prev_lex(self): + """Return the previous partition of the integer, n, in lexical order, + wrapping around to [1, ..., 1] if the partition is [n]. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> p = IntegerPartition([4]) + >>> print(p.prev_lex()) + [3, 1] + >>> p.partition > p.prev_lex().partition + True + """ + d = defaultdict(int) + d.update(self.as_dict()) + keys = self._keys + if keys == [1]: + return IntegerPartition({self.integer: 1}) + if keys[-1] != 1: + d[keys[-1]] -= 1 + if keys[-1] == 2: + d[1] = 2 + else: + d[keys[-1] - 1] = d[1] = 1 + else: + d[keys[-2]] -= 1 + left = d[1] + keys[-2] + new = keys[-2] + d[1] = 0 + while left: + new -= 1 + if left - new >= 0: + d[new] += left//new + left -= d[new]*new + return IntegerPartition(self.integer, d) + + def next_lex(self): + """Return the next partition of the integer, n, in lexical order, + wrapping around to [n] if the partition is [1, ..., 1]. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> p = IntegerPartition([3, 1]) + >>> print(p.next_lex()) + [4] + >>> p.partition < p.next_lex().partition + True + """ + d = defaultdict(int) + d.update(self.as_dict()) + key = self._keys + a = key[-1] + if a == self.integer: + d.clear() + d[1] = self.integer + elif a == 1: + if d[a] > 1: + d[a + 1] += 1 + d[a] -= 2 + else: + b = key[-2] + d[b + 1] += 1 + d[1] = (d[b] - 1)*b + d[b] = 0 + else: + if d[a] > 1: + if len(key) == 1: + d.clear() + d[a + 1] = 1 + d[1] = self.integer - a - 1 + else: + a1 = a + 1 + d[a1] += 1 + d[1] = d[a]*a - a1 + d[a] = 0 + else: + b = key[-2] + b1 = b + 1 + d[b1] += 1 + need = d[b]*b + d[a]*a - b1 + d[a] = d[b] = 0 + d[1] = need + return IntegerPartition(self.integer, d) + + def as_dict(self): + """Return the partition as a dictionary whose keys are the + partition integers and the values are the multiplicity of that + integer. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> IntegerPartition([1]*3 + [2] + [3]*4).as_dict() + {1: 3, 2: 1, 3: 4} + """ + if self._dict is None: + groups = group(self.partition, multiple=False) + self._keys = [g[0] for g in groups] + self._dict = dict(groups) + return self._dict + + @property + def conjugate(self): + """ + Computes the conjugate partition of itself. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> a = IntegerPartition([6, 3, 3, 2, 1]) + >>> a.conjugate + [5, 4, 3, 1, 1, 1] + """ + j = 1 + temp_arr = list(self.partition) + [0] + k = temp_arr[0] + b = [0]*k + while k > 0: + while k > temp_arr[j]: + b[k - 1] = j + k -= 1 + j += 1 + return b + + def __lt__(self, other): + """Return True if self is less than other when the partition + is listed from smallest to biggest. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> a = IntegerPartition([3, 1]) + >>> a < a + False + >>> b = a.next_lex() + >>> a < b + True + >>> a == b + False + """ + return list(reversed(self.partition)) < list(reversed(other.partition)) + + def __le__(self, other): + """Return True if self is less than other when the partition + is listed from smallest to biggest. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> a = IntegerPartition([4]) + >>> a <= a + True + """ + return list(reversed(self.partition)) <= list(reversed(other.partition)) + + def as_ferrers(self, char='#'): + """ + Prints the ferrer diagram of a partition. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import IntegerPartition + >>> print(IntegerPartition([1, 1, 5]).as_ferrers()) + ##### + # + # + """ + return "\n".join([char*i for i in self.partition]) + + def __str__(self): + return str(list(self.partition)) + + +def random_integer_partition(n, seed=None): + """ + Generates a random integer partition summing to ``n`` as a list + of reverse-sorted integers. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import random_integer_partition + + For the following, a seed is given so a known value can be shown; in + practice, the seed would not be given. + + >>> random_integer_partition(100, seed=[1, 1, 12, 1, 2, 1, 85, 1]) + [85, 12, 2, 1] + >>> random_integer_partition(10, seed=[1, 2, 3, 1, 5, 1]) + [5, 3, 1, 1] + >>> random_integer_partition(1) + [1] + """ + from sympy.core.random import _randint + + n = as_int(n) + if n < 1: + raise ValueError('n must be a positive integer') + + randint = _randint(seed) + + partition = [] + while (n > 0): + k = randint(1, n) + mult = randint(1, n//k) + partition.append((k, mult)) + n -= k*mult + partition.sort(reverse=True) + partition = flatten([[k]*m for k, m in partition]) + return partition + + +def RGS_generalized(m): + """ + Computes the m + 1 generalized unrestricted growth strings + and returns them as rows in matrix. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import RGS_generalized + >>> RGS_generalized(6) + Matrix([ + [ 1, 1, 1, 1, 1, 1, 1], + [ 1, 2, 3, 4, 5, 6, 0], + [ 2, 5, 10, 17, 26, 0, 0], + [ 5, 15, 37, 77, 0, 0, 0], + [ 15, 52, 151, 0, 0, 0, 0], + [ 52, 203, 0, 0, 0, 0, 0], + [203, 0, 0, 0, 0, 0, 0]]) + """ + d = zeros(m + 1) + for i in range(m + 1): + d[0, i] = 1 + + for i in range(1, m + 1): + for j in range(m): + if j <= m - i: + d[i, j] = j * d[i - 1, j] + d[i - 1, j + 1] + else: + d[i, j] = 0 + return d + + +def RGS_enum(m): + """ + RGS_enum computes the total number of restricted growth strings + possible for a superset of size m. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import RGS_enum + >>> from sympy.combinatorics import Partition + >>> RGS_enum(4) + 15 + >>> RGS_enum(5) + 52 + >>> RGS_enum(6) + 203 + + We can check that the enumeration is correct by actually generating + the partitions. Here, the 15 partitions of 4 items are generated: + + >>> a = Partition(list(range(4))) + >>> s = set() + >>> for i in range(20): + ... s.add(a) + ... a += 1 + ... + >>> assert len(s) == 15 + + """ + if (m < 1): + return 0 + elif (m == 1): + return 1 + else: + return bell(m) + + +def RGS_unrank(rank, m): + """ + Gives the unranked restricted growth string for a given + superset size. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import RGS_unrank + >>> RGS_unrank(14, 4) + [0, 1, 2, 3] + >>> RGS_unrank(0, 4) + [0, 0, 0, 0] + """ + if m < 1: + raise ValueError("The superset size must be >= 1") + if rank < 0 or RGS_enum(m) <= rank: + raise ValueError("Invalid arguments") + + L = [1] * (m + 1) + j = 1 + D = RGS_generalized(m) + for i in range(2, m + 1): + v = D[m - i, j] + cr = j*v + if cr <= rank: + L[i] = j + 1 + rank -= cr + j += 1 + else: + L[i] = int(rank / v + 1) + rank %= v + return [x - 1 for x in L[1:]] + + +def RGS_rank(rgs): + """ + Computes the rank of a restricted growth string. + + Examples + ======== + + >>> from sympy.combinatorics.partitions import RGS_rank, RGS_unrank + >>> RGS_rank([0, 1, 2, 1, 3]) + 42 + >>> RGS_rank(RGS_unrank(4, 7)) + 4 + """ + rgs_size = len(rgs) + rank = 0 + D = RGS_generalized(rgs_size) + for i in range(1, rgs_size): + n = len(rgs[(i + 1):]) + m = max(rgs[0:i]) + rank += D[n, m + 1] * rgs[i] + return rank diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/pc_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/pc_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..abf7b82258d8bb61ba350509fcbb3110a035fc88 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/pc_groups.py @@ -0,0 +1,710 @@ +from sympy.ntheory.primetest import isprime +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.printing.defaults import DefaultPrinting +from sympy.combinatorics.free_groups import free_group + + +class PolycyclicGroup(DefaultPrinting): + + is_group = True + is_solvable = True + + def __init__(self, pc_sequence, pc_series, relative_order, collector=None): + """ + + Parameters + ========== + + pc_sequence : list + A sequence of elements whose classes generate the cyclic factor + groups of pc_series. + pc_series : list + A subnormal sequence of subgroups where each factor group is cyclic. + relative_order : list + The orders of factor groups of pc_series. + collector : Collector + By default, it is None. Collector class provides the + polycyclic presentation with various other functionalities. + + """ + self.pcgs = pc_sequence + self.pc_series = pc_series + self.relative_order = relative_order + self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector + + def is_prime_order(self): + return all(isprime(order) for order in self.relative_order) + + def length(self): + return len(self.pcgs) + + +class Collector(DefaultPrinting): + + """ + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of Computational Group Theory" + Section 8.1.3 + """ + + def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None): + """ + + Most of the parameters for the Collector class are the same as for PolycyclicGroup. + Others are described below. + + Parameters + ========== + + free_group_ : tuple + free_group_ provides the mapping of polycyclic generating + sequence with the free group elements. + pc_presentation : dict + Provides the presentation of polycyclic groups with the + help of power and conjugate relators. + + See Also + ======== + + PolycyclicGroup + + """ + self.pcgs = pcgs + self.pc_series = pc_series + self.relative_order = relative_order + self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_ + self.index = {s: i for i, s in enumerate(self.free_group.symbols)} + self.pc_presentation = self.pc_relators() + + def minimal_uncollected_subword(self, word): + r""" + Returns the minimal uncollected subwords. + + Explanation + =========== + + A word ``v`` defined on generators in ``X`` is a minimal + uncollected subword of the word ``w`` if ``v`` is a subword + of ``w`` and it has one of the following form + + * `v = {x_{i+1}}^{a_j}x_i` + + * `v = {x_{i+1}}^{a_j}{x_i}^{-1}` + + * `v = {x_i}^{a_j}` + + for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power + exponent of the corresponding generator. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics import free_group + >>> G = SymmetricGroup(4) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> F, x1, x2 = free_group("x1, x2") + >>> word = x2**2*x1**7 + >>> collector.minimal_uncollected_subword(word) + ((x2, 2),) + + """ + # To handle the case word = + if not word: + return None + + array = word.array_form + re = self.relative_order + index = self.index + + for i in range(len(array)): + s1, e1 = array[i] + + if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1): + return ((s1, e1), ) + + for i in range(len(array)-1): + s1, e1 = array[i] + s2, e2 = array[i+1] + + if index[s1] > index[s2]: + e = 1 if e2 > 0 else -1 + return ((s1, e1), (s2, e)) + + return None + + def relations(self): + """ + Separates the given relators of pc presentation in power and + conjugate relations. + + Returns + ======= + + (power_rel, conj_rel) + Separates pc presentation into power and conjugate relations. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> G = SymmetricGroup(3) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> power_rel, conj_rel = collector.relations() + >>> power_rel + {x0**2: (), x1**3: ()} + >>> conj_rel + {x0**-1*x1*x0: x1**2} + + See Also + ======== + + pc_relators + + """ + power_relators = {} + conjugate_relators = {} + for key, value in self.pc_presentation.items(): + if len(key.array_form) == 1: + power_relators[key] = value + else: + conjugate_relators[key] = value + return power_relators, conjugate_relators + + def subword_index(self, word, w): + """ + Returns the start and ending index of a given + subword in a word. + + Parameters + ========== + + word : FreeGroupElement + word defined on free group elements for a + polycyclic group. + w : FreeGroupElement + subword of a given word, whose starting and + ending index to be computed. + + Returns + ======= + + (i, j) + A tuple containing starting and ending index of ``w`` + in the given word. If not exists, (-1,-1) is returned. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics import free_group + >>> G = SymmetricGroup(4) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> F, x1, x2 = free_group("x1, x2") + >>> word = x2**2*x1**7 + >>> w = x2**2*x1 + >>> collector.subword_index(word, w) + (0, 3) + >>> w = x1**7 + >>> collector.subword_index(word, w) + (2, 9) + >>> w = x1**8 + >>> collector.subword_index(word, w) + (-1, -1) + + """ + low = -1 + high = -1 + for i in range(len(word)-len(w)+1): + if word.subword(i, i+len(w)) == w: + low = i + high = i+len(w) + break + return low, high + + def map_relation(self, w): + """ + Return a conjugate relation. + + Explanation + =========== + + Given a word formed by two free group elements, the + corresponding conjugate relation with those free + group elements is formed and mapped with the collected + word in the polycyclic presentation. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics import free_group + >>> G = SymmetricGroup(3) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> F, x0, x1 = free_group("x0, x1") + >>> w = x1*x0 + >>> collector.map_relation(w) + x1**2 + + See Also + ======== + + pc_presentation + + """ + array = w.array_form + s1 = array[0][0] + s2 = array[1][0] + key = ((s2, -1), (s1, 1), (s2, 1)) + key = self.free_group.dtype(key) + return self.pc_presentation[key] + + + def collected_word(self, word): + r""" + Return the collected form of a word. + + Explanation + =========== + + A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots * + {x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in + `\{1, \ldots, {s_j}-1\}`. + + Otherwise w is uncollected. + + Parameters + ========== + + word : FreeGroupElement + An uncollected word. + + Returns + ======= + + word + A collected word of form `w = {x_{i_1}}^{a_1}, \ldots, + {x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in + \{1, \ldots, {s_j}-1\}`. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics.perm_groups import PermutationGroup + >>> from sympy.combinatorics import free_group + >>> G = SymmetricGroup(4) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3") + >>> word = x3*x2*x1*x0 + >>> collected_word = collector.collected_word(word) + >>> free_to_perm = {} + >>> free_group = collector.free_group + >>> for sym, gen in zip(free_group.symbols, collector.pcgs): + ... free_to_perm[sym] = gen + >>> G1 = PermutationGroup() + >>> for w in word: + ... sym = w[0] + ... perm = free_to_perm[sym] + ... G1 = PermutationGroup([perm] + G1.generators) + >>> G2 = PermutationGroup() + >>> for w in collected_word: + ... sym = w[0] + ... perm = free_to_perm[sym] + ... G2 = PermutationGroup([perm] + G2.generators) + + The two are not identical, but they are equivalent: + + >>> G1.equals(G2), G1 == G2 + (True, False) + + See Also + ======== + + minimal_uncollected_subword + + """ + free_group = self.free_group + while True: + w = self.minimal_uncollected_subword(word) + if not w: + break + + low, high = self.subword_index(word, free_group.dtype(w)) + if low == -1: + continue + + s1, e1 = w[0] + if len(w) == 1: + re = self.relative_order[self.index[s1]] + q = e1 // re + r = e1-q*re + + key = ((w[0][0], re), ) + key = free_group.dtype(key) + if self.pc_presentation[key]: + presentation = self.pc_presentation[key].array_form + sym, exp = presentation[0] + word_ = ((w[0][0], r), (sym, q*exp)) + word_ = free_group.dtype(word_) + else: + if r != 0: + word_ = ((w[0][0], r), ) + word_ = free_group.dtype(word_) + else: + word_ = None + word = word.eliminate_word(free_group.dtype(w), word_) + + if len(w) == 2 and w[1][1] > 0: + s2, e2 = w[1] + s2 = ((s2, 1), ) + s2 = free_group.dtype(s2) + word_ = self.map_relation(free_group.dtype(w)) + word_ = s2*word_**e1 + word_ = free_group.dtype(word_) + word = word.substituted_word(low, high, word_) + + elif len(w) == 2 and w[1][1] < 0: + s2, e2 = w[1] + s2 = ((s2, 1), ) + s2 = free_group.dtype(s2) + word_ = self.map_relation(free_group.dtype(w)) + word_ = s2**-1*word_**e1 + word_ = free_group.dtype(word_) + word = word.substituted_word(low, high, word_) + + return word + + + def pc_relators(self): + r""" + Return the polycyclic presentation. + + Explanation + =========== + + There are two types of relations used in polycyclic + presentation. + + * Power relations : Power relators are of the form `x_i^{re_i}`, + where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic + generator and ``re`` is the corresponding relative order. + + * Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`, + where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`. + + Returns + ======= + + A dictionary with power and conjugate relations as key and + their collected form as corresponding values. + + Notes + ===== + + Identity Permutation is mapped with empty ``()``. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics.permutations import Permutation + >>> S = SymmetricGroup(49).sylow_subgroup(7) + >>> der = S.derived_series() + >>> G = der[len(der)-2] + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> pcgs = PcGroup.pcgs + >>> len(pcgs) + 6 + >>> free_group = collector.free_group + >>> pc_resentation = collector.pc_presentation + >>> free_to_perm = {} + >>> for s, g in zip(free_group.symbols, pcgs): + ... free_to_perm[s] = g + + >>> for k, v in pc_resentation.items(): + ... k_array = k.array_form + ... if v != (): + ... v_array = v.array_form + ... lhs = Permutation() + ... for gen in k_array: + ... s = gen[0] + ... e = gen[1] + ... lhs = lhs*free_to_perm[s]**e + ... if v == (): + ... assert lhs.is_identity + ... continue + ... rhs = Permutation() + ... for gen in v_array: + ... s = gen[0] + ... e = gen[1] + ... rhs = rhs*free_to_perm[s]**e + ... assert lhs == rhs + + """ + free_group = self.free_group + rel_order = self.relative_order + pc_relators = {} + perm_to_free = {} + pcgs = self.pcgs + + for gen, s in zip(pcgs, free_group.generators): + perm_to_free[gen**-1] = s**-1 + perm_to_free[gen] = s + + pcgs = pcgs[::-1] + series = self.pc_series[::-1] + rel_order = rel_order[::-1] + collected_gens = [] + + for i, gen in enumerate(pcgs): + re = rel_order[i] + relation = perm_to_free[gen]**re + G = series[i] + + l = G.generator_product(gen**re, original = True) + l.reverse() + + word = free_group.identity + for g in l: + word = word*perm_to_free[g] + + word = self.collected_word(word) + pc_relators[relation] = word if word else () + self.pc_presentation = pc_relators + + collected_gens.append(gen) + if len(collected_gens) > 1: + conj = collected_gens[len(collected_gens)-1] + conjugator = perm_to_free[conj] + + for j in range(len(collected_gens)-1): + conjugated = perm_to_free[collected_gens[j]] + + relation = conjugator**-1*conjugated*conjugator + gens = conj**-1*collected_gens[j]*conj + + l = G.generator_product(gens, original = True) + l.reverse() + word = free_group.identity + for g in l: + word = word*perm_to_free[g] + + word = self.collected_word(word) + pc_relators[relation] = word if word else () + self.pc_presentation = pc_relators + + return pc_relators + + def exponent_vector(self, element): + r""" + Return the exponent vector of length equal to the + length of polycyclic generating sequence. + + Explanation + =========== + + For a given generator/element ``g`` of the polycyclic group, + it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`, + where `x_i` represents polycyclic generators and ``n`` is + the number of generators in the free_group equal to the length + of pcgs. + + Parameters + ========== + + element : Permutation + Generator of a polycyclic group. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics.permutations import Permutation + >>> G = SymmetricGroup(4) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> pcgs = PcGroup.pcgs + >>> collector.exponent_vector(G[0]) + [1, 0, 0, 0] + >>> exp = collector.exponent_vector(G[1]) + >>> g = Permutation() + >>> for i in range(len(exp)): + ... g = g*pcgs[i]**exp[i] if exp[i] else g + >>> assert g == G[1] + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of Computational Group Theory" + Section 8.1.1, Definition 8.4 + + """ + free_group = self.free_group + G = PermutationGroup() + for g in self.pcgs: + G = PermutationGroup([g] + G.generators) + gens = G.generator_product(element, original = True) + gens.reverse() + + perm_to_free = {} + for sym, g in zip(free_group.generators, self.pcgs): + perm_to_free[g**-1] = sym**-1 + perm_to_free[g] = sym + w = free_group.identity + for g in gens: + w = w*perm_to_free[g] + + word = self.collected_word(w) + + index = self.index + exp_vector = [0]*len(free_group) + word = word.array_form + for t in word: + exp_vector[index[t[0]]] = t[1] + return exp_vector + + def depth(self, element): + r""" + Return the depth of a given element. + + Explanation + =========== + + The depth of a given element ``g`` is defined by + `\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0` + and `e_i != 0`, where ``e`` represents the exponent-vector. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> G = SymmetricGroup(3) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> collector.depth(G[0]) + 2 + >>> collector.depth(G[1]) + 1 + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of Computational Group Theory" + Section 8.1.1, Definition 8.5 + + """ + exp_vector = self.exponent_vector(element) + return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1) + + def leading_exponent(self, element): + r""" + Return the leading non-zero exponent. + + Explanation + =========== + + The leading exponent for a given element `g` is defined + by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> G = SymmetricGroup(3) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> collector.leading_exponent(G[1]) + 1 + + """ + exp_vector = self.exponent_vector(element) + depth = self.depth(element) + if depth != len(self.pcgs)+1: + return exp_vector[depth-1] + return None + + def _sift(self, z, g): + h = g + d = self.depth(h) + while d < len(self.pcgs) and z[d-1] != 1: + k = z[d-1] + e = self.leading_exponent(h)*(self.leading_exponent(k))**-1 + e = e % self.relative_order[d-1] + h = k**-e*h + d = self.depth(h) + return h + + def induced_pcgs(self, gens): + """ + + Parameters + ========== + + gens : list + A list of generators on which polycyclic subgroup + is to be defined. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> S = SymmetricGroup(8) + >>> G = S.sylow_subgroup(2) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> gens = [G[0], G[1]] + >>> ipcgs = collector.induced_pcgs(gens) + >>> [gen.order() for gen in ipcgs] + [2, 2, 2] + >>> G = S.sylow_subgroup(3) + >>> PcGroup = G.polycyclic_group() + >>> collector = PcGroup.collector + >>> gens = [G[0], G[1]] + >>> ipcgs = collector.induced_pcgs(gens) + >>> [gen.order() for gen in ipcgs] + [3] + + """ + z = [1]*len(self.pcgs) + G = gens + while G: + g = G.pop(0) + h = self._sift(z, g) + d = self.depth(h) + if d < len(self.pcgs): + for gen in z: + if gen != 1: + G.append(h**-1*gen**-1*h*gen) + z[d-1] = h + z = [gen for gen in z if gen != 1] + return z + + def constructive_membership_test(self, ipcgs, g): + """ + Return the exponent vector for induced pcgs. + """ + e = [0]*len(ipcgs) + h = g + d = self.depth(h) + for i, gen in enumerate(ipcgs): + while self.depth(gen) == d: + f = self.leading_exponent(h)*self.leading_exponent(gen) + f = f % self.relative_order[d-1] + h = gen**(-f)*h + e[i] = f + d = self.depth(h) + if h == 1: + return e + return False diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/perm_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/perm_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..24359cb546246d63470de92b2fb2ab0804fc9886 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/perm_groups.py @@ -0,0 +1,5459 @@ +from math import factorial as _factorial, log, prod +from itertools import chain, product + + +from sympy.combinatorics import Permutation +from sympy.combinatorics.permutations import (_af_commutes_with, _af_invert, + _af_rmul, _af_rmuln, _af_pow, Cycle) +from sympy.combinatorics.util import (_check_cycles_alt_sym, + _distribute_gens_by_base, _orbits_transversals_from_bsgs, + _handle_precomputed_bsgs, _base_ordering, _strong_gens_from_distr, + _strip, _strip_af) +from sympy.core import Basic +from sympy.core.random import _randrange, randrange, choice +from sympy.core.symbol import Symbol +from sympy.core.sympify import _sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.ntheory import primefactors, sieve +from sympy.ntheory.factor_ import (factorint, multiplicity) +from sympy.ntheory.primetest import isprime +from sympy.utilities.iterables import has_variety, is_sequence, uniq + +rmul = Permutation.rmul_with_af +_af_new = Permutation._af_new + + +class PermutationGroup(Basic): + r"""The class defining a Permutation group. + + Explanation + =========== + + ``PermutationGroup([p1, p2, ..., pn])`` returns the permutation group + generated by the list of permutations. This group can be supplied + to Polyhedron if one desires to decorate the elements to which the + indices of the permutation refer. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> from sympy.combinatorics import Polyhedron + + The permutations corresponding to motion of the front, right and + bottom face of a $2 \times 2$ Rubik's cube are defined: + + >>> F = Permutation(2, 19, 21, 8)(3, 17, 20, 10)(4, 6, 7, 5) + >>> R = Permutation(1, 5, 21, 14)(3, 7, 23, 12)(8, 10, 11, 9) + >>> D = Permutation(6, 18, 14, 10)(7, 19, 15, 11)(20, 22, 23, 21) + + These are passed as permutations to PermutationGroup: + + >>> G = PermutationGroup(F, R, D) + >>> G.order() + 3674160 + + The group can be supplied to a Polyhedron in order to track the + objects being moved. An example involving the $2 \times 2$ Rubik's cube is + given there, but here is a simple demonstration: + + >>> a = Permutation(2, 1) + >>> b = Permutation(1, 0) + >>> G = PermutationGroup(a, b) + >>> P = Polyhedron(list('ABC'), pgroup=G) + >>> P.corners + (A, B, C) + >>> P.rotate(0) # apply permutation 0 + >>> P.corners + (A, C, B) + >>> P.reset() + >>> P.corners + (A, B, C) + + Or one can make a permutation as a product of selected permutations + and apply them to an iterable directly: + + >>> P10 = G.make_perm([0, 1]) + >>> P10('ABC') + ['C', 'A', 'B'] + + See Also + ======== + + sympy.combinatorics.polyhedron.Polyhedron, + sympy.combinatorics.permutations.Permutation + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of Computational Group Theory" + + .. [2] Seress, A. + "Permutation Group Algorithms" + + .. [3] https://en.wikipedia.org/wiki/Schreier_vector + + .. [4] https://en.wikipedia.org/wiki/Nielsen_transformation#Product_replacement_algorithm + + .. [5] Frank Celler, Charles R.Leedham-Green, Scott H.Murray, + Alice C.Niemeyer, and E.A.O'Brien. "Generating Random + Elements of a Finite Group" + + .. [6] https://en.wikipedia.org/wiki/Block_%28permutation_group_theory%29 + + .. [7] https://algorithmist.com/wiki/Union_find + + .. [8] https://en.wikipedia.org/wiki/Multiply_transitive_group#Multiply_transitive_groups + + .. [9] https://en.wikipedia.org/wiki/Center_%28group_theory%29 + + .. [10] https://en.wikipedia.org/wiki/Centralizer_and_normalizer + + .. [11] https://groupprops.subwiki.org/wiki/Derived_subgroup + + .. [12] https://en.wikipedia.org/wiki/Nilpotent_group + + .. [13] https://www.math.colostate.edu/~hulpke/CGT/cgtnotes.pdf + + .. [14] https://docs.gap-system.org/doc/ref/manual.pdf + + """ + is_group = True + + def __new__(cls, *args, dups=True, **kwargs): + """The default constructor. Accepts Cycle and Permutation forms. + Removes duplicates unless ``dups`` keyword is ``False``. + """ + if not args: + args = [Permutation()] + else: + args = list(args[0] if is_sequence(args[0]) else args) + if not args: + args = [Permutation()] + if any(isinstance(a, Cycle) for a in args): + args = [Permutation(a) for a in args] + if has_variety(a.size for a in args): + degree = kwargs.pop('degree', None) + if degree is None: + degree = max(a.size for a in args) + for i in range(len(args)): + if args[i].size != degree: + args[i] = Permutation(args[i], size=degree) + if dups: + args = list(uniq([_af_new(list(a)) for a in args])) + if len(args) > 1: + args = [g for g in args if not g.is_identity] + return Basic.__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + self._generators = list(self.args) + self._order = None + self._elements = [] + self._center = None + self._is_abelian = None + self._is_transitive = None + self._is_sym = None + self._is_alt = None + self._is_primitive = None + self._is_nilpotent = None + self._is_solvable = None + self._is_trivial = None + self._transitivity_degree = None + self._max_div = None + self._is_perfect = None + self._is_cyclic = None + self._is_dihedral = None + self._r = len(self._generators) + self._degree = self._generators[0].size + + # these attributes are assigned after running schreier_sims + self._base = [] + self._strong_gens = [] + self._strong_gens_slp = [] + self._basic_orbits = [] + self._transversals = [] + self._transversal_slp = [] + + # these attributes are assigned after running _random_pr_init + self._random_gens = [] + + # finite presentation of the group as an instance of `FpGroup` + self._fp_presentation = None + + def __getitem__(self, i): + return self._generators[i] + + def __contains__(self, i): + """Return ``True`` if *i* is contained in PermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> p = Permutation(1, 2, 3) + >>> Permutation(3) in PermutationGroup(p) + True + + """ + if not isinstance(i, Permutation): + raise TypeError("A PermutationGroup contains only Permutations as " + "elements, not elements of type %s" % type(i)) + return self.contains(i) + + def __len__(self): + return len(self._generators) + + def equals(self, other): + """Return ``True`` if PermutationGroup generated by elements in the + group are same i.e they represent the same PermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> p = Permutation(0, 1, 2, 3, 4, 5) + >>> G = PermutationGroup([p, p**2]) + >>> H = PermutationGroup([p**2, p]) + >>> G.generators == H.generators + False + >>> G.equals(H) + True + + """ + if not isinstance(other, PermutationGroup): + return False + + set_self_gens = set(self.generators) + set_other_gens = set(other.generators) + + # before reaching the general case there are also certain + # optimisation and obvious cases requiring less or no actual + # computation. + if set_self_gens == set_other_gens: + return True + + # in the most general case it will check that each generator of + # one group belongs to the other PermutationGroup and vice-versa + for gen1 in set_self_gens: + if not other.contains(gen1): + return False + for gen2 in set_other_gens: + if not self.contains(gen2): + return False + return True + + def __mul__(self, other): + """ + Return the direct product of two permutation groups as a permutation + group. + + Explanation + =========== + + This implementation realizes the direct product by shifting the index + set for the generators of the second group: so if we have ``G`` acting + on ``n1`` points and ``H`` acting on ``n2`` points, ``G*H`` acts on + ``n1 + n2`` points. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import CyclicGroup + >>> G = CyclicGroup(5) + >>> H = G*G + >>> H + PermutationGroup([ + (9)(0 1 2 3 4), + (5 6 7 8 9)]) + >>> H.order() + 25 + + """ + if isinstance(other, Permutation): + return Coset(other, self, dir='+') + gens1 = [perm._array_form for perm in self.generators] + gens2 = [perm._array_form for perm in other.generators] + n1 = self._degree + n2 = other._degree + start = list(range(n1)) + end = list(range(n1, n1 + n2)) + for i in range(len(gens2)): + gens2[i] = [x + n1 for x in gens2[i]] + gens2 = [start + gen for gen in gens2] + gens1 = [gen + end for gen in gens1] + together = gens1 + gens2 + gens = [_af_new(x) for x in together] + return PermutationGroup(gens) + + def _random_pr_init(self, r, n, _random_prec_n=None): + r"""Initialize random generators for the product replacement algorithm. + + Explanation + =========== + + The implementation uses a modification of the original product + replacement algorithm due to Leedham-Green, as described in [1], + pp. 69-71; also, see [2], pp. 27-29 for a detailed theoretical + analysis of the original product replacement algorithm, and [4]. + + The product replacement algorithm is used for producing random, + uniformly distributed elements of a group `G` with a set of generators + `S`. For the initialization ``_random_pr_init``, a list ``R`` of + `\max\{r, |S|\}` group generators is created as the attribute + ``G._random_gens``, repeating elements of `S` if necessary, and the + identity element of `G` is appended to ``R`` - we shall refer to this + last element as the accumulator. Then the function ``random_pr()`` + is called ``n`` times, randomizing the list ``R`` while preserving + the generation of `G` by ``R``. The function ``random_pr()`` itself + takes two random elements ``g, h`` among all elements of ``R`` but + the accumulator and replaces ``g`` with a randomly chosen element + from `\{gh, g(~h), hg, (~h)g\}`. Then the accumulator is multiplied + by whatever ``g`` was replaced by. The new value of the accumulator is + then returned by ``random_pr()``. + + The elements returned will eventually (for ``n`` large enough) become + uniformly distributed across `G` ([5]). For practical purposes however, + the values ``n = 50, r = 11`` are suggested in [1]. + + Notes + ===== + + THIS FUNCTION HAS SIDE EFFECTS: it changes the attribute + self._random_gens + + See Also + ======== + + random_pr + + """ + deg = self.degree + random_gens = [x._array_form for x in self.generators] + k = len(random_gens) + if k < r: + for i in range(k, r): + random_gens.append(random_gens[i - k]) + acc = list(range(deg)) + random_gens.append(acc) + self._random_gens = random_gens + + # handle randomized input for testing purposes + if _random_prec_n is None: + for i in range(n): + self.random_pr() + else: + for i in range(n): + self.random_pr(_random_prec=_random_prec_n[i]) + + def _union_find_merge(self, first, second, ranks, parents, not_rep): + """Merges two classes in a union-find data structure. + + Explanation + =========== + + Used in the implementation of Atkinson's algorithm as suggested in [1], + pp. 83-87. The class merging process uses union by rank as an + optimization. ([7]) + + Notes + ===== + + THIS FUNCTION HAS SIDE EFFECTS: the list of class representatives, + ``parents``, the list of class sizes, ``ranks``, and the list of + elements that are not representatives, ``not_rep``, are changed due to + class merging. + + See Also + ======== + + minimal_block, _union_find_rep + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of computational group theory" + + .. [7] https://algorithmist.com/wiki/Union_find + + """ + rep_first = self._union_find_rep(first, parents) + rep_second = self._union_find_rep(second, parents) + if rep_first != rep_second: + # union by rank + if ranks[rep_first] >= ranks[rep_second]: + new_1, new_2 = rep_first, rep_second + else: + new_1, new_2 = rep_second, rep_first + total_rank = ranks[new_1] + ranks[new_2] + if total_rank > self.max_div: + return -1 + parents[new_2] = new_1 + ranks[new_1] = total_rank + not_rep.append(new_2) + return 1 + return 0 + + def _union_find_rep(self, num, parents): + """Find representative of a class in a union-find data structure. + + Explanation + =========== + + Used in the implementation of Atkinson's algorithm as suggested in [1], + pp. 83-87. After the representative of the class to which ``num`` + belongs is found, path compression is performed as an optimization + ([7]). + + Notes + ===== + + THIS FUNCTION HAS SIDE EFFECTS: the list of class representatives, + ``parents``, is altered due to path compression. + + See Also + ======== + + minimal_block, _union_find_merge + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of computational group theory" + + .. [7] https://algorithmist.com/wiki/Union_find + + """ + rep, parent = num, parents[num] + while parent != rep: + rep = parent + parent = parents[rep] + # path compression + temp, parent = num, parents[num] + while parent != rep: + parents[temp] = rep + temp = parent + parent = parents[temp] + return rep + + @property + def base(self): + r"""Return a base from the Schreier-Sims algorithm. + + Explanation + =========== + + For a permutation group `G`, a base is a sequence of points + `B = (b_1, b_2, \dots, b_k)` such that no element of `G` apart + from the identity fixes all the points in `B`. The concepts of + a base and strong generating set and their applications are + discussed in depth in [1], pp. 87-89 and [2], pp. 55-57. + + An alternative way to think of `B` is that it gives the + indices of the stabilizer cosets that contain more than the + identity permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> G = PermutationGroup([Permutation(0, 1, 3)(2, 4)]) + >>> G.base + [0, 2] + + See Also + ======== + + strong_gens, basic_transversals, basic_orbits, basic_stabilizers + + """ + if self._base == []: + self.schreier_sims() + return self._base + + def baseswap(self, base, strong_gens, pos, randomized=False, + transversals=None, basic_orbits=None, strong_gens_distr=None): + r"""Swap two consecutive base points in base and strong generating set. + + Explanation + =========== + + If a base for a group `G` is given by `(b_1, b_2, \dots, b_k)`, this + function returns a base `(b_1, b_2, \dots, b_{i+1}, b_i, \dots, b_k)`, + where `i` is given by ``pos``, and a strong generating set relative + to that base. The original base and strong generating set are not + modified. + + The randomized version (default) is of Las Vegas type. + + Parameters + ========== + + base, strong_gens + The base and strong generating set. + pos + The position at which swapping is performed. + randomized + A switch between randomized and deterministic version. + transversals + The transversals for the basic orbits, if known. + basic_orbits + The basic orbits, if known. + strong_gens_distr + The strong generators distributed by basic stabilizers, if known. + + Returns + ======= + + (base, strong_gens) + ``base`` is the new base, and ``strong_gens`` is a generating set + relative to it. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics.testutil import _verify_bsgs + >>> from sympy.combinatorics.perm_groups import PermutationGroup + >>> S = SymmetricGroup(4) + >>> S.schreier_sims() + >>> S.base + [0, 1, 2] + >>> base, gens = S.baseswap(S.base, S.strong_gens, 1, randomized=False) + >>> base, gens + ([0, 2, 1], + [(0 1 2 3), (3)(0 1), (1 3 2), + (2 3), (1 3)]) + + check that base, gens is a BSGS + + >>> S1 = PermutationGroup(gens) + >>> _verify_bsgs(S1, base, gens) + True + + See Also + ======== + + schreier_sims + + Notes + ===== + + The deterministic version of the algorithm is discussed in + [1], pp. 102-103; the randomized version is discussed in [1], p.103, and + [2], p.98. It is of Las Vegas type. + Notice that [1] contains a mistake in the pseudocode and + discussion of BASESWAP: on line 3 of the pseudocode, + `|\beta_{i+1}^{\left\langle T\right\rangle}|` should be replaced by + `|\beta_{i}^{\left\langle T\right\rangle}|`, and the same for the + discussion of the algorithm. + + """ + # construct the basic orbits, generators for the stabilizer chain + # and transversal elements from whatever was provided + transversals, basic_orbits, strong_gens_distr = \ + _handle_precomputed_bsgs(base, strong_gens, transversals, + basic_orbits, strong_gens_distr) + base_len = len(base) + degree = self.degree + # size of orbit of base[pos] under the stabilizer we seek to insert + # in the stabilizer chain at position pos + 1 + size = len(basic_orbits[pos])*len(basic_orbits[pos + 1]) \ + //len(_orbit(degree, strong_gens_distr[pos], base[pos + 1])) + # initialize the wanted stabilizer by a subgroup + if pos + 2 > base_len - 1: + T = [] + else: + T = strong_gens_distr[pos + 2][:] + # randomized version + if randomized is True: + stab_pos = PermutationGroup(strong_gens_distr[pos]) + schreier_vector = stab_pos.schreier_vector(base[pos + 1]) + # add random elements of the stabilizer until they generate it + while len(_orbit(degree, T, base[pos])) != size: + new = stab_pos.random_stab(base[pos + 1], + schreier_vector=schreier_vector) + T.append(new) + # deterministic version + else: + Gamma = set(basic_orbits[pos]) + Gamma.remove(base[pos]) + if base[pos + 1] in Gamma: + Gamma.remove(base[pos + 1]) + # add elements of the stabilizer until they generate it by + # ruling out member of the basic orbit of base[pos] along the way + while len(_orbit(degree, T, base[pos])) != size: + gamma = next(iter(Gamma)) + x = transversals[pos][gamma] + temp = x._array_form.index(base[pos + 1]) # (~x)(base[pos + 1]) + if temp not in basic_orbits[pos + 1]: + Gamma = Gamma - _orbit(degree, T, gamma) + else: + y = transversals[pos + 1][temp] + el = rmul(x, y) + if el(base[pos]) not in _orbit(degree, T, base[pos]): + T.append(el) + Gamma = Gamma - _orbit(degree, T, base[pos]) + # build the new base and strong generating set + strong_gens_new_distr = strong_gens_distr[:] + strong_gens_new_distr[pos + 1] = T + base_new = base[:] + base_new[pos], base_new[pos + 1] = base_new[pos + 1], base_new[pos] + strong_gens_new = _strong_gens_from_distr(strong_gens_new_distr) + for gen in T: + if gen not in strong_gens_new: + strong_gens_new.append(gen) + return base_new, strong_gens_new + + @property + def basic_orbits(self): + r""" + Return the basic orbits relative to a base and strong generating set. + + Explanation + =========== + + If `(b_1, b_2, \dots, b_k)` is a base for a group `G`, and + `G^{(i)} = G_{b_1, b_2, \dots, b_{i-1}}` is the ``i``-th basic stabilizer + (so that `G^{(1)} = G`), the ``i``-th basic orbit relative to this base + is the orbit of `b_i` under `G^{(i)}`. See [1], pp. 87-89 for more + information. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> S = SymmetricGroup(4) + >>> S.basic_orbits + [[0, 1, 2, 3], [1, 2, 3], [2, 3]] + + See Also + ======== + + base, strong_gens, basic_transversals, basic_stabilizers + + """ + if self._basic_orbits == []: + self.schreier_sims() + return self._basic_orbits + + @property + def basic_stabilizers(self): + r""" + Return a chain of stabilizers relative to a base and strong generating + set. + + Explanation + =========== + + The ``i``-th basic stabilizer `G^{(i)}` relative to a base + `(b_1, b_2, \dots, b_k)` is `G_{b_1, b_2, \dots, b_{i-1}}`. For more + information, see [1], pp. 87-89. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AlternatingGroup + >>> A = AlternatingGroup(4) + >>> A.schreier_sims() + >>> A.base + [0, 1] + >>> for g in A.basic_stabilizers: + ... print(g) + ... + PermutationGroup([ + (3)(0 1 2), + (1 2 3)]) + PermutationGroup([ + (1 2 3)]) + + See Also + ======== + + base, strong_gens, basic_orbits, basic_transversals + + """ + + if self._transversals == []: + self.schreier_sims() + strong_gens = self._strong_gens + base = self._base + if not base: # e.g. if self is trivial + return [] + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + basic_stabilizers = [] + for gens in strong_gens_distr: + basic_stabilizers.append(PermutationGroup(gens)) + return basic_stabilizers + + @property + def basic_transversals(self): + """ + Return basic transversals relative to a base and strong generating set. + + Explanation + =========== + + The basic transversals are transversals of the basic orbits. They + are provided as a list of dictionaries, each dictionary having + keys - the elements of one of the basic orbits, and values - the + corresponding transversal elements. See [1], pp. 87-89 for more + information. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AlternatingGroup + >>> A = AlternatingGroup(4) + >>> A.basic_transversals + [{0: (3), 1: (3)(0 1 2), 2: (3)(0 2 1), 3: (0 3 1)}, {1: (3), 2: (1 2 3), 3: (1 3 2)}] + + See Also + ======== + + strong_gens, base, basic_orbits, basic_stabilizers + + """ + + if self._transversals == []: + self.schreier_sims() + return self._transversals + + def composition_series(self): + r""" + Return the composition series for a group as a list + of permutation groups. + + Explanation + =========== + + The composition series for a group `G` is defined as a + subnormal series `G = H_0 > H_1 > H_2 \ldots` A composition + series is a subnormal series such that each factor group + `H(i+1) / H(i)` is simple. + A subnormal series is a composition series only if it is of + maximum length. + + The algorithm works as follows: + Starting with the derived series the idea is to fill + the gap between `G = der[i]` and `H = der[i+1]` for each + `i` independently. Since, all subgroups of the abelian group + `G/H` are normal so, first step is to take the generators + `g` of `G` and add them to generators of `H` one by one. + + The factor groups formed are not simple in general. Each + group is obtained from the previous one by adding one + generator `g`, if the previous group is denoted by `H` + then the next group `K` is generated by `g` and `H`. + The factor group `K/H` is cyclic and it's order is + `K.order()//G.order()`. The series is then extended between + `K` and `H` by groups generated by powers of `g` and `H`. + The series formed is then prepended to the already existing + series. + + Examples + ======== + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics.named_groups import CyclicGroup + >>> S = SymmetricGroup(12) + >>> G = S.sylow_subgroup(2) + >>> C = G.composition_series() + >>> [H.order() for H in C] + [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1] + >>> G = S.sylow_subgroup(3) + >>> C = G.composition_series() + >>> [H.order() for H in C] + [243, 81, 27, 9, 3, 1] + >>> G = CyclicGroup(12) + >>> C = G.composition_series() + >>> [H.order() for H in C] + [12, 6, 3, 1] + + """ + der = self.derived_series() + if not all(g.is_identity for g in der[-1].generators): + raise NotImplementedError('Group should be solvable') + series = [] + + for i in range(len(der)-1): + H = der[i+1] + up_seg = [] + for g in der[i].generators: + K = PermutationGroup([g] + H.generators) + order = K.order() // H.order() + down_seg = [] + for p, e in factorint(order).items(): + for _ in range(e): + down_seg.append(PermutationGroup([g] + H.generators)) + g = g**p + up_seg = down_seg + up_seg + H = K + up_seg[0] = der[i] + series.extend(up_seg) + series.append(der[-1]) + return series + + def coset_transversal(self, H): + """Return a transversal of the right cosets of self by its subgroup H + using the second method described in [1], Subsection 4.6.7 + + """ + + if not H.is_subgroup(self): + raise ValueError("The argument must be a subgroup") + + if H.order() == 1: + return self.elements + + self._schreier_sims(base=H.base) # make G.base an extension of H.base + + base = self.base + base_ordering = _base_ordering(base, self.degree) + identity = Permutation(self.degree - 1) + + transversals = self.basic_transversals[:] + # transversals is a list of dictionaries. Get rid of the keys + # so that it is a list of lists and sort each list in + # the increasing order of base[l]^x + for l, t in enumerate(transversals): + transversals[l] = sorted(t.values(), + key = lambda x: base_ordering[base[l]^x]) + + orbits = H.basic_orbits + h_stabs = H.basic_stabilizers + g_stabs = self.basic_stabilizers + + indices = [x.order()//y.order() for x, y in zip(g_stabs, h_stabs)] + + # T^(l) should be a right transversal of H^(l) in G^(l) for + # 1<=l<=len(base). While H^(l) is the trivial group, T^(l) + # contains all the elements of G^(l) so we might just as well + # start with l = len(h_stabs)-1 + if len(g_stabs) > len(h_stabs): + T = g_stabs[len(h_stabs)].elements + else: + T = [identity] + l = len(h_stabs)-1 + t_len = len(T) + while l > -1: + T_next = [] + for u in transversals[l]: + if u == identity: + continue + b = base_ordering[base[l]^u] + for t in T: + p = t*u + if all(base_ordering[h^p] >= b for h in orbits[l]): + T_next.append(p) + if t_len + len(T_next) == indices[l]: + break + if t_len + len(T_next) == indices[l]: + break + T += T_next + t_len += len(T_next) + l -= 1 + T.remove(identity) + T = [identity] + T + return T + + def _coset_representative(self, g, H): + """Return the representative of Hg from the transversal that + would be computed by ``self.coset_transversal(H)``. + + """ + if H.order() == 1: + return g + # The base of self must be an extension of H.base. + if not(self.base[:len(H.base)] == H.base): + self._schreier_sims(base=H.base) + orbits = H.basic_orbits[:] + h_transversals = [list(_.values()) for _ in H.basic_transversals] + transversals = [list(_.values()) for _ in self.basic_transversals] + base = self.base + base_ordering = _base_ordering(base, self.degree) + def step(l, x): + gamma = min(orbits[l], key = lambda y: base_ordering[y^x]) + i = [base[l]^h for h in h_transversals[l]].index(gamma) + x = h_transversals[l][i]*x + if l < len(orbits)-1: + for u in transversals[l]: + if base[l]^u == base[l]^x: + break + x = step(l+1, x*u**-1)*u + return x + return step(0, g) + + def coset_table(self, H): + """Return the standardised (right) coset table of self in H as + a list of lists. + """ + # Maybe this should be made to return an instance of CosetTable + # from fp_groups.py but the class would need to be changed first + # to be compatible with PermutationGroups + + if not H.is_subgroup(self): + raise ValueError("The argument must be a subgroup") + T = self.coset_transversal(H) + n = len(T) + + A = list(chain.from_iterable((gen, gen**-1) + for gen in self.generators)) + + table = [] + for i in range(n): + row = [self._coset_representative(T[i]*x, H) for x in A] + row = [T.index(r) for r in row] + table.append(row) + + # standardize (this is the same as the algorithm used in coset_table) + # If CosetTable is made compatible with PermutationGroups, this + # should be replaced by table.standardize() + A = range(len(A)) + gamma = 1 + for alpha, a in product(range(n), A): + beta = table[alpha][a] + if beta >= gamma: + if beta > gamma: + for x in A: + z = table[gamma][x] + table[gamma][x] = table[beta][x] + table[beta][x] = z + for i in range(n): + if table[i][x] == beta: + table[i][x] = gamma + elif table[i][x] == gamma: + table[i][x] = beta + gamma += 1 + if gamma >= n-1: + return table + + def center(self): + r""" + Return the center of a permutation group. + + Explanation + =========== + + The center for a group `G` is defined as + `Z(G) = \{z\in G | \forall g\in G, zg = gz \}`, + the set of elements of `G` that commute with all elements of `G`. + It is equal to the centralizer of `G` inside `G`, and is naturally a + subgroup of `G` ([9]). + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> D = DihedralGroup(4) + >>> G = D.center() + >>> G.order() + 2 + + See Also + ======== + + centralizer + + Notes + ===== + + This is a naive implementation that is a straightforward application + of ``.centralizer()`` + + """ + if not self._center: + self._center = self.centralizer(self) + return self._center + + def centralizer(self, other): + r""" + Return the centralizer of a group/set/element. + + Explanation + =========== + + The centralizer of a set of permutations ``S`` inside + a group ``G`` is the set of elements of ``G`` that commute with all + elements of ``S``:: + + `C_G(S) = \{ g \in G | gs = sg \forall s \in S\}` ([10]) + + Usually, ``S`` is a subset of ``G``, but if ``G`` is a proper subgroup of + the full symmetric group, we allow for ``S`` to have elements outside + ``G``. + + It is naturally a subgroup of ``G``; the centralizer of a permutation + group is equal to the centralizer of any set of generators for that + group, since any element commuting with the generators commutes with + any product of the generators. + + Parameters + ========== + + other + a permutation group/list of permutations/single permutation + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... CyclicGroup) + >>> S = SymmetricGroup(6) + >>> C = CyclicGroup(6) + >>> H = S.centralizer(C) + >>> H.is_subgroup(C) + True + + See Also + ======== + + subgroup_search + + Notes + ===== + + The implementation is an application of ``.subgroup_search()`` with + tests using a specific base for the group ``G``. + + """ + if hasattr(other, 'generators'): + if other.is_trivial or self.is_trivial: + return self + degree = self.degree + identity = _af_new(list(range(degree))) + orbits = other.orbits() + num_orbits = len(orbits) + orbits.sort(key=lambda x: -len(x)) + long_base = [] + orbit_reps = [None]*num_orbits + orbit_reps_indices = [None]*num_orbits + orbit_descr = [None]*degree + for i in range(num_orbits): + orbit = list(orbits[i]) + orbit_reps[i] = orbit[0] + orbit_reps_indices[i] = len(long_base) + for point in orbit: + orbit_descr[point] = i + long_base = long_base + orbit + base, strong_gens = self.schreier_sims_incremental(base=long_base) + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + i = 0 + for i in range(len(base)): + if strong_gens_distr[i] == [identity]: + break + base = base[:i] + base_len = i + for j in range(num_orbits): + if base[base_len - 1] in orbits[j]: + break + rel_orbits = orbits[: j + 1] + num_rel_orbits = len(rel_orbits) + transversals = [None]*num_rel_orbits + for j in range(num_rel_orbits): + rep = orbit_reps[j] + transversals[j] = dict( + other.orbit_transversal(rep, pairs=True)) + trivial_test = lambda x: True + tests = [None]*base_len + for l in range(base_len): + if base[l] in orbit_reps: + tests[l] = trivial_test + else: + def test(computed_words, l=l): + g = computed_words[l] + rep_orb_index = orbit_descr[base[l]] + rep = orbit_reps[rep_orb_index] + im = g._array_form[base[l]] + im_rep = g._array_form[rep] + tr_el = transversals[rep_orb_index][base[l]] + # using the definition of transversal, + # base[l]^g = rep^(tr_el*g); + # if g belongs to the centralizer, then + # base[l]^g = (rep^g)^tr_el + return im == tr_el._array_form[im_rep] + tests[l] = test + + def prop(g): + return [rmul(g, gen) for gen in other.generators] == \ + [rmul(gen, g) for gen in other.generators] + return self.subgroup_search(prop, base=base, + strong_gens=strong_gens, tests=tests) + elif hasattr(other, '__getitem__'): + gens = list(other) + return self.centralizer(PermutationGroup(gens)) + elif hasattr(other, 'array_form'): + return self.centralizer(PermutationGroup([other])) + + def commutator(self, G, H): + """ + Return the commutator of two subgroups. + + Explanation + =========== + + For a permutation group ``K`` and subgroups ``G``, ``H``, the + commutator of ``G`` and ``H`` is defined as the group generated + by all the commutators `[g, h] = hgh^{-1}g^{-1}` for ``g`` in ``G`` and + ``h`` in ``H``. It is naturally a subgroup of ``K`` ([1], p.27). + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... AlternatingGroup) + >>> S = SymmetricGroup(5) + >>> A = AlternatingGroup(5) + >>> G = S.commutator(S, A) + >>> G.is_subgroup(A) + True + + See Also + ======== + + derived_subgroup + + Notes + ===== + + The commutator of two subgroups `H, G` is equal to the normal closure + of the commutators of all the generators, i.e. `hgh^{-1}g^{-1}` for `h` + a generator of `H` and `g` a generator of `G` ([1], p.28) + + """ + ggens = G.generators + hgens = H.generators + commutators = [] + for ggen in ggens: + for hgen in hgens: + commutator = rmul(hgen, ggen, ~hgen, ~ggen) + if commutator not in commutators: + commutators.append(commutator) + res = self.normal_closure(commutators) + return res + + def coset_factor(self, g, factor_index=False): + """Return ``G``'s (self's) coset factorization of ``g`` + + Explanation + =========== + + If ``g`` is an element of ``G`` then it can be written as the product + of permutations drawn from the Schreier-Sims coset decomposition, + + The permutations returned in ``f`` are those for which + the product gives ``g``: ``g = f[n]*...f[1]*f[0]`` where ``n = len(B)`` + and ``B = G.base``. f[i] is one of the permutations in + ``self._basic_orbits[i]``. + + If factor_index==True, + returns a tuple ``[b[0],..,b[n]]``, where ``b[i]`` + belongs to ``self._basic_orbits[i]`` + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation(0, 1, 3, 7, 6, 4)(2, 5) + >>> b = Permutation(0, 1, 3, 2)(4, 5, 7, 6) + >>> G = PermutationGroup([a, b]) + + Define g: + + >>> g = Permutation(7)(1, 2, 4)(3, 6, 5) + + Confirm that it is an element of G: + + >>> G.contains(g) + True + + Thus, it can be written as a product of factors (up to + 3) drawn from u. See below that a factor from u1 and u2 + and the Identity permutation have been used: + + >>> f = G.coset_factor(g) + >>> f[2]*f[1]*f[0] == g + True + >>> f1 = G.coset_factor(g, True); f1 + [0, 4, 4] + >>> tr = G.basic_transversals + >>> f[0] == tr[0][f1[0]] + True + + If g is not an element of G then [] is returned: + + >>> c = Permutation(5, 6, 7) + >>> G.coset_factor(c) + [] + + See Also + ======== + + sympy.combinatorics.util._strip + + """ + if isinstance(g, (Cycle, Permutation)): + g = g.list() + if len(g) != self._degree: + # this could either adjust the size or return [] immediately + # but we don't choose between the two and just signal a possible + # error + raise ValueError('g should be the same size as permutations of G') + I = list(range(self._degree)) + basic_orbits = self.basic_orbits + transversals = self._transversals + factors = [] + base = self.base + h = g + for i in range(len(base)): + beta = h[base[i]] + if beta == base[i]: + factors.append(beta) + continue + if beta not in basic_orbits[i]: + return [] + u = transversals[i][beta]._array_form + h = _af_rmul(_af_invert(u), h) + factors.append(beta) + if h != I: + return [] + if factor_index: + return factors + tr = self.basic_transversals + factors = [tr[i][factors[i]] for i in range(len(base))] + return factors + + def generator_product(self, g, original=False): + r''' + Return a list of strong generators `[s1, \dots, sn]` + s.t `g = sn \times \dots \times s1`. If ``original=True``, make the + list contain only the original group generators + + ''' + product = [] + if g.is_identity: + return [] + if g in self.strong_gens: + if not original or g in self.generators: + return [g] + else: + slp = self._strong_gens_slp[g] + for s in slp: + product.extend(self.generator_product(s, original=True)) + return product + elif g**-1 in self.strong_gens: + g = g**-1 + if not original or g in self.generators: + return [g**-1] + else: + slp = self._strong_gens_slp[g] + for s in slp: + product.extend(self.generator_product(s, original=True)) + l = len(product) + product = [product[l-i-1]**-1 for i in range(l)] + return product + + f = self.coset_factor(g, True) + for i, j in enumerate(f): + slp = self._transversal_slp[i][j] + for s in slp: + if not original: + product.append(self.strong_gens[s]) + else: + s = self.strong_gens[s] + product.extend(self.generator_product(s, original=True)) + return product + + def coset_rank(self, g): + """rank using Schreier-Sims representation. + + Explanation + =========== + + The coset rank of ``g`` is the ordering number in which + it appears in the lexicographic listing according to the + coset decomposition + + The ordering is the same as in G.generate(method='coset'). + If ``g`` does not belong to the group it returns None. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation(0, 1, 3, 7, 6, 4)(2, 5) + >>> b = Permutation(0, 1, 3, 2)(4, 5, 7, 6) + >>> G = PermutationGroup([a, b]) + >>> c = Permutation(7)(2, 4)(3, 5) + >>> G.coset_rank(c) + 16 + >>> G.coset_unrank(16) + (7)(2 4)(3 5) + + See Also + ======== + + coset_factor + + """ + factors = self.coset_factor(g, True) + if not factors: + return None + rank = 0 + b = 1 + transversals = self._transversals + base = self._base + basic_orbits = self._basic_orbits + for i in range(len(base)): + k = factors[i] + j = basic_orbits[i].index(k) + rank += b*j + b = b*len(transversals[i]) + return rank + + def coset_unrank(self, rank, af=False): + """unrank using Schreier-Sims representation + + coset_unrank is the inverse operation of coset_rank + if 0 <= rank < order; otherwise it returns None. + + """ + if rank < 0 or rank >= self.order(): + return None + base = self.base + transversals = self.basic_transversals + basic_orbits = self.basic_orbits + m = len(base) + v = [0]*m + for i in range(m): + rank, c = divmod(rank, len(transversals[i])) + v[i] = basic_orbits[i][c] + a = [transversals[i][v[i]]._array_form for i in range(m)] + h = _af_rmuln(*a) + if af: + return h + else: + return _af_new(h) + + @property + def degree(self): + """Returns the size of the permutations in the group. + + Explanation + =========== + + The number of permutations comprising the group is given by + ``len(group)``; the number of permutations that can be generated + by the group is given by ``group.order()``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a]) + >>> G.degree + 3 + >>> len(G) + 1 + >>> G.order() + 2 + >>> list(G.generate()) + [(2), (2)(0 1)] + + See Also + ======== + + order + """ + return self._degree + + @property + def identity(self): + ''' + Return the identity element of the permutation group. + + ''' + return _af_new(list(range(self.degree))) + + @property + def elements(self): + """Returns all the elements of the permutation group as a list + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> p = PermutationGroup(Permutation(1, 3), Permutation(1, 2)) + >>> p.elements + [(3), (3)(1 2), (1 3), (2 3), (1 2 3), (1 3 2)] + + """ + if not self._elements: + self._elements = list(self.generate()) + + return self._elements + + def derived_series(self): + r"""Return the derived series for the group. + + Explanation + =========== + + The derived series for a group `G` is defined as + `G = G_0 > G_1 > G_2 > \ldots` where `G_i = [G_{i-1}, G_{i-1}]`, + i.e. `G_i` is the derived subgroup of `G_{i-1}`, for + `i\in\mathbb{N}`. When we have `G_k = G_{k-1}` for some + `k\in\mathbb{N}`, the series terminates. + + Returns + ======= + + A list of permutation groups containing the members of the derived + series in the order `G = G_0, G_1, G_2, \ldots`. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... AlternatingGroup, DihedralGroup) + >>> A = AlternatingGroup(5) + >>> len(A.derived_series()) + 1 + >>> S = SymmetricGroup(4) + >>> len(S.derived_series()) + 4 + >>> S.derived_series()[1].is_subgroup(AlternatingGroup(4)) + True + >>> S.derived_series()[2].is_subgroup(DihedralGroup(2)) + True + + See Also + ======== + + derived_subgroup + + """ + res = [self] + current = self + nxt = self.derived_subgroup() + while not current.is_subgroup(nxt): + res.append(nxt) + current = nxt + nxt = nxt.derived_subgroup() + return res + + def derived_subgroup(self): + r"""Compute the derived subgroup. + + Explanation + =========== + + The derived subgroup, or commutator subgroup is the subgroup generated + by all commutators `[g, h] = hgh^{-1}g^{-1}` for `g, h\in G` ; it is + equal to the normal closure of the set of commutators of the generators + ([1], p.28, [11]). + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([1, 0, 2, 4, 3]) + >>> b = Permutation([0, 1, 3, 2, 4]) + >>> G = PermutationGroup([a, b]) + >>> C = G.derived_subgroup() + >>> list(C.generate(af=True)) + [[0, 1, 2, 3, 4], [0, 1, 3, 4, 2], [0, 1, 4, 2, 3]] + + See Also + ======== + + derived_series + + """ + r = self._r + gens = [p._array_form for p in self.generators] + set_commutators = set() + degree = self._degree + rng = list(range(degree)) + for i in range(r): + for j in range(r): + p1 = gens[i] + p2 = gens[j] + c = list(range(degree)) + for k in rng: + c[p2[p1[k]]] = p1[p2[k]] + ct = tuple(c) + if ct not in set_commutators: + set_commutators.add(ct) + cms = [_af_new(p) for p in set_commutators] + G2 = self.normal_closure(cms) + return G2 + + def generate(self, method="coset", af=False): + """Return iterator to generate the elements of the group. + + Explanation + =========== + + Iteration is done with one of these methods:: + + method='coset' using the Schreier-Sims coset representation + method='dimino' using the Dimino method + + If ``af = True`` it yields the array form of the permutations + + Examples + ======== + + >>> from sympy.combinatorics import PermutationGroup + >>> from sympy.combinatorics.polyhedron import tetrahedron + + The permutation group given in the tetrahedron object is also + true groups: + + >>> G = tetrahedron.pgroup + >>> G.is_group + True + + Also the group generated by the permutations in the tetrahedron + pgroup -- even the first two -- is a proper group: + + >>> H = PermutationGroup(G[0], G[1]) + >>> J = PermutationGroup(list(H.generate())); J + PermutationGroup([ + (0 1)(2 3), + (1 2 3), + (1 3 2), + (0 3 1), + (0 2 3), + (0 3)(1 2), + (0 1 3), + (3)(0 2 1), + (0 3 2), + (3)(0 1 2), + (0 2)(1 3)]) + >>> _.is_group + True + """ + if method == "coset": + return self.generate_schreier_sims(af) + elif method == "dimino": + return self.generate_dimino(af) + else: + raise NotImplementedError('No generation defined for %s' % method) + + def generate_dimino(self, af=False): + """Yield group elements using Dimino's algorithm. + + If ``af == True`` it yields the array form of the permutations. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1, 3]) + >>> b = Permutation([0, 2, 3, 1]) + >>> g = PermutationGroup([a, b]) + >>> list(g.generate_dimino(af=True)) + [[0, 1, 2, 3], [0, 2, 1, 3], [0, 2, 3, 1], + [0, 1, 3, 2], [0, 3, 2, 1], [0, 3, 1, 2]] + + References + ========== + + .. [1] The Implementation of Various Algorithms for Permutation Groups in + the Computer Algebra System: AXIOM, N.J. Doye, M.Sc. Thesis + + """ + idn = list(range(self.degree)) + order = 0 + element_list = [idn] + set_element_list = {tuple(idn)} + if af: + yield idn + else: + yield _af_new(idn) + gens = [p._array_form for p in self.generators] + + for i in range(len(gens)): + # D elements of the subgroup G_i generated by gens[:i] + D = element_list.copy() + N = [idn] + while N: + A = N + N = [] + for a in A: + for g in gens[:i + 1]: + ag = _af_rmul(a, g) + if tuple(ag) not in set_element_list: + # produce G_i*g + for d in D: + order += 1 + ap = _af_rmul(d, ag) + if af: + yield ap + else: + p = _af_new(ap) + yield p + element_list.append(ap) + set_element_list.add(tuple(ap)) + N.append(ap) + self._order = len(element_list) + + def generate_schreier_sims(self, af=False): + """Yield group elements using the Schreier-Sims representation + in coset_rank order + + If ``af = True`` it yields the array form of the permutations + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1, 3]) + >>> b = Permutation([0, 2, 3, 1]) + >>> g = PermutationGroup([a, b]) + >>> list(g.generate_schreier_sims(af=True)) + [[0, 1, 2, 3], [0, 2, 1, 3], [0, 3, 2, 1], + [0, 1, 3, 2], [0, 2, 3, 1], [0, 3, 1, 2]] + """ + + n = self._degree + u = self.basic_transversals + basic_orbits = self._basic_orbits + if len(u) == 0: + for x in self.generators: + if af: + yield x._array_form + else: + yield x + return + if len(u) == 1: + for i in basic_orbits[0]: + if af: + yield u[0][i]._array_form + else: + yield u[0][i] + return + + u = list(reversed(u)) + basic_orbits = basic_orbits[::-1] + # stg stack of group elements + stg = [list(range(n))] + posmax = [len(x) for x in u] + n1 = len(posmax) - 1 + pos = [0]*n1 + h = 0 + while 1: + # backtrack when finished iterating over coset + if pos[h] >= posmax[h]: + if h == 0: + return + pos[h] = 0 + h -= 1 + stg.pop() + continue + p = _af_rmul(u[h][basic_orbits[h][pos[h]]]._array_form, stg[-1]) + pos[h] += 1 + stg.append(p) + h += 1 + if h == n1: + if af: + for i in basic_orbits[-1]: + p = _af_rmul(u[-1][i]._array_form, stg[-1]) + yield p + else: + for i in basic_orbits[-1]: + p = _af_rmul(u[-1][i]._array_form, stg[-1]) + p1 = _af_new(p) + yield p1 + stg.pop() + h -= 1 + + @property + def generators(self): + """Returns the generators of the group. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.generators + [(1 2), (2)(0 1)] + + """ + return self._generators + + def contains(self, g, strict=True): + """Test if permutation ``g`` belong to self, ``G``. + + Explanation + =========== + + If ``g`` is an element of ``G`` it can be written as a product + of factors drawn from the cosets of ``G``'s stabilizers. To see + if ``g`` is one of the actual generators defining the group use + ``G.has(g)``. + + If ``strict`` is not ``True``, ``g`` will be resized, if necessary, + to match the size of permutations in ``self``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + + >>> a = Permutation(1, 2) + >>> b = Permutation(2, 3, 1) + >>> G = PermutationGroup(a, b, degree=5) + >>> G.contains(G[0]) # trivial check + True + >>> elem = Permutation([[2, 3]], size=5) + >>> G.contains(elem) + True + >>> G.contains(Permutation(4)(0, 1, 2, 3)) + False + + If strict is False, a permutation will be resized, if + necessary: + + >>> H = PermutationGroup(Permutation(5)) + >>> H.contains(Permutation(3)) + False + >>> H.contains(Permutation(3), strict=False) + True + + To test if a given permutation is present in the group: + + >>> elem in G.generators + False + >>> G.has(elem) + False + + See Also + ======== + + coset_factor, sympy.core.basic.Basic.has, __contains__ + + """ + if not isinstance(g, Permutation): + return False + if g.size != self.degree: + if strict: + return False + g = Permutation(g, size=self.degree) + if g in self.generators: + return True + return bool(self.coset_factor(g.array_form, True)) + + @property + def is_perfect(self): + """Return ``True`` if the group is perfect. + A group is perfect if it equals to its derived subgroup. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation(1,2,3)(4,5) + >>> b = Permutation(1,2,3,4,5) + >>> G = PermutationGroup([a, b]) + >>> G.is_perfect + False + + """ + if self._is_perfect is None: + self._is_perfect = self.equals(self.derived_subgroup()) + return self._is_perfect + + @property + def is_abelian(self): + """Test if the group is Abelian. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.is_abelian + False + >>> a = Permutation([0, 2, 1]) + >>> G = PermutationGroup([a]) + >>> G.is_abelian + True + + """ + if self._is_abelian is not None: + return self._is_abelian + + self._is_abelian = True + gens = [p._array_form for p in self.generators] + for x in gens: + for y in gens: + if y <= x: + continue + if not _af_commutes_with(x, y): + self._is_abelian = False + return False + return True + + def abelian_invariants(self): + """ + Returns the abelian invariants for the given group. + Let ``G`` be a nontrivial finite abelian group. Then G is isomorphic to + the direct product of finitely many nontrivial cyclic groups of + prime-power order. + + Explanation + =========== + + The prime-powers that occur as the orders of the factors are uniquely + determined by G. More precisely, the primes that occur in the orders of the + factors in any such decomposition of ``G`` are exactly the primes that divide + ``|G|`` and for any such prime ``p``, if the orders of the factors that are + p-groups in one such decomposition of ``G`` are ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``, + then the orders of the factors that are p-groups in any such decomposition of ``G`` + are ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``. + + The uniquely determined integers ``p^{t_1} >= p^{t_2} >= ... p^{t_r}``, taken + for all primes that divide ``|G|`` are called the invariants of the nontrivial + group ``G`` as suggested in ([14], p. 542). + + Notes + ===== + + We adopt the convention that the invariants of a trivial group are []. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.abelian_invariants() + [2] + >>> from sympy.combinatorics import CyclicGroup + >>> G = CyclicGroup(7) + >>> G.abelian_invariants() + [7] + + """ + if self.is_trivial: + return [] + gns = self.generators + inv = [] + G = self + H = G.derived_subgroup() + Hgens = H.generators + for p in primefactors(G.order()): + ranks = [] + while True: + pows = [] + for g in gns: + elm = g**p + if not H.contains(elm): + pows.append(elm) + K = PermutationGroup(Hgens + pows) if pows else H + r = G.order()//K.order() + G = K + gns = pows + if r == 1: + break + ranks.append(multiplicity(p, r)) + + if ranks: + pows = [1]*ranks[0] + for i in ranks: + for j in range(i): + pows[j] = pows[j]*p + inv.extend(pows) + inv.sort() + return inv + + def is_elementary(self, p): + """Return ``True`` if the group is elementary abelian. An elementary + abelian group is a finite abelian group, where every nontrivial + element has order `p`, where `p` is a prime. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1]) + >>> G = PermutationGroup([a]) + >>> G.is_elementary(2) + True + >>> a = Permutation([0, 2, 1, 3]) + >>> b = Permutation([3, 1, 2, 0]) + >>> G = PermutationGroup([a, b]) + >>> G.is_elementary(2) + True + >>> G.is_elementary(3) + False + + """ + return self.is_abelian and all(g.order() == p for g in self.generators) + + def _eval_is_alt_sym_naive(self, only_sym=False, only_alt=False): + """A naive test using the group order.""" + if only_sym and only_alt: + raise ValueError( + "Both {} and {} cannot be set to True" + .format(only_sym, only_alt)) + + n = self.degree + sym_order = _factorial(n) + order = self.order() + + if order == sym_order: + self._is_sym = True + self._is_alt = False + return not only_alt + + if 2*order == sym_order: + self._is_sym = False + self._is_alt = True + return not only_sym + + return False + + def _eval_is_alt_sym_monte_carlo(self, eps=0.05, perms=None): + """A test using monte-carlo algorithm. + + Parameters + ========== + + eps : float, optional + The criterion for the incorrect ``False`` return. + + perms : list[Permutation], optional + If explicitly given, it tests over the given candidates + for testing. + + If ``None``, it randomly computes ``N_eps`` and chooses + ``N_eps`` sample of the permutation from the group. + + See Also + ======== + + _check_cycles_alt_sym + """ + if perms is None: + n = self.degree + if n < 17: + c_n = 0.34 + else: + c_n = 0.57 + d_n = (c_n*log(2))/log(n) + N_eps = int(-log(eps)/d_n) + + perms = (self.random_pr() for i in range(N_eps)) + return self._eval_is_alt_sym_monte_carlo(perms=perms) + + for perm in perms: + if _check_cycles_alt_sym(perm): + return True + return False + + def is_alt_sym(self, eps=0.05, _random_prec=None): + r"""Monte Carlo test for the symmetric/alternating group for degrees + >= 8. + + Explanation + =========== + + More specifically, it is one-sided Monte Carlo with the + answer True (i.e., G is symmetric/alternating) guaranteed to be + correct, and the answer False being incorrect with probability eps. + + For degree < 8, the order of the group is checked so the test + is deterministic. + + Notes + ===== + + The algorithm itself uses some nontrivial results from group theory and + number theory: + 1) If a transitive group ``G`` of degree ``n`` contains an element + with a cycle of length ``n/2 < p < n-2`` for ``p`` a prime, ``G`` is the + symmetric or alternating group ([1], pp. 81-82) + 2) The proportion of elements in the symmetric/alternating group having + the property described in 1) is approximately `\log(2)/\log(n)` + ([1], p.82; [2], pp. 226-227). + The helper function ``_check_cycles_alt_sym`` is used to + go over the cycles in a permutation and look for ones satisfying 1). + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> D = DihedralGroup(10) + >>> D.is_alt_sym() + False + + See Also + ======== + + _check_cycles_alt_sym + + """ + if _random_prec is not None: + N_eps = _random_prec['N_eps'] + perms= (_random_prec[i] for i in range(N_eps)) + return self._eval_is_alt_sym_monte_carlo(perms=perms) + + if self._is_sym or self._is_alt: + return True + if self._is_sym is False and self._is_alt is False: + return False + + n = self.degree + if n < 8: + return self._eval_is_alt_sym_naive() + elif self.is_transitive(): + return self._eval_is_alt_sym_monte_carlo(eps=eps) + + self._is_sym, self._is_alt = False, False + return False + + @property + def is_nilpotent(self): + """Test if the group is nilpotent. + + Explanation + =========== + + A group `G` is nilpotent if it has a central series of finite length. + Alternatively, `G` is nilpotent if its lower central series terminates + with the trivial group. Every nilpotent group is also solvable + ([1], p.29, [12]). + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... CyclicGroup) + >>> C = CyclicGroup(6) + >>> C.is_nilpotent + True + >>> S = SymmetricGroup(5) + >>> S.is_nilpotent + False + + See Also + ======== + + lower_central_series, is_solvable + + """ + if self._is_nilpotent is None: + lcs = self.lower_central_series() + terminator = lcs[len(lcs) - 1] + gens = terminator.generators + degree = self.degree + identity = _af_new(list(range(degree))) + if all(g == identity for g in gens): + self._is_solvable = True + self._is_nilpotent = True + return True + else: + self._is_nilpotent = False + return False + else: + return self._is_nilpotent + + def is_normal(self, gr, strict=True): + """Test if ``G=self`` is a normal subgroup of ``gr``. + + Explanation + =========== + + G is normal in gr if + for each g2 in G, g1 in gr, ``g = g1*g2*g1**-1`` belongs to G + It is sufficient to check this for each g1 in gr.generators and + g2 in G.generators. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([1, 2, 0]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G1 = PermutationGroup([a, Permutation([2, 0, 1])]) + >>> G1.is_normal(G) + True + + """ + if not self.is_subgroup(gr, strict=strict): + return False + d_self = self.degree + d_gr = gr.degree + if self.is_trivial and (d_self == d_gr or not strict): + return True + if self._is_abelian: + return True + new_self = self.copy() + if not strict and d_self != d_gr: + if d_self < d_gr: + new_self = PermGroup(new_self.generators + [Permutation(d_gr - 1)]) + else: + gr = PermGroup(gr.generators + [Permutation(d_self - 1)]) + gens2 = [p._array_form for p in new_self.generators] + gens1 = [p._array_form for p in gr.generators] + for g1 in gens1: + for g2 in gens2: + p = _af_rmuln(g1, g2, _af_invert(g1)) + if not new_self.coset_factor(p, True): + return False + return True + + def is_primitive(self, randomized=True): + r"""Test if a group is primitive. + + Explanation + =========== + + A permutation group ``G`` acting on a set ``S`` is called primitive if + ``S`` contains no nontrivial block under the action of ``G`` + (a block is nontrivial if its cardinality is more than ``1``). + + Notes + ===== + + The algorithm is described in [1], p.83, and uses the function + minimal_block to search for blocks of the form `\{0, k\}` for ``k`` + ranging over representatives for the orbits of `G_0`, the stabilizer of + ``0``. This algorithm has complexity `O(n^2)` where ``n`` is the degree + of the group, and will perform badly if `G_0` is small. + + There are two implementations offered: one finds `G_0` + deterministically using the function ``stabilizer``, and the other + (default) produces random elements of `G_0` using ``random_stab``, + hoping that they generate a subgroup of `G_0` with not too many more + orbits than `G_0` (this is suggested in [1], p.83). Behavior is changed + by the ``randomized`` flag. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> D = DihedralGroup(10) + >>> D.is_primitive() + False + + See Also + ======== + + minimal_block, random_stab + + """ + if self._is_primitive is not None: + return self._is_primitive + + if self.is_transitive() is False: + return False + + if randomized: + random_stab_gens = [] + v = self.schreier_vector(0) + for _ in range(len(self)): + random_stab_gens.append(self.random_stab(0, v)) + stab = PermutationGroup(random_stab_gens) + else: + stab = self.stabilizer(0) + orbits = stab.orbits() + for orb in orbits: + x = orb.pop() + if x != 0 and any(e != 0 for e in self.minimal_block([0, x])): + self._is_primitive = False + return False + self._is_primitive = True + return True + + def minimal_blocks(self, randomized=True): + ''' + For a transitive group, return the list of all minimal + block systems. If a group is intransitive, return `False`. + + Examples + ======== + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> DihedralGroup(6).minimal_blocks() + [[0, 1, 0, 1, 0, 1], [0, 1, 2, 0, 1, 2]] + >>> G = PermutationGroup(Permutation(1,2,5)) + >>> G.minimal_blocks() + False + + See Also + ======== + + minimal_block, is_transitive, is_primitive + + ''' + def _number_blocks(blocks): + # number the blocks of a block system + # in order and return the number of + # blocks and the tuple with the + # reordering + n = len(blocks) + appeared = {} + m = 0 + b = [None]*n + for i in range(n): + if blocks[i] not in appeared: + appeared[blocks[i]] = m + b[i] = m + m += 1 + else: + b[i] = appeared[blocks[i]] + return tuple(b), m + + if not self.is_transitive(): + return False + blocks = [] + num_blocks = [] + rep_blocks = [] + if randomized: + random_stab_gens = [] + v = self.schreier_vector(0) + for i in range(len(self)): + random_stab_gens.append(self.random_stab(0, v)) + stab = PermutationGroup(random_stab_gens) + else: + stab = self.stabilizer(0) + orbits = stab.orbits() + for orb in orbits: + x = orb.pop() + if x != 0: + block = self.minimal_block([0, x]) + num_block, _ = _number_blocks(block) + # a representative block (containing 0) + rep = {j for j in range(self.degree) if num_block[j] == 0} + # check if the system is minimal with + # respect to the already discovere ones + minimal = True + blocks_remove_mask = [False] * len(blocks) + for i, r in enumerate(rep_blocks): + if len(r) > len(rep) and rep.issubset(r): + # i-th block system is not minimal + blocks_remove_mask[i] = True + elif len(r) < len(rep) and r.issubset(rep): + # the system being checked is not minimal + minimal = False + break + # remove non-minimal representative blocks + blocks = [b for i, b in enumerate(blocks) if not blocks_remove_mask[i]] + num_blocks = [n for i, n in enumerate(num_blocks) if not blocks_remove_mask[i]] + rep_blocks = [r for i, r in enumerate(rep_blocks) if not blocks_remove_mask[i]] + + if minimal and num_block not in num_blocks: + blocks.append(block) + num_blocks.append(num_block) + rep_blocks.append(rep) + return blocks + + @property + def is_solvable(self): + """Test if the group is solvable. + + ``G`` is solvable if its derived series terminates with the trivial + group ([1], p.29). + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> S = SymmetricGroup(3) + >>> S.is_solvable + True + + See Also + ======== + + is_nilpotent, derived_series + + """ + if self._is_solvable is None: + if self.order() % 2 != 0: + return True + ds = self.derived_series() + terminator = ds[len(ds) - 1] + gens = terminator.generators + degree = self.degree + identity = _af_new(list(range(degree))) + if all(g == identity for g in gens): + self._is_solvable = True + return True + else: + self._is_solvable = False + return False + else: + return self._is_solvable + + def is_subgroup(self, G, strict=True): + """Return ``True`` if all elements of ``self`` belong to ``G``. + + If ``strict`` is ``False`` then if ``self``'s degree is smaller + than ``G``'s, the elements will be resized to have the same degree. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> from sympy.combinatorics import SymmetricGroup, CyclicGroup + + Testing is strict by default: the degree of each group must be the + same: + + >>> p = Permutation(0, 1, 2, 3, 4, 5) + >>> G1 = PermutationGroup([Permutation(0, 1, 2), Permutation(0, 1)]) + >>> G2 = PermutationGroup([Permutation(0, 2), Permutation(0, 1, 2)]) + >>> G3 = PermutationGroup([p, p**2]) + >>> assert G1.order() == G2.order() == G3.order() == 6 + >>> G1.is_subgroup(G2) + True + >>> G1.is_subgroup(G3) + False + >>> G3.is_subgroup(PermutationGroup(G3[1])) + False + >>> G3.is_subgroup(PermutationGroup(G3[0])) + True + + To ignore the size, set ``strict`` to ``False``: + + >>> S3 = SymmetricGroup(3) + >>> S5 = SymmetricGroup(5) + >>> S3.is_subgroup(S5, strict=False) + True + >>> C7 = CyclicGroup(7) + >>> G = S5*C7 + >>> S5.is_subgroup(G, False) + True + >>> C7.is_subgroup(G, 0) + False + + """ + if isinstance(G, SymmetricPermutationGroup): + if self.degree != G.degree: + return False + return True + if not isinstance(G, PermutationGroup): + return False + if self == G or self.generators[0]==Permutation(): + return True + if G.order() % self.order() != 0: + return False + if self.degree == G.degree or \ + (self.degree < G.degree and not strict): + gens = self.generators + else: + return False + return all(G.contains(g, strict=strict) for g in gens) + + @property + def is_polycyclic(self): + """Return ``True`` if a group is polycyclic. A group is polycyclic if + it has a subnormal series with cyclic factors. For finite groups, + this is the same as if the group is solvable. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1, 3]) + >>> b = Permutation([2, 0, 1, 3]) + >>> G = PermutationGroup([a, b]) + >>> G.is_polycyclic + True + + """ + return self.is_solvable + + def is_transitive(self, strict=True): + """Test if the group is transitive. + + Explanation + =========== + + A group is transitive if it has a single orbit. + + If ``strict`` is ``False`` the group is transitive if it has + a single orbit of length different from 1. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1, 3]) + >>> b = Permutation([2, 0, 1, 3]) + >>> G1 = PermutationGroup([a, b]) + >>> G1.is_transitive() + False + >>> G1.is_transitive(strict=False) + True + >>> c = Permutation([2, 3, 0, 1]) + >>> G2 = PermutationGroup([a, c]) + >>> G2.is_transitive() + True + >>> d = Permutation([1, 0, 2, 3]) + >>> e = Permutation([0, 1, 3, 2]) + >>> G3 = PermutationGroup([d, e]) + >>> G3.is_transitive() or G3.is_transitive(strict=False) + False + + """ + if self._is_transitive: # strict or not, if True then True + return self._is_transitive + if strict: + if self._is_transitive is not None: # we only store strict=True + return self._is_transitive + + ans = len(self.orbit(0)) == self.degree + self._is_transitive = ans + return ans + + got_orb = False + for x in self.orbits(): + if len(x) > 1: + if got_orb: + return False + got_orb = True + return got_orb + + @property + def is_trivial(self): + """Test if the group is the trivial group. + + This is true if the group contains only the identity permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> G = PermutationGroup([Permutation([0, 1, 2])]) + >>> G.is_trivial + True + + """ + if self._is_trivial is None: + self._is_trivial = len(self) == 1 and self[0].is_Identity + return self._is_trivial + + def lower_central_series(self): + r"""Return the lower central series for the group. + + The lower central series for a group `G` is the series + `G = G_0 > G_1 > G_2 > \ldots` where + `G_k = [G, G_{k-1}]`, i.e. every term after the first is equal to the + commutator of `G` and the previous term in `G1` ([1], p.29). + + Returns + ======= + + A list of permutation groups in the order `G = G_0, G_1, G_2, \ldots` + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (AlternatingGroup, + ... DihedralGroup) + >>> A = AlternatingGroup(4) + >>> len(A.lower_central_series()) + 2 + >>> A.lower_central_series()[1].is_subgroup(DihedralGroup(2)) + True + + See Also + ======== + + commutator, derived_series + + """ + res = [self] + current = self + nxt = self.commutator(self, current) + while not current.is_subgroup(nxt): + res.append(nxt) + current = nxt + nxt = self.commutator(self, current) + return res + + @property + def max_div(self): + """Maximum proper divisor of the degree of a permutation group. + + Explanation + =========== + + Obviously, this is the degree divided by its minimal proper divisor + (larger than ``1``, if one exists). As it is guaranteed to be prime, + the ``sieve`` from ``sympy.ntheory`` is used. + This function is also used as an optimization tool for the functions + ``minimal_block`` and ``_union_find_merge``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> G = PermutationGroup([Permutation([0, 2, 1, 3])]) + >>> G.max_div + 2 + + See Also + ======== + + minimal_block, _union_find_merge + + """ + if self._max_div is not None: + return self._max_div + n = self.degree + if n == 1: + return 1 + for x in sieve: + if n % x == 0: + d = n//x + self._max_div = d + return d + + def minimal_block(self, points): + r"""For a transitive group, finds the block system generated by + ``points``. + + Explanation + =========== + + If a group ``G`` acts on a set ``S``, a nonempty subset ``B`` of ``S`` + is called a block under the action of ``G`` if for all ``g`` in ``G`` + we have ``gB = B`` (``g`` fixes ``B``) or ``gB`` and ``B`` have no + common points (``g`` moves ``B`` entirely). ([1], p.23; [6]). + + The distinct translates ``gB`` of a block ``B`` for ``g`` in ``G`` + partition the set ``S`` and this set of translates is known as a block + system. Moreover, we obviously have that all blocks in the partition + have the same size, hence the block size divides ``|S|`` ([1], p.23). + A ``G``-congruence is an equivalence relation ``~`` on the set ``S`` + such that ``a ~ b`` implies ``g(a) ~ g(b)`` for all ``g`` in ``G``. + For a transitive group, the equivalence classes of a ``G``-congruence + and the blocks of a block system are the same thing ([1], p.23). + + The algorithm below checks the group for transitivity, and then finds + the ``G``-congruence generated by the pairs ``(p_0, p_1), (p_0, p_2), + ..., (p_0,p_{k-1})`` which is the same as finding the maximal block + system (i.e., the one with minimum block size) such that + ``p_0, ..., p_{k-1}`` are in the same block ([1], p.83). + + It is an implementation of Atkinson's algorithm, as suggested in [1], + and manipulates an equivalence relation on the set ``S`` using a + union-find data structure. The running time is just above + `O(|points||S|)`. ([1], pp. 83-87; [7]). + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> D = DihedralGroup(10) + >>> D.minimal_block([0, 5]) + [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] + >>> D.minimal_block([0, 1]) + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + See Also + ======== + + _union_find_rep, _union_find_merge, is_transitive, is_primitive + + """ + if not self.is_transitive(): + return False + n = self.degree + gens = self.generators + # initialize the list of equivalence class representatives + parents = list(range(n)) + ranks = [1]*n + not_rep = [] + k = len(points) + # the block size must divide the degree of the group + if k > self.max_div: + return [0]*n + for i in range(k - 1): + parents[points[i + 1]] = points[0] + not_rep.append(points[i + 1]) + ranks[points[0]] = k + i = 0 + len_not_rep = k - 1 + while i < len_not_rep: + gamma = not_rep[i] + i += 1 + for gen in gens: + # find has side effects: performs path compression on the list + # of representatives + delta = self._union_find_rep(gamma, parents) + # union has side effects: performs union by rank on the list + # of representatives + temp = self._union_find_merge(gen(gamma), gen(delta), ranks, + parents, not_rep) + if temp == -1: + return [0]*n + len_not_rep += temp + for i in range(n): + # force path compression to get the final state of the equivalence + # relation + self._union_find_rep(i, parents) + + # rewrite result so that block representatives are minimal + new_reps = {} + return [new_reps.setdefault(r, i) for i, r in enumerate(parents)] + + def conjugacy_class(self, x): + r"""Return the conjugacy class of an element in the group. + + Explanation + =========== + + The conjugacy class of an element ``g`` in a group ``G`` is the set of + elements ``x`` in ``G`` that are conjugate with ``g``, i.e. for which + + ``g = xax^{-1}`` + + for some ``a`` in ``G``. + + Note that conjugacy is an equivalence relation, and therefore that + conjugacy classes are partitions of ``G``. For a list of all the + conjugacy classes of the group, use the conjugacy_classes() method. + + In a permutation group, each conjugacy class corresponds to a particular + `cycle structure': for example, in ``S_3``, the conjugacy classes are: + + * the identity class, ``{()}`` + * all transpositions, ``{(1 2), (1 3), (2 3)}`` + * all 3-cycles, ``{(1 2 3), (1 3 2)}`` + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, SymmetricGroup + >>> S3 = SymmetricGroup(3) + >>> S3.conjugacy_class(Permutation(0, 1, 2)) + {(0 1 2), (0 2 1)} + + Notes + ===== + + This procedure computes the conjugacy class directly by finding the + orbit of the element under conjugation in G. This algorithm is only + feasible for permutation groups of relatively small order, but is like + the orbit() function itself in that respect. + """ + # Ref: "Computing the conjugacy classes of finite groups"; Butler, G. + # Groups '93 Galway/St Andrews; edited by Campbell, C. M. + new_class = {x} + last_iteration = new_class + + while len(last_iteration) > 0: + this_iteration = set() + + for y in last_iteration: + for s in self.generators: + conjugated = s * y * (~s) + if conjugated not in new_class: + this_iteration.add(conjugated) + + new_class.update(last_iteration) + last_iteration = this_iteration + + return new_class + + + def conjugacy_classes(self): + r"""Return the conjugacy classes of the group. + + Explanation + =========== + + As described in the documentation for the .conjugacy_class() function, + conjugacy is an equivalence relation on a group G which partitions the + set of elements. This method returns a list of all these conjugacy + classes of G. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricGroup + >>> SymmetricGroup(3).conjugacy_classes() + [{(2)}, {(0 1 2), (0 2 1)}, {(0 2), (1 2), (2)(0 1)}] + + """ + identity = _af_new(list(range(self.degree))) + known_elements = {identity} + classes = [known_elements.copy()] + + for x in self.generate(): + if x not in known_elements: + new_class = self.conjugacy_class(x) + classes.append(new_class) + known_elements.update(new_class) + + return classes + + def normal_closure(self, other, k=10): + r"""Return the normal closure of a subgroup/set of permutations. + + Explanation + =========== + + If ``S`` is a subset of a group ``G``, the normal closure of ``A`` in ``G`` + is defined as the intersection of all normal subgroups of ``G`` that + contain ``A`` ([1], p.14). Alternatively, it is the group generated by + the conjugates ``x^{-1}yx`` for ``x`` a generator of ``G`` and ``y`` a + generator of the subgroup ``\left\langle S\right\rangle`` generated by + ``S`` (for some chosen generating set for ``\left\langle S\right\rangle``) + ([1], p.73). + + Parameters + ========== + + other + a subgroup/list of permutations/single permutation + k + an implementation-specific parameter that determines the number + of conjugates that are adjoined to ``other`` at once + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... CyclicGroup, AlternatingGroup) + >>> S = SymmetricGroup(5) + >>> C = CyclicGroup(5) + >>> G = S.normal_closure(C) + >>> G.order() + 60 + >>> G.is_subgroup(AlternatingGroup(5)) + True + + See Also + ======== + + commutator, derived_subgroup, random_pr + + Notes + ===== + + The algorithm is described in [1], pp. 73-74; it makes use of the + generation of random elements for permutation groups by the product + replacement algorithm. + + """ + if hasattr(other, 'generators'): + degree = self.degree + identity = _af_new(list(range(degree))) + + if all(g == identity for g in other.generators): + return other + Z = PermutationGroup(other.generators[:]) + base, strong_gens = Z.schreier_sims_incremental() + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + basic_orbits, basic_transversals = \ + _orbits_transversals_from_bsgs(base, strong_gens_distr) + + self._random_pr_init(r=10, n=20) + + _loop = True + while _loop: + Z._random_pr_init(r=10, n=10) + for _ in range(k): + g = self.random_pr() + h = Z.random_pr() + conj = h^g + res = _strip(conj, base, basic_orbits, basic_transversals) + if res[0] != identity or res[1] != len(base) + 1: + gens = Z.generators + gens.append(conj) + Z = PermutationGroup(gens) + strong_gens.append(conj) + temp_base, temp_strong_gens = \ + Z.schreier_sims_incremental(base, strong_gens) + base, strong_gens = temp_base, temp_strong_gens + strong_gens_distr = \ + _distribute_gens_by_base(base, strong_gens) + basic_orbits, basic_transversals = \ + _orbits_transversals_from_bsgs(base, + strong_gens_distr) + _loop = False + for g in self.generators: + for h in Z.generators: + conj = h^g + res = _strip(conj, base, basic_orbits, + basic_transversals) + if res[0] != identity or res[1] != len(base) + 1: + _loop = True + break + if _loop: + break + return Z + elif hasattr(other, '__getitem__'): + return self.normal_closure(PermutationGroup(other)) + elif hasattr(other, 'array_form'): + return self.normal_closure(PermutationGroup([other])) + + def orbit(self, alpha, action='tuples'): + r"""Compute the orbit of alpha `\{g(\alpha) | g \in G\}` as a set. + + Explanation + =========== + + The time complexity of the algorithm used here is `O(|Orb|*r)` where + `|Orb|` is the size of the orbit and ``r`` is the number of generators of + the group. For a more detailed analysis, see [1], p.78, [2], pp. 19-21. + Here alpha can be a single point, or a list of points. + + If alpha is a single point, the ordinary orbit is computed. + if alpha is a list of points, there are three available options: + + 'union' - computes the union of the orbits of the points in the list + 'tuples' - computes the orbit of the list interpreted as an ordered + tuple under the group action ( i.e., g((1,2,3)) = (g(1), g(2), g(3)) ) + 'sets' - computes the orbit of the list interpreted as a sets + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([1, 2, 0, 4, 5, 6, 3]) + >>> G = PermutationGroup([a]) + >>> G.orbit(0) + {0, 1, 2} + >>> G.orbit([0, 4], 'union') + {0, 1, 2, 3, 4, 5, 6} + + See Also + ======== + + orbit_transversal + + """ + return _orbit(self.degree, self.generators, alpha, action) + + def orbit_rep(self, alpha, beta, schreier_vector=None): + """Return a group element which sends ``alpha`` to ``beta``. + + Explanation + =========== + + If ``beta`` is not in the orbit of ``alpha``, the function returns + ``False``. This implementation makes use of the schreier vector. + For a proof of correctness, see [1], p.80 + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AlternatingGroup + >>> G = AlternatingGroup(5) + >>> G.orbit_rep(0, 4) + (0 4 1 2 3) + + See Also + ======== + + schreier_vector + + """ + if schreier_vector is None: + schreier_vector = self.schreier_vector(alpha) + if schreier_vector[beta] is None: + return False + k = schreier_vector[beta] + gens = [x._array_form for x in self.generators] + a = [] + while k != -1: + a.append(gens[k]) + beta = gens[k].index(beta) # beta = (~gens[k])(beta) + k = schreier_vector[beta] + if a: + return _af_new(_af_rmuln(*a)) + else: + return _af_new(list(range(self._degree))) + + def orbit_transversal(self, alpha, pairs=False): + r"""Computes a transversal for the orbit of ``alpha`` as a set. + + Explanation + =========== + + For a permutation group `G`, a transversal for the orbit + `Orb = \{g(\alpha) | g \in G\}` is a set + `\{g_\beta | g_\beta(\alpha) = \beta\}` for `\beta \in Orb`. + Note that there may be more than one possible transversal. + If ``pairs`` is set to ``True``, it returns the list of pairs + `(\beta, g_\beta)`. For a proof of correctness, see [1], p.79 + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> G = DihedralGroup(6) + >>> G.orbit_transversal(0) + [(5), (0 1 2 3 4 5), (0 5)(1 4)(2 3), (0 2 4)(1 3 5), (5)(0 4)(1 3), (0 3)(1 4)(2 5)] + + See Also + ======== + + orbit + + """ + return _orbit_transversal(self._degree, self.generators, alpha, pairs) + + def orbits(self, rep=False): + """Return the orbits of ``self``, ordered according to lowest element + in each orbit. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation(1, 5)(2, 3)(4, 0, 6) + >>> b = Permutation(1, 5)(3, 4)(2, 6, 0) + >>> G = PermutationGroup([a, b]) + >>> G.orbits() + [{0, 2, 3, 4, 6}, {1, 5}] + """ + return _orbits(self._degree, self._generators) + + def order(self): + """Return the order of the group: the number of permutations that + can be generated from elements of the group. + + The number of permutations comprising the group is given by + ``len(group)``; the length of each permutation in the group is + given by ``group.size``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + + >>> a = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a]) + >>> G.degree + 3 + >>> len(G) + 1 + >>> G.order() + 2 + >>> list(G.generate()) + [(2), (2)(0 1)] + + >>> a = Permutation([0, 2, 1]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.order() + 6 + + See Also + ======== + + degree + + """ + if self._order is not None: + return self._order + if self._is_sym: + n = self._degree + self._order = factorial(n) + return self._order + if self._is_alt: + n = self._degree + self._order = factorial(n)/2 + return self._order + + m = prod([len(x) for x in self.basic_transversals]) + self._order = m + return m + + def index(self, H): + """ + Returns the index of a permutation group. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation(1,2,3) + >>> b =Permutation(3) + >>> G = PermutationGroup([a]) + >>> H = PermutationGroup([b]) + >>> G.index(H) + 3 + + """ + if H.is_subgroup(self): + return self.order()//H.order() + + @property + def is_symmetric(self): + """Return ``True`` if the group is symmetric. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricGroup + >>> g = SymmetricGroup(5) + >>> g.is_symmetric + True + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> g = PermutationGroup( + ... Permutation(0, 1, 2, 3, 4), + ... Permutation(2, 3)) + >>> g.is_symmetric + True + + Notes + ===== + + This uses a naive test involving the computation of the full + group order. + If you need more quicker taxonomy for large groups, you can use + :meth:`PermutationGroup.is_alt_sym`. + However, :meth:`PermutationGroup.is_alt_sym` may not be accurate + and is not able to distinguish between an alternating group and + a symmetric group. + + See Also + ======== + + is_alt_sym + """ + _is_sym = self._is_sym + if _is_sym is not None: + return _is_sym + + n = self.degree + if n >= 8: + if self.is_transitive(): + _is_alt_sym = self._eval_is_alt_sym_monte_carlo() + if _is_alt_sym: + if any(g.is_odd for g in self.generators): + self._is_sym, self._is_alt = True, False + return True + + self._is_sym, self._is_alt = False, True + return False + + return self._eval_is_alt_sym_naive(only_sym=True) + + self._is_sym, self._is_alt = False, False + return False + + return self._eval_is_alt_sym_naive(only_sym=True) + + + @property + def is_alternating(self): + """Return ``True`` if the group is alternating. + + Examples + ======== + + >>> from sympy.combinatorics import AlternatingGroup + >>> g = AlternatingGroup(5) + >>> g.is_alternating + True + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> g = PermutationGroup( + ... Permutation(0, 1, 2, 3, 4), + ... Permutation(2, 3, 4)) + >>> g.is_alternating + True + + Notes + ===== + + This uses a naive test involving the computation of the full + group order. + If you need more quicker taxonomy for large groups, you can use + :meth:`PermutationGroup.is_alt_sym`. + However, :meth:`PermutationGroup.is_alt_sym` may not be accurate + and is not able to distinguish between an alternating group and + a symmetric group. + + See Also + ======== + + is_alt_sym + """ + _is_alt = self._is_alt + if _is_alt is not None: + return _is_alt + + n = self.degree + if n >= 8: + if self.is_transitive(): + _is_alt_sym = self._eval_is_alt_sym_monte_carlo() + if _is_alt_sym: + if all(g.is_even for g in self.generators): + self._is_sym, self._is_alt = False, True + return True + + self._is_sym, self._is_alt = True, False + return False + + return self._eval_is_alt_sym_naive(only_alt=True) + + self._is_sym, self._is_alt = False, False + return False + + return self._eval_is_alt_sym_naive(only_alt=True) + + @classmethod + def _distinct_primes_lemma(cls, primes): + """Subroutine to test if there is only one cyclic group for the + order.""" + primes = sorted(primes) + l = len(primes) + for i in range(l): + for j in range(i+1, l): + if primes[j] % primes[i] == 1: + return None + return True + + @property + def is_cyclic(self): + r""" + Return ``True`` if the group is Cyclic. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AbelianGroup + >>> G = AbelianGroup(3, 4) + >>> G.is_cyclic + True + >>> G = AbelianGroup(4, 4) + >>> G.is_cyclic + False + + Notes + ===== + + If the order of a group $n$ can be factored into the distinct + primes $p_1, p_2, \dots , p_s$ and if + + .. math:: + \forall i, j \in \{1, 2, \dots, s \}: + p_i \not \equiv 1 \pmod {p_j} + + holds true, there is only one group of the order $n$ which + is a cyclic group [1]_. This is a generalization of the lemma + that the group of order $15, 35, \dots$ are cyclic. + + And also, these additional lemmas can be used to test if a + group is cyclic if the order of the group is already found. + + - If the group is abelian and the order of the group is + square-free, the group is cyclic. + - If the order of the group is less than $6$ and is not $4$, the + group is cyclic. + - If the order of the group is prime, the group is cyclic. + + References + ========== + + .. [1] 1978: John S. Rose: A Course on Group Theory, + Introduction to Finite Group Theory: 1.4 + """ + if self._is_cyclic is not None: + return self._is_cyclic + + if len(self.generators) == 1: + self._is_cyclic = True + self._is_abelian = True + return True + + if self._is_abelian is False: + self._is_cyclic = False + return False + + order = self.order() + + if order < 6: + self._is_abelian = True + if order != 4: + self._is_cyclic = True + return True + + factors = factorint(order) + if all(v == 1 for v in factors.values()): + if self._is_abelian: + self._is_cyclic = True + return True + + primes = list(factors.keys()) + if PermutationGroup._distinct_primes_lemma(primes) is True: + self._is_cyclic = True + self._is_abelian = True + return True + + if not self.is_abelian: + self._is_cyclic = False + return False + + self._is_cyclic = all( + any(g**(order//p) != self.identity for g in self.generators) + for p, e in factors.items() if e > 1 + ) + return self._is_cyclic + + @property + def is_dihedral(self): + r""" + Return ``True`` if the group is dihedral. + + Examples + ======== + + >>> from sympy.combinatorics.perm_groups import PermutationGroup + >>> from sympy.combinatorics.permutations import Permutation + >>> from sympy.combinatorics.named_groups import SymmetricGroup, CyclicGroup + >>> G = PermutationGroup(Permutation(1, 6)(2, 5)(3, 4), Permutation(0, 1, 2, 3, 4, 5, 6)) + >>> G.is_dihedral + True + >>> G = SymmetricGroup(3) + >>> G.is_dihedral + True + >>> G = CyclicGroup(6) + >>> G.is_dihedral + False + + References + ========== + + .. [Di1] https://math.stackexchange.com/questions/827230/given-a-cayley-table-is-there-an-algorithm-to-determine-if-it-is-a-dihedral-gro/827273#827273 + .. [Di2] https://kconrad.math.uconn.edu/blurbs/grouptheory/dihedral.pdf + .. [Di3] https://kconrad.math.uconn.edu/blurbs/grouptheory/dihedral2.pdf + .. [Di4] https://en.wikipedia.org/wiki/Dihedral_group + """ + if self._is_dihedral is not None: + return self._is_dihedral + + order = self.order() + + if order % 2 == 1: + self._is_dihedral = False + return False + if order == 2: + self._is_dihedral = True + return True + if order == 4: + # The dihedral group of order 4 is the Klein 4-group. + self._is_dihedral = not self.is_cyclic + return self._is_dihedral + if self.is_abelian: + # The only abelian dihedral groups are the ones of orders 2 and 4. + self._is_dihedral = False + return False + + # Now we know the group is of even order >= 6, and nonabelian. + n = order // 2 + + # Handle special cases where there are exactly two generators. + gens = self.generators + if len(gens) == 2: + x, y = gens + a, b = x.order(), y.order() + # Make a >= b + if a < b: + x, y, a, b = y, x, b, a + # Using Theorem 2.1 of [Di3]: + if a == 2 == b: + self._is_dihedral = True + return True + # Using Theorem 1.1 of [Di3]: + if a == n and b == 2 and y*x*y == ~x: + self._is_dihedral = True + return True + + # Proceed with algorithm of [Di1] + # Find elements of orders 2 and n + order_2, order_n = [], [] + for p in self.elements: + k = p.order() + if k == 2: + order_2.append(p) + elif k == n: + order_n.append(p) + + if len(order_2) != n + 1 - (n % 2): + self._is_dihedral = False + return False + + if not order_n: + self._is_dihedral = False + return False + + x = order_n[0] + # Want an element y of order 2 that is not a power of x + # (i.e. that is not the 180-deg rotation, when n is even). + y = order_2[0] + if n % 2 == 0 and y == x**(n//2): + y = order_2[1] + + self._is_dihedral = (y*x*y == ~x) + return self._is_dihedral + + def pointwise_stabilizer(self, points, incremental=True): + r"""Return the pointwise stabilizer for a set of points. + + Explanation + =========== + + For a permutation group `G` and a set of points + `\{p_1, p_2,\ldots, p_k\}`, the pointwise stabilizer of + `p_1, p_2, \ldots, p_k` is defined as + `G_{p_1,\ldots, p_k} = + \{g\in G | g(p_i) = p_i \forall i\in\{1, 2,\ldots,k\}\}` ([1],p20). + It is a subgroup of `G`. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> S = SymmetricGroup(7) + >>> Stab = S.pointwise_stabilizer([2, 3, 5]) + >>> Stab.is_subgroup(S.stabilizer(2).stabilizer(3).stabilizer(5)) + True + + See Also + ======== + + stabilizer, schreier_sims_incremental + + Notes + ===== + + When incremental == True, + rather than the obvious implementation using successive calls to + ``.stabilizer()``, this uses the incremental Schreier-Sims algorithm + to obtain a base with starting segment - the given points. + + """ + if incremental: + base, strong_gens = self.schreier_sims_incremental(base=points) + stab_gens = [] + degree = self.degree + for gen in strong_gens: + if [gen(point) for point in points] == points: + stab_gens.append(gen) + if not stab_gens: + stab_gens = _af_new(list(range(degree))) + return PermutationGroup(stab_gens) + else: + gens = self._generators + degree = self.degree + for x in points: + gens = _stabilizer(degree, gens, x) + return PermutationGroup(gens) + + def make_perm(self, n, seed=None): + """ + Multiply ``n`` randomly selected permutations from + pgroup together, starting with the identity + permutation. If ``n`` is a list of integers, those + integers will be used to select the permutations and they + will be applied in L to R order: make_perm((A, B, C)) will + give CBA(I) where I is the identity permutation. + + ``seed`` is used to set the seed for the random selection + of permutations from pgroup. If this is a list of integers, + the corresponding permutations from pgroup will be selected + in the order give. This is mainly used for testing purposes. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a, b = [Permutation([1, 0, 3, 2]), Permutation([1, 3, 0, 2])] + >>> G = PermutationGroup([a, b]) + >>> G.make_perm(1, [0]) + (0 1)(2 3) + >>> G.make_perm(3, [0, 1, 0]) + (0 2 3 1) + >>> G.make_perm([0, 1, 0]) + (0 2 3 1) + + See Also + ======== + + random + """ + if is_sequence(n): + if seed is not None: + raise ValueError('If n is a sequence, seed should be None') + n, seed = len(n), n + else: + try: + n = int(n) + except TypeError: + raise ValueError('n must be an integer or a sequence.') + randomrange = _randrange(seed) + + # start with the identity permutation + result = Permutation(list(range(self.degree))) + m = len(self) + for _ in range(n): + p = self[randomrange(m)] + result = rmul(result, p) + return result + + def random(self, af=False): + """Return a random group element + """ + rank = randrange(self.order()) + return self.coset_unrank(rank, af) + + def random_pr(self, gen_count=11, iterations=50, _random_prec=None): + """Return a random group element using product replacement. + + Explanation + =========== + + For the details of the product replacement algorithm, see + ``_random_pr_init`` In ``random_pr`` the actual 'product replacement' + is performed. Notice that if the attribute ``_random_gens`` + is empty, it needs to be initialized by ``_random_pr_init``. + + See Also + ======== + + _random_pr_init + + """ + if self._random_gens == []: + self._random_pr_init(gen_count, iterations) + random_gens = self._random_gens + r = len(random_gens) - 1 + + # handle randomized input for testing purposes + if _random_prec is None: + s = randrange(r) + t = randrange(r - 1) + if t == s: + t = r - 1 + x = choice([1, 2]) + e = choice([-1, 1]) + else: + s = _random_prec['s'] + t = _random_prec['t'] + if t == s: + t = r - 1 + x = _random_prec['x'] + e = _random_prec['e'] + + if x == 1: + random_gens[s] = _af_rmul(random_gens[s], _af_pow(random_gens[t], e)) + random_gens[r] = _af_rmul(random_gens[r], random_gens[s]) + else: + random_gens[s] = _af_rmul(_af_pow(random_gens[t], e), random_gens[s]) + random_gens[r] = _af_rmul(random_gens[s], random_gens[r]) + return _af_new(random_gens[r]) + + def random_stab(self, alpha, schreier_vector=None, _random_prec=None): + """Random element from the stabilizer of ``alpha``. + + The schreier vector for ``alpha`` is an optional argument used + for speeding up repeated calls. The algorithm is described in [1], p.81 + + See Also + ======== + + random_pr, orbit_rep + + """ + if schreier_vector is None: + schreier_vector = self.schreier_vector(alpha) + if _random_prec is None: + rand = self.random_pr() + else: + rand = _random_prec['rand'] + beta = rand(alpha) + h = self.orbit_rep(alpha, beta, schreier_vector) + return rmul(~h, rand) + + def schreier_sims(self): + """Schreier-Sims algorithm. + + Explanation + =========== + + It computes the generators of the chain of stabilizers + `G > G_{b_1} > .. > G_{b1,..,b_r} > 1` + in which `G_{b_1,..,b_i}` stabilizes `b_1,..,b_i`, + and the corresponding ``s`` cosets. + An element of the group can be written as the product + `h_1*..*h_s`. + + We use the incremental Schreier-Sims algorithm. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([0, 2, 1]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.schreier_sims() + >>> G.basic_transversals + [{0: (2)(0 1), 1: (2), 2: (1 2)}, + {0: (2), 2: (0 2)}] + """ + if self._transversals: + return + self._schreier_sims() + return + + def _schreier_sims(self, base=None): + schreier = self.schreier_sims_incremental(base=base, slp_dict=True) + base, strong_gens = schreier[:2] + self._base = base + self._strong_gens = strong_gens + self._strong_gens_slp = schreier[2] + if not base: + self._transversals = [] + self._basic_orbits = [] + return + + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + basic_orbits, transversals, slps = _orbits_transversals_from_bsgs(base,\ + strong_gens_distr, slp=True) + + # rewrite the indices stored in slps in terms of strong_gens + for i, slp in enumerate(slps): + gens = strong_gens_distr[i] + for k in slp: + slp[k] = [strong_gens.index(gens[s]) for s in slp[k]] + + self._transversals = transversals + self._basic_orbits = [sorted(x) for x in basic_orbits] + self._transversal_slp = slps + + def schreier_sims_incremental(self, base=None, gens=None, slp_dict=False): + """Extend a sequence of points and generating set to a base and strong + generating set. + + Parameters + ========== + + base + The sequence of points to be extended to a base. Optional + parameter with default value ``[]``. + gens + The generating set to be extended to a strong generating set + relative to the base obtained. Optional parameter with default + value ``self.generators``. + + slp_dict + If `True`, return a dictionary `{g: gens}` for each strong + generator `g` where `gens` is a list of strong generators + coming before `g` in `strong_gens`, such that the product + of the elements of `gens` is equal to `g`. + + Returns + ======= + + (base, strong_gens) + ``base`` is the base obtained, and ``strong_gens`` is the strong + generating set relative to it. The original parameters ``base``, + ``gens`` remain unchanged. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AlternatingGroup + >>> from sympy.combinatorics.testutil import _verify_bsgs + >>> A = AlternatingGroup(7) + >>> base = [2, 3] + >>> seq = [2, 3] + >>> base, strong_gens = A.schreier_sims_incremental(base=seq) + >>> _verify_bsgs(A, base, strong_gens) + True + >>> base[:2] + [2, 3] + + Notes + ===== + + This version of the Schreier-Sims algorithm runs in polynomial time. + There are certain assumptions in the implementation - if the trivial + group is provided, ``base`` and ``gens`` are returned immediately, + as any sequence of points is a base for the trivial group. If the + identity is present in the generators ``gens``, it is removed as + it is a redundant generator. + The implementation is described in [1], pp. 90-93. + + See Also + ======== + + schreier_sims, schreier_sims_random + + """ + if base is None: + base = [] + if gens is None: + gens = self.generators[:] + degree = self.degree + id_af = list(range(degree)) + # handle the trivial group + if len(gens) == 1 and gens[0].is_Identity: + if slp_dict: + return base, gens, {gens[0]: [gens[0]]} + return base, gens + # prevent side effects + _base, _gens = base[:], gens[:] + # remove the identity as a generator + _gens = [x for x in _gens if not x.is_Identity] + # make sure no generator fixes all base points + for gen in _gens: + if all(x == gen._array_form[x] for x in _base): + for new in id_af: + if gen._array_form[new] != new: + break + else: + assert None # can this ever happen? + _base.append(new) + # distribute generators according to basic stabilizers + strong_gens_distr = _distribute_gens_by_base(_base, _gens) + strong_gens_slp = [] + # initialize the basic stabilizers, basic orbits and basic transversals + orbs = {} + transversals = {} + slps = {} + base_len = len(_base) + for i in range(base_len): + transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i], + _base[i], pairs=True, af=True, slp=True) + transversals[i] = dict(transversals[i]) + orbs[i] = list(transversals[i].keys()) + # main loop: amend the stabilizer chain until we have generators + # for all stabilizers + i = base_len - 1 + while i >= 0: + # this flag is used to continue with the main loop from inside + # a nested loop + continue_i = False + # test the generators for being a strong generating set + db = {} + for beta, u_beta in list(transversals[i].items()): + for j, gen in enumerate(strong_gens_distr[i]): + gb = gen._array_form[beta] + u1 = transversals[i][gb] + g1 = _af_rmul(gen._array_form, u_beta) + slp = [(i, g) for g in slps[i][beta]] + slp = [(i, j)] + slp + if g1 != u1: + # test if the schreier generator is in the i+1-th + # would-be basic stabilizer + y = True + try: + u1_inv = db[gb] + except KeyError: + u1_inv = db[gb] = _af_invert(u1) + schreier_gen = _af_rmul(u1_inv, g1) + u1_inv_slp = slps[i][gb][:] + u1_inv_slp.reverse() + u1_inv_slp = [(i, (g,)) for g in u1_inv_slp] + slp = u1_inv_slp + slp + h, j, slp = _strip_af(schreier_gen, _base, orbs, transversals, i, slp=slp, slps=slps) + if j <= base_len: + # new strong generator h at level j + y = False + elif h: + # h fixes all base points + y = False + moved = 0 + while h[moved] == moved: + moved += 1 + _base.append(moved) + base_len += 1 + strong_gens_distr.append([]) + if y is False: + # if a new strong generator is found, update the + # data structures and start over + h = _af_new(h) + strong_gens_slp.append((h, slp)) + for l in range(i + 1, j): + strong_gens_distr[l].append(h) + transversals[l], slps[l] =\ + _orbit_transversal(degree, strong_gens_distr[l], + _base[l], pairs=True, af=True, slp=True) + transversals[l] = dict(transversals[l]) + orbs[l] = list(transversals[l].keys()) + i = j - 1 + # continue main loop using the flag + continue_i = True + if continue_i is True: + break + if continue_i is True: + break + if continue_i is True: + continue + i -= 1 + + strong_gens = _gens[:] + + if slp_dict: + # create the list of the strong generators strong_gens and + # rewrite the indices of strong_gens_slp in terms of the + # elements of strong_gens + for k, slp in strong_gens_slp: + strong_gens.append(k) + for i in range(len(slp)): + s = slp[i] + if isinstance(s[1], tuple): + slp[i] = strong_gens_distr[s[0]][s[1][0]]**-1 + else: + slp[i] = strong_gens_distr[s[0]][s[1]] + strong_gens_slp = dict(strong_gens_slp) + # add the original generators + for g in _gens: + strong_gens_slp[g] = [g] + return (_base, strong_gens, strong_gens_slp) + + strong_gens.extend([k for k, _ in strong_gens_slp]) + return _base, strong_gens + + def schreier_sims_random(self, base=None, gens=None, consec_succ=10, + _random_prec=None): + r"""Randomized Schreier-Sims algorithm. + + Explanation + =========== + + The randomized Schreier-Sims algorithm takes the sequence ``base`` + and the generating set ``gens``, and extends ``base`` to a base, and + ``gens`` to a strong generating set relative to that base with + probability of a wrong answer at most `2^{-consec\_succ}`, + provided the random generators are sufficiently random. + + Parameters + ========== + + base + The sequence to be extended to a base. + gens + The generating set to be extended to a strong generating set. + consec_succ + The parameter defining the probability of a wrong answer. + _random_prec + An internal parameter used for testing purposes. + + Returns + ======= + + (base, strong_gens) + ``base`` is the base and ``strong_gens`` is the strong generating + set relative to it. + + Examples + ======== + + >>> from sympy.combinatorics.testutil import _verify_bsgs + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> S = SymmetricGroup(5) + >>> base, strong_gens = S.schreier_sims_random(consec_succ=5) + >>> _verify_bsgs(S, base, strong_gens) #doctest: +SKIP + True + + Notes + ===== + + The algorithm is described in detail in [1], pp. 97-98. It extends + the orbits ``orbs`` and the permutation groups ``stabs`` to + basic orbits and basic stabilizers for the base and strong generating + set produced in the end. + The idea of the extension process + is to "sift" random group elements through the stabilizer chain + and amend the stabilizers/orbits along the way when a sift + is not successful. + The helper function ``_strip`` is used to attempt + to decompose a random group element according to the current + state of the stabilizer chain and report whether the element was + fully decomposed (successful sift) or not (unsuccessful sift). In + the latter case, the level at which the sift failed is reported and + used to amend ``stabs``, ``base``, ``gens`` and ``orbs`` accordingly. + The halting condition is for ``consec_succ`` consecutive successful + sifts to pass. This makes sure that the current ``base`` and ``gens`` + form a BSGS with probability at least `1 - 1/\text{consec\_succ}`. + + See Also + ======== + + schreier_sims + + """ + if base is None: + base = [] + if gens is None: + gens = self.generators + base_len = len(base) + n = self.degree + # make sure no generator fixes all base points + for gen in gens: + if all(gen(x) == x for x in base): + new = 0 + while gen._array_form[new] == new: + new += 1 + base.append(new) + base_len += 1 + # distribute generators according to basic stabilizers + strong_gens_distr = _distribute_gens_by_base(base, gens) + # initialize the basic stabilizers, basic transversals and basic orbits + transversals = {} + orbs = {} + for i in range(base_len): + transversals[i] = dict(_orbit_transversal(n, strong_gens_distr[i], + base[i], pairs=True)) + orbs[i] = list(transversals[i].keys()) + # initialize the number of consecutive elements sifted + c = 0 + # start sifting random elements while the number of consecutive sifts + # is less than consec_succ + while c < consec_succ: + if _random_prec is None: + g = self.random_pr() + else: + g = _random_prec['g'].pop() + h, j = _strip(g, base, orbs, transversals) + y = True + # determine whether a new base point is needed + if j <= base_len: + y = False + elif not h.is_Identity: + y = False + moved = 0 + while h(moved) == moved: + moved += 1 + base.append(moved) + base_len += 1 + strong_gens_distr.append([]) + # if the element doesn't sift, amend the strong generators and + # associated stabilizers and orbits + if y is False: + for l in range(1, j): + strong_gens_distr[l].append(h) + transversals[l] = dict(_orbit_transversal(n, + strong_gens_distr[l], base[l], pairs=True)) + orbs[l] = list(transversals[l].keys()) + c = 0 + else: + c += 1 + # build the strong generating set + strong_gens = strong_gens_distr[0][:] + for gen in strong_gens_distr[1]: + if gen not in strong_gens: + strong_gens.append(gen) + return base, strong_gens + + def schreier_vector(self, alpha): + """Computes the schreier vector for ``alpha``. + + Explanation + =========== + + The Schreier vector efficiently stores information + about the orbit of ``alpha``. It can later be used to quickly obtain + elements of the group that send ``alpha`` to a particular element + in the orbit. Notice that the Schreier vector depends on the order + in which the group generators are listed. For a definition, see [3]. + Since list indices start from zero, we adopt the convention to use + "None" instead of 0 to signify that an element does not belong + to the orbit. + For the algorithm and its correctness, see [2], pp.78-80. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([2, 4, 6, 3, 1, 5, 0]) + >>> b = Permutation([0, 1, 3, 5, 4, 6, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.schreier_vector(0) + [-1, None, 0, 1, None, 1, 0] + + See Also + ======== + + orbit + + """ + n = self.degree + v = [None]*n + v[alpha] = -1 + orb = [alpha] + used = [False]*n + used[alpha] = True + gens = self.generators + r = len(gens) + for b in orb: + for i in range(r): + temp = gens[i]._array_form[b] + if used[temp] is False: + orb.append(temp) + used[temp] = True + v[temp] = i + return v + + def stabilizer(self, alpha): + r"""Return the stabilizer subgroup of ``alpha``. + + Explanation + =========== + + The stabilizer of `\alpha` is the group `G_\alpha = + \{g \in G | g(\alpha) = \alpha\}`. + For a proof of correctness, see [1], p.79. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> G = DihedralGroup(6) + >>> G.stabilizer(5) + PermutationGroup([ + (5)(0 4)(1 3)]) + + See Also + ======== + + orbit + + """ + return PermGroup(_stabilizer(self._degree, self._generators, alpha)) + + @property + def strong_gens(self): + r"""Return a strong generating set from the Schreier-Sims algorithm. + + Explanation + =========== + + A generating set `S = \{g_1, g_2, \dots, g_t\}` for a permutation group + `G` is a strong generating set relative to the sequence of points + (referred to as a "base") `(b_1, b_2, \dots, b_k)` if, for + `1 \leq i \leq k` we have that the intersection of the pointwise + stabilizer `G^{(i+1)} := G_{b_1, b_2, \dots, b_i}` with `S` generates + the pointwise stabilizer `G^{(i+1)}`. The concepts of a base and + strong generating set and their applications are discussed in depth + in [1], pp. 87-89 and [2], pp. 55-57. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> D = DihedralGroup(4) + >>> D.strong_gens + [(0 1 2 3), (0 3)(1 2), (1 3)] + >>> D.base + [0, 1] + + See Also + ======== + + base, basic_transversals, basic_orbits, basic_stabilizers + + """ + if self._strong_gens == []: + self.schreier_sims() + return self._strong_gens + + def subgroup(self, gens): + """ + Return the subgroup generated by `gens` which is a list of + elements of the group + """ + + if not all(g in self for g in gens): + raise ValueError("The group does not contain the supplied generators") + + G = PermutationGroup(gens) + return G + + def subgroup_search(self, prop, base=None, strong_gens=None, tests=None, + init_subgroup=None): + """Find the subgroup of all elements satisfying the property ``prop``. + + Explanation + =========== + + This is done by a depth-first search with respect to base images that + uses several tests to prune the search tree. + + Parameters + ========== + + prop + The property to be used. Has to be callable on group elements + and always return ``True`` or ``False``. It is assumed that + all group elements satisfying ``prop`` indeed form a subgroup. + base + A base for the supergroup. + strong_gens + A strong generating set for the supergroup. + tests + A list of callables of length equal to the length of ``base``. + These are used to rule out group elements by partial base images, + so that ``tests[l](g)`` returns False if the element ``g`` is known + not to satisfy prop base on where g sends the first ``l + 1`` base + points. + init_subgroup + if a subgroup of the sought group is + known in advance, it can be passed to the function as this + parameter. + + Returns + ======= + + res + The subgroup of all elements satisfying ``prop``. The generating + set for this group is guaranteed to be a strong generating set + relative to the base ``base``. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... AlternatingGroup) + >>> from sympy.combinatorics.testutil import _verify_bsgs + >>> S = SymmetricGroup(7) + >>> prop_even = lambda x: x.is_even + >>> base, strong_gens = S.schreier_sims_incremental() + >>> G = S.subgroup_search(prop_even, base=base, strong_gens=strong_gens) + >>> G.is_subgroup(AlternatingGroup(7)) + True + >>> _verify_bsgs(G, base, G.generators) + True + + Notes + ===== + + This function is extremely lengthy and complicated and will require + some careful attention. The implementation is described in + [1], pp. 114-117, and the comments for the code here follow the lines + of the pseudocode in the book for clarity. + + The complexity is exponential in general, since the search process by + itself visits all members of the supergroup. However, there are a lot + of tests which are used to prune the search tree, and users can define + their own tests via the ``tests`` parameter, so in practice, and for + some computations, it's not terrible. + + A crucial part in the procedure is the frequent base change performed + (this is line 11 in the pseudocode) in order to obtain a new basic + stabilizer. The book mentiones that this can be done by using + ``.baseswap(...)``, however the current implementation uses a more + straightforward way to find the next basic stabilizer - calling the + function ``.stabilizer(...)`` on the previous basic stabilizer. + + """ + # initialize BSGS and basic group properties + def get_reps(orbits): + # get the minimal element in the base ordering + return [min(orbit, key = lambda x: base_ordering[x]) \ + for orbit in orbits] + + def update_nu(l): + temp_index = len(basic_orbits[l]) + 1 -\ + len(res_basic_orbits_init_base[l]) + # this corresponds to the element larger than all points + if temp_index >= len(sorted_orbits[l]): + nu[l] = base_ordering[degree] + else: + nu[l] = sorted_orbits[l][temp_index] + + if base is None: + base, strong_gens = self.schreier_sims_incremental() + base_len = len(base) + degree = self.degree + identity = _af_new(list(range(degree))) + base_ordering = _base_ordering(base, degree) + # add an element larger than all points + base_ordering.append(degree) + # add an element smaller than all points + base_ordering.append(-1) + # compute BSGS-related structures + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + basic_orbits, transversals = _orbits_transversals_from_bsgs(base, + strong_gens_distr) + # handle subgroup initialization and tests + if init_subgroup is None: + init_subgroup = PermutationGroup([identity]) + if tests is None: + trivial_test = lambda x: True + tests = [] + for i in range(base_len): + tests.append(trivial_test) + # line 1: more initializations. + res = init_subgroup + f = base_len - 1 + l = base_len - 1 + # line 2: set the base for K to the base for G + res_base = base[:] + # line 3: compute BSGS and related structures for K + res_base, res_strong_gens = res.schreier_sims_incremental( + base=res_base) + res_strong_gens_distr = _distribute_gens_by_base(res_base, + res_strong_gens) + res_generators = res.generators + res_basic_orbits_init_base = \ + [_orbit(degree, res_strong_gens_distr[i], res_base[i])\ + for i in range(base_len)] + # initialize orbit representatives + orbit_reps = [None]*base_len + # line 4: orbit representatives for f-th basic stabilizer of K + orbits = _orbits(degree, res_strong_gens_distr[f]) + orbit_reps[f] = get_reps(orbits) + # line 5: remove the base point from the representatives to avoid + # getting the identity element as a generator for K + orbit_reps[f].remove(base[f]) + # line 6: more initializations + c = [0]*base_len + u = [identity]*base_len + sorted_orbits = [None]*base_len + for i in range(base_len): + sorted_orbits[i] = basic_orbits[i][:] + sorted_orbits[i].sort(key=lambda point: base_ordering[point]) + # line 7: initializations + mu = [None]*base_len + nu = [None]*base_len + # this corresponds to the element smaller than all points + mu[l] = degree + 1 + update_nu(l) + # initialize computed words + computed_words = [identity]*base_len + # line 8: main loop + while True: + # apply all the tests + while l < base_len - 1 and \ + computed_words[l](base[l]) in orbit_reps[l] and \ + base_ordering[mu[l]] < \ + base_ordering[computed_words[l](base[l])] < \ + base_ordering[nu[l]] and \ + tests[l](computed_words): + # line 11: change the (partial) base of K + new_point = computed_words[l](base[l]) + res_base[l] = new_point + new_stab_gens = _stabilizer(degree, res_strong_gens_distr[l], + new_point) + res_strong_gens_distr[l + 1] = new_stab_gens + # line 12: calculate minimal orbit representatives for the + # l+1-th basic stabilizer + orbits = _orbits(degree, new_stab_gens) + orbit_reps[l + 1] = get_reps(orbits) + # line 13: amend sorted orbits + l += 1 + temp_orbit = [computed_words[l - 1](point) for point + in basic_orbits[l]] + temp_orbit.sort(key=lambda point: base_ordering[point]) + sorted_orbits[l] = temp_orbit + # lines 14 and 15: update variables used minimality tests + new_mu = degree + 1 + for i in range(l): + if base[l] in res_basic_orbits_init_base[i]: + candidate = computed_words[i](base[i]) + if base_ordering[candidate] > base_ordering[new_mu]: + new_mu = candidate + mu[l] = new_mu + update_nu(l) + # line 16: determine the new transversal element + c[l] = 0 + temp_point = sorted_orbits[l][c[l]] + gamma = computed_words[l - 1]._array_form.index(temp_point) + u[l] = transversals[l][gamma] + # update computed words + computed_words[l] = rmul(computed_words[l - 1], u[l]) + # lines 17 & 18: apply the tests to the group element found + g = computed_words[l] + temp_point = g(base[l]) + if l == base_len - 1 and \ + base_ordering[mu[l]] < \ + base_ordering[temp_point] < base_ordering[nu[l]] and \ + temp_point in orbit_reps[l] and \ + tests[l](computed_words) and \ + prop(g): + # line 19: reset the base of K + res_generators.append(g) + res_base = base[:] + # line 20: recalculate basic orbits (and transversals) + res_strong_gens.append(g) + res_strong_gens_distr = _distribute_gens_by_base(res_base, + res_strong_gens) + res_basic_orbits_init_base = \ + [_orbit(degree, res_strong_gens_distr[i], res_base[i]) \ + for i in range(base_len)] + # line 21: recalculate orbit representatives + # line 22: reset the search depth + orbit_reps[f] = get_reps(orbits) + l = f + # line 23: go up the tree until in the first branch not fully + # searched + while l >= 0 and c[l] == len(basic_orbits[l]) - 1: + l = l - 1 + # line 24: if the entire tree is traversed, return K + if l == -1: + return PermutationGroup(res_generators) + # lines 25-27: update orbit representatives + if l < f: + # line 26 + f = l + c[l] = 0 + # line 27 + temp_orbits = _orbits(degree, res_strong_gens_distr[f]) + orbit_reps[f] = get_reps(temp_orbits) + # line 28: update variables used for minimality testing + mu[l] = degree + 1 + temp_index = len(basic_orbits[l]) + 1 - \ + len(res_basic_orbits_init_base[l]) + if temp_index >= len(sorted_orbits[l]): + nu[l] = base_ordering[degree] + else: + nu[l] = sorted_orbits[l][temp_index] + # line 29: set the next element from the current branch and update + # accordingly + c[l] += 1 + if l == 0: + gamma = sorted_orbits[l][c[l]] + else: + gamma = computed_words[l - 1]._array_form.index(sorted_orbits[l][c[l]]) + + u[l] = transversals[l][gamma] + if l == 0: + computed_words[l] = u[l] + else: + computed_words[l] = rmul(computed_words[l - 1], u[l]) + + @property + def transitivity_degree(self): + r"""Compute the degree of transitivity of the group. + + Explanation + =========== + + A permutation group `G` acting on `\Omega = \{0, 1, \dots, n-1\}` is + ``k``-fold transitive, if, for any `k` points + `(a_1, a_2, \dots, a_k) \in \Omega` and any `k` points + `(b_1, b_2, \dots, b_k) \in \Omega` there exists `g \in G` such that + `g(a_1) = b_1, g(a_2) = b_2, \dots, g(a_k) = b_k` + The degree of transitivity of `G` is the maximum ``k`` such that + `G` is ``k``-fold transitive. ([8]) + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> a = Permutation([1, 2, 0]) + >>> b = Permutation([1, 0, 2]) + >>> G = PermutationGroup([a, b]) + >>> G.transitivity_degree + 3 + + See Also + ======== + + is_transitive, orbit + + """ + if self._transitivity_degree is None: + n = self.degree + G = self + # if G is k-transitive, a tuple (a_0,..,a_k) + # can be brought to (b_0,...,b_(k-1), b_k) + # where b_0,...,b_(k-1) are fixed points; + # consider the group G_k which stabilizes b_0,...,b_(k-1) + # if G_k is transitive on the subset excluding b_0,...,b_(k-1) + # then G is (k+1)-transitive + for i in range(n): + orb = G.orbit(i) + if len(orb) != n - i: + self._transitivity_degree = i + return i + G = G.stabilizer(i) + self._transitivity_degree = n + return n + else: + return self._transitivity_degree + + def _p_elements_group(self, p): + ''' + For an abelian p-group, return the subgroup consisting of + all elements of order p (and the identity) + + ''' + gens = self.generators[:] + gens = sorted(gens, key=lambda x: x.order(), reverse=True) + gens_p = [g**(g.order()/p) for g in gens] + gens_r = [] + for i in range(len(gens)): + x = gens[i] + x_order = x.order() + # x_p has order p + x_p = x**(x_order/p) + if i > 0: + P = PermutationGroup(gens_p[:i]) + else: + P = PermutationGroup(self.identity) + if x**(x_order/p) not in P: + gens_r.append(x**(x_order/p)) + else: + # replace x by an element of order (x.order()/p) + # so that gens still generates G + g = P.generator_product(x_p, original=True) + for s in g: + x = x*s**-1 + x_order = x_order/p + # insert x to gens so that the sorting is preserved + del gens[i] + del gens_p[i] + j = i - 1 + while j < len(gens) and gens[j].order() >= x_order: + j += 1 + gens = gens[:j] + [x] + gens[j:] + gens_p = gens_p[:j] + [x] + gens_p[j:] + return PermutationGroup(gens_r) + + def _sylow_alt_sym(self, p): + ''' + Return a p-Sylow subgroup of a symmetric or an + alternating group. + + Explanation + =========== + + The algorithm for this is hinted at in [1], Chapter 4, + Exercise 4. + + For Sym(n) with n = p^i, the idea is as follows. Partition + the interval [0..n-1] into p equal parts, each of length p^(i-1): + [0..p^(i-1)-1], [p^(i-1)..2*p^(i-1)-1]...[(p-1)*p^(i-1)..p^i-1]. + Find a p-Sylow subgroup of Sym(p^(i-1)) (treated as a subgroup + of ``self``) acting on each of the parts. Call the subgroups + P_1, P_2...P_p. The generators for the subgroups P_2...P_p + can be obtained from those of P_1 by applying a "shifting" + permutation to them, that is, a permutation mapping [0..p^(i-1)-1] + to the second part (the other parts are obtained by using the shift + multiple times). The union of this permutation and the generators + of P_1 is a p-Sylow subgroup of ``self``. + + For n not equal to a power of p, partition + [0..n-1] in accordance with how n would be written in base p. + E.g. for p=2 and n=11, 11 = 2^3 + 2^2 + 1 so the partition + is [[0..7], [8..9], {10}]. To generate a p-Sylow subgroup, + take the union of the generators for each of the parts. + For the above example, {(0 1), (0 2)(1 3), (0 4), (1 5)(2 7)} + from the first part, {(8 9)} from the second part and + nothing from the third. This gives 4 generators in total, and + the subgroup they generate is p-Sylow. + + Alternating groups are treated the same except when p=2. In this + case, (0 1)(s s+1) should be added for an appropriate s (the start + of a part) for each part in the partitions. + + See Also + ======== + + sylow_subgroup, is_alt_sym + + ''' + n = self.degree + gens = [] + identity = Permutation(n-1) + # the case of 2-sylow subgroups of alternating groups + # needs special treatment + alt = p == 2 and all(g.is_even for g in self.generators) + + # find the presentation of n in base p + coeffs = [] + m = n + while m > 0: + coeffs.append(m % p) + m = m // p + + power = len(coeffs)-1 + # for a symmetric group, gens[:i] is the generating + # set for a p-Sylow subgroup on [0..p**(i-1)-1]. For + # alternating groups, the same is given by gens[:2*(i-1)] + for i in range(1, power+1): + if i == 1 and alt: + # (0 1) shouldn't be added for alternating groups + continue + gen = Permutation([(j + p**(i-1)) % p**i for j in range(p**i)]) + gens.append(identity*gen) + if alt: + gen = Permutation(0, 1)*gen*Permutation(0, 1)*gen + gens.append(gen) + + # the first point in the current part (see the algorithm + # description in the docstring) + start = 0 + + while power > 0: + a = coeffs[power] + + # make the permutation shifting the start of the first + # part ([0..p^i-1] for some i) to the current one + for _ in range(a): + shift = Permutation() + if start > 0: + for i in range(p**power): + shift = shift(i, start + i) + + if alt: + gen = Permutation(0, 1)*shift*Permutation(0, 1)*shift + gens.append(gen) + j = 2*(power - 1) + else: + j = power + + for i, gen in enumerate(gens[:j]): + if alt and i % 2 == 1: + continue + # shift the generator to the start of the + # partition part + gen = shift*gen*shift + gens.append(gen) + + start += p**power + power = power-1 + + return gens + + def sylow_subgroup(self, p): + ''' + Return a p-Sylow subgroup of the group. + + The algorithm is described in [1], Chapter 4, Section 7 + + Examples + ======== + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> from sympy.combinatorics.named_groups import SymmetricGroup + >>> from sympy.combinatorics.named_groups import AlternatingGroup + + >>> D = DihedralGroup(6) + >>> S = D.sylow_subgroup(2) + >>> S.order() + 4 + >>> G = SymmetricGroup(6) + >>> S = G.sylow_subgroup(5) + >>> S.order() + 5 + + >>> G1 = AlternatingGroup(3) + >>> G2 = AlternatingGroup(5) + >>> G3 = AlternatingGroup(9) + + >>> S1 = G1.sylow_subgroup(3) + >>> S2 = G2.sylow_subgroup(3) + >>> S3 = G3.sylow_subgroup(3) + + >>> len1 = len(S1.lower_central_series()) + >>> len2 = len(S2.lower_central_series()) + >>> len3 = len(S3.lower_central_series()) + + >>> len1 == len2 + True + >>> len1 < len3 + True + + ''' + from sympy.combinatorics.homomorphisms import ( + orbit_homomorphism, block_homomorphism) + + if not isprime(p): + raise ValueError("p must be a prime") + + def is_p_group(G): + # check if the order of G is a power of p + # and return the power + m = G.order() + n = 0 + while m % p == 0: + m = m/p + n += 1 + if m == 1: + return True, n + return False, n + + def _sylow_reduce(mu, nu): + # reduction based on two homomorphisms + # mu and nu with trivially intersecting + # kernels + Q = mu.image().sylow_subgroup(p) + Q = mu.invert_subgroup(Q) + nu = nu.restrict_to(Q) + R = nu.image().sylow_subgroup(p) + return nu.invert_subgroup(R) + + order = self.order() + if order % p != 0: + return PermutationGroup([self.identity]) + p_group, n = is_p_group(self) + if p_group: + return self + + if self.is_alt_sym(): + return PermutationGroup(self._sylow_alt_sym(p)) + + # if there is a non-trivial orbit with size not divisible + # by p, the sylow subgroup is contained in its stabilizer + # (by orbit-stabilizer theorem) + orbits = self.orbits() + non_p_orbits = [o for o in orbits if len(o) % p != 0 and len(o) != 1] + if non_p_orbits: + G = self.stabilizer(list(non_p_orbits[0]).pop()) + return G.sylow_subgroup(p) + + if not self.is_transitive(): + # apply _sylow_reduce to orbit actions + orbits = sorted(orbits, key=len) + omega1 = orbits.pop() + omega2 = orbits[0].union(*orbits) + mu = orbit_homomorphism(self, omega1) + nu = orbit_homomorphism(self, omega2) + return _sylow_reduce(mu, nu) + + blocks = self.minimal_blocks() + if len(blocks) > 1: + # apply _sylow_reduce to block system actions + mu = block_homomorphism(self, blocks[0]) + nu = block_homomorphism(self, blocks[1]) + return _sylow_reduce(mu, nu) + elif len(blocks) == 1: + block = list(blocks)[0] + if any(e != 0 for e in block): + # self is imprimitive + mu = block_homomorphism(self, block) + if not is_p_group(mu.image())[0]: + S = mu.image().sylow_subgroup(p) + return mu.invert_subgroup(S).sylow_subgroup(p) + + # find an element of order p + g = self.random() + g_order = g.order() + while g_order % p != 0 or g_order == 0: + g = self.random() + g_order = g.order() + g = g**(g_order // p) + if order % p**2 != 0: + return PermutationGroup(g) + + C = self.centralizer(g) + while C.order() % p**n != 0: + S = C.sylow_subgroup(p) + s_order = S.order() + Z = S.center() + P = Z._p_elements_group(p) + h = P.random() + C_h = self.centralizer(h) + while C_h.order() % p*s_order != 0: + h = P.random() + C_h = self.centralizer(h) + C = C_h + + return C.sylow_subgroup(p) + + def _block_verify(self, L, alpha): + delta = sorted(self.orbit(alpha)) + # p[i] will be the number of the block + # delta[i] belongs to + p = [-1]*len(delta) + blocks = [-1]*len(delta) + + B = [[]] # future list of blocks + u = [0]*len(delta) # u[i] in L s.t. alpha^u[i] = B[0][i] + + t = L.orbit_transversal(alpha, pairs=True) + for a, beta in t: + B[0].append(a) + i_a = delta.index(a) + p[i_a] = 0 + blocks[i_a] = alpha + u[i_a] = beta + + rho = 0 + m = 0 # number of blocks - 1 + + while rho <= m: + beta = B[rho][0] + for g in self.generators: + d = beta^g + i_d = delta.index(d) + sigma = p[i_d] + if sigma < 0: + # define a new block + m += 1 + sigma = m + u[i_d] = u[delta.index(beta)]*g + p[i_d] = sigma + rep = d + blocks[i_d] = rep + newb = [rep] + for gamma in B[rho][1:]: + i_gamma = delta.index(gamma) + d = gamma^g + i_d = delta.index(d) + if p[i_d] < 0: + u[i_d] = u[i_gamma]*g + p[i_d] = sigma + blocks[i_d] = rep + newb.append(d) + else: + # B[rho] is not a block + s = u[i_gamma]*g*u[i_d]**(-1) + return False, s + + B.append(newb) + else: + for h in B[rho][1:]: + if h^g not in B[sigma]: + # B[rho] is not a block + s = u[delta.index(beta)]*g*u[i_d]**(-1) + return False, s + rho += 1 + + return True, blocks + + def _verify(H, K, phi, z, alpha): + ''' + Return a list of relators ``rels`` in generators ``gens`_h` that + are mapped to ``H.generators`` by ``phi`` so that given a finite + presentation of ``K`` on a subset of ``gens_h`` + is a finite presentation of ``H``. + + Explanation + =========== + + ``H`` should be generated by the union of ``K.generators`` and ``z`` + (a single generator), and ``H.stabilizer(alpha) == K``; ``phi`` is a + canonical injection from a free group into a permutation group + containing ``H``. + + The algorithm is described in [1], Chapter 6. + + Examples + ======== + + >>> from sympy.combinatorics import free_group, Permutation, PermutationGroup + >>> from sympy.combinatorics.homomorphisms import homomorphism + >>> from sympy.combinatorics.fp_groups import FpGroup + + >>> H = PermutationGroup(Permutation(0, 2), Permutation (1, 5)) + >>> K = PermutationGroup(Permutation(5)(0, 2)) + >>> F = free_group("x_0 x_1")[0] + >>> gens = F.generators + >>> phi = homomorphism(F, H, F.generators, H.generators) + >>> rels_k = [gens[0]**2] # relators for presentation of K + >>> z= Permutation(1, 5) + >>> check, rels_h = H._verify(K, phi, z, 1) + >>> check + True + >>> rels = rels_k + rels_h + >>> G = FpGroup(F, rels) # presentation of H + >>> G.order() == H.order() + True + + See also + ======== + + strong_presentation, presentation, stabilizer + + ''' + + orbit = H.orbit(alpha) + beta = alpha^(z**-1) + + K_beta = K.stabilizer(beta) + + # orbit representatives of K_beta + gammas = [alpha, beta] + orbits = list({tuple(K_beta.orbit(o)) for o in orbit}) + orbit_reps = [orb[0] for orb in orbits] + for rep in orbit_reps: + if rep not in gammas: + gammas.append(rep) + + # orbit transversal of K + betas = [alpha, beta] + transversal = {alpha: phi.invert(H.identity), beta: phi.invert(z**-1)} + + for s, g in K.orbit_transversal(beta, pairs=True): + if s not in transversal: + transversal[s] = transversal[beta]*phi.invert(g) + + + union = K.orbit(alpha).union(K.orbit(beta)) + while (len(union) < len(orbit)): + for gamma in gammas: + if gamma in union: + r = gamma^z + if r not in union: + betas.append(r) + transversal[r] = transversal[gamma]*phi.invert(z) + for s, g in K.orbit_transversal(r, pairs=True): + if s not in transversal: + transversal[s] = transversal[r]*phi.invert(g) + union = union.union(K.orbit(r)) + break + + # compute relators + rels = [] + + for b in betas: + k_gens = K.stabilizer(b).generators + for y in k_gens: + new_rel = transversal[b] + gens = K.generator_product(y, original=True) + for g in gens[::-1]: + new_rel = new_rel*phi.invert(g) + new_rel = new_rel*transversal[b]**-1 + + perm = phi(new_rel) + try: + gens = K.generator_product(perm, original=True) + except ValueError: + return False, perm + for g in gens: + new_rel = new_rel*phi.invert(g)**-1 + if new_rel not in rels: + rels.append(new_rel) + + for gamma in gammas: + new_rel = transversal[gamma]*phi.invert(z)*transversal[gamma^z]**-1 + perm = phi(new_rel) + try: + gens = K.generator_product(perm, original=True) + except ValueError: + return False, perm + for g in gens: + new_rel = new_rel*phi.invert(g)**-1 + if new_rel not in rels: + rels.append(new_rel) + + return True, rels + + def strong_presentation(self): + ''' + Return a strong finite presentation of group. The generators + of the returned group are in the same order as the strong + generators of group. + + The algorithm is based on Sims' Verify algorithm described + in [1], Chapter 6. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> P = DihedralGroup(4) + >>> G = P.strong_presentation() + >>> P.order() == G.order() + True + + See Also + ======== + + presentation, _verify + + ''' + from sympy.combinatorics.fp_groups import (FpGroup, + simplify_presentation) + from sympy.combinatorics.free_groups import free_group + from sympy.combinatorics.homomorphisms import (block_homomorphism, + homomorphism, GroupHomomorphism) + + strong_gens = self.strong_gens[:] + stabs = self.basic_stabilizers[:] + base = self.base[:] + + # injection from a free group on len(strong_gens) + # generators into G + gen_syms = [('x_%d'%i) for i in range(len(strong_gens))] + F = free_group(', '.join(gen_syms))[0] + phi = homomorphism(F, self, F.generators, strong_gens) + + H = PermutationGroup(self.identity) + while stabs: + alpha = base.pop() + K = H + H = stabs.pop() + new_gens = [g for g in H.generators if g not in K] + + if K.order() == 1: + z = new_gens.pop() + rels = [F.generators[-1]**z.order()] + intermediate_gens = [z] + K = PermutationGroup(intermediate_gens) + + # add generators one at a time building up from K to H + while new_gens: + z = new_gens.pop() + intermediate_gens = [z] + intermediate_gens + K_s = PermutationGroup(intermediate_gens) + orbit = K_s.orbit(alpha) + orbit_k = K.orbit(alpha) + + # split into cases based on the orbit of K_s + if orbit_k == orbit: + if z in K: + rel = phi.invert(z) + perm = z + else: + t = K.orbit_rep(alpha, alpha^z) + rel = phi.invert(z)*phi.invert(t)**-1 + perm = z*t**-1 + for g in K.generator_product(perm, original=True): + rel = rel*phi.invert(g)**-1 + new_rels = [rel] + elif len(orbit_k) == 1: + # `success` is always true because `strong_gens` + # and `base` are already a verified BSGS. Later + # this could be changed to start with a randomly + # generated (potential) BSGS, and then new elements + # would have to be appended to it when `success` + # is false. + success, new_rels = K_s._verify(K, phi, z, alpha) + else: + # K.orbit(alpha) should be a block + # under the action of K_s on K_s.orbit(alpha) + check, block = K_s._block_verify(K, alpha) + if check: + # apply _verify to the action of K_s + # on the block system; for convenience, + # add the blocks as additional points + # that K_s should act on + t = block_homomorphism(K_s, block) + m = t.codomain.degree # number of blocks + d = K_s.degree + + # conjugating with p will shift + # permutations in t.image() to + # higher numbers, e.g. + # p*(0 1)*p = (m m+1) + p = Permutation() + for i in range(m): + p *= Permutation(i, i+d) + + t_img = t.images + # combine generators of K_s with their + # action on the block system + images = {g: g*p*t_img[g]*p for g in t_img} + for g in self.strong_gens[:-len(K_s.generators)]: + images[g] = g + K_s_act = PermutationGroup(list(images.values())) + f = GroupHomomorphism(self, K_s_act, images) + + K_act = PermutationGroup([f(g) for g in K.generators]) + success, new_rels = K_s_act._verify(K_act, f.compose(phi), f(z), d) + + for n in new_rels: + if n not in rels: + rels.append(n) + K = K_s + + group = FpGroup(F, rels) + return simplify_presentation(group) + + def presentation(self, eliminate_gens=True): + ''' + Return an `FpGroup` presentation of the group. + + The algorithm is described in [1], Chapter 6.1. + + ''' + from sympy.combinatorics.fp_groups import (FpGroup, + simplify_presentation) + from sympy.combinatorics.coset_table import CosetTable + from sympy.combinatorics.free_groups import free_group + from sympy.combinatorics.homomorphisms import homomorphism + + if self._fp_presentation: + return self._fp_presentation + + def _factor_group_by_rels(G, rels): + if isinstance(G, FpGroup): + rels.extend(G.relators) + return FpGroup(G.free_group, list(set(rels))) + return FpGroup(G, rels) + + gens = self.generators + len_g = len(gens) + + if len_g == 1: + order = gens[0].order() + # handle the trivial group + if order == 1: + return free_group([])[0] + F, x = free_group('x') + return FpGroup(F, [x**order]) + + if self.order() > 20: + half_gens = self.generators[0:(len_g+1)//2] + else: + half_gens = [] + H = PermutationGroup(half_gens) + H_p = H.presentation() + + len_h = len(H_p.generators) + + C = self.coset_table(H) + n = len(C) # subgroup index + + gen_syms = [('x_%d'%i) for i in range(len(gens))] + F = free_group(', '.join(gen_syms))[0] + + # mapping generators of H_p to those of F + images = [F.generators[i] for i in range(len_h)] + R = homomorphism(H_p, F, H_p.generators, images, check=False) + + # rewrite relators + rels = R(H_p.relators) + G_p = FpGroup(F, rels) + + # injective homomorphism from G_p into self + T = homomorphism(G_p, self, G_p.generators, gens) + + C_p = CosetTable(G_p, []) + + C_p.table = [[None]*(2*len_g) for i in range(n)] + + # initiate the coset transversal + transversal = [None]*n + transversal[0] = G_p.identity + + # fill in the coset table as much as possible + for i in range(2*len_h): + C_p.table[0][i] = 0 + + gamma = 1 + for alpha, x in product(range(n), range(2*len_g)): + beta = C[alpha][x] + if beta == gamma: + gen = G_p.generators[x//2]**((-1)**(x % 2)) + transversal[beta] = transversal[alpha]*gen + C_p.table[alpha][x] = beta + C_p.table[beta][x + (-1)**(x % 2)] = alpha + gamma += 1 + if gamma == n: + break + + C_p.p = list(range(n)) + beta = x = 0 + + while not C_p.is_complete(): + # find the first undefined entry + while C_p.table[beta][x] == C[beta][x]: + x = (x + 1) % (2*len_g) + if x == 0: + beta = (beta + 1) % n + + # define a new relator + gen = G_p.generators[x//2]**((-1)**(x % 2)) + new_rel = transversal[beta]*gen*transversal[C[beta][x]]**-1 + perm = T(new_rel) + nxt = G_p.identity + for s in H.generator_product(perm, original=True): + nxt = nxt*T.invert(s)**-1 + new_rel = new_rel*nxt + + # continue coset enumeration + G_p = _factor_group_by_rels(G_p, [new_rel]) + C_p.scan_and_fill(0, new_rel) + C_p = G_p.coset_enumeration([], strategy="coset_table", + draft=C_p, max_cosets=n, incomplete=True) + + self._fp_presentation = simplify_presentation(G_p) + return self._fp_presentation + + def polycyclic_group(self): + """ + Return the PolycyclicGroup instance with below parameters: + + Explanation + =========== + + * pc_sequence : Polycyclic sequence is formed by collecting all + the missing generators between the adjacent groups in the + derived series of given permutation group. + + * pc_series : Polycyclic series is formed by adding all the missing + generators of ``der[i+1]`` in ``der[i]``, where ``der`` represents + the derived series. + + * relative_order : A list, computed by the ratio of adjacent groups in + pc_series. + + """ + from sympy.combinatorics.pc_groups import PolycyclicGroup + if not self.is_polycyclic: + raise ValueError("The group must be solvable") + + der = self.derived_series() + pc_series = [] + pc_sequence = [] + relative_order = [] + pc_series.append(der[-1]) + der.reverse() + + for i in range(len(der)-1): + H = der[i] + for g in der[i+1].generators: + if g not in H: + H = PermutationGroup([g] + H.generators) + pc_series.insert(0, H) + pc_sequence.insert(0, g) + + G1 = pc_series[0].order() + G2 = pc_series[1].order() + relative_order.insert(0, G1 // G2) + + return PolycyclicGroup(pc_sequence, pc_series, relative_order, collector=None) + + +def _orbit(degree, generators, alpha, action='tuples'): + r"""Compute the orbit of alpha `\{g(\alpha) | g \in G\}` as a set. + + Explanation + =========== + + The time complexity of the algorithm used here is `O(|Orb|*r)` where + `|Orb|` is the size of the orbit and ``r`` is the number of generators of + the group. For a more detailed analysis, see [1], p.78, [2], pp. 19-21. + Here alpha can be a single point, or a list of points. + + If alpha is a single point, the ordinary orbit is computed. + if alpha is a list of points, there are three available options: + + 'union' - computes the union of the orbits of the points in the list + 'tuples' - computes the orbit of the list interpreted as an ordered + tuple under the group action ( i.e., g((1, 2, 3)) = (g(1), g(2), g(3)) ) + 'sets' - computes the orbit of the list interpreted as a sets + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> from sympy.combinatorics.perm_groups import _orbit + >>> a = Permutation([1, 2, 0, 4, 5, 6, 3]) + >>> G = PermutationGroup([a]) + >>> _orbit(G.degree, G.generators, 0) + {0, 1, 2} + >>> _orbit(G.degree, G.generators, [0, 4], 'union') + {0, 1, 2, 3, 4, 5, 6} + + See Also + ======== + + orbit, orbit_transversal + + """ + if not hasattr(alpha, '__getitem__'): + alpha = [alpha] + + gens = [x._array_form for x in generators] + if len(alpha) == 1 or action == 'union': + orb = alpha + used = [False]*degree + for el in alpha: + used[el] = True + for b in orb: + for gen in gens: + temp = gen[b] + if used[temp] == False: + orb.append(temp) + used[temp] = True + return set(orb) + elif action == 'tuples': + alpha = tuple(alpha) + orb = [alpha] + used = {alpha} + for b in orb: + for gen in gens: + temp = tuple([gen[x] for x in b]) + if temp not in used: + orb.append(temp) + used.add(temp) + return set(orb) + elif action == 'sets': + alpha = frozenset(alpha) + orb = [alpha] + used = {alpha} + for b in orb: + for gen in gens: + temp = frozenset([gen[x] for x in b]) + if temp not in used: + orb.append(temp) + used.add(temp) + return {tuple(x) for x in orb} + + +def _orbits(degree, generators): + """Compute the orbits of G. + + If ``rep=False`` it returns a list of sets else it returns a list of + representatives of the orbits + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy.combinatorics.perm_groups import _orbits + >>> a = Permutation([0, 2, 1]) + >>> b = Permutation([1, 0, 2]) + >>> _orbits(a.size, [a, b]) + [{0, 1, 2}] + """ + + orbs = [] + sorted_I = list(range(degree)) + I = set(sorted_I) + while I: + i = sorted_I[0] + orb = _orbit(degree, generators, i) + orbs.append(orb) + # remove all indices that are in this orbit + I -= orb + sorted_I = [i for i in sorted_I if i not in orb] + return orbs + + +def _orbit_transversal(degree, generators, alpha, pairs, af=False, slp=False): + r"""Computes a transversal for the orbit of ``alpha`` as a set. + + Explanation + =========== + + generators generators of the group ``G`` + + For a permutation group ``G``, a transversal for the orbit + `Orb = \{g(\alpha) | g \in G\}` is a set + `\{g_\beta | g_\beta(\alpha) = \beta\}` for `\beta \in Orb`. + Note that there may be more than one possible transversal. + If ``pairs`` is set to ``True``, it returns the list of pairs + `(\beta, g_\beta)`. For a proof of correctness, see [1], p.79 + + if ``af`` is ``True``, the transversal elements are given in + array form. + + If `slp` is `True`, a dictionary `{beta: slp_beta}` is returned + for `\beta \in Orb` where `slp_beta` is a list of indices of the + generators in `generators` s.t. if `slp_beta = [i_1 \dots i_n]` + `g_\beta = generators[i_n] \times \dots \times generators[i_1]`. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> from sympy.combinatorics.perm_groups import _orbit_transversal + >>> G = DihedralGroup(6) + >>> _orbit_transversal(G.degree, G.generators, 0, False) + [(5), (0 1 2 3 4 5), (0 5)(1 4)(2 3), (0 2 4)(1 3 5), (5)(0 4)(1 3), (0 3)(1 4)(2 5)] + """ + + tr = [(alpha, list(range(degree)))] + slp_dict = {alpha: []} + used = [False]*degree + used[alpha] = True + gens = [x._array_form for x in generators] + for x, px in tr: + px_slp = slp_dict[x] + for gen in gens: + temp = gen[x] + if used[temp] == False: + slp_dict[temp] = [gens.index(gen)] + px_slp + tr.append((temp, _af_rmul(gen, px))) + used[temp] = True + if pairs: + if not af: + tr = [(x, _af_new(y)) for x, y in tr] + if not slp: + return tr + return tr, slp_dict + + if af: + tr = [y for _, y in tr] + if not slp: + return tr + return tr, slp_dict + + tr = [_af_new(y) for _, y in tr] + if not slp: + return tr + return tr, slp_dict + + +def _stabilizer(degree, generators, alpha): + r"""Return the stabilizer subgroup of ``alpha``. + + Explanation + =========== + + The stabilizer of `\alpha` is the group `G_\alpha = + \{g \in G | g(\alpha) = \alpha\}`. + For a proof of correctness, see [1], p.79. + + degree : degree of G + generators : generators of G + + Examples + ======== + + >>> from sympy.combinatorics.perm_groups import _stabilizer + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> G = DihedralGroup(6) + >>> _stabilizer(G.degree, G.generators, 5) + [(5)(0 4)(1 3), (5)] + + See Also + ======== + + orbit + + """ + orb = [alpha] + table = {alpha: list(range(degree))} + table_inv = {alpha: list(range(degree))} + used = [False]*degree + used[alpha] = True + gens = [x._array_form for x in generators] + stab_gens = [] + for b in orb: + for gen in gens: + temp = gen[b] + if used[temp] is False: + gen_temp = _af_rmul(gen, table[b]) + orb.append(temp) + table[temp] = gen_temp + table_inv[temp] = _af_invert(gen_temp) + used[temp] = True + else: + schreier_gen = _af_rmuln(table_inv[temp], gen, table[b]) + if schreier_gen not in stab_gens: + stab_gens.append(schreier_gen) + return [_af_new(x) for x in stab_gens] + + +PermGroup = PermutationGroup + + +class SymmetricPermutationGroup(Basic): + """ + The class defining the lazy form of SymmetricGroup. + + deg : int + + """ + def __new__(cls, deg): + deg = _sympify(deg) + obj = Basic.__new__(cls, deg) + return obj + + def __init__(self, *args, **kwargs): + self._deg = self.args[0] + self._order = None + + def __contains__(self, i): + """Return ``True`` if *i* is contained in SymmetricPermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, SymmetricPermutationGroup + >>> G = SymmetricPermutationGroup(4) + >>> Permutation(1, 2, 3) in G + True + + """ + if not isinstance(i, Permutation): + raise TypeError("A SymmetricPermutationGroup contains only Permutations as " + "elements, not elements of type %s" % type(i)) + return i.size == self.degree + + def order(self): + """ + Return the order of the SymmetricPermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricPermutationGroup + >>> G = SymmetricPermutationGroup(4) + >>> G.order() + 24 + """ + if self._order is not None: + return self._order + n = self._deg + self._order = factorial(n) + return self._order + + @property + def degree(self): + """ + Return the degree of the SymmetricPermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricPermutationGroup + >>> G = SymmetricPermutationGroup(4) + >>> G.degree + 4 + + """ + return self._deg + + @property + def identity(self): + ''' + Return the identity element of the SymmetricPermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricPermutationGroup + >>> G = SymmetricPermutationGroup(4) + >>> G.identity() + (3) + + ''' + return _af_new(list(range(self._deg))) + + +class Coset(Basic): + """A left coset of a permutation group with respect to an element. + + Parameters + ========== + + g : Permutation + + H : PermutationGroup + + dir : "+" or "-", If not specified by default it will be "+" + here ``dir`` specified the type of coset "+" represent the + right coset and "-" represent the left coset. + + G : PermutationGroup, optional + The group which contains *H* as its subgroup and *g* as its + element. + + If not specified, it would automatically become a symmetric + group ``SymmetricPermutationGroup(g.size)`` and + ``SymmetricPermutationGroup(H.degree)`` if ``g.size`` and ``H.degree`` + are matching.``SymmetricPermutationGroup`` is a lazy form of SymmetricGroup + used for representation purpose. + + """ + + def __new__(cls, g, H, G=None, dir="+"): + g = _sympify(g) + if not isinstance(g, Permutation): + raise NotImplementedError + + H = _sympify(H) + if not isinstance(H, PermutationGroup): + raise NotImplementedError + + if G is not None: + G = _sympify(G) + if not isinstance(G, (PermutationGroup, SymmetricPermutationGroup)): + raise NotImplementedError + if not H.is_subgroup(G): + raise ValueError("{} must be a subgroup of {}.".format(H, G)) + if g not in G: + raise ValueError("{} must be an element of {}.".format(g, G)) + else: + g_size = g.size + h_degree = H.degree + if g_size != h_degree: + raise ValueError( + "The size of the permutation {} and the degree of " + "the permutation group {} should be matching " + .format(g, H)) + G = SymmetricPermutationGroup(g.size) + + if isinstance(dir, str): + dir = Symbol(dir) + elif not isinstance(dir, Symbol): + raise TypeError("dir must be of type basestring or " + "Symbol, not %s" % type(dir)) + if str(dir) not in ('+', '-'): + raise ValueError("dir must be one of '+' or '-' not %s" % dir) + obj = Basic.__new__(cls, g, H, G, dir) + return obj + + def __init__(self, *args, **kwargs): + self._dir = self.args[3] + + @property + def is_left_coset(self): + """ + Check if the coset is left coset that is ``gH``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup, Coset + >>> a = Permutation(1, 2) + >>> b = Permutation(0, 1) + >>> G = PermutationGroup([a, b]) + >>> cst = Coset(a, G, dir="-") + >>> cst.is_left_coset + True + + """ + return str(self._dir) == '-' + + @property + def is_right_coset(self): + """ + Check if the coset is right coset that is ``Hg``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, PermutationGroup, Coset + >>> a = Permutation(1, 2) + >>> b = Permutation(0, 1) + >>> G = PermutationGroup([a, b]) + >>> cst = Coset(a, G, dir="+") + >>> cst.is_right_coset + True + + """ + return str(self._dir) == '+' + + def as_list(self): + """ + Return all the elements of coset in the form of list. + """ + g = self.args[0] + H = self.args[1] + cst = [] + if str(self._dir) == '+': + for h in H.elements: + cst.append(h*g) + else: + for h in H.elements: + cst.append(g*h) + return cst diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/permutations.py b/phivenv/Lib/site-packages/sympy/combinatorics/permutations.py new file mode 100644 index 0000000000000000000000000000000000000000..3718467b69cbab2b9b0dd73e8fa160cceb0324bf --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/permutations.py @@ -0,0 +1,3114 @@ +import random +from collections import defaultdict +from collections.abc import Iterable +from functools import reduce + +from sympy.core.parameters import global_parameters +from sympy.core.basic import Atom +from sympy.core.expr import Expr +from sympy.core.numbers import int_valued +from sympy.core.numbers import Integer +from sympy.core.sympify import _sympify +from sympy.matrices import zeros +from sympy.polys.polytools import lcm +from sympy.printing.repr import srepr +from sympy.utilities.iterables import (flatten, has_variety, minlex, + has_dups, runs, is_sequence) +from sympy.utilities.misc import as_int +from mpmath.libmp.libintmath import ifac +from sympy.multipledispatch import dispatch + +def _af_rmul(a, b): + """ + Return the product b*a; input and output are array forms. The ith value + is a[b[i]]. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import _af_rmul, Permutation + + >>> a, b = [1, 0, 2], [0, 2, 1] + >>> _af_rmul(a, b) + [1, 2, 0] + >>> [a[b[i]] for i in range(3)] + [1, 2, 0] + + This handles the operands in reverse order compared to the ``*`` operator: + + >>> a = Permutation(a) + >>> b = Permutation(b) + >>> list(a*b) + [2, 0, 1] + >>> [b(a(i)) for i in range(3)] + [2, 0, 1] + + See Also + ======== + + rmul, _af_rmuln + """ + return [a[i] for i in b] + + +def _af_rmuln(*abc): + """ + Given [a, b, c, ...] return the product of ...*c*b*a using array forms. + The ith value is a[b[c[i]]]. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import _af_rmul, Permutation + + >>> a, b = [1, 0, 2], [0, 2, 1] + >>> _af_rmul(a, b) + [1, 2, 0] + >>> [a[b[i]] for i in range(3)] + [1, 2, 0] + + This handles the operands in reverse order compared to the ``*`` operator: + + >>> a = Permutation(a); b = Permutation(b) + >>> list(a*b) + [2, 0, 1] + >>> [b(a(i)) for i in range(3)] + [2, 0, 1] + + See Also + ======== + + rmul, _af_rmul + """ + a = abc + m = len(a) + if m == 3: + p0, p1, p2 = a + return [p0[p1[i]] for i in p2] + if m == 4: + p0, p1, p2, p3 = a + return [p0[p1[p2[i]]] for i in p3] + if m == 5: + p0, p1, p2, p3, p4 = a + return [p0[p1[p2[p3[i]]]] for i in p4] + if m == 6: + p0, p1, p2, p3, p4, p5 = a + return [p0[p1[p2[p3[p4[i]]]]] for i in p5] + if m == 7: + p0, p1, p2, p3, p4, p5, p6 = a + return [p0[p1[p2[p3[p4[p5[i]]]]]] for i in p6] + if m == 8: + p0, p1, p2, p3, p4, p5, p6, p7 = a + return [p0[p1[p2[p3[p4[p5[p6[i]]]]]]] for i in p7] + if m == 1: + return a[0][:] + if m == 2: + a, b = a + return [a[i] for i in b] + if m == 0: + raise ValueError("String must not be empty") + p0 = _af_rmuln(*a[:m//2]) + p1 = _af_rmuln(*a[m//2:]) + return [p0[i] for i in p1] + + +def _af_parity(pi): + """ + Computes the parity of a permutation in array form. + + Explanation + =========== + + The parity of a permutation reflects the parity of the + number of inversions in the permutation, i.e., the + number of pairs of x and y such that x > y but p[x] < p[y]. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import _af_parity + >>> _af_parity([0, 1, 2, 3]) + 0 + >>> _af_parity([3, 2, 0, 1]) + 1 + + See Also + ======== + + Permutation + """ + n = len(pi) + a = [0] * n + c = 0 + for j in range(n): + if a[j] == 0: + c += 1 + a[j] = 1 + i = j + while pi[i] != j: + i = pi[i] + a[i] = 1 + return (n - c) % 2 + + +def _af_invert(a): + """ + Finds the inverse, ~A, of a permutation, A, given in array form. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import _af_invert, _af_rmul + >>> A = [1, 2, 0, 3] + >>> _af_invert(A) + [2, 0, 1, 3] + >>> _af_rmul(_, A) + [0, 1, 2, 3] + + See Also + ======== + + Permutation, __invert__ + """ + inv_form = [0] * len(a) + for i, ai in enumerate(a): + inv_form[ai] = i + return inv_form + + +def _af_pow(a, n): + """ + Routine for finding powers of a permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy.combinatorics.permutations import _af_pow + >>> p = Permutation([2, 0, 3, 1]) + >>> p.order() + 4 + >>> _af_pow(p._array_form, 4) + [0, 1, 2, 3] + """ + if n == 0: + return list(range(len(a))) + if n < 0: + return _af_pow(_af_invert(a), -n) + if n == 1: + return a[:] + elif n == 2: + b = [a[i] for i in a] + elif n == 3: + b = [a[a[i]] for i in a] + elif n == 4: + b = [a[a[a[i]]] for i in a] + else: + # use binary multiplication + b = list(range(len(a))) + while 1: + if n & 1: + b = [b[i] for i in a] + n -= 1 + if not n: + break + if n % 4 == 0: + a = [a[a[a[i]]] for i in a] + n = n // 4 + elif n % 2 == 0: + a = [a[i] for i in a] + n = n // 2 + return b + + +def _af_commutes_with(a, b): + """ + Checks if the two permutations with array forms + given by ``a`` and ``b`` commute. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import _af_commutes_with + >>> _af_commutes_with([1, 2, 0], [0, 2, 1]) + False + + See Also + ======== + + Permutation, commutes_with + """ + return not any(a[b[i]] != b[a[i]] for i in range(len(a) - 1)) + + +class Cycle(dict): + """ + Wrapper around dict which provides the functionality of a disjoint cycle. + + Explanation + =========== + + A cycle shows the rule to use to move subsets of elements to obtain + a permutation. The Cycle class is more flexible than Permutation in + that 1) all elements need not be present in order to investigate how + multiple cycles act in sequence and 2) it can contain singletons: + + >>> from sympy.combinatorics.permutations import Perm, Cycle + + A Cycle will automatically parse a cycle given as a tuple on the rhs: + + >>> Cycle(1, 2)(2, 3) + (1 3 2) + + The identity cycle, Cycle(), can be used to start a product: + + >>> Cycle()(1, 2)(2, 3) + (1 3 2) + + The array form of a Cycle can be obtained by calling the list + method (or passing it to the list function) and all elements from + 0 will be shown: + + >>> a = Cycle(1, 2) + >>> a.list() + [0, 2, 1] + >>> list(a) + [0, 2, 1] + + If a larger (or smaller) range is desired use the list method and + provide the desired size -- but the Cycle cannot be truncated to + a size smaller than the largest element that is out of place: + + >>> b = Cycle(2, 4)(1, 2)(3, 1, 4)(1, 3) + >>> b.list() + [0, 2, 1, 3, 4] + >>> b.list(b.size + 1) + [0, 2, 1, 3, 4, 5] + >>> b.list(-1) + [0, 2, 1] + + Singletons are not shown when printing with one exception: the largest + element is always shown -- as a singleton if necessary: + + >>> Cycle(1, 4, 10)(4, 5) + (1 5 4 10) + >>> Cycle(1, 2)(4)(5)(10) + (1 2)(10) + + The array form can be used to instantiate a Permutation so other + properties of the permutation can be investigated: + + >>> Perm(Cycle(1, 2)(3, 4).list()).transpositions() + [(1, 2), (3, 4)] + + Notes + ===== + + The underlying structure of the Cycle is a dictionary and although + the __iter__ method has been redefined to give the array form of the + cycle, the underlying dictionary items are still available with the + such methods as items(): + + >>> list(Cycle(1, 2).items()) + [(1, 2), (2, 1)] + + See Also + ======== + + Permutation + """ + def __missing__(self, arg): + """Enter arg into dictionary and return arg.""" + return as_int(arg) + + def __iter__(self): + yield from self.list() + + def __call__(self, *other): + """Return product of cycles processed from R to L. + + Examples + ======== + + >>> from sympy.combinatorics import Cycle + >>> Cycle(1, 2)(2, 3) + (1 3 2) + + An instance of a Cycle will automatically parse list-like + objects and Permutations that are on the right. It is more + flexible than the Permutation in that all elements need not + be present: + + >>> a = Cycle(1, 2) + >>> a(2, 3) + (1 3 2) + >>> a(2, 3)(4, 5) + (1 3 2)(4 5) + + """ + rv = Cycle(*other) + for k, v in zip(list(self.keys()), [rv[self[k]] for k in self.keys()]): + rv[k] = v + return rv + + def list(self, size=None): + """Return the cycles as an explicit list starting from 0 up + to the greater of the largest value in the cycles and size. + + Truncation of trailing unmoved items will occur when size + is less than the maximum element in the cycle; if this is + desired, setting ``size=-1`` will guarantee such trimming. + + Examples + ======== + + >>> from sympy.combinatorics import Cycle + >>> p = Cycle(2, 3)(4, 5) + >>> p.list() + [0, 1, 3, 2, 5, 4] + >>> p.list(10) + [0, 1, 3, 2, 5, 4, 6, 7, 8, 9] + + Passing a length too small will trim trailing, unchanged elements + in the permutation: + + >>> Cycle(2, 4)(1, 2, 4).list(-1) + [0, 2, 1] + """ + if not self and size is None: + raise ValueError('must give size for empty Cycle') + if size is not None: + big = max([i for i in self.keys() if self[i] != i] + [0]) + size = max(size, big + 1) + else: + size = self.size + return [self[i] for i in range(size)] + + def __repr__(self): + """We want it to print as a Cycle, not as a dict. + + Examples + ======== + + >>> from sympy.combinatorics import Cycle + >>> Cycle(1, 2) + (1 2) + >>> print(_) + (1 2) + >>> list(Cycle(1, 2).items()) + [(1, 2), (2, 1)] + """ + if not self: + return 'Cycle()' + cycles = Permutation(self).cyclic_form + s = ''.join(str(tuple(c)) for c in cycles) + big = self.size - 1 + if not any(i == big for c in cycles for i in c): + s += '(%s)' % big + return 'Cycle%s' % s + + def __str__(self): + """We want it to be printed in a Cycle notation with no + comma in-between. + + Examples + ======== + + >>> from sympy.combinatorics import Cycle + >>> Cycle(1, 2) + (1 2) + >>> Cycle(1, 2, 4)(5, 6) + (1 2 4)(5 6) + """ + if not self: + return '()' + cycles = Permutation(self).cyclic_form + s = ''.join(str(tuple(c)) for c in cycles) + big = self.size - 1 + if not any(i == big for c in cycles for i in c): + s += '(%s)' % big + s = s.replace(',', '') + return s + + def __init__(self, *args): + """Load up a Cycle instance with the values for the cycle. + + Examples + ======== + + >>> from sympy.combinatorics import Cycle + >>> Cycle(1, 2, 6) + (1 2 6) + """ + + if not args: + return + if len(args) == 1: + if isinstance(args[0], Permutation): + for c in args[0].cyclic_form: + self.update(self(*c)) + return + elif isinstance(args[0], Cycle): + for k, v in args[0].items(): + self[k] = v + return + args = [as_int(a) for a in args] + if any(i < 0 for i in args): + raise ValueError('negative integers are not allowed in a cycle.') + if has_dups(args): + raise ValueError('All elements must be unique in a cycle.') + for i in range(-len(args), 0): + self[args[i]] = args[i + 1] + + @property + def size(self): + if not self: + return 0 + return max(self.keys()) + 1 + + def copy(self): + return Cycle(self) + + +class Permutation(Atom): + r""" + A permutation, alternatively known as an 'arrangement number' or 'ordering' + is an arrangement of the elements of an ordered list into a one-to-one + mapping with itself. The permutation of a given arrangement is given by + indicating the positions of the elements after re-arrangement [2]_. For + example, if one started with elements ``[x, y, a, b]`` (in that order) and + they were reordered as ``[x, y, b, a]`` then the permutation would be + ``[0, 1, 3, 2]``. Notice that (in SymPy) the first element is always referred + to as 0 and the permutation uses the indices of the elements in the + original ordering, not the elements ``(a, b, ...)`` themselves. + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + + Permutations Notation + ===================== + + Permutations are commonly represented in disjoint cycle or array forms. + + Array Notation and 2-line Form + ------------------------------------ + + In the 2-line form, the elements and their final positions are shown + as a matrix with 2 rows: + + [0 1 2 ... n-1] + [p(0) p(1) p(2) ... p(n-1)] + + Since the first line is always ``range(n)``, where n is the size of p, + it is sufficient to represent the permutation by the second line, + referred to as the "array form" of the permutation. This is entered + in brackets as the argument to the Permutation class: + + >>> p = Permutation([0, 2, 1]); p + Permutation([0, 2, 1]) + + Given i in range(p.size), the permutation maps i to i^p + + >>> [i^p for i in range(p.size)] + [0, 2, 1] + + The composite of two permutations p*q means first apply p, then q, so + i^(p*q) = (i^p)^q which is i^p^q according to Python precedence rules: + + >>> q = Permutation([2, 1, 0]) + >>> [i^p^q for i in range(3)] + [2, 0, 1] + >>> [i^(p*q) for i in range(3)] + [2, 0, 1] + + One can use also the notation p(i) = i^p, but then the composition + rule is (p*q)(i) = q(p(i)), not p(q(i)): + + >>> [(p*q)(i) for i in range(p.size)] + [2, 0, 1] + >>> [q(p(i)) for i in range(p.size)] + [2, 0, 1] + >>> [p(q(i)) for i in range(p.size)] + [1, 2, 0] + + Disjoint Cycle Notation + ----------------------- + + In disjoint cycle notation, only the elements that have shifted are + indicated. + + For example, [1, 3, 2, 0] can be represented as (0, 1, 3)(2). + This can be understood from the 2 line format of the given permutation. + In the 2-line form, + [0 1 2 3] + [1 3 2 0] + + The element in the 0th position is 1, so 0 -> 1. The element in the 1st + position is three, so 1 -> 3. And the element in the third position is again + 0, so 3 -> 0. Thus, 0 -> 1 -> 3 -> 0, and 2 -> 2. Thus, this can be represented + as 2 cycles: (0, 1, 3)(2). + In common notation, singular cycles are not explicitly written as they can be + inferred implicitly. + + Only the relative ordering of elements in a cycle matter: + + >>> Permutation(1,2,3) == Permutation(2,3,1) == Permutation(3,1,2) + True + + The disjoint cycle notation is convenient when representing + permutations that have several cycles in them: + + >>> Permutation(1, 2)(3, 5) == Permutation([[1, 2], [3, 5]]) + True + + It also provides some economy in entry when computing products of + permutations that are written in disjoint cycle notation: + + >>> Permutation(1, 2)(1, 3)(2, 3) + Permutation([0, 3, 2, 1]) + >>> _ == Permutation([[1, 2]])*Permutation([[1, 3]])*Permutation([[2, 3]]) + True + + Caution: when the cycles have common elements between them then the order + in which the permutations are applied matters. This module applies + the permutations from *left to right*. + + >>> Permutation(1, 2)(2, 3) == Permutation([(1, 2), (2, 3)]) + True + >>> Permutation(1, 2)(2, 3).list() + [0, 3, 1, 2] + + In the above case, (1,2) is computed before (2,3). + As 0 -> 0, 0 -> 0, element in position 0 is 0. + As 1 -> 2, 2 -> 3, element in position 1 is 3. + As 2 -> 1, 1 -> 1, element in position 2 is 1. + As 3 -> 3, 3 -> 2, element in position 3 is 2. + + If the first and second elements had been + swapped first, followed by the swapping of the second + and third, the result would have been [0, 2, 3, 1]. + If, you want to apply the cycles in the conventional + right to left order, call the function with arguments in reverse order + as demonstrated below: + + >>> Permutation([(1, 2), (2, 3)][::-1]).list() + [0, 2, 3, 1] + + Entering a singleton in a permutation is a way to indicate the size of the + permutation. The ``size`` keyword can also be used. + + Array-form entry: + + >>> Permutation([[1, 2], [9]]) + Permutation([0, 2, 1], size=10) + >>> Permutation([[1, 2]], size=10) + Permutation([0, 2, 1], size=10) + + Cyclic-form entry: + + >>> Permutation(1, 2, size=10) + Permutation([0, 2, 1], size=10) + >>> Permutation(9)(1, 2) + Permutation([0, 2, 1], size=10) + + Caution: no singleton containing an element larger than the largest + in any previous cycle can be entered. This is an important difference + in how Permutation and Cycle handle the ``__call__`` syntax. A singleton + argument at the start of a Permutation performs instantiation of the + Permutation and is permitted: + + >>> Permutation(5) + Permutation([], size=6) + + A singleton entered after instantiation is a call to the permutation + -- a function call -- and if the argument is out of range it will + trigger an error. For this reason, it is better to start the cycle + with the singleton: + + The following fails because there is no element 3: + + >>> Permutation(1, 2)(3) + Traceback (most recent call last): + ... + IndexError: list index out of range + + This is ok: only the call to an out of range singleton is prohibited; + otherwise the permutation autosizes: + + >>> Permutation(3)(1, 2) + Permutation([0, 2, 1, 3]) + >>> Permutation(1, 2)(3, 4) == Permutation(3, 4)(1, 2) + True + + + Equality testing + ---------------- + + The array forms must be the same in order for permutations to be equal: + + >>> Permutation([1, 0, 2, 3]) == Permutation([1, 0]) + False + + + Identity Permutation + -------------------- + + The identity permutation is a permutation in which no element is out of + place. It can be entered in a variety of ways. All the following create + an identity permutation of size 4: + + >>> I = Permutation([0, 1, 2, 3]) + >>> all(p == I for p in [ + ... Permutation(3), + ... Permutation(range(4)), + ... Permutation([], size=4), + ... Permutation(size=4)]) + True + + Watch out for entering the range *inside* a set of brackets (which is + cycle notation): + + >>> I == Permutation([range(4)]) + False + + + Permutation Printing + ==================== + + There are a few things to note about how Permutations are printed. + + .. deprecated:: 1.6 + + Configuring Permutation printing by setting + ``Permutation.print_cyclic`` is deprecated. Users should use the + ``perm_cyclic`` flag to the printers, as described below. + + 1) If you prefer one form (array or cycle) over another, you can set + ``init_printing`` with the ``perm_cyclic`` flag. + + >>> from sympy import init_printing + >>> p = Permutation(1, 2)(4, 5)(3, 4) + >>> p + Permutation([0, 2, 1, 4, 5, 3]) + + >>> init_printing(perm_cyclic=True, pretty_print=False) + >>> p + (1 2)(3 4 5) + + 2) Regardless of the setting, a list of elements in the array for cyclic + form can be obtained and either of those can be copied and supplied as + the argument to Permutation: + + >>> p.array_form + [0, 2, 1, 4, 5, 3] + >>> p.cyclic_form + [[1, 2], [3, 4, 5]] + >>> Permutation(_) == p + True + + 3) Printing is economical in that as little as possible is printed while + retaining all information about the size of the permutation: + + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> Permutation([1, 0, 2, 3]) + Permutation([1, 0, 2, 3]) + >>> Permutation([1, 0, 2, 3], size=20) + Permutation([1, 0], size=20) + >>> Permutation([1, 0, 2, 4, 3, 5, 6], size=20) + Permutation([1, 0, 2, 4, 3], size=20) + + >>> p = Permutation([1, 0, 2, 3]) + >>> init_printing(perm_cyclic=True, pretty_print=False) + >>> p + (3)(0 1) + >>> init_printing(perm_cyclic=False, pretty_print=False) + + The 2 was not printed but it is still there as can be seen with the + array_form and size methods: + + >>> p.array_form + [1, 0, 2, 3] + >>> p.size + 4 + + Short introduction to other methods + =================================== + + The permutation can act as a bijective function, telling what element is + located at a given position + + >>> q = Permutation([5, 2, 3, 4, 1, 0]) + >>> q.array_form[1] # the hard way + 2 + >>> q(1) # the easy way + 2 + >>> {i: q(i) for i in range(q.size)} # showing the bijection + {0: 5, 1: 2, 2: 3, 3: 4, 4: 1, 5: 0} + + The full cyclic form (including singletons) can be obtained: + + >>> p.full_cyclic_form + [[0, 1], [2], [3]] + + Any permutation can be factored into transpositions of pairs of elements: + + >>> Permutation([[1, 2], [3, 4, 5]]).transpositions() + [(1, 2), (3, 5), (3, 4)] + >>> Permutation.rmul(*[Permutation([ti], size=6) for ti in _]).cyclic_form + [[1, 2], [3, 4, 5]] + + The number of permutations on a set of n elements is given by n! and is + called the cardinality. + + >>> p.size + 4 + >>> p.cardinality + 24 + + A given permutation has a rank among all the possible permutations of the + same elements, but what that rank is depends on how the permutations are + enumerated. (There are a number of different methods of doing so.) The + lexicographic rank is given by the rank method and this rank is used to + increment a permutation with addition/subtraction: + + >>> p.rank() + 6 + >>> p + 1 + Permutation([1, 0, 3, 2]) + >>> p.next_lex() + Permutation([1, 0, 3, 2]) + >>> _.rank() + 7 + >>> p.unrank_lex(p.size, rank=7) + Permutation([1, 0, 3, 2]) + + The product of two permutations p and q is defined as their composition as + functions, (p*q)(i) = q(p(i)) [6]_. + + >>> p = Permutation([1, 0, 2, 3]) + >>> q = Permutation([2, 3, 1, 0]) + >>> list(q*p) + [2, 3, 0, 1] + >>> list(p*q) + [3, 2, 1, 0] + >>> [q(p(i)) for i in range(p.size)] + [3, 2, 1, 0] + + The permutation can be 'applied' to any list-like object, not only + Permutations: + + >>> p(['zero', 'one', 'four', 'two']) + ['one', 'zero', 'four', 'two'] + >>> p('zo42') + ['o', 'z', '4', '2'] + + If you have a list of arbitrary elements, the corresponding permutation + can be found with the from_sequence method: + + >>> Permutation.from_sequence('SymPy') + Permutation([1, 3, 2, 0, 4]) + + Checking if a Permutation is contained in a Group + ================================================= + + Generally if you have a group of permutations G on n symbols, and + you're checking if a permutation on less than n symbols is part + of that group, the check will fail. + + Here is an example for n=5 and we check if the cycle + (1,2,3) is in G: + + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=True, pretty_print=False) + >>> from sympy.combinatorics import Cycle, Permutation + >>> from sympy.combinatorics.perm_groups import PermutationGroup + >>> G = PermutationGroup(Cycle(2, 3)(4, 5), Cycle(1, 2, 3, 4, 5)) + >>> p1 = Permutation(Cycle(2, 5, 3)) + >>> p2 = Permutation(Cycle(1, 2, 3)) + >>> a1 = Permutation(Cycle(1, 2, 3).list(6)) + >>> a2 = Permutation(Cycle(1, 2, 3)(5)) + >>> a3 = Permutation(Cycle(1, 2, 3),size=6) + >>> for p in [p1,p2,a1,a2,a3]: p, G.contains(p) + ((2 5 3), True) + ((1 2 3), False) + ((5)(1 2 3), True) + ((5)(1 2 3), True) + ((5)(1 2 3), True) + + The check for p2 above will fail. + + Checking if p1 is in G works because SymPy knows + G is a group on 5 symbols, and p1 is also on 5 symbols + (its largest element is 5). + + For ``a1``, the ``.list(6)`` call will extend the permutation to 5 + symbols, so the test will work as well. In the case of ``a2`` the + permutation is being extended to 5 symbols by using a singleton, + and in the case of ``a3`` it's extended through the constructor + argument ``size=6``. + + There is another way to do this, which is to tell the ``contains`` + method that the number of symbols the group is on does not need to + match perfectly the number of symbols for the permutation: + + >>> G.contains(p2,strict=False) + True + + This can be via the ``strict`` argument to the ``contains`` method, + and SymPy will try to extend the permutation on its own and then + perform the containment check. + + See Also + ======== + + Cycle + + References + ========== + + .. [1] Skiena, S. 'Permutations.' 1.1 in Implementing Discrete Mathematics + Combinatorics and Graph Theory with Mathematica. Reading, MA: + Addison-Wesley, pp. 3-16, 1990. + + .. [2] Knuth, D. E. The Art of Computer Programming, Vol. 4: Combinatorial + Algorithms, 1st ed. Reading, MA: Addison-Wesley, 2011. + + .. [3] Wendy Myrvold and Frank Ruskey. 2001. Ranking and unranking + permutations in linear time. Inf. Process. Lett. 79, 6 (September 2001), + 281-284. DOI=10.1016/S0020-0190(01)00141-7 + + .. [4] D. L. Kreher, D. R. Stinson 'Combinatorial Algorithms' + CRC Press, 1999 + + .. [5] Graham, R. L.; Knuth, D. E.; and Patashnik, O. + Concrete Mathematics: A Foundation for Computer Science, 2nd ed. + Reading, MA: Addison-Wesley, 1994. + + .. [6] https://en.wikipedia.org/w/index.php?oldid=499948155#Product_and_inverse + + .. [7] https://en.wikipedia.org/wiki/Lehmer_code + + """ + + is_Permutation = True + + _array_form = None + _cyclic_form = None + _cycle_structure = None + _size = None + _rank = None + + def __new__(cls, *args, size=None, **kwargs): + """ + Constructor for the Permutation object from a list or a + list of lists in which all elements of the permutation may + appear only once. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + + Permutations entered in array-form are left unaltered: + + >>> Permutation([0, 2, 1]) + Permutation([0, 2, 1]) + + Permutations entered in cyclic form are converted to array form; + singletons need not be entered, but can be entered to indicate the + largest element: + + >>> Permutation([[4, 5, 6], [0, 1]]) + Permutation([1, 0, 2, 3, 5, 6, 4]) + >>> Permutation([[4, 5, 6], [0, 1], [19]]) + Permutation([1, 0, 2, 3, 5, 6, 4], size=20) + + All manipulation of permutations assumes that the smallest element + is 0 (in keeping with 0-based indexing in Python) so if the 0 is + missing when entering a permutation in array form, an error will be + raised: + + >>> Permutation([2, 1]) + Traceback (most recent call last): + ... + ValueError: Integers 0 through 2 must be present. + + If a permutation is entered in cyclic form, it can be entered without + singletons and the ``size`` specified so those values can be filled + in, otherwise the array form will only extend to the maximum value + in the cycles: + + >>> Permutation([[1, 4], [3, 5, 2]], size=10) + Permutation([0, 4, 3, 5, 1, 2], size=10) + >>> _.array_form + [0, 4, 3, 5, 1, 2, 6, 7, 8, 9] + """ + if size is not None: + size = int(size) + + #a) () + #b) (1) = identity + #c) (1, 2) = cycle + #d) ([1, 2, 3]) = array form + #e) ([[1, 2]]) = cyclic form + #f) (Cycle) = conversion to permutation + #g) (Permutation) = adjust size or return copy + ok = True + if not args: # a + return cls._af_new(list(range(size or 0))) + elif len(args) > 1: # c + return cls._af_new(Cycle(*args).list(size)) + if len(args) == 1: + a = args[0] + if isinstance(a, cls): # g + if size is None or size == a.size: + return a + return cls(a.array_form, size=size) + if isinstance(a, Cycle): # f + return cls._af_new(a.list(size)) + if not is_sequence(a): # b + if size is not None and a + 1 > size: + raise ValueError('size is too small when max is %s' % a) + return cls._af_new(list(range(a + 1))) + if has_variety(is_sequence(ai) for ai in a): + ok = False + else: + ok = False + if not ok: + raise ValueError("Permutation argument must be a list of ints, " + "a list of lists, Permutation or Cycle.") + + # safe to assume args are valid; this also makes a copy + # of the args + args = list(args[0]) + + is_cycle = args and is_sequence(args[0]) + if is_cycle: # e + args = [[int(i) for i in c] for c in args] + else: # d + args = [int(i) for i in args] + + # if there are n elements present, 0, 1, ..., n-1 should be present + # unless a cycle notation has been provided. A 0 will be added + # for convenience in case one wants to enter permutations where + # counting starts from 1. + + temp = flatten(args) + if has_dups(temp) and not is_cycle: + raise ValueError('there were repeated elements.') + temp = set(temp) + + if not is_cycle: + if temp != set(range(len(temp))): + raise ValueError('Integers 0 through %s must be present.' % + max(temp)) + if size is not None and temp and max(temp) + 1 > size: + raise ValueError('max element should not exceed %s' % (size - 1)) + + if is_cycle: + # it's not necessarily canonical so we won't store + # it -- use the array form instead + c = Cycle() + for ci in args: + c = c(*ci) + aform = c.list() + else: + aform = list(args) + if size and size > len(aform): + # don't allow for truncation of permutation which + # might split a cycle and lead to an invalid aform + # but do allow the permutation size to be increased + aform.extend(list(range(len(aform), size))) + + return cls._af_new(aform) + + @classmethod + def _af_new(cls, perm): + """A method to produce a Permutation object from a list; + the list is bound to the _array_form attribute, so it must + not be modified; this method is meant for internal use only; + the list ``a`` is supposed to be generated as a temporary value + in a method, so p = Perm._af_new(a) is the only object + to hold a reference to ``a``:: + + Examples + ======== + + >>> from sympy.combinatorics.permutations import Perm + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> a = [2, 1, 3, 0] + >>> p = Perm._af_new(a) + >>> p + Permutation([2, 1, 3, 0]) + + """ + p = super().__new__(cls) + p._array_form = perm + p._size = len(perm) + return p + + def copy(self): + return self.__class__(self.array_form) + + def __getnewargs__(self): + return (self.array_form,) + + def _hashable_content(self): + # the array_form (a list) is the Permutation arg, so we need to + # return a tuple, instead + return tuple(self.array_form) + + @property + def array_form(self): + """ + Return a copy of the attribute _array_form + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([[2, 0], [3, 1]]) + >>> p.array_form + [2, 3, 0, 1] + >>> Permutation([[2, 0, 3, 1]]).array_form + [3, 2, 0, 1] + >>> Permutation([2, 0, 3, 1]).array_form + [2, 0, 3, 1] + >>> Permutation([[1, 2], [4, 5]]).array_form + [0, 2, 1, 3, 5, 4] + """ + return self._array_form[:] + + def list(self, size=None): + """Return the permutation as an explicit list, possibly + trimming unmoved elements if size is less than the maximum + element in the permutation; if this is desired, setting + ``size=-1`` will guarantee such trimming. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation(2, 3)(4, 5) + >>> p.list() + [0, 1, 3, 2, 5, 4] + >>> p.list(10) + [0, 1, 3, 2, 5, 4, 6, 7, 8, 9] + + Passing a length too small will trim trailing, unchanged elements + in the permutation: + + >>> Permutation(2, 4)(1, 2, 4).list(-1) + [0, 2, 1] + >>> Permutation(3).list(-1) + [] + """ + if not self and size is None: + raise ValueError('must give size for empty Cycle') + rv = self.array_form + if size is not None: + if size > self.size: + rv.extend(list(range(self.size, size))) + else: + # find first value from rhs where rv[i] != i + i = self.size - 1 + while rv: + if rv[-1] != i: + break + rv.pop() + i -= 1 + return rv + + @property + def cyclic_form(self): + """ + This is used to convert to the cyclic notation + from the canonical notation. Singletons are omitted. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 3, 1, 2]) + >>> p.cyclic_form + [[1, 3, 2]] + >>> Permutation([1, 0, 2, 4, 3, 5]).cyclic_form + [[0, 1], [3, 4]] + + See Also + ======== + + array_form, full_cyclic_form + """ + if self._cyclic_form is not None: + return list(self._cyclic_form) + array_form = self.array_form + unchecked = [True] * len(array_form) + cyclic_form = [] + for i in range(len(array_form)): + if unchecked[i]: + cycle = [] + cycle.append(i) + unchecked[i] = False + j = i + while unchecked[array_form[j]]: + j = array_form[j] + cycle.append(j) + unchecked[j] = False + if len(cycle) > 1: + cyclic_form.append(cycle) + assert cycle == list(minlex(cycle)) + cyclic_form.sort() + self._cyclic_form = cyclic_form.copy() + return cyclic_form + + @property + def full_cyclic_form(self): + """Return permutation in cyclic form including singletons. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([0, 2, 1]).full_cyclic_form + [[0], [1, 2]] + """ + need = set(range(self.size)) - set(flatten(self.cyclic_form)) + rv = self.cyclic_form + [[i] for i in need] + rv.sort() + return rv + + @property + def size(self): + """ + Returns the number of elements in the permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([[3, 2], [0, 1]]).size + 4 + + See Also + ======== + + cardinality, length, order, rank + """ + return self._size + + def support(self): + """Return the elements in permutation, P, for which P[i] != i. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([[3, 2], [0, 1], [4]]) + >>> p.array_form + [1, 0, 3, 2, 4] + >>> p.support() + [0, 1, 2, 3] + """ + a = self.array_form + return [i for i, e in enumerate(a) if e != i] + + def __add__(self, other): + """Return permutation that is other higher in rank than self. + + The rank is the lexicographical rank, with the identity permutation + having rank of 0. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> I = Permutation([0, 1, 2, 3]) + >>> a = Permutation([2, 1, 3, 0]) + >>> I + a.rank() == a + True + + See Also + ======== + + __sub__, inversion_vector + + """ + rank = (self.rank() + other) % self.cardinality + rv = self.unrank_lex(self.size, rank) + rv._rank = rank + return rv + + def __sub__(self, other): + """Return the permutation that is other lower in rank than self. + + See Also + ======== + + __add__ + """ + return self.__add__(-other) + + @staticmethod + def rmul(*args): + """ + Return product of Permutations [a, b, c, ...] as the Permutation whose + ith value is a(b(c(i))). + + a, b, c, ... can be Permutation objects or tuples. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + + >>> a, b = [1, 0, 2], [0, 2, 1] + >>> a = Permutation(a); b = Permutation(b) + >>> list(Permutation.rmul(a, b)) + [1, 2, 0] + >>> [a(b(i)) for i in range(3)] + [1, 2, 0] + + This handles the operands in reverse order compared to the ``*`` operator: + + >>> a = Permutation(a); b = Permutation(b) + >>> list(a*b) + [2, 0, 1] + >>> [b(a(i)) for i in range(3)] + [2, 0, 1] + + Notes + ===== + + All items in the sequence will be parsed by Permutation as + necessary as long as the first item is a Permutation: + + >>> Permutation.rmul(a, [0, 2, 1]) == Permutation.rmul(a, b) + True + + The reverse order of arguments will raise a TypeError. + + """ + rv = args[0] + for i in range(1, len(args)): + rv = args[i]*rv + return rv + + @classmethod + def rmul_with_af(cls, *args): + """ + same as rmul, but the elements of args are Permutation objects + which have _array_form + """ + a = [x._array_form for x in args] + rv = cls._af_new(_af_rmuln(*a)) + return rv + + def mul_inv(self, other): + """ + other*~self, self and other have _array_form + """ + a = _af_invert(self._array_form) + b = other._array_form + return self._af_new(_af_rmul(a, b)) + + def __rmul__(self, other): + """This is needed to coerce other to Permutation in rmul.""" + cls = type(self) + return cls(other)*self + + def __mul__(self, other): + """ + Return the product a*b as a Permutation; the ith value is b(a(i)). + + Examples + ======== + + >>> from sympy.combinatorics.permutations import _af_rmul, Permutation + + >>> a, b = [1, 0, 2], [0, 2, 1] + >>> a = Permutation(a); b = Permutation(b) + >>> list(a*b) + [2, 0, 1] + >>> [b(a(i)) for i in range(3)] + [2, 0, 1] + + This handles operands in reverse order compared to _af_rmul and rmul: + + >>> al = list(a); bl = list(b) + >>> _af_rmul(al, bl) + [1, 2, 0] + >>> [al[bl[i]] for i in range(3)] + [1, 2, 0] + + It is acceptable for the arrays to have different lengths; the shorter + one will be padded to match the longer one: + + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> b*Permutation([1, 0]) + Permutation([1, 2, 0]) + >>> Permutation([1, 0])*b + Permutation([2, 0, 1]) + + It is also acceptable to allow coercion to handle conversion of a + single list to the left of a Permutation: + + >>> [0, 1]*a # no change: 2-element identity + Permutation([1, 0, 2]) + >>> [[0, 1]]*a # exchange first two elements + Permutation([0, 1, 2]) + + You cannot use more than 1 cycle notation in a product of cycles + since coercion can only handle one argument to the left. To handle + multiple cycles it is convenient to use Cycle instead of Permutation: + + >>> [[1, 2]]*[[2, 3]]*Permutation([]) # doctest: +SKIP + >>> from sympy.combinatorics.permutations import Cycle + >>> Cycle(1, 2)(2, 3) + (1 3 2) + + """ + from sympy.combinatorics.perm_groups import PermutationGroup, Coset + if isinstance(other, PermutationGroup): + return Coset(self, other, dir='-') + a = self.array_form + # __rmul__ makes sure the other is a Permutation + b = other.array_form + if not b: + perm = a + else: + b.extend(list(range(len(b), len(a)))) + perm = [b[i] for i in a] + b[len(a):] + return self._af_new(perm) + + def commutes_with(self, other): + """ + Checks if the elements are commuting. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> a = Permutation([1, 4, 3, 0, 2, 5]) + >>> b = Permutation([0, 1, 2, 3, 4, 5]) + >>> a.commutes_with(b) + True + >>> b = Permutation([2, 3, 5, 4, 1, 0]) + >>> a.commutes_with(b) + False + """ + a = self.array_form + b = other.array_form + return _af_commutes_with(a, b) + + def __pow__(self, n): + """ + Routine for finding powers of a permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation([2, 0, 3, 1]) + >>> p.order() + 4 + >>> p**4 + Permutation([0, 1, 2, 3]) + """ + if isinstance(n, Permutation): + raise NotImplementedError( + 'p**p is not defined; do you mean p^p (conjugate)?') + n = int(n) + return self._af_new(_af_pow(self.array_form, n)) + + def __rxor__(self, i): + """Return self(i) when ``i`` is an int. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation(1, 2, 9) + >>> 2^p == p(2) == 9 + True + """ + if int_valued(i): + return self(i) + else: + raise NotImplementedError( + "i^p = p(i) when i is an integer, not %s." % i) + + def __xor__(self, h): + """Return the conjugate permutation ``~h*self*h` `. + + Explanation + =========== + + If ``a`` and ``b`` are conjugates, ``a = h*b*~h`` and + ``b = ~h*a*h`` and both have the same cycle structure. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation(1, 2, 9) + >>> q = Permutation(6, 9, 8) + >>> p*q != q*p + True + + Calculate and check properties of the conjugate: + + >>> c = p^q + >>> c == ~q*p*q and p == q*c*~q + True + + The expression q^p^r is equivalent to q^(p*r): + + >>> r = Permutation(9)(4, 6, 8) + >>> q^p^r == q^(p*r) + True + + If the term to the left of the conjugate operator, i, is an integer + then this is interpreted as selecting the ith element from the + permutation to the right: + + >>> all(i^p == p(i) for i in range(p.size)) + True + + Note that the * operator as higher precedence than the ^ operator: + + >>> q^r*p^r == q^(r*p)^r == Permutation(9)(1, 6, 4) + True + + Notes + ===== + + In Python the precedence rule is p^q^r = (p^q)^r which differs + in general from p^(q^r) + + >>> q^p^r + (9)(1 4 8) + >>> q^(p^r) + (9)(1 8 6) + + For a given r and p, both of the following are conjugates of p: + ~r*p*r and r*p*~r. But these are not necessarily the same: + + >>> ~r*p*r == r*p*~r + True + + >>> p = Permutation(1, 2, 9)(5, 6) + >>> ~r*p*r == r*p*~r + False + + The conjugate ~r*p*r was chosen so that ``p^q^r`` would be equivalent + to ``p^(q*r)`` rather than ``p^(r*q)``. To obtain r*p*~r, pass ~r to + this method: + + >>> p^~r == r*p*~r + True + """ + + if self.size != h.size: + raise ValueError("The permutations must be of equal size.") + a = [None]*self.size + h = h._array_form + p = self._array_form + for i in range(self.size): + a[h[i]] = h[p[i]] + return self._af_new(a) + + def transpositions(self): + """ + Return the permutation decomposed into a list of transpositions. + + Explanation + =========== + + It is always possible to express a permutation as the product of + transpositions, see [1] + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([[1, 2, 3], [0, 4, 5, 6, 7]]) + >>> t = p.transpositions() + >>> t + [(0, 7), (0, 6), (0, 5), (0, 4), (1, 3), (1, 2)] + >>> print(''.join(str(c) for c in t)) + (0, 7)(0, 6)(0, 5)(0, 4)(1, 3)(1, 2) + >>> Permutation.rmul(*[Permutation([ti], size=p.size) for ti in t]) == p + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Transposition_%28mathematics%29#Properties + + """ + a = self.cyclic_form + res = [] + for x in a: + nx = len(x) + if nx == 2: + res.append(tuple(x)) + elif nx > 2: + first = x[0] + res.extend((first, y) for y in x[nx - 1:0:-1]) + return res + + @classmethod + def from_sequence(self, i, key=None): + """Return the permutation needed to obtain ``i`` from the sorted + elements of ``i``. If custom sorting is desired, a key can be given. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + + >>> Permutation.from_sequence('SymPy') + (4)(0 1 3) + >>> _(sorted("SymPy")) + ['S', 'y', 'm', 'P', 'y'] + >>> Permutation.from_sequence('SymPy', key=lambda x: x.lower()) + (4)(0 2)(1 3) + """ + ic = list(zip(i, list(range(len(i))))) + if key: + ic.sort(key=lambda x: key(x[0])) + else: + ic.sort() + return ~Permutation([i[1] for i in ic]) + + def __invert__(self): + """ + Return the inverse of the permutation. + + A permutation multiplied by its inverse is the identity permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation([[2, 0], [3, 1]]) + >>> ~p + Permutation([2, 3, 0, 1]) + >>> _ == p**-1 + True + >>> p*~p == ~p*p == Permutation([0, 1, 2, 3]) + True + """ + return self._af_new(_af_invert(self._array_form)) + + def __iter__(self): + """Yield elements from array form. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> list(Permutation(range(3))) + [0, 1, 2] + """ + yield from self.array_form + + def __repr__(self): + return srepr(self) + + def __call__(self, *i): + """ + Allows applying a permutation instance as a bijective function. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([[2, 0], [3, 1]]) + >>> p.array_form + [2, 3, 0, 1] + >>> [p(i) for i in range(4)] + [2, 3, 0, 1] + + If an array is given then the permutation selects the items + from the array (i.e. the permutation is applied to the array): + + >>> from sympy.abc import x + >>> p([x, 1, 0, x**2]) + [0, x**2, x, 1] + """ + # list indices can be Integer or int; leave this + # as it is (don't test or convert it) because this + # gets called a lot and should be fast + if len(i) == 1: + i = i[0] + if not isinstance(i, Iterable): + i = as_int(i) + if i < 0 or i > self.size: + raise TypeError( + "{} should be an integer between 0 and {}" + .format(i, self.size-1)) + return self._array_form[i] + # P([a, b, c]) + if len(i) != self.size: + raise TypeError( + "{} should have the length {}.".format(i, self.size)) + return [i[j] for j in self._array_form] + # P(1, 2, 3) + return self*Permutation(Cycle(*i), size=self.size) + + def atoms(self): + """ + Returns all the elements of a permutation + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([0, 1, 2, 3, 4, 5]).atoms() + {0, 1, 2, 3, 4, 5} + >>> Permutation([[0, 1], [2, 3], [4, 5]]).atoms() + {0, 1, 2, 3, 4, 5} + """ + return set(self.array_form) + + def apply(self, i): + r"""Apply the permutation to an expression. + + Parameters + ========== + + i : Expr + It should be an integer between $0$ and $n-1$ where $n$ + is the size of the permutation. + + If it is a symbol or a symbolic expression that can + have integer values, an ``AppliedPermutation`` object + will be returned which can represent an unevaluated + function. + + Notes + ===== + + Any permutation can be defined as a bijective function + $\sigma : \{ 0, 1, \dots, n-1 \} \rightarrow \{ 0, 1, \dots, n-1 \}$ + where $n$ denotes the size of the permutation. + + The definition may even be extended for any set with distinctive + elements, such that the permutation can even be applied for + real numbers or such, however, it is not implemented for now for + computational reasons and the integrity with the group theory + module. + + This function is similar to the ``__call__`` magic, however, + ``__call__`` magic already has some other applications like + permuting an array or attaching new cycles, which would + not always be mathematically consistent. + + This also guarantees that the return type is a SymPy integer, + which guarantees the safety to use assumptions. + """ + i = _sympify(i) + if i.is_integer is False: + raise NotImplementedError("{} should be an integer.".format(i)) + + n = self.size + if (i < 0) == True or (i >= n) == True: + raise NotImplementedError( + "{} should be an integer between 0 and {}".format(i, n-1)) + + if i.is_Integer: + return Integer(self._array_form[i]) + return AppliedPermutation(self, i) + + def next_lex(self): + """ + Returns the next permutation in lexicographical order. + If self is the last permutation in lexicographical order + it returns None. + See [4] section 2.4. + + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([2, 3, 1, 0]) + >>> p = Permutation([2, 3, 1, 0]); p.rank() + 17 + >>> p = p.next_lex(); p.rank() + 18 + + See Also + ======== + + rank, unrank_lex + """ + perm = self.array_form[:] + n = len(perm) + i = n - 2 + while perm[i + 1] < perm[i]: + i -= 1 + if i == -1: + return None + else: + j = n - 1 + while perm[j] < perm[i]: + j -= 1 + perm[j], perm[i] = perm[i], perm[j] + i += 1 + j = n - 1 + while i < j: + perm[j], perm[i] = perm[i], perm[j] + i += 1 + j -= 1 + return self._af_new(perm) + + @classmethod + def unrank_nonlex(self, n, r): + """ + This is a linear time unranking algorithm that does not + respect lexicographic order [3]. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> Permutation.unrank_nonlex(4, 5) + Permutation([2, 0, 3, 1]) + >>> Permutation.unrank_nonlex(4, -1) + Permutation([0, 1, 2, 3]) + + See Also + ======== + + next_nonlex, rank_nonlex + """ + def _unrank1(n, r, a): + if n > 0: + a[n - 1], a[r % n] = a[r % n], a[n - 1] + _unrank1(n - 1, r//n, a) + + id_perm = list(range(n)) + n = int(n) + r = r % ifac(n) + _unrank1(n, r, id_perm) + return self._af_new(id_perm) + + def rank_nonlex(self, inv_perm=None): + """ + This is a linear time ranking algorithm that does not + enforce lexicographic order [3]. + + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.rank_nonlex() + 23 + + See Also + ======== + + next_nonlex, unrank_nonlex + """ + def _rank1(n, perm, inv_perm): + if n == 1: + return 0 + s = perm[n - 1] + t = inv_perm[n - 1] + perm[n - 1], perm[t] = perm[t], s + inv_perm[n - 1], inv_perm[s] = inv_perm[s], t + return s + n*_rank1(n - 1, perm, inv_perm) + + if inv_perm is None: + inv_perm = (~self).array_form + if not inv_perm: + return 0 + perm = self.array_form[:] + r = _rank1(len(perm), perm, inv_perm) + return r + + def next_nonlex(self): + """ + Returns the next permutation in nonlex order [3]. + If self is the last permutation in this order it returns None. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation([2, 0, 3, 1]); p.rank_nonlex() + 5 + >>> p = p.next_nonlex(); p + Permutation([3, 0, 1, 2]) + >>> p.rank_nonlex() + 6 + + See Also + ======== + + rank_nonlex, unrank_nonlex + """ + r = self.rank_nonlex() + if r == ifac(self.size) - 1: + return None + return self.unrank_nonlex(self.size, r + 1) + + def rank(self): + """ + Returns the lexicographic rank of the permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.rank() + 0 + >>> p = Permutation([3, 2, 1, 0]) + >>> p.rank() + 23 + + See Also + ======== + + next_lex, unrank_lex, cardinality, length, order, size + """ + if self._rank is not None: + return self._rank + rank = 0 + rho = self.array_form[:] + n = self.size - 1 + size = n + 1 + psize = int(ifac(n)) + for j in range(size - 1): + rank += rho[j]*psize + for i in range(j + 1, size): + if rho[i] > rho[j]: + rho[i] -= 1 + psize //= n + n -= 1 + self._rank = rank + return rank + + @property + def cardinality(self): + """ + Returns the number of all possible permutations. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.cardinality + 24 + + See Also + ======== + + length, order, rank, size + """ + return int(ifac(self.size)) + + def parity(self): + """ + Computes the parity of a permutation. + + Explanation + =========== + + The parity of a permutation reflects the parity of the + number of inversions in the permutation, i.e., the + number of pairs of x and y such that ``x > y`` but ``p[x] < p[y]``. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.parity() + 0 + >>> p = Permutation([3, 2, 0, 1]) + >>> p.parity() + 1 + + See Also + ======== + + _af_parity + """ + if self._cyclic_form is not None: + return (self.size - self.cycles) % 2 + + return _af_parity(self.array_form) + + @property + def is_even(self): + """ + Checks if a permutation is even. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.is_even + True + >>> p = Permutation([3, 2, 1, 0]) + >>> p.is_even + True + + See Also + ======== + + is_odd + """ + return not self.is_odd + + @property + def is_odd(self): + """ + Checks if a permutation is odd. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.is_odd + False + >>> p = Permutation([3, 2, 0, 1]) + >>> p.is_odd + True + + See Also + ======== + + is_even + """ + return bool(self.parity() % 2) + + @property + def is_Singleton(self): + """ + Checks to see if the permutation contains only one number and is + thus the only possible permutation of this set of numbers + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([0]).is_Singleton + True + >>> Permutation([0, 1]).is_Singleton + False + + See Also + ======== + + is_Empty + """ + return self.size == 1 + + @property + def is_Empty(self): + """ + Checks to see if the permutation is a set with zero elements + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([]).is_Empty + True + >>> Permutation([0]).is_Empty + False + + See Also + ======== + + is_Singleton + """ + return self.size == 0 + + @property + def is_identity(self): + return self.is_Identity + + @property + def is_Identity(self): + """ + Returns True if the Permutation is an identity permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([]) + >>> p.is_Identity + True + >>> p = Permutation([[0], [1], [2]]) + >>> p.is_Identity + True + >>> p = Permutation([0, 1, 2]) + >>> p.is_Identity + True + >>> p = Permutation([0, 2, 1]) + >>> p.is_Identity + False + + See Also + ======== + + order + """ + af = self.array_form + return not af or all(i == af[i] for i in range(self.size)) + + def ascents(self): + """ + Returns the positions of ascents in a permutation, ie, the location + where p[i] < p[i+1] + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([4, 0, 1, 3, 2]) + >>> p.ascents() + [1, 2] + + See Also + ======== + + descents, inversions, min, max + """ + a = self.array_form + pos = [i for i in range(len(a) - 1) if a[i] < a[i + 1]] + return pos + + def descents(self): + """ + Returns the positions of descents in a permutation, ie, the location + where p[i] > p[i+1] + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([4, 0, 1, 3, 2]) + >>> p.descents() + [0, 3] + + See Also + ======== + + ascents, inversions, min, max + """ + a = self.array_form + pos = [i for i in range(len(a) - 1) if a[i] > a[i + 1]] + return pos + + def max(self) -> int: + """ + The maximum element moved by the permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([1, 0, 2, 3, 4]) + >>> p.max() + 1 + + See Also + ======== + + min, descents, ascents, inversions + """ + a = self.array_form + if not a: + return 0 + return max(_a for i, _a in enumerate(a) if _a != i) + + def min(self) -> int: + """ + The minimum element moved by the permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 4, 3, 2]) + >>> p.min() + 2 + + See Also + ======== + + max, descents, ascents, inversions + """ + a = self.array_form + if not a: + return 0 + return min(_a for i, _a in enumerate(a) if _a != i) + + def inversions(self): + """ + Computes the number of inversions of a permutation. + + Explanation + =========== + + An inversion is where i > j but p[i] < p[j]. + + For small length of p, it iterates over all i and j + values and calculates the number of inversions. + For large length of p, it uses a variation of merge + sort to calculate the number of inversions. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3, 4, 5]) + >>> p.inversions() + 0 + >>> Permutation([3, 2, 1, 0]).inversions() + 6 + + See Also + ======== + + descents, ascents, min, max + + References + ========== + + .. [1] https://www.cp.eng.chula.ac.th/~prabhas//teaching/algo/algo2008/count-inv.htm + + """ + inversions = 0 + a = self.array_form + n = len(a) + if n < 130: + for i in range(n - 1): + b = a[i] + for c in a[i + 1:]: + if b > c: + inversions += 1 + else: + k = 1 + right = 0 + arr = a[:] + temp = a[:] + while k < n: + i = 0 + while i + k < n: + right = i + k * 2 - 1 + if right >= n: + right = n - 1 + inversions += _merge(arr, temp, i, i + k, right) + i = i + k * 2 + k = k * 2 + return inversions + + def commutator(self, x): + """Return the commutator of ``self`` and ``x``: ``~x*~self*x*self`` + + If f and g are part of a group, G, then the commutator of f and g + is the group identity iff f and g commute, i.e. fg == gf. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation([0, 2, 3, 1]) + >>> x = Permutation([2, 0, 3, 1]) + >>> c = p.commutator(x); c + Permutation([2, 1, 3, 0]) + >>> c == ~x*~p*x*p + True + + >>> I = Permutation(3) + >>> p = [I + i for i in range(6)] + >>> for i in range(len(p)): + ... for j in range(len(p)): + ... c = p[i].commutator(p[j]) + ... if p[i]*p[j] == p[j]*p[i]: + ... assert c == I + ... else: + ... assert c != I + ... + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Commutator + """ + + a = self.array_form + b = x.array_form + n = len(a) + if len(b) != n: + raise ValueError("The permutations must be of equal size.") + inva = [None]*n + for i in range(n): + inva[a[i]] = i + invb = [None]*n + for i in range(n): + invb[b[i]] = i + return self._af_new([a[b[inva[i]]] for i in invb]) + + def signature(self): + """ + Gives the signature of the permutation needed to place the + elements of the permutation in canonical order. + + The signature is calculated as (-1)^ + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2]) + >>> p.inversions() + 0 + >>> p.signature() + 1 + >>> q = Permutation([0,2,1]) + >>> q.inversions() + 1 + >>> q.signature() + -1 + + See Also + ======== + + inversions + """ + if self.is_even: + return 1 + return -1 + + def order(self): + """ + Computes the order of a permutation. + + When the permutation is raised to the power of its + order it equals the identity permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation([3, 1, 5, 2, 4, 0]) + >>> p.order() + 4 + >>> (p**(p.order())) + Permutation([], size=6) + + See Also + ======== + + identity, cardinality, length, rank, size + """ + + return reduce(lcm, [len(cycle) for cycle in self.cyclic_form], 1) + + def length(self): + """ + Returns the number of integers moved by a permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([0, 3, 2, 1]).length() + 2 + >>> Permutation([[0, 1], [2, 3]]).length() + 4 + + See Also + ======== + + min, max, support, cardinality, order, rank, size + """ + + return len(self.support()) + + @property + def cycle_structure(self): + """Return the cycle structure of the permutation as a dictionary + indicating the multiplicity of each cycle length. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation(3).cycle_structure + {1: 4} + >>> Permutation(0, 4, 3)(1, 2)(5, 6).cycle_structure + {2: 2, 3: 1} + """ + if self._cycle_structure: + rv = self._cycle_structure + else: + rv = defaultdict(int) + singletons = self.size + for c in self.cyclic_form: + rv[len(c)] += 1 + singletons -= len(c) + if singletons: + rv[1] = singletons + self._cycle_structure = rv + return dict(rv) # make a copy + + @property + def cycles(self): + """ + Returns the number of cycles contained in the permutation + (including singletons). + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation([0, 1, 2]).cycles + 3 + >>> Permutation([0, 1, 2]).full_cyclic_form + [[0], [1], [2]] + >>> Permutation(0, 1)(2, 3).cycles + 2 + + See Also + ======== + sympy.functions.combinatorial.numbers.stirling + """ + return len(self.full_cyclic_form) + + def index(self): + """ + Returns the index of a permutation. + + The index of a permutation is the sum of all subscripts j such + that p[j] is greater than p[j+1]. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([3, 0, 2, 1, 4]) + >>> p.index() + 2 + """ + a = self.array_form + + return sum(j for j in range(len(a) - 1) if a[j] > a[j + 1]) + + def runs(self): + """ + Returns the runs of a permutation. + + An ascending sequence in a permutation is called a run [5]. + + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([2, 5, 7, 3, 6, 0, 1, 4, 8]) + >>> p.runs() + [[2, 5, 7], [3, 6], [0, 1, 4, 8]] + >>> q = Permutation([1,3,2,0]) + >>> q.runs() + [[1, 3], [2], [0]] + """ + return runs(self.array_form) + + def inversion_vector(self): + """Return the inversion vector of the permutation. + + The inversion vector consists of elements whose value + indicates the number of elements in the permutation + that are lesser than it and lie on its right hand side. + + The inversion vector is the same as the Lehmer encoding of a + permutation. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([4, 8, 0, 7, 1, 5, 3, 6, 2]) + >>> p.inversion_vector() + [4, 7, 0, 5, 0, 2, 1, 1] + >>> p = Permutation([3, 2, 1, 0]) + >>> p.inversion_vector() + [3, 2, 1] + + The inversion vector increases lexicographically with the rank + of the permutation, the -ith element cycling through 0..i. + + >>> p = Permutation(2) + >>> while p: + ... print('%s %s %s' % (p, p.inversion_vector(), p.rank())) + ... p = p.next_lex() + (2) [0, 0] 0 + (1 2) [0, 1] 1 + (2)(0 1) [1, 0] 2 + (0 1 2) [1, 1] 3 + (0 2 1) [2, 0] 4 + (0 2) [2, 1] 5 + + See Also + ======== + + from_inversion_vector + """ + self_array_form = self.array_form + n = len(self_array_form) + inversion_vector = [0] * (n - 1) + + for i in range(n - 1): + val = 0 + for j in range(i + 1, n): + if self_array_form[j] < self_array_form[i]: + val += 1 + inversion_vector[i] = val + return inversion_vector + + def rank_trotterjohnson(self): + """ + Returns the Trotter Johnson rank, which we get from the minimal + change algorithm. See [4] section 2.4. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 1, 2, 3]) + >>> p.rank_trotterjohnson() + 0 + >>> p = Permutation([0, 2, 1, 3]) + >>> p.rank_trotterjohnson() + 7 + + See Also + ======== + + unrank_trotterjohnson, next_trotterjohnson + """ + if self.array_form == [] or self.is_Identity: + return 0 + if self.array_form == [1, 0]: + return 1 + perm = self.array_form + n = self.size + rank = 0 + for j in range(1, n): + k = 1 + i = 0 + while perm[i] != j: + if perm[i] < j: + k += 1 + i += 1 + j1 = j + 1 + if rank % 2 == 0: + rank = j1*rank + j1 - k + else: + rank = j1*rank + k - 1 + return rank + + @classmethod + def unrank_trotterjohnson(cls, size, rank): + """ + Trotter Johnson permutation unranking. See [4] section 2.4. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> Permutation.unrank_trotterjohnson(5, 10) + Permutation([0, 3, 1, 2, 4]) + + See Also + ======== + + rank_trotterjohnson, next_trotterjohnson + """ + perm = [0]*size + r2 = 0 + n = ifac(size) + pj = 1 + for j in range(2, size + 1): + pj *= j + r1 = (rank * pj) // n + k = r1 - j*r2 + if r2 % 2 == 0: + for i in range(j - 1, j - k - 1, -1): + perm[i] = perm[i - 1] + perm[j - k - 1] = j - 1 + else: + for i in range(j - 1, k, -1): + perm[i] = perm[i - 1] + perm[k] = j - 1 + r2 = r1 + return cls._af_new(perm) + + def next_trotterjohnson(self): + """ + Returns the next permutation in Trotter-Johnson order. + If self is the last permutation it returns None. + See [4] section 2.4. If it is desired to generate all such + permutations, they can be generated in order more quickly + with the ``generate_bell`` function. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation([3, 0, 2, 1]) + >>> p.rank_trotterjohnson() + 4 + >>> p = p.next_trotterjohnson(); p + Permutation([0, 3, 2, 1]) + >>> p.rank_trotterjohnson() + 5 + + See Also + ======== + + rank_trotterjohnson, unrank_trotterjohnson, sympy.utilities.iterables.generate_bell + """ + pi = self.array_form[:] + n = len(pi) + st = 0 + rho = pi[:] + done = False + m = n-1 + while m > 0 and not done: + d = rho.index(m) + for i in range(d, m): + rho[i] = rho[i + 1] + par = _af_parity(rho[:m]) + if par == 1: + if d == m: + m -= 1 + else: + pi[st + d], pi[st + d + 1] = pi[st + d + 1], pi[st + d] + done = True + else: + if d == 0: + m -= 1 + st += 1 + else: + pi[st + d], pi[st + d - 1] = pi[st + d - 1], pi[st + d] + done = True + if m == 0: + return None + return self._af_new(pi) + + def get_precedence_matrix(self): + """ + Gets the precedence matrix. This is used for computing the + distance between two permutations. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> p = Permutation.josephus(3, 6, 1) + >>> p + Permutation([2, 5, 3, 1, 4, 0]) + >>> p.get_precedence_matrix() + Matrix([ + [0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 1, 0], + [1, 1, 0, 1, 1, 1], + [1, 1, 0, 0, 1, 0], + [1, 0, 0, 0, 0, 0], + [1, 1, 0, 1, 1, 0]]) + + See Also + ======== + + get_precedence_distance, get_adjacency_matrix, get_adjacency_distance + """ + m = zeros(self.size) + perm = self.array_form + for i in range(m.rows): + for j in range(i + 1, m.cols): + m[perm[i], perm[j]] = 1 + return m + + def get_precedence_distance(self, other): + """ + Computes the precedence distance between two permutations. + + Explanation + =========== + + Suppose p and p' represent n jobs. The precedence metric + counts the number of times a job j is preceded by job i + in both p and p'. This metric is commutative. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([2, 0, 4, 3, 1]) + >>> q = Permutation([3, 1, 2, 4, 0]) + >>> p.get_precedence_distance(q) + 7 + >>> q.get_precedence_distance(p) + 7 + + See Also + ======== + + get_precedence_matrix, get_adjacency_matrix, get_adjacency_distance + """ + if self.size != other.size: + raise ValueError("The permutations must be of equal size.") + self_prec_mat = self.get_precedence_matrix() + other_prec_mat = other.get_precedence_matrix() + n_prec = 0 + for i in range(self.size): + for j in range(self.size): + if i == j: + continue + if self_prec_mat[i, j] * other_prec_mat[i, j] == 1: + n_prec += 1 + d = self.size * (self.size - 1)//2 - n_prec + return d + + def get_adjacency_matrix(self): + """ + Computes the adjacency matrix of a permutation. + + Explanation + =========== + + If job i is adjacent to job j in a permutation p + then we set m[i, j] = 1 where m is the adjacency + matrix of p. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation.josephus(3, 6, 1) + >>> p.get_adjacency_matrix() + Matrix([ + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0]]) + >>> q = Permutation([0, 1, 2, 3]) + >>> q.get_adjacency_matrix() + Matrix([ + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [0, 0, 0, 0]]) + + See Also + ======== + + get_precedence_matrix, get_precedence_distance, get_adjacency_distance + """ + m = zeros(self.size) + perm = self.array_form + for i in range(self.size - 1): + m[perm[i], perm[i + 1]] = 1 + return m + + def get_adjacency_distance(self, other): + """ + Computes the adjacency distance between two permutations. + + Explanation + =========== + + This metric counts the number of times a pair i,j of jobs is + adjacent in both p and p'. If n_adj is this quantity then + the adjacency distance is n - n_adj - 1 [1] + + [1] Reeves, Colin R. Landscapes, Operators and Heuristic search, Annals + of Operational Research, 86, pp 473-490. (1999) + + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 3, 1, 2, 4]) + >>> q = Permutation.josephus(4, 5, 2) + >>> p.get_adjacency_distance(q) + 3 + >>> r = Permutation([0, 2, 1, 4, 3]) + >>> p.get_adjacency_distance(r) + 4 + + See Also + ======== + + get_precedence_matrix, get_precedence_distance, get_adjacency_matrix + """ + if self.size != other.size: + raise ValueError("The permutations must be of the same size.") + self_adj_mat = self.get_adjacency_matrix() + other_adj_mat = other.get_adjacency_matrix() + n_adj = 0 + for i in range(self.size): + for j in range(self.size): + if i == j: + continue + if self_adj_mat[i, j] * other_adj_mat[i, j] == 1: + n_adj += 1 + d = self.size - n_adj - 1 + return d + + def get_positional_distance(self, other): + """ + Computes the positional distance between two permutations. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> p = Permutation([0, 3, 1, 2, 4]) + >>> q = Permutation.josephus(4, 5, 2) + >>> r = Permutation([3, 1, 4, 0, 2]) + >>> p.get_positional_distance(q) + 12 + >>> p.get_positional_distance(r) + 12 + + See Also + ======== + + get_precedence_distance, get_adjacency_distance + """ + a = self.array_form + b = other.array_form + if len(a) != len(b): + raise ValueError("The permutations must be of the same size.") + return sum(abs(a[i] - b[i]) for i in range(len(a))) + + @classmethod + def josephus(cls, m, n, s=1): + """Return as a permutation the shuffling of range(n) using the Josephus + scheme in which every m-th item is selected until all have been chosen. + The returned permutation has elements listed by the order in which they + were selected. + + The parameter ``s`` stops the selection process when there are ``s`` + items remaining and these are selected by continuing the selection, + counting by 1 rather than by ``m``. + + Consider selecting every 3rd item from 6 until only 2 remain:: + + choices chosen + ======== ====== + 012345 + 01 345 2 + 01 34 25 + 01 4 253 + 0 4 2531 + 0 25314 + 253140 + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation.josephus(3, 6, 2).array_form + [2, 5, 3, 1, 4, 0] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Flavius_Josephus + .. [2] https://en.wikipedia.org/wiki/Josephus_problem + .. [3] https://web.archive.org/web/20171008094331/http://www.wou.edu/~burtonl/josephus.html + + """ + from collections import deque + m -= 1 + Q = deque(list(range(n))) + perm = [] + while len(Q) > max(s, 1): + for dp in range(m): + Q.append(Q.popleft()) + perm.append(Q.popleft()) + perm.extend(list(Q)) + return cls(perm) + + @classmethod + def from_inversion_vector(cls, inversion): + """ + Calculates the permutation from the inversion vector. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> Permutation.from_inversion_vector([3, 2, 1, 0, 0]) + Permutation([3, 2, 1, 0, 4, 5]) + + """ + size = len(inversion) + N = list(range(size + 1)) + perm = [] + try: + for k in range(size): + val = N[inversion[k]] + perm.append(val) + N.remove(val) + except IndexError: + raise ValueError("The inversion vector is not valid.") + perm.extend(N) + return cls._af_new(perm) + + @classmethod + def random(cls, n): + """ + Generates a random permutation of length ``n``. + + Uses the underlying Python pseudo-random number generator. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1])) + True + + """ + perm_array = list(range(n)) + random.shuffle(perm_array) + return cls._af_new(perm_array) + + @classmethod + def unrank_lex(cls, size, rank): + """ + Lexicographic permutation unranking. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy import init_printing + >>> init_printing(perm_cyclic=False, pretty_print=False) + >>> a = Permutation.unrank_lex(5, 10) + >>> a.rank() + 10 + >>> a + Permutation([0, 2, 4, 1, 3]) + + See Also + ======== + + rank, next_lex + """ + perm_array = [0] * size + psize = 1 + for i in range(size): + new_psize = psize*(i + 1) + d = (rank % new_psize) // psize + rank -= d*psize + perm_array[size - i - 1] = d + for j in range(size - i, size): + if perm_array[j] > d - 1: + perm_array[j] += 1 + psize = new_psize + return cls._af_new(perm_array) + + def resize(self, n): + """Resize the permutation to the new size ``n``. + + Parameters + ========== + + n : int + The new size of the permutation. + + Raises + ====== + + ValueError + If the permutation cannot be resized to the given size. + This may only happen when resized to a smaller size than + the original. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + + Increasing the size of a permutation: + + >>> p = Permutation(0, 1, 2) + >>> p = p.resize(5) + >>> p + (4)(0 1 2) + + Decreasing the size of the permutation: + + >>> p = p.resize(4) + >>> p + (3)(0 1 2) + + If resizing to the specific size breaks the cycles: + + >>> p.resize(2) + Traceback (most recent call last): + ... + ValueError: The permutation cannot be resized to 2 because the + cycle (0, 1, 2) may break. + """ + aform = self.array_form + l = len(aform) + if n > l: + aform += list(range(l, n)) + return Permutation._af_new(aform) + + elif n < l: + cyclic_form = self.full_cyclic_form + new_cyclic_form = [] + for cycle in cyclic_form: + cycle_min = min(cycle) + cycle_max = max(cycle) + if cycle_min <= n-1: + if cycle_max > n-1: + raise ValueError( + "The permutation cannot be resized to {} " + "because the cycle {} may break." + .format(n, tuple(cycle))) + + new_cyclic_form.append(cycle) + return Permutation(new_cyclic_form) + + return self + + # XXX Deprecated flag + print_cyclic = None + + +def _merge(arr, temp, left, mid, right): + """ + Merges two sorted arrays and calculates the inversion count. + + Helper function for calculating inversions. This method is + for internal use only. + """ + i = k = left + j = mid + inv_count = 0 + while i < mid and j <= right: + if arr[i] < arr[j]: + temp[k] = arr[i] + k += 1 + i += 1 + else: + temp[k] = arr[j] + k += 1 + j += 1 + inv_count += (mid -i) + while i < mid: + temp[k] = arr[i] + k += 1 + i += 1 + if j <= right: + k += right - j + 1 + j += right - j + 1 + arr[left:k + 1] = temp[left:k + 1] + else: + arr[left:right + 1] = temp[left:right + 1] + return inv_count + +Perm = Permutation +_af_new = Perm._af_new + + +class AppliedPermutation(Expr): + """A permutation applied to a symbolic variable. + + Parameters + ========== + + perm : Permutation + x : Expr + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.combinatorics import Permutation + + Creating a symbolic permutation function application: + + >>> x = Symbol('x') + >>> p = Permutation(0, 1, 2) + >>> p.apply(x) + AppliedPermutation((0 1 2), x) + >>> _.subs(x, 1) + 2 + """ + def __new__(cls, perm, x, evaluate=None): + if evaluate is None: + evaluate = global_parameters.evaluate + + perm = _sympify(perm) + x = _sympify(x) + + if not isinstance(perm, Permutation): + raise ValueError("{} must be a Permutation instance." + .format(perm)) + + if evaluate: + if x.is_Integer: + return perm.apply(x) + + obj = super().__new__(cls, perm, x) + return obj + + +@dispatch(Permutation, Permutation) +def _eval_is_eq(lhs, rhs): + if lhs._size != rhs._size: + return None + return lhs._array_form == rhs._array_form diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/polyhedron.py b/phivenv/Lib/site-packages/sympy/combinatorics/polyhedron.py new file mode 100644 index 0000000000000000000000000000000000000000..2bc05d7d97c840649661f3290442499c841ca7c1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/polyhedron.py @@ -0,0 +1,1019 @@ +from sympy.combinatorics import Permutation as Perm +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.core import Basic, Tuple, default_sort_key +from sympy.sets import FiniteSet +from sympy.utilities.iterables import (minlex, unflatten, flatten) +from sympy.utilities.misc import as_int + +rmul = Perm.rmul + + +class Polyhedron(Basic): + """ + Represents the polyhedral symmetry group (PSG). + + Explanation + =========== + + The PSG is one of the symmetry groups of the Platonic solids. + There are three polyhedral groups: the tetrahedral group + of order 12, the octahedral group of order 24, and the + icosahedral group of order 60. + + All doctests have been given in the docstring of the + constructor of the object. + + References + ========== + + .. [1] https://mathworld.wolfram.com/PolyhedralGroup.html + + """ + _edges = None + + def __new__(cls, corners, faces=(), pgroup=()): + """ + The constructor of the Polyhedron group object. + + Explanation + =========== + + It takes up to three parameters: the corners, faces, and + allowed transformations. + + The corners/vertices are entered as a list of arbitrary + expressions that are used to identify each vertex. + + The faces are entered as a list of tuples of indices; a tuple + of indices identifies the vertices which define the face. They + should be entered in a cw or ccw order; they will be standardized + by reversal and rotation to be give the lowest lexical ordering. + If no faces are given then no edges will be computed. + + >>> from sympy.combinatorics.polyhedron import Polyhedron + >>> Polyhedron(list('abc'), [(1, 2, 0)]).faces + {(0, 1, 2)} + >>> Polyhedron(list('abc'), [(1, 0, 2)]).faces + {(0, 1, 2)} + + The allowed transformations are entered as allowable permutations + of the vertices for the polyhedron. Instance of Permutations + (as with faces) should refer to the supplied vertices by index. + These permutation are stored as a PermutationGroup. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import Permutation + >>> from sympy import init_printing + >>> from sympy.abc import w, x, y, z + >>> init_printing(pretty_print=False, perm_cyclic=False) + + Here we construct the Polyhedron object for a tetrahedron. + + >>> corners = [w, x, y, z] + >>> faces = [(0, 1, 2), (0, 2, 3), (0, 3, 1), (1, 2, 3)] + + Next, allowed transformations of the polyhedron must be given. This + is given as permutations of vertices. + + Although the vertices of a tetrahedron can be numbered in 24 (4!) + different ways, there are only 12 different orientations for a + physical tetrahedron. The following permutations, applied once or + twice, will generate all 12 of the orientations. (The identity + permutation, Permutation(range(4)), is not included since it does + not change the orientation of the vertices.) + + >>> pgroup = [Permutation([[0, 1, 2], [3]]), \ + Permutation([[0, 1, 3], [2]]), \ + Permutation([[0, 2, 3], [1]]), \ + Permutation([[1, 2, 3], [0]]), \ + Permutation([[0, 1], [2, 3]]), \ + Permutation([[0, 2], [1, 3]]), \ + Permutation([[0, 3], [1, 2]])] + + The Polyhedron is now constructed and demonstrated: + + >>> tetra = Polyhedron(corners, faces, pgroup) + >>> tetra.size + 4 + >>> tetra.edges + {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)} + >>> tetra.corners + (w, x, y, z) + + It can be rotated with an arbitrary permutation of vertices, e.g. + the following permutation is not in the pgroup: + + >>> tetra.rotate(Permutation([0, 1, 3, 2])) + >>> tetra.corners + (w, x, z, y) + + An allowed permutation of the vertices can be constructed by + repeatedly applying permutations from the pgroup to the vertices. + Here is a demonstration that applying p and p**2 for every p in + pgroup generates all the orientations of a tetrahedron and no others: + + >>> all = ( (w, x, y, z), \ + (x, y, w, z), \ + (y, w, x, z), \ + (w, z, x, y), \ + (z, w, y, x), \ + (w, y, z, x), \ + (y, z, w, x), \ + (x, z, y, w), \ + (z, y, x, w), \ + (y, x, z, w), \ + (x, w, z, y), \ + (z, x, w, y) ) + + >>> got = [] + >>> for p in (pgroup + [p**2 for p in pgroup]): + ... h = Polyhedron(corners) + ... h.rotate(p) + ... got.append(h.corners) + ... + >>> set(got) == set(all) + True + + The make_perm method of a PermutationGroup will randomly pick + permutations, multiply them together, and return the permutation that + can be applied to the polyhedron to give the orientation produced + by those individual permutations. + + Here, 3 permutations are used: + + >>> tetra.pgroup.make_perm(3) # doctest: +SKIP + Permutation([0, 3, 1, 2]) + + To select the permutations that should be used, supply a list + of indices to the permutations in pgroup in the order they should + be applied: + + >>> use = [0, 0, 2] + >>> p002 = tetra.pgroup.make_perm(3, use) + >>> p002 + Permutation([1, 0, 3, 2]) + + + Apply them one at a time: + + >>> tetra.reset() + >>> for i in use: + ... tetra.rotate(pgroup[i]) + ... + >>> tetra.vertices + (x, w, z, y) + >>> sequentially = tetra.vertices + + Apply the composite permutation: + + >>> tetra.reset() + >>> tetra.rotate(p002) + >>> tetra.corners + (x, w, z, y) + >>> tetra.corners in all and tetra.corners == sequentially + True + + Notes + ===== + + Defining permutation groups + --------------------------- + + It is not necessary to enter any permutations, nor is necessary to + enter a complete set of transformations. In fact, for a polyhedron, + all configurations can be constructed from just two permutations. + For example, the orientations of a tetrahedron can be generated from + an axis passing through a vertex and face and another axis passing + through a different vertex or from an axis passing through the + midpoints of two edges opposite of each other. + + For simplicity of presentation, consider a square -- + not a cube -- with vertices 1, 2, 3, and 4: + + 1-----2 We could think of axes of rotation being: + | | 1) through the face + | | 2) from midpoint 1-2 to 3-4 or 1-3 to 2-4 + 3-----4 3) lines 1-4 or 2-3 + + + To determine how to write the permutations, imagine 4 cameras, + one at each corner, labeled A-D: + + A B A B + 1-----2 1-----3 vertex index: + | | | | 1 0 + | | | | 2 1 + 3-----4 2-----4 3 2 + C D C D 4 3 + + original after rotation + along 1-4 + + A diagonal and a face axis will be chosen for the "permutation group" + from which any orientation can be constructed. + + >>> pgroup = [] + + Imagine a clockwise rotation when viewing 1-4 from camera A. The new + orientation is (in camera-order): 1, 3, 2, 4 so the permutation is + given using the *indices* of the vertices as: + + >>> pgroup.append(Permutation((0, 2, 1, 3))) + + Now imagine rotating clockwise when looking down an axis entering the + center of the square as viewed. The new camera-order would be + 3, 1, 4, 2 so the permutation is (using indices): + + >>> pgroup.append(Permutation((2, 0, 3, 1))) + + The square can now be constructed: + ** use real-world labels for the vertices, entering them in + camera order + ** for the faces we use zero-based indices of the vertices + in *edge-order* as the face is traversed; neither the + direction nor the starting point matter -- the faces are + only used to define edges (if so desired). + + >>> square = Polyhedron((1, 2, 3, 4), [(0, 1, 3, 2)], pgroup) + + To rotate the square with a single permutation we can do: + + >>> square.rotate(square.pgroup[0]) + >>> square.corners + (1, 3, 2, 4) + + To use more than one permutation (or to use one permutation more + than once) it is more convenient to use the make_perm method: + + >>> p011 = square.pgroup.make_perm([0, 1, 1]) # diag flip + 2 rotations + >>> square.reset() # return to initial orientation + >>> square.rotate(p011) + >>> square.corners + (4, 2, 3, 1) + + Thinking outside the box + ------------------------ + + Although the Polyhedron object has a direct physical meaning, it + actually has broader application. In the most general sense it is + just a decorated PermutationGroup, allowing one to connect the + permutations to something physical. For example, a Rubik's cube is + not a proper polyhedron, but the Polyhedron class can be used to + represent it in a way that helps to visualize the Rubik's cube. + + >>> from sympy import flatten, unflatten, symbols + >>> from sympy.combinatorics import RubikGroup + >>> facelets = flatten([symbols(s+'1:5') for s in 'UFRBLD']) + >>> def show(): + ... pairs = unflatten(r2.corners, 2) + ... print(pairs[::2]) + ... print(pairs[1::2]) + ... + >>> r2 = Polyhedron(facelets, pgroup=RubikGroup(2)) + >>> show() + [(U1, U2), (F1, F2), (R1, R2), (B1, B2), (L1, L2), (D1, D2)] + [(U3, U4), (F3, F4), (R3, R4), (B3, B4), (L3, L4), (D3, D4)] + >>> r2.rotate(0) # cw rotation of F + >>> show() + [(U1, U2), (F3, F1), (U3, R2), (B1, B2), (L1, D1), (R3, R1)] + [(L4, L2), (F4, F2), (U4, R4), (B3, B4), (L3, D2), (D3, D4)] + + Predefined Polyhedra + ==================== + + For convenience, the vertices and faces are defined for the following + standard solids along with a permutation group for transformations. + When the polyhedron is oriented as indicated below, the vertices in + a given horizontal plane are numbered in ccw direction, starting from + the vertex that will give the lowest indices in a given face. (In the + net of the vertices, indices preceded by "-" indicate replication of + the lhs index in the net.) + + tetrahedron, tetrahedron_faces + ------------------------------ + + 4 vertices (vertex up) net: + + 0 0-0 + 1 2 3-1 + + 4 faces: + + (0, 1, 2) (0, 2, 3) (0, 3, 1) (1, 2, 3) + + cube, cube_faces + ---------------- + + 8 vertices (face up) net: + + 0 1 2 3-0 + 4 5 6 7-4 + + 6 faces: + + (0, 1, 2, 3) + (0, 1, 5, 4) (1, 2, 6, 5) (2, 3, 7, 6) (0, 3, 7, 4) + (4, 5, 6, 7) + + octahedron, octahedron_faces + ---------------------------- + + 6 vertices (vertex up) net: + + 0 0 0-0 + 1 2 3 4-1 + 5 5 5-5 + + 8 faces: + + (0, 1, 2) (0, 2, 3) (0, 3, 4) (0, 1, 4) + (1, 2, 5) (2, 3, 5) (3, 4, 5) (1, 4, 5) + + dodecahedron, dodecahedron_faces + -------------------------------- + + 20 vertices (vertex up) net: + + 0 1 2 3 4 -0 + 5 6 7 8 9 -5 + 14 10 11 12 13-14 + 15 16 17 18 19-15 + + 12 faces: + + (0, 1, 2, 3, 4) (0, 1, 6, 10, 5) (1, 2, 7, 11, 6) + (2, 3, 8, 12, 7) (3, 4, 9, 13, 8) (0, 4, 9, 14, 5) + (5, 10, 16, 15, 14) (6, 10, 16, 17, 11) (7, 11, 17, 18, 12) + (8, 12, 18, 19, 13) (9, 13, 19, 15, 14)(15, 16, 17, 18, 19) + + icosahedron, icosahedron_faces + ------------------------------ + + 12 vertices (face up) net: + + 0 0 0 0 -0 + 1 2 3 4 5 -1 + 6 7 8 9 10 -6 + 11 11 11 11 -11 + + 20 faces: + + (0, 1, 2) (0, 2, 3) (0, 3, 4) + (0, 4, 5) (0, 1, 5) (1, 2, 6) + (2, 3, 7) (3, 4, 8) (4, 5, 9) + (1, 5, 10) (2, 6, 7) (3, 7, 8) + (4, 8, 9) (5, 9, 10) (1, 6, 10) + (6, 7, 11) (7, 8, 11) (8, 9, 11) + (9, 10, 11) (6, 10, 11) + + >>> from sympy.combinatorics.polyhedron import cube + >>> cube.edges + {(0, 1), (0, 3), (0, 4), (1, 2), (1, 5), (2, 3), (2, 6), (3, 7), (4, 5), (4, 7), (5, 6), (6, 7)} + + If you want to use letters or other names for the corners you + can still use the pre-calculated faces: + + >>> corners = list('abcdefgh') + >>> Polyhedron(corners, cube.faces).corners + (a, b, c, d, e, f, g, h) + + References + ========== + + .. [1] www.ocf.berkeley.edu/~wwu/articles/platonicsolids.pdf + + """ + faces = [minlex(f, directed=False, key=default_sort_key) for f in faces] + corners, faces, pgroup = args = \ + [Tuple(*a) for a in (corners, faces, pgroup)] + obj = Basic.__new__(cls, *args) + obj._corners = tuple(corners) # in order given + obj._faces = FiniteSet(*faces) + if pgroup and pgroup[0].size != len(corners): + raise ValueError("Permutation size unequal to number of corners.") + # use the identity permutation if none are given + obj._pgroup = PermutationGroup( + pgroup or [Perm(range(len(corners)))] ) + return obj + + @property + def corners(self): + """ + Get the corners of the Polyhedron. + + The method ``vertices`` is an alias for ``corners``. + + Examples + ======== + + >>> from sympy.combinatorics import Polyhedron + >>> from sympy.abc import a, b, c, d + >>> p = Polyhedron(list('abcd')) + >>> p.corners == p.vertices == (a, b, c, d) + True + + See Also + ======== + + array_form, cyclic_form + """ + return self._corners + vertices = corners + + @property + def array_form(self): + """Return the indices of the corners. + + The indices are given relative to the original position of corners. + + Examples + ======== + + >>> from sympy.combinatorics.polyhedron import tetrahedron + >>> tetrahedron = tetrahedron.copy() + >>> tetrahedron.array_form + [0, 1, 2, 3] + + >>> tetrahedron.rotate(0) + >>> tetrahedron.array_form + [0, 2, 3, 1] + >>> tetrahedron.pgroup[0].array_form + [0, 2, 3, 1] + + See Also + ======== + + corners, cyclic_form + """ + corners = list(self.args[0]) + return [corners.index(c) for c in self.corners] + + @property + def cyclic_form(self): + """Return the indices of the corners in cyclic notation. + + The indices are given relative to the original position of corners. + + See Also + ======== + + corners, array_form + """ + return Perm._af_new(self.array_form).cyclic_form + + @property + def size(self): + """ + Get the number of corners of the Polyhedron. + """ + return len(self._corners) + + @property + def faces(self): + """ + Get the faces of the Polyhedron. + """ + return self._faces + + @property + def pgroup(self): + """ + Get the permutations of the Polyhedron. + """ + return self._pgroup + + @property + def edges(self): + """ + Given the faces of the polyhedra we can get the edges. + + Examples + ======== + + >>> from sympy.combinatorics import Polyhedron + >>> from sympy.abc import a, b, c + >>> corners = (a, b, c) + >>> faces = [(0, 1, 2)] + >>> Polyhedron(corners, faces).edges + {(0, 1), (0, 2), (1, 2)} + + """ + if self._edges is None: + output = set() + for face in self.faces: + for i in range(len(face)): + edge = tuple(sorted([face[i], face[i - 1]])) + output.add(edge) + self._edges = FiniteSet(*output) + return self._edges + + def rotate(self, perm): + """ + Apply a permutation to the polyhedron *in place*. The permutation + may be given as a Permutation instance or an integer indicating + which permutation from pgroup of the Polyhedron should be + applied. + + This is an operation that is analogous to rotation about + an axis by a fixed increment. + + Notes + ===== + + When a Permutation is applied, no check is done to see if that + is a valid permutation for the Polyhedron. For example, a cube + could be given a permutation which effectively swaps only 2 + vertices. A valid permutation (that rotates the object in a + physical way) will be obtained if one only uses + permutations from the ``pgroup`` of the Polyhedron. On the other + hand, allowing arbitrary rotations (applications of permutations) + gives a way to follow named elements rather than indices since + Polyhedron allows vertices to be named while Permutation works + only with indices. + + Examples + ======== + + >>> from sympy.combinatorics import Polyhedron, Permutation + >>> from sympy.combinatorics.polyhedron import cube + >>> cube = cube.copy() + >>> cube.corners + (0, 1, 2, 3, 4, 5, 6, 7) + >>> cube.rotate(0) + >>> cube.corners + (1, 2, 3, 0, 5, 6, 7, 4) + + A non-physical "rotation" that is not prohibited by this method: + + >>> cube.reset() + >>> cube.rotate(Permutation([[1, 2]], size=8)) + >>> cube.corners + (0, 2, 1, 3, 4, 5, 6, 7) + + Polyhedron can be used to follow elements of set that are + identified by letters instead of integers: + + >>> shadow = h5 = Polyhedron(list('abcde')) + >>> p = Permutation([3, 0, 1, 2, 4]) + >>> h5.rotate(p) + >>> h5.corners + (d, a, b, c, e) + >>> _ == shadow.corners + True + >>> copy = h5.copy() + >>> h5.rotate(p) + >>> h5.corners == copy.corners + False + """ + if not isinstance(perm, Perm): + perm = self.pgroup[perm] + # and we know it's valid + else: + if perm.size != self.size: + raise ValueError('Polyhedron and Permutation sizes differ.') + a = perm.array_form + corners = [self.corners[a[i]] for i in range(len(self.corners))] + self._corners = tuple(corners) + + def reset(self): + """Return corners to their original positions. + + Examples + ======== + + >>> from sympy.combinatorics.polyhedron import tetrahedron as T + >>> T = T.copy() + >>> T.corners + (0, 1, 2, 3) + >>> T.rotate(0) + >>> T.corners + (0, 2, 3, 1) + >>> T.reset() + >>> T.corners + (0, 1, 2, 3) + """ + self._corners = self.args[0] + + +def _pgroup_calcs(): + """Return the permutation groups for each of the polyhedra and the face + definitions: tetrahedron, cube, octahedron, dodecahedron, icosahedron, + tetrahedron_faces, cube_faces, octahedron_faces, dodecahedron_faces, + icosahedron_faces + + Explanation + =========== + + (This author did not find and did not know of a better way to do it though + there likely is such a way.) + + Although only 2 permutations are needed for a polyhedron in order to + generate all the possible orientations, a group of permutations is + provided instead. A set of permutations is called a "group" if:: + + a*b = c (for any pair of permutations in the group, a and b, their + product, c, is in the group) + + a*(b*c) = (a*b)*c (for any 3 permutations in the group associativity holds) + + there is an identity permutation, I, such that I*a = a*I for all elements + in the group + + a*b = I (the inverse of each permutation is also in the group) + + None of the polyhedron groups defined follow these definitions of a group. + Instead, they are selected to contain those permutations whose powers + alone will construct all orientations of the polyhedron, i.e. for + permutations ``a``, ``b``, etc... in the group, ``a, a**2, ..., a**o_a``, + ``b, b**2, ..., b**o_b``, etc... (where ``o_i`` is the order of + permutation ``i``) generate all permutations of the polyhedron instead of + mixed products like ``a*b``, ``a*b**2``, etc.... + + Note that for a polyhedron with n vertices, the valid permutations of the + vertices exclude those that do not maintain its faces. e.g. the + permutation BCDE of a square's four corners, ABCD, is a valid + permutation while CBDE is not (because this would twist the square). + + Examples + ======== + + The is_group checks for: closure, the presence of the Identity permutation, + and the presence of the inverse for each of the elements in the group. This + confirms that none of the polyhedra are true groups: + + >>> from sympy.combinatorics.polyhedron import ( + ... tetrahedron, cube, octahedron, dodecahedron, icosahedron) + ... + >>> polyhedra = (tetrahedron, cube, octahedron, dodecahedron, icosahedron) + >>> [h.pgroup.is_group for h in polyhedra] + ... + [True, True, True, True, True] + + Although tests in polyhedron's test suite check that powers of the + permutations in the groups generate all permutations of the vertices + of the polyhedron, here we also demonstrate the powers of the given + permutations create a complete group for the tetrahedron: + + >>> from sympy.combinatorics import Permutation, PermutationGroup + >>> for h in polyhedra[:1]: + ... G = h.pgroup + ... perms = set() + ... for g in G: + ... for e in range(g.order()): + ... p = tuple((g**e).array_form) + ... perms.add(p) + ... + ... perms = [Permutation(p) for p in perms] + ... assert PermutationGroup(perms).is_group + + In addition to doing the above, the tests in the suite confirm that the + faces are all present after the application of each permutation. + + References + ========== + + .. [1] https://dogschool.tripod.com/trianglegroup.html + + """ + def _pgroup_of_double(polyh, ordered_faces, pgroup): + n = len(ordered_faces[0]) + # the vertices of the double which sits inside a give polyhedron + # can be found by tracking the faces of the outer polyhedron. + # A map between face and the vertex of the double is made so that + # after rotation the position of the vertices can be located + fmap = dict(zip(ordered_faces, + range(len(ordered_faces)))) + flat_faces = flatten(ordered_faces) + new_pgroup = [] + for p in pgroup: + h = polyh.copy() + h.rotate(p) + c = h.corners + # reorder corners in the order they should appear when + # enumerating the faces + reorder = unflatten([c[j] for j in flat_faces], n) + # make them canonical + reorder = [tuple(map(as_int, + minlex(f, directed=False))) + for f in reorder] + # map face to vertex: the resulting list of vertices are the + # permutation that we seek for the double + new_pgroup.append(Perm([fmap[f] for f in reorder])) + return new_pgroup + + tetrahedron_faces = [ + (0, 1, 2), (0, 2, 3), (0, 3, 1), # upper 3 + (1, 2, 3), # bottom + ] + + # cw from top + # + _t_pgroup = [ + Perm([[1, 2, 3], [0]]), # cw from top + Perm([[0, 1, 2], [3]]), # cw from front face + Perm([[0, 3, 2], [1]]), # cw from back right face + Perm([[0, 3, 1], [2]]), # cw from back left face + Perm([[0, 1], [2, 3]]), # through front left edge + Perm([[0, 2], [1, 3]]), # through front right edge + Perm([[0, 3], [1, 2]]), # through back edge + ] + + tetrahedron = Polyhedron( + range(4), + tetrahedron_faces, + _t_pgroup) + + cube_faces = [ + (0, 1, 2, 3), # upper + (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 3, 7, 4), # middle 4 + (4, 5, 6, 7), # lower + ] + + # U, D, F, B, L, R = up, down, front, back, left, right + _c_pgroup = [Perm(p) for p in + [ + [1, 2, 3, 0, 5, 6, 7, 4], # cw from top, U + [4, 0, 3, 7, 5, 1, 2, 6], # cw from F face + [4, 5, 1, 0, 7, 6, 2, 3], # cw from R face + + [1, 0, 4, 5, 2, 3, 7, 6], # cw through UF edge + [6, 2, 1, 5, 7, 3, 0, 4], # cw through UR edge + [6, 7, 3, 2, 5, 4, 0, 1], # cw through UB edge + [3, 7, 4, 0, 2, 6, 5, 1], # cw through UL edge + [4, 7, 6, 5, 0, 3, 2, 1], # cw through FL edge + [6, 5, 4, 7, 2, 1, 0, 3], # cw through FR edge + + [0, 3, 7, 4, 1, 2, 6, 5], # cw through UFL vertex + [5, 1, 0, 4, 6, 2, 3, 7], # cw through UFR vertex + [5, 6, 2, 1, 4, 7, 3, 0], # cw through UBR vertex + [7, 4, 0, 3, 6, 5, 1, 2], # cw through UBL + ]] + + cube = Polyhedron( + range(8), + cube_faces, + _c_pgroup) + + octahedron_faces = [ + (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 1, 4), # top 4 + (1, 2, 5), (2, 3, 5), (3, 4, 5), (1, 4, 5), # bottom 4 + ] + + octahedron = Polyhedron( + range(6), + octahedron_faces, + _pgroup_of_double(cube, cube_faces, _c_pgroup)) + + dodecahedron_faces = [ + (0, 1, 2, 3, 4), # top + (0, 1, 6, 10, 5), (1, 2, 7, 11, 6), (2, 3, 8, 12, 7), # upper 5 + (3, 4, 9, 13, 8), (0, 4, 9, 14, 5), + (5, 10, 16, 15, 14), (6, 10, 16, 17, 11), (7, 11, 17, 18, + 12), # lower 5 + (8, 12, 18, 19, 13), (9, 13, 19, 15, 14), + (15, 16, 17, 18, 19) # bottom + ] + + def _string_to_perm(s): + rv = [Perm(range(20))] + p = None + for si in s: + if si not in '01': + count = int(si) - 1 + else: + count = 1 + if si == '0': + p = _f0 + elif si == '1': + p = _f1 + rv.extend([p]*count) + return Perm.rmul(*rv) + + # top face cw + _f0 = Perm([ + 1, 2, 3, 4, 0, 6, 7, 8, 9, 5, 11, + 12, 13, 14, 10, 16, 17, 18, 19, 15]) + # front face cw + _f1 = Perm([ + 5, 0, 4, 9, 14, 10, 1, 3, 13, 15, + 6, 2, 8, 19, 16, 17, 11, 7, 12, 18]) + # the strings below, like 0104 are shorthand for F0*F1*F0**4 and are + # the remaining 4 face rotations, 15 edge permutations, and the + # 10 vertex rotations. + _dodeca_pgroup = [_f0, _f1] + [_string_to_perm(s) for s in ''' + 0104 140 014 0410 + 010 1403 03104 04103 102 + 120 1304 01303 021302 03130 + 0412041 041204103 04120410 041204104 041204102 + 10 01 1402 0140 04102 0412 1204 1302 0130 03120'''.strip().split()] + + dodecahedron = Polyhedron( + range(20), + dodecahedron_faces, + _dodeca_pgroup) + + icosahedron_faces = [ + (0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 5), (0, 1, 5), + (1, 6, 7), (1, 2, 7), (2, 7, 8), (2, 3, 8), (3, 8, 9), + (3, 4, 9), (4, 9, 10), (4, 5, 10), (5, 6, 10), (1, 5, 6), + (6, 7, 11), (7, 8, 11), (8, 9, 11), (9, 10, 11), (6, 10, 11)] + + icosahedron = Polyhedron( + range(12), + icosahedron_faces, + _pgroup_of_double( + dodecahedron, dodecahedron_faces, _dodeca_pgroup)) + + return (tetrahedron, cube, octahedron, dodecahedron, icosahedron, + tetrahedron_faces, cube_faces, octahedron_faces, + dodecahedron_faces, icosahedron_faces) + +# ----------------------------------------------------------------------- +# Standard Polyhedron groups +# +# These are generated using _pgroup_calcs() above. However to save +# import time we encode them explicitly here. +# ----------------------------------------------------------------------- + +tetrahedron = Polyhedron( + Tuple(0, 1, 2, 3), + Tuple( + Tuple(0, 1, 2), + Tuple(0, 2, 3), + Tuple(0, 1, 3), + Tuple(1, 2, 3)), + Tuple( + Perm(1, 2, 3), + Perm(3)(0, 1, 2), + Perm(0, 3, 2), + Perm(0, 3, 1), + Perm(0, 1)(2, 3), + Perm(0, 2)(1, 3), + Perm(0, 3)(1, 2) + )) + +cube = Polyhedron( + Tuple(0, 1, 2, 3, 4, 5, 6, 7), + Tuple( + Tuple(0, 1, 2, 3), + Tuple(0, 1, 5, 4), + Tuple(1, 2, 6, 5), + Tuple(2, 3, 7, 6), + Tuple(0, 3, 7, 4), + Tuple(4, 5, 6, 7)), + Tuple( + Perm(0, 1, 2, 3)(4, 5, 6, 7), + Perm(0, 4, 5, 1)(2, 3, 7, 6), + Perm(0, 4, 7, 3)(1, 5, 6, 2), + Perm(0, 1)(2, 4)(3, 5)(6, 7), + Perm(0, 6)(1, 2)(3, 5)(4, 7), + Perm(0, 6)(1, 7)(2, 3)(4, 5), + Perm(0, 3)(1, 7)(2, 4)(5, 6), + Perm(0, 4)(1, 7)(2, 6)(3, 5), + Perm(0, 6)(1, 5)(2, 4)(3, 7), + Perm(1, 3, 4)(2, 7, 5), + Perm(7)(0, 5, 2)(3, 4, 6), + Perm(0, 5, 7)(1, 6, 3), + Perm(0, 7, 2)(1, 4, 6))) + +octahedron = Polyhedron( + Tuple(0, 1, 2, 3, 4, 5), + Tuple( + Tuple(0, 1, 2), + Tuple(0, 2, 3), + Tuple(0, 3, 4), + Tuple(0, 1, 4), + Tuple(1, 2, 5), + Tuple(2, 3, 5), + Tuple(3, 4, 5), + Tuple(1, 4, 5)), + Tuple( + Perm(5)(1, 2, 3, 4), + Perm(0, 4, 5, 2), + Perm(0, 1, 5, 3), + Perm(0, 1)(2, 4)(3, 5), + Perm(0, 2)(1, 3)(4, 5), + Perm(0, 3)(1, 5)(2, 4), + Perm(0, 4)(1, 3)(2, 5), + Perm(0, 5)(1, 4)(2, 3), + Perm(0, 5)(1, 2)(3, 4), + Perm(0, 4, 1)(2, 3, 5), + Perm(0, 1, 2)(3, 4, 5), + Perm(0, 2, 3)(1, 5, 4), + Perm(0, 4, 3)(1, 5, 2))) + +dodecahedron = Polyhedron( + Tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), + Tuple( + Tuple(0, 1, 2, 3, 4), + Tuple(0, 1, 6, 10, 5), + Tuple(1, 2, 7, 11, 6), + Tuple(2, 3, 8, 12, 7), + Tuple(3, 4, 9, 13, 8), + Tuple(0, 4, 9, 14, 5), + Tuple(5, 10, 16, 15, 14), + Tuple(6, 10, 16, 17, 11), + Tuple(7, 11, 17, 18, 12), + Tuple(8, 12, 18, 19, 13), + Tuple(9, 13, 19, 15, 14), + Tuple(15, 16, 17, 18, 19)), + Tuple( + Perm(0, 1, 2, 3, 4)(5, 6, 7, 8, 9)(10, 11, 12, 13, 14)(15, 16, 17, 18, 19), + Perm(0, 5, 10, 6, 1)(2, 4, 14, 16, 11)(3, 9, 15, 17, 7)(8, 13, 19, 18, 12), + Perm(0, 10, 17, 12, 3)(1, 6, 11, 7, 2)(4, 5, 16, 18, 8)(9, 14, 15, 19, 13), + Perm(0, 6, 17, 19, 9)(1, 11, 18, 13, 4)(2, 7, 12, 8, 3)(5, 10, 16, 15, 14), + Perm(0, 2, 12, 19, 14)(1, 7, 18, 15, 5)(3, 8, 13, 9, 4)(6, 11, 17, 16, 10), + Perm(0, 4, 9, 14, 5)(1, 3, 13, 15, 10)(2, 8, 19, 16, 6)(7, 12, 18, 17, 11), + Perm(0, 1)(2, 5)(3, 10)(4, 6)(7, 14)(8, 16)(9, 11)(12, 15)(13, 17)(18, 19), + Perm(0, 7)(1, 2)(3, 6)(4, 11)(5, 12)(8, 10)(9, 17)(13, 16)(14, 18)(15, 19), + Perm(0, 12)(1, 8)(2, 3)(4, 7)(5, 18)(6, 13)(9, 11)(10, 19)(14, 17)(15, 16), + Perm(0, 8)(1, 13)(2, 9)(3, 4)(5, 12)(6, 19)(7, 14)(10, 18)(11, 15)(16, 17), + Perm(0, 4)(1, 9)(2, 14)(3, 5)(6, 13)(7, 15)(8, 10)(11, 19)(12, 16)(17, 18), + Perm(0, 5)(1, 14)(2, 15)(3, 16)(4, 10)(6, 9)(7, 19)(8, 17)(11, 13)(12, 18), + Perm(0, 11)(1, 6)(2, 10)(3, 16)(4, 17)(5, 7)(8, 15)(9, 18)(12, 14)(13, 19), + Perm(0, 18)(1, 12)(2, 7)(3, 11)(4, 17)(5, 19)(6, 8)(9, 16)(10, 13)(14, 15), + Perm(0, 18)(1, 19)(2, 13)(3, 8)(4, 12)(5, 17)(6, 15)(7, 9)(10, 16)(11, 14), + Perm(0, 13)(1, 19)(2, 15)(3, 14)(4, 9)(5, 8)(6, 18)(7, 16)(10, 12)(11, 17), + Perm(0, 16)(1, 15)(2, 19)(3, 18)(4, 17)(5, 10)(6, 14)(7, 13)(8, 12)(9, 11), + Perm(0, 18)(1, 17)(2, 16)(3, 15)(4, 19)(5, 12)(6, 11)(7, 10)(8, 14)(9, 13), + Perm(0, 15)(1, 19)(2, 18)(3, 17)(4, 16)(5, 14)(6, 13)(7, 12)(8, 11)(9, 10), + Perm(0, 17)(1, 16)(2, 15)(3, 19)(4, 18)(5, 11)(6, 10)(7, 14)(8, 13)(9, 12), + Perm(0, 19)(1, 18)(2, 17)(3, 16)(4, 15)(5, 13)(6, 12)(7, 11)(8, 10)(9, 14), + Perm(1, 4, 5)(2, 9, 10)(3, 14, 6)(7, 13, 16)(8, 15, 11)(12, 19, 17), + Perm(19)(0, 6, 2)(3, 5, 11)(4, 10, 7)(8, 14, 17)(9, 16, 12)(13, 15, 18), + Perm(0, 11, 8)(1, 7, 3)(4, 6, 12)(5, 17, 13)(9, 10, 18)(14, 16, 19), + Perm(0, 7, 13)(1, 12, 9)(2, 8, 4)(5, 11, 19)(6, 18, 14)(10, 17, 15), + Perm(0, 3, 9)(1, 8, 14)(2, 13, 5)(6, 12, 15)(7, 19, 10)(11, 18, 16), + Perm(0, 14, 10)(1, 9, 16)(2, 13, 17)(3, 19, 11)(4, 15, 6)(7, 8, 18), + Perm(0, 16, 7)(1, 10, 11)(2, 5, 17)(3, 14, 18)(4, 15, 12)(8, 9, 19), + Perm(0, 16, 13)(1, 17, 8)(2, 11, 12)(3, 6, 18)(4, 10, 19)(5, 15, 9), + Perm(0, 11, 15)(1, 17, 14)(2, 18, 9)(3, 12, 13)(4, 7, 19)(5, 6, 16), + Perm(0, 8, 15)(1, 12, 16)(2, 18, 10)(3, 19, 5)(4, 13, 14)(6, 7, 17))) + +icosahedron = Polyhedron( + Tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), + Tuple( + Tuple(0, 1, 2), + Tuple(0, 2, 3), + Tuple(0, 3, 4), + Tuple(0, 4, 5), + Tuple(0, 1, 5), + Tuple(1, 6, 7), + Tuple(1, 2, 7), + Tuple(2, 7, 8), + Tuple(2, 3, 8), + Tuple(3, 8, 9), + Tuple(3, 4, 9), + Tuple(4, 9, 10), + Tuple(4, 5, 10), + Tuple(5, 6, 10), + Tuple(1, 5, 6), + Tuple(6, 7, 11), + Tuple(7, 8, 11), + Tuple(8, 9, 11), + Tuple(9, 10, 11), + Tuple(6, 10, 11)), + Tuple( + Perm(11)(1, 2, 3, 4, 5)(6, 7, 8, 9, 10), + Perm(0, 5, 6, 7, 2)(3, 4, 10, 11, 8), + Perm(0, 1, 7, 8, 3)(4, 5, 6, 11, 9), + Perm(0, 2, 8, 9, 4)(1, 7, 11, 10, 5), + Perm(0, 3, 9, 10, 5)(1, 2, 8, 11, 6), + Perm(0, 4, 10, 6, 1)(2, 3, 9, 11, 7), + Perm(0, 1)(2, 5)(3, 6)(4, 7)(8, 10)(9, 11), + Perm(0, 2)(1, 3)(4, 7)(5, 8)(6, 9)(10, 11), + Perm(0, 3)(1, 9)(2, 4)(5, 8)(6, 11)(7, 10), + Perm(0, 4)(1, 9)(2, 10)(3, 5)(6, 8)(7, 11), + Perm(0, 5)(1, 4)(2, 10)(3, 6)(7, 9)(8, 11), + Perm(0, 6)(1, 5)(2, 10)(3, 11)(4, 7)(8, 9), + Perm(0, 7)(1, 2)(3, 6)(4, 11)(5, 8)(9, 10), + Perm(0, 8)(1, 9)(2, 3)(4, 7)(5, 11)(6, 10), + Perm(0, 9)(1, 11)(2, 10)(3, 4)(5, 8)(6, 7), + Perm(0, 10)(1, 9)(2, 11)(3, 6)(4, 5)(7, 8), + Perm(0, 11)(1, 6)(2, 10)(3, 9)(4, 8)(5, 7), + Perm(0, 11)(1, 8)(2, 7)(3, 6)(4, 10)(5, 9), + Perm(0, 11)(1, 10)(2, 9)(3, 8)(4, 7)(5, 6), + Perm(0, 11)(1, 7)(2, 6)(3, 10)(4, 9)(5, 8), + Perm(0, 11)(1, 9)(2, 8)(3, 7)(4, 6)(5, 10), + Perm(0, 5, 1)(2, 4, 6)(3, 10, 7)(8, 9, 11), + Perm(0, 1, 2)(3, 5, 7)(4, 6, 8)(9, 10, 11), + Perm(0, 2, 3)(1, 8, 4)(5, 7, 9)(6, 11, 10), + Perm(0, 3, 4)(1, 8, 10)(2, 9, 5)(6, 7, 11), + Perm(0, 4, 5)(1, 3, 10)(2, 9, 6)(7, 8, 11), + Perm(0, 10, 7)(1, 5, 6)(2, 4, 11)(3, 9, 8), + Perm(0, 6, 8)(1, 7, 2)(3, 5, 11)(4, 10, 9), + Perm(0, 7, 9)(1, 11, 4)(2, 8, 3)(5, 6, 10), + Perm(0, 8, 10)(1, 7, 6)(2, 11, 5)(3, 9, 4), + Perm(0, 9, 6)(1, 3, 11)(2, 8, 7)(4, 10, 5))) + +tetrahedron_faces = [tuple(arg) for arg in tetrahedron.faces] + +cube_faces = [tuple(arg) for arg in cube.faces] + +octahedron_faces = [tuple(arg) for arg in octahedron.faces] + +dodecahedron_faces = [tuple(arg) for arg in dodecahedron.faces] + +icosahedron_faces = [tuple(arg) for arg in icosahedron.faces] diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/prufer.py b/phivenv/Lib/site-packages/sympy/combinatorics/prufer.py new file mode 100644 index 0000000000000000000000000000000000000000..e389df87cddc0152b2376e18b9f3df2e94d3d2fb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/prufer.py @@ -0,0 +1,435 @@ +from sympy.core import Basic +from sympy.core.containers import Tuple +from sympy.tensor.array import Array +from sympy.core.sympify import _sympify +from sympy.utilities.iterables import flatten, iterable +from sympy.utilities.misc import as_int + +from collections import defaultdict + + +class Prufer(Basic): + """ + The Prufer correspondence is an algorithm that describes the + bijection between labeled trees and the Prufer code. A Prufer + code of a labeled tree is unique up to isomorphism and has + a length of n - 2. + + Prufer sequences were first used by Heinz Prufer to give a + proof of Cayley's formula. + + References + ========== + + .. [1] https://mathworld.wolfram.com/LabeledTree.html + + """ + _prufer_repr = None + _tree_repr = None + _nodes = None + _rank = None + + @property + def prufer_repr(self): + """Returns Prufer sequence for the Prufer object. + + This sequence is found by removing the highest numbered vertex, + recording the node it was attached to, and continuing until only + two vertices remain. The Prufer sequence is the list of recorded nodes. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).prufer_repr + [3, 3, 3, 4] + >>> Prufer([1, 0, 0]).prufer_repr + [1, 0, 0] + + See Also + ======== + + to_prufer + + """ + if self._prufer_repr is None: + self._prufer_repr = self.to_prufer(self._tree_repr[:], self.nodes) + return self._prufer_repr + + @property + def tree_repr(self): + """Returns the tree representation of the Prufer object. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).tree_repr + [[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]] + >>> Prufer([1, 0, 0]).tree_repr + [[1, 2], [0, 1], [0, 3], [0, 4]] + + See Also + ======== + + to_tree + + """ + if self._tree_repr is None: + self._tree_repr = self.to_tree(self._prufer_repr[:]) + return self._tree_repr + + @property + def nodes(self): + """Returns the number of nodes in the tree. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]).nodes + 6 + >>> Prufer([1, 0, 0]).nodes + 5 + + """ + return self._nodes + + @property + def rank(self): + """Returns the rank of the Prufer sequence. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> p = Prufer([[0, 3], [1, 3], [2, 3], [3, 4], [4, 5]]) + >>> p.rank + 778 + >>> p.next(1).rank + 779 + >>> p.prev().rank + 777 + + See Also + ======== + + prufer_rank, next, prev, size + + """ + if self._rank is None: + self._rank = self.prufer_rank() + return self._rank + + @property + def size(self): + """Return the number of possible trees of this Prufer object. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> Prufer([0]*4).size == Prufer([6]*4).size == 1296 + True + + See Also + ======== + + prufer_rank, rank, next, prev + + """ + return self.prev(self.rank).prev().rank + 1 + + @staticmethod + def to_prufer(tree, n): + """Return the Prufer sequence for a tree given as a list of edges where + ``n`` is the number of nodes in the tree. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> a = Prufer([[0, 1], [0, 2], [0, 3]]) + >>> a.prufer_repr + [0, 0] + >>> Prufer.to_prufer([[0, 1], [0, 2], [0, 3]], 4) + [0, 0] + + See Also + ======== + prufer_repr: returns Prufer sequence of a Prufer object. + + """ + d = defaultdict(int) + L = [] + for edge in tree: + # Increment the value of the corresponding + # node in the degree list as we encounter an + # edge involving it. + d[edge[0]] += 1 + d[edge[1]] += 1 + for i in range(n - 2): + # find the smallest leaf + for x in range(n): + if d[x] == 1: + break + # find the node it was connected to + y = None + for edge in tree: + if x == edge[0]: + y = edge[1] + elif x == edge[1]: + y = edge[0] + if y is not None: + break + # record and update + L.append(y) + for j in (x, y): + d[j] -= 1 + if not d[j]: + d.pop(j) + tree.remove(edge) + return L + + @staticmethod + def to_tree(prufer): + """Return the tree (as a list of edges) of the given Prufer sequence. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> a = Prufer([0, 2], 4) + >>> a.tree_repr + [[0, 1], [0, 2], [2, 3]] + >>> Prufer.to_tree([0, 2]) + [[0, 1], [0, 2], [2, 3]] + + References + ========== + + .. [1] https://hamberg.no/erlend/posts/2010-11-06-prufer-sequence-compact-tree-representation.html + + See Also + ======== + tree_repr: returns tree representation of a Prufer object. + + """ + tree = [] + last = [] + n = len(prufer) + 2 + d = defaultdict(lambda: 1) + for p in prufer: + d[p] += 1 + for i in prufer: + for j in range(n): + # find the smallest leaf (degree = 1) + if d[j] == 1: + break + # (i, j) is the new edge that we append to the tree + # and remove from the degree dictionary + d[i] -= 1 + d[j] -= 1 + tree.append(sorted([i, j])) + last = [i for i in range(n) if d[i] == 1] or [0, 1] + tree.append(last) + + return tree + + @staticmethod + def edges(*runs): + """Return a list of edges and the number of nodes from the given runs + that connect nodes in an integer-labelled tree. + + All node numbers will be shifted so that the minimum node is 0. It is + not a problem if edges are repeated in the runs; only unique edges are + returned. There is no assumption made about what the range of the node + labels should be, but all nodes from the smallest through the largest + must be present. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> Prufer.edges([1, 2, 3], [2, 4, 5]) # a T + ([[0, 1], [1, 2], [1, 3], [3, 4]], 5) + + Duplicate edges are removed: + + >>> Prufer.edges([0, 1, 2, 3], [1, 4, 5], [1, 4, 6]) # a K + ([[0, 1], [1, 2], [1, 4], [2, 3], [4, 5], [4, 6]], 7) + + """ + e = set() + nmin = runs[0][0] + for r in runs: + for i in range(len(r) - 1): + a, b = r[i: i + 2] + if b < a: + a, b = b, a + e.add((a, b)) + rv = [] + got = set() + nmin = nmax = None + for ei in e: + got.update(ei) + nmin = min(ei[0], nmin) if nmin is not None else ei[0] + nmax = max(ei[1], nmax) if nmax is not None else ei[1] + rv.append(list(ei)) + missing = set(range(nmin, nmax + 1)) - got + if missing: + missing = [i + nmin for i in missing] + if len(missing) == 1: + msg = 'Node %s is missing.' % missing.pop() + else: + msg = 'Nodes %s are missing.' % sorted(missing) + raise ValueError(msg) + if nmin != 0: + for i, ei in enumerate(rv): + rv[i] = [n - nmin for n in ei] + nmax -= nmin + return sorted(rv), nmax + 1 + + def prufer_rank(self): + """Computes the rank of a Prufer sequence. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> a = Prufer([[0, 1], [0, 2], [0, 3]]) + >>> a.prufer_rank() + 0 + + See Also + ======== + + rank, next, prev, size + + """ + r = 0 + p = 1 + for i in range(self.nodes - 3, -1, -1): + r += p*self.prufer_repr[i] + p *= self.nodes + return r + + @classmethod + def unrank(self, rank, n): + """Finds the unranked Prufer sequence. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> Prufer.unrank(0, 4) + Prufer([0, 0]) + + """ + n, rank = as_int(n), as_int(rank) + L = defaultdict(int) + for i in range(n - 3, -1, -1): + L[i] = rank % n + rank = (rank - L[i])//n + return Prufer([L[i] for i in range(len(L))]) + + def __new__(cls, *args, **kw_args): + """The constructor for the Prufer object. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + + A Prufer object can be constructed from a list of edges: + + >>> a = Prufer([[0, 1], [0, 2], [0, 3]]) + >>> a.prufer_repr + [0, 0] + + If the number of nodes is given, no checking of the nodes will + be performed; it will be assumed that nodes 0 through n - 1 are + present: + + >>> Prufer([[0, 1], [0, 2], [0, 3]], 4) + Prufer([[0, 1], [0, 2], [0, 3]], 4) + + A Prufer object can be constructed from a Prufer sequence: + + >>> b = Prufer([1, 3]) + >>> b.tree_repr + [[0, 1], [1, 3], [2, 3]] + + """ + arg0 = Array(args[0]) if args[0] else Tuple() + args = (arg0,) + tuple(_sympify(arg) for arg in args[1:]) + ret_obj = Basic.__new__(cls, *args, **kw_args) + args = [list(args[0])] + if args[0] and iterable(args[0][0]): + if not args[0][0]: + raise ValueError( + 'Prufer expects at least one edge in the tree.') + if len(args) > 1: + nnodes = args[1] + else: + nodes = set(flatten(args[0])) + nnodes = max(nodes) + 1 + if nnodes != len(nodes): + missing = set(range(nnodes)) - nodes + if len(missing) == 1: + msg = 'Node %s is missing.' % missing.pop() + else: + msg = 'Nodes %s are missing.' % sorted(missing) + raise ValueError(msg) + ret_obj._tree_repr = [list(i) for i in args[0]] + ret_obj._nodes = nnodes + else: + ret_obj._prufer_repr = args[0] + ret_obj._nodes = len(ret_obj._prufer_repr) + 2 + return ret_obj + + def next(self, delta=1): + """Generates the Prufer sequence that is delta beyond the current one. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> a = Prufer([[0, 1], [0, 2], [0, 3]]) + >>> b = a.next(1) # == a.next() + >>> b.tree_repr + [[0, 2], [0, 1], [1, 3]] + >>> b.rank + 1 + + See Also + ======== + + prufer_rank, rank, prev, size + + """ + return Prufer.unrank(self.rank + delta, self.nodes) + + def prev(self, delta=1): + """Generates the Prufer sequence that is -delta before the current one. + + Examples + ======== + + >>> from sympy.combinatorics.prufer import Prufer + >>> a = Prufer([[0, 1], [1, 2], [2, 3], [1, 4]]) + >>> a.rank + 36 + >>> b = a.prev() + >>> b + Prufer([1, 2, 0]) + >>> b.rank + 35 + + See Also + ======== + + prufer_rank, rank, next, size + + """ + return Prufer.unrank(self.rank -delta, self.nodes) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/rewritingsystem.py b/phivenv/Lib/site-packages/sympy/combinatorics/rewritingsystem.py new file mode 100644 index 0000000000000000000000000000000000000000..b9e8dfe4c831db6490c987c85a57d0d1a939de61 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/rewritingsystem.py @@ -0,0 +1,453 @@ +from collections import deque +from sympy.combinatorics.rewritingsystem_fsm import StateMachine + +class RewritingSystem: + ''' + A class implementing rewriting systems for `FpGroup`s. + + References + ========== + .. [1] Epstein, D., Holt, D. and Rees, S. (1991). + The use of Knuth-Bendix methods to solve the word problem in automatic groups. + Journal of Symbolic Computation, 12(4-5), pp.397-414. + + .. [2] GAP's Manual on its KBMAG package + https://www.gap-system.org/Manuals/pkg/kbmag-1.5.3/doc/manual.pdf + + ''' + def __init__(self, group): + self.group = group + self.alphabet = group.generators + self._is_confluent = None + + # these values are taken from [2] + self.maxeqns = 32767 # max rules + self.tidyint = 100 # rules before tidying + + # _max_exceeded is True if maxeqns is exceeded + # at any point + self._max_exceeded = False + + # Reduction automaton + self.reduction_automaton = None + self._new_rules = {} + + # dictionary of reductions + self.rules = {} + self.rules_cache = deque([], 50) + self._init_rules() + + + # All the transition symbols in the automaton + generators = list(self.alphabet) + generators += [gen**-1 for gen in generators] + # Create a finite state machine as an instance of the StateMachine object + self.reduction_automaton = StateMachine('Reduction automaton for '+ repr(self.group), generators) + self.construct_automaton() + + def set_max(self, n): + ''' + Set the maximum number of rules that can be defined + + ''' + if n > self.maxeqns: + self._max_exceeded = False + self.maxeqns = n + return + + @property + def is_confluent(self): + ''' + Return `True` if the system is confluent + + ''' + if self._is_confluent is None: + self._is_confluent = self._check_confluence() + return self._is_confluent + + def _init_rules(self): + identity = self.group.free_group.identity + for r in self.group.relators: + self.add_rule(r, identity) + self._remove_redundancies() + return + + def _add_rule(self, r1, r2): + ''' + Add the rule r1 -> r2 with no checking or further + deductions + + ''' + if len(self.rules) + 1 > self.maxeqns: + self._is_confluent = self._check_confluence() + self._max_exceeded = True + raise RuntimeError("Too many rules were defined.") + self.rules[r1] = r2 + # Add the newly added rule to the `new_rules` dictionary. + if self.reduction_automaton: + self._new_rules[r1] = r2 + + def add_rule(self, w1, w2, check=False): + new_keys = set() + + if w1 == w2: + return new_keys + + if w1 < w2: + w1, w2 = w2, w1 + + if (w1, w2) in self.rules_cache: + return new_keys + self.rules_cache.append((w1, w2)) + + s1, s2 = w1, w2 + + # The following is the equivalent of checking + # s1 for overlaps with the implicit reductions + # {g*g**-1 -> } and {g**-1*g -> } + # for any generator g without installing the + # redundant rules that would result from processing + # the overlaps. See [1], Section 3 for details. + + if len(s1) - len(s2) < 3: + if s1 not in self.rules: + new_keys.add(s1) + if not check: + self._add_rule(s1, s2) + if s2**-1 > s1**-1 and s2**-1 not in self.rules: + new_keys.add(s2**-1) + if not check: + self._add_rule(s2**-1, s1**-1) + + # overlaps on the right + while len(s1) - len(s2) > -1: + g = s1[len(s1)-1] + s1 = s1.subword(0, len(s1)-1) + s2 = s2*g**-1 + if len(s1) - len(s2) < 0: + if s2 not in self.rules: + if not check: + self._add_rule(s2, s1) + new_keys.add(s2) + elif len(s1) - len(s2) < 3: + new = self.add_rule(s1, s2, check) + new_keys.update(new) + + # overlaps on the left + while len(w1) - len(w2) > -1: + g = w1[0] + w1 = w1.subword(1, len(w1)) + w2 = g**-1*w2 + if len(w1) - len(w2) < 0: + if w2 not in self.rules: + if not check: + self._add_rule(w2, w1) + new_keys.add(w2) + elif len(w1) - len(w2) < 3: + new = self.add_rule(w1, w2, check) + new_keys.update(new) + + return new_keys + + def _remove_redundancies(self, changes=False): + ''' + Reduce left- and right-hand sides of reduction rules + and remove redundant equations (i.e. those for which + lhs == rhs). If `changes` is `True`, return a set + containing the removed keys and a set containing the + added keys + + ''' + removed = set() + added = set() + rules = self.rules.copy() + for r in rules: + v = self.reduce(r, exclude=r) + w = self.reduce(rules[r]) + if v != r: + del self.rules[r] + removed.add(r) + if v > w: + added.add(v) + self.rules[v] = w + elif v < w: + added.add(w) + self.rules[w] = v + else: + self.rules[v] = w + if changes: + return removed, added + return + + def make_confluent(self, check=False): + ''' + Try to make the system confluent using the Knuth-Bendix + completion algorithm + + ''' + if self._max_exceeded: + return self._is_confluent + lhs = list(self.rules.keys()) + + def _overlaps(r1, r2): + len1 = len(r1) + len2 = len(r2) + result = [] + for j in range(1, len1 + len2): + if (r1.subword(len1 - j, len1 + len2 - j, strict=False) + == r2.subword(j - len1, j, strict=False)): + a = r1.subword(0, len1-j, strict=False) + a = a*r2.subword(0, j-len1, strict=False) + b = r2.subword(j-len1, j, strict=False) + c = r2.subword(j, len2, strict=False) + c = c*r1.subword(len1 + len2 - j, len1, strict=False) + result.append(a*b*c) + return result + + def _process_overlap(w, r1, r2, check): + s = w.eliminate_word(r1, self.rules[r1]) + s = self.reduce(s) + t = w.eliminate_word(r2, self.rules[r2]) + t = self.reduce(t) + if s != t: + if check: + # system not confluent + return [0] + try: + new_keys = self.add_rule(t, s, check) + return new_keys + except RuntimeError: + return False + return + + added = 0 + i = 0 + while i < len(lhs): + r1 = lhs[i] + i += 1 + # j could be i+1 to not + # check each pair twice but lhs + # is extended in the loop and the new + # elements have to be checked with the + # preceding ones. there is probably a better way + # to handle this + j = 0 + while j < len(lhs): + r2 = lhs[j] + j += 1 + if r1 == r2: + continue + overlaps = _overlaps(r1, r2) + overlaps.extend(_overlaps(r1**-1, r2)) + if not overlaps: + continue + for w in overlaps: + new_keys = _process_overlap(w, r1, r2, check) + if new_keys: + if check: + return False + lhs.extend(new_keys) + added += len(new_keys) + elif new_keys == False: + # too many rules were added so the process + # couldn't complete + return self._is_confluent + + if added > self.tidyint and not check: + # tidy up + r, a = self._remove_redundancies(changes=True) + added = 0 + if r: + # reset i since some elements were removed + i = min(lhs.index(s) for s in r) + lhs = [l for l in lhs if l not in r] + lhs.extend(a) + if r1 in r: + # r1 was removed as redundant + break + + self._is_confluent = True + if not check: + self._remove_redundancies() + return True + + def _check_confluence(self): + return self.make_confluent(check=True) + + def reduce(self, word, exclude=None): + ''' + Apply reduction rules to `word` excluding the reduction rule + for the lhs equal to `exclude` + + ''' + rules = {r: self.rules[r] for r in self.rules if r != exclude} + # the following is essentially `eliminate_words()` code from the + # `FreeGroupElement` class, the only difference being the first + # "if" statement + again = True + new = word + while again: + again = False + for r in rules: + prev = new + if rules[r]**-1 > r**-1: + new = new.eliminate_word(r, rules[r], _all=True, inverse=False) + else: + new = new.eliminate_word(r, rules[r], _all=True) + if new != prev: + again = True + return new + + def _compute_inverse_rules(self, rules): + ''' + Compute the inverse rules for a given set of rules. + The inverse rules are used in the automaton for word reduction. + + Arguments: + rules (dictionary): Rules for which the inverse rules are to computed. + + Returns: + Dictionary of inverse_rules. + + ''' + inverse_rules = {} + for r in rules: + rule_key_inverse = r**-1 + rule_value_inverse = (rules[r])**-1 + if (rule_value_inverse < rule_key_inverse): + inverse_rules[rule_key_inverse] = rule_value_inverse + else: + inverse_rules[rule_value_inverse] = rule_key_inverse + return inverse_rules + + def construct_automaton(self): + ''' + Construct the automaton based on the set of reduction rules of the system. + + Automata Design: + The accept states of the automaton are the proper prefixes of the left hand side of the rules. + The complete left hand side of the rules are the dead states of the automaton. + + ''' + self._add_to_automaton(self.rules) + + def _add_to_automaton(self, rules): + ''' + Add new states and transitions to the automaton. + + Summary: + States corresponding to the new rules added to the system are computed and added to the automaton. + Transitions in the previously added states are also modified if necessary. + + Arguments: + rules (dictionary) -- Dictionary of the newly added rules. + + ''' + # Automaton variables + automaton_alphabet = [] + proper_prefixes = {} + + # compute the inverses of all the new rules added + all_rules = rules + inverse_rules = self._compute_inverse_rules(all_rules) + all_rules.update(inverse_rules) + + # Keep track of the accept_states. + accept_states = [] + + for rule in all_rules: + # The symbols present in the new rules are the symbols to be verified at each state. + # computes the automaton_alphabet, as the transitions solely depend upon the new states. + automaton_alphabet += rule.letter_form_elm + # Compute the proper prefixes for every rule. + proper_prefixes[rule] = [] + letter_word_array = list(rule.letter_form_elm) + len_letter_word_array = len(letter_word_array) + for i in range (1, len_letter_word_array): + letter_word_array[i] = letter_word_array[i-1]*letter_word_array[i] + # Add accept states. + elem = letter_word_array[i-1] + if elem not in self.reduction_automaton.states: + self.reduction_automaton.add_state(elem, state_type='a') + accept_states.append(elem) + proper_prefixes[rule] = letter_word_array + # Check for overlaps between dead and accept states. + if rule in accept_states: + self.reduction_automaton.states[rule].state_type = 'd' + self.reduction_automaton.states[rule].rh_rule = all_rules[rule] + accept_states.remove(rule) + # Add dead states + if rule not in self.reduction_automaton.states: + self.reduction_automaton.add_state(rule, state_type='d', rh_rule=all_rules[rule]) + + automaton_alphabet = set(automaton_alphabet) + + # Add new transitions for every state. + for state in self.reduction_automaton.states: + current_state_name = state + current_state_type = self.reduction_automaton.states[state].state_type + # Transitions will be modified only when suffixes of the current_state + # belongs to the proper_prefixes of the new rules. + # The rest are ignored if they cannot lead to a dead state after a finite number of transisitons. + if current_state_type == 's': + for letter in automaton_alphabet: + if letter in self.reduction_automaton.states: + self.reduction_automaton.states[state].add_transition(letter, letter) + else: + self.reduction_automaton.states[state].add_transition(letter, current_state_name) + elif current_state_type == 'a': + # Check if the transition to any new state in possible. + for letter in automaton_alphabet: + _next = current_state_name*letter + while len(_next) and _next not in self.reduction_automaton.states: + _next = _next.subword(1, len(_next)) + if not len(_next): + _next = 'start' + self.reduction_automaton.states[state].add_transition(letter, _next) + + # Add transitions for new states. All symbols used in the automaton are considered here. + # Ignore this if `reduction_automaton.automaton_alphabet` = `automaton_alphabet`. + if len(self.reduction_automaton.automaton_alphabet) != len(automaton_alphabet): + for state in accept_states: + current_state_name = state + for letter in self.reduction_automaton.automaton_alphabet: + _next = current_state_name*letter + while len(_next) and _next not in self.reduction_automaton.states: + _next = _next.subword(1, len(_next)) + if not len(_next): + _next = 'start' + self.reduction_automaton.states[state].add_transition(letter, _next) + + def reduce_using_automaton(self, word): + ''' + Reduce a word using an automaton. + + Summary: + All the symbols of the word are stored in an array and are given as the input to the automaton. + If the automaton reaches a dead state that subword is replaced and the automaton is run from the beginning. + The complete word has to be replaced when the word is read and the automaton reaches a dead state. + So, this process is repeated until the word is read completely and the automaton reaches the accept state. + + Arguments: + word (instance of FreeGroupElement) -- Word that needs to be reduced. + + ''' + # Modify the automaton if new rules are found. + if self._new_rules: + self._add_to_automaton(self._new_rules) + self._new_rules = {} + + flag = 1 + while flag: + flag = 0 + current_state = self.reduction_automaton.states['start'] + for i, s in enumerate(word.letter_form_elm): + next_state_name = current_state.transitions[s] + next_state = self.reduction_automaton.states[next_state_name] + if next_state.state_type == 'd': + subst = next_state.rh_rule + word = word.substituted_word(i - len(next_state_name) + 1, i+1, subst) + flag = 1 + break + current_state = next_state + return word diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/rewritingsystem_fsm.py b/phivenv/Lib/site-packages/sympy/combinatorics/rewritingsystem_fsm.py new file mode 100644 index 0000000000000000000000000000000000000000..21916530040ac321180692d1a0811da4ae36a056 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/rewritingsystem_fsm.py @@ -0,0 +1,60 @@ +class State: + ''' + A representation of a state managed by a ``StateMachine``. + + Attributes: + name (instance of FreeGroupElement or string) -- State name which is also assigned to the Machine. + transisitons (OrderedDict) -- Represents all the transitions of the state object. + state_type (string) -- Denotes the type (accept/start/dead) of the state. + rh_rule (instance of FreeGroupElement) -- right hand rule for dead state. + state_machine (instance of StateMachine object) -- The finite state machine that the state belongs to. + ''' + + def __init__(self, name, state_machine, state_type=None, rh_rule=None): + self.name = name + self.transitions = {} + self.state_machine = state_machine + self.state_type = state_type[0] + self.rh_rule = rh_rule + + def add_transition(self, letter, state): + ''' + Add a transition from the current state to a new state. + + Keyword Arguments: + letter -- The alphabet element the current state reads to make the state transition. + state -- This will be an instance of the State object which represents a new state after in the transition after the alphabet is read. + + ''' + self.transitions[letter] = state + +class StateMachine: + ''' + Representation of a finite state machine the manages the states and the transitions of the automaton. + + Attributes: + states (dictionary) -- Collection of all registered `State` objects. + name (str) -- Name of the state machine. + ''' + + def __init__(self, name, automaton_alphabet): + self.name = name + self.automaton_alphabet = automaton_alphabet + self.states = {} # Contains all the states in the machine. + self.add_state('start', state_type='s') + + def add_state(self, state_name, state_type=None, rh_rule=None): + ''' + Instantiate a state object and stores it in the 'states' dictionary. + + Arguments: + state_name (instance of FreeGroupElement or string) -- name of the new states. + state_type (string) -- Denotes the type (accept/start/dead) of the state added. + rh_rule (instance of FreeGroupElement) -- right hand rule for dead state. + + ''' + new_state = State(state_name, self, state_type, rh_rule) + self.states[state_name] = new_state + + def __repr__(self): + return "%s" % (self.name) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/schur_number.py b/phivenv/Lib/site-packages/sympy/combinatorics/schur_number.py new file mode 100644 index 0000000000000000000000000000000000000000..83aac98e543d4b54d4e6af17adca6e4f4de1b9ac --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/schur_number.py @@ -0,0 +1,160 @@ +""" +The Schur number S(k) is the largest integer n for which the interval [1,n] +can be partitioned into k sum-free sets.(https://mathworld.wolfram.com/SchurNumber.html) +""" +import math +from sympy.core import S +from sympy.core.basic import Basic +from sympy.core.function import Function +from sympy.core.numbers import Integer + + +class SchurNumber(Function): + r""" + This function creates a SchurNumber object + which is evaluated for `k \le 5` otherwise only + the lower bound information can be retrieved. + + Examples + ======== + + >>> from sympy.combinatorics.schur_number import SchurNumber + + Since S(3) = 13, hence the output is a number + >>> SchurNumber(3) + 13 + + We do not know the Schur number for values greater than 5, hence + only the object is returned + >>> SchurNumber(6) + SchurNumber(6) + + Now, the lower bound information can be retrieved using lower_bound() + method + >>> SchurNumber(6).lower_bound() + 536 + + """ + + @classmethod + def eval(cls, k): + if k.is_Number: + if k is S.Infinity: + return S.Infinity + if k.is_zero: + return S.Zero + if not k.is_integer or k.is_negative: + raise ValueError("k should be a positive integer") + first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160} + if k <= 5: + return Integer(first_known_schur_numbers[k]) + + def lower_bound(self): + f_ = self.args[0] + # Improved lower bounds known for S(6) and S(7) + if f_ == 6: + return Integer(536) + if f_ == 7: + return Integer(1680) + # For other cases, use general expression + if f_.is_Integer: + return 3*self.func(f_ - 1).lower_bound() - 1 + return (3**f_ - 1)/2 + + +def _schur_subsets_number(n): + + if n is S.Infinity: + raise ValueError("Input must be finite") + if n <= 0: + raise ValueError("n must be a non-zero positive integer.") + elif n <= 3: + min_k = 1 + else: + min_k = math.ceil(math.log(2*n + 1, 3)) + + return Integer(min_k) + + +def schur_partition(n): + """ + + This function returns the partition in the minimum number of sum-free subsets + according to the lower bound given by the Schur Number. + + Parameters + ========== + + n: a number + n is the upper limit of the range [1, n] for which we need to find and + return the minimum number of free subsets according to the lower bound + of schur number + + Returns + ======= + + List of lists + List of the minimum number of sum-free subsets + + Notes + ===== + + It is possible for some n to make the partition into less + subsets since the only known Schur numbers are: + S(1) = 1, S(2) = 4, S(3) = 13, S(4) = 44. + e.g for n = 44 the lower bound from the function above is 5 subsets but it has been proven + that can be done with 4 subsets. + + Examples + ======== + + For n = 1, 2, 3 the answer is the set itself + + >>> from sympy.combinatorics.schur_number import schur_partition + >>> schur_partition(2) + [[1, 2]] + + For n > 3, the answer is the minimum number of sum-free subsets: + + >>> schur_partition(5) + [[3, 2], [5], [1, 4]] + + >>> schur_partition(8) + [[3, 2], [6, 5, 8], [1, 4, 7]] + """ + + if isinstance(n, Basic) and not n.is_Number: + raise ValueError("Input value must be a number") + + number_of_subsets = _schur_subsets_number(n) + if n == 1: + sum_free_subsets = [[1]] + elif n == 2: + sum_free_subsets = [[1, 2]] + elif n == 3: + sum_free_subsets = [[1, 2, 3]] + else: + sum_free_subsets = [[1, 4], [2, 3]] + + while len(sum_free_subsets) < number_of_subsets: + sum_free_subsets = _generate_next_list(sum_free_subsets, n) + missed_elements = [3*k + 1 for k in range(len(sum_free_subsets), (n-1)//3 + 1)] + sum_free_subsets[-1] += missed_elements + + return sum_free_subsets + + +def _generate_next_list(current_list, n): + new_list = [] + + for item in current_list: + temp_1 = [number*3 for number in item if number*3 <= n] + temp_2 = [number*3 - 1 for number in item if number*3 - 1 <= n] + new_item = temp_1 + temp_2 + new_list.append(new_item) + + last_list = [3*k + 1 for k in range(len(current_list)+1) if 3*k + 1 <= n] + new_list.append(last_list) + current_list = new_list + + return current_list diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/subsets.py b/phivenv/Lib/site-packages/sympy/combinatorics/subsets.py new file mode 100644 index 0000000000000000000000000000000000000000..e540cb2395cb27e04c9d513831cb834a05ec2abd --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/subsets.py @@ -0,0 +1,619 @@ +from itertools import combinations + +from sympy.combinatorics.graycode import GrayCode + + +class Subset(): + """ + Represents a basic subset object. + + Explanation + =========== + + We generate subsets using essentially two techniques, + binary enumeration and lexicographic enumeration. + The Subset class takes two arguments, the first one + describes the initial subset to consider and the second + describes the superset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.next_binary().subset + ['b'] + >>> a.prev_binary().subset + ['c'] + """ + + _rank_binary = None + _rank_lex = None + _rank_graycode = None + _subset = None + _superset = None + + def __new__(cls, subset, superset): + """ + Default constructor. + + It takes the ``subset`` and its ``superset`` as its parameters. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.subset + ['c', 'd'] + >>> a.superset + ['a', 'b', 'c', 'd'] + >>> a.size + 2 + """ + if len(subset) > len(superset): + raise ValueError('Invalid arguments have been provided. The ' + 'superset must be larger than the subset.') + for elem in subset: + if elem not in superset: + raise ValueError('The superset provided is invalid as it does ' + 'not contain the element {}'.format(elem)) + obj = object.__new__(cls) + obj._subset = subset + obj._superset = superset + return obj + + def __eq__(self, other): + """Return a boolean indicating whether a == b on the basis of + whether both objects are of the class Subset and if the values + of the subset and superset attributes are the same. + """ + if not isinstance(other, Subset): + return NotImplemented + return self.subset == other.subset and self.superset == other.superset + + def iterate_binary(self, k): + """ + This is a helper function. It iterates over the + binary subsets by ``k`` steps. This variable can be + both positive or negative. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.iterate_binary(-2).subset + ['d'] + >>> a = Subset(['a', 'b', 'c'], ['a', 'b', 'c', 'd']) + >>> a.iterate_binary(2).subset + [] + + See Also + ======== + + next_binary, prev_binary + """ + bin_list = Subset.bitlist_from_subset(self.subset, self.superset) + n = (int(''.join(bin_list), 2) + k) % 2**self.superset_size + bits = bin(n)[2:].rjust(self.superset_size, '0') + return Subset.subset_from_bitlist(self.superset, bits) + + def next_binary(self): + """ + Generates the next binary ordered subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.next_binary().subset + ['b'] + >>> a = Subset(['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.next_binary().subset + [] + + See Also + ======== + + prev_binary, iterate_binary + """ + return self.iterate_binary(1) + + def prev_binary(self): + """ + Generates the previous binary ordered subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset([], ['a', 'b', 'c', 'd']) + >>> a.prev_binary().subset + ['a', 'b', 'c', 'd'] + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.prev_binary().subset + ['c'] + + See Also + ======== + + next_binary, iterate_binary + """ + return self.iterate_binary(-1) + + def next_lexicographic(self): + """ + Generates the next lexicographically ordered subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.next_lexicographic().subset + ['d'] + >>> a = Subset(['d'], ['a', 'b', 'c', 'd']) + >>> a.next_lexicographic().subset + [] + + See Also + ======== + + prev_lexicographic + """ + i = self.superset_size - 1 + indices = Subset.subset_indices(self.subset, self.superset) + + if i in indices: + if i - 1 in indices: + indices.remove(i - 1) + else: + indices.remove(i) + i = i - 1 + while i >= 0 and i not in indices: + i = i - 1 + if i >= 0: + indices.remove(i) + indices.append(i+1) + else: + while i not in indices and i >= 0: + i = i - 1 + indices.append(i + 1) + + ret_set = [] + super_set = self.superset + for i in indices: + ret_set.append(super_set[i]) + return Subset(ret_set, super_set) + + def prev_lexicographic(self): + """ + Generates the previous lexicographically ordered subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset([], ['a', 'b', 'c', 'd']) + >>> a.prev_lexicographic().subset + ['d'] + >>> a = Subset(['c','d'], ['a', 'b', 'c', 'd']) + >>> a.prev_lexicographic().subset + ['c'] + + See Also + ======== + + next_lexicographic + """ + i = self.superset_size - 1 + indices = Subset.subset_indices(self.subset, self.superset) + + while i >= 0 and i not in indices: + i = i - 1 + + if i == 0 or i - 1 in indices: + indices.remove(i) + else: + if i >= 0: + indices.remove(i) + indices.append(i - 1) + indices.append(self.superset_size - 1) + + ret_set = [] + super_set = self.superset + for i in indices: + ret_set.append(super_set[i]) + return Subset(ret_set, super_set) + + def iterate_graycode(self, k): + """ + Helper function used for prev_gray and next_gray. + It performs ``k`` step overs to get the respective Gray codes. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset([1, 2, 3], [1, 2, 3, 4]) + >>> a.iterate_graycode(3).subset + [1, 4] + >>> a.iterate_graycode(-2).subset + [1, 2, 4] + + See Also + ======== + + next_gray, prev_gray + """ + unranked_code = GrayCode.unrank(self.superset_size, + (self.rank_gray + k) % self.cardinality) + return Subset.subset_from_bitlist(self.superset, + unranked_code) + + def next_gray(self): + """ + Generates the next Gray code ordered subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset([1, 2, 3], [1, 2, 3, 4]) + >>> a.next_gray().subset + [1, 3] + + See Also + ======== + + iterate_graycode, prev_gray + """ + return self.iterate_graycode(1) + + def prev_gray(self): + """ + Generates the previous Gray code ordered subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset([2, 3, 4], [1, 2, 3, 4, 5]) + >>> a.prev_gray().subset + [2, 3, 4, 5] + + See Also + ======== + + iterate_graycode, next_gray + """ + return self.iterate_graycode(-1) + + @property + def rank_binary(self): + """ + Computes the binary ordered rank. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset([], ['a','b','c','d']) + >>> a.rank_binary + 0 + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.rank_binary + 3 + + See Also + ======== + + iterate_binary, unrank_binary + """ + if self._rank_binary is None: + self._rank_binary = int("".join( + Subset.bitlist_from_subset(self.subset, + self.superset)), 2) + return self._rank_binary + + @property + def rank_lexicographic(self): + """ + Computes the lexicographic ranking of the subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.rank_lexicographic + 14 + >>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6]) + >>> a.rank_lexicographic + 43 + """ + if self._rank_lex is None: + def _ranklex(self, subset_index, i, n): + if subset_index == [] or i > n: + return 0 + if i in subset_index: + subset_index.remove(i) + return 1 + _ranklex(self, subset_index, i + 1, n) + return 2**(n - i - 1) + _ranklex(self, subset_index, i + 1, n) + indices = Subset.subset_indices(self.subset, self.superset) + self._rank_lex = _ranklex(self, indices, 0, self.superset_size) + return self._rank_lex + + @property + def rank_gray(self): + """ + Computes the Gray code ranking of the subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c','d'], ['a','b','c','d']) + >>> a.rank_gray + 2 + >>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6]) + >>> a.rank_gray + 27 + + See Also + ======== + + iterate_graycode, unrank_gray + """ + if self._rank_graycode is None: + bits = Subset.bitlist_from_subset(self.subset, self.superset) + self._rank_graycode = GrayCode(len(bits), start=bits).rank + return self._rank_graycode + + @property + def subset(self): + """ + Gets the subset represented by the current instance. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.subset + ['c', 'd'] + + See Also + ======== + + superset, size, superset_size, cardinality + """ + return self._subset + + @property + def size(self): + """ + Gets the size of the subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.size + 2 + + See Also + ======== + + subset, superset, superset_size, cardinality + """ + return len(self.subset) + + @property + def superset(self): + """ + Gets the superset of the subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.superset + ['a', 'b', 'c', 'd'] + + See Also + ======== + + subset, size, superset_size, cardinality + """ + return self._superset + + @property + def superset_size(self): + """ + Returns the size of the superset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.superset_size + 4 + + See Also + ======== + + subset, superset, size, cardinality + """ + return len(self.superset) + + @property + def cardinality(self): + """ + Returns the number of all possible subsets. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + >>> a.cardinality + 16 + + See Also + ======== + + subset, superset, size, superset_size + """ + return 2**(self.superset_size) + + @classmethod + def subset_from_bitlist(self, super_set, bitlist): + """ + Gets the subset defined by the bitlist. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> Subset.subset_from_bitlist(['a', 'b', 'c', 'd'], '0011').subset + ['c', 'd'] + + See Also + ======== + + bitlist_from_subset + """ + if len(super_set) != len(bitlist): + raise ValueError("The sizes of the lists are not equal") + ret_set = [] + for i in range(len(bitlist)): + if bitlist[i] == '1': + ret_set.append(super_set[i]) + return Subset(ret_set, super_set) + + @classmethod + def bitlist_from_subset(self, subset, superset): + """ + Gets the bitlist corresponding to a subset. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> Subset.bitlist_from_subset(['c', 'd'], ['a', 'b', 'c', 'd']) + '0011' + + See Also + ======== + + subset_from_bitlist + """ + bitlist = ['0'] * len(superset) + if isinstance(subset, Subset): + subset = subset.subset + for i in Subset.subset_indices(subset, superset): + bitlist[i] = '1' + return ''.join(bitlist) + + @classmethod + def unrank_binary(self, rank, superset): + """ + Gets the binary ordered subset of the specified rank. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> Subset.unrank_binary(4, ['a', 'b', 'c', 'd']).subset + ['b'] + + See Also + ======== + + iterate_binary, rank_binary + """ + bits = bin(rank)[2:].rjust(len(superset), '0') + return Subset.subset_from_bitlist(superset, bits) + + @classmethod + def unrank_gray(self, rank, superset): + """ + Gets the Gray code ordered subset of the specified rank. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> Subset.unrank_gray(4, ['a', 'b', 'c']).subset + ['a', 'b'] + >>> Subset.unrank_gray(0, ['a', 'b', 'c']).subset + [] + + See Also + ======== + + iterate_graycode, rank_gray + """ + graycode_bitlist = GrayCode.unrank(len(superset), rank) + return Subset.subset_from_bitlist(superset, graycode_bitlist) + + @classmethod + def subset_indices(self, subset, superset): + """Return indices of subset in superset in a list; the list is empty + if all elements of ``subset`` are not in ``superset``. + + Examples + ======== + + >>> from sympy.combinatorics import Subset + >>> superset = [1, 3, 2, 5, 4] + >>> Subset.subset_indices([3, 2, 1], superset) + [1, 2, 0] + >>> Subset.subset_indices([1, 6], superset) + [] + >>> Subset.subset_indices([], superset) + [] + + """ + a, b = superset, subset + sb = set(b) + d = {} + for i, ai in enumerate(a): + if ai in sb: + d[ai] = i + sb.remove(ai) + if not sb: + break + else: + return [] + return [d[bi] for bi in b] + + +def ksubsets(superset, k): + """ + Finds the subsets of size ``k`` in lexicographic order. + + This uses the itertools generator. + + Examples + ======== + + >>> from sympy.combinatorics.subsets import ksubsets + >>> list(ksubsets([1, 2, 3], 2)) + [(1, 2), (1, 3), (2, 3)] + >>> list(ksubsets([1, 2, 3, 4, 5], 2)) + [(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), \ + (2, 5), (3, 4), (3, 5), (4, 5)] + + See Also + ======== + + Subset + """ + return combinations(superset, k) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tensor_can.py b/phivenv/Lib/site-packages/sympy/combinatorics/tensor_can.py new file mode 100644 index 0000000000000000000000000000000000000000..d43e96ed27a21f988aaaa8fd16827987541f7373 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tensor_can.py @@ -0,0 +1,1189 @@ +from sympy.combinatorics.permutations import Permutation, _af_rmul, \ + _af_invert, _af_new +from sympy.combinatorics.perm_groups import PermutationGroup, _orbit, \ + _orbit_transversal +from sympy.combinatorics.util import _distribute_gens_by_base, \ + _orbits_transversals_from_bsgs + +""" + References for tensor canonicalization: + + [1] R. Portugal "Algorithmic simplification of tensor expressions", + J. Phys. A 32 (1999) 7779-7789 + + [2] R. Portugal, B.F. Svaiter "Group-theoretic Approach for Symbolic + Tensor Manipulation: I. Free Indices" + arXiv:math-ph/0107031v1 + + [3] L.R.U. Manssur, R. Portugal "Group-theoretic Approach for Symbolic + Tensor Manipulation: II. Dummy Indices" + arXiv:math-ph/0107032v1 + + [4] xperm.c part of XPerm written by J. M. Martin-Garcia + http://www.xact.es/index.html +""" + + +def dummy_sgs(dummies, sym, n): + """ + Return the strong generators for dummy indices. + + Parameters + ========== + + dummies : List of dummy indices. + `dummies[2k], dummies[2k+1]` are paired indices. + In base form, the dummy indices are always in + consecutive positions. + sym : symmetry under interchange of contracted dummies:: + * None no symmetry + * 0 commuting + * 1 anticommuting + + n : number of indices + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import dummy_sgs + >>> dummy_sgs(list(range(2, 8)), 0, 8) + [[0, 1, 3, 2, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 5, 4, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 7, 6, 8, 9], [0, 1, 4, 5, 2, 3, 6, 7, 8, 9], + [0, 1, 2, 3, 6, 7, 4, 5, 8, 9]] + """ + if len(dummies) > n: + raise ValueError("List too large") + res = [] + # exchange of contravariant and covariant indices + if sym is not None: + for j in dummies[::2]: + a = list(range(n + 2)) + if sym == 1: + a[n] = n + 1 + a[n + 1] = n + a[j], a[j + 1] = a[j + 1], a[j] + res.append(a) + # rename dummy indices + for j in dummies[:-3:2]: + a = list(range(n + 2)) + a[j:j + 4] = a[j + 2], a[j + 3], a[j], a[j + 1] + res.append(a) + return res + + +def _min_dummies(dummies, sym, indices): + """ + Return list of minima of the orbits of indices in group of dummies. + See ``double_coset_can_rep`` for the description of ``dummies`` and ``sym``. + ``indices`` is the initial list of dummy indices. + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import _min_dummies + >>> _min_dummies([list(range(2, 8))], [0], list(range(10))) + [0, 1, 2, 2, 2, 2, 2, 2, 8, 9] + """ + num_types = len(sym) + m = [min(dx) if dx else None for dx in dummies] + res = indices[:] + for i in range(num_types): + for c, i in enumerate(indices): + for j in range(num_types): + if i in dummies[j]: + res[c] = m[j] + break + return res + + +def _trace_S(s, j, b, S_cosets): + """ + Return the representative h satisfying s[h[b]] == j + + If there is not such a representative return None + """ + for h in S_cosets[b]: + if s[h[b]] == j: + return h + return None + + +def _trace_D(gj, p_i, Dxtrav): + """ + Return the representative h satisfying h[gj] == p_i + + If there is not such a representative return None + """ + for h in Dxtrav: + if h[gj] == p_i: + return h + return None + + +def _dumx_remove(dumx, dumx_flat, p0): + """ + remove p0 from dumx + """ + res = [] + for dx in dumx: + if p0 not in dx: + res.append(dx) + continue + k = dx.index(p0) + if k % 2 == 0: + p0_paired = dx[k + 1] + else: + p0_paired = dx[k - 1] + dx.remove(p0) + dx.remove(p0_paired) + dumx_flat.remove(p0) + dumx_flat.remove(p0_paired) + res.append(dx) + + +def transversal2coset(size, base, transversal): + a = [] + j = 0 + for i in range(size): + if i in base: + a.append(sorted(transversal[j].values())) + j += 1 + else: + a.append([list(range(size))]) + j = len(a) - 1 + while a[j] == [list(range(size))]: + j -= 1 + return a[:j + 1] + + +def double_coset_can_rep(dummies, sym, b_S, sgens, S_transversals, g): + r""" + Butler-Portugal algorithm for tensor canonicalization with dummy indices. + + Parameters + ========== + + dummies + list of lists of dummy indices, + one list for each type of index; + the dummy indices are put in order contravariant, covariant + [d0, -d0, d1, -d1, ...]. + + sym + list of the symmetries of the index metric for each type. + + possible symmetries of the metrics + * 0 symmetric + * 1 antisymmetric + * None no symmetry + + b_S + base of a minimal slot symmetry BSGS. + + sgens + generators of the slot symmetry BSGS. + + S_transversals + transversals for the slot BSGS. + + g + permutation representing the tensor. + + Returns + ======= + + Return 0 if the tensor is zero, else return the array form of + the permutation representing the canonical form of the tensor. + + Notes + ===== + + A tensor with dummy indices can be represented in a number + of equivalent ways which typically grows exponentially with + the number of indices. To be able to establish if two tensors + with many indices are equal becomes computationally very slow + in absence of an efficient algorithm. + + The Butler-Portugal algorithm [3] is an efficient algorithm to + put tensors in canonical form, solving the above problem. + + Portugal observed that a tensor can be represented by a permutation, + and that the class of tensors equivalent to it under slot and dummy + symmetries is equivalent to the double coset `D*g*S` + (Note: in this documentation we use the conventions for multiplication + of permutations p, q with (p*q)(i) = p[q[i]] which is opposite + to the one used in the Permutation class) + + Using the algorithm by Butler to find a representative of the + double coset one can find a canonical form for the tensor. + + To see this correspondence, + let `g` be a permutation in array form; a tensor with indices `ind` + (the indices including both the contravariant and the covariant ones) + can be written as + + `t = T(ind[g[0]], \dots, ind[g[n-1]])`, + + where `n = len(ind)`; + `g` has size `n + 2`, the last two indices for the sign of the tensor + (trick introduced in [4]). + + A slot symmetry transformation `s` is a permutation acting on the slots + `t \rightarrow T(ind[(g*s)[0]], \dots, ind[(g*s)[n-1]])` + + A dummy symmetry transformation acts on `ind` + `t \rightarrow T(ind[(d*g)[0]], \dots, ind[(d*g)[n-1]])` + + Being interested only in the transformations of the tensor under + these symmetries, one can represent the tensor by `g`, which transforms + as + + `g -> d*g*s`, so it belongs to the coset `D*g*S`, or in other words + to the set of all permutations allowed by the slot and dummy symmetries. + + Let us explain the conventions by an example. + + Given a tensor `T^{d3 d2 d1}{}_{d1 d2 d3}` with the slot symmetries + `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}` + + `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}` + + and symmetric metric, find the tensor equivalent to it which + is the lowest under the ordering of indices: + lexicographic ordering `d1, d2, d3` and then contravariant + before covariant index; that is the canonical form of the tensor. + + The canonical form is `-T^{d1 d2 d3}{}_{d1 d2 d3}` + obtained using `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}`. + + To convert this problem in the input for this function, + use the following ordering of the index names + (- for covariant for short) `d1, -d1, d2, -d2, d3, -d3` + + `T^{d3 d2 d1}{}_{d1 d2 d3}` corresponds to `g = [4, 2, 0, 1, 3, 5, 6, 7]` + where the last two indices are for the sign + + `sgens = [Permutation(0, 2)(6, 7), Permutation(0, 4)(6, 7)]` + + sgens[0] is the slot symmetry `-(0, 2)` + `T^{a0 a1 a2 a3 a4 a5} = -T^{a2 a1 a0 a3 a4 a5}` + + sgens[1] is the slot symmetry `-(0, 4)` + `T^{a0 a1 a2 a3 a4 a5} = -T^{a4 a1 a2 a3 a0 a5}` + + The dummy symmetry group D is generated by the strong base generators + `[(0, 1), (2, 3), (4, 5), (0, 2)(1, 3), (0, 4)(1, 5)]` + where the first three interchange covariant and contravariant + positions of the same index (d1 <-> -d1) and the last two interchange + the dummy indices themselves (d1 <-> d2). + + The dummy symmetry acts from the left + `d = [1, 0, 2, 3, 4, 5, 6, 7]` exchange `d1 \leftrightarrow -d1` + `T^{d3 d2 d1}{}_{d1 d2 d3} == T^{d3 d2}{}_{d1}{}^{d1}{}_{d2 d3}` + + `g=[4, 2, 0, 1, 3, 5, 6, 7] -> [4, 2, 1, 0, 3, 5, 6, 7] = _af_rmul(d, g)` + which differs from `_af_rmul(g, d)`. + + The slot symmetry acts from the right + `s = [2, 1, 0, 3, 4, 5, 7, 6]` exchanges slots 0 and 2 and changes sign + `T^{d3 d2 d1}{}_{d1 d2 d3} == -T^{d1 d2 d3}{}_{d1 d2 d3}` + + `g=[4,2,0,1,3,5,6,7] -> [0, 2, 4, 1, 3, 5, 7, 6] = _af_rmul(g, s)` + + Example in which the tensor is zero, same slot symmetries as above: + `T^{d2}{}_{d1 d3}{}^{d1 d3}{}_{d2}` + + `= -T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,4)`; + + `= T_{d3 d1}{}^{d3}{}^{d1 d2}{}_{d2}` under slot symmetry `-(0,2)`; + + `= T^{d3}{}_{d1 d3}{}^{d1 d2}{}_{d2}` symmetric metric; + + `= 0` since two of these lines have tensors differ only for the sign. + + The double coset D*g*S consists of permutations `h = d*g*s` corresponding + to equivalent tensors; if there are two `h` which are the same apart + from the sign, return zero; otherwise + choose as representative the tensor with indices + ordered lexicographically according to `[d1, -d1, d2, -d2, d3, -d3]` + that is ``rep = min(D*g*S) = min([d*g*s for d in D for s in S])`` + + The indices are fixed one by one; first choose the lowest index + for slot 0, then the lowest remaining index for slot 1, etc. + Doing this one obtains a chain of stabilizers + + `S \rightarrow S_{b0} \rightarrow S_{b0,b1} \rightarrow \dots` and + `D \rightarrow D_{p0} \rightarrow D_{p0,p1} \rightarrow \dots` + + where ``[b0, b1, ...] = range(b)`` is a base of the symmetric group; + the strong base `b_S` of S is an ordered sublist of it; + therefore it is sufficient to compute once the + strong base generators of S using the Schreier-Sims algorithm; + the stabilizers of the strong base generators are the + strong base generators of the stabilizer subgroup. + + ``dbase = [p0, p1, ...]`` is not in general in lexicographic order, + so that one must recompute the strong base generators each time; + however this is trivial, there is no need to use the Schreier-Sims + algorithm for D. + + The algorithm keeps a TAB of elements `(s_i, d_i, h_i)` + where `h_i = d_i \times g \times s_i` satisfying `h_i[j] = p_j` for `0 \le j < i` + starting from `s_0 = id, d_0 = id, h_0 = g`. + + The equations `h_0[0] = p_0, h_1[1] = p_1, \dots` are solved in this order, + choosing each time the lowest possible value of p_i + + For `j < i` + `d_i*g*s_i*S_{b_0, \dots, b_{i-1}}*b_j = D_{p_0, \dots, p_{i-1}}*p_j` + so that for dx in `D_{p_0,\dots,p_{i-1}}` and sx in + `S_{base[0], \dots, base[i-1]}` one has `dx*d_i*g*s_i*sx*b_j = p_j` + + Search for dx, sx such that this equation holds for `j = i`; + it can be written as `s_i*sx*b_j = J, dx*d_i*g*J = p_j` + `sx*b_j = s_i**-1*J; sx = trace(s_i**-1, S_{b_0,...,b_{i-1}})` + `dx**-1*p_j = d_i*g*J; dx = trace(d_i*g*J, D_{p_0,...,p_{i-1}})` + + `s_{i+1} = s_i*trace(s_i**-1*J, S_{b_0,...,b_{i-1}})` + `d_{i+1} = trace(d_i*g*J, D_{p_0,...,p_{i-1}})**-1*d_i` + `h_{i+1}*b_i = d_{i+1}*g*s_{i+1}*b_i = p_i` + + `h_n*b_j = p_j` for all j, so that `h_n` is the solution. + + Add the found `(s, d, h)` to TAB1. + + At the end of the iteration sort TAB1 with respect to the `h`; + if there are two consecutive `h` in TAB1 which differ only for the + sign, the tensor is zero, so return 0; + if there are two consecutive `h` which are equal, keep only one. + + Then stabilize the slot generators under `i` and the dummy generators + under `p_i`. + + Assign `TAB = TAB1` at the end of the iteration step. + + At the end `TAB` contains a unique `(s, d, h)`, since all the slots + of the tensor `h` have been fixed to have the minimum value according + to the symmetries. The algorithm returns `h`. + + It is important that the slot BSGS has lexicographic minimal base, + otherwise there is an `i` which does not belong to the slot base + for which `p_i` is fixed by the dummy symmetry only, while `i` + is not invariant from the slot stabilizer, so `p_i` is not in + general the minimal value. + + This algorithm differs slightly from the original algorithm [3]: + the canonical form is minimal lexicographically, and + the BSGS has minimal base under lexicographic order. + Equal tensors `h` are eliminated from TAB. + + + Examples + ======== + + >>> from sympy.combinatorics.permutations import Permutation + >>> from sympy.combinatorics.tensor_can import double_coset_can_rep, get_transversals + >>> gens = [Permutation(x) for x in [[2, 1, 0, 3, 4, 5, 7, 6], [4, 1, 2, 3, 0, 5, 7, 6]]] + >>> base = [0, 2] + >>> g = Permutation([4, 2, 0, 1, 3, 5, 6, 7]) + >>> transversals = get_transversals(base, gens) + >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g) + [0, 1, 2, 3, 4, 5, 7, 6] + + >>> g = Permutation([4, 1, 3, 0, 5, 2, 6, 7]) + >>> double_coset_can_rep([list(range(6))], [0], base, gens, transversals, g) + 0 + """ + size = g.size + g = g.array_form + num_dummies = size - 2 + indices = list(range(num_dummies)) + all_metrics_with_sym = not any(_ is None for _ in sym) + num_types = len(sym) + dumx = dummies[:] + dumx_flat = [] + for dx in dumx: + dumx_flat.extend(dx) + b_S = b_S[:] + sgensx = [h._array_form for h in sgens] + if b_S: + S_transversals = transversal2coset(size, b_S, S_transversals) + # strong generating set for D + dsgsx = [] + for i in range(num_types): + dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies)) + idn = list(range(size)) + # TAB = list of entries (s, d, h) where h = _af_rmuln(d,g,s) + # for short, in the following d*g*s means _af_rmuln(d,g,s) + TAB = [(idn, idn, g)] + for i in range(size - 2): + b = i + testb = b in b_S and sgensx + if testb: + sgensx1 = [_af_new(_) for _ in sgensx] + deltab = _orbit(size, sgensx1, b) + else: + deltab = {b} + # p1 = min(IMAGES) = min(Union D_p*h*deltab for h in TAB) + if all_metrics_with_sym: + md = _min_dummies(dumx, sym, indices) + else: + md = [min(_orbit(size, [_af_new( + ddx) for ddx in dsgsx], ii)) for ii in range(size - 2)] + + p_i = min(min(md[h[x]] for x in deltab) for s, d, h in TAB) + dsgsx1 = [_af_new(_) for _ in dsgsx] + Dxtrav = _orbit_transversal(size, dsgsx1, p_i, False, af=True) \ + if dsgsx else None + if Dxtrav: + Dxtrav = [_af_invert(x) for x in Dxtrav] + # compute the orbit of p_i + for ii in range(num_types): + if p_i in dumx[ii]: + # the orbit is made by all the indices in dum[ii] + if sym[ii] is not None: + deltap = dumx[ii] + else: + # the orbit is made by all the even indices if p_i + # is even, by all the odd indices if p_i is odd + p_i_index = dumx[ii].index(p_i) % 2 + deltap = dumx[ii][p_i_index::2] + break + else: + deltap = [p_i] + TAB1 = [] + while TAB: + s, d, h = TAB.pop() + if min(md[h[x]] for x in deltab) != p_i: + continue + deltab1 = [x for x in deltab if md[h[x]] == p_i] + # NEXT = s*deltab1 intersection (d*g)**-1*deltap + dg = _af_rmul(d, g) + dginv = _af_invert(dg) + sdeltab = [s[x] for x in deltab1] + gdeltap = [dginv[x] for x in deltap] + NEXT = [x for x in sdeltab if x in gdeltap] + # d, s satisfy + # d*g*s*base[i-1] = p_{i-1}; using the stabilizers + # d*g*s*S_{base[0],...,base[i-1]}*base[i-1] = + # D_{p_0,...,p_{i-1}}*p_{i-1} + # so that to find d1, s1 satisfying d1*g*s1*b = p_i + # one can look for dx in D_{p_0,...,p_{i-1}} and + # sx in S_{base[0],...,base[i-1]} + # d1 = dx*d; s1 = s*sx + # d1*g*s1*b = dx*d*g*s*sx*b = p_i + for j in NEXT: + if testb: + # solve s1*b = j with s1 = s*sx for some element sx + # of the stabilizer of ..., base[i-1] + # sx*b = s**-1*j; sx = _trace_S(s, j,...) + # s1 = s*trace_S(s**-1*j,...) + s1 = _trace_S(s, j, b, S_transversals) + if not s1: + continue + else: + s1 = [s[ix] for ix in s1] + else: + s1 = s + # assert s1[b] == j # invariant + # solve d1*g*j = p_i with d1 = dx*d for some element dg + # of the stabilizer of ..., p_{i-1} + # dx**-1*p_i = d*g*j; dx**-1 = trace_D(d*g*j,...) + # d1 = trace_D(d*g*j,...)**-1*d + # to save an inversion in the inner loop; notice we did + # Dxtrav = [perm_af_invert(x) for x in Dxtrav] out of the loop + if Dxtrav: + d1 = _trace_D(dg[j], p_i, Dxtrav) + if not d1: + continue + else: + if p_i != dg[j]: + continue + d1 = idn + assert d1[dg[j]] == p_i # invariant + d1 = [d1[ix] for ix in d] + h1 = [d1[g[ix]] for ix in s1] + # assert h1[b] == p_i # invariant + TAB1.append((s1, d1, h1)) + + # if TAB contains equal permutations, keep only one of them; + # if TAB contains equal permutations up to the sign, return 0 + TAB1.sort(key=lambda x: x[-1]) + prev = [0] * size + while TAB1: + s, d, h = TAB1.pop() + if h[:-2] == prev[:-2]: + if h[-1] != prev[-1]: + return 0 + else: + TAB.append((s, d, h)) + prev = h + + # stabilize the SGS + sgensx = [h for h in sgensx if h[b] == b] + if b in b_S: + b_S.remove(b) + _dumx_remove(dumx, dumx_flat, p_i) + dsgsx = [] + for i in range(num_types): + dsgsx.extend(dummy_sgs(dumx[i], sym[i], num_dummies)) + return TAB[0][-1] + + +def canonical_free(base, gens, g, num_free): + """ + Canonicalization of a tensor with respect to free indices + choosing the minimum with respect to lexicographical ordering + in the free indices. + + Explanation + =========== + + ``base``, ``gens`` BSGS for slot permutation group + ``g`` permutation representing the tensor + ``num_free`` number of free indices + The indices must be ordered with first the free indices + + See explanation in double_coset_can_rep + The algorithm is a variation of the one given in [2]. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy.combinatorics.tensor_can import canonical_free + >>> gens = [[1, 0, 2, 3, 5, 4], [2, 3, 0, 1, 4, 5],[0, 1, 3, 2, 5, 4]] + >>> gens = [Permutation(h) for h in gens] + >>> base = [0, 2] + >>> g = Permutation([2, 1, 0, 3, 4, 5]) + >>> canonical_free(base, gens, g, 4) + [0, 3, 1, 2, 5, 4] + + Consider the product of Riemann tensors + ``T = R^{a}_{d0}^{d1,d2}*R_{d2,d1}^{d0,b}`` + The order of the indices is ``[a, b, d0, -d0, d1, -d1, d2, -d2]`` + The permutation corresponding to the tensor is + ``g = [0, 3, 4, 6, 7, 5, 2, 1, 8, 9]`` + + In particular ``a`` is position ``0``, ``b`` is in position ``9``. + Use the slot symmetries to get `T` is a form which is the minimal + in lexicographic order in the free indices ``a`` and ``b``, e.g. + ``-R^{a}_{d0}^{d1,d2}*R^{b,d0}_{d2,d1}`` corresponding to + ``[0, 3, 4, 6, 1, 2, 7, 5, 9, 8]`` + + >>> from sympy.combinatorics.tensor_can import riemann_bsgs, tensor_gens + >>> base, gens = riemann_bsgs + >>> size, sbase, sgens = tensor_gens(base, gens, [[], []], 0) + >>> g = Permutation([0, 3, 4, 6, 7, 5, 2, 1, 8, 9]) + >>> canonical_free(sbase, [Permutation(h) for h in sgens], g, 2) + [0, 3, 4, 6, 1, 2, 7, 5, 9, 8] + """ + g = g.array_form + size = len(g) + if not base: + return g[:] + + transversals = get_transversals(base, gens) + for x in sorted(g[:-2]): + if x not in base: + base.append(x) + h = g + for transv in transversals: + h_i = [size]*num_free + # find the element s in transversals[i] such that + # _af_rmul(h, s) has its free elements with the lowest position in h + s = None + for sk in transv.values(): + h1 = _af_rmul(h, sk) + hi = [h1.index(ix) for ix in range(num_free)] + if hi < h_i: + h_i = hi + s = sk + if s: + h = _af_rmul(h, s) + return h + + +def _get_map_slots(size, fixed_slots): + res = list(range(size)) + pos = 0 + for i in range(size): + if i in fixed_slots: + continue + res[i] = pos + pos += 1 + return res + + +def _lift_sgens(size, fixed_slots, free, s): + a = [] + j = k = 0 + fd = [y for _, y in sorted(zip(fixed_slots, free))] + num_free = len(free) + for i in range(size): + if i in fixed_slots: + a.append(fd[k]) + k += 1 + else: + a.append(s[j] + num_free) + j += 1 + return a + + +def canonicalize(g, dummies, msym, *v): + """ + canonicalize tensor formed by tensors + + Parameters + ========== + + g : permutation representing the tensor + + dummies : list representing the dummy indices + it can be a list of dummy indices of the same type + or a list of lists of dummy indices, one list for each + type of index; + the dummy indices must come after the free indices, + and put in order contravariant, covariant + [d0, -d0, d1,-d1,...] + + msym : symmetry of the metric(s) + it can be an integer or a list; + in the first case it is the symmetry of the dummy index metric; + in the second case it is the list of the symmetries of the + index metric for each type + + v : list, (base_i, gens_i, n_i, sym_i) for tensors of type `i` + + base_i, gens_i : BSGS for tensors of this type. + The BSGS should have minimal base under lexicographic ordering; + if not, an attempt is made do get the minimal BSGS; + in case of failure, + canonicalize_naive is used, which is much slower. + + n_i : number of tensors of type `i`. + + sym_i : symmetry under exchange of component tensors of type `i`. + + Both for msym and sym_i the cases are + * None no symmetry + * 0 commuting + * 1 anticommuting + + Returns + ======= + + 0 if the tensor is zero, else return the array form of + the permutation representing the canonical form of the tensor. + + Algorithm + ========= + + First one uses canonical_free to get the minimum tensor under + lexicographic order, using only the slot symmetries. + If the component tensors have not minimal BSGS, it is attempted + to find it; if the attempt fails canonicalize_naive + is used instead. + + Compute the residual slot symmetry keeping fixed the free indices + using tensor_gens(base, gens, list_free_indices, sym). + + Reduce the problem eliminating the free indices. + + Then use double_coset_can_rep and lift back the result reintroducing + the free indices. + + Examples + ======== + + one type of index with commuting metric; + + `A_{a b}` and `B_{a b}` antisymmetric and commuting + + `T = A_{d0 d1} * B^{d0}{}_{d2} * B^{d2 d1}` + + `ord = [d0,-d0,d1,-d1,d2,-d2]` order of the indices + + g = [1, 3, 0, 5, 4, 2, 6, 7] + + `T_c = 0` + + >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize, bsgs_direct_product + >>> from sympy.combinatorics import Permutation + >>> base2a, gens2a = get_symmetric_group_sgs(2, 1) + >>> t0 = (base2a, gens2a, 1, 0) + >>> t1 = (base2a, gens2a, 2, 0) + >>> g = Permutation([1, 3, 0, 5, 4, 2, 6, 7]) + >>> canonicalize(g, range(6), 0, t0, t1) + 0 + + same as above, but with `B_{a b}` anticommuting + + `T_c = -A^{d0 d1} * B_{d0}{}^{d2} * B_{d1 d2}` + + can = [0,2,1,4,3,5,7,6] + + >>> t1 = (base2a, gens2a, 2, 1) + >>> canonicalize(g, range(6), 0, t0, t1) + [0, 2, 1, 4, 3, 5, 7, 6] + + two types of indices `[a,b,c,d,e,f]` and `[m,n]`, in this order, + both with commuting metric + + `f^{a b c}` antisymmetric, commuting + + `A_{m a}` no symmetry, commuting + + `T = f^c{}_{d a} * f^f{}_{e b} * A_m{}^d * A^{m b} * A_n{}^a * A^{n e}` + + ord = [c,f,a,-a,b,-b,d,-d,e,-e,m,-m,n,-n] + + g = [0,7,3, 1,9,5, 11,6, 10,4, 13,2, 12,8, 14,15] + + The canonical tensor is + `T_c = -f^{c a b} * f^{f d e} * A^m{}_a * A_{m d} * A^n{}_b * A_{n e}` + + can = [0,2,4, 1,6,8, 10,3, 11,7, 12,5, 13,9, 15,14] + + >>> base_f, gens_f = get_symmetric_group_sgs(3, 1) + >>> base1, gens1 = get_symmetric_group_sgs(1) + >>> base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1) + >>> t0 = (base_f, gens_f, 2, 0) + >>> t1 = (base_A, gens_A, 4, 0) + >>> dummies = [range(2, 10), range(10, 14)] + >>> g = Permutation([0, 7, 3, 1, 9, 5, 11, 6, 10, 4, 13, 2, 12, 8, 14, 15]) + >>> canonicalize(g, dummies, [0, 0], t0, t1) + [0, 2, 4, 1, 6, 8, 10, 3, 11, 7, 12, 5, 13, 9, 15, 14] + """ + from sympy.combinatorics.testutil import canonicalize_naive + if not isinstance(msym, list): + if msym not in (0, 1, None): + raise ValueError('msym must be 0, 1 or None') + num_types = 1 + else: + num_types = len(msym) + if not all(msymx in (0, 1, None) for msymx in msym): + raise ValueError('msym entries must be 0, 1 or None') + if len(dummies) != num_types: + raise ValueError( + 'dummies and msym must have the same number of elements') + size = g.size + num_tensors = 0 + v1 = [] + for base_i, gens_i, n_i, sym_i in v: + # check that the BSGS is minimal; + # this property is used in double_coset_can_rep; + # if it is not minimal use canonicalize_naive + if not _is_minimal_bsgs(base_i, gens_i): + mbsgs = get_minimal_bsgs(base_i, gens_i) + if not mbsgs: + can = canonicalize_naive(g, dummies, msym, *v) + return can + base_i, gens_i = mbsgs + v1.append((base_i, gens_i, [[]] * n_i, sym_i)) + num_tensors += n_i + + if num_types == 1 and not isinstance(msym, list): + dummies = [dummies] + msym = [msym] + flat_dummies = [] + for dumx in dummies: + flat_dummies.extend(dumx) + + if flat_dummies and flat_dummies != list(range(flat_dummies[0], flat_dummies[-1] + 1)): + raise ValueError('dummies is not valid') + + # slot symmetry of the tensor + size1, sbase, sgens = gens_products(*v1) + if size != size1: + raise ValueError( + 'g has size %d, generators have size %d' % (size, size1)) + free = [i for i in range(size - 2) if i not in flat_dummies] + num_free = len(free) + + # g1 minimal tensor under slot symmetry + g1 = canonical_free(sbase, sgens, g, num_free) + if not flat_dummies: + return g1 + # save the sign of g1 + sign = 0 if g1[-1] == size - 1 else 1 + + # the free indices are kept fixed. + # Determine free_i, the list of slots of tensors which are fixed + # since they are occupied by free indices, which are fixed. + start = 0 + for i, (base_i, gens_i, n_i, sym_i) in enumerate(v): + free_i = [] + len_tens = gens_i[0].size - 2 + # for each component tensor get a list od fixed islots + for j in range(n_i): + # get the elements corresponding to the component tensor + h = g1[start:(start + len_tens)] + fr = [] + # get the positions of the fixed elements in h + for k in free: + if k in h: + fr.append(h.index(k)) + free_i.append(fr) + start += len_tens + v1[i] = (base_i, gens_i, free_i, sym_i) + # BSGS of the tensor with fixed free indices + # if tensor_gens fails in gens_product, use canonicalize_naive + size, sbase, sgens = gens_products(*v1) + + # reduce the permutations getting rid of the free indices + pos_free = [g1.index(x) for x in range(num_free)] + size_red = size - num_free + g1_red = [x - num_free for x in g1 if x in flat_dummies] + if sign: + g1_red.extend([size_red - 1, size_red - 2]) + else: + g1_red.extend([size_red - 2, size_red - 1]) + map_slots = _get_map_slots(size, pos_free) + sbase_red = [map_slots[i] for i in sbase if i not in pos_free] + sgens_red = [_af_new([map_slots[i] for i in y._array_form if i not in pos_free]) for y in sgens] + dummies_red = [[x - num_free for x in y] for y in dummies] + transv_red = get_transversals(sbase_red, sgens_red) + g1_red = _af_new(g1_red) + g2 = double_coset_can_rep( + dummies_red, msym, sbase_red, sgens_red, transv_red, g1_red) + if g2 == 0: + return 0 + # lift to the case with the free indices + g3 = _lift_sgens(size, pos_free, free, g2) + return g3 + + +def perm_af_direct_product(gens1, gens2, signed=True): + """ + Direct products of the generators gens1 and gens2. + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import perm_af_direct_product + >>> gens1 = [[1, 0, 2, 3], [0, 1, 3, 2]] + >>> gens2 = [[1, 0]] + >>> perm_af_direct_product(gens1, gens2, False) + [[1, 0, 2, 3, 4, 5], [0, 1, 3, 2, 4, 5], [0, 1, 2, 3, 5, 4]] + >>> gens1 = [[1, 0, 2, 3, 5, 4], [0, 1, 3, 2, 4, 5]] + >>> gens2 = [[1, 0, 2, 3]] + >>> perm_af_direct_product(gens1, gens2, True) + [[1, 0, 2, 3, 4, 5, 7, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 2, 3, 5, 4, 6, 7]] + """ + gens1 = [list(x) for x in gens1] + gens2 = [list(x) for x in gens2] + s = 2 if signed else 0 + n1 = len(gens1[0]) - s + n2 = len(gens2[0]) - s + start = list(range(n1)) + end = list(range(n1, n1 + n2)) + if signed: + gens1 = [gen[:-2] + end + [gen[-2] + n2, gen[-1] + n2] + for gen in gens1] + gens2 = [start + [x + n1 for x in gen] for gen in gens2] + else: + gens1 = [gen + end for gen in gens1] + gens2 = [start + [x + n1 for x in gen] for gen in gens2] + + res = gens1 + gens2 + + return res + + +def bsgs_direct_product(base1, gens1, base2, gens2, signed=True): + """ + Direct product of two BSGS. + + Parameters + ========== + + base1 : base of the first BSGS. + + gens1 : strong generating sequence of the first BSGS. + + base2, gens2 : similarly for the second BSGS. + + signed : flag for signed permutations. + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import (get_symmetric_group_sgs, bsgs_direct_product) + >>> base1, gens1 = get_symmetric_group_sgs(1) + >>> base2, gens2 = get_symmetric_group_sgs(2) + >>> bsgs_direct_product(base1, gens1, base2, gens2) + ([1], [(4)(1 2)]) + """ + s = 2 if signed else 0 + n1 = gens1[0].size - s + base = list(base1) + base += [x + n1 for x in base2] + gens1 = [h._array_form for h in gens1] + gens2 = [h._array_form for h in gens2] + gens = perm_af_direct_product(gens1, gens2, signed) + size = len(gens[0]) + id_af = list(range(size)) + gens = [h for h in gens if h != id_af] + if not gens: + gens = [id_af] + return base, [_af_new(h) for h in gens] + + +def get_symmetric_group_sgs(n, antisym=False): + """ + Return base, gens of the minimal BSGS for (anti)symmetric tensor + + Parameters + ========== + + n : rank of the tensor + antisym : bool + ``antisym = False`` symmetric tensor + ``antisym = True`` antisymmetric tensor + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs + >>> get_symmetric_group_sgs(3) + ([0, 1], [(4)(0 1), (4)(1 2)]) + """ + if n == 1: + return [], [_af_new(list(range(3)))] + gens = [Permutation(n - 1)(i, i + 1)._array_form for i in range(n - 1)] + if antisym == 0: + gens = [x + [n, n + 1] for x in gens] + else: + gens = [x + [n + 1, n] for x in gens] + base = list(range(n - 1)) + return base, [_af_new(h) for h in gens] + +riemann_bsgs = [0, 2], [Permutation(0, 1)(4, 5), Permutation(2, 3)(4, 5), + Permutation(5)(0, 2)(1, 3)] + + +def get_transversals(base, gens): + """ + Return transversals for the group with BSGS base, gens + """ + if not base: + return [] + stabs = _distribute_gens_by_base(base, gens) + orbits, transversals = _orbits_transversals_from_bsgs(base, stabs) + transversals = [{x: h._array_form for x, h in y.items()} for y in + transversals] + return transversals + + +def _is_minimal_bsgs(base, gens): + """ + Check if the BSGS has minimal base under lexigographic order. + + base, gens BSGS + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy.combinatorics.tensor_can import riemann_bsgs, _is_minimal_bsgs + >>> _is_minimal_bsgs(*riemann_bsgs) + True + >>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)])) + >>> _is_minimal_bsgs(*riemann_bsgs1) + False + """ + base1 = [] + sgs1 = gens[:] + size = gens[0].size + for i in range(size): + if not all(h._array_form[i] == i for h in sgs1): + base1.append(i) + sgs1 = [h for h in sgs1 if h._array_form[i] == i] + return base1 == base + + +def get_minimal_bsgs(base, gens): + """ + Compute a minimal GSGS + + base, gens BSGS + + If base, gens is a minimal BSGS return it; else return a minimal BSGS + if it fails in finding one, it returns None + + TODO: use baseswap in the case in which if it fails in finding a + minimal BSGS + + Examples + ======== + + >>> from sympy.combinatorics import Permutation + >>> from sympy.combinatorics.tensor_can import get_minimal_bsgs + >>> riemann_bsgs1 = ([2, 0], ([Permutation(5)(0, 1)(4, 5), Permutation(5)(0, 2)(1, 3)])) + >>> get_minimal_bsgs(*riemann_bsgs1) + ([0, 2], [(0 1)(4 5), (5)(0 2)(1 3), (2 3)(4 5)]) + """ + G = PermutationGroup(gens) + base, gens = G.schreier_sims_incremental() + if not _is_minimal_bsgs(base, gens): + return None + return base, gens + + +def tensor_gens(base, gens, list_free_indices, sym=0): + """ + Returns size, res_base, res_gens BSGS for n tensors of the + same type. + + Explanation + =========== + + base, gens BSGS for tensors of this type + list_free_indices list of the slots occupied by fixed indices + for each of the tensors + + sym symmetry under commutation of two tensors + sym None no symmetry + sym 0 commuting + sym 1 anticommuting + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import tensor_gens, get_symmetric_group_sgs + + two symmetric tensors with 3 indices without free indices + + >>> base, gens = get_symmetric_group_sgs(3) + >>> tensor_gens(base, gens, [[], []]) + (8, [0, 1, 3, 4], [(7)(0 1), (7)(1 2), (7)(3 4), (7)(4 5), (7)(0 3)(1 4)(2 5)]) + + two symmetric tensors with 3 indices with free indices in slot 1 and 0 + + >>> tensor_gens(base, gens, [[1], [0]]) + (8, [0, 4], [(7)(0 2), (7)(4 5)]) + + four symmetric tensors with 3 indices, two of which with free indices + + """ + def _get_bsgs(G, base, gens, free_indices): + """ + return the BSGS for G.pointwise_stabilizer(free_indices) + """ + if not free_indices: + return base[:], gens[:] + else: + H = G.pointwise_stabilizer(free_indices) + base, sgs = H.schreier_sims_incremental() + return base, sgs + + # if not base there is no slot symmetry for the component tensors + # if list_free_indices.count([]) < 2 there is no commutation symmetry + # so there is no resulting slot symmetry + if not base and list_free_indices.count([]) < 2: + n = len(list_free_indices) + size = gens[0].size + size = n * (size - 2) + 2 + return size, [], [_af_new(list(range(size)))] + + # if any(list_free_indices) one needs to compute the pointwise + # stabilizer, so G is needed + if any(list_free_indices): + G = PermutationGroup(gens) + else: + G = None + + # no_free list of lists of indices for component tensors without fixed + # indices + no_free = [] + size = gens[0].size + id_af = list(range(size)) + num_indices = size - 2 + if not list_free_indices[0]: + no_free.append(list(range(num_indices))) + res_base, res_gens = _get_bsgs(G, base, gens, list_free_indices[0]) + for i in range(1, len(list_free_indices)): + base1, gens1 = _get_bsgs(G, base, gens, list_free_indices[i]) + res_base, res_gens = bsgs_direct_product(res_base, res_gens, + base1, gens1, 1) + if not list_free_indices[i]: + no_free.append(list(range(size - 2, size - 2 + num_indices))) + size += num_indices + nr = size - 2 + res_gens = [h for h in res_gens if h._array_form != id_af] + # if sym there are no commuting tensors stop here + if sym is None or not no_free: + if not res_gens: + res_gens = [_af_new(id_af)] + return size, res_base, res_gens + + # if the component tensors have moinimal BSGS, so is their direct + # product P; the slot symmetry group is S = P*C, where C is the group + # to (anti)commute the component tensors with no free indices + # a stabilizer has the property S_i = P_i*C_i; + # the BSGS of P*C has SGS_P + SGS_C and the base is + # the ordered union of the bases of P and C. + # If P has minimal BSGS, so has S with this base. + base_comm = [] + for i in range(len(no_free) - 1): + ind1 = no_free[i] + ind2 = no_free[i + 1] + a = list(range(ind1[0])) + a.extend(ind2) + a.extend(ind1) + base_comm.append(ind1[0]) + a.extend(list(range(ind2[-1] + 1, nr))) + if sym == 0: + a.extend([nr, nr + 1]) + else: + a.extend([nr + 1, nr]) + res_gens.append(_af_new(a)) + res_base = list(res_base) + # each base is ordered; order the union of the two bases + for i in base_comm: + if i not in res_base: + res_base.append(i) + res_base.sort() + if not res_gens: + res_gens = [_af_new(id_af)] + + return size, res_base, res_gens + + +def gens_products(*v): + """ + Returns size, res_base, res_gens BSGS for n tensors of different types. + + Explanation + =========== + + v is a sequence of (base_i, gens_i, free_i, sym_i) + where + base_i, gens_i BSGS of tensor of type `i` + free_i list of the fixed slots for each of the tensors + of type `i`; if there are `n_i` tensors of type `i` + and none of them have fixed slots, `free = [[]]*n_i` + sym 0 (1) if the tensors of type `i` (anti)commute among themselves + + Examples + ======== + + >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, gens_products + >>> base, gens = get_symmetric_group_sgs(2) + >>> gens_products((base, gens, [[], []], 0)) + (6, [0, 2], [(5)(0 1), (5)(2 3), (5)(0 2)(1 3)]) + >>> gens_products((base, gens, [[1], []], 0)) + (6, [2], [(5)(2 3)]) + """ + res_size, res_base, res_gens = tensor_gens(*v[0]) + for i in range(1, len(v)): + size, base, gens = tensor_gens(*v[i]) + res_base, res_gens = bsgs_direct_product(res_base, res_gens, base, + gens, 1) + res_size = res_gens[0].size + id_af = list(range(res_size)) + res_gens = [h for h in res_gens if h != id_af] + if not res_gens: + res_gens = [id_af] + return res_size, res_base, res_gens diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__init__.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9b531211e22df62c1d1eda449e110ee1160ec00 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_coset_table.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_coset_table.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80f62030a4cd5218004ef443109fa1c4e84fb429 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_coset_table.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_fp_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_fp_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5c696e6049fc0783ca18c4900bccff6a2ecc093 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_fp_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_free_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_free_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8152b51210719c4765541cb6dd948d38b1e41d7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_free_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_galois.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_galois.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e1b64c8e683fbd2ef77fa2e6c862de925344dd7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_galois.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_generators.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_generators.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75513e1a9c9d87735883c9259ef7b8ff0bcfcfb3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_generators.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_graycode.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_graycode.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29e84ffba7d5fdd20ceb5da87bdbeb8c6671e653 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_graycode.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_group_constructs.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_group_constructs.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ee8a8914f7b562846959ef821ac729e204bcc9d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_group_constructs.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_group_numbers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_group_numbers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41e1ad4bc6140acb83d2cacb65b93d4273b916fa Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_group_numbers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_homomorphisms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_homomorphisms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..998fcb4d582fd4ee7d6d8b2278b6ac82bea08cf0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_homomorphisms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_named_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_named_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f4728845b6ee0c4779b2deab30cd1658d24df52 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_named_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_partitions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_partitions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9941f14c3f146742dca04cc55f3308812cb07955 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_partitions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_pc_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_pc_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ed51a8fb46a71bff0f4348994bb0517c98b69d6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_pc_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_perm_groups.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_perm_groups.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d35459a0d0d9cd0871472976ab26eb767ab9d382 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_perm_groups.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_permutations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_permutations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73bcf1f7df31023dee7be3fcdab323df55900f8b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_permutations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_polyhedron.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_polyhedron.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05749d04347f57dac029221231350f66167e4033 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_polyhedron.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_prufer.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_prufer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afec935f26200cfb97f27735e957cab41b0bc74c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_prufer.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_rewriting.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_rewriting.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bdea241c974f4be8b4868e01069654b5433f6f7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_rewriting.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_schur_number.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_schur_number.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..252d07c7e7b449a6e9170680d8aecbd686b6f249 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_schur_number.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_subsets.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_subsets.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c14f438e821cd0fe68074a886bd4a9afa07aa15 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_subsets.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_tensor_can.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_tensor_can.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..002b0c6f1b32c243d027c62f17ac2fc0a315099c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_tensor_can.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_testutil.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_testutil.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..157c0c5bbe5f59863b5ab674d6b2feccd5ca4971 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_testutil.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_util.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..609626dae838498baf9ef6c24a19e70b7ca8d81a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/combinatorics/tests/__pycache__/test_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_coset_table.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_coset_table.py new file mode 100644 index 0000000000000000000000000000000000000000..ab3f62880445c5deb526797ee0623fe3510bcbc3 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_coset_table.py @@ -0,0 +1,825 @@ +from sympy.combinatorics.fp_groups import FpGroup +from sympy.combinatorics.coset_table import (CosetTable, + coset_enumeration_r, coset_enumeration_c) +from sympy.combinatorics.coset_table import modified_coset_enumeration_r +from sympy.combinatorics.free_groups import free_group + +from sympy.testing.pytest import slow + +""" +References +========== + +[1] Holt, D., Eick, B., O'Brien, E. +"Handbook of Computational Group Theory" + +[2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson +Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490. +"Implementation and Analysis of the Todd-Coxeter Algorithm" + +""" + +def test_scan_1(): + # Example 5.1 from [1] + F, x, y = free_group("x, y") + f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + c = CosetTable(f, [x]) + + c.scan_and_fill(0, x) + assert c.table == [[0, 0, None, None]] + assert c.p == [0] + assert c.n == 1 + assert c.omega == [0] + + c.scan_and_fill(0, x**3) + assert c.table == [[0, 0, None, None]] + assert c.p == [0] + assert c.n == 1 + assert c.omega == [0] + + c.scan_and_fill(0, y**3) + assert c.table == [[0, 0, 1, 2], [None, None, 2, 0], [None, None, 0, 1]] + assert c.p == [0, 1, 2] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + c.scan_and_fill(0, x**-1*y**-1*x*y) + assert c.table == [[0, 0, 1, 2], [None, None, 2, 0], [2, 2, 0, 1]] + assert c.p == [0, 1, 2] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + c.scan_and_fill(1, x**3) + assert c.table == [[0, 0, 1, 2], [3, 4, 2, 0], [2, 2, 0, 1], \ + [4, 1, None, None], [1, 3, None, None]] + assert c.p == [0, 1, 2, 3, 4] + assert c.n == 5 + assert c.omega == [0, 1, 2, 3, 4] + + c.scan_and_fill(1, y**3) + assert c.table == [[0, 0, 1, 2], [3, 4, 2, 0], [2, 2, 0, 1], \ + [4, 1, None, None], [1, 3, None, None]] + assert c.p == [0, 1, 2, 3, 4] + assert c.n == 5 + assert c.omega == [0, 1, 2, 3, 4] + + c.scan_and_fill(1, x**-1*y**-1*x*y) + assert c.table == [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1], \ + [None, 1, None, None], [1, 3, None, None]] + assert c.p == [0, 1, 2, 1, 1] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + # Example 5.2 from [1] + f = FpGroup(F, [x**2, y**3, (x*y)**3]) + c = CosetTable(f, [x*y]) + + c.scan_and_fill(0, x*y) + assert c.table == [[1, None, None, 1], [None, 0, 0, None]] + assert c.p == [0, 1] + assert c.n == 2 + assert c.omega == [0, 1] + + c.scan_and_fill(0, x**2) + assert c.table == [[1, 1, None, 1], [0, 0, 0, None]] + assert c.p == [0, 1] + assert c.n == 2 + assert c.omega == [0, 1] + + c.scan_and_fill(0, y**3) + assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]] + assert c.p == [0, 1, 2] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + c.scan_and_fill(0, (x*y)**3) + assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]] + assert c.p == [0, 1, 2] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + c.scan_and_fill(1, x**2) + assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]] + assert c.p == [0, 1, 2] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + c.scan_and_fill(1, y**3) + assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [None, None, 1, 0]] + assert c.p == [0, 1, 2] + assert c.n == 3 + assert c.omega == [0, 1, 2] + + c.scan_and_fill(1, (x*y)**3) + assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [3, 4, 1, 0], [None, 2, 4, None], [2, None, None, 3]] + assert c.p == [0, 1, 2, 3, 4] + assert c.n == 5 + assert c.omega == [0, 1, 2, 3, 4] + + c.scan_and_fill(2, x**2) + assert c.table == [[1, 1, 2, 1], [0, 0, 0, 2], [3, 3, 1, 0], [2, 2, 3, 3], [2, None, None, 3]] + assert c.p == [0, 1, 2, 3, 3] + assert c.n == 4 + assert c.omega == [0, 1, 2, 3] + + +@slow +def test_coset_enumeration(): + # this test function contains the combined tests for the two strategies + # i.e. HLT and Felsch strategies. + + # Example 5.1 from [1] + F, x, y = free_group("x, y") + f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + C_r = coset_enumeration_r(f, [x]) + C_r.compress(); C_r.standardize() + C_c = coset_enumeration_c(f, [x]) + C_c.compress(); C_c.standardize() + table1 = [[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]] + assert C_r.table == table1 + assert C_c.table == table1 + + # E1 from [2] Pg. 474 + F, r, s, t = free_group("r, s, t") + E1 = FpGroup(F, [t**-1*r*t*r**-2, r**-1*s*r*s**-2, s**-1*t*s*t**-2]) + C_r = coset_enumeration_r(E1, []) + C_r.compress() + C_c = coset_enumeration_c(E1, []) + C_c.compress() + table2 = [[0, 0, 0, 0, 0, 0]] + assert C_r.table == table2 + # test for issue #11449 + assert C_c.table == table2 + + # Cox group from [2] Pg. 474 + F, a, b = free_group("a, b") + Cox = FpGroup(F, [a**6, b**6, (a*b)**2, (a**2*b**2)**2, (a**3*b**3)**5]) + C_r = coset_enumeration_r(Cox, [a]) + C_r.compress(); C_r.standardize() + C_c = coset_enumeration_c(Cox, [a]) + C_c.compress(); C_c.standardize() + table3 = [[0, 0, 1, 2], + [2, 3, 4, 0], + [5, 1, 0, 6], + [1, 7, 8, 9], + [9, 10, 11, 1], + [12, 2, 9, 13], + [14, 9, 2, 11], + [3, 12, 15, 16], + [16, 17, 18, 3], + [6, 4, 3, 5], + [4, 19, 20, 21], + [21, 22, 6, 4], + [7, 5, 23, 24], + [25, 23, 5, 18], + [19, 6, 22, 26], + [24, 27, 28, 7], + [29, 8, 7, 30], + [8, 31, 32, 33], + [33, 34, 13, 8], + [10, 14, 35, 35], + [35, 36, 37, 10], + [30, 11, 10, 29], + [11, 38, 39, 14], + [13, 39, 38, 12], + [40, 15, 12, 41], + [42, 13, 34, 43], + [44, 35, 14, 45], + [15, 46, 47, 34], + [34, 48, 49, 15], + [50, 16, 21, 51], + [52, 21, 16, 49], + [17, 50, 53, 54], + [54, 55, 56, 17], + [41, 18, 17, 40], + [18, 28, 27, 25], + [26, 20, 19, 19], + [20, 57, 58, 59], + [59, 60, 51, 20], + [22, 52, 61, 23], + [23, 62, 63, 22], + [64, 24, 33, 65], + [48, 33, 24, 61], + [62, 25, 54, 66], + [67, 54, 25, 68], + [57, 26, 59, 69], + [70, 59, 26, 63], + [27, 64, 71, 72], + [72, 73, 68, 27], + [28, 41, 74, 75], + [75, 76, 30, 28], + [31, 29, 77, 78], + [79, 77, 29, 37], + [38, 30, 76, 80], + [78, 81, 82, 31], + [43, 32, 31, 42], + [32, 83, 84, 85], + [85, 86, 65, 32], + [36, 44, 87, 88], + [88, 89, 90, 36], + [45, 37, 36, 44], + [37, 82, 81, 79], + [80, 74, 41, 38], + [39, 42, 91, 92], + [92, 93, 45, 39], + [46, 40, 94, 95], + [96, 94, 40, 56], + [97, 91, 42, 82], + [83, 43, 98, 99], + [100, 98, 43, 47], + [101, 87, 44, 90], + [82, 45, 93, 97], + [95, 102, 103, 46], + [104, 47, 46, 105], + [47, 106, 107, 100], + [61, 108, 109, 48], + [105, 49, 48, 104], + [49, 110, 111, 52], + [51, 111, 110, 50], + [112, 53, 50, 113], + [114, 51, 60, 115], + [116, 61, 52, 117], + [53, 118, 119, 60], + [60, 70, 66, 53], + [55, 67, 120, 121], + [121, 122, 123, 55], + [113, 56, 55, 112], + [56, 103, 102, 96], + [69, 124, 125, 57], + [115, 58, 57, 114], + [58, 126, 127, 128], + [128, 128, 69, 58], + [66, 129, 130, 62], + [117, 63, 62, 116], + [63, 125, 124, 70], + [65, 109, 108, 64], + [131, 71, 64, 132], + [133, 65, 86, 134], + [135, 66, 70, 136], + [68, 130, 129, 67], + [137, 120, 67, 138], + [132, 68, 73, 131], + [139, 69, 128, 140], + [71, 141, 142, 86], + [86, 143, 144, 71], + [145, 72, 75, 146], + [147, 75, 72, 144], + [73, 145, 148, 120], + [120, 149, 150, 73], + [74, 151, 152, 94], + [94, 153, 146, 74], + [76, 147, 154, 77], + [77, 155, 156, 76], + [157, 78, 85, 158], + [143, 85, 78, 154], + [155, 79, 88, 159], + [160, 88, 79, 161], + [151, 80, 92, 162], + [163, 92, 80, 156], + [81, 157, 164, 165], + [165, 166, 161, 81], + [99, 107, 106, 83], + [134, 84, 83, 133], + [84, 167, 168, 169], + [169, 170, 158, 84], + [87, 171, 172, 93], + [93, 163, 159, 87], + [89, 160, 173, 174], + [174, 175, 176, 89], + [90, 90, 89, 101], + [91, 177, 178, 98], + [98, 179, 162, 91], + [180, 95, 100, 181], + [179, 100, 95, 152], + [153, 96, 121, 148], + [182, 121, 96, 183], + [177, 97, 165, 184], + [185, 165, 97, 172], + [186, 99, 169, 187], + [188, 169, 99, 178], + [171, 101, 174, 189], + [190, 174, 101, 176], + [102, 180, 191, 192], + [192, 193, 183, 102], + [103, 113, 194, 195], + [195, 196, 105, 103], + [106, 104, 197, 198], + [199, 197, 104, 109], + [110, 105, 196, 200], + [198, 201, 133, 106], + [107, 186, 202, 203], + [203, 204, 181, 107], + [108, 116, 205, 206], + [206, 207, 132, 108], + [109, 133, 201, 199], + [200, 194, 113, 110], + [111, 114, 208, 209], + [209, 210, 117, 111], + [118, 112, 211, 212], + [213, 211, 112, 123], + [214, 208, 114, 125], + [126, 115, 215, 216], + [217, 215, 115, 119], + [218, 205, 116, 130], + [125, 117, 210, 214], + [212, 219, 220, 118], + [136, 119, 118, 135], + [119, 221, 222, 217], + [122, 182, 223, 224], + [224, 225, 226, 122], + [138, 123, 122, 137], + [123, 220, 219, 213], + [124, 139, 227, 228], + [228, 229, 136, 124], + [216, 222, 221, 126], + [140, 127, 126, 139], + [127, 230, 231, 232], + [232, 233, 140, 127], + [129, 135, 234, 235], + [235, 236, 138, 129], + [130, 132, 207, 218], + [141, 131, 237, 238], + [239, 237, 131, 150], + [167, 134, 240, 241], + [242, 240, 134, 142], + [243, 234, 135, 220], + [221, 136, 229, 244], + [149, 137, 245, 246], + [247, 245, 137, 226], + [220, 138, 236, 243], + [244, 227, 139, 221], + [230, 140, 233, 248], + [238, 249, 250, 141], + [251, 142, 141, 252], + [142, 253, 254, 242], + [154, 255, 256, 143], + [252, 144, 143, 251], + [144, 257, 258, 147], + [146, 258, 257, 145], + [259, 148, 145, 260], + [261, 146, 153, 262], + [263, 154, 147, 264], + [148, 265, 266, 153], + [246, 267, 268, 149], + [260, 150, 149, 259], + [150, 250, 249, 239], + [162, 269, 270, 151], + [262, 152, 151, 261], + [152, 271, 272, 179], + [159, 273, 274, 155], + [264, 156, 155, 263], + [156, 270, 269, 163], + [158, 256, 255, 157], + [275, 164, 157, 276], + [277, 158, 170, 278], + [279, 159, 163, 280], + [161, 274, 273, 160], + [281, 173, 160, 282], + [276, 161, 166, 275], + [283, 162, 179, 284], + [164, 285, 286, 170], + [170, 188, 184, 164], + [166, 185, 189, 173], + [173, 287, 288, 166], + [241, 254, 253, 167], + [278, 168, 167, 277], + [168, 289, 290, 291], + [291, 292, 187, 168], + [189, 293, 294, 171], + [280, 172, 171, 279], + [172, 295, 296, 185], + [175, 190, 297, 297], + [297, 298, 299, 175], + [282, 176, 175, 281], + [176, 294, 293, 190], + [184, 296, 295, 177], + [284, 178, 177, 283], + [178, 300, 301, 188], + [181, 272, 271, 180], + [302, 191, 180, 303], + [304, 181, 204, 305], + [183, 266, 265, 182], + [306, 223, 182, 307], + [303, 183, 193, 302], + [308, 184, 188, 309], + [310, 189, 185, 311], + [187, 301, 300, 186], + [305, 202, 186, 304], + [312, 187, 292, 313], + [314, 297, 190, 315], + [191, 316, 317, 204], + [204, 318, 319, 191], + [320, 192, 195, 321], + [322, 195, 192, 319], + [193, 320, 323, 223], + [223, 324, 325, 193], + [194, 326, 327, 211], + [211, 328, 321, 194], + [196, 322, 329, 197], + [197, 330, 331, 196], + [332, 198, 203, 333], + [318, 203, 198, 329], + [330, 199, 206, 334], + [335, 206, 199, 336], + [326, 200, 209, 337], + [338, 209, 200, 331], + [201, 332, 339, 240], + [240, 340, 336, 201], + [202, 341, 342, 292], + [292, 343, 333, 202], + [205, 344, 345, 210], + [210, 338, 334, 205], + [207, 335, 346, 237], + [237, 347, 348, 207], + [208, 349, 350, 215], + [215, 351, 337, 208], + [352, 212, 217, 353], + [351, 217, 212, 327], + [328, 213, 224, 323], + [354, 224, 213, 355], + [349, 214, 228, 356], + [357, 228, 214, 345], + [358, 216, 232, 359], + [360, 232, 216, 350], + [344, 218, 235, 361], + [362, 235, 218, 348], + [219, 352, 363, 364], + [364, 365, 355, 219], + [222, 358, 366, 367], + [367, 368, 353, 222], + [225, 354, 369, 370], + [370, 371, 372, 225], + [307, 226, 225, 306], + [226, 268, 267, 247], + [227, 373, 374, 233], + [233, 360, 356, 227], + [229, 357, 361, 234], + [234, 375, 376, 229], + [248, 231, 230, 230], + [231, 377, 378, 379], + [379, 380, 359, 231], + [236, 362, 381, 245], + [245, 382, 383, 236], + [384, 238, 242, 385], + [340, 242, 238, 346], + [347, 239, 246, 381], + [386, 246, 239, 387], + [388, 241, 291, 389], + [343, 291, 241, 339], + [375, 243, 364, 390], + [391, 364, 243, 383], + [373, 244, 367, 392], + [393, 367, 244, 376], + [382, 247, 370, 394], + [395, 370, 247, 396], + [377, 248, 379, 397], + [398, 379, 248, 374], + [249, 384, 399, 400], + [400, 401, 387, 249], + [250, 260, 402, 403], + [403, 404, 252, 250], + [253, 251, 405, 406], + [407, 405, 251, 256], + [257, 252, 404, 408], + [406, 409, 277, 253], + [254, 388, 410, 411], + [411, 412, 385, 254], + [255, 263, 413, 414], + [414, 415, 276, 255], + [256, 277, 409, 407], + [408, 402, 260, 257], + [258, 261, 416, 417], + [417, 418, 264, 258], + [265, 259, 419, 420], + [421, 419, 259, 268], + [422, 416, 261, 270], + [271, 262, 423, 424], + [425, 423, 262, 266], + [426, 413, 263, 274], + [270, 264, 418, 422], + [420, 427, 307, 265], + [266, 303, 428, 425], + [267, 386, 429, 430], + [430, 431, 396, 267], + [268, 307, 427, 421], + [269, 283, 432, 433], + [433, 434, 280, 269], + [424, 428, 303, 271], + [272, 304, 435, 436], + [436, 437, 284, 272], + [273, 279, 438, 439], + [439, 440, 282, 273], + [274, 276, 415, 426], + [285, 275, 441, 442], + [443, 441, 275, 288], + [289, 278, 444, 445], + [446, 444, 278, 286], + [447, 438, 279, 294], + [295, 280, 434, 448], + [287, 281, 449, 450], + [451, 449, 281, 299], + [294, 282, 440, 447], + [448, 432, 283, 295], + [300, 284, 437, 452], + [442, 453, 454, 285], + [309, 286, 285, 308], + [286, 455, 456, 446], + [450, 457, 458, 287], + [311, 288, 287, 310], + [288, 454, 453, 443], + [445, 456, 455, 289], + [313, 290, 289, 312], + [290, 459, 460, 461], + [461, 462, 389, 290], + [293, 310, 463, 464], + [464, 465, 315, 293], + [296, 308, 466, 467], + [467, 468, 311, 296], + [298, 314, 469, 470], + [470, 471, 472, 298], + [315, 299, 298, 314], + [299, 458, 457, 451], + [452, 435, 304, 300], + [301, 312, 473, 474], + [474, 475, 309, 301], + [316, 302, 476, 477], + [478, 476, 302, 325], + [341, 305, 479, 480], + [481, 479, 305, 317], + [324, 306, 482, 483], + [484, 482, 306, 372], + [485, 466, 308, 454], + [455, 309, 475, 486], + [487, 463, 310, 458], + [454, 311, 468, 485], + [486, 473, 312, 455], + [459, 313, 488, 489], + [490, 488, 313, 342], + [491, 469, 314, 472], + [458, 315, 465, 487], + [477, 492, 485, 316], + [463, 317, 316, 468], + [317, 487, 493, 481], + [329, 447, 464, 318], + [468, 319, 318, 463], + [319, 467, 448, 322], + [321, 448, 467, 320], + [475, 323, 320, 466], + [432, 321, 328, 437], + [438, 329, 322, 434], + [323, 474, 452, 328], + [483, 494, 486, 324], + [466, 325, 324, 475], + [325, 485, 492, 478], + [337, 422, 433, 326], + [437, 327, 326, 432], + [327, 436, 424, 351], + [334, 426, 439, 330], + [434, 331, 330, 438], + [331, 433, 422, 338], + [333, 464, 447, 332], + [449, 339, 332, 440], + [465, 333, 343, 469], + [413, 334, 338, 418], + [336, 439, 426, 335], + [441, 346, 335, 415], + [440, 336, 340, 449], + [416, 337, 351, 423], + [339, 451, 470, 343], + [346, 443, 450, 340], + [480, 493, 487, 341], + [469, 342, 341, 465], + [342, 491, 495, 490], + [361, 407, 414, 344], + [418, 345, 344, 413], + [345, 417, 408, 357], + [381, 446, 442, 347], + [415, 348, 347, 441], + [348, 414, 407, 362], + [356, 408, 417, 349], + [423, 350, 349, 416], + [350, 425, 420, 360], + [353, 424, 436, 352], + [479, 363, 352, 435], + [428, 353, 368, 476], + [355, 452, 474, 354], + [488, 369, 354, 473], + [435, 355, 365, 479], + [402, 356, 360, 419], + [405, 361, 357, 404], + [359, 420, 425, 358], + [476, 366, 358, 428], + [427, 359, 380, 482], + [444, 381, 362, 409], + [363, 481, 477, 368], + [368, 393, 390, 363], + [365, 391, 394, 369], + [369, 490, 480, 365], + [366, 478, 483, 380], + [380, 398, 392, 366], + [371, 395, 496, 497], + [497, 498, 489, 371], + [473, 372, 371, 488], + [372, 486, 494, 484], + [392, 400, 403, 373], + [419, 374, 373, 402], + [374, 421, 430, 398], + [390, 411, 406, 375], + [404, 376, 375, 405], + [376, 403, 400, 393], + [397, 430, 421, 377], + [482, 378, 377, 427], + [378, 484, 497, 499], + [499, 499, 397, 378], + [394, 461, 445, 382], + [409, 383, 382, 444], + [383, 406, 411, 391], + [385, 450, 443, 384], + [492, 399, 384, 453], + [457, 385, 412, 493], + [387, 442, 446, 386], + [494, 429, 386, 456], + [453, 387, 401, 492], + [389, 470, 451, 388], + [493, 410, 388, 457], + [471, 389, 462, 495], + [412, 390, 393, 399], + [462, 394, 391, 410], + [401, 392, 398, 429], + [396, 445, 461, 395], + [498, 496, 395, 460], + [456, 396, 431, 494], + [431, 397, 499, 496], + [399, 477, 481, 412], + [429, 483, 478, 401], + [410, 480, 490, 462], + [496, 497, 484, 431], + [489, 495, 491, 459], + [495, 460, 459, 471], + [460, 489, 498, 498], + [472, 472, 471, 491]] + + assert C_r.table == table3 + assert C_c.table == table3 + + # Group denoted by B2,4 from [2] Pg. 474 + F, a, b = free_group("a, b") + B_2_4 = FpGroup(F, [a**4, b**4, (a*b)**4, (a**-1*b)**4, (a**2*b)**4, \ + (a*b**2)**4, (a**2*b**2)**4, (a**-1*b*a*b)**4, (a*b**-1*a*b)**4]) + C_r = coset_enumeration_r(B_2_4, [a]) + C_c = coset_enumeration_c(B_2_4, [a]) + index_r = 0 + for i in range(len(C_r.p)): + if C_r.p[i] == i: + index_r += 1 + assert index_r == 1024 + + index_c = 0 + for i in range(len(C_c.p)): + if C_c.p[i] == i: + index_c += 1 + assert index_c == 1024 + + # trivial Macdonald group G(2,2) from [2] Pg. 480 + M = FpGroup(F, [b**-1*a**-1*b*a*b**-1*a*b*a**-2, a**-1*b**-1*a*b*a**-1*b*a*b**-2]) + C_r = coset_enumeration_r(M, [a]) + C_r.compress(); C_r.standardize() + C_c = coset_enumeration_c(M, [a]) + C_c.compress(); C_c.standardize() + table4 = [[0, 0, 0, 0]] + assert C_r.table == table4 + assert C_c.table == table4 + + +def test_look_ahead(): + # Section 3.2 [Test Example] Example (d) from [2] + F, a, b, c = free_group("a, b, c") + f = FpGroup(F, [a**11, b**5, c**4, (a*c)**3, b**2*c**-1*b**-1*c, a**4*b**-1*a**-1*b]) + H = [c, b, c**2] + table0 = [[1, 2, 0, 0, 0, 0], + [3, 0, 4, 5, 6, 7], + [0, 8, 9, 10, 11, 12], + [5, 1, 10, 13, 14, 15], + [16, 5, 16, 1, 17, 18], + [4, 3, 1, 8, 19, 20], + [12, 21, 22, 23, 24, 1], + [25, 26, 27, 28, 1, 24], + [2, 10, 5, 16, 22, 28], + [10, 13, 13, 2, 29, 30]] + CosetTable.max_stack_size = 10 + C_c = coset_enumeration_c(f, H) + C_c.compress(); C_c.standardize() + assert C_c.table[: 10] == table0 + +def test_modified_methods(): + ''' + Tests for modified coset table methods. + Example 5.7 from [1] Holt, D., Eick, B., O'Brien + "Handbook of Computational Group Theory". + + ''' + F, x, y = free_group("x, y") + f = FpGroup(F, [x**3, y**5, (x*y)**2]) + H = [x*y, x**-1*y**-1*x*y*x] + C = CosetTable(f, H) + C.modified_define(0, x) + identity = C._grp.identity + a_0 = C._grp.generators[0] + a_1 = C._grp.generators[1] + + assert C.P == [[identity, None, None, None], + [None, identity, None, None]] + assert C.table == [[1, None, None, None], + [None, 0, None, None]] + + C.modified_define(1, x) + assert C.table == [[1, None, None, None], + [2, 0, None, None], + [None, 1, None, None]] + assert C.P == [[identity, None, None, None], + [identity, identity, None, None], + [None, identity, None, None]] + + C.modified_scan(0, x**3, C._grp.identity, fill=False) + assert C.P == [[identity, identity, None, None], + [identity, identity, None, None], + [identity, identity, None, None]] + assert C.table == [[1, 2, None, None], + [2, 0, None, None], + [0, 1, None, None]] + + C.modified_scan(0, x*y, C._grp.generators[0], fill=False) + assert C.P == [[identity, identity, None, a_0**-1], + [identity, identity, a_0, None], + [identity, identity, None, None]] + assert C.table == [[1, 2, None, 1], + [2, 0, 0, None], + [0, 1, None, None]] + + C.modified_define(2, y**-1) + assert C.table == [[1, 2, None, 1], + [2, 0, 0, None], + [0, 1, None, 3], + [None, None, 2, None]] + assert C.P == [[identity, identity, None, a_0**-1], + [identity, identity, a_0, None], + [identity, identity, None, identity], + [None, None, identity, None]] + + C.modified_scan(0, x**-1*y**-1*x*y*x, C._grp.generators[1]) + assert C.table == [[1, 2, None, 1], + [2, 0, 0, None], + [0, 1, None, 3], + [3, 3, 2, None]] + assert C.P == [[identity, identity, None, a_0**-1], + [identity, identity, a_0, None], + [identity, identity, None, identity], + [a_1, a_1**-1, identity, None]] + + C.modified_scan(2, (x*y)**2, C._grp.identity) + assert C.table == [[1, 2, 3, 1], + [2, 0, 0, None], + [0, 1, None, 3], + [3, 3, 2, 0]] + assert C.P == [[identity, identity, a_1**-1, a_0**-1], + [identity, identity, a_0, None], + [identity, identity, None, identity], + [a_1, a_1**-1, identity, a_1]] + + C.modified_define(2, y) + assert C.table == [[1, 2, 3, 1], + [2, 0, 0, None], + [0, 1, 4, 3], + [3, 3, 2, 0], + [None, None, None, 2]] + assert C.P == [[identity, identity, a_1**-1, a_0**-1], + [identity, identity, a_0, None], + [identity, identity, identity, identity], + [a_1, a_1**-1, identity, a_1], + [None, None, None, identity]] + + C.modified_scan(0, y**5, C._grp.identity) + assert C.table == [[1, 2, 3, 1], [2, 0, 0, 4], [0, 1, 4, 3], [3, 3, 2, 0], [None, None, 1, 2]] + assert C.P == [[identity, identity, a_1**-1, a_0**-1], + [identity, identity, a_0, a_0*a_1**-1], + [identity, identity, identity, identity], + [a_1, a_1**-1, identity, a_1], + [None, None, a_1*a_0**-1, identity]] + + C.modified_scan(1, (x*y)**2, C._grp.identity) + assert C.table == [[1, 2, 3, 1], + [2, 0, 0, 4], + [0, 1, 4, 3], + [3, 3, 2, 0], + [4, 4, 1, 2]] + assert C.P == [[identity, identity, a_1**-1, a_0**-1], + [identity, identity, a_0, a_0*a_1**-1], + [identity, identity, identity, identity], + [a_1, a_1**-1, identity, a_1], + [a_0*a_1**-1, a_1*a_0**-1, a_1*a_0**-1, identity]] + + # Modified coset enumeration test + f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y]) + C = coset_enumeration_r(f, [x]) + C_m = modified_coset_enumeration_r(f, [x]) + assert C_m.table == C.table diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_fp_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_fp_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..3f57bdf8eff92a3022d8e01cd74ce98575987929 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_fp_groups.py @@ -0,0 +1,257 @@ +from sympy.core.singleton import S +from sympy.combinatorics.fp_groups import (FpGroup, low_index_subgroups, + reidemeister_presentation, FpSubgroup, + simplify_presentation) +from sympy.combinatorics.free_groups import (free_group, FreeGroup) + +from sympy.testing.pytest import slow + +""" +References +========== + +[1] Holt, D., Eick, B., O'Brien, E. +"Handbook of Computational Group Theory" + +[2] John J. Cannon; Lucien A. Dimino; George Havas; Jane M. Watson +Mathematics of Computation, Vol. 27, No. 123. (Jul., 1973), pp. 463-490. +"Implementation and Analysis of the Todd-Coxeter Algorithm" + +[3] PROC. SECOND INTERNAT. CONF. THEORY OF GROUPS, CANBERRA 1973, +pp. 347-356. "A Reidemeister-Schreier program" by George Havas. +http://staff.itee.uq.edu.au/havas/1973cdhw.pdf + +""" + +def test_low_index_subgroups(): + F, x, y = free_group("x, y") + + # Example 5.10 from [1] Pg. 194 + f = FpGroup(F, [x**2, y**3, (x*y)**4]) + L = low_index_subgroups(f, 4) + t1 = [[[0, 0, 0, 0]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 3, 3]], + [[0, 0, 1, 2], [2, 2, 2, 0], [1, 1, 0, 1]], + [[1, 1, 0, 0], [0, 0, 1, 1]]] + for i in range(len(t1)): + assert L[i].table == t1[i] + + f = FpGroup(F, [x**2, y**3, (x*y)**7]) + L = low_index_subgroups(f, 15) + t2 = [[[0, 0, 0, 0]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], + [4, 4, 5, 3], [6, 6, 3, 4], [5, 5, 6, 6]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], + [6, 6, 5, 3], [5, 5, 3, 4], [4, 4, 6, 6]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], + [6, 6, 5, 3], [7, 7, 3, 4], [4, 4, 8, 9], [5, 5, 10, 11], + [11, 11, 9, 6], [9, 9, 6, 8], [12, 12, 11, 7], [8, 8, 7, 10], + [10, 10, 13, 14], [14, 14, 14, 12], [13, 13, 12, 13]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], + [6, 6, 5, 3], [7, 7, 3, 4], [4, 4, 8, 9], [5, 5, 10, 11], + [11, 11, 9, 6], [12, 12, 6, 8], [10, 10, 11, 7], [8, 8, 7, 10], + [9, 9, 13, 14], [14, 14, 14, 12], [13, 13, 12, 13]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], + [6, 6, 5, 3], [7, 7, 3, 4], [4, 4, 8, 9], [5, 5, 10, 11], + [11, 11, 9, 6], [12, 12, 6, 8], [13, 13, 11, 7], [8, 8, 7, 10], + [9, 9, 12, 12], [10, 10, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 3, 3], [2, 2, 5, 6] + , [7, 7, 6, 4], [8, 8, 4, 5], [5, 5, 8, 9], [6, 6, 9, 7], + [10, 10, 7, 8], [9, 9, 11, 12], [11, 11, 12, 10], [13, 13, 10, 11], + [12, 12, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 3, 3], [2, 2, 5, 6] + , [7, 7, 6, 4], [8, 8, 4, 5], [5, 5, 8, 9], [6, 6, 9, 7], + [10, 10, 7, 8], [9, 9, 11, 12], [13, 13, 12, 10], [12, 12, 10, 11], + [11, 11, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 4, 4] + , [7, 7, 6, 3], [8, 8, 3, 5], [5, 5, 8, 9], [6, 6, 9, 7], + [10, 10, 7, 8], [9, 9, 11, 12], [13, 13, 12, 10], [12, 12, 10, 11], + [11, 11, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [5, 5, 6, 3], [9, 9, 3, 5], [10, 10, 8, 4], [8, 8, 4, 7], + [6, 6, 10, 11], [7, 7, 11, 9], [12, 12, 9, 10], [11, 11, 13, 14], + [14, 14, 14, 12], [13, 13, 12, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [6, 6, 6, 3], [5, 5, 3, 5], [8, 8, 8, 4], [7, 7, 4, 7]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [9, 9, 6, 3], [6, 6, 3, 5], [10, 10, 8, 4], [11, 11, 4, 7], + [5, 5, 10, 12], [7, 7, 12, 9], [8, 8, 11, 11], [13, 13, 9, 10], + [12, 12, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [9, 9, 6, 3], [6, 6, 3, 5], [10, 10, 8, 4], [11, 11, 4, 7], + [5, 5, 12, 11], [7, 7, 10, 10], [8, 8, 9, 12], [13, 13, 11, 9], + [12, 12, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [9, 9, 6, 3], [10, 10, 3, 5], [7, 7, 8, 4], [11, 11, 4, 7], + [5, 5, 9, 9], [6, 6, 11, 12], [8, 8, 12, 10], [13, 13, 10, 11], + [12, 12, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [9, 9, 6, 3], [10, 10, 3, 5], [7, 7, 8, 4], [11, 11, 4, 7], + [5, 5, 12, 11], [6, 6, 10, 10], [8, 8, 9, 12], [13, 13, 11, 9], + [12, 12, 13, 13]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8] + , [9, 9, 6, 3], [10, 10, 3, 5], [11, 11, 8, 4], [12, 12, 4, 7], + [5, 5, 9, 9], [6, 6, 12, 13], [7, 7, 11, 11], [8, 8, 13, 10], + [13, 13, 10, 12]], + [[1, 1, 0, 0], [0, 0, 2, 3], [4, 4, 3, 1], [5, 5, 1, 2], [2, 2, 4, 4] + , [3, 3, 6, 7], [7, 7, 7, 5], [6, 6, 5, 6]]] + for i in range(len(t2)): + assert L[i].table == t2[i] + + f = FpGroup(F, [x**2, y**3, (x*y)**7]) + L = low_index_subgroups(f, 10, [x]) + t3 = [[[0, 0, 0, 0]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], [4, 4, 5, 3], + [6, 6, 3, 4], [5, 5, 6, 6]], + [[0, 0, 1, 2], [1, 1, 2, 0], [3, 3, 0, 1], [2, 2, 4, 5], [6, 6, 5, 3], + [5, 5, 3, 4], [4, 4, 6, 6]], + [[0, 0, 1, 2], [3, 3, 2, 0], [4, 4, 0, 1], [1, 1, 5, 6], [2, 2, 7, 8], + [6, 6, 6, 3], [5, 5, 3, 5], [8, 8, 8, 4], [7, 7, 4, 7]]] + for i in range(len(t3)): + assert L[i].table == t3[i] + + +def test_subgroup_presentations(): + F, x, y = free_group("x, y") + f = FpGroup(F, [x**3, y**5, (x*y)**2]) + H = [x*y, x**-1*y**-1*x*y*x] + p1 = reidemeister_presentation(f, H) + assert str(p1) == "((y_1, y_2), (y_1**2, y_2**3, y_2*y_1*y_2*y_1*y_2*y_1))" + + H = f.subgroup(H) + assert (H.generators, H.relators) == p1 + + f = FpGroup(F, [x**3, y**3, (x*y)**3]) + H = [x*y, x*y**-1] + p2 = reidemeister_presentation(f, H) + assert str(p2) == "((x_0, y_0), (x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0))" + + f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3]) + H = [x] + p3 = reidemeister_presentation(f, H) + assert str(p3) == "((x_0,), (x_0**4,))" + + f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2]) + H = [x] + p4 = reidemeister_presentation(f, H) + assert str(p4) == "((x_0,), (x_0**6,))" + + # this presentation can be improved, the most simplified form + # of presentation is + # See [2] Pg 474 group PSL_2(11) + # This is the group PSL_2(11) + F, a, b, c = free_group("a, b, c") + f = FpGroup(F, [a**11, b**5, c**4, (b*c**2)**2, (a*b*c)**3, (a**4*c**2)**3, b**2*c**-1*b**-1*c, a**4*b**-1*a**-1*b]) + H = [a, b, c**2] + gens, rels = reidemeister_presentation(f, H) + assert str(gens) == "(b_1, c_3)" + assert len(rels) == 18 + + +@slow +def test_order(): + F, x, y = free_group("x, y") + f = FpGroup(F, [x**4, y**2, x*y*x**-1*y]) + assert f.order() == 8 + + f = FpGroup(F, [x*y*x**-1*y**-1, y**2]) + assert f.order() is S.Infinity + + F, a, b, c = free_group("a, b, c") + f = FpGroup(F, [a**250, b**2, c*b*c**-1*b, c**4, c**-1*a**-1*c*a, a**-1*b**-1*a*b]) + assert f.order() == 2000 + + F, x = free_group("x") + f = FpGroup(F, []) + assert f.order() is S.Infinity + + f = FpGroup(free_group('')[0], []) + assert f.order() == 1 + +def test_fp_subgroup(): + def _test_subgroup(K, T, S): + _gens = T(K.generators) + assert all(elem in S for elem in _gens) + assert T.is_injective() + assert T.image().order() == S.order() + F, x, y = free_group("x, y") + f = FpGroup(F, [x**4, y**2, x*y*x**-1*y]) + S = FpSubgroup(f, [x*y]) + assert (x*y)**-3 in S + K, T = f.subgroup([x*y], homomorphism=True) + assert T(K.generators) == [y*x**-1] + _test_subgroup(K, T, S) + + S = FpSubgroup(f, [x**-1*y*x]) + assert x**-1*y**4*x in S + assert x**-1*y**4*x**2 not in S + K, T = f.subgroup([x**-1*y*x], homomorphism=True) + assert T(K.generators[0]**3) == y**3 + _test_subgroup(K, T, S) + + f = FpGroup(F, [x**3, y**5, (x*y)**2]) + H = [x*y, x**-1*y**-1*x*y*x] + K, T = f.subgroup(H, homomorphism=True) + S = FpSubgroup(f, H) + _test_subgroup(K, T, S) + +def test_permutation_methods(): + F, x, y = free_group("x, y") + # DihedralGroup(8) + G = FpGroup(F, [x**2, y**8, x*y*x**-1*y]) + T = G._to_perm_group()[1] + assert T.is_isomorphism() + assert G.center() == [y**4] + + # DiheadralGroup(4) + G = FpGroup(F, [x**2, y**4, x*y*x**-1*y]) + S = FpSubgroup(G, G.normal_closure([x])) + assert x in S + assert y**-1*x*y in S + + # Z_5xZ_4 + G = FpGroup(F, [x*y*x**-1*y**-1, y**5, x**4]) + assert G.is_abelian + assert G.is_solvable + + # AlternatingGroup(5) + G = FpGroup(F, [x**3, y**2, (x*y)**5]) + assert not G.is_solvable + + # AlternatingGroup(4) + G = FpGroup(F, [x**3, y**2, (x*y)**3]) + assert len(G.derived_series()) == 3 + S = FpSubgroup(G, G.derived_subgroup()) + assert S.order() == 4 + + +def test_simplify_presentation(): + # ref #16083 + G = simplify_presentation(FpGroup(FreeGroup([]), [])) + assert not G.generators + assert not G.relators + + # CyclicGroup(3) + # The second generator in is trivial due to relators {x^2, x^5} + F, x, y = free_group("x, y") + G = simplify_presentation(FpGroup(F, [x**2, x**5, y**3])) + assert x in G.relators + +def test_cyclic(): + F, x, y = free_group("x, y") + f = FpGroup(F, [x*y, x**-1*y**-1*x*y*x]) + assert f.is_cyclic + f = FpGroup(F, [x*y, x*y**-1]) + assert f.is_cyclic + f = FpGroup(F, [x**4, y**2, x*y*x**-1*y]) + assert not f.is_cyclic + + +def test_abelian_invariants(): + F, x, y = free_group("x, y") + f = FpGroup(F, [x*y, x**-1*y**-1*x*y*x]) + assert f.abelian_invariants() == [] + f = FpGroup(F, [x*y, x*y**-1]) + assert f.abelian_invariants() == [2] + f = FpGroup(F, [x**4, y**2, x*y*x**-1*y]) + assert f.abelian_invariants() == [2, 4] diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_free_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_free_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..439be4b7c5e8bb5ff592c9b7f07773e82952b3d5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_free_groups.py @@ -0,0 +1,226 @@ +from sympy.combinatorics.free_groups import free_group, FreeGroup +from sympy.core import Symbol +from sympy.testing.pytest import raises +from sympy.core.numbers import oo + +F, x, y, z = free_group("x, y, z") + + +def test_FreeGroup__init__(): + x, y, z = map(Symbol, "xyz") + + assert len(FreeGroup("x, y, z").generators) == 3 + assert len(FreeGroup(x).generators) == 1 + assert len(FreeGroup(("x", "y", "z"))) == 3 + assert len(FreeGroup((x, y, z)).generators) == 3 + + +def test_FreeGroup__getnewargs__(): + x, y, z = map(Symbol, "xyz") + assert FreeGroup("x, y, z").__getnewargs__() == ((x, y, z),) + + +def test_free_group(): + G, a, b, c = free_group("a, b, c") + assert F.generators == (x, y, z) + assert x*z**2 in F + assert x in F + assert y*z**-1 in F + assert (y*z)**0 in F + assert a not in F + assert a**0 not in F + assert len(F) == 3 + assert str(F) == '' + assert not F == G + assert F.order() is oo + assert F.is_abelian == False + assert F.center() == {F.identity} + + (e,) = free_group("") + assert e.order() == 1 + assert e.generators == () + assert e.elements == {e.identity} + assert e.is_abelian == True + + +def test_FreeGroup__hash__(): + assert hash(F) + + +def test_FreeGroup__eq__(): + assert free_group("x, y, z")[0] == free_group("x, y, z")[0] + assert free_group("x, y, z")[0] is free_group("x, y, z")[0] + + assert free_group("x, y, z")[0] != free_group("a, x, y")[0] + assert free_group("x, y, z")[0] is not free_group("a, x, y")[0] + + assert free_group("x, y")[0] != free_group("x, y, z")[0] + assert free_group("x, y")[0] is not free_group("x, y, z")[0] + + assert free_group("x, y, z")[0] != free_group("x, y")[0] + assert free_group("x, y, z")[0] is not free_group("x, y")[0] + + +def test_FreeGroup__getitem__(): + assert F[0:] == FreeGroup("x, y, z") + assert F[1:] == FreeGroup("y, z") + assert F[2:] == FreeGroup("z") + + +def test_FreeGroupElm__hash__(): + assert hash(x*y*z) + + +def test_FreeGroupElm_copy(): + f = x*y*z**3 + g = f.copy() + h = x*y*z**7 + + assert f == g + assert f != h + + +def test_FreeGroupElm_inverse(): + assert x.inverse() == x**-1 + assert (x*y).inverse() == y**-1*x**-1 + assert (y*x*y**-1).inverse() == y*x**-1*y**-1 + assert (y**2*x**-1).inverse() == x*y**-2 + + +def test_FreeGroupElm_type_error(): + raises(TypeError, lambda: 2/x) + raises(TypeError, lambda: x**2 + y**2) + raises(TypeError, lambda: x/2) + + +def test_FreeGroupElm_methods(): + assert (x**0).order() == 1 + assert (y**2).order() is oo + assert (x**-1*y).commutator(x) == y**-1*x**-1*y*x + assert len(x**2*y**-1) == 3 + assert len(x**-1*y**3*z) == 5 + + +def test_FreeGroupElm_eliminate_word(): + w = x**5*y*x**2*y**-4*x + assert w.eliminate_word( x, x**2 ) == x**10*y*x**4*y**-4*x**2 + w3 = x**2*y**3*x**-1*y + assert w3.eliminate_word(x, x**2) == x**4*y**3*x**-2*y + assert w3.eliminate_word(x, y) == y**5 + assert w3.eliminate_word(x, y**4) == y**8 + assert w3.eliminate_word(y, x**-1) == x**-3 + assert w3.eliminate_word(x, y*z) == y*z*y*z*y**3*z**-1 + assert (y**-3).eliminate_word(y, x**-1*z**-1) == z*x*z*x*z*x + #assert w3.eliminate_word(x, y*x) == y*x*y*x**2*y*x*y*x*y*x*z**3 + #assert w3.eliminate_word(x, x*y) == x*y*x**2*y*x*y*x*y*x*y*z**3 + + +def test_FreeGroupElm_array_form(): + assert (x*z).array_form == ((Symbol('x'), 1), (Symbol('z'), 1)) + assert (x**2*z*y*x**-2).array_form == \ + ((Symbol('x'), 2), (Symbol('z'), 1), (Symbol('y'), 1), (Symbol('x'), -2)) + assert (x**-2*y**-1).array_form == ((Symbol('x'), -2), (Symbol('y'), -1)) + + +def test_FreeGroupElm_letter_form(): + assert (x**3).letter_form == (Symbol('x'), Symbol('x'), Symbol('x')) + assert (x**2*z**-2*x).letter_form == \ + (Symbol('x'), Symbol('x'), -Symbol('z'), -Symbol('z'), Symbol('x')) + + +def test_FreeGroupElm_ext_rep(): + assert (x**2*z**-2*x).ext_rep == \ + (Symbol('x'), 2, Symbol('z'), -2, Symbol('x'), 1) + assert (x**-2*y**-1).ext_rep == (Symbol('x'), -2, Symbol('y'), -1) + assert (x*z).ext_rep == (Symbol('x'), 1, Symbol('z'), 1) + + +def test_FreeGroupElm__mul__pow__(): + x1 = x.group.dtype(((Symbol('x'), 1),)) + assert x**2 == x1*x + + assert (x**2*y*x**-2)**4 == x**2*y**4*x**-2 + assert (x**2)**2 == x**4 + assert (x**-1)**-1 == x + assert (x**-1)**0 == F.identity + assert (y**2)**-2 == y**-4 + + assert x**2*x**-1 == x + assert x**2*y**2*y**-1 == x**2*y + assert x*x**-1 == F.identity + + assert x/x == F.identity + assert x/x**2 == x**-1 + assert (x**2*y)/(x**2*y**-1) == x**2*y**2*x**-2 + assert (x**2*y)/(y**-1*x**2) == x**2*y*x**-2*y + + assert x*(x**-1*y*z*y**-1) == y*z*y**-1 + assert x**2*(x**-2*y**-1*z**2*y) == y**-1*z**2*y + + a = F.identity + for n in range(10): + assert a == x**n + assert a**-1 == x**-n + a *= x + + +def test_FreeGroupElm__len__(): + assert len(x**5*y*x**2*y**-4*x) == 13 + assert len(x**17) == 17 + assert len(y**0) == 0 + + +def test_FreeGroupElm_comparison(): + assert not (x*y == y*x) + assert x**0 == y**0 + + assert x**2 < y**3 + assert not x**3 < y**2 + assert x*y < x**2*y + assert x**2*y**2 < y**4 + assert not y**4 < y**-4 + assert not y**4 < x**-4 + assert y**-2 < y**2 + + assert x**2 <= y**2 + assert x**2 <= x**2 + + assert not y*z > z*y + assert x > x**-1 + + assert not x**2 >= y**2 + + +def test_FreeGroupElm_syllables(): + w = x**5*y*x**2*y**-4*x + assert w.number_syllables() == 5 + assert w.exponent_syllable(2) == 2 + assert w.generator_syllable(3) == Symbol('y') + assert w.sub_syllables(1, 2) == y + assert w.sub_syllables(3, 3) == F.identity + + +def test_FreeGroup_exponents(): + w1 = x**2*y**3 + assert w1.exponent_sum(x) == 2 + assert w1.exponent_sum(x**-1) == -2 + assert w1.generator_count(x) == 2 + + w2 = x**2*y**4*x**-3 + assert w2.exponent_sum(x) == -1 + assert w2.generator_count(x) == 5 + + +def test_FreeGroup_generators(): + assert (x**2*y**4*z**-1).contains_generators() == {x, y, z} + assert (x**-1*y**3).contains_generators() == {x, y} + + +def test_FreeGroupElm_words(): + w = x**5*y*x**2*y**-4*x + assert w.subword(2, 6) == x**3*y + assert w.subword(3, 2) == F.identity + assert w.subword(6, 10) == x**2*y**-2 + + assert w.substituted_word(0, 7, y**-1) == y**-1*x*y**-4*x + assert w.substituted_word(0, 7, y**2*x) == y**2*x**2*y**-4*x diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_galois.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_galois.py new file mode 100644 index 0000000000000000000000000000000000000000..0d2ac29a846db88444d275b72a85ce3debaeaf05 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_galois.py @@ -0,0 +1,82 @@ +"""Test groups defined by the galois module. """ + +from sympy.combinatorics.galois import ( + S4TransitiveSubgroups, S5TransitiveSubgroups, S6TransitiveSubgroups, + find_transitive_subgroups_of_S6, +) +from sympy.combinatorics.homomorphisms import is_isomorphic +from sympy.combinatorics.named_groups import ( + SymmetricGroup, AlternatingGroup, CyclicGroup, +) + + +def test_four_group(): + G = S4TransitiveSubgroups.V.get_perm_group() + A4 = AlternatingGroup(4) + assert G.is_subgroup(A4) + assert G.degree == 4 + assert G.is_transitive() + assert G.order() == 4 + assert not G.is_cyclic + + +def test_M20(): + G = S5TransitiveSubgroups.M20.get_perm_group() + S5 = SymmetricGroup(5) + A5 = AlternatingGroup(5) + assert G.is_subgroup(S5) + assert not G.is_subgroup(A5) + assert G.degree == 5 + assert G.is_transitive() + assert G.order() == 20 + + +# Setting this True means that for each of the transitive subgroups of S6, +# we run a test not only on the fixed representation, but also on one freshly +# generated by the search procedure. +INCLUDE_SEARCH_REPS = False +S6_randomized = {} +if INCLUDE_SEARCH_REPS: + S6_randomized = find_transitive_subgroups_of_S6(*list(S6TransitiveSubgroups)) + + +def get_versions_of_S6_subgroup(name): + vers = [name.get_perm_group()] + if INCLUDE_SEARCH_REPS: + vers.append(S6_randomized[name]) + return vers + + +def test_S6_transitive_subgroups(): + """ + Test enough characteristics to distinguish all 16 transitive subgroups. + """ + ts = S6TransitiveSubgroups + A6 = AlternatingGroup(6) + for name, alt, order, is_isom, not_isom in [ + (ts.C6, False, 6, CyclicGroup(6), None), + (ts.S3, False, 6, SymmetricGroup(3), None), + (ts.D6, False, 12, None, None), + (ts.A4, True, 12, None, None), + (ts.G18, False, 18, None, None), + (ts.A4xC2, False, 24, None, SymmetricGroup(4)), + (ts.S4m, False, 24, SymmetricGroup(4), None), + (ts.S4p, True, 24, None, None), + (ts.G36m, False, 36, None, None), + (ts.G36p, True, 36, None, None), + (ts.S4xC2, False, 48, None, None), + (ts.PSL2F5, True, 60, None, None), + (ts.G72, False, 72, None, None), + (ts.PGL2F5, False, 120, None, None), + (ts.A6, True, 360, None, None), + (ts.S6, False, 720, None, None), + ]: + for G in get_versions_of_S6_subgroup(name): + assert G.is_transitive() + assert G.degree == 6 + assert G.is_subgroup(A6) is alt + assert G.order() == order + if is_isom: + assert is_isomorphic(G, is_isom) + if not_isom: + assert not is_isomorphic(G, not_isom) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_generators.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_generators.py new file mode 100644 index 0000000000000000000000000000000000000000..795ef8f08f6ec212879f528c6a0c2f0bd73037f0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_generators.py @@ -0,0 +1,105 @@ +from sympy.combinatorics.generators import symmetric, cyclic, alternating, \ + dihedral, rubik +from sympy.combinatorics.permutations import Permutation +from sympy.testing.pytest import raises + +def test_generators(): + + assert list(cyclic(6)) == [ + Permutation([0, 1, 2, 3, 4, 5]), + Permutation([1, 2, 3, 4, 5, 0]), + Permutation([2, 3, 4, 5, 0, 1]), + Permutation([3, 4, 5, 0, 1, 2]), + Permutation([4, 5, 0, 1, 2, 3]), + Permutation([5, 0, 1, 2, 3, 4])] + + assert list(cyclic(10)) == [ + Permutation([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), + Permutation([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + Permutation([2, 3, 4, 5, 6, 7, 8, 9, 0, 1]), + Permutation([3, 4, 5, 6, 7, 8, 9, 0, 1, 2]), + Permutation([4, 5, 6, 7, 8, 9, 0, 1, 2, 3]), + Permutation([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]), + Permutation([6, 7, 8, 9, 0, 1, 2, 3, 4, 5]), + Permutation([7, 8, 9, 0, 1, 2, 3, 4, 5, 6]), + Permutation([8, 9, 0, 1, 2, 3, 4, 5, 6, 7]), + Permutation([9, 0, 1, 2, 3, 4, 5, 6, 7, 8])] + + assert list(alternating(4)) == [ + Permutation([0, 1, 2, 3]), + Permutation([0, 2, 3, 1]), + Permutation([0, 3, 1, 2]), + Permutation([1, 0, 3, 2]), + Permutation([1, 2, 0, 3]), + Permutation([1, 3, 2, 0]), + Permutation([2, 0, 1, 3]), + Permutation([2, 1, 3, 0]), + Permutation([2, 3, 0, 1]), + Permutation([3, 0, 2, 1]), + Permutation([3, 1, 0, 2]), + Permutation([3, 2, 1, 0])] + + assert list(symmetric(3)) == [ + Permutation([0, 1, 2]), + Permutation([0, 2, 1]), + Permutation([1, 0, 2]), + Permutation([1, 2, 0]), + Permutation([2, 0, 1]), + Permutation([2, 1, 0])] + + assert list(symmetric(4)) == [ + Permutation([0, 1, 2, 3]), + Permutation([0, 1, 3, 2]), + Permutation([0, 2, 1, 3]), + Permutation([0, 2, 3, 1]), + Permutation([0, 3, 1, 2]), + Permutation([0, 3, 2, 1]), + Permutation([1, 0, 2, 3]), + Permutation([1, 0, 3, 2]), + Permutation([1, 2, 0, 3]), + Permutation([1, 2, 3, 0]), + Permutation([1, 3, 0, 2]), + Permutation([1, 3, 2, 0]), + Permutation([2, 0, 1, 3]), + Permutation([2, 0, 3, 1]), + Permutation([2, 1, 0, 3]), + Permutation([2, 1, 3, 0]), + Permutation([2, 3, 0, 1]), + Permutation([2, 3, 1, 0]), + Permutation([3, 0, 1, 2]), + Permutation([3, 0, 2, 1]), + Permutation([3, 1, 0, 2]), + Permutation([3, 1, 2, 0]), + Permutation([3, 2, 0, 1]), + Permutation([3, 2, 1, 0])] + + assert list(dihedral(1)) == [ + Permutation([0, 1]), Permutation([1, 0])] + + assert list(dihedral(2)) == [ + Permutation([0, 1, 2, 3]), + Permutation([1, 0, 3, 2]), + Permutation([2, 3, 0, 1]), + Permutation([3, 2, 1, 0])] + + assert list(dihedral(3)) == [ + Permutation([0, 1, 2]), + Permutation([2, 1, 0]), + Permutation([1, 2, 0]), + Permutation([0, 2, 1]), + Permutation([2, 0, 1]), + Permutation([1, 0, 2])] + + assert list(dihedral(5)) == [ + Permutation([0, 1, 2, 3, 4]), + Permutation([4, 3, 2, 1, 0]), + Permutation([1, 2, 3, 4, 0]), + Permutation([0, 4, 3, 2, 1]), + Permutation([2, 3, 4, 0, 1]), + Permutation([1, 0, 4, 3, 2]), + Permutation([3, 4, 0, 1, 2]), + Permutation([2, 1, 0, 4, 3]), + Permutation([4, 0, 1, 2, 3]), + Permutation([3, 2, 1, 0, 4])] + + raises(ValueError, lambda: rubik(1)) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_graycode.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_graycode.py new file mode 100644 index 0000000000000000000000000000000000000000..a754a3c401b07c9c12cb9bdeeefdfc94f6cb8b5c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_graycode.py @@ -0,0 +1,72 @@ +from sympy.combinatorics.graycode import (GrayCode, bin_to_gray, + random_bitstring, get_subset_from_bitstring, graycode_subsets, + gray_to_bin) +from sympy.testing.pytest import raises + +def test_graycode(): + g = GrayCode(2) + got = [] + for i in g.generate_gray(): + if i.startswith('0'): + g.skip() + got.append(i) + assert got == '00 11 10'.split() + a = GrayCode(6) + assert a.current == '0'*6 + assert a.rank == 0 + assert len(list(a.generate_gray())) == 64 + codes = ['011001', '011011', '011010', + '011110', '011111', '011101', '011100', '010100', '010101', '010111', + '010110', '010010', '010011', '010001', '010000', '110000', '110001', + '110011', '110010', '110110', '110111', '110101', '110100', '111100', + '111101', '111111', '111110', '111010', '111011', '111001', '111000', + '101000', '101001', '101011', '101010', '101110', '101111', '101101', + '101100', '100100', '100101', '100111', '100110', '100010', '100011', + '100001', '100000'] + assert list(a.generate_gray(start='011001')) == codes + assert list( + a.generate_gray(rank=GrayCode(6, start='011001').rank)) == codes + assert a.next().current == '000001' + assert a.next(2).current == '000011' + assert a.next(-1).current == '100000' + + a = GrayCode(5, start='10010') + assert a.rank == 28 + a = GrayCode(6, start='101000') + assert a.rank == 48 + + assert GrayCode(6, rank=4).current == '000110' + assert GrayCode(6, rank=4).rank == 4 + assert [GrayCode(4, start=s).rank for s in + GrayCode(4).generate_gray()] == [0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15] + a = GrayCode(15, rank=15) + assert a.current == '000000000001000' + + assert bin_to_gray('111') == '100' + + a = random_bitstring(5) + assert type(a) is str + assert len(a) == 5 + assert all(i in ['0', '1'] for i in a) + + assert get_subset_from_bitstring( + ['a', 'b', 'c', 'd'], '0011') == ['c', 'd'] + assert get_subset_from_bitstring('abcd', '1001') == ['a', 'd'] + assert list(graycode_subsets(['a', 'b', 'c'])) == \ + [[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], + ['a', 'c'], ['a']] + + raises(ValueError, lambda: GrayCode(0)) + raises(ValueError, lambda: GrayCode(2.2)) + raises(ValueError, lambda: GrayCode(2, start=[1, 1, 0])) + raises(ValueError, lambda: GrayCode(2, rank=2.5)) + raises(ValueError, lambda: get_subset_from_bitstring(['c', 'a', 'c'], '1100')) + raises(ValueError, lambda: list(GrayCode(3).generate_gray(start="1111"))) + + +def test_live_issue_117(): + assert bin_to_gray('0100') == '0110' + assert bin_to_gray('0101') == '0111' + for bits in ('0100', '0101'): + assert gray_to_bin(bin_to_gray(bits)) == bits diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_group_constructs.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_group_constructs.py new file mode 100644 index 0000000000000000000000000000000000000000..d0f7d6394bbc2e285650ea95d36be8e2ed5ea69e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_group_constructs.py @@ -0,0 +1,15 @@ +from sympy.combinatorics.group_constructs import DirectProduct +from sympy.combinatorics.named_groups import CyclicGroup, DihedralGroup + + +def test_direct_product_n(): + C = CyclicGroup(4) + D = DihedralGroup(4) + G = DirectProduct(C, C, C) + assert G.order() == 64 + assert G.degree == 12 + assert len(G.orbits()) == 3 + assert G.is_abelian is True + H = DirectProduct(D, C) + assert H.order() == 32 + assert H.is_abelian is False diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_group_numbers.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_group_numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..743f1dcc8b642c19706687eeeddf6c9070b59166 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_group_numbers.py @@ -0,0 +1,110 @@ +from sympy.combinatorics.group_numbers import (is_nilpotent_number, + is_abelian_number, is_cyclic_number, _holder_formula, groups_count) +from sympy.ntheory.factor_ import factorint +from sympy.ntheory.generate import prime +from sympy.testing.pytest import raises +from sympy import randprime + + +def test_is_nilpotent_number(): + assert is_nilpotent_number(21) == False + assert is_nilpotent_number(randprime(1, 30)**12) == True + raises(ValueError, lambda: is_nilpotent_number(-5)) + + A056867 = [1, 2, 3, 4, 5, 7, 8, 9, 11, 13, 15, 16, 17, 19, + 23, 25, 27, 29, 31, 32, 33, 35, 37, 41, 43, 45, + 47, 49, 51, 53, 59, 61, 64, 65, 67, 69, 71, 73, + 77, 79, 81, 83, 85, 87, 89, 91, 95, 97, 99] + for n in range(1, 100): + assert is_nilpotent_number(n) == (n in A056867) + + +def test_is_abelian_number(): + assert is_abelian_number(4) == True + assert is_abelian_number(randprime(1, 2000)**2) == True + assert is_abelian_number(randprime(1000, 100000)) == True + assert is_abelian_number(60) == False + assert is_abelian_number(24) == False + raises(ValueError, lambda: is_abelian_number(-5)) + + A051532 = [1, 2, 3, 4, 5, 7, 9, 11, 13, 15, 17, 19, 23, 25, + 29, 31, 33, 35, 37, 41, 43, 45, 47, 49, 51, 53, + 59, 61, 65, 67, 69, 71, 73, 77, 79, 83, 85, 87, + 89, 91, 95, 97, 99] + for n in range(1, 100): + assert is_abelian_number(n) == (n in A051532) + + +A003277 = [1, 2, 3, 5, 7, 11, 13, 15, 17, 19, 23, 29, + 31, 33, 35, 37, 41, 43, 47, 51, 53, 59, 61, + 65, 67, 69, 71, 73, 77, 79, 83, 85, 87, 89, + 91, 95, 97] + + +def test_is_cyclic_number(): + assert is_cyclic_number(15) == True + assert is_cyclic_number(randprime(1, 2000)**2) == False + assert is_cyclic_number(randprime(1000, 100000)) == True + assert is_cyclic_number(4) == False + raises(ValueError, lambda: is_cyclic_number(-5)) + + for n in range(1, 100): + assert is_cyclic_number(n) == (n in A003277) + + +def test_holder_formula(): + # semiprime + assert _holder_formula({3, 5}) == 1 + assert _holder_formula({5, 11}) == 2 + # n in A003277 is always 1 + for n in A003277: + assert _holder_formula(set(factorint(n).keys())) == 1 + # otherwise + assert _holder_formula({2, 3, 5, 7}) == 12 + + +def test_groups_count(): + A000001 = [0, 1, 1, 1, 2, 1, 2, 1, 5, 2, 2, 1, 5, 1, + 2, 1, 14, 1, 5, 1, 5, 2, 2, 1, 15, 2, 2, + 5, 4, 1, 4, 1, 51, 1, 2, 1, 14, 1, 2, 2, + 14, 1, 6, 1, 4, 2, 2, 1, 52, 2, 5, 1, 5, + 1, 15, 2, 13, 2, 2, 1, 13, 1, 2, 4, 267, + 1, 4, 1, 5, 1, 4, 1, 50, 1, 2, 3, 4, 1, + 6, 1, 52, 15, 2, 1, 15, 1, 2, 1, 12, 1, + 10, 1, 4, 2] + for n in range(1, len(A000001)): + try: + assert groups_count(n) == A000001[n] + except ValueError: + pass + + A000679 = [1, 1, 2, 5, 14, 51, 267, 2328, 56092, 10494213, 49487367289] + for e in range(1, len(A000679)): + assert groups_count(2**e) == A000679[e] + + A090091 = [1, 1, 2, 5, 15, 67, 504, 9310, 1396077, 5937876645] + for e in range(1, len(A090091)): + assert groups_count(3**e) == A090091[e] + + A090130 = [1, 1, 2, 5, 15, 77, 684, 34297] + for e in range(1, len(A090130)): + assert groups_count(5**e) == A090130[e] + + A090140 = [1, 1, 2, 5, 15, 83, 860, 113147] + for e in range(1, len(A090140)): + assert groups_count(7**e) == A090140[e] + + A232105 = [51, 67, 77, 83, 87, 97, 101, 107, 111, 125, 131, + 145, 149, 155, 159, 173, 183, 193, 203, 207, 217] + for i in range(len(A232105)): + assert groups_count(prime(i+1)**5) == A232105[i] + + A232106 = [267, 504, 684, 860, 1192, 1476, 1944, 2264, 2876, + 4068, 4540, 6012, 7064, 7664, 8852, 10908, 13136] + for i in range(len(A232106)): + assert groups_count(prime(i+1)**6) == A232106[i] + + A232107 = [2328, 9310, 34297, 113147, 750735, 1600573, + 5546909, 9380741, 23316851, 71271069, 98488755] + for i in range(len(A232107)): + assert groups_count(prime(i+1)**7) == A232107[i] diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_homomorphisms.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_homomorphisms.py new file mode 100644 index 0000000000000000000000000000000000000000..0936bbddf46a16dccdfbaebda8d1c675c131f05a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_homomorphisms.py @@ -0,0 +1,114 @@ +from sympy.combinatorics import Permutation +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.combinatorics.homomorphisms import homomorphism, group_isomorphism, is_isomorphic +from sympy.combinatorics.free_groups import free_group +from sympy.combinatorics.fp_groups import FpGroup +from sympy.combinatorics.named_groups import AlternatingGroup, DihedralGroup, CyclicGroup +from sympy.testing.pytest import raises + +def test_homomorphism(): + # FpGroup -> PermutationGroup + F, a, b = free_group("a, b") + G = FpGroup(F, [a**3, b**3, (a*b)**2]) + + c = Permutation(3)(0, 1, 2) + d = Permutation(3)(1, 2, 3) + A = AlternatingGroup(4) + T = homomorphism(G, A, [a, b], [c, d]) + assert T(a*b**2*a**-1) == c*d**2*c**-1 + assert T.is_isomorphism() + assert T(T.invert(Permutation(3)(0, 2, 3))) == Permutation(3)(0, 2, 3) + + T = homomorphism(G, AlternatingGroup(4), G.generators) + assert T.is_trivial() + assert T.kernel().order() == G.order() + + E, e = free_group("e") + G = FpGroup(E, [e**8]) + P = PermutationGroup([Permutation(0, 1, 2, 3), Permutation(0, 2)]) + T = homomorphism(G, P, [e], [Permutation(0, 1, 2, 3)]) + assert T.image().order() == 4 + assert T(T.invert(Permutation(0, 2)(1, 3))) == Permutation(0, 2)(1, 3) + + T = homomorphism(E, AlternatingGroup(4), E.generators, [c]) + assert T.invert(c**2) == e**-1 #order(c) == 3 so c**2 == c**-1 + + # FreeGroup -> FreeGroup + T = homomorphism(F, E, [a], [e]) + assert T(a**-2*b**4*a**2).is_identity + + # FreeGroup -> FpGroup + G = FpGroup(F, [a*b*a**-1*b**-1]) + T = homomorphism(F, G, F.generators, G.generators) + assert T.invert(a**-1*b**-1*a**2) == a*b**-1 + + # PermutationGroup -> PermutationGroup + D = DihedralGroup(8) + p = Permutation(0, 1, 2, 3, 4, 5, 6, 7) + P = PermutationGroup(p) + T = homomorphism(P, D, [p], [p]) + assert T.is_injective() + assert not T.is_isomorphism() + assert T.invert(p**3) == p**3 + + T2 = homomorphism(F, P, [F.generators[0]], P.generators) + T = T.compose(T2) + assert T.domain == F + assert T.codomain == D + assert T(a*b) == p + + D3 = DihedralGroup(3) + T = homomorphism(D3, D3, D3.generators, D3.generators) + assert T.is_isomorphism() + + +def test_isomorphisms(): + + F, a, b = free_group("a, b") + E, c, d = free_group("c, d") + # Infinite groups with differently ordered relators. + G = FpGroup(F, [a**2, b**3]) + H = FpGroup(F, [b**3, a**2]) + assert is_isomorphic(G, H) + + # Trivial Case + # FpGroup -> FpGroup + H = FpGroup(F, [a**3, b**3, (a*b)**2]) + F, c, d = free_group("c, d") + G = FpGroup(F, [c**3, d**3, (c*d)**2]) + check, T = group_isomorphism(G, H) + assert check + assert T(c**3*d**2) == a**3*b**2 + + # FpGroup -> PermutationGroup + # FpGroup is converted to the equivalent isomorphic group. + F, a, b = free_group("a, b") + G = FpGroup(F, [a**3, b**3, (a*b)**2]) + H = AlternatingGroup(4) + check, T = group_isomorphism(G, H) + assert check + assert T(b*a*b**-1*a**-1*b**-1) == Permutation(0, 2, 3) + assert T(b*a*b*a**-1*b**-1) == Permutation(0, 3, 2) + + # PermutationGroup -> PermutationGroup + D = DihedralGroup(8) + p = Permutation(0, 1, 2, 3, 4, 5, 6, 7) + P = PermutationGroup(p) + assert not is_isomorphic(D, P) + + A = CyclicGroup(5) + B = CyclicGroup(7) + assert not is_isomorphic(A, B) + + # Two groups of the same prime order are isomorphic to each other. + G = FpGroup(F, [a, b**5]) + H = CyclicGroup(5) + assert G.order() == H.order() + assert is_isomorphic(G, H) + + +def test_check_homomorphism(): + a = Permutation(1,2,3,4) + b = Permutation(1,3) + G = PermutationGroup([a, b]) + raises(ValueError, lambda: homomorphism(G, G, [a], [a])) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_named_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_named_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..59bcb6ef3f020335de76d7a72152a0b58cbc6976 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_named_groups.py @@ -0,0 +1,70 @@ +from sympy.combinatorics.named_groups import (SymmetricGroup, CyclicGroup, + DihedralGroup, AlternatingGroup, + AbelianGroup, RubikGroup) +from sympy.testing.pytest import raises + + +def test_SymmetricGroup(): + G = SymmetricGroup(5) + elements = list(G.generate()) + assert (G.generators[0]).size == 5 + assert len(elements) == 120 + assert G.is_solvable is False + assert G.is_abelian is False + assert G.is_nilpotent is False + assert G.is_transitive() is True + H = SymmetricGroup(1) + assert H.order() == 1 + L = SymmetricGroup(2) + assert L.order() == 2 + + +def test_CyclicGroup(): + G = CyclicGroup(10) + elements = list(G.generate()) + assert len(elements) == 10 + assert (G.derived_subgroup()).order() == 1 + assert G.is_abelian is True + assert G.is_solvable is True + assert G.is_nilpotent is True + H = CyclicGroup(1) + assert H.order() == 1 + L = CyclicGroup(2) + assert L.order() == 2 + + +def test_DihedralGroup(): + G = DihedralGroup(6) + elements = list(G.generate()) + assert len(elements) == 12 + assert G.is_transitive() is True + assert G.is_abelian is False + assert G.is_solvable is True + assert G.is_nilpotent is False + H = DihedralGroup(1) + assert H.order() == 2 + L = DihedralGroup(2) + assert L.order() == 4 + assert L.is_abelian is True + assert L.is_nilpotent is True + + +def test_AlternatingGroup(): + G = AlternatingGroup(5) + elements = list(G.generate()) + assert len(elements) == 60 + assert [perm.is_even for perm in elements] == [True]*60 + H = AlternatingGroup(1) + assert H.order() == 1 + L = AlternatingGroup(2) + assert L.order() == 1 + + +def test_AbelianGroup(): + A = AbelianGroup(3, 3, 3) + assert A.order() == 27 + assert A.is_abelian is True + + +def test_RubikGroup(): + raises(ValueError, lambda: RubikGroup(1)) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_partitions.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_partitions.py new file mode 100644 index 0000000000000000000000000000000000000000..32e70e53a53aadbb17c8292bbef8f52d1144d6e0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_partitions.py @@ -0,0 +1,118 @@ +from sympy.core.sorting import ordered, default_sort_key +from sympy.combinatorics.partitions import (Partition, IntegerPartition, + RGS_enum, RGS_unrank, RGS_rank, + random_integer_partition) +from sympy.testing.pytest import raises +from sympy.utilities.iterables import partitions +from sympy.sets.sets import Set, FiniteSet + + +def test_partition_constructor(): + raises(ValueError, lambda: Partition([1, 1, 2])) + raises(ValueError, lambda: Partition([1, 2, 3], [2, 3, 4])) + raises(ValueError, lambda: Partition(1, 2, 3)) + raises(ValueError, lambda: Partition(*list(range(3)))) + + assert Partition([1, 2, 3], [4, 5]) == Partition([4, 5], [1, 2, 3]) + assert Partition({1, 2, 3}, {4, 5}) == Partition([1, 2, 3], [4, 5]) + + a = FiniteSet(1, 2, 3) + b = FiniteSet(4, 5) + assert Partition(a, b) == Partition([1, 2, 3], [4, 5]) + assert Partition({a, b}) == Partition(FiniteSet(a, b)) + assert Partition({a, b}) != Partition(a, b) + +def test_partition(): + from sympy.abc import x + + a = Partition([1, 2, 3], [4]) + b = Partition([1, 2], [3, 4]) + c = Partition([x]) + l = [a, b, c] + l.sort(key=default_sort_key) + assert l == [c, a, b] + l.sort(key=lambda w: default_sort_key(w, order='rev-lex')) + assert l == [c, a, b] + + assert (a == b) is False + assert a <= b + assert (a > b) is False + assert a != b + assert a < b + + assert (a + 2).partition == [[1, 2], [3, 4]] + assert (b - 1).partition == [[1, 2, 4], [3]] + + assert (a - 1).partition == [[1, 2, 3, 4]] + assert (a + 1).partition == [[1, 2, 4], [3]] + assert (b + 1).partition == [[1, 2], [3], [4]] + + assert a.rank == 1 + assert b.rank == 3 + + assert a.RGS == (0, 0, 0, 1) + assert b.RGS == (0, 0, 1, 1) + + +def test_integer_partition(): + # no zeros in partition + raises(ValueError, lambda: IntegerPartition(list(range(3)))) + # check fails since 1 + 2 != 100 + raises(ValueError, lambda: IntegerPartition(100, list(range(1, 3)))) + a = IntegerPartition(8, [1, 3, 4]) + b = a.next_lex() + c = IntegerPartition([1, 3, 4]) + d = IntegerPartition(8, {1: 3, 3: 1, 2: 1}) + assert a == c + assert a.integer == d.integer + assert a.conjugate == [3, 2, 2, 1] + assert (a == b) is False + assert a <= b + assert (a > b) is False + assert a != b + + for i in range(1, 11): + next = set() + prev = set() + a = IntegerPartition([i]) + ans = {IntegerPartition(p) for p in partitions(i)} + n = len(ans) + for j in range(n): + next.add(a) + a = a.next_lex() + IntegerPartition(i, a.partition) # check it by giving i + for j in range(n): + prev.add(a) + a = a.prev_lex() + IntegerPartition(i, a.partition) # check it by giving i + assert next == ans + assert prev == ans + + assert IntegerPartition([1, 2, 3]).as_ferrers() == '###\n##\n#' + assert IntegerPartition([1, 1, 3]).as_ferrers('o') == 'ooo\no\no' + assert str(IntegerPartition([1, 1, 3])) == '[3, 1, 1]' + assert IntegerPartition([1, 1, 3]).partition == [3, 1, 1] + + raises(ValueError, lambda: random_integer_partition(-1)) + assert random_integer_partition(1) == [1] + assert random_integer_partition(10, seed=[1, 3, 2, 1, 5, 1] + ) == [5, 2, 1, 1, 1] + + +def test_rgs(): + raises(ValueError, lambda: RGS_unrank(-1, 3)) + raises(ValueError, lambda: RGS_unrank(3, 0)) + raises(ValueError, lambda: RGS_unrank(10, 1)) + + raises(ValueError, lambda: Partition.from_rgs(list(range(3)), list(range(2)))) + raises(ValueError, lambda: Partition.from_rgs(list(range(1, 3)), list(range(2)))) + assert RGS_enum(-1) == 0 + assert RGS_enum(1) == 1 + assert RGS_unrank(7, 5) == [0, 0, 1, 0, 2] + assert RGS_unrank(23, 14) == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2] + assert RGS_rank(RGS_unrank(40, 100)) == 40 + +def test_ordered_partition_9608(): + a = Partition([1, 2, 3], [4]) + b = Partition([1, 2], [3, 4]) + assert list(ordered([a,b], Set._infimum_key)) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_pc_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_pc_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..b0c146279921e1e6499534fe9e33b993348d1503 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_pc_groups.py @@ -0,0 +1,87 @@ +from sympy.combinatorics.permutations import Permutation +from sympy.combinatorics.named_groups import SymmetricGroup, AlternatingGroup, DihedralGroup +from sympy.matrices import Matrix + +def test_pc_presentation(): + Groups = [SymmetricGroup(3), SymmetricGroup(4), SymmetricGroup(9).sylow_subgroup(3), + SymmetricGroup(9).sylow_subgroup(2), SymmetricGroup(8).sylow_subgroup(2), DihedralGroup(10)] + + S = SymmetricGroup(125).sylow_subgroup(5) + G = S.derived_series()[2] + Groups.append(G) + + G = SymmetricGroup(25).sylow_subgroup(5) + Groups.append(G) + + S = SymmetricGroup(11**2).sylow_subgroup(11) + G = S.derived_series()[2] + Groups.append(G) + + for G in Groups: + PcGroup = G.polycyclic_group() + collector = PcGroup.collector + pc_presentation = collector.pc_presentation + + pcgs = PcGroup.pcgs + free_group = collector.free_group + free_to_perm = {} + for s, g in zip(free_group.symbols, pcgs): + free_to_perm[s] = g + + for k, v in pc_presentation.items(): + k_array = k.array_form + if v != (): + v_array = v.array_form + + lhs = Permutation() + for gen in k_array: + s = gen[0] + e = gen[1] + lhs = lhs*free_to_perm[s]**e + + if v == (): + assert lhs.is_identity + continue + + rhs = Permutation() + for gen in v_array: + s = gen[0] + e = gen[1] + rhs = rhs*free_to_perm[s]**e + + assert lhs == rhs + + +def test_exponent_vector(): + + Groups = [SymmetricGroup(3), SymmetricGroup(4), SymmetricGroup(9).sylow_subgroup(3), + SymmetricGroup(9).sylow_subgroup(2), SymmetricGroup(8).sylow_subgroup(2)] + + for G in Groups: + PcGroup = G.polycyclic_group() + collector = PcGroup.collector + + pcgs = PcGroup.pcgs + # free_group = collector.free_group + + for gen in G.generators: + exp = collector.exponent_vector(gen) + g = Permutation() + for i in range(len(exp)): + g = g*pcgs[i]**exp[i] if exp[i] else g + assert g == gen + + +def test_induced_pcgs(): + G = [SymmetricGroup(9).sylow_subgroup(3), SymmetricGroup(20).sylow_subgroup(2), AlternatingGroup(4), + DihedralGroup(4), DihedralGroup(10), DihedralGroup(9), SymmetricGroup(3), SymmetricGroup(4)] + + for g in G: + PcGroup = g.polycyclic_group() + collector = PcGroup.collector + gens = list(g.generators) + ipcgs = collector.induced_pcgs(gens) + m = [] + for i in ipcgs: + m.append(collector.exponent_vector(i)) + assert Matrix(m).is_upper diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_perm_groups.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_perm_groups.py new file mode 100644 index 0000000000000000000000000000000000000000..763b8fb0ae357500d68c29fe1c9e6b156e224949 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_perm_groups.py @@ -0,0 +1,1243 @@ +from sympy.core.containers import Tuple +from sympy.combinatorics.generators import rubik_cube_generators +from sympy.combinatorics.homomorphisms import is_isomorphic +from sympy.combinatorics.named_groups import SymmetricGroup, CyclicGroup,\ + DihedralGroup, AlternatingGroup, AbelianGroup, RubikGroup +from sympy.combinatorics.perm_groups import (PermutationGroup, + _orbit_transversal, Coset, SymmetricPermutationGroup) +from sympy.combinatorics.permutations import Permutation +from sympy.combinatorics.polyhedron import tetrahedron as Tetra, cube +from sympy.combinatorics.testutil import _verify_bsgs, _verify_centralizer,\ + _verify_normal_closure +from sympy.testing.pytest import skip, XFAIL, slow + +rmul = Permutation.rmul + + +def test_has(): + a = Permutation([1, 0]) + G = PermutationGroup([a]) + assert G.is_abelian + a = Permutation([2, 0, 1]) + b = Permutation([2, 1, 0]) + G = PermutationGroup([a, b]) + assert not G.is_abelian + + G = PermutationGroup([a]) + assert G.has(a) + assert not G.has(b) + + a = Permutation([2, 0, 1, 3, 4, 5]) + b = Permutation([0, 2, 1, 3, 4]) + assert PermutationGroup(a, b).degree == \ + PermutationGroup(a, b).degree == 6 + + g = PermutationGroup(Permutation(0, 2, 1)) + assert Tuple(1, g).has(g) + + +def test_generate(): + a = Permutation([1, 0]) + g = list(PermutationGroup([a]).generate()) + assert g == [Permutation([0, 1]), Permutation([1, 0])] + assert len(list(PermutationGroup(Permutation((0, 1))).generate())) == 1 + g = PermutationGroup([a]).generate(method='dimino') + assert list(g) == [Permutation([0, 1]), Permutation([1, 0])] + a = Permutation([2, 0, 1]) + b = Permutation([2, 1, 0]) + G = PermutationGroup([a, b]) + g = G.generate() + v1 = [p.array_form for p in list(g)] + v1.sort() + assert v1 == [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, + 1], [2, 1, 0]] + v2 = list(G.generate(method='dimino', af=True)) + assert v1 == sorted(v2) + a = Permutation([2, 0, 1, 3, 4, 5]) + b = Permutation([2, 1, 3, 4, 5, 0]) + g = PermutationGroup([a, b]).generate(af=True) + assert len(list(g)) == 360 + + +def test_order(): + a = Permutation([2, 0, 1, 3, 4, 5, 6, 7, 8, 9]) + b = Permutation([2, 1, 3, 4, 5, 6, 7, 8, 9, 0]) + g = PermutationGroup([a, b]) + assert g.order() == 1814400 + assert PermutationGroup().order() == 1 + + +def test_equality(): + p_1 = Permutation(0, 1, 3) + p_2 = Permutation(0, 2, 3) + p_3 = Permutation(0, 1, 2) + p_4 = Permutation(0, 1, 3) + g_1 = PermutationGroup(p_1, p_2) + g_2 = PermutationGroup(p_3, p_4) + g_3 = PermutationGroup(p_2, p_1) + g_4 = PermutationGroup(p_1, p_2) + + assert g_1 != g_2 + assert g_1.generators != g_2.generators + assert g_1.equals(g_2) + assert g_1 != g_3 + assert g_1.equals(g_3) + assert g_1 == g_4 + + +def test_stabilizer(): + S = SymmetricGroup(2) + H = S.stabilizer(0) + assert H.generators == [Permutation(1)] + a = Permutation([2, 0, 1, 3, 4, 5]) + b = Permutation([2, 1, 3, 4, 5, 0]) + G = PermutationGroup([a, b]) + G0 = G.stabilizer(0) + assert G0.order() == 60 + + gens_cube = [[1, 3, 5, 7, 0, 2, 4, 6], [1, 3, 0, 2, 5, 7, 4, 6]] + gens = [Permutation(p) for p in gens_cube] + G = PermutationGroup(gens) + G2 = G.stabilizer(2) + assert G2.order() == 6 + G2_1 = G2.stabilizer(1) + v = list(G2_1.generate(af=True)) + assert v == [[0, 1, 2, 3, 4, 5, 6, 7], [3, 1, 2, 0, 7, 5, 6, 4]] + + gens = ( + (1, 2, 0, 4, 5, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), + (0, 1, 2, 3, 4, 5, 19, 6, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 7, 17, 18), + (0, 1, 2, 3, 4, 5, 6, 7, 9, 18, 16, 11, 12, 13, 14, 15, 8, 17, 10, 19)) + gens = [Permutation(p) for p in gens] + G = PermutationGroup(gens) + G2 = G.stabilizer(2) + assert G2.order() == 181440 + S = SymmetricGroup(3) + assert [G.order() for G in S.basic_stabilizers] == [6, 2] + + +def test_center(): + # the center of the dihedral group D_n is of order 2 for even n + for i in (4, 6, 10): + D = DihedralGroup(i) + assert (D.center()).order() == 2 + # the center of the dihedral group D_n is of order 1 for odd n>2 + for i in (3, 5, 7): + D = DihedralGroup(i) + assert (D.center()).order() == 1 + # the center of an abelian group is the group itself + for i in (2, 3, 5): + for j in (1, 5, 7): + for k in (1, 1, 11): + G = AbelianGroup(i, j, k) + assert G.center().is_subgroup(G) + # the center of a nonabelian simple group is trivial + for i in(1, 5, 9): + A = AlternatingGroup(i) + assert (A.center()).order() == 1 + # brute-force verifications + D = DihedralGroup(5) + A = AlternatingGroup(3) + C = CyclicGroup(4) + G.is_subgroup(D*A*C) + assert _verify_centralizer(G, G) + + +def test_centralizer(): + # the centralizer of the trivial group is the entire group + S = SymmetricGroup(2) + assert S.centralizer(Permutation(list(range(2)))).is_subgroup(S) + A = AlternatingGroup(5) + assert A.centralizer(Permutation(list(range(5)))).is_subgroup(A) + # a centralizer in the trivial group is the trivial group itself + triv = PermutationGroup([Permutation([0, 1, 2, 3])]) + D = DihedralGroup(4) + assert triv.centralizer(D).is_subgroup(triv) + # brute-force verifications for centralizers of groups + for i in (4, 5, 6): + S = SymmetricGroup(i) + A = AlternatingGroup(i) + C = CyclicGroup(i) + D = DihedralGroup(i) + for gp in (S, A, C, D): + for gp2 in (S, A, C, D): + if not gp2.is_subgroup(gp): + assert _verify_centralizer(gp, gp2) + # verify the centralizer for all elements of several groups + S = SymmetricGroup(5) + elements = list(S.generate_dimino()) + for element in elements: + assert _verify_centralizer(S, element) + A = AlternatingGroup(5) + elements = list(A.generate_dimino()) + for element in elements: + assert _verify_centralizer(A, element) + D = DihedralGroup(7) + elements = list(D.generate_dimino()) + for element in elements: + assert _verify_centralizer(D, element) + # verify centralizers of small groups within small groups + small = [] + for i in (1, 2, 3): + small.append(SymmetricGroup(i)) + small.append(AlternatingGroup(i)) + small.append(DihedralGroup(i)) + small.append(CyclicGroup(i)) + for gp in small: + for gp2 in small: + if gp.degree == gp2.degree: + assert _verify_centralizer(gp, gp2) + + +def test_coset_rank(): + gens_cube = [[1, 3, 5, 7, 0, 2, 4, 6], [1, 3, 0, 2, 5, 7, 4, 6]] + gens = [Permutation(p) for p in gens_cube] + G = PermutationGroup(gens) + i = 0 + for h in G.generate(af=True): + rk = G.coset_rank(h) + assert rk == i + h1 = G.coset_unrank(rk, af=True) + assert h == h1 + i += 1 + assert G.coset_unrank(48) is None + assert G.coset_unrank(G.coset_rank(gens[0])) == gens[0] + + +def test_coset_factor(): + a = Permutation([0, 2, 1]) + G = PermutationGroup([a]) + c = Permutation([2, 1, 0]) + assert not G.coset_factor(c) + assert G.coset_rank(c) is None + + a = Permutation([2, 0, 1, 3, 4, 5]) + b = Permutation([2, 1, 3, 4, 5, 0]) + g = PermutationGroup([a, b]) + assert g.order() == 360 + d = Permutation([1, 0, 2, 3, 4, 5]) + assert not g.coset_factor(d.array_form) + assert not g.contains(d) + assert Permutation(2) in G + c = Permutation([1, 0, 2, 3, 5, 4]) + v = g.coset_factor(c, True) + tr = g.basic_transversals + p = Permutation.rmul(*[tr[i][v[i]] for i in range(len(g.base))]) + assert p == c + v = g.coset_factor(c) + p = Permutation.rmul(*v) + assert p == c + assert g.contains(c) + G = PermutationGroup([Permutation([2, 1, 0])]) + p = Permutation([1, 0, 2]) + assert G.coset_factor(p) == [] + + +def test_orbits(): + a = Permutation([2, 0, 1]) + b = Permutation([2, 1, 0]) + g = PermutationGroup([a, b]) + assert g.orbit(0) == {0, 1, 2} + assert g.orbits() == [{0, 1, 2}] + assert g.is_transitive() and g.is_transitive(strict=False) + assert g.orbit_transversal(0) == \ + [Permutation( + [0, 1, 2]), Permutation([2, 0, 1]), Permutation([1, 2, 0])] + assert g.orbit_transversal(0, True) == \ + [(0, Permutation([0, 1, 2])), (2, Permutation([2, 0, 1])), + (1, Permutation([1, 2, 0]))] + + G = DihedralGroup(6) + transversal, slps = _orbit_transversal(G.degree, G.generators, 0, True, slp=True) + for i, t in transversal: + slp = slps[i] + w = G.identity + for s in slp: + w = G.generators[s]*w + assert w == t + + a = Permutation(list(range(1, 100)) + [0]) + G = PermutationGroup([a]) + assert [min(o) for o in G.orbits()] == [0] + G = PermutationGroup(rubik_cube_generators()) + assert [min(o) for o in G.orbits()] == [0, 1] + assert not G.is_transitive() and not G.is_transitive(strict=False) + G = PermutationGroup([Permutation(0, 1, 3), Permutation(3)(0, 1)]) + assert not G.is_transitive() and G.is_transitive(strict=False) + assert PermutationGroup( + Permutation(3)).is_transitive(strict=False) is False + + +def test_is_normal(): + gens_s5 = [Permutation(p) for p in [[1, 2, 3, 4, 0], [2, 1, 4, 0, 3]]] + G1 = PermutationGroup(gens_s5) + assert G1.order() == 120 + gens_a5 = [Permutation(p) for p in [[1, 0, 3, 2, 4], [2, 1, 4, 3, 0]]] + G2 = PermutationGroup(gens_a5) + assert G2.order() == 60 + assert G2.is_normal(G1) + gens3 = [Permutation(p) for p in [[2, 1, 3, 0, 4], [1, 2, 0, 3, 4]]] + G3 = PermutationGroup(gens3) + assert not G3.is_normal(G1) + assert G3.order() == 12 + G4 = G1.normal_closure(G3.generators) + assert G4.order() == 60 + gens5 = [Permutation(p) for p in [[1, 2, 3, 0, 4], [1, 2, 0, 3, 4]]] + G5 = PermutationGroup(gens5) + assert G5.order() == 24 + G6 = G1.normal_closure(G5.generators) + assert G6.order() == 120 + assert G1.is_subgroup(G6) + assert not G1.is_subgroup(G4) + assert G2.is_subgroup(G4) + I5 = PermutationGroup(Permutation(4)) + assert I5.is_normal(G5) + assert I5.is_normal(G6, strict=False) + p1 = Permutation([1, 0, 2, 3, 4]) + p2 = Permutation([0, 1, 2, 4, 3]) + p3 = Permutation([3, 4, 2, 1, 0]) + id_ = Permutation([0, 1, 2, 3, 4]) + H = PermutationGroup([p1, p3]) + H_n1 = PermutationGroup([p1, p2]) + H_n2_1 = PermutationGroup(p1) + H_n2_2 = PermutationGroup(p2) + H_id = PermutationGroup(id_) + assert H_n1.is_normal(H) + assert H_n2_1.is_normal(H_n1) + assert H_n2_2.is_normal(H_n1) + assert H_id.is_normal(H_n2_1) + assert H_id.is_normal(H_n1) + assert H_id.is_normal(H) + assert not H_n2_1.is_normal(H) + assert not H_n2_2.is_normal(H) + + +def test_eq(): + a = [[1, 2, 0, 3, 4, 5], [1, 0, 2, 3, 4, 5], [2, 1, 0, 3, 4, 5], [ + 1, 2, 0, 3, 4, 5]] + a = [Permutation(p) for p in a + [[1, 2, 3, 4, 5, 0]]] + g = Permutation([1, 2, 3, 4, 5, 0]) + G1, G2, G3 = [PermutationGroup(x) for x in [a[:2], a[2:4], [g, g**2]]] + assert G1.order() == G2.order() == G3.order() == 6 + assert G1.is_subgroup(G2) + assert not G1.is_subgroup(G3) + G4 = PermutationGroup([Permutation([0, 1])]) + assert not G1.is_subgroup(G4) + assert G4.is_subgroup(G1, 0) + assert PermutationGroup(g, g).is_subgroup(PermutationGroup(g)) + assert SymmetricGroup(3).is_subgroup(SymmetricGroup(4), 0) + assert SymmetricGroup(3).is_subgroup(SymmetricGroup(3)*CyclicGroup(5), 0) + assert not CyclicGroup(5).is_subgroup(SymmetricGroup(3)*CyclicGroup(5), 0) + assert CyclicGroup(3).is_subgroup(SymmetricGroup(3)*CyclicGroup(5), 0) + + +def test_derived_subgroup(): + a = Permutation([1, 0, 2, 4, 3]) + b = Permutation([0, 1, 3, 2, 4]) + G = PermutationGroup([a, b]) + C = G.derived_subgroup() + assert C.order() == 3 + assert C.is_normal(G) + assert C.is_subgroup(G, 0) + assert not G.is_subgroup(C, 0) + gens_cube = [[1, 3, 5, 7, 0, 2, 4, 6], [1, 3, 0, 2, 5, 7, 4, 6]] + gens = [Permutation(p) for p in gens_cube] + G = PermutationGroup(gens) + C = G.derived_subgroup() + assert C.order() == 12 + + +def test_is_solvable(): + a = Permutation([1, 2, 0]) + b = Permutation([1, 0, 2]) + G = PermutationGroup([a, b]) + assert G.is_solvable + G = PermutationGroup([a]) + assert G.is_solvable + a = Permutation([1, 2, 3, 4, 0]) + b = Permutation([1, 0, 2, 3, 4]) + G = PermutationGroup([a, b]) + assert not G.is_solvable + P = SymmetricGroup(10) + S = P.sylow_subgroup(3) + assert S.is_solvable + +def test_rubik1(): + gens = rubik_cube_generators() + gens1 = [gens[-1]] + [p**2 for p in gens[1:]] + G1 = PermutationGroup(gens1) + assert G1.order() == 19508428800 + gens2 = [p**2 for p in gens] + G2 = PermutationGroup(gens2) + assert G2.order() == 663552 + assert G2.is_subgroup(G1, 0) + C1 = G1.derived_subgroup() + assert C1.order() == 4877107200 + assert C1.is_subgroup(G1, 0) + assert not G2.is_subgroup(C1, 0) + + G = RubikGroup(2) + assert G.order() == 3674160 + + +@XFAIL +def test_rubik(): + skip('takes too much time') + G = PermutationGroup(rubik_cube_generators()) + assert G.order() == 43252003274489856000 + G1 = PermutationGroup(G[:3]) + assert G1.order() == 170659735142400 + assert not G1.is_normal(G) + G2 = G.normal_closure(G1.generators) + assert G2.is_subgroup(G) + + +def test_direct_product(): + C = CyclicGroup(4) + D = DihedralGroup(4) + G = C*C*C + assert G.order() == 64 + assert G.degree == 12 + assert len(G.orbits()) == 3 + assert G.is_abelian is True + H = D*C + assert H.order() == 32 + assert H.is_abelian is False + + +def test_orbit_rep(): + G = DihedralGroup(6) + assert G.orbit_rep(1, 3) in [Permutation([2, 3, 4, 5, 0, 1]), + Permutation([4, 3, 2, 1, 0, 5])] + H = CyclicGroup(4)*G + assert H.orbit_rep(1, 5) is False + + +def test_schreier_vector(): + G = CyclicGroup(50) + v = [0]*50 + v[23] = -1 + assert G.schreier_vector(23) == v + H = DihedralGroup(8) + assert H.schreier_vector(2) == [0, 1, -1, 0, 0, 1, 0, 0] + L = SymmetricGroup(4) + assert L.schreier_vector(1) == [1, -1, 0, 0] + + +def test_random_pr(): + D = DihedralGroup(6) + r = 11 + n = 3 + _random_prec_n = {} + _random_prec_n[0] = {'s': 7, 't': 3, 'x': 2, 'e': -1} + _random_prec_n[1] = {'s': 5, 't': 5, 'x': 1, 'e': -1} + _random_prec_n[2] = {'s': 3, 't': 4, 'x': 2, 'e': 1} + D._random_pr_init(r, n, _random_prec_n=_random_prec_n) + assert D._random_gens[11] == [0, 1, 2, 3, 4, 5] + _random_prec = {'s': 2, 't': 9, 'x': 1, 'e': -1} + assert D.random_pr(_random_prec=_random_prec) == \ + Permutation([0, 5, 4, 3, 2, 1]) + + +def test_is_alt_sym(): + G = DihedralGroup(10) + assert G.is_alt_sym() is False + assert G._eval_is_alt_sym_naive() is False + assert G._eval_is_alt_sym_naive(only_alt=True) is False + assert G._eval_is_alt_sym_naive(only_sym=True) is False + + S = SymmetricGroup(10) + assert S._eval_is_alt_sym_naive() is True + assert S._eval_is_alt_sym_naive(only_alt=True) is False + assert S._eval_is_alt_sym_naive(only_sym=True) is True + + N_eps = 10 + _random_prec = {'N_eps': N_eps, + 0: Permutation([[2], [1, 4], [0, 6, 7, 8, 9, 3, 5]]), + 1: Permutation([[1, 8, 7, 6, 3, 5, 2, 9], [0, 4]]), + 2: Permutation([[5, 8], [4, 7], [0, 1, 2, 3, 6, 9]]), + 3: Permutation([[3], [0, 8, 2, 7, 4, 1, 6, 9, 5]]), + 4: Permutation([[8], [4, 7, 9], [3, 6], [0, 5, 1, 2]]), + 5: Permutation([[6], [0, 2, 4, 5, 1, 8, 3, 9, 7]]), + 6: Permutation([[6, 9, 8], [4, 5], [1, 3, 7], [0, 2]]), + 7: Permutation([[4], [0, 2, 9, 1, 3, 8, 6, 5, 7]]), + 8: Permutation([[1, 5, 6, 3], [0, 2, 7, 8, 4, 9]]), + 9: Permutation([[8], [6, 7], [2, 3, 4, 5], [0, 1, 9]])} + assert S.is_alt_sym(_random_prec=_random_prec) is True + + A = AlternatingGroup(10) + assert A._eval_is_alt_sym_naive() is True + assert A._eval_is_alt_sym_naive(only_alt=True) is True + assert A._eval_is_alt_sym_naive(only_sym=True) is False + + _random_prec = {'N_eps': N_eps, + 0: Permutation([[1, 6, 4, 2, 7, 8, 5, 9, 3], [0]]), + 1: Permutation([[1], [0, 5, 8, 4, 9, 2, 3, 6, 7]]), + 2: Permutation([[1, 9, 8, 3, 2, 5], [0, 6, 7, 4]]), + 3: Permutation([[6, 8, 9], [4, 5], [1, 3, 7, 2], [0]]), + 4: Permutation([[8], [5], [4], [2, 6, 9, 3], [1], [0, 7]]), + 5: Permutation([[3, 6], [0, 8, 1, 7, 5, 9, 4, 2]]), + 6: Permutation([[5], [2, 9], [1, 8, 3], [0, 4, 7, 6]]), + 7: Permutation([[1, 8, 4, 7, 2, 3], [0, 6, 9, 5]]), + 8: Permutation([[5, 8, 7], [3], [1, 4, 2, 6], [0, 9]]), + 9: Permutation([[4, 9, 6], [3, 8], [1, 2], [0, 5, 7]])} + assert A.is_alt_sym(_random_prec=_random_prec) is False + + G = PermutationGroup( + Permutation(1, 3, size=8)(0, 2, 4, 6), + Permutation(5, 7, size=8)(0, 2, 4, 6)) + assert G.is_alt_sym() is False + + # Tests for monte-carlo c_n parameter setting, and which guarantees + # to give False. + G = DihedralGroup(10) + assert G._eval_is_alt_sym_monte_carlo() is False + G = DihedralGroup(20) + assert G._eval_is_alt_sym_monte_carlo() is False + + # A dry-running test to check if it looks up for the updated cache. + G = DihedralGroup(6) + G.is_alt_sym() + assert G.is_alt_sym() is False + + +def test_minimal_block(): + D = DihedralGroup(6) + block_system = D.minimal_block([0, 3]) + for i in range(3): + assert block_system[i] == block_system[i + 3] + S = SymmetricGroup(6) + assert S.minimal_block([0, 1]) == [0, 0, 0, 0, 0, 0] + + assert Tetra.pgroup.minimal_block([0, 1]) == [0, 0, 0, 0] + + P1 = PermutationGroup(Permutation(1, 5)(2, 4), Permutation(0, 1, 2, 3, 4, 5)) + P2 = PermutationGroup(Permutation(0, 1, 2, 3, 4, 5), Permutation(1, 5)(2, 4)) + assert P1.minimal_block([0, 2]) == [0, 1, 0, 1, 0, 1] + assert P2.minimal_block([0, 2]) == [0, 1, 0, 1, 0, 1] + + +def test_minimal_blocks(): + P = PermutationGroup(Permutation(1, 5)(2, 4), Permutation(0, 1, 2, 3, 4, 5)) + assert P.minimal_blocks() == [[0, 1, 0, 1, 0, 1], [0, 1, 2, 0, 1, 2]] + + P = SymmetricGroup(5) + assert P.minimal_blocks() == [[0]*5] + + P = PermutationGroup(Permutation(0, 3)) + assert P.minimal_blocks() is False + + +def test_max_div(): + S = SymmetricGroup(10) + assert S.max_div == 5 + + +def test_is_primitive(): + S = SymmetricGroup(5) + assert S.is_primitive() is True + C = CyclicGroup(7) + assert C.is_primitive() is True + + a = Permutation(0, 1, 2, size=6) + b = Permutation(3, 4, 5, size=6) + G = PermutationGroup(a, b) + assert G.is_primitive() is False + + +def test_random_stab(): + S = SymmetricGroup(5) + _random_el = Permutation([1, 3, 2, 0, 4]) + _random_prec = {'rand': _random_el} + g = S.random_stab(2, _random_prec=_random_prec) + assert g == Permutation([1, 3, 2, 0, 4]) + h = S.random_stab(1) + assert h(1) == 1 + + +def test_transitivity_degree(): + perm = Permutation([1, 2, 0]) + C = PermutationGroup([perm]) + assert C.transitivity_degree == 1 + gen1 = Permutation([1, 2, 0, 3, 4]) + gen2 = Permutation([1, 2, 3, 4, 0]) + # alternating group of degree 5 + Alt = PermutationGroup([gen1, gen2]) + assert Alt.transitivity_degree == 3 + + +def test_schreier_sims_random(): + assert sorted(Tetra.pgroup.base) == [0, 1] + + S = SymmetricGroup(3) + base = [0, 1] + strong_gens = [Permutation([1, 2, 0]), Permutation([1, 0, 2]), + Permutation([0, 2, 1])] + assert S.schreier_sims_random(base, strong_gens, 5) == (base, strong_gens) + D = DihedralGroup(3) + _random_prec = {'g': [Permutation([2, 0, 1]), Permutation([1, 2, 0]), + Permutation([1, 0, 2])]} + base = [0, 1] + strong_gens = [Permutation([1, 2, 0]), Permutation([2, 1, 0]), + Permutation([0, 2, 1])] + assert D.schreier_sims_random([], D.generators, 2, + _random_prec=_random_prec) == (base, strong_gens) + + +def test_baseswap(): + S = SymmetricGroup(4) + S.schreier_sims() + base = S.base + strong_gens = S.strong_gens + assert base == [0, 1, 2] + deterministic = S.baseswap(base, strong_gens, 1, randomized=False) + randomized = S.baseswap(base, strong_gens, 1) + assert deterministic[0] == [0, 2, 1] + assert _verify_bsgs(S, deterministic[0], deterministic[1]) is True + assert randomized[0] == [0, 2, 1] + assert _verify_bsgs(S, randomized[0], randomized[1]) is True + + +def test_schreier_sims_incremental(): + identity = Permutation([0, 1, 2, 3, 4]) + TrivialGroup = PermutationGroup([identity]) + base, strong_gens = TrivialGroup.schreier_sims_incremental(base=[0, 1, 2]) + assert _verify_bsgs(TrivialGroup, base, strong_gens) is True + S = SymmetricGroup(5) + base, strong_gens = S.schreier_sims_incremental(base=[0, 1, 2]) + assert _verify_bsgs(S, base, strong_gens) is True + D = DihedralGroup(2) + base, strong_gens = D.schreier_sims_incremental(base=[1]) + assert _verify_bsgs(D, base, strong_gens) is True + A = AlternatingGroup(7) + gens = A.generators[:] + gen0 = gens[0] + gen1 = gens[1] + gen1 = rmul(gen1, ~gen0) + gen0 = rmul(gen0, gen1) + gen1 = rmul(gen0, gen1) + base, strong_gens = A.schreier_sims_incremental(base=[0, 1], gens=gens) + assert _verify_bsgs(A, base, strong_gens) is True + C = CyclicGroup(11) + gen = C.generators[0] + base, strong_gens = C.schreier_sims_incremental(gens=[gen**3]) + assert _verify_bsgs(C, base, strong_gens) is True + + +def _subgroup_search(i, j, k): + prop_true = lambda x: True + prop_fix_points = lambda x: [x(point) for point in points] == points + prop_comm_g = lambda x: rmul(x, g) == rmul(g, x) + prop_even = lambda x: x.is_even + for i in range(i, j, k): + S = SymmetricGroup(i) + A = AlternatingGroup(i) + C = CyclicGroup(i) + Sym = S.subgroup_search(prop_true) + assert Sym.is_subgroup(S) + Alt = S.subgroup_search(prop_even) + assert Alt.is_subgroup(A) + Sym = S.subgroup_search(prop_true, init_subgroup=C) + assert Sym.is_subgroup(S) + points = [7] + assert S.stabilizer(7).is_subgroup(S.subgroup_search(prop_fix_points)) + points = [3, 4] + assert S.stabilizer(3).stabilizer(4).is_subgroup( + S.subgroup_search(prop_fix_points)) + points = [3, 5] + fix35 = A.subgroup_search(prop_fix_points) + points = [5] + fix5 = A.subgroup_search(prop_fix_points) + assert A.subgroup_search(prop_fix_points, init_subgroup=fix35 + ).is_subgroup(fix5) + base, strong_gens = A.schreier_sims_incremental() + g = A.generators[0] + comm_g = \ + A.subgroup_search(prop_comm_g, base=base, strong_gens=strong_gens) + assert _verify_bsgs(comm_g, base, comm_g.generators) is True + assert [prop_comm_g(gen) is True for gen in comm_g.generators] + + +def test_subgroup_search(): + _subgroup_search(10, 15, 2) + + +@XFAIL +def test_subgroup_search2(): + skip('takes too much time') + _subgroup_search(16, 17, 1) + + +def test_normal_closure(): + # the normal closure of the trivial group is trivial + S = SymmetricGroup(3) + identity = Permutation([0, 1, 2]) + closure = S.normal_closure(identity) + assert closure.is_trivial + # the normal closure of the entire group is the entire group + A = AlternatingGroup(4) + assert A.normal_closure(A).is_subgroup(A) + # brute-force verifications for subgroups + for i in (3, 4, 5): + S = SymmetricGroup(i) + A = AlternatingGroup(i) + D = DihedralGroup(i) + C = CyclicGroup(i) + for gp in (A, D, C): + assert _verify_normal_closure(S, gp) + # brute-force verifications for all elements of a group + S = SymmetricGroup(5) + elements = list(S.generate_dimino()) + for element in elements: + assert _verify_normal_closure(S, element) + # small groups + small = [] + for i in (1, 2, 3): + small.append(SymmetricGroup(i)) + small.append(AlternatingGroup(i)) + small.append(DihedralGroup(i)) + small.append(CyclicGroup(i)) + for gp in small: + for gp2 in small: + if gp2.is_subgroup(gp, 0) and gp2.degree == gp.degree: + assert _verify_normal_closure(gp, gp2) + + +def test_derived_series(): + # the derived series of the trivial group consists only of the trivial group + triv = PermutationGroup([Permutation([0, 1, 2])]) + assert triv.derived_series()[0].is_subgroup(triv) + # the derived series for a simple group consists only of the group itself + for i in (5, 6, 7): + A = AlternatingGroup(i) + assert A.derived_series()[0].is_subgroup(A) + # the derived series for S_4 is S_4 > A_4 > K_4 > triv + S = SymmetricGroup(4) + series = S.derived_series() + assert series[1].is_subgroup(AlternatingGroup(4)) + assert series[2].is_subgroup(DihedralGroup(2)) + assert series[3].is_trivial + + +def test_lower_central_series(): + # the lower central series of the trivial group consists of the trivial + # group + triv = PermutationGroup([Permutation([0, 1, 2])]) + assert triv.lower_central_series()[0].is_subgroup(triv) + # the lower central series of a simple group consists of the group itself + for i in (5, 6, 7): + A = AlternatingGroup(i) + assert A.lower_central_series()[0].is_subgroup(A) + # GAP-verified example + S = SymmetricGroup(6) + series = S.lower_central_series() + assert len(series) == 2 + assert series[1].is_subgroup(AlternatingGroup(6)) + + +def test_commutator(): + # the commutator of the trivial group and the trivial group is trivial + S = SymmetricGroup(3) + triv = PermutationGroup([Permutation([0, 1, 2])]) + assert S.commutator(triv, triv).is_subgroup(triv) + # the commutator of the trivial group and any other group is again trivial + A = AlternatingGroup(3) + assert S.commutator(triv, A).is_subgroup(triv) + # the commutator is commutative + for i in (3, 4, 5): + S = SymmetricGroup(i) + A = AlternatingGroup(i) + D = DihedralGroup(i) + assert S.commutator(A, D).is_subgroup(S.commutator(D, A)) + # the commutator of an abelian group is trivial + S = SymmetricGroup(7) + A1 = AbelianGroup(2, 5) + A2 = AbelianGroup(3, 4) + triv = PermutationGroup([Permutation([0, 1, 2, 3, 4, 5, 6])]) + assert S.commutator(A1, A1).is_subgroup(triv) + assert S.commutator(A2, A2).is_subgroup(triv) + # examples calculated by hand + S = SymmetricGroup(3) + A = AlternatingGroup(3) + assert S.commutator(A, S).is_subgroup(A) + + +def test_is_nilpotent(): + # every abelian group is nilpotent + for i in (1, 2, 3): + C = CyclicGroup(i) + Ab = AbelianGroup(i, i + 2) + assert C.is_nilpotent + assert Ab.is_nilpotent + Ab = AbelianGroup(5, 7, 10) + assert Ab.is_nilpotent + # A_5 is not solvable and thus not nilpotent + assert AlternatingGroup(5).is_nilpotent is False + + +def test_is_trivial(): + for i in range(5): + triv = PermutationGroup([Permutation(list(range(i)))]) + assert triv.is_trivial + + +def test_pointwise_stabilizer(): + S = SymmetricGroup(2) + stab = S.pointwise_stabilizer([0]) + assert stab.generators == [Permutation(1)] + S = SymmetricGroup(5) + points = [] + stab = S + for point in (2, 0, 3, 4, 1): + stab = stab.stabilizer(point) + points.append(point) + assert S.pointwise_stabilizer(points).is_subgroup(stab) + + +def test_make_perm(): + assert cube.pgroup.make_perm(5, seed=list(range(5))) == \ + Permutation([4, 7, 6, 5, 0, 3, 2, 1]) + assert cube.pgroup.make_perm(7, seed=list(range(7))) == \ + Permutation([6, 7, 3, 2, 5, 4, 0, 1]) + + +def test_elements(): + from sympy.sets.sets import FiniteSet + + p = Permutation(2, 3) + assert set(PermutationGroup(p).elements) == {Permutation(3), Permutation(2, 3)} + assert FiniteSet(*PermutationGroup(p).elements) \ + == FiniteSet(Permutation(2, 3), Permutation(3)) + + +def test_is_group(): + assert PermutationGroup(Permutation(1,2), Permutation(2,4)).is_group is True + assert SymmetricGroup(4).is_group is True + + +def test_PermutationGroup(): + assert PermutationGroup() == PermutationGroup(Permutation()) + assert (PermutationGroup() == 0) is False + + +def test_coset_transvesal(): + G = AlternatingGroup(5) + H = PermutationGroup(Permutation(0,1,2),Permutation(1,2)(3,4)) + assert G.coset_transversal(H) == \ + [Permutation(4), Permutation(2, 3, 4), Permutation(2, 4, 3), + Permutation(1, 2, 4), Permutation(4)(1, 2, 3), Permutation(1, 3)(2, 4), + Permutation(0, 1, 2, 3, 4), Permutation(0, 1, 2, 4, 3), + Permutation(0, 1, 3, 2, 4), Permutation(0, 2, 4, 1, 3)] + + +def test_coset_table(): + G = PermutationGroup(Permutation(0,1,2,3), Permutation(0,1,2), + Permutation(0,4,2,7), Permutation(5,6), Permutation(0,7)) + H = PermutationGroup(Permutation(0,1,2,3), Permutation(0,7)) + assert G.coset_table(H) == \ + [[0, 0, 0, 0, 1, 2, 3, 3, 0, 0], [4, 5, 2, 5, 6, 0, 7, 7, 1, 1], + [5, 4, 5, 1, 0, 6, 8, 8, 6, 6], [3, 3, 3, 3, 7, 8, 0, 0, 3, 3], + [2, 1, 4, 4, 4, 4, 9, 9, 4, 4], [1, 2, 1, 2, 5, 5, 10, 10, 5, 5], + [6, 6, 6, 6, 2, 1, 11, 11, 2, 2], [9, 10, 8, 10, 11, 3, 1, 1, 7, 7], + [10, 9, 10, 7, 3, 11, 2, 2, 11, 11], [8, 7, 9, 9, 9, 9, 4, 4, 9, 9], + [7, 8, 7, 8, 10, 10, 5, 5, 10, 10], [11, 11, 11, 11, 8, 7, 6, 6, 8, 8]] + + +def test_subgroup(): + G = PermutationGroup(Permutation(0,1,2), Permutation(0,2,3)) + H = G.subgroup([Permutation(0,1,3)]) + assert H.is_subgroup(G) + + +def test_generator_product(): + G = SymmetricGroup(5) + p = Permutation(0, 2, 3)(1, 4) + gens = G.generator_product(p) + assert all(g in G.strong_gens for g in gens) + w = G.identity + for g in gens: + w = g*w + assert w == p + + +def test_sylow_subgroup(): + P = PermutationGroup(Permutation(1, 5)(2, 4), Permutation(0, 1, 2, 3, 4, 5)) + S = P.sylow_subgroup(2) + assert S.order() == 4 + + P = DihedralGroup(12) + S = P.sylow_subgroup(3) + assert S.order() == 3 + + P = PermutationGroup( + Permutation(1, 5)(2, 4), Permutation(0, 1, 2, 3, 4, 5), Permutation(0, 2)) + S = P.sylow_subgroup(3) + assert S.order() == 9 + S = P.sylow_subgroup(2) + assert S.order() == 8 + + P = SymmetricGroup(10) + S = P.sylow_subgroup(2) + assert S.order() == 256 + S = P.sylow_subgroup(3) + assert S.order() == 81 + S = P.sylow_subgroup(5) + assert S.order() == 25 + + # the length of the lower central series + # of a p-Sylow subgroup of Sym(n) grows with + # the highest exponent exp of p such + # that n >= p**exp + exp = 1 + length = 0 + for i in range(2, 9): + P = SymmetricGroup(i) + S = P.sylow_subgroup(2) + ls = S.lower_central_series() + if i // 2**exp > 0: + # length increases with exponent + assert len(ls) > length + length = len(ls) + exp += 1 + else: + assert len(ls) == length + + G = SymmetricGroup(100) + S = G.sylow_subgroup(3) + assert G.order() % S.order() == 0 + assert G.order()/S.order() % 3 > 0 + + G = AlternatingGroup(100) + S = G.sylow_subgroup(2) + assert G.order() % S.order() == 0 + assert G.order()/S.order() % 2 > 0 + + G = DihedralGroup(18) + S = G.sylow_subgroup(p=2) + assert S.order() == 4 + + G = DihedralGroup(50) + S = G.sylow_subgroup(p=2) + assert S.order() == 4 + + +@slow +def test_presentation(): + def _test(P): + G = P.presentation() + return G.order() == P.order() + + def _strong_test(P): + G = P.strong_presentation() + chk = len(G.generators) == len(P.strong_gens) + return chk and G.order() == P.order() + + P = PermutationGroup(Permutation(0,1,5,2)(3,7,4,6), Permutation(0,3,5,4)(1,6,2,7)) + assert _test(P) + + P = AlternatingGroup(5) + assert _test(P) + + P = SymmetricGroup(5) + assert _test(P) + + P = PermutationGroup( + [Permutation(0,3,1,2), Permutation(3)(0,1), Permutation(0,1)(2,3)]) + assert _strong_test(P) + + P = DihedralGroup(6) + assert _strong_test(P) + + a = Permutation(0,1)(2,3) + b = Permutation(0,2)(3,1) + c = Permutation(4,5) + P = PermutationGroup(c, a, b) + assert _strong_test(P) + + +def test_polycyclic(): + a = Permutation([0, 1, 2]) + b = Permutation([2, 1, 0]) + G = PermutationGroup([a, b]) + assert G.is_polycyclic is True + + a = Permutation([1, 2, 3, 4, 0]) + b = Permutation([1, 0, 2, 3, 4]) + G = PermutationGroup([a, b]) + assert G.is_polycyclic is False + + +def test_elementary(): + a = Permutation([1, 5, 2, 0, 3, 6, 4]) + G = PermutationGroup([a]) + assert G.is_elementary(7) is False + + a = Permutation(0, 1)(2, 3) + b = Permutation(0, 2)(3, 1) + G = PermutationGroup([a, b]) + assert G.is_elementary(2) is True + c = Permutation(4, 5, 6) + G = PermutationGroup([a, b, c]) + assert G.is_elementary(2) is False + + G = SymmetricGroup(4).sylow_subgroup(2) + assert G.is_elementary(2) is False + H = AlternatingGroup(4).sylow_subgroup(2) + assert H.is_elementary(2) is True + + +def test_perfect(): + G = AlternatingGroup(3) + assert G.is_perfect is False + G = AlternatingGroup(5) + assert G.is_perfect is True + + +def test_index(): + G = PermutationGroup(Permutation(0,1,2), Permutation(0,2,3)) + H = G.subgroup([Permutation(0,1,3)]) + assert G.index(H) == 4 + + +def test_cyclic(): + G = SymmetricGroup(2) + assert G.is_cyclic + G = AbelianGroup(3, 7) + assert G.is_cyclic + G = AbelianGroup(7, 7) + assert not G.is_cyclic + G = AlternatingGroup(3) + assert G.is_cyclic + G = AlternatingGroup(4) + assert not G.is_cyclic + + # Order less than 6 + G = PermutationGroup(Permutation(0, 1, 2), Permutation(0, 2, 1)) + assert G.is_cyclic + G = PermutationGroup( + Permutation(0, 1, 2, 3), + Permutation(0, 2)(1, 3) + ) + assert G.is_cyclic + G = PermutationGroup( + Permutation(3), + Permutation(0, 1)(2, 3), + Permutation(0, 2)(1, 3), + Permutation(0, 3)(1, 2) + ) + assert G.is_cyclic is False + + # Order 15 + G = PermutationGroup( + Permutation(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), + Permutation(0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13) + ) + assert G.is_cyclic + + # Distinct prime orders + assert PermutationGroup._distinct_primes_lemma([3, 5]) is True + assert PermutationGroup._distinct_primes_lemma([5, 7]) is True + assert PermutationGroup._distinct_primes_lemma([2, 3]) is None + assert PermutationGroup._distinct_primes_lemma([3, 5, 7]) is None + assert PermutationGroup._distinct_primes_lemma([5, 7, 13]) is True + + G = PermutationGroup( + Permutation(0, 1, 2, 3), + Permutation(0, 2)(1, 3)) + assert G.is_cyclic + assert G._is_abelian + + # Non-abelian and therefore not cyclic + G = PermutationGroup(*SymmetricGroup(3).generators) + assert G.is_cyclic is False + + # Abelian and cyclic + G = PermutationGroup( + Permutation(0, 1, 2, 3), + Permutation(4, 5, 6) + ) + assert G.is_cyclic + + # Abelian but not cyclic + G = PermutationGroup( + Permutation(0, 1), + Permutation(2, 3), + Permutation(4, 5, 6) + ) + assert G.is_cyclic is False + + +def test_dihedral(): + G = SymmetricGroup(2) + assert G.is_dihedral + G = SymmetricGroup(3) + assert G.is_dihedral + + G = AbelianGroup(2, 2) + assert G.is_dihedral + G = CyclicGroup(4) + assert not G.is_dihedral + + G = AbelianGroup(3, 5) + assert not G.is_dihedral + G = AbelianGroup(2) + assert G.is_dihedral + G = AbelianGroup(6) + assert not G.is_dihedral + + # D6, generated by two adjacent flips + G = PermutationGroup( + Permutation(1, 5)(2, 4), + Permutation(0, 1)(3, 4)(2, 5)) + assert G.is_dihedral + + # D7, generated by a flip and a rotation + G = PermutationGroup( + Permutation(1, 6)(2, 5)(3, 4), + Permutation(0, 1, 2, 3, 4, 5, 6)) + assert G.is_dihedral + + # S4, presented by three generators, fails due to having exactly 9 + # elements of order 2: + G = PermutationGroup( + Permutation(0, 1), Permutation(0, 2), + Permutation(0, 3)) + assert not G.is_dihedral + + # D7, given by three generators + G = PermutationGroup( + Permutation(1, 6)(2, 5)(3, 4), + Permutation(2, 0)(3, 6)(4, 5), + Permutation(0, 1, 2, 3, 4, 5, 6)) + assert G.is_dihedral + + +def test_abelian_invariants(): + G = AbelianGroup(2, 3, 4) + assert G.abelian_invariants() == [2, 3, 4] + G=PermutationGroup([Permutation(1, 2, 3, 4), Permutation(1, 2), Permutation(5, 6)]) + assert G.abelian_invariants() == [2, 2] + G = AlternatingGroup(7) + assert G.abelian_invariants() == [] + G = AlternatingGroup(4) + assert G.abelian_invariants() == [3] + G = DihedralGroup(4) + assert G.abelian_invariants() == [2, 2] + + G = PermutationGroup([Permutation(1, 2, 3, 4, 5, 6, 7)]) + assert G.abelian_invariants() == [7] + G = DihedralGroup(12) + S = G.sylow_subgroup(3) + assert S.abelian_invariants() == [3] + G = PermutationGroup(Permutation(0, 1, 2), Permutation(0, 2, 3)) + assert G.abelian_invariants() == [3] + G = PermutationGroup([Permutation(0, 1), Permutation(0, 2, 4, 6)(1, 3, 5, 7)]) + assert G.abelian_invariants() == [2, 4] + G = SymmetricGroup(30) + S = G.sylow_subgroup(2) + assert S.abelian_invariants() == [2, 2, 2, 2, 2, 2, 2, 2, 2, 2] + S = G.sylow_subgroup(3) + assert S.abelian_invariants() == [3, 3, 3, 3] + S = G.sylow_subgroup(5) + assert S.abelian_invariants() == [5, 5, 5] + + +def test_composition_series(): + a = Permutation(1, 2, 3) + b = Permutation(1, 2) + G = PermutationGroup([a, b]) + comp_series = G.composition_series() + assert comp_series == G.derived_series() + # The first group in the composition series is always the group itself and + # the last group in the series is the trivial group. + S = SymmetricGroup(4) + assert S.composition_series()[0] == S + assert len(S.composition_series()) == 5 + A = AlternatingGroup(4) + assert A.composition_series()[0] == A + assert len(A.composition_series()) == 4 + + # the composition series for C_8 is C_8 > C_4 > C_2 > triv + G = CyclicGroup(8) + series = G.composition_series() + assert is_isomorphic(series[1], CyclicGroup(4)) + assert is_isomorphic(series[2], CyclicGroup(2)) + assert series[3].is_trivial + + +def test_is_symmetric(): + a = Permutation(0, 1, 2) + b = Permutation(0, 1, size=3) + assert PermutationGroup(a, b).is_symmetric is True + + a = Permutation(0, 2, 1) + b = Permutation(1, 2, size=3) + assert PermutationGroup(a, b).is_symmetric is True + + a = Permutation(0, 1, 2, 3) + b = Permutation(0, 3)(1, 2) + assert PermutationGroup(a, b).is_symmetric is False + +def test_conjugacy_class(): + S = SymmetricGroup(4) + x = Permutation(1, 2, 3) + C = {Permutation(0, 1, 2, size = 4), Permutation(0, 1, 3), + Permutation(0, 2, 1, size = 4), Permutation(0, 2, 3), + Permutation(0, 3, 1), Permutation(0, 3, 2), + Permutation(1, 2, 3), Permutation(1, 3, 2)} + assert S.conjugacy_class(x) == C + +def test_conjugacy_classes(): + S = SymmetricGroup(3) + expected = [{Permutation(size = 3)}, + {Permutation(0, 1, size = 3), Permutation(0, 2), Permutation(1, 2)}, + {Permutation(0, 1, 2), Permutation(0, 2, 1)}] + computed = S.conjugacy_classes() + + assert len(expected) == len(computed) + assert all(e in computed for e in expected) + +def test_coset_class(): + a = Permutation(1, 2) + b = Permutation(0, 1) + G = PermutationGroup([a, b]) + #Creating right coset + rht_coset = G*a + #Checking whether it is left coset or right coset + assert rht_coset.is_right_coset + assert not rht_coset.is_left_coset + #Creating list representation of coset + list_repr = rht_coset.as_list() + expected = [Permutation(0, 2), Permutation(0, 2, 1), Permutation(1, 2), + Permutation(2), Permutation(2)(0, 1), Permutation(0, 1, 2)] + for ele in list_repr: + assert ele in expected + #Creating left coset + left_coset = a*G + #Checking whether it is left coset or right coset + assert not left_coset.is_right_coset + assert left_coset.is_left_coset + #Creating list representation of Coset + list_repr = left_coset.as_list() + expected = [Permutation(2)(0, 1), Permutation(0, 1, 2), Permutation(1, 2), + Permutation(2), Permutation(0, 2), Permutation(0, 2, 1)] + for ele in list_repr: + assert ele in expected + + G = PermutationGroup(Permutation(1, 2, 3, 4), Permutation(2, 3, 4)) + H = PermutationGroup(Permutation(1, 2, 3, 4)) + g = Permutation(1, 3)(2, 4) + rht_coset = Coset(g, H, G, dir='+') + assert rht_coset.is_right_coset + list_repr = rht_coset.as_list() + expected = [Permutation(1, 2, 3, 4), Permutation(4), Permutation(1, 3)(2, 4), + Permutation(1, 4, 3, 2)] + for ele in list_repr: + assert ele in expected + +def test_symmetricpermutationgroup(): + a = SymmetricPermutationGroup(5) + assert a.degree == 5 + assert a.order() == 120 + assert a.identity() == Permutation(4) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_permutations.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_permutations.py new file mode 100644 index 0000000000000000000000000000000000000000..b52fcfec0e2fb3be872efaa814077760e121c748 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_permutations.py @@ -0,0 +1,564 @@ +from itertools import permutations +from copy import copy + +from sympy.core.expr import unchanged +from sympy.core.numbers import Integer +from sympy.core.relational import Eq +from sympy.core.symbol import Symbol +from sympy.core.singleton import S +from sympy.combinatorics.permutations import \ + Permutation, _af_parity, _af_rmul, _af_rmuln, AppliedPermutation, Cycle +from sympy.printing import sstr, srepr, pretty, latex +from sympy.testing.pytest import raises, warns_deprecated_sympy + + +rmul = Permutation.rmul +a = Symbol('a', integer=True) + + +def test_Permutation(): + # don't auto fill 0 + raises(ValueError, lambda: Permutation([1])) + p = Permutation([0, 1, 2, 3]) + # call as bijective + assert [p(i) for i in range(p.size)] == list(p) + # call as operator + assert p(list(range(p.size))) == list(p) + # call as function + assert list(p(1, 2)) == [0, 2, 1, 3] + raises(TypeError, lambda: p(-1)) + raises(TypeError, lambda: p(5)) + # conversion to list + assert list(p) == list(range(4)) + assert p.copy() == p + assert copy(p) == p + assert Permutation(size=4) == Permutation(3) + assert Permutation(Permutation(3), size=5) == Permutation(4) + # cycle form with size + assert Permutation([[1, 2]], size=4) == Permutation([[1, 2], [0], [3]]) + # random generation + assert Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1])) + + p = Permutation([2, 5, 1, 6, 3, 0, 4]) + q = Permutation([[1], [0, 3, 5, 6, 2, 4]]) + assert len({p, p}) == 1 + r = Permutation([1, 3, 2, 0, 4, 6, 5]) + ans = Permutation(_af_rmuln(*[w.array_form for w in (p, q, r)])).array_form + assert rmul(p, q, r).array_form == ans + # make sure no other permutation of p, q, r could have given + # that answer + for a, b, c in permutations((p, q, r)): + if (a, b, c) == (p, q, r): + continue + assert rmul(a, b, c).array_form != ans + + assert p.support() == list(range(7)) + assert q.support() == [0, 2, 3, 4, 5, 6] + assert Permutation(p.cyclic_form).array_form == p.array_form + assert p.cardinality == 5040 + assert q.cardinality == 5040 + assert q.cycles == 2 + assert rmul(q, p) == Permutation([4, 6, 1, 2, 5, 3, 0]) + assert rmul(p, q) == Permutation([6, 5, 3, 0, 2, 4, 1]) + assert _af_rmul(p.array_form, q.array_form) == \ + [6, 5, 3, 0, 2, 4, 1] + + assert rmul(Permutation([[1, 2, 3], [0, 4]]), + Permutation([[1, 2, 4], [0], [3]])).cyclic_form == \ + [[0, 4, 2], [1, 3]] + assert q.array_form == [3, 1, 4, 5, 0, 6, 2] + assert q.cyclic_form == [[0, 3, 5, 6, 2, 4]] + assert q.full_cyclic_form == [[0, 3, 5, 6, 2, 4], [1]] + assert p.cyclic_form == [[0, 2, 1, 5], [3, 6, 4]] + t = p.transpositions() + assert t == [(0, 5), (0, 1), (0, 2), (3, 4), (3, 6)] + assert Permutation.rmul(*[Permutation(Cycle(*ti)) for ti in (t)]) + assert Permutation([1, 0]).transpositions() == [(0, 1)] + + assert p**13 == p + assert q**0 == Permutation(list(range(q.size))) + assert q**-2 == ~q**2 + assert q**2 == Permutation([5, 1, 0, 6, 3, 2, 4]) + assert q**3 == q**2*q + assert q**4 == q**2*q**2 + + a = Permutation(1, 3) + b = Permutation(2, 0, 3) + I = Permutation(3) + assert ~a == a**-1 + assert a*~a == I + assert a*b**-1 == a*~b + + ans = Permutation(0, 5, 3, 1, 6)(2, 4) + assert (p + q.rank()).rank() == ans.rank() + assert (p + q.rank())._rank == ans.rank() + assert (q + p.rank()).rank() == ans.rank() + raises(TypeError, lambda: p + Permutation(list(range(10)))) + + assert (p - q.rank()).rank() == Permutation(0, 6, 3, 1, 2, 5, 4).rank() + assert p.rank() - q.rank() < 0 # for coverage: make sure mod is used + assert (q - p.rank()).rank() == Permutation(1, 4, 6, 2)(3, 5).rank() + + assert p*q == Permutation(_af_rmuln(*[list(w) for w in (q, p)])) + assert p*Permutation([]) == p + assert Permutation([])*p == p + assert p*Permutation([[0, 1]]) == Permutation([2, 5, 0, 6, 3, 1, 4]) + assert Permutation([[0, 1]])*p == Permutation([5, 2, 1, 6, 3, 0, 4]) + + pq = p ^ q + assert pq == Permutation([5, 6, 0, 4, 1, 2, 3]) + assert pq == rmul(q, p, ~q) + qp = q ^ p + assert qp == Permutation([4, 3, 6, 2, 1, 5, 0]) + assert qp == rmul(p, q, ~p) + raises(ValueError, lambda: p ^ Permutation([])) + + assert p.commutator(q) == Permutation(0, 1, 3, 4, 6, 5, 2) + assert q.commutator(p) == Permutation(0, 2, 5, 6, 4, 3, 1) + assert p.commutator(q) == ~q.commutator(p) + raises(ValueError, lambda: p.commutator(Permutation([]))) + + assert len(p.atoms()) == 7 + assert q.atoms() == {0, 1, 2, 3, 4, 5, 6} + + assert p.inversion_vector() == [2, 4, 1, 3, 1, 0] + assert q.inversion_vector() == [3, 1, 2, 2, 0, 1] + + assert Permutation.from_inversion_vector(p.inversion_vector()) == p + assert Permutation.from_inversion_vector(q.inversion_vector()).array_form\ + == q.array_form + raises(ValueError, lambda: Permutation.from_inversion_vector([0, 2])) + assert Permutation(list(range(500, -1, -1))).inversions() == 125250 + + s = Permutation([0, 4, 1, 3, 2]) + assert s.parity() == 0 + _ = s.cyclic_form # needed to create a value for _cyclic_form + assert len(s._cyclic_form) != s.size and s.parity() == 0 + assert not s.is_odd + assert s.is_even + assert Permutation([0, 1, 4, 3, 2]).parity() == 1 + assert _af_parity([0, 4, 1, 3, 2]) == 0 + assert _af_parity([0, 1, 4, 3, 2]) == 1 + + s = Permutation([0]) + + assert s.is_Singleton + assert Permutation([]).is_Empty + + r = Permutation([3, 2, 1, 0]) + assert (r**2).is_Identity + + assert rmul(~p, p).is_Identity + assert (~p)**13 == Permutation([5, 2, 0, 4, 6, 1, 3]) + assert p.max() == 6 + assert p.min() == 0 + + q = Permutation([[6], [5], [0, 1, 2, 3, 4]]) + + assert q.max() == 4 + assert q.min() == 0 + + p = Permutation([1, 5, 2, 0, 3, 6, 4]) + q = Permutation([[1, 2, 3, 5, 6], [0, 4]]) + + assert p.ascents() == [0, 3, 4] + assert q.ascents() == [1, 2, 4] + assert r.ascents() == [] + + assert p.descents() == [1, 2, 5] + assert q.descents() == [0, 3, 5] + assert Permutation(r.descents()).is_Identity + + assert p.inversions() == 7 + # test the merge-sort with a longer permutation + big = list(p) + list(range(p.max() + 1, p.max() + 130)) + assert Permutation(big).inversions() == 7 + assert p.signature() == -1 + assert q.inversions() == 11 + assert q.signature() == -1 + assert rmul(p, ~p).inversions() == 0 + assert rmul(p, ~p).signature() == 1 + + assert p.order() == 6 + assert q.order() == 10 + assert (p**(p.order())).is_Identity + + assert p.length() == 6 + assert q.length() == 7 + assert r.length() == 4 + + assert p.runs() == [[1, 5], [2], [0, 3, 6], [4]] + assert q.runs() == [[4], [2, 3, 5], [0, 6], [1]] + assert r.runs() == [[3], [2], [1], [0]] + + assert p.index() == 8 + assert q.index() == 8 + assert r.index() == 3 + + assert p.get_precedence_distance(q) == q.get_precedence_distance(p) + assert p.get_adjacency_distance(q) == p.get_adjacency_distance(q) + assert p.get_positional_distance(q) == p.get_positional_distance(q) + p = Permutation([0, 1, 2, 3]) + q = Permutation([3, 2, 1, 0]) + assert p.get_precedence_distance(q) == 6 + assert p.get_adjacency_distance(q) == 3 + assert p.get_positional_distance(q) == 8 + p = Permutation([0, 3, 1, 2, 4]) + q = Permutation.josephus(4, 5, 2) + assert p.get_adjacency_distance(q) == 3 + raises(ValueError, lambda: p.get_adjacency_distance(Permutation([]))) + raises(ValueError, lambda: p.get_positional_distance(Permutation([]))) + raises(ValueError, lambda: p.get_precedence_distance(Permutation([]))) + + a = [Permutation.unrank_nonlex(4, i) for i in range(5)] + iden = Permutation([0, 1, 2, 3]) + for i in range(5): + for j in range(i + 1, 5): + assert a[i].commutes_with(a[j]) == \ + (rmul(a[i], a[j]) == rmul(a[j], a[i])) + if a[i].commutes_with(a[j]): + assert a[i].commutator(a[j]) == iden + assert a[j].commutator(a[i]) == iden + + a = Permutation(3) + b = Permutation(0, 6, 3)(1, 2) + assert a.cycle_structure == {1: 4} + assert b.cycle_structure == {2: 1, 3: 1, 1: 2} + # issue 11130 + raises(ValueError, lambda: Permutation(3, size=3)) + raises(ValueError, lambda: Permutation([1, 2, 0, 3], size=3)) + + +def test_Permutation_subclassing(): + # Subclass that adds permutation application on iterables + class CustomPermutation(Permutation): + def __call__(self, *i): + try: + return super().__call__(*i) + except TypeError: + pass + + try: + perm_obj = i[0] + return [self._array_form[j] for j in perm_obj] + except TypeError: + raise TypeError('unrecognized argument') + + def __eq__(self, other): + if isinstance(other, Permutation): + return self._hashable_content() == other._hashable_content() + else: + return super().__eq__(other) + + def __hash__(self): + return super().__hash__() + + p = CustomPermutation([1, 2, 3, 0]) + q = Permutation([1, 2, 3, 0]) + + assert p == q + raises(TypeError, lambda: q([1, 2])) + assert [2, 3] == p([1, 2]) + + assert type(p * q) == CustomPermutation + assert type(q * p) == Permutation # True because q.__mul__(p) is called! + + # Run all tests for the Permutation class also on the subclass + def wrapped_test_Permutation(): + # Monkeypatch the class definition in the globals + globals()['__Perm'] = globals()['Permutation'] + globals()['Permutation'] = CustomPermutation + test_Permutation() + globals()['Permutation'] = globals()['__Perm'] # Restore + del globals()['__Perm'] + + wrapped_test_Permutation() + + +def test_josephus(): + assert Permutation.josephus(4, 6, 1) == Permutation([3, 1, 0, 2, 5, 4]) + assert Permutation.josephus(1, 5, 1).is_Identity + + +def test_ranking(): + assert Permutation.unrank_lex(5, 10).rank() == 10 + p = Permutation.unrank_lex(15, 225) + assert p.rank() == 225 + p1 = p.next_lex() + assert p1.rank() == 226 + assert Permutation.unrank_lex(15, 225).rank() == 225 + assert Permutation.unrank_lex(10, 0).is_Identity + p = Permutation.unrank_lex(4, 23) + assert p.rank() == 23 + assert p.array_form == [3, 2, 1, 0] + assert p.next_lex() is None + + p = Permutation([1, 5, 2, 0, 3, 6, 4]) + q = Permutation([[1, 2, 3, 5, 6], [0, 4]]) + a = [Permutation.unrank_trotterjohnson(4, i).array_form for i in range(5)] + assert a == [[0, 1, 2, 3], [0, 1, 3, 2], [0, 3, 1, 2], [3, 0, 1, + 2], [3, 0, 2, 1] ] + assert [Permutation(pa).rank_trotterjohnson() for pa in a] == list(range(5)) + assert Permutation([0, 1, 2, 3]).next_trotterjohnson() == \ + Permutation([0, 1, 3, 2]) + + assert q.rank_trotterjohnson() == 2283 + assert p.rank_trotterjohnson() == 3389 + assert Permutation([1, 0]).rank_trotterjohnson() == 1 + a = Permutation(list(range(3))) + b = a + l = [] + tj = [] + for i in range(6): + l.append(a) + tj.append(b) + a = a.next_lex() + b = b.next_trotterjohnson() + assert a == b is None + assert {tuple(a) for a in l} == {tuple(a) for a in tj} + + p = Permutation([2, 5, 1, 6, 3, 0, 4]) + q = Permutation([[6], [5], [0, 1, 2, 3, 4]]) + assert p.rank() == 1964 + assert q.rank() == 870 + assert Permutation([]).rank_nonlex() == 0 + prank = p.rank_nonlex() + assert prank == 1600 + assert Permutation.unrank_nonlex(7, 1600) == p + qrank = q.rank_nonlex() + assert qrank == 41 + assert Permutation.unrank_nonlex(7, 41) == Permutation(q.array_form) + + a = [Permutation.unrank_nonlex(4, i).array_form for i in range(24)] + assert a == [ + [1, 2, 3, 0], [3, 2, 0, 1], [1, 3, 0, 2], [1, 2, 0, 3], [2, 3, 1, 0], + [2, 0, 3, 1], [3, 0, 1, 2], [2, 0, 1, 3], [1, 3, 2, 0], [3, 0, 2, 1], + [1, 0, 3, 2], [1, 0, 2, 3], [2, 1, 3, 0], [2, 3, 0, 1], [3, 1, 0, 2], + [2, 1, 0, 3], [3, 2, 1, 0], [0, 2, 3, 1], [0, 3, 1, 2], [0, 2, 1, 3], + [3, 1, 2, 0], [0, 3, 2, 1], [0, 1, 3, 2], [0, 1, 2, 3]] + + N = 10 + p1 = Permutation(a[0]) + for i in range(1, N+1): + p1 = p1*Permutation(a[i]) + p2 = Permutation.rmul_with_af(*[Permutation(h) for h in a[N::-1]]) + assert p1 == p2 + + ok = [] + p = Permutation([1, 0]) + for i in range(3): + ok.append(p.array_form) + p = p.next_nonlex() + if p is None: + ok.append(None) + break + assert ok == [[1, 0], [0, 1], None] + assert Permutation([3, 2, 0, 1]).next_nonlex() == Permutation([1, 3, 0, 2]) + assert [Permutation(pa).rank_nonlex() for pa in a] == list(range(24)) + + +def test_mul(): + a, b = [0, 2, 1, 3], [0, 1, 3, 2] + assert _af_rmul(a, b) == [0, 2, 3, 1] + assert _af_rmuln(a, b, list(range(4))) == [0, 2, 3, 1] + assert rmul(Permutation(a), Permutation(b)).array_form == [0, 2, 3, 1] + + a = Permutation([0, 2, 1, 3]) + b = (0, 1, 3, 2) + c = (3, 1, 2, 0) + assert Permutation.rmul(a, b, c) == Permutation([1, 2, 3, 0]) + assert Permutation.rmul(a, c) == Permutation([3, 2, 1, 0]) + raises(TypeError, lambda: Permutation.rmul(b, c)) + + n = 6 + m = 8 + a = [Permutation.unrank_nonlex(n, i).array_form for i in range(m)] + h = list(range(n)) + for i in range(m): + h = _af_rmul(h, a[i]) + h2 = _af_rmuln(*a[:i + 1]) + assert h == h2 + + +def test_args(): + p = Permutation([(0, 3, 1, 2), (4, 5)]) + assert p._cyclic_form is None + assert Permutation(p) == p + assert p.cyclic_form == [[0, 3, 1, 2], [4, 5]] + assert p._array_form == [3, 2, 0, 1, 5, 4] + p = Permutation((0, 3, 1, 2)) + assert p._cyclic_form is None + assert p._array_form == [0, 3, 1, 2] + assert Permutation([0]) == Permutation((0, )) + assert Permutation([[0], [1]]) == Permutation(((0, ), (1, ))) == \ + Permutation(((0, ), [1])) + assert Permutation([[1, 2]]) == Permutation([0, 2, 1]) + assert Permutation([[1], [4, 2]]) == Permutation([0, 1, 4, 3, 2]) + assert Permutation([[1], [4, 2]], size=1) == Permutation([0, 1, 4, 3, 2]) + assert Permutation( + [[1], [4, 2]], size=6) == Permutation([0, 1, 4, 3, 2, 5]) + assert Permutation([[0, 1], [0, 2]]) == Permutation(0, 1, 2) + assert Permutation([], size=3) == Permutation([0, 1, 2]) + assert Permutation(3).list(5) == [0, 1, 2, 3, 4] + assert Permutation(3).list(-1) == [] + assert Permutation(5)(1, 2).list(-1) == [0, 2, 1] + assert Permutation(5)(1, 2).list() == [0, 2, 1, 3, 4, 5] + raises(ValueError, lambda: Permutation([1, 2], [0])) + # enclosing brackets needed + raises(ValueError, lambda: Permutation([[1, 2], 0])) + # enclosing brackets needed on 0 + raises(ValueError, lambda: Permutation([1, 1, 0])) + raises(ValueError, lambda: Permutation([4, 5], size=10)) # where are 0-3? + # but this is ok because cycles imply that only those listed moved + assert Permutation(4, 5) == Permutation([0, 1, 2, 3, 5, 4]) + + +def test_Cycle(): + assert str(Cycle()) == '()' + assert Cycle(Cycle(1,2)) == Cycle(1, 2) + assert Cycle(1,2).copy() == Cycle(1,2) + assert list(Cycle(1, 3, 2)) == [0, 3, 1, 2] + assert Cycle(1, 2)(2, 3) == Cycle(1, 3, 2) + assert Cycle(1, 2)(2, 3)(4, 5) == Cycle(1, 3, 2)(4, 5) + assert Permutation(Cycle(1, 2)(2, 1, 0, 3)).cyclic_form, Cycle(0, 2, 1) + raises(ValueError, lambda: Cycle().list()) + assert Cycle(1, 2).list() == [0, 2, 1] + assert Cycle(1, 2).list(4) == [0, 2, 1, 3] + assert Cycle(3).list(2) == [0, 1] + assert Cycle(3).list(6) == [0, 1, 2, 3, 4, 5] + assert Permutation(Cycle(1, 2), size=4) == \ + Permutation([0, 2, 1, 3]) + assert str(Cycle(1, 2)(4, 5)) == '(1 2)(4 5)' + assert str(Cycle(1, 2)) == '(1 2)' + assert Cycle(Permutation(list(range(3)))) == Cycle() + assert Cycle(1, 2).list() == [0, 2, 1] + assert Cycle(1, 2).list(4) == [0, 2, 1, 3] + assert Cycle().size == 0 + raises(ValueError, lambda: Cycle((1, 2))) + raises(ValueError, lambda: Cycle(1, 2, 1)) + raises(TypeError, lambda: Cycle(1, 2)*{}) + raises(ValueError, lambda: Cycle(4)[a]) + raises(ValueError, lambda: Cycle(2, -4, 3)) + + # check round-trip + p = Permutation([[1, 2], [4, 3]], size=5) + assert Permutation(Cycle(p)) == p + + +def test_from_sequence(): + assert Permutation.from_sequence('SymPy') == Permutation(4)(0, 1, 3) + assert Permutation.from_sequence('SymPy', key=lambda x: x.lower()) == \ + Permutation(4)(0, 2)(1, 3) + + +def test_resize(): + p = Permutation(0, 1, 2) + assert p.resize(5) == Permutation(0, 1, 2, size=5) + assert p.resize(4) == Permutation(0, 1, 2, size=4) + assert p.resize(3) == p + raises(ValueError, lambda: p.resize(2)) + + p = Permutation(0, 1, 2)(3, 4)(5, 6) + assert p.resize(3) == Permutation(0, 1, 2) + raises(ValueError, lambda: p.resize(4)) + + +def test_printing_cyclic(): + p1 = Permutation([0, 2, 1]) + assert repr(p1) == 'Permutation(1, 2)' + assert str(p1) == '(1 2)' + p2 = Permutation() + assert repr(p2) == 'Permutation()' + assert str(p2) == '()' + p3 = Permutation([1, 2, 0, 3]) + assert repr(p3) == 'Permutation(3)(0, 1, 2)' + + +def test_printing_non_cyclic(): + p1 = Permutation([0, 1, 2, 3, 4, 5]) + assert srepr(p1, perm_cyclic=False) == 'Permutation([], size=6)' + assert sstr(p1, perm_cyclic=False) == 'Permutation([], size=6)' + p2 = Permutation([0, 1, 2]) + assert srepr(p2, perm_cyclic=False) == 'Permutation([0, 1, 2])' + assert sstr(p2, perm_cyclic=False) == 'Permutation([0, 1, 2])' + + p3 = Permutation([0, 2, 1]) + assert srepr(p3, perm_cyclic=False) == 'Permutation([0, 2, 1])' + assert sstr(p3, perm_cyclic=False) == 'Permutation([0, 2, 1])' + p4 = Permutation([0, 1, 3, 2, 4, 5, 6, 7]) + assert srepr(p4, perm_cyclic=False) == 'Permutation([0, 1, 3, 2], size=8)' + + +def test_deprecated_print_cyclic(): + p = Permutation(0, 1, 2) + try: + Permutation.print_cyclic = True + with warns_deprecated_sympy(): + assert sstr(p) == '(0 1 2)' + with warns_deprecated_sympy(): + assert srepr(p) == 'Permutation(0, 1, 2)' + with warns_deprecated_sympy(): + assert pretty(p) == '(0 1 2)' + with warns_deprecated_sympy(): + assert latex(p) == r'\left( 0\; 1\; 2\right)' + + Permutation.print_cyclic = False + with warns_deprecated_sympy(): + assert sstr(p) == 'Permutation([1, 2, 0])' + with warns_deprecated_sympy(): + assert srepr(p) == 'Permutation([1, 2, 0])' + with warns_deprecated_sympy(): + assert pretty(p, use_unicode=False) == '/0 1 2\\\n\\1 2 0/' + with warns_deprecated_sympy(): + assert latex(p) == \ + r'\begin{pmatrix} 0 & 1 & 2 \\ 1 & 2 & 0 \end{pmatrix}' + finally: + Permutation.print_cyclic = None + + +def test_permutation_equality(): + a = Permutation(0, 1, 2) + b = Permutation(0, 1, 2) + assert Eq(a, b) is S.true + c = Permutation(0, 2, 1) + assert Eq(a, c) is S.false + + d = Permutation(0, 1, 2, size=4) + assert unchanged(Eq, a, d) + e = Permutation(0, 2, 1, size=4) + assert unchanged(Eq, a, e) + + i = Permutation() + assert unchanged(Eq, i, 0) + assert unchanged(Eq, 0, i) + + +def test_issue_17661(): + c1 = Cycle(1,2) + c2 = Cycle(1,2) + assert c1 == c2 + assert repr(c1) == 'Cycle(1, 2)' + assert c1 == c2 + + +def test_permutation_apply(): + x = Symbol('x') + p = Permutation(0, 1, 2) + assert p.apply(0) == 1 + assert isinstance(p.apply(0), Integer) + assert p.apply(x) == AppliedPermutation(p, x) + assert AppliedPermutation(p, x).subs(x, 0) == 1 + + x = Symbol('x', integer=False) + raises(NotImplementedError, lambda: p.apply(x)) + x = Symbol('x', negative=True) + raises(NotImplementedError, lambda: p.apply(x)) + + +def test_AppliedPermutation(): + x = Symbol('x') + p = Permutation(0, 1, 2) + raises(ValueError, lambda: AppliedPermutation((0, 1, 2), x)) + assert AppliedPermutation(p, 1, evaluate=True) == 2 + assert AppliedPermutation(p, 1, evaluate=False).__class__ == \ + AppliedPermutation diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_polyhedron.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_polyhedron.py new file mode 100644 index 0000000000000000000000000000000000000000..abf469bb560eef1f378eff4740a84b80b696035f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_polyhedron.py @@ -0,0 +1,105 @@ +from sympy.core.symbol import symbols +from sympy.sets.sets import FiniteSet +from sympy.combinatorics.polyhedron import (Polyhedron, + tetrahedron, cube as square, octahedron, dodecahedron, icosahedron, + cube_faces) +from sympy.combinatorics.permutations import Permutation +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.testing.pytest import raises + +rmul = Permutation.rmul + + +def test_polyhedron(): + raises(ValueError, lambda: Polyhedron(list('ab'), + pgroup=[Permutation([0])])) + pgroup = [Permutation([[0, 7, 2, 5], [6, 1, 4, 3]]), + Permutation([[0, 7, 1, 6], [5, 2, 4, 3]]), + Permutation([[3, 6, 0, 5], [4, 1, 7, 2]]), + Permutation([[7, 4, 5], [1, 3, 0], [2], [6]]), + Permutation([[1, 3, 2], [7, 6, 5], [4], [0]]), + Permutation([[4, 7, 6], [2, 0, 3], [1], [5]]), + Permutation([[1, 2, 0], [4, 5, 6], [3], [7]]), + Permutation([[4, 2], [0, 6], [3, 7], [1, 5]]), + Permutation([[3, 5], [7, 1], [2, 6], [0, 4]]), + Permutation([[2, 5], [1, 6], [0, 4], [3, 7]]), + Permutation([[4, 3], [7, 0], [5, 1], [6, 2]]), + Permutation([[4, 1], [0, 5], [6, 2], [7, 3]]), + Permutation([[7, 2], [3, 6], [0, 4], [1, 5]]), + Permutation([0, 1, 2, 3, 4, 5, 6, 7])] + corners = tuple(symbols('A:H')) + faces = cube_faces + cube = Polyhedron(corners, faces, pgroup) + + assert cube.edges == FiniteSet(*( + (0, 1), (6, 7), (1, 2), (5, 6), (0, 3), (2, 3), + (4, 7), (4, 5), (3, 7), (1, 5), (0, 4), (2, 6))) + + for i in range(3): # add 180 degree face rotations + cube.rotate(cube.pgroup[i]**2) + + assert cube.corners == corners + + for i in range(3, 7): # add 240 degree axial corner rotations + cube.rotate(cube.pgroup[i]**2) + + assert cube.corners == corners + cube.rotate(1) + raises(ValueError, lambda: cube.rotate(Permutation([0, 1]))) + assert cube.corners != corners + assert cube.array_form == [7, 6, 4, 5, 3, 2, 0, 1] + assert cube.cyclic_form == [[0, 7, 1, 6], [2, 4, 3, 5]] + cube.reset() + assert cube.corners == corners + + def check(h, size, rpt, target): + + assert len(h.faces) + len(h.vertices) - len(h.edges) == 2 + assert h.size == size + + got = set() + for p in h.pgroup: + # make sure it restores original + P = h.copy() + hit = P.corners + for i in range(rpt): + P.rotate(p) + if P.corners == hit: + break + else: + print('error in permutation', p.array_form) + for i in range(rpt): + P.rotate(p) + got.add(tuple(P.corners)) + c = P.corners + f = [[c[i] for i in f] for f in P.faces] + assert h.faces == Polyhedron(c, f).faces + assert len(got) == target + assert PermutationGroup([Permutation(g) for g in got]).is_group + + for h, size, rpt, target in zip( + (tetrahedron, square, octahedron, dodecahedron, icosahedron), + (4, 8, 6, 20, 12), + (3, 4, 4, 5, 5), + (12, 24, 24, 60, 60)): + check(h, size, rpt, target) + + +def test_pgroups(): + from sympy.combinatorics.polyhedron import (cube, tetrahedron_faces, + octahedron_faces, dodecahedron_faces, icosahedron_faces) + from sympy.combinatorics.polyhedron import _pgroup_calcs + (tetrahedron2, cube2, octahedron2, dodecahedron2, icosahedron2, + tetrahedron_faces2, cube_faces2, octahedron_faces2, + dodecahedron_faces2, icosahedron_faces2) = _pgroup_calcs() + + assert tetrahedron == tetrahedron2 + assert cube == cube2 + assert octahedron == octahedron2 + assert dodecahedron == dodecahedron2 + assert icosahedron == icosahedron2 + assert sorted(map(sorted, tetrahedron_faces)) == sorted(map(sorted, tetrahedron_faces2)) + assert sorted(cube_faces) == sorted(cube_faces2) + assert sorted(octahedron_faces) == sorted(octahedron_faces2) + assert sorted(dodecahedron_faces) == sorted(dodecahedron_faces2) + assert sorted(icosahedron_faces) == sorted(icosahedron_faces2) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_prufer.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_prufer.py new file mode 100644 index 0000000000000000000000000000000000000000..b077c7cf3f023a4c36d7039505e6165ab29f275a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_prufer.py @@ -0,0 +1,74 @@ +from sympy.combinatorics.prufer import Prufer +from sympy.testing.pytest import raises + + +def test_prufer(): + # number of nodes is optional + assert Prufer([[0, 1], [0, 2], [0, 3], [0, 4]], 5).nodes == 5 + assert Prufer([[0, 1], [0, 2], [0, 3], [0, 4]]).nodes == 5 + + a = Prufer([[0, 1], [0, 2], [0, 3], [0, 4]]) + assert a.rank == 0 + assert a.nodes == 5 + assert a.prufer_repr == [0, 0, 0] + + a = Prufer([[2, 4], [1, 4], [1, 3], [0, 5], [0, 4]]) + assert a.rank == 924 + assert a.nodes == 6 + assert a.tree_repr == [[2, 4], [1, 4], [1, 3], [0, 5], [0, 4]] + assert a.prufer_repr == [4, 1, 4, 0] + + assert Prufer.edges([0, 1, 2, 3], [1, 4, 5], [1, 4, 6]) == \ + ([[0, 1], [1, 2], [1, 4], [2, 3], [4, 5], [4, 6]], 7) + assert Prufer([0]*4).size == Prufer([6]*4).size == 1296 + + # accept iterables but convert to list of lists + tree = [(0, 1), (1, 5), (0, 3), (0, 2), (2, 6), (4, 7), (2, 4)] + tree_lists = [list(t) for t in tree] + assert Prufer(tree).tree_repr == tree_lists + assert sorted(Prufer(set(tree)).tree_repr) == sorted(tree_lists) + + raises(ValueError, lambda: Prufer([[1, 2], [3, 4]])) # 0 is missing + raises(ValueError, lambda: Prufer([[2, 3], [3, 4]])) # 0, 1 are missing + assert Prufer(*Prufer.edges([1, 2], [3, 4])).prufer_repr == [1, 3] + raises(ValueError, lambda: Prufer.edges( + [1, 3], [3, 4])) # a broken tree but edges doesn't care + raises(ValueError, lambda: Prufer.edges([1, 2], [5, 6])) + raises(ValueError, lambda: Prufer([[]])) + + a = Prufer([[0, 1], [0, 2], [0, 3]]) + b = a.next() + assert b.tree_repr == [[0, 2], [0, 1], [1, 3]] + assert b.rank == 1 + + +def test_round_trip(): + def doit(t, b): + e, n = Prufer.edges(*t) + t = Prufer(e, n) + a = sorted(t.tree_repr) + b = [i - 1 for i in b] + assert t.prufer_repr == b + assert sorted(Prufer(b).tree_repr) == a + assert Prufer.unrank(t.rank, n).prufer_repr == b + + doit([[1, 2]], []) + doit([[2, 1, 3]], [1]) + doit([[1, 3, 2]], [3]) + doit([[1, 2, 3]], [2]) + doit([[2, 1, 4], [1, 3]], [1, 1]) + doit([[3, 2, 1, 4]], [2, 1]) + doit([[3, 2, 1], [2, 4]], [2, 2]) + doit([[1, 3, 2, 4]], [3, 2]) + doit([[1, 4, 2, 3]], [4, 2]) + doit([[3, 1, 4, 2]], [4, 1]) + doit([[4, 2, 1, 3]], [1, 2]) + doit([[1, 2, 4, 3]], [2, 4]) + doit([[1, 3, 4, 2]], [3, 4]) + doit([[2, 4, 1], [4, 3]], [4, 4]) + doit([[1, 2, 3, 4]], [2, 3]) + doit([[2, 3, 1], [3, 4]], [3, 3]) + doit([[1, 4, 3, 2]], [4, 3]) + doit([[2, 1, 4, 3]], [1, 4]) + doit([[2, 1, 3, 4]], [1, 3]) + doit([[6, 2, 1, 4], [1, 3, 5, 8], [3, 7]], [1, 2, 1, 3, 3, 5]) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_rewriting.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_rewriting.py new file mode 100644 index 0000000000000000000000000000000000000000..97c562bd57a2cd6318fa1dcb13c6f6278c861cca --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_rewriting.py @@ -0,0 +1,49 @@ +from sympy.combinatorics.fp_groups import FpGroup +from sympy.combinatorics.free_groups import free_group +from sympy.testing.pytest import raises + + +def test_rewriting(): + F, a, b = free_group("a, b") + G = FpGroup(F, [a*b*a**-1*b**-1]) + a, b = G.generators + R = G._rewriting_system + assert R.is_confluent + + assert G.reduce(b**-1*a) == a*b**-1 + assert G.reduce(b**3*a**4*b**-2*a) == a**5*b + assert G.equals(b**2*a**-1*b, b**4*a**-1*b**-1) + + assert R.reduce_using_automaton(b*a*a**2*b**-1) == a**3 + assert R.reduce_using_automaton(b**3*a**4*b**-2*a) == a**5*b + assert R.reduce_using_automaton(b**-1*a) == a*b**-1 + + G = FpGroup(F, [a**3, b**3, (a*b)**2]) + R = G._rewriting_system + R.make_confluent() + # R._is_confluent should be set to True after + # a successful run of make_confluent + assert R.is_confluent + # but also the system should actually be confluent + assert R._check_confluence() + assert G.reduce(b*a**-1*b**-1*a**3*b**4*a**-1*b**-15) == a**-1*b**-1 + # check for automaton reduction + assert R.reduce_using_automaton(b*a**-1*b**-1*a**3*b**4*a**-1*b**-15) == a**-1*b**-1 + + G = FpGroup(F, [a**2, b**3, (a*b)**4]) + R = G._rewriting_system + assert G.reduce(a**2*b**-2*a**2*b) == b**-1 + assert R.reduce_using_automaton(a**2*b**-2*a**2*b) == b**-1 + assert G.reduce(a**3*b**-2*a**2*b) == a**-1*b**-1 + assert R.reduce_using_automaton(a**3*b**-2*a**2*b) == a**-1*b**-1 + # Check after adding a rule + R.add_rule(a**2, b) + assert R.reduce_using_automaton(a**2*b**-2*a**2*b) == b**-1 + assert R.reduce_using_automaton(a**4*b**-2*a**2*b**3) == b + + R.set_max(15) + raises(RuntimeError, lambda: R.add_rule(a**-3, b)) + R.set_max(20) + R.add_rule(a**-3, b) + + assert R.add_rule(a, a) == set() diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_schur_number.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_schur_number.py new file mode 100644 index 0000000000000000000000000000000000000000..e6beb9b11fa993a99b71d89b8485050fc3575b8e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_schur_number.py @@ -0,0 +1,55 @@ +from sympy.core import S, Rational +from sympy.combinatorics.schur_number import schur_partition, SchurNumber +from sympy.core.random import _randint +from sympy.testing.pytest import raises +from sympy.core.symbol import symbols + + +def _sum_free_test(subset): + """ + Checks if subset is sum-free(There are no x,y,z in the subset such that + x + y = z) + """ + for i in subset: + for j in subset: + assert (i + j in subset) is False + + +def test_schur_partition(): + raises(ValueError, lambda: schur_partition(S.Infinity)) + raises(ValueError, lambda: schur_partition(-1)) + raises(ValueError, lambda: schur_partition(0)) + assert schur_partition(2) == [[1, 2]] + + random_number_generator = _randint(1000) + for _ in range(5): + n = random_number_generator(1, 1000) + result = schur_partition(n) + t = 0 + numbers = [] + for item in result: + _sum_free_test(item) + """ + Checks if the occurrence of all numbers is exactly one + """ + t += len(item) + for l in item: + assert (l in numbers) is False + numbers.append(l) + assert n == t + + x = symbols("x") + raises(ValueError, lambda: schur_partition(x)) + +def test_schur_number(): + first_known_schur_numbers = {1: 1, 2: 4, 3: 13, 4: 44, 5: 160} + for k in first_known_schur_numbers: + assert SchurNumber(k) == first_known_schur_numbers[k] + + assert SchurNumber(S.Infinity) == S.Infinity + assert SchurNumber(0) == 0 + raises(ValueError, lambda: SchurNumber(0.5)) + + n = symbols("n") + assert SchurNumber(n).lower_bound() == 3**n/2 - Rational(1, 2) + assert SchurNumber(8).lower_bound() == 5039 diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_subsets.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_subsets.py new file mode 100644 index 0000000000000000000000000000000000000000..1d50076da1c685294c2d2561dcc2a6af629eaf83 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_subsets.py @@ -0,0 +1,63 @@ +from sympy.combinatorics.subsets import Subset, ksubsets +from sympy.testing.pytest import raises + + +def test_subset(): + a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) + assert a.next_binary() == Subset(['b'], ['a', 'b', 'c', 'd']) + assert a.prev_binary() == Subset(['c'], ['a', 'b', 'c', 'd']) + assert a.next_lexicographic() == Subset(['d'], ['a', 'b', 'c', 'd']) + assert a.prev_lexicographic() == Subset(['c'], ['a', 'b', 'c', 'd']) + assert a.next_gray() == Subset(['c'], ['a', 'b', 'c', 'd']) + assert a.prev_gray() == Subset(['d'], ['a', 'b', 'c', 'd']) + assert a.rank_binary == 3 + assert a.rank_lexicographic == 14 + assert a.rank_gray == 2 + assert a.cardinality == 16 + assert a.size == 2 + assert Subset.bitlist_from_subset(a, ['a', 'b', 'c', 'd']) == '0011' + + a = Subset([2, 5, 7], [1, 2, 3, 4, 5, 6, 7]) + assert a.next_binary() == Subset([2, 5, 6], [1, 2, 3, 4, 5, 6, 7]) + assert a.prev_binary() == Subset([2, 5], [1, 2, 3, 4, 5, 6, 7]) + assert a.next_lexicographic() == Subset([2, 6], [1, 2, 3, 4, 5, 6, 7]) + assert a.prev_lexicographic() == Subset([2, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7]) + assert a.next_gray() == Subset([2, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7]) + assert a.prev_gray() == Subset([2, 5], [1, 2, 3, 4, 5, 6, 7]) + assert a.rank_binary == 37 + assert a.rank_lexicographic == 93 + assert a.rank_gray == 57 + assert a.cardinality == 128 + + superset = ['a', 'b', 'c', 'd'] + assert Subset.unrank_binary(4, superset).rank_binary == 4 + assert Subset.unrank_gray(10, superset).rank_gray == 10 + + superset = [1, 2, 3, 4, 5, 6, 7, 8, 9] + assert Subset.unrank_binary(33, superset).rank_binary == 33 + assert Subset.unrank_gray(25, superset).rank_gray == 25 + + a = Subset([], ['a', 'b', 'c', 'd']) + i = 1 + while a.subset != Subset(['d'], ['a', 'b', 'c', 'd']).subset: + a = a.next_lexicographic() + i = i + 1 + assert i == 16 + + i = 1 + while a.subset != Subset([], ['a', 'b', 'c', 'd']).subset: + a = a.prev_lexicographic() + i = i + 1 + assert i == 16 + + raises(ValueError, lambda: Subset(['a', 'b'], ['a'])) + raises(ValueError, lambda: Subset(['a'], ['b', 'c'])) + raises(ValueError, lambda: Subset.subset_from_bitlist(['a', 'b'], '010')) + + assert Subset(['a'], ['a', 'b']) != Subset(['b'], ['a', 'b']) + assert Subset(['a'], ['a', 'b']) != Subset(['a'], ['a', 'c']) + +def test_ksubsets(): + assert list(ksubsets([1, 2, 3], 2)) == [(1, 2), (1, 3), (2, 3)] + assert list(ksubsets([1, 2, 3, 4, 5], 2)) == [(1, 2), (1, 3), (1, 4), + (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)] diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_tensor_can.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_tensor_can.py new file mode 100644 index 0000000000000000000000000000000000000000..3922419f20b92536426bfaae4b7e94df5db671b5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_tensor_can.py @@ -0,0 +1,560 @@ +from sympy.combinatorics.permutations import Permutation, Perm +from sympy.combinatorics.tensor_can import (perm_af_direct_product, dummy_sgs, + riemann_bsgs, get_symmetric_group_sgs, canonicalize, bsgs_direct_product) +from sympy.combinatorics.testutil import canonicalize_naive, graph_certificate +from sympy.testing.pytest import skip, XFAIL + +def test_perm_af_direct_product(): + gens1 = [[1,0,2,3], [0,1,3,2]] + gens2 = [[1,0]] + assert perm_af_direct_product(gens1, gens2, 0) == [[1, 0, 2, 3, 4, 5], [0, 1, 3, 2, 4, 5], [0, 1, 2, 3, 5, 4]] + gens1 = [[1,0,2,3,5,4], [0,1,3,2,4,5]] + gens2 = [[1,0,2,3]] + assert [[1, 0, 2, 3, 4, 5, 7, 6], [0, 1, 3, 2, 4, 5, 6, 7], [0, 1, 2, 3, 5, 4, 6, 7]] + +def test_dummy_sgs(): + a = dummy_sgs([1,2], 0, 4) + assert a == [[0,2,1,3,4,5]] + a = dummy_sgs([2,3,4,5], 0, 8) + assert a == [x._array_form for x in [Perm(9)(2,3), Perm(9)(4,5), + Perm(9)(2,4)(3,5)]] + + a = dummy_sgs([2,3,4,5], 1, 8) + assert a == [x._array_form for x in [Perm(2,3)(8,9), Perm(4,5)(8,9), + Perm(9)(2,4)(3,5)]] + +def test_get_symmetric_group_sgs(): + assert get_symmetric_group_sgs(2) == ([0], [Permutation(3)(0,1)]) + assert get_symmetric_group_sgs(2, 1) == ([0], [Permutation(0,1)(2,3)]) + assert get_symmetric_group_sgs(3) == ([0,1], [Permutation(4)(0,1), Permutation(4)(1,2)]) + assert get_symmetric_group_sgs(3, 1) == ([0,1], [Permutation(0,1)(3,4), Permutation(1,2)(3,4)]) + assert get_symmetric_group_sgs(4) == ([0,1,2], [Permutation(5)(0,1), Permutation(5)(1,2), Permutation(5)(2,3)]) + assert get_symmetric_group_sgs(4, 1) == ([0,1,2], [Permutation(0,1)(4,5), Permutation(1,2)(4,5), Permutation(2,3)(4,5)]) + + +def test_canonicalize_no_slot_sym(): + # cases in which there is no slot symmetry after fixing the + # free indices; here and in the following if the symmetry of the + # metric is not specified, it is assumed to be symmetric. + # If it is not specified, tensors are commuting. + + # A_d0 * B^d0; g = [1,0, 2,3]; T_c = A^d0*B_d0; can = [0,1,2,3] + base1, gens1 = get_symmetric_group_sgs(1) + dummies = [0, 1] + g = Permutation([1,0,2,3]) + can = canonicalize(g, dummies, 0, (base1,gens1,1,0), (base1,gens1,1,0)) + assert can == [0,1,2,3] + # equivalently + can = canonicalize(g, dummies, 0, (base1, gens1, 2, None)) + assert can == [0,1,2,3] + + # with antisymmetric metric; T_c = -A^d0*B_d0; can = [0,1,3,2] + can = canonicalize(g, dummies, 1, (base1,gens1,1,0), (base1,gens1,1,0)) + assert can == [0,1,3,2] + + # A^a * B^b; ord = [a,b]; g = [0,1,2,3]; can = g + g = Permutation([0,1,2,3]) + dummies = [] + t0 = t1 = (base1, gens1, 1, 0) + can = canonicalize(g, dummies, 0, t0, t1) + assert can == [0,1,2,3] + # B^b * A^a + g = Permutation([1,0,2,3]) + can = canonicalize(g, dummies, 0, t0, t1) + assert can == [1,0,2,3] + + # A symmetric + # A^{b}_{d0}*A^{d0, a} order a,b,d0,-d0; T_c = A^{a d0}*A{b}_{d0} + # g = [1,3,2,0,4,5]; can = [0,2,1,3,4,5] + base2, gens2 = get_symmetric_group_sgs(2) + dummies = [2,3] + g = Permutation([1,3,2,0,4,5]) + can = canonicalize(g, dummies, 0, (base2, gens2, 2, 0)) + assert can == [0, 2, 1, 3, 4, 5] + # with antisymmetric metric + can = canonicalize(g, dummies, 1, (base2, gens2, 2, 0)) + assert can == [0, 2, 1, 3, 4, 5] + # A^{a}_{d0}*A^{d0, b} + g = Permutation([0,3,2,1,4,5]) + can = canonicalize(g, dummies, 1, (base2, gens2, 2, 0)) + assert can == [0, 2, 1, 3, 5, 4] + + # A, B symmetric + # A^b_d0*B^{d0,a}; g=[1,3,2,0,4,5] + # T_c = A^{b,d0}*B_{a,d0}; can = [1,2,0,3,4,5] + dummies = [2,3] + g = Permutation([1,3,2,0,4,5]) + can = canonicalize(g, dummies, 0, (base2,gens2,1,0), (base2,gens2,1,0)) + assert can == [1,2,0,3,4,5] + # same with antisymmetric metric + can = canonicalize(g, dummies, 1, (base2,gens2,1,0), (base2,gens2,1,0)) + assert can == [1,2,0,3,5,4] + + # A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5] + # T_c = A^{d0 d1}*B_d0*C_d1; can = [0,2,1,3,4,5] + base1, gens1 = get_symmetric_group_sgs(1) + base2, gens2 = get_symmetric_group_sgs(2) + g = Permutation([2,1,0,3,4,5]) + dummies = [0,1,2,3] + t0 = (base2, gens2, 1, 0) + t1 = t2 = (base1, gens1, 1, 0) + can = canonicalize(g, dummies, 0, t0, t1, t2) + assert can == [0, 2, 1, 3, 4, 5] + + # A without symmetry + # A^{d1}_{d0}*B^d0*C_d1 ord=[d0,-d0,d1,-d1]; g = [2,1,0,3,4,5] + # T_c = A^{d0 d1}*B_d1*C_d0; can = [0,2,3,1,4,5] + g = Permutation([2,1,0,3,4,5]) + dummies = [0,1,2,3] + t0 = ([], [Permutation(list(range(4)))], 1, 0) + can = canonicalize(g, dummies, 0, t0, t1, t2) + assert can == [0,2,3,1,4,5] + # A, B without symmetry + # A^{d1}_{d0}*B_{d1}^{d0}; g = [2,1,3,0,4,5] + # T_c = A^{d0 d1}*B_{d0 d1}; can = [0,2,1,3,4,5] + t0 = t1 = ([], [Permutation(list(range(4)))], 1, 0) + dummies = [0,1,2,3] + g = Permutation([2,1,3,0,4,5]) + can = canonicalize(g, dummies, 0, t0, t1) + assert can == [0, 2, 1, 3, 4, 5] + # A_{d0}^{d1}*B_{d1}^{d0}; g = [1,2,3,0,4,5] + # T_c = A^{d0 d1}*B_{d1 d0}; can = [0,2,3,1,4,5] + g = Permutation([1,2,3,0,4,5]) + can = canonicalize(g, dummies, 0, t0, t1) + assert can == [0,2,3,1,4,5] + + # A, B, C without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1] + # g=[4,2,0,3,5,1,6,7] + # T_c=A^{d0 d1}*B_{a d1}*C_{d0 b}; can = [2,4,0,5,3,1,6,7] + t0 = t1 = t2 = ([], [Permutation(list(range(4)))], 1, 0) + dummies = [2,3,4,5] + g = Permutation([4,2,0,3,5,1,6,7]) + can = canonicalize(g, dummies, 0, t0, t1, t2) + assert can == [2,4,0,5,3,1,6,7] + + # A symmetric, B and C without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1] + # g=[4,2,0,3,5,1,6,7] + # T_c = A^{d0 d1}*B_{a d0}*C_{d1 b}; can = [2,4,0,3,5,1,6,7] + t0 = (base2,gens2,1,0) + t1 = t2 = ([], [Permutation(list(range(4)))], 1, 0) + dummies = [2,3,4,5] + g = Permutation([4,2,0,3,5,1,6,7]) + can = canonicalize(g, dummies, 0, t0, t1, t2) + assert can == [2,4,0,3,5,1,6,7] + + # A and C symmetric, B without symmetry + # A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1] + # g=[4,2,0,3,5,1,6,7] + # T_c = A^{d0 d1}*B_{a d0}*C_{b d1}; can = [2,4,0,3,1,5,6,7] + t0 = t2 = (base2,gens2,1,0) + t1 = ([], [Permutation(list(range(4)))], 1, 0) + dummies = [2,3,4,5] + g = Permutation([4,2,0,3,5,1,6,7]) + can = canonicalize(g, dummies, 0, t0, t1, t2) + assert can == [2,4,0,3,1,5,6,7] + + # A symmetric, B without symmetry, C antisymmetric + # A^{d1 d0}*B_{a d0}*C_{d1 b} ord=[a,b,d0,-d0,d1,-d1] + # g=[4,2,0,3,5,1,6,7] + # T_c = -A^{d0 d1}*B_{a d0}*C_{b d1}; can = [2,4,0,3,1,5,7,6] + t0 = (base2,gens2, 1, 0) + t1 = ([], [Permutation(list(range(4)))], 1, 0) + base2a, gens2a = get_symmetric_group_sgs(2, 1) + t2 = (base2a, gens2a, 1, 0) + dummies = [2,3,4,5] + g = Permutation([4,2,0,3,5,1,6,7]) + can = canonicalize(g, dummies, 0, t0, t1, t2) + assert can == [2,4,0,3,1,5,7,6] + + +def test_canonicalize_no_dummies(): + base1, gens1 = get_symmetric_group_sgs(1) + base2, gens2 = get_symmetric_group_sgs(2) + base2a, gens2a = get_symmetric_group_sgs(2, 1) + + # A commuting + # A^c A^b A^a; ord = [a,b,c]; g = [2,1,0,3,4] + # T_c = A^a A^b A^c; can = list(range(5)) + g = Permutation([2,1,0,3,4]) + can = canonicalize(g, [], 0, (base1, gens1, 3, 0)) + assert can == list(range(5)) + + # A anticommuting + # A^c A^b A^a; ord = [a,b,c]; g = [2,1,0,3,4] + # T_c = -A^a A^b A^c; can = [0,1,2,4,3] + g = Permutation([2,1,0,3,4]) + can = canonicalize(g, [], 0, (base1, gens1, 3, 1)) + assert can == [0,1,2,4,3] + + # A commuting and symmetric + # A^{b,d}*A^{c,a}; ord = [a,b,c,d]; g = [1,3,2,0,4,5] + # T_c = A^{a c}*A^{b d}; can = [0,2,1,3,4,5] + g = Permutation([1,3,2,0,4,5]) + can = canonicalize(g, [], 0, (base2, gens2, 2, 0)) + assert can == [0,2,1,3,4,5] + + # A anticommuting and symmetric + # A^{b,d}*A^{c,a}; ord = [a,b,c,d]; g = [1,3,2,0,4,5] + # T_c = -A^{a c}*A^{b d}; can = [0,2,1,3,5,4] + g = Permutation([1,3,2,0,4,5]) + can = canonicalize(g, [], 0, (base2, gens2, 2, 1)) + assert can == [0,2,1,3,5,4] + # A^{c,a}*A^{b,d} ; g = [2,0,1,3,4,5] + # T_c = A^{a c}*A^{b d}; can = [0,2,1,3,4,5] + g = Permutation([2,0,1,3,4,5]) + can = canonicalize(g, [], 0, (base2, gens2, 2, 1)) + assert can == [0,2,1,3,4,5] + +def test_no_metric_symmetry(): + # no metric symmetry + # A^d1_d0 * A^d0_d1; ord = [d0,-d0,d1,-d1]; g= [2,1,0,3,4,5] + # T_c = A^d0_d1 * A^d1_d0; can = [0,3,2,1,4,5] + g = Permutation([2,1,0,3,4,5]) + can = canonicalize(g, list(range(4)), None, [[], [Permutation(list(range(4)))], 2, 0]) + assert can == [0,3,2,1,4,5] + + # A^d1_d2 * A^d0_d3 * A^d2_d1 * A^d3_d0 + # ord = [d0,-d0,d1,-d1,d2,-d2,d3,-d3] + # 0 1 2 3 4 5 6 7 + # g = [2,5,0,7,4,3,6,1,8,9] + # T_c = A^d0_d1 * A^d1_d0 * A^d2_d3 * A^d3_d2 + # can = [0,3,2,1,4,7,6,5,8,9] + g = Permutation([2,5,0,7,4,3,6,1,8,9]) + #can = canonicalize(g, list(range(8)), 0, [[], [list(range(4))], 4, 0]) + #assert can == [0, 2, 3, 1, 4, 6, 7, 5, 8, 9] + can = canonicalize(g, list(range(8)), None, [[], [Permutation(list(range(4)))], 4, 0]) + assert can == [0, 3, 2, 1, 4, 7, 6, 5, 8, 9] + + # A^d0_d2 * A^d1_d3 * A^d3_d0 * A^d2_d1 + # g = [0,5,2,7,6,1,4,3,8,9] + # T_c = A^d0_d1 * A^d1_d2 * A^d2_d3 * A^d3_d0 + # can = [0,3,2,5,4,7,6,1,8,9] + g = Permutation([0,5,2,7,6,1,4,3,8,9]) + can = canonicalize(g, list(range(8)), None, [[], [Permutation(list(range(4)))], 4, 0]) + assert can == [0,3,2,5,4,7,6,1,8,9] + + g = Permutation([12,7,10,3,14,13,4,11,6,1,2,9,0,15,8,5,16,17]) + can = canonicalize(g, list(range(16)), None, [[], [Permutation(list(range(4)))], 8, 0]) + assert can == [0,3,2,5,4,7,6,1,8,11,10,13,12,15,14,9,16,17] + +def test_canonical_free(): + # t = A^{d0 a1}*A_d0^a0 + # ord = [a0,a1,d0,-d0]; g = [2,1,3,0,4,5]; dummies = [[2,3]] + # t_c = A_d0^a0*A^{d0 a1} + # can = [3,0, 2,1, 4,5] + g = Permutation([2,1,3,0,4,5]) + dummies = [[2,3]] + can = canonicalize(g, dummies, [None], ([], [Permutation(3)], 2, 0)) + assert can == [3,0, 2,1, 4,5] + +def test_canonicalize1(): + base1, gens1 = get_symmetric_group_sgs(1) + base1a, gens1a = get_symmetric_group_sgs(1, 1) + base2, gens2 = get_symmetric_group_sgs(2) + base3, gens3 = get_symmetric_group_sgs(3) + base2a, gens2a = get_symmetric_group_sgs(2, 1) + base3a, gens3a = get_symmetric_group_sgs(3, 1) + + # A_d0*A^d0; ord = [d0,-d0]; g = [1,0,2,3] + # T_c = A^d0*A_d0; can = [0,1,2,3] + g = Permutation([1,0,2,3]) + can = canonicalize(g, [0, 1], 0, (base1, gens1, 2, 0)) + assert can == list(range(4)) + + # A commuting + # A_d0*A_d1*A_d2*A^d2*A^d1*A^d0; ord=[d0,-d0,d1,-d1,d2,-d2] + # g = [1,3,5,4,2,0,6,7] + # T_c = A^d0*A_d0*A^d1*A_d1*A^d2*A_d2; can = list(range(8)) + g = Permutation([1,3,5,4,2,0,6,7]) + can = canonicalize(g, list(range(6)), 0, (base1, gens1, 6, 0)) + assert can == list(range(8)) + + # A anticommuting + # A_d0*A_d1*A_d2*A^d2*A^d1*A^d0; ord=[d0,-d0,d1,-d1,d2,-d2] + # g = [1,3,5,4,2,0,6,7] + # T_c 0; can = 0 + g = Permutation([1,3,5,4,2,0,6,7]) + can = canonicalize(g, list(range(6)), 0, (base1, gens1, 6, 1)) + assert can == 0 + can1 = canonicalize_naive(g, list(range(6)), 0, (base1, gens1, 6, 1)) + assert can1 == 0 + + # A commuting symmetric + # A^{d0 b}*A^a_d1*A^d1_d0; ord=[a,b,d0,-d0,d1,-d1] + # g = [2,1,0,5,4,3,6,7] + # T_c = A^{a d0}*A^{b d1}*A_{d0 d1}; can = [0,2,1,4,3,5,6,7] + g = Permutation([2,1,0,5,4,3,6,7]) + can = canonicalize(g, list(range(2,6)), 0, (base2, gens2, 3, 0)) + assert can == [0,2,1,4,3,5,6,7] + + # A, B commuting symmetric + # A^{d0 b}*A^d1_d0*B^a_d1; ord=[a,b,d0,-d0,d1,-d1] + # g = [2,1,4,3,0,5,6,7] + # T_c = A^{b d0}*A_d0^d1*B^a_d1; can = [1,2,3,4,0,5,6,7] + g = Permutation([2,1,4,3,0,5,6,7]) + can = canonicalize(g, list(range(2,6)), 0, (base2,gens2,2,0), (base2,gens2,1,0)) + assert can == [1,2,3,4,0,5,6,7] + + # A commuting symmetric + # A^{d1 d0 b}*A^{a}_{d1 d0}; ord=[a,b, d0,-d0,d1,-d1] + # g = [4,2,1,0,5,3,6,7] + # T_c = A^{a d0 d1}*A^{b}_{d0 d1}; can = [0,2,4,1,3,5,6,7] + g = Permutation([4,2,1,0,5,3,6,7]) + can = canonicalize(g, list(range(2,6)), 0, (base3, gens3, 2, 0)) + assert can == [0,2,4,1,3,5,6,7] + + + # A^{d3 d0 d2}*A^a0_{d1 d2}*A^d1_d3^a1*A^{a2 a3}_d0 + # ord = [a0,a1,a2,a3,d0,-d0,d1,-d1,d2,-d2,d3,-d3] + # 0 1 2 3 4 5 6 7 8 9 10 11 + # g = [10,4,8, 0,7,9, 6,11,1, 2,3,5, 12,13] + # T_c = A^{a0 d0 d1}*A^a1_d0^d2*A^{a2 a3 d3}*A_{d1 d2 d3} + # can = [0,4,6, 1,5,8, 2,3,10, 7,9,11, 12,13] + g = Permutation([10,4,8, 0,7,9, 6,11,1, 2,3,5, 12,13]) + can = canonicalize(g, list(range(4,12)), 0, (base3, gens3, 4, 0)) + assert can == [0,4,6, 1,5,8, 2,3,10, 7,9,11, 12,13] + + # A commuting symmetric, B antisymmetric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # ord = [d0,-d0,d1,-d1,d2,-d2,d3,-d3] + # g = [0,2,4,5,7,3,1,6,8,9] + # in this esxample and in the next three, + # renaming dummy indices and using symmetry of A, + # T = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 + # can = 0 + g = Permutation([0,2,4,5,7,3,1,6,8,9]) + can = canonicalize(g, list(range(8)), 0, (base3, gens3,2,0), (base2a,gens2a,1,0)) + assert can == 0 + # A anticommuting symmetric, B anticommuting + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} + # can = [0,2,4, 1,3,6, 5,7, 8,9] + can = canonicalize(g, list(range(8)), 0, (base3, gens3,2,1), (base2a,gens2a,1,0)) + assert can == [0,2,4, 1,3,6, 5,7, 8,9] + # A anticommuting symmetric, B antisymmetric commuting, antisymmetric metric + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = -A^{d0 d1 d2} * A_{d0 d1}^d3 * B_{d2 d3} + # can = [0,2,4, 1,3,6, 5,7, 9,8] + can = canonicalize(g, list(range(8)), 1, (base3, gens3,2,1), (base2a,gens2a,1,0)) + assert can == [0,2,4, 1,3,6, 5,7, 9,8] + + # A anticommuting symmetric, B anticommuting anticommuting, + # no metric symmetry + # A^{d0 d1 d2} * A_{d2 d3 d1} * B_d0^d3 + # T_c = A^{d0 d1 d2} * A_{d0 d1 d3} * B_d2^d3 + # can = [0,2,4, 1,3,7, 5,6, 8,9] + can = canonicalize(g, list(range(8)), None, (base3, gens3,2,1), (base2a,gens2a,1,0)) + assert can == [0,2,4,1,3,7,5,6,8,9] + + # Gamma anticommuting + # Gamma_{mu nu} * gamma^rho * Gamma^{nu mu alpha} + # ord = [alpha, rho, mu,-mu,nu,-nu] + # g = [3,5,1,4,2,0,6,7] + # T_c = -Gamma^{mu nu} * gamma^rho * Gamma_{alpha mu nu} + # can = [2,4,1,0,3,5,7,6]] + g = Permutation([3,5,1,4,2,0,6,7]) + t0 = (base2a, gens2a, 1, None) + t1 = (base1, gens1, 1, None) + t2 = (base3a, gens3a, 1, None) + can = canonicalize(g, list(range(2, 6)), 0, t0, t1, t2) + assert can == [2,4,1,0,3,5,7,6] + + # Gamma_{mu nu} * Gamma^{gamma beta} * gamma_rho * Gamma^{nu mu alpha} + # ord = [alpha, beta, gamma, -rho, mu,-mu,nu,-nu] + # 0 1 2 3 4 5 6 7 + # g = [5,7,2,1,3,6,4,0,8,9] + # T_c = Gamma^{mu nu} * Gamma^{beta gamma} * gamma_rho * Gamma^alpha_{mu nu} # can = [4,6,1,2,3,0,5,7,8,9] + t0 = (base2a, gens2a, 2, None) + g = Permutation([5,7,2,1,3,6,4,0,8,9]) + can = canonicalize(g, list(range(4, 8)), 0, t0, t1, t2) + assert can == [4,6,1,2,3,0,5,7,8,9] + + # f^a_{b,c} antisymmetric in b,c; A_mu^a no symmetry + # f^c_{d a} * f_{c e b} * A_mu^d * A_nu^a * A^{nu e} * A^{mu b} + # ord = [mu,-mu,nu,-nu,a,-a,b,-b,c,-c,d,-d, e, -e] + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + # g = [8,11,5, 9,13,7, 1,10, 3,4, 2,12, 0,6, 14,15] + # T_c = -f^{a b c} * f_a^{d e} * A^mu_b * A_{mu d} * A^nu_c * A_{nu e} + # can = [4,6,8, 5,10,12, 0,7, 1,11, 2,9, 3,13, 15,14] + g = Permutation([8,11,5, 9,13,7, 1,10, 3,4, 2,12, 0,6, 14,15]) + base_f, gens_f = bsgs_direct_product(base1, gens1, base2a, gens2a) + base_A, gens_A = bsgs_direct_product(base1, gens1, base1, gens1) + t0 = (base_f, gens_f, 2, 0) + t1 = (base_A, gens_A, 4, 0) + can = canonicalize(g, [list(range(4)), list(range(4, 14))], [0, 0], t0, t1) + assert can == [4,6,8, 5,10,12, 0,7, 1,11, 2,9, 3,13, 15,14] + + +def test_riemann_invariants(): + baser, gensr = riemann_bsgs + # R^{d0 d1}_{d1 d0}; ord = [d0,-d0,d1,-d1]; g = [0,2,3,1,4,5] + # T_c = -R^{d0 d1}_{d0 d1}; can = [0,2,1,3,5,4] + g = Permutation([0,2,3,1,4,5]) + can = canonicalize(g, list(range(2, 4)), 0, (baser, gensr, 1, 0)) + assert can == [0,2,1,3,5,4] + # use a non minimal BSGS + can = canonicalize(g, list(range(2, 4)), 0, ([2, 0], [Permutation([1,0,2,3,5,4]), Permutation([2,3,0,1,4,5])], 1, 0)) + assert can == [0,2,1,3,5,4] + + """ + The following tests in test_riemann_invariants and in + test_riemann_invariants1 have been checked using xperm.c from XPerm in + in [1] and with an older version contained in [2] + + [1] xperm.c part of xPerm written by J. M. Martin-Garcia + http://www.xact.es/index.html + [2] test_xperm.cc in cadabra by Kasper Peeters, http://cadabra.phi-sci.com/ + """ + # R_d11^d1_d0^d5 * R^{d6 d4 d0}_d5 * R_{d7 d2 d8 d9} * + # R_{d10 d3 d6 d4} * R^{d2 d7 d11}_d1 * R^{d8 d9 d3 d10} + # ord: contravariant d_k ->2*k, covariant d_k -> 2*k+1 + # T_c = R^{d0 d1 d2 d3} * R_{d0 d1}^{d4 d5} * R_{d2 d3}^{d6 d7} * + # R_{d4 d5}^{d8 d9} * R_{d6 d7}^{d10 d11} * R_{d8 d9 d10 d11} + g = Permutation([23,2,1,10,12,8,0,11,15,5,17,19,21,7,13,9,4,14,22,3,16,18,6,20,24,25]) + can = canonicalize(g, list(range(24)), 0, (baser, gensr, 6, 0)) + assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,21,23,24,25] + + # use a non minimal BSGS + can = canonicalize(g, list(range(24)), 0, ([2, 0], [Permutation([1,0,2,3,5,4]), Permutation([2,3,0,1,4,5])], 6, 0)) + assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,21,23,24,25] + + g = Permutation([0,2,5,7,4,6,9,11,8,10,13,15,12,14,17,19,16,18,21,23,20,22,25,27,24,26,29,31,28,30,33,35,32,34,37,39,36,38,1,3,40,41]) + can = canonicalize(g, list(range(40)), 0, (baser, gensr, 10, 0)) + assert can == [0,2,4,6,1,3,8,10,5,7,12,14,9,11,16,18,13,15,20,22,17,19,24,26,21,23,28,30,25,27,32,34,29,31,36,38,33,35,37,39,40,41] + + +@XFAIL +def test_riemann_invariants1(): + skip('takes too much time') + baser, gensr = riemann_bsgs + g = Permutation([17, 44, 11, 3, 0, 19, 23, 15, 38, 4, 25, 27, 43, 36, 22, 14, 8, 30, 41, 20, 2, 10, 12, 28, 18, 1, 29, 13, 37, 42, 33, 7, 9, 31, 24, 26, 39, 5, 34, 47, 32, 6, 21, 40, 35, 46, 45, 16, 48, 49]) + can = canonicalize(g, list(range(48)), 0, (baser, gensr, 12, 0)) + assert can == [0, 2, 4, 6, 1, 3, 8, 10, 5, 7, 12, 14, 9, 11, 16, 18, 13, 15, 20, 22, 17, 19, 24, 26, 21, 23, 28, 30, 25, 27, 32, 34, 29, 31, 36, 38, 33, 35, 40, 42, 37, 39, 44, 46, 41, 43, 45, 47, 48, 49] + + g = Permutation([0,2,4,6, 7,8,10,12, 14,16,18,20, 19,22,24,26, 5,21,28,30, 32,34,36,38, 40,42,44,46, 13,48,50,52, 15,49,54,56, 17,33,41,58, 9,23,60,62, 29,35,63,64, 3,45,66,68, 25,37,47,57, 11,31,69,70, 27,39,53,72, 1,59,73,74, 55,61,67,76, 43,65,75,78, 51,71,77,79, 80,81]) + can = canonicalize(g, list(range(80)), 0, (baser, gensr, 20, 0)) + assert can == [0,2,4,6, 1,8,10,12, 3,14,16,18, 5,20,22,24, 7,26,28,30, 9,15,32,34, 11,36,23,38, 13,40,42,44, 17,39,29,46, 19,48,43,50, 21,45,52,54, 25,56,33,58, 27,60,53,62, 31,51,64,66, 35,65,47,68, 37,70,49,72, 41,74,57,76, 55,67,59,78, 61,69,71,75, 63,79,73,77, 80,81] + + +def test_riemann_products(): + baser, gensr = riemann_bsgs + base1, gens1 = get_symmetric_group_sgs(1) + base2, gens2 = get_symmetric_group_sgs(2) + base2a, gens2a = get_symmetric_group_sgs(2, 1) + + # R^{a b d0}_d0 = 0 + g = Permutation([0,1,2,3,4,5]) + can = canonicalize(g, list(range(2,4)), 0, (baser, gensr, 1, 0)) + assert can == 0 + + # R^{d0 b a}_d0 ; ord = [a,b,d0,-d0}; g = [2,1,0,3,4,5] + # T_c = -R^{a d0 b}_d0; can = [0,2,1,3,5,4] + g = Permutation([2,1,0,3,4,5]) + can = canonicalize(g, list(range(2, 4)), 0, (baser, gensr, 1, 0)) + assert can == [0,2,1,3,5,4] + + # R^d1_d2^b_d0 * R^{d0 a}_d1^d2; ord=[a,b,d0,-d0,d1,-d1,d2,-d2] + # g = [4,7,1,3,2,0,5,6,8,9] + # T_c = -R^{a d0 d1 d2}* R^b_{d0 d1 d2} + # can = [0,2,4,6,1,3,5,7,9,8] + g = Permutation([4,7,1,3,2,0,5,6,8,9]) + can = canonicalize(g, list(range(2,8)), 0, (baser, gensr, 2, 0)) + assert can == [0,2,4,6,1,3,5,7,9,8] + can1 = canonicalize_naive(g, list(range(2,8)), 0, (baser, gensr, 2, 0)) + assert can == can1 + + # A symmetric commuting + # R^{d6 d5}_d2^d1 * R^{d4 d0 d2 d3} * A_{d6 d0} A_{d3 d1} * A_{d4 d5} + # g = [12,10,5,2, 8,0,4,6, 13,1, 7,3, 9,11,14,15] + # T_c = -R^{d0 d1 d2 d3} * R_d0^{d4 d5 d6} * A_{d1 d4}*A_{d2 d5}*A_{d3 d6} + + g = Permutation([12,10,5,2,8,0,4,6,13,1,7,3,9,11,14,15]) + can = canonicalize(g, list(range(14)), 0, ((baser,gensr,2,0)), (base2,gens2,3,0)) + assert can == [0, 2, 4, 6, 1, 8, 10, 12, 3, 9, 5, 11, 7, 13, 15, 14] + + # R^{d2 a0 a2 d0} * R^d1_d2^{a1 a3} * R^{a4 a5}_{d0 d1} + # ord = [a0,a1,a2,a3,a4,a5,d0,-d0,d1,-d1,d2,-d2] + # 0 1 2 3 4 5 6 7 8 9 10 11 + # can = [0, 6, 2, 8, 1, 3, 7, 10, 4, 5, 9, 11, 12, 13] + # T_c = R^{a0 d0 a2 d1}*R^{a1 a3}_d0^d2*R^{a4 a5}_{d1 d2} + g = Permutation([10,0,2,6,8,11,1,3,4,5,7,9,12,13]) + can = canonicalize(g, list(range(6,12)), 0, (baser, gensr, 3, 0)) + assert can == [0, 6, 2, 8, 1, 3, 7, 10, 4, 5, 9, 11, 12, 13] + #can1 = canonicalize_naive(g, list(range(6,12)), 0, (baser, gensr, 3, 0)) + #assert can == can1 + + # A^n_{i, j} antisymmetric in i,j + # A_m0^d0_a1 * A_m1^a0_d0; ord = [m0,m1,a0,a1,d0,-d0] + # g = [0,4,3,1,2,5,6,7] + # T_c = -A_{m a1}^d0 * A_m1^a0_d0 + # can = [0,3,4,1,2,5,7,6] + base, gens = bsgs_direct_product(base1, gens1, base2a, gens2a) + dummies = list(range(4, 6)) + g = Permutation([0,4,3,1,2,5,6,7]) + can = canonicalize(g, dummies, 0, (base, gens, 2, 0)) + assert can == [0, 3, 4, 1, 2, 5, 7, 6] + + + # A^n_{i, j} symmetric in i,j + # A^m0_a0^d2 * A^n0_d2^d1 * A^n1_d1^d0 * A_{m0 d0}^a1 + # ordering: first the free indices; then first n, then d + # ord=[n0,n1,a0,a1, m0,-m0,d0,-d0,d1,-d1,d2,-d2] + # 0 1 2 3 4 5 6 7 8 9 10 11] + # g = [4,2,10, 0,11,8, 1,9,6, 5,7,3, 12,13] + # if the dummy indices m_i and d_i were separated, + # one gets + # T_c = A^{n0 d0 d1} * A^n1_d0^d2 * A^m0^a0_d1 * A_m0^a1_d2 + # can = [0, 6, 8, 1, 7, 10, 4, 2, 9, 5, 3, 11, 12, 13] + # If they are not, so can is + # T_c = A^{n0 m0 d0} A^n1_m0^d1 A^{d2 a0}_d0 A_d2^a1_d1 + # can = [0, 4, 6, 1, 5, 8, 10, 2, 7, 11, 3, 9, 12, 13] + # case with single type of indices + + base, gens = bsgs_direct_product(base1, gens1, base2, gens2) + dummies = list(range(4, 12)) + g = Permutation([4,2,10, 0,11,8, 1,9,6, 5,7,3, 12,13]) + can = canonicalize(g, dummies, 0, (base, gens, 4, 0)) + assert can == [0, 4, 6, 1, 5, 8, 10, 2, 7, 11, 3, 9, 12, 13] + # case with separated indices + dummies = [list(range(4, 6)), list(range(6,12))] + sym = [0, 0] + can = canonicalize(g, dummies, sym, (base, gens, 4, 0)) + assert can == [0, 6, 8, 1, 7, 10, 4, 2, 9, 5, 3, 11, 12, 13] + # case with separated indices with the second type of index + # with antisymmetric metric: there is a sign change + sym = [0, 1] + can = canonicalize(g, dummies, sym, (base, gens, 4, 0)) + assert can == [0, 6, 8, 1, 7, 10, 4, 2, 9, 5, 3, 11, 13, 12] + +def test_graph_certificate(): + # test tensor invariants constructed from random regular graphs; + # checked graph isomorphism with networkx + import random + def randomize_graph(size, g): + p = list(range(size)) + random.shuffle(p) + g1a = {} + for k, v in g1.items(): + g1a[p[k]] = [p[i] for i in v] + return g1a + + g1 = {0: [2, 3, 7], 1: [4, 5, 7], 2: [0, 4, 6], 3: [0, 6, 7], 4: [1, 2, 5], 5: [1, 4, 6], 6: [2, 3, 5], 7: [0, 1, 3]} + g2 = {0: [2, 3, 7], 1: [2, 4, 5], 2: [0, 1, 5], 3: [0, 6, 7], 4: [1, 5, 6], 5: [1, 2, 4], 6: [3, 4, 7], 7: [0, 3, 6]} + + c1 = graph_certificate(g1) + c2 = graph_certificate(g2) + assert c1 != c2 + g1a = randomize_graph(8, g1) + c1a = graph_certificate(g1a) + assert c1 == c1a + + g1 = {0: [8, 1, 9, 7], 1: [0, 9, 3, 4], 2: [3, 4, 6, 7], 3: [1, 2, 5, 6], 4: [8, 1, 2, 5], 5: [9, 3, 4, 7], 6: [8, 2, 3, 7], 7: [0, 2, 5, 6], 8: [0, 9, 4, 6], 9: [8, 0, 5, 1]} + g2 = {0: [1, 2, 5, 6], 1: [0, 9, 5, 7], 2: [0, 4, 6, 7], 3: [8, 9, 6, 7], 4: [8, 2, 6, 7], 5: [0, 9, 8, 1], 6: [0, 2, 3, 4], 7: [1, 2, 3, 4], 8: [9, 3, 4, 5], 9: [8, 1, 3, 5]} + c1 = graph_certificate(g1) + c2 = graph_certificate(g2) + assert c1 != c2 + g1a = randomize_graph(10, g1) + c1a = graph_certificate(g1a) + assert c1 == c1a diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_testutil.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_testutil.py new file mode 100644 index 0000000000000000000000000000000000000000..736e7a4ff86967e41dca71cf12de6c387a82d26d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_testutil.py @@ -0,0 +1,55 @@ +from sympy.combinatorics.named_groups import SymmetricGroup, AlternatingGroup,\ + CyclicGroup +from sympy.combinatorics.testutil import _verify_bsgs, _cmp_perm_lists,\ + _naive_list_centralizer, _verify_centralizer,\ + _verify_normal_closure +from sympy.combinatorics.permutations import Permutation +from sympy.combinatorics.perm_groups import PermutationGroup +from sympy.core.random import shuffle + + +def test_cmp_perm_lists(): + S = SymmetricGroup(4) + els = list(S.generate_dimino()) + other = els.copy() + shuffle(other) + assert _cmp_perm_lists(els, other) is True + + +def test_naive_list_centralizer(): + # verified by GAP + S = SymmetricGroup(3) + A = AlternatingGroup(3) + assert _naive_list_centralizer(S, S) == [Permutation([0, 1, 2])] + assert PermutationGroup(_naive_list_centralizer(S, A)).is_subgroup(A) + + +def test_verify_bsgs(): + S = SymmetricGroup(5) + S.schreier_sims() + base = S.base + strong_gens = S.strong_gens + assert _verify_bsgs(S, base, strong_gens) is True + assert _verify_bsgs(S, base[:-1], strong_gens) is False + assert _verify_bsgs(S, base, S.generators) is False + + +def test_verify_centralizer(): + # verified by GAP + S = SymmetricGroup(3) + A = AlternatingGroup(3) + triv = PermutationGroup([Permutation([0, 1, 2])]) + assert _verify_centralizer(S, S, centr=triv) + assert _verify_centralizer(S, A, centr=A) + + +def test_verify_normal_closure(): + # verified by GAP + S = SymmetricGroup(3) + A = AlternatingGroup(3) + assert _verify_normal_closure(S, A, closure=A) + S = SymmetricGroup(5) + A = AlternatingGroup(5) + C = CyclicGroup(5) + assert _verify_normal_closure(S, A, closure=A) + assert _verify_normal_closure(S, C, closure=A) diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_util.py b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_util.py new file mode 100644 index 0000000000000000000000000000000000000000..bca183e81f354e398aee9ae809fe79b20c7f2468 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/tests/test_util.py @@ -0,0 +1,120 @@ +from sympy.combinatorics.named_groups import SymmetricGroup, DihedralGroup,\ + AlternatingGroup +from sympy.combinatorics.permutations import Permutation +from sympy.combinatorics.util import _check_cycles_alt_sym, _strip,\ + _distribute_gens_by_base, _strong_gens_from_distr,\ + _orbits_transversals_from_bsgs, _handle_precomputed_bsgs, _base_ordering,\ + _remove_gens +from sympy.combinatorics.testutil import _verify_bsgs + + +def test_check_cycles_alt_sym(): + perm1 = Permutation([[0, 1, 2, 3, 4, 5, 6], [7], [8], [9]]) + perm2 = Permutation([[0, 1, 2, 3, 4, 5], [6, 7, 8, 9]]) + perm3 = Permutation([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) + assert _check_cycles_alt_sym(perm1) is True + assert _check_cycles_alt_sym(perm2) is False + assert _check_cycles_alt_sym(perm3) is False + + +def test_strip(): + D = DihedralGroup(5) + D.schreier_sims() + member = Permutation([4, 0, 1, 2, 3]) + not_member1 = Permutation([0, 1, 4, 3, 2]) + not_member2 = Permutation([3, 1, 4, 2, 0]) + identity = Permutation([0, 1, 2, 3, 4]) + res1 = _strip(member, D.base, D.basic_orbits, D.basic_transversals) + res2 = _strip(not_member1, D.base, D.basic_orbits, D.basic_transversals) + res3 = _strip(not_member2, D.base, D.basic_orbits, D.basic_transversals) + assert res1[0] == identity + assert res1[1] == len(D.base) + 1 + assert res2[0] == not_member1 + assert res2[1] == len(D.base) + 1 + assert res3[0] != identity + assert res3[1] == 2 + + +def test_distribute_gens_by_base(): + base = [0, 1, 2] + gens = [Permutation([0, 1, 2, 3]), Permutation([0, 1, 3, 2]), + Permutation([0, 2, 3, 1]), Permutation([3, 2, 1, 0])] + assert _distribute_gens_by_base(base, gens) == [gens, + [Permutation([0, 1, 2, 3]), + Permutation([0, 1, 3, 2]), + Permutation([0, 2, 3, 1])], + [Permutation([0, 1, 2, 3]), + Permutation([0, 1, 3, 2])]] + + +def test_strong_gens_from_distr(): + strong_gens_distr = [[Permutation([0, 2, 1]), Permutation([1, 2, 0]), + Permutation([1, 0, 2])], [Permutation([0, 2, 1])]] + assert _strong_gens_from_distr(strong_gens_distr) == \ + [Permutation([0, 2, 1]), + Permutation([1, 2, 0]), + Permutation([1, 0, 2])] + + +def test_orbits_transversals_from_bsgs(): + S = SymmetricGroup(4) + S.schreier_sims() + base = S.base + strong_gens = S.strong_gens + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + result = _orbits_transversals_from_bsgs(base, strong_gens_distr) + orbits = result[0] + transversals = result[1] + base_len = len(base) + for i in range(base_len): + for el in orbits[i]: + assert transversals[i][el](base[i]) == el + for j in range(i): + assert transversals[i][el](base[j]) == base[j] + order = 1 + for i in range(base_len): + order *= len(orbits[i]) + assert S.order() == order + + +def test_handle_precomputed_bsgs(): + A = AlternatingGroup(5) + A.schreier_sims() + base = A.base + strong_gens = A.strong_gens + result = _handle_precomputed_bsgs(base, strong_gens) + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + assert strong_gens_distr == result[2] + transversals = result[0] + orbits = result[1] + base_len = len(base) + for i in range(base_len): + for el in orbits[i]: + assert transversals[i][el](base[i]) == el + for j in range(i): + assert transversals[i][el](base[j]) == base[j] + order = 1 + for i in range(base_len): + order *= len(orbits[i]) + assert A.order() == order + + +def test_base_ordering(): + base = [2, 4, 5] + degree = 7 + assert _base_ordering(base, degree) == [3, 4, 0, 5, 1, 2, 6] + + +def test_remove_gens(): + S = SymmetricGroup(10) + base, strong_gens = S.schreier_sims_incremental() + new_gens = _remove_gens(base, strong_gens) + assert _verify_bsgs(S, base, new_gens) is True + A = AlternatingGroup(7) + base, strong_gens = A.schreier_sims_incremental() + new_gens = _remove_gens(base, strong_gens) + assert _verify_bsgs(A, base, new_gens) is True + D = DihedralGroup(2) + base, strong_gens = D.schreier_sims_incremental() + new_gens = _remove_gens(base, strong_gens) + assert _verify_bsgs(D, base, new_gens) is True diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/testutil.py b/phivenv/Lib/site-packages/sympy/combinatorics/testutil.py new file mode 100644 index 0000000000000000000000000000000000000000..9fe664ce9e7437b97feec90f2052eb2987c57a4e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/testutil.py @@ -0,0 +1,357 @@ +from sympy.combinatorics import Permutation +from sympy.combinatorics.util import _distribute_gens_by_base + +rmul = Permutation.rmul + + +def _cmp_perm_lists(first, second): + """ + Compare two lists of permutations as sets. + + Explanation + =========== + + This is used for testing purposes. Since the array form of a + permutation is currently a list, Permutation is not hashable + and cannot be put into a set. + + Examples + ======== + + >>> from sympy.combinatorics.permutations import Permutation + >>> from sympy.combinatorics.testutil import _cmp_perm_lists + >>> a = Permutation([0, 2, 3, 4, 1]) + >>> b = Permutation([1, 2, 0, 4, 3]) + >>> c = Permutation([3, 4, 0, 1, 2]) + >>> ls1 = [a, b, c] + >>> ls2 = [b, c, a] + >>> _cmp_perm_lists(ls1, ls2) + True + + """ + return {tuple(a) for a in first} == \ + {tuple(a) for a in second} + + +def _naive_list_centralizer(self, other, af=False): + from sympy.combinatorics.perm_groups import PermutationGroup + """ + Return a list of elements for the centralizer of a subgroup/set/element. + + Explanation + =========== + + This is a brute force implementation that goes over all elements of the + group and checks for membership in the centralizer. It is used to + test ``.centralizer()`` from ``sympy.combinatorics.perm_groups``. + + Examples + ======== + + >>> from sympy.combinatorics.testutil import _naive_list_centralizer + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> D = DihedralGroup(4) + >>> _naive_list_centralizer(D, D) + [Permutation([0, 1, 2, 3]), Permutation([2, 3, 0, 1])] + + See Also + ======== + + sympy.combinatorics.perm_groups.centralizer + + """ + from sympy.combinatorics.permutations import _af_commutes_with + if hasattr(other, 'generators'): + elements = list(self.generate_dimino(af=True)) + gens = [x._array_form for x in other.generators] + commutes_with_gens = lambda x: all(_af_commutes_with(x, gen) for gen in gens) + centralizer_list = [] + if not af: + for element in elements: + if commutes_with_gens(element): + centralizer_list.append(Permutation._af_new(element)) + else: + for element in elements: + if commutes_with_gens(element): + centralizer_list.append(element) + return centralizer_list + elif hasattr(other, 'getitem'): + return _naive_list_centralizer(self, PermutationGroup(other), af) + elif hasattr(other, 'array_form'): + return _naive_list_centralizer(self, PermutationGroup([other]), af) + + +def _verify_bsgs(group, base, gens): + """ + Verify the correctness of a base and strong generating set. + + Explanation + =========== + + This is a naive implementation using the definition of a base and a strong + generating set relative to it. There are other procedures for + verifying a base and strong generating set, but this one will + serve for more robust testing. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import AlternatingGroup + >>> from sympy.combinatorics.testutil import _verify_bsgs + >>> A = AlternatingGroup(4) + >>> A.schreier_sims() + >>> _verify_bsgs(A, A.base, A.strong_gens) + True + + See Also + ======== + + sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims + + """ + from sympy.combinatorics.perm_groups import PermutationGroup + strong_gens_distr = _distribute_gens_by_base(base, gens) + current_stabilizer = group + for i in range(len(base)): + candidate = PermutationGroup(strong_gens_distr[i]) + if current_stabilizer.order() != candidate.order(): + return False + current_stabilizer = current_stabilizer.stabilizer(base[i]) + if current_stabilizer.order() != 1: + return False + return True + + +def _verify_centralizer(group, arg, centr=None): + """ + Verify the centralizer of a group/set/element inside another group. + + This is used for testing ``.centralizer()`` from + ``sympy.combinatorics.perm_groups`` + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... AlternatingGroup) + >>> from sympy.combinatorics.perm_groups import PermutationGroup + >>> from sympy.combinatorics.permutations import Permutation + >>> from sympy.combinatorics.testutil import _verify_centralizer + >>> S = SymmetricGroup(5) + >>> A = AlternatingGroup(5) + >>> centr = PermutationGroup([Permutation([0, 1, 2, 3, 4])]) + >>> _verify_centralizer(S, A, centr) + True + + See Also + ======== + + _naive_list_centralizer, + sympy.combinatorics.perm_groups.PermutationGroup.centralizer, + _cmp_perm_lists + + """ + if centr is None: + centr = group.centralizer(arg) + centr_list = list(centr.generate_dimino(af=True)) + centr_list_naive = _naive_list_centralizer(group, arg, af=True) + return _cmp_perm_lists(centr_list, centr_list_naive) + + +def _verify_normal_closure(group, arg, closure=None): + from sympy.combinatorics.perm_groups import PermutationGroup + """ + Verify the normal closure of a subgroup/subset/element in a group. + + This is used to test + sympy.combinatorics.perm_groups.PermutationGroup.normal_closure + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import (SymmetricGroup, + ... AlternatingGroup) + >>> from sympy.combinatorics.testutil import _verify_normal_closure + >>> S = SymmetricGroup(3) + >>> A = AlternatingGroup(3) + >>> _verify_normal_closure(S, A, closure=A) + True + + See Also + ======== + + sympy.combinatorics.perm_groups.PermutationGroup.normal_closure + + """ + if closure is None: + closure = group.normal_closure(arg) + conjugates = set() + if hasattr(arg, 'generators'): + subgr_gens = arg.generators + elif hasattr(arg, '__getitem__'): + subgr_gens = arg + elif hasattr(arg, 'array_form'): + subgr_gens = [arg] + for el in group.generate_dimino(): + conjugates.update(gen ^ el for gen in subgr_gens) + naive_closure = PermutationGroup(list(conjugates)) + return closure.is_subgroup(naive_closure) + + +def canonicalize_naive(g, dummies, sym, *v): + """ + Canonicalize tensor formed by tensors of the different types. + + Explanation + =========== + + sym_i symmetry under exchange of two component tensors of type `i` + None no symmetry + 0 commuting + 1 anticommuting + + Parameters + ========== + + g : Permutation representing the tensor. + dummies : List of dummy indices. + msym : Symmetry of the metric. + v : A list of (base_i, gens_i, n_i, sym_i) for tensors of type `i`. + base_i, gens_i BSGS for tensors of this type + n_i number of tensors of type `i` + + Returns + ======= + + Returns 0 if the tensor is zero, else returns the array form of + the permutation representing the canonical form of the tensor. + + Examples + ======== + + >>> from sympy.combinatorics.testutil import canonicalize_naive + >>> from sympy.combinatorics.tensor_can import get_symmetric_group_sgs + >>> from sympy.combinatorics import Permutation + >>> g = Permutation([1, 3, 2, 0, 4, 5]) + >>> base2, gens2 = get_symmetric_group_sgs(2) + >>> canonicalize_naive(g, [2, 3], 0, (base2, gens2, 2, 0)) + [0, 2, 1, 3, 4, 5] + """ + from sympy.combinatorics.perm_groups import PermutationGroup + from sympy.combinatorics.tensor_can import gens_products, dummy_sgs + from sympy.combinatorics.permutations import _af_rmul + v1 = [] + for i in range(len(v)): + base_i, gens_i, n_i, sym_i = v[i] + v1.append((base_i, gens_i, [[]]*n_i, sym_i)) + size, sbase, sgens = gens_products(*v1) + dgens = dummy_sgs(dummies, sym, size-2) + if isinstance(sym, int): + num_types = 1 + dummies = [dummies] + sym = [sym] + else: + num_types = len(sym) + dgens = [] + for i in range(num_types): + dgens.extend(dummy_sgs(dummies[i], sym[i], size - 2)) + S = PermutationGroup(sgens) + D = PermutationGroup([Permutation(x) for x in dgens]) + dlist = list(D.generate(af=True)) + g = g.array_form + st = set() + for s in S.generate(af=True): + h = _af_rmul(g, s) + for d in dlist: + q = tuple(_af_rmul(d, h)) + st.add(q) + a = list(st) + a.sort() + prev = (0,)*size + for h in a: + if h[:-2] == prev[:-2]: + if h[-1] != prev[-1]: + return 0 + prev = h + return list(a[0]) + + +def graph_certificate(gr): + """ + Return a certificate for the graph + + Parameters + ========== + + gr : adjacency list + + Explanation + =========== + + The graph is assumed to be unoriented and without + external lines. + + Associate to each vertex of the graph a symmetric tensor with + number of indices equal to the degree of the vertex; indices + are contracted when they correspond to the same line of the graph. + The canonical form of the tensor gives a certificate for the graph. + + This is not an efficient algorithm to get the certificate of a graph. + + Examples + ======== + + >>> from sympy.combinatorics.testutil import graph_certificate + >>> gr1 = {0:[1, 2, 3, 5], 1:[0, 2, 4], 2:[0, 1, 3, 4], 3:[0, 2, 4], 4:[1, 2, 3, 5], 5:[0, 4]} + >>> gr2 = {0:[1, 5], 1:[0, 2, 3, 4], 2:[1, 3, 5], 3:[1, 2, 4, 5], 4:[1, 3, 5], 5:[0, 2, 3, 4]} + >>> c1 = graph_certificate(gr1) + >>> c2 = graph_certificate(gr2) + >>> c1 + [0, 2, 4, 6, 1, 8, 10, 12, 3, 14, 16, 18, 5, 9, 15, 7, 11, 17, 13, 19, 20, 21] + >>> c1 == c2 + True + """ + from sympy.combinatorics.permutations import _af_invert + from sympy.combinatorics.tensor_can import get_symmetric_group_sgs, canonicalize + items = list(gr.items()) + items.sort(key=lambda x: len(x[1]), reverse=True) + pvert = [x[0] for x in items] + pvert = _af_invert(pvert) + + # the indices of the tensor are twice the number of lines of the graph + num_indices = 0 + for v, neigh in items: + num_indices += len(neigh) + # associate to each vertex its indices; for each line + # between two vertices assign the + # even index to the vertex which comes first in items, + # the odd index to the other vertex + vertices = [[] for i in items] + i = 0 + for v, neigh in items: + for v2 in neigh: + if pvert[v] < pvert[v2]: + vertices[pvert[v]].append(i) + vertices[pvert[v2]].append(i+1) + i += 2 + g = [] + for v in vertices: + g.extend(v) + assert len(g) == num_indices + g += [num_indices, num_indices + 1] + size = num_indices + 2 + assert sorted(g) == list(range(size)) + g = Permutation(g) + vlen = [0]*(len(vertices[0])+1) + for neigh in vertices: + vlen[len(neigh)] += 1 + v = [] + for i in range(len(vlen)): + n = vlen[i] + if n: + base, gens = get_symmetric_group_sgs(i) + v.append((base, gens, n, 0)) + v.reverse() + dummies = list(range(num_indices)) + can = canonicalize(g, dummies, 0, *v) + return can diff --git a/phivenv/Lib/site-packages/sympy/combinatorics/util.py b/phivenv/Lib/site-packages/sympy/combinatorics/util.py new file mode 100644 index 0000000000000000000000000000000000000000..fc73b02f94f4aae6f1b98bb3f0c837fd5a1d1e6d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/combinatorics/util.py @@ -0,0 +1,532 @@ +from sympy.combinatorics.permutations import Permutation, _af_invert, _af_rmul +from sympy.ntheory import isprime + +rmul = Permutation.rmul +_af_new = Permutation._af_new + +############################################ +# +# Utilities for computational group theory +# +############################################ + + +def _base_ordering(base, degree): + r""" + Order `\{0, 1, \dots, n-1\}` so that base points come first and in order. + + Parameters + ========== + + base : the base + degree : the degree of the associated permutation group + + Returns + ======= + + A list ``base_ordering`` such that ``base_ordering[point]`` is the + number of ``point`` in the ordering. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricGroup + >>> from sympy.combinatorics.util import _base_ordering + >>> S = SymmetricGroup(4) + >>> S.schreier_sims() + >>> _base_ordering(S.base, S.degree) + [0, 1, 2, 3] + + Notes + ===== + + This is used in backtrack searches, when we define a relation `\ll` on + the underlying set for a permutation group of degree `n`, + `\{0, 1, \dots, n-1\}`, so that if `(b_1, b_2, \dots, b_k)` is a base we + have `b_i \ll b_j` whenever `i>> from sympy.combinatorics.util import _check_cycles_alt_sym + >>> from sympy.combinatorics import Permutation + >>> a = Permutation([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12]]) + >>> _check_cycles_alt_sym(a) + False + >>> b = Permutation([[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10]]) + >>> _check_cycles_alt_sym(b) + True + + See Also + ======== + + sympy.combinatorics.perm_groups.PermutationGroup.is_alt_sym + + """ + n = perm.size + af = perm.array_form + current_len = 0 + total_len = 0 + used = set() + for i in range(n//2): + if i not in used and i < n//2 - total_len: + current_len = 1 + used.add(i) + j = i + while af[j] != i: + current_len += 1 + j = af[j] + used.add(j) + total_len += current_len + if current_len > n//2 and current_len < n - 2 and isprime(current_len): + return True + return False + + +def _distribute_gens_by_base(base, gens): + r""" + Distribute the group elements ``gens`` by membership in basic stabilizers. + + Explanation + =========== + + Notice that for a base `(b_1, b_2, \dots, b_k)`, the basic stabilizers + are defined as `G^{(i)} = G_{b_1, \dots, b_{i-1}}` for + `i \in\{1, 2, \dots, k\}`. + + Parameters + ========== + + base : a sequence of points in `\{0, 1, \dots, n-1\}` + gens : a list of elements of a permutation group of degree `n`. + + Returns + ======= + list + List of length `k`, where `k` is the length of *base*. The `i`-th entry + contains those elements in *gens* which fix the first `i` elements of + *base* (so that the `0`-th entry is equal to *gens* itself). If no + element fixes the first `i` elements of *base*, the `i`-th element is + set to a list containing the identity element. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> from sympy.combinatorics.util import _distribute_gens_by_base + >>> D = DihedralGroup(3) + >>> D.schreier_sims() + >>> D.strong_gens + [(0 1 2), (0 2), (1 2)] + >>> D.base + [0, 1] + >>> _distribute_gens_by_base(D.base, D.strong_gens) + [[(0 1 2), (0 2), (1 2)], + [(1 2)]] + + See Also + ======== + + _strong_gens_from_distr, _orbits_transversals_from_bsgs, + _handle_precomputed_bsgs + + """ + base_len = len(base) + degree = gens[0].size + stabs = [[] for _ in range(base_len)] + max_stab_index = 0 + for gen in gens: + j = 0 + while j < base_len - 1 and gen._array_form[base[j]] == base[j]: + j += 1 + if j > max_stab_index: + max_stab_index = j + for k in range(j + 1): + stabs[k].append(gen) + for i in range(max_stab_index + 1, base_len): + stabs[i].append(_af_new(list(range(degree)))) + return stabs + + +def _handle_precomputed_bsgs(base, strong_gens, transversals=None, + basic_orbits=None, strong_gens_distr=None): + """ + Calculate BSGS-related structures from those present. + + Explanation + =========== + + The base and strong generating set must be provided; if any of the + transversals, basic orbits or distributed strong generators are not + provided, they will be calculated from the base and strong generating set. + + Parameters + ========== + + base : the base + strong_gens : the strong generators + transversals : basic transversals + basic_orbits : basic orbits + strong_gens_distr : strong generators distributed by membership in basic stabilizers + + Returns + ======= + + (transversals, basic_orbits, strong_gens_distr) + where *transversals* are the basic transversals, *basic_orbits* are the + basic orbits, and *strong_gens_distr* are the strong generators distributed + by membership in basic stabilizers. + + Examples + ======== + + >>> from sympy.combinatorics.named_groups import DihedralGroup + >>> from sympy.combinatorics.util import _handle_precomputed_bsgs + >>> D = DihedralGroup(3) + >>> D.schreier_sims() + >>> _handle_precomputed_bsgs(D.base, D.strong_gens, + ... basic_orbits=D.basic_orbits) + ([{0: (2), 1: (0 1 2), 2: (0 2)}, {1: (2), 2: (1 2)}], [[0, 1, 2], [1, 2]], [[(0 1 2), (0 2), (1 2)], [(1 2)]]) + + See Also + ======== + + _orbits_transversals_from_bsgs, _distribute_gens_by_base + + """ + if strong_gens_distr is None: + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + if transversals is None: + if basic_orbits is None: + basic_orbits, transversals = \ + _orbits_transversals_from_bsgs(base, strong_gens_distr) + else: + transversals = \ + _orbits_transversals_from_bsgs(base, strong_gens_distr, + transversals_only=True) + else: + if basic_orbits is None: + base_len = len(base) + basic_orbits = [None]*base_len + for i in range(base_len): + basic_orbits[i] = list(transversals[i].keys()) + return transversals, basic_orbits, strong_gens_distr + + +def _orbits_transversals_from_bsgs(base, strong_gens_distr, + transversals_only=False, slp=False): + """ + Compute basic orbits and transversals from a base and strong generating set. + + Explanation + =========== + + The generators are provided as distributed across the basic stabilizers. + If the optional argument ``transversals_only`` is set to True, only the + transversals are returned. + + Parameters + ========== + + base : The base. + strong_gens_distr : Strong generators distributed by membership in basic stabilizers. + transversals_only : bool, default: False + A flag switching between returning only the + transversals and both orbits and transversals. + slp : bool, default: False + If ``True``, return a list of dictionaries containing the + generator presentations of the elements of the transversals, + i.e. the list of indices of generators from ``strong_gens_distr[i]`` + such that their product is the relevant transversal element. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricGroup + >>> from sympy.combinatorics.util import _distribute_gens_by_base + >>> S = SymmetricGroup(3) + >>> S.schreier_sims() + >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens) + >>> (S.base, strong_gens_distr) + ([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]]) + + See Also + ======== + + _distribute_gens_by_base, _handle_precomputed_bsgs + + """ + from sympy.combinatorics.perm_groups import _orbit_transversal + base_len = len(base) + degree = strong_gens_distr[0][0].size + transversals = [None]*base_len + slps = [None]*base_len + if transversals_only is False: + basic_orbits = [None]*base_len + for i in range(base_len): + transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i], + base[i], pairs=True, slp=True) + transversals[i] = dict(transversals[i]) + if transversals_only is False: + basic_orbits[i] = list(transversals[i].keys()) + if transversals_only: + return transversals + else: + if not slp: + return basic_orbits, transversals + return basic_orbits, transversals, slps + + +def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None): + """ + Remove redundant generators from a strong generating set. + + Parameters + ========== + + base : a base + strong_gens : a strong generating set relative to *base* + basic_orbits : basic orbits + strong_gens_distr : strong generators distributed by membership in basic stabilizers + + Returns + ======= + + A strong generating set with respect to ``base`` which is a subset of + ``strong_gens``. + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricGroup + >>> from sympy.combinatorics.util import _remove_gens + >>> from sympy.combinatorics.testutil import _verify_bsgs + >>> S = SymmetricGroup(15) + >>> base, strong_gens = S.schreier_sims_incremental() + >>> new_gens = _remove_gens(base, strong_gens) + >>> len(new_gens) + 14 + >>> _verify_bsgs(S, base, new_gens) + True + + Notes + ===== + + This procedure is outlined in [1],p.95. + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E. + "Handbook of computational group theory" + + """ + from sympy.combinatorics.perm_groups import _orbit + base_len = len(base) + degree = strong_gens[0].size + if strong_gens_distr is None: + strong_gens_distr = _distribute_gens_by_base(base, strong_gens) + if basic_orbits is None: + basic_orbits = [] + for i in range(base_len): + basic_orbit = _orbit(degree, strong_gens_distr[i], base[i]) + basic_orbits.append(basic_orbit) + strong_gens_distr.append([]) + res = strong_gens[:] + for i in range(base_len - 1, -1, -1): + gens_copy = strong_gens_distr[i][:] + for gen in strong_gens_distr[i]: + if gen not in strong_gens_distr[i + 1]: + temp_gens = gens_copy[:] + temp_gens.remove(gen) + if temp_gens == []: + continue + temp_orbit = _orbit(degree, temp_gens, base[i]) + if temp_orbit == basic_orbits[i]: + gens_copy.remove(gen) + res.remove(gen) + return res + + +def _strip(g, base, orbits, transversals): + """ + Attempt to decompose a permutation using a (possibly partial) BSGS + structure. + + Explanation + =========== + + This is done by treating the sequence ``base`` as an actual base, and + the orbits ``orbits`` and transversals ``transversals`` as basic orbits and + transversals relative to it. + + This process is called "sifting". A sift is unsuccessful when a certain + orbit element is not found or when after the sift the decomposition + does not end with the identity element. + + The argument ``transversals`` is a list of dictionaries that provides + transversal elements for the orbits ``orbits``. + + Parameters + ========== + + g : permutation to be decomposed + base : sequence of points + orbits : list + A list in which the ``i``-th entry is an orbit of ``base[i]`` + under some subgroup of the pointwise stabilizer of ` + `base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit + in this function since the only information we need is encoded in the orbits + and transversals + transversals : list + A list of orbit transversals associated with the orbits *orbits*. + + Examples + ======== + + >>> from sympy.combinatorics import Permutation, SymmetricGroup + >>> from sympy.combinatorics.util import _strip + >>> S = SymmetricGroup(5) + >>> S.schreier_sims() + >>> g = Permutation([0, 2, 3, 1, 4]) + >>> _strip(g, S.base, S.basic_orbits, S.basic_transversals) + ((4), 5) + + Notes + ===== + + The algorithm is described in [1],pp.89-90. The reason for returning + both the current state of the element being decomposed and the level + at which the sifting ends is that they provide important information for + the randomized version of the Schreier-Sims algorithm. + + References + ========== + + .. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory" + + See Also + ======== + + sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims + sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random + + """ + h = g._array_form + base_len = len(base) + for i in range(base_len): + beta = h[base[i]] + if beta == base[i]: + continue + if beta not in orbits[i]: + return _af_new(h), i + 1 + u = transversals[i][beta]._array_form + h = _af_rmul(_af_invert(u), h) + return _af_new(h), base_len + 1 + + +def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}): + """ + optimized _strip, with h, transversals and result in array form + if the stripped elements is the identity, it returns False, base_len + 1 + + j h[base[i]] == base[i] for i <= j + + """ + base_len = len(base) + for i in range(j+1, base_len): + beta = h[base[i]] + if beta == base[i]: + continue + if beta not in orbits[i]: + if not slp: + return h, i + 1 + return h, i + 1, slp + u = transversals[i][beta] + if h == u: + if not slp: + return False, base_len + 1 + return False, base_len + 1, slp + h = _af_rmul(_af_invert(u), h) + if slp: + u_slp = slps[i][beta][:] + u_slp.reverse() + u_slp = [(i, (g,)) for g in u_slp] + slp = u_slp + slp + if not slp: + return h, base_len + 1 + return h, base_len + 1, slp + + +def _strong_gens_from_distr(strong_gens_distr): + """ + Retrieve strong generating set from generators of basic stabilizers. + + This is just the union of the generators of the first and second basic + stabilizers. + + Parameters + ========== + + strong_gens_distr : strong generators distributed by membership in basic stabilizers + + Examples + ======== + + >>> from sympy.combinatorics import SymmetricGroup + >>> from sympy.combinatorics.util import (_strong_gens_from_distr, + ... _distribute_gens_by_base) + >>> S = SymmetricGroup(3) + >>> S.schreier_sims() + >>> S.strong_gens + [(0 1 2), (2)(0 1), (1 2)] + >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens) + >>> _strong_gens_from_distr(strong_gens_distr) + [(0 1 2), (2)(0 1), (1 2)] + + See Also + ======== + + _distribute_gens_by_base + + """ + if len(strong_gens_distr) == 1: + return strong_gens_distr[0][:] + else: + result = strong_gens_distr[0] + for gen in strong_gens_distr[1]: + if gen not in result: + result.append(gen) + return result diff --git a/phivenv/Lib/site-packages/sympy/concrete/__init__.py b/phivenv/Lib/site-packages/sympy/concrete/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7048bd548cca04780479539339dd8cc49a21990f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/__init__.py @@ -0,0 +1,8 @@ +from .products import product, Product +from .summations import summation, Sum + +__all__ = [ + 'product', 'Product', + + 'summation', 'Sum', +] diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73a23dc1aa280973a1afc5dff3214fedbea53203 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/delta.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/delta.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e5221e6cf5899c3b19c4cf148068fcde9493b63 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/delta.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/expr_with_intlimits.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/expr_with_intlimits.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f1cb8812b527a746d3d4d162c4b9a10b71c13da Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/expr_with_intlimits.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/expr_with_limits.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/expr_with_limits.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e6875e7f017a2c5827f8ac0348b4c17518fe8bc Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/expr_with_limits.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/gosper.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/gosper.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bed24ef640b5becf4fcd4a0dfbe391b2cda3159b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/gosper.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/guess.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/guess.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f4ee71141bce53669c89d0f917a072427d2cc6e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/guess.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/products.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/products.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8d5b1aabd11b06b7b852565fc915b645e6424c4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/products.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/__pycache__/summations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/summations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8f14f4a3a7a4a59642f9bebf08e3dcdb20488e7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/__pycache__/summations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/delta.py b/phivenv/Lib/site-packages/sympy/concrete/delta.py new file mode 100644 index 0000000000000000000000000000000000000000..0471d2b2891a66a1b97b7a3a37bbd31d7b173a81 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/delta.py @@ -0,0 +1,327 @@ +""" +This module implements sums and products containing the Kronecker Delta function. + +References +========== + +.. [1] https://mathworld.wolfram.com/KroneckerDelta.html + +""" +from .products import product +from .summations import Sum, summation +from sympy.core import Add, Mul, S, Dummy +from sympy.core.cache import cacheit +from sympy.core.sorting import default_sort_key +from sympy.functions import KroneckerDelta, Piecewise, piecewise_fold +from sympy.polys.polytools import factor +from sympy.sets.sets import Interval +from sympy.solvers.solvers import solve + + +@cacheit +def _expand_delta(expr, index): + """ + Expand the first Add containing a simple KroneckerDelta. + """ + if not expr.is_Mul: + return expr + delta = None + func = Add + terms = [S.One] + for h in expr.args: + if delta is None and h.is_Add and _has_simple_delta(h, index): + delta = True + func = h.func + terms = [terms[0]*t for t in h.args] + else: + terms = [t*h for t in terms] + return func(*terms) + + +@cacheit +def _extract_delta(expr, index): + """ + Extract a simple KroneckerDelta from the expression. + + Explanation + =========== + + Returns the tuple ``(delta, newexpr)`` where: + + - ``delta`` is a simple KroneckerDelta expression if one was found, + or ``None`` if no simple KroneckerDelta expression was found. + + - ``newexpr`` is a Mul containing the remaining terms; ``expr`` is + returned unchanged if no simple KroneckerDelta expression was found. + + Examples + ======== + + >>> from sympy import KroneckerDelta + >>> from sympy.concrete.delta import _extract_delta + >>> from sympy.abc import x, y, i, j, k + >>> _extract_delta(4*x*y*KroneckerDelta(i, j), i) + (KroneckerDelta(i, j), 4*x*y) + >>> _extract_delta(4*x*y*KroneckerDelta(i, j), k) + (None, 4*x*y*KroneckerDelta(i, j)) + + See Also + ======== + + sympy.functions.special.tensor_functions.KroneckerDelta + deltaproduct + deltasummation + """ + if not _has_simple_delta(expr, index): + return (None, expr) + if isinstance(expr, KroneckerDelta): + return (expr, S.One) + if not expr.is_Mul: + raise ValueError("Incorrect expr") + delta = None + terms = [] + + for arg in expr.args: + if delta is None and _is_simple_delta(arg, index): + delta = arg + else: + terms.append(arg) + return (delta, expr.func(*terms)) + + +@cacheit +def _has_simple_delta(expr, index): + """ + Returns True if ``expr`` is an expression that contains a KroneckerDelta + that is simple in the index ``index``, meaning that this KroneckerDelta + is nonzero for a single value of the index ``index``. + """ + if expr.has(KroneckerDelta): + if _is_simple_delta(expr, index): + return True + if expr.is_Add or expr.is_Mul: + return any(_has_simple_delta(arg, index) for arg in expr.args) + return False + + +@cacheit +def _is_simple_delta(delta, index): + """ + Returns True if ``delta`` is a KroneckerDelta and is nonzero for a single + value of the index ``index``. + """ + if isinstance(delta, KroneckerDelta) and delta.has(index): + p = (delta.args[0] - delta.args[1]).as_poly(index) + if p: + return p.degree() == 1 + return False + + +@cacheit +def _remove_multiple_delta(expr): + """ + Evaluate products of KroneckerDelta's. + """ + if expr.is_Add: + return expr.func(*list(map(_remove_multiple_delta, expr.args))) + if not expr.is_Mul: + return expr + eqs = [] + newargs = [] + for arg in expr.args: + if isinstance(arg, KroneckerDelta): + eqs.append(arg.args[0] - arg.args[1]) + else: + newargs.append(arg) + if not eqs: + return expr + solns = solve(eqs, dict=True) + if len(solns) == 0: + return S.Zero + elif len(solns) == 1: + newargs += [KroneckerDelta(k, v) for k, v in solns[0].items()] + expr2 = expr.func(*newargs) + if expr != expr2: + return _remove_multiple_delta(expr2) + return expr + + +@cacheit +def _simplify_delta(expr): + """ + Rewrite a KroneckerDelta's indices in its simplest form. + """ + if isinstance(expr, KroneckerDelta): + try: + slns = solve(expr.args[0] - expr.args[1], dict=True) + if slns and len(slns) == 1: + return Mul(*[KroneckerDelta(*(key, value)) + for key, value in slns[0].items()]) + except NotImplementedError: + pass + return expr + + +@cacheit +def deltaproduct(f, limit): + """ + Handle products containing a KroneckerDelta. + + See Also + ======== + + deltasummation + sympy.functions.special.tensor_functions.KroneckerDelta + sympy.concrete.products.product + """ + if ((limit[2] - limit[1]) < 0) == True: + return S.One + + if not f.has(KroneckerDelta): + return product(f, limit) + + if f.is_Add: + # Identify the term in the Add that has a simple KroneckerDelta + delta = None + terms = [] + for arg in sorted(f.args, key=default_sort_key): + if delta is None and _has_simple_delta(arg, limit[0]): + delta = arg + else: + terms.append(arg) + newexpr = f.func(*terms) + k = Dummy("kprime", integer=True) + if isinstance(limit[1], int) and isinstance(limit[2], int): + result = deltaproduct(newexpr, limit) + sum(deltaproduct(newexpr, (limit[0], limit[1], ik - 1)) * + delta.subs(limit[0], ik) * + deltaproduct(newexpr, (limit[0], ik + 1, limit[2])) for ik in range(int(limit[1]), int(limit[2] + 1)) + ) + else: + result = deltaproduct(newexpr, limit) + deltasummation( + deltaproduct(newexpr, (limit[0], limit[1], k - 1)) * + delta.subs(limit[0], k) * + deltaproduct(newexpr, (limit[0], k + 1, limit[2])), + (k, limit[1], limit[2]), + no_piecewise=_has_simple_delta(newexpr, limit[0]) + ) + return _remove_multiple_delta(result) + + delta, _ = _extract_delta(f, limit[0]) + + if not delta: + g = _expand_delta(f, limit[0]) + if f != g: + try: + return factor(deltaproduct(g, limit)) + except AssertionError: + return deltaproduct(g, limit) + return product(f, limit) + + return _remove_multiple_delta(f.subs(limit[0], limit[1])*KroneckerDelta(limit[2], limit[1])) + \ + S.One*_simplify_delta(KroneckerDelta(limit[2], limit[1] - 1)) + + +@cacheit +def deltasummation(f, limit, no_piecewise=False): + """ + Handle summations containing a KroneckerDelta. + + Explanation + =========== + + The idea for summation is the following: + + - If we are dealing with a KroneckerDelta expression, i.e. KroneckerDelta(g(x), j), + we try to simplify it. + + If we could simplify it, then we sum the resulting expression. + We already know we can sum a simplified expression, because only + simple KroneckerDelta expressions are involved. + + If we could not simplify it, there are two cases: + + 1) The expression is a simple expression: we return the summation, + taking care if we are dealing with a Derivative or with a proper + KroneckerDelta. + + 2) The expression is not simple (i.e. KroneckerDelta(cos(x))): we can do + nothing at all. + + - If the expr is a multiplication expr having a KroneckerDelta term: + + First we expand it. + + If the expansion did work, then we try to sum the expansion. + + If not, we try to extract a simple KroneckerDelta term, then we have two + cases: + + 1) We have a simple KroneckerDelta term, so we return the summation. + + 2) We did not have a simple term, but we do have an expression with + simplified KroneckerDelta terms, so we sum this expression. + + Examples + ======== + + >>> from sympy import oo, symbols + >>> from sympy.abc import k + >>> i, j = symbols('i, j', integer=True, finite=True) + >>> from sympy.concrete.delta import deltasummation + >>> from sympy import KroneckerDelta + >>> deltasummation(KroneckerDelta(i, k), (k, -oo, oo)) + 1 + >>> deltasummation(KroneckerDelta(i, k), (k, 0, oo)) + Piecewise((1, i >= 0), (0, True)) + >>> deltasummation(KroneckerDelta(i, k), (k, 1, 3)) + Piecewise((1, (i >= 1) & (i <= 3)), (0, True)) + >>> deltasummation(k*KroneckerDelta(i, j)*KroneckerDelta(j, k), (k, -oo, oo)) + j*KroneckerDelta(i, j) + >>> deltasummation(j*KroneckerDelta(i, j), (j, -oo, oo)) + i + >>> deltasummation(i*KroneckerDelta(i, j), (i, -oo, oo)) + j + + See Also + ======== + + deltaproduct + sympy.functions.special.tensor_functions.KroneckerDelta + sympy.concrete.sums.summation + """ + if ((limit[2] - limit[1]) < 0) == True: + return S.Zero + + if not f.has(KroneckerDelta): + return summation(f, limit) + + x = limit[0] + + g = _expand_delta(f, x) + if g.is_Add: + return piecewise_fold( + g.func(*[deltasummation(h, limit, no_piecewise) for h in g.args])) + + # try to extract a simple KroneckerDelta term + delta, expr = _extract_delta(g, x) + + if (delta is not None) and (delta.delta_range is not None): + dinf, dsup = delta.delta_range + if (limit[1] - dinf <= 0) == True and (limit[2] - dsup >= 0) == True: + no_piecewise = True + + if not delta: + return summation(f, limit) + + solns = solve(delta.args[0] - delta.args[1], x) + if len(solns) == 0: + return S.Zero + elif len(solns) != 1: + return Sum(f, limit) + value = solns[0] + if no_piecewise: + return expr.subs(x, value) + return Piecewise( + (expr.subs(x, value), Interval(*limit[1:3]).as_relational(value)), + (S.Zero, True) + ) diff --git a/phivenv/Lib/site-packages/sympy/concrete/expr_with_intlimits.py b/phivenv/Lib/site-packages/sympy/concrete/expr_with_intlimits.py new file mode 100644 index 0000000000000000000000000000000000000000..8e109913cdb2f3018096972b14651b990f4b985e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/expr_with_intlimits.py @@ -0,0 +1,354 @@ +from sympy.concrete.expr_with_limits import ExprWithLimits +from sympy.core.singleton import S +from sympy.core.relational import Eq + +class ReorderError(NotImplementedError): + """ + Exception raised when trying to reorder dependent limits. + """ + def __init__(self, expr, msg): + super().__init__( + "%s could not be reordered: %s." % (expr, msg)) + +class ExprWithIntLimits(ExprWithLimits): + """ + Superclass for Product and Sum. + + See Also + ======== + + sympy.concrete.expr_with_limits.ExprWithLimits + sympy.concrete.products.Product + sympy.concrete.summations.Sum + """ + __slots__ = () + + def change_index(self, var, trafo, newvar=None): + r""" + Change index of a Sum or Product. + + Perform a linear transformation `x \mapsto a x + b` on the index variable + `x`. For `a` the only values allowed are `\pm 1`. A new variable to be used + after the change of index can also be specified. + + Explanation + =========== + + ``change_index(expr, var, trafo, newvar=None)`` where ``var`` specifies the + index variable `x` to transform. The transformation ``trafo`` must be linear + and given in terms of ``var``. If the optional argument ``newvar`` is + provided then ``var`` gets replaced by ``newvar`` in the final expression. + + Examples + ======== + + >>> from sympy import Sum, Product, simplify + >>> from sympy.abc import x, y, a, b, c, d, u, v, i, j, k, l + + >>> S = Sum(x, (x, a, b)) + >>> S.doit() + -a**2/2 + a/2 + b**2/2 + b/2 + + >>> Sn = S.change_index(x, x + 1, y) + >>> Sn + Sum(y - 1, (y, a + 1, b + 1)) + >>> Sn.doit() + -a**2/2 + a/2 + b**2/2 + b/2 + + >>> Sn = S.change_index(x, -x, y) + >>> Sn + Sum(-y, (y, -b, -a)) + >>> Sn.doit() + -a**2/2 + a/2 + b**2/2 + b/2 + + >>> Sn = S.change_index(x, x+u) + >>> Sn + Sum(-u + x, (x, a + u, b + u)) + >>> Sn.doit() + -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u + >>> simplify(Sn.doit()) + -a**2/2 + a/2 + b**2/2 + b/2 + + >>> Sn = S.change_index(x, -x - u, y) + >>> Sn + Sum(-u - y, (y, -b - u, -a - u)) + >>> Sn.doit() + -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u + >>> simplify(Sn.doit()) + -a**2/2 + a/2 + b**2/2 + b/2 + + >>> P = Product(i*j**2, (i, a, b), (j, c, d)) + >>> P + Product(i*j**2, (i, a, b), (j, c, d)) + >>> P2 = P.change_index(i, i+3, k) + >>> P2 + Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d)) + >>> P3 = P2.change_index(j, -j, l) + >>> P3 + Product(l**2*(k - 3), (k, a + 3, b + 3), (l, -d, -c)) + + When dealing with symbols only, we can make a + general linear transformation: + + >>> Sn = S.change_index(x, u*x+v, y) + >>> Sn + Sum((-v + y)/u, (y, b*u + v, a*u + v)) + >>> Sn.doit() + -v*(a*u - b*u + 1)/u + (a**2*u**2/2 + a*u*v + a*u/2 - b**2*u**2/2 - b*u*v + b*u/2 + v)/u + >>> simplify(Sn.doit()) + a**2*u/2 + a/2 - b**2*u/2 + b/2 + + However, the last result can be inconsistent with usual + summation where the index increment is always 1. This is + obvious as we get back the original value only for ``u`` + equal +1 or -1. + + See Also + ======== + + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index, + reorder_limit, + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder, + sympy.concrete.summations.Sum.reverse_order, + sympy.concrete.products.Product.reverse_order + """ + if newvar is None: + newvar = var + + limits = [] + for limit in self.limits: + if limit[0] == var: + p = trafo.as_poly(var) + if p.degree() != 1: + raise ValueError("Index transformation is not linear") + alpha = p.coeff_monomial(var) + beta = p.coeff_monomial(S.One) + if alpha.is_number: + if alpha == S.One: + limits.append((newvar, alpha*limit[1] + beta, alpha*limit[2] + beta)) + elif alpha == S.NegativeOne: + limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta)) + else: + raise ValueError("Linear transformation results in non-linear summation stepsize") + else: + # Note that the case of alpha being symbolic can give issues if alpha < 0. + limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta)) + else: + limits.append(limit) + + function = self.function.subs(var, (var - beta)/alpha) + function = function.subs(var, newvar) + + return self.func(function, *limits) + + + def index(expr, x): + """ + Return the index of a dummy variable in the list of limits. + + Explanation + =========== + + ``index(expr, x)`` returns the index of the dummy variable ``x`` in the + limits of ``expr``. Note that we start counting with 0 at the inner-most + limits tuple. + + Examples + ======== + + >>> from sympy.abc import x, y, a, b, c, d + >>> from sympy import Sum, Product + >>> Sum(x*y, (x, a, b), (y, c, d)).index(x) + 0 + >>> Sum(x*y, (x, a, b), (y, c, d)).index(y) + 1 + >>> Product(x*y, (x, a, b), (y, c, d)).index(x) + 0 + >>> Product(x*y, (x, a, b), (y, c, d)).index(y) + 1 + + See Also + ======== + + reorder_limit, reorder, sympy.concrete.summations.Sum.reverse_order, + sympy.concrete.products.Product.reverse_order + """ + variables = [limit[0] for limit in expr.limits] + + if variables.count(x) != 1: + raise ValueError(expr, "Number of instances of variable not equal to one") + else: + return variables.index(x) + + def reorder(expr, *arg): + """ + Reorder limits in a expression containing a Sum or a Product. + + Explanation + =========== + + ``expr.reorder(*arg)`` reorders the limits in the expression ``expr`` + according to the list of tuples given by ``arg``. These tuples can + contain numerical indices or index variable names or involve both. + + Examples + ======== + + >>> from sympy import Sum, Product + >>> from sympy.abc import x, y, z, a, b, c, d, e, f + + >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((x, y)) + Sum(x*y, (y, c, d), (x, a, b)) + + >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder((x, y), (x, z), (y, z)) + Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b)) + + >>> P = Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)) + >>> P.reorder((x, y), (x, z), (y, z)) + Product(x*y*z, (z, e, f), (y, c, d), (x, a, b)) + + We can also select the index variables by counting them, starting + with the inner-most one: + + >>> Sum(x**2, (x, a, b), (x, c, d)).reorder((0, 1)) + Sum(x**2, (x, c, d), (x, a, b)) + + And of course we can mix both schemes: + + >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x)) + Sum(x*y, (y, c, d), (x, a, b)) + >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, 0)) + Sum(x*y, (y, c, d), (x, a, b)) + + See Also + ======== + + reorder_limit, index, sympy.concrete.summations.Sum.reverse_order, + sympy.concrete.products.Product.reverse_order + """ + new_expr = expr + + for r in arg: + if len(r) != 2: + raise ValueError(r, "Invalid number of arguments") + + index1 = r[0] + index2 = r[1] + + if not isinstance(r[0], int): + index1 = expr.index(r[0]) + if not isinstance(r[1], int): + index2 = expr.index(r[1]) + + new_expr = new_expr.reorder_limit(index1, index2) + + return new_expr + + + def reorder_limit(expr, x, y): + """ + Interchange two limit tuples of a Sum or Product expression. + + Explanation + =========== + + ``expr.reorder_limit(x, y)`` interchanges two limit tuples. The + arguments ``x`` and ``y`` are integers corresponding to the index + variables of the two limits which are to be interchanged. The + expression ``expr`` has to be either a Sum or a Product. + + Examples + ======== + + >>> from sympy.abc import x, y, z, a, b, c, d, e, f + >>> from sympy import Sum, Product + + >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2) + Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b)) + >>> Sum(x**2, (x, a, b), (x, c, d)).reorder_limit(1, 0) + Sum(x**2, (x, c, d), (x, a, b)) + + >>> Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2) + Product(x*y*z, (z, e, f), (y, c, d), (x, a, b)) + + See Also + ======== + + index, reorder, sympy.concrete.summations.Sum.reverse_order, + sympy.concrete.products.Product.reverse_order + """ + var = {limit[0] for limit in expr.limits} + limit_x = expr.limits[x] + limit_y = expr.limits[y] + + if (len(set(limit_x[1].free_symbols).intersection(var)) == 0 and + len(set(limit_x[2].free_symbols).intersection(var)) == 0 and + len(set(limit_y[1].free_symbols).intersection(var)) == 0 and + len(set(limit_y[2].free_symbols).intersection(var)) == 0): + + limits = [] + for i, limit in enumerate(expr.limits): + if i == x: + limits.append(limit_y) + elif i == y: + limits.append(limit_x) + else: + limits.append(limit) + + return type(expr)(expr.function, *limits) + else: + raise ReorderError(expr, "could not interchange the two limits specified") + + @property + def has_empty_sequence(self): + """ + Returns True if the Sum or Product is computed for an empty sequence. + + Examples + ======== + + >>> from sympy import Sum, Product, Symbol + >>> m = Symbol('m') + >>> Sum(m, (m, 1, 0)).has_empty_sequence + True + + >>> Sum(m, (m, 1, 1)).has_empty_sequence + False + + >>> M = Symbol('M', integer=True, positive=True) + >>> Product(m, (m, 1, M)).has_empty_sequence + False + + >>> Product(m, (m, 2, M)).has_empty_sequence + + >>> Product(m, (m, M + 1, M)).has_empty_sequence + True + + >>> N = Symbol('N', integer=True, positive=True) + >>> Sum(m, (m, N, M)).has_empty_sequence + + >>> N = Symbol('N', integer=True, negative=True) + >>> Sum(m, (m, N, M)).has_empty_sequence + False + + See Also + ======== + + has_reversed_limits + has_finite_limits + + """ + ret_None = False + for lim in self.limits: + dif = lim[1] - lim[2] + eq = Eq(dif, 1) + if eq == True: + return True + elif eq == False: + continue + else: + ret_None = True + + if ret_None: + return None + return False diff --git a/phivenv/Lib/site-packages/sympy/concrete/expr_with_limits.py b/phivenv/Lib/site-packages/sympy/concrete/expr_with_limits.py new file mode 100644 index 0000000000000000000000000000000000000000..034e6ab0a7663525632abe0224fbac973c505c08 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/expr_with_limits.py @@ -0,0 +1,603 @@ +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.function import AppliedUndef, UndefinedFunction +from sympy.core.mul import Mul +from sympy.core.relational import Equality, Relational +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, Dummy +from sympy.core.sympify import sympify +from sympy.functions.elementary.piecewise import (piecewise_fold, + Piecewise) +from sympy.logic.boolalg import BooleanFunction +from sympy.matrices.matrixbase import MatrixBase +from sympy.sets.sets import Interval, Set +from sympy.sets.fancysets import Range +from sympy.tensor.indexed import Idx +from sympy.utilities import flatten +from sympy.utilities.iterables import sift, is_sequence +from sympy.utilities.exceptions import sympy_deprecation_warning + + +def _common_new(cls, function, *symbols, discrete, **assumptions): + """Return either a special return value or the tuple, + (function, limits, orientation). This code is common to + both ExprWithLimits and AddWithLimits.""" + function = sympify(function) + + if isinstance(function, Equality): + # This transforms e.g. Integral(Eq(x, y)) to Eq(Integral(x), Integral(y)) + # but that is only valid for definite integrals. + limits, orientation = _process_limits(*symbols, discrete=discrete) + if not (limits and all(len(limit) == 3 for limit in limits)): + sympy_deprecation_warning( + """ + Creating a indefinite integral with an Eq() argument is + deprecated. + + This is because indefinite integrals do not preserve equality + due to the arbitrary constants. If you want an equality of + indefinite integrals, use Eq(Integral(a, x), Integral(b, x)) + explicitly. + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-indefinite-integral-eq", + stacklevel=5, + ) + + lhs = function.lhs + rhs = function.rhs + return Equality(cls(lhs, *symbols, **assumptions), \ + cls(rhs, *symbols, **assumptions)) + + if function is S.NaN: + return S.NaN + + if symbols: + limits, orientation = _process_limits(*symbols, discrete=discrete) + for i, li in enumerate(limits): + if len(li) == 4: + function = function.subs(li[0], li[-1]) + limits[i] = Tuple(*li[:-1]) + else: + # symbol not provided -- we can still try to compute a general form + free = function.free_symbols + if len(free) != 1: + raise ValueError( + "specify dummy variables for %s" % function) + limits, orientation = [Tuple(s) for s in free], 1 + + # denest any nested calls + while cls == type(function): + limits = list(function.limits) + limits + function = function.function + + # Any embedded piecewise functions need to be brought out to the + # top level. We only fold Piecewise that contain the integration + # variable. + reps = {} + symbols_of_integration = {i[0] for i in limits} + for p in function.atoms(Piecewise): + if not p.has(*symbols_of_integration): + reps[p] = Dummy() + # mask off those that don't + function = function.xreplace(reps) + # do the fold + function = piecewise_fold(function) + # remove the masking + function = function.xreplace({v: k for k, v in reps.items()}) + + return function, limits, orientation + + +def _process_limits(*symbols, discrete=None): + """Process the list of symbols and convert them to canonical limits, + storing them as Tuple(symbol, lower, upper). The orientation of + the function is also returned when the upper limit is missing + so (x, 1, None) becomes (x, None, 1) and the orientation is changed. + In the case that a limit is specified as (symbol, Range), a list of + length 4 may be returned if a change of variables is needed; the + expression that should replace the symbol in the expression is + the fourth element in the list. + """ + limits = [] + orientation = 1 + if discrete is None: + err_msg = 'discrete must be True or False' + elif discrete: + err_msg = 'use Range, not Interval or Relational' + else: + err_msg = 'use Interval or Relational, not Range' + for V in symbols: + if isinstance(V, (Relational, BooleanFunction)): + if discrete: + raise TypeError(err_msg) + variable = V.atoms(Symbol).pop() + V = (variable, V.as_set()) + elif isinstance(V, Symbol) or getattr(V, '_diff_wrt', False): + if isinstance(V, Idx): + if V.lower is None or V.upper is None: + limits.append(Tuple(V)) + else: + limits.append(Tuple(V, V.lower, V.upper)) + else: + limits.append(Tuple(V)) + continue + if is_sequence(V) and not isinstance(V, Set): + if len(V) == 2 and isinstance(V[1], Set): + V = list(V) + if isinstance(V[1], Interval): # includes Reals + if discrete: + raise TypeError(err_msg) + V[1:] = V[1].inf, V[1].sup + elif isinstance(V[1], Range): + if not discrete: + raise TypeError(err_msg) + lo = V[1].inf + hi = V[1].sup + dx = abs(V[1].step) # direction doesn't matter + if dx == 1: + V[1:] = [lo, hi] + else: + if lo is not S.NegativeInfinity: + V = [V[0]] + [0, (hi - lo)//dx, dx*V[0] + lo] + else: + V = [V[0]] + [0, S.Infinity, -dx*V[0] + hi] + else: + # more complicated sets would require splitting, e.g. + # Union(Interval(1, 3), interval(6,10)) + raise NotImplementedError( + 'expecting Range' if discrete else + 'Relational or single Interval' ) + V = sympify(flatten(V)) # list of sympified elements/None + if isinstance(V[0], (Symbol, Idx)) or getattr(V[0], '_diff_wrt', False): + newsymbol = V[0] + if len(V) == 3: + # general case + if V[2] is None and V[1] is not None: + orientation *= -1 + V = [newsymbol] + [i for i in V[1:] if i is not None] + + lenV = len(V) + if not isinstance(newsymbol, Idx) or lenV == 3: + if lenV == 4: + limits.append(Tuple(*V)) + continue + if lenV == 3: + if isinstance(newsymbol, Idx): + # Idx represents an integer which may have + # specified values it can take on; if it is + # given such a value, an error is raised here + # if the summation would try to give it a larger + # or smaller value than permitted. None and Symbolic + # values will not raise an error. + lo, hi = newsymbol.lower, newsymbol.upper + try: + if lo is not None and not bool(V[1] >= lo): + raise ValueError("Summation will set Idx value too low.") + except TypeError: + pass + try: + if hi is not None and not bool(V[2] <= hi): + raise ValueError("Summation will set Idx value too high.") + except TypeError: + pass + limits.append(Tuple(*V)) + continue + if lenV == 1 or (lenV == 2 and V[1] is None): + limits.append(Tuple(newsymbol)) + continue + elif lenV == 2: + limits.append(Tuple(newsymbol, V[1])) + continue + + raise ValueError('Invalid limits given: %s' % str(symbols)) + + return limits, orientation + + +class ExprWithLimits(Expr): + __slots__ = ('is_commutative',) + + def __new__(cls, function, *symbols, **assumptions): + from sympy.concrete.products import Product + pre = _common_new(cls, function, *symbols, + discrete=issubclass(cls, Product), **assumptions) + if isinstance(pre, tuple): + function, limits, _ = pre + else: + return pre + + # limits must have upper and lower bounds; the indefinite form + # is not supported. This restriction does not apply to AddWithLimits + if any(len(l) != 3 or None in l for l in limits): + raise ValueError('ExprWithLimits requires values for lower and upper bounds.') + + obj = Expr.__new__(cls, **assumptions) + arglist = [function] + arglist.extend(limits) + obj._args = tuple(arglist) + obj.is_commutative = function.is_commutative # limits already checked + + return obj + + @property + def function(self): + """Return the function applied across limits. + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x + >>> Integral(x**2, (x,)).function + x**2 + + See Also + ======== + + limits, variables, free_symbols + """ + return self._args[0] + + @property + def kind(self): + return self.function.kind + + @property + def limits(self): + """Return the limits of expression. + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x, i + >>> Integral(x**i, (i, 1, 3)).limits + ((i, 1, 3),) + + See Also + ======== + + function, variables, free_symbols + """ + return self._args[1:] + + @property + def variables(self): + """Return a list of the limit variables. + + >>> from sympy import Sum + >>> from sympy.abc import x, i + >>> Sum(x**i, (i, 1, 3)).variables + [i] + + See Also + ======== + + function, limits, free_symbols + as_dummy : Rename dummy variables + sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable + """ + return [l[0] for l in self.limits] + + @property + def bound_symbols(self): + """Return only variables that are dummy variables. + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x, i, j, k + >>> Integral(x**i, (i, 1, 3), (j, 2), k).bound_symbols + [i, j] + + See Also + ======== + + function, limits, free_symbols + as_dummy : Rename dummy variables + sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable + """ + return [l[0] for l in self.limits if len(l) != 1] + + @property + def free_symbols(self): + """ + This method returns the symbols in the object, excluding those + that take on a specific value (i.e. the dummy symbols). + + Examples + ======== + + >>> from sympy import Sum + >>> from sympy.abc import x, y + >>> Sum(x, (x, y, 1)).free_symbols + {y} + """ + # don't test for any special values -- nominal free symbols + # should be returned, e.g. don't return set() if the + # function is zero -- treat it like an unevaluated expression. + function, limits = self.function, self.limits + # mask off non-symbol integration variables that have + # more than themself as a free symbol + reps = {i[0]: i[0] if i[0].free_symbols == {i[0]} else Dummy() + for i in self.limits} + function = function.xreplace(reps) + isyms = function.free_symbols + for xab in limits: + v = reps[xab[0]] + if len(xab) == 1: + isyms.add(v) + continue + # take out the target symbol + if v in isyms: + isyms.remove(v) + # add in the new symbols + for i in xab[1:]: + isyms.update(i.free_symbols) + reps = {v: k for k, v in reps.items()} + return {reps.get(_, _) for _ in isyms} + + @property + def is_number(self): + """Return True if the Sum has no free symbols, else False.""" + return not self.free_symbols + + def _eval_interval(self, x, a, b): + limits = [(i if i[0] != x else (x, a, b)) for i in self.limits] + integrand = self.function + return self.func(integrand, *limits) + + def _eval_subs(self, old, new): + """ + Perform substitutions over non-dummy variables + of an expression with limits. Also, can be used + to specify point-evaluation of an abstract antiderivative. + + Examples + ======== + + >>> from sympy import Sum, oo + >>> from sympy.abc import s, n + >>> Sum(1/n**s, (n, 1, oo)).subs(s, 2) + Sum(n**(-2), (n, 1, oo)) + + >>> from sympy import Integral + >>> from sympy.abc import x, a + >>> Integral(a*x**2, x).subs(x, 4) + Integral(a*x**2, (x, 4)) + + See Also + ======== + + variables : Lists the integration variables + transform : Perform mapping on the dummy variable for integrals + change_index : Perform mapping on the sum and product dummy variables + + """ + func, limits = self.function, list(self.limits) + + # If one of the expressions we are replacing is used as a func index + # one of two things happens. + # - the old variable first appears as a free variable + # so we perform all free substitutions before it becomes + # a func index. + # - the old variable first appears as a func index, in + # which case we ignore. See change_index. + + # Reorder limits to match standard mathematical practice for scoping + limits.reverse() + + if not isinstance(old, Symbol) or \ + old.free_symbols.intersection(self.free_symbols): + sub_into_func = True + for i, xab in enumerate(limits): + if 1 == len(xab) and old == xab[0]: + if new._diff_wrt: + xab = (new,) + else: + xab = (old, old) + limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]]) + if len(xab[0].free_symbols.intersection(old.free_symbols)) != 0: + sub_into_func = False + break + if isinstance(old, (AppliedUndef, UndefinedFunction)): + sy2 = set(self.variables).intersection(set(new.atoms(Symbol))) + sy1 = set(self.variables).intersection(set(old.args)) + if not sy2.issubset(sy1): + raise ValueError( + "substitution cannot create dummy dependencies") + sub_into_func = True + if sub_into_func: + func = func.subs(old, new) + else: + # old is a Symbol and a dummy variable of some limit + for i, xab in enumerate(limits): + if len(xab) == 3: + limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]]) + if old == xab[0]: + break + # simplify redundant limits (x, x) to (x, ) + for i, xab in enumerate(limits): + if len(xab) == 2 and (xab[0] - xab[1]).is_zero: + limits[i] = Tuple(xab[0], ) + + # Reorder limits back to representation-form + limits.reverse() + + return self.func(func, *limits) + + @property + def has_finite_limits(self): + """ + Returns True if the limits are known to be finite, either by the + explicit bounds, assumptions on the bounds, or assumptions on the + variables. False if known to be infinite, based on the bounds. + None if not enough information is available to determine. + + Examples + ======== + + >>> from sympy import Sum, Integral, Product, oo, Symbol + >>> x = Symbol('x') + >>> Sum(x, (x, 1, 8)).has_finite_limits + True + + >>> Integral(x, (x, 1, oo)).has_finite_limits + False + + >>> M = Symbol('M') + >>> Sum(x, (x, 1, M)).has_finite_limits + + >>> N = Symbol('N', integer=True) + >>> Product(x, (x, 1, N)).has_finite_limits + True + + See Also + ======== + + has_reversed_limits + + """ + + ret_None = False + for lim in self.limits: + if len(lim) == 3: + if any(l.is_infinite for l in lim[1:]): + # Any of the bounds are +/-oo + return False + elif any(l.is_infinite is None for l in lim[1:]): + # Maybe there are assumptions on the variable? + if lim[0].is_infinite is None: + ret_None = True + else: + if lim[0].is_infinite is None: + ret_None = True + + if ret_None: + return None + return True + + @property + def has_reversed_limits(self): + """ + Returns True if the limits are known to be in reversed order, either + by the explicit bounds, assumptions on the bounds, or assumptions on the + variables. False if known to be in normal order, based on the bounds. + None if not enough information is available to determine. + + Examples + ======== + + >>> from sympy import Sum, Integral, Product, oo, Symbol + >>> x = Symbol('x') + >>> Sum(x, (x, 8, 1)).has_reversed_limits + True + + >>> Sum(x, (x, 1, oo)).has_reversed_limits + False + + >>> M = Symbol('M') + >>> Integral(x, (x, 1, M)).has_reversed_limits + + >>> N = Symbol('N', integer=True, positive=True) + >>> Sum(x, (x, 1, N)).has_reversed_limits + False + + >>> Product(x, (x, 2, N)).has_reversed_limits + + >>> Product(x, (x, 2, N)).subs(N, N + 2).has_reversed_limits + False + + See Also + ======== + + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.has_empty_sequence + + """ + ret_None = False + for lim in self.limits: + if len(lim) == 3: + var, a, b = lim + dif = b - a + if dif.is_extended_negative: + return True + elif dif.is_extended_nonnegative: + continue + else: + ret_None = True + else: + return None + if ret_None: + return None + return False + + +class AddWithLimits(ExprWithLimits): + r"""Represents unevaluated oriented additions. + Parent class for Integral and Sum. + """ + + __slots__ = () + + def __new__(cls, function, *symbols, **assumptions): + from sympy.concrete.summations import Sum + pre = _common_new(cls, function, *symbols, + discrete=issubclass(cls, Sum), **assumptions) + if isinstance(pre, tuple): + function, limits, orientation = pre + else: + return pre + + obj = Expr.__new__(cls, **assumptions) + arglist = [orientation*function] # orientation not used in ExprWithLimits + arglist.extend(limits) + obj._args = tuple(arglist) + obj.is_commutative = function.is_commutative # limits already checked + + return obj + + def _eval_adjoint(self): + if all(x.is_real for x in flatten(self.limits)): + return self.func(self.function.adjoint(), *self.limits) + return None + + def _eval_conjugate(self): + if all(x.is_real for x in flatten(self.limits)): + return self.func(self.function.conjugate(), *self.limits) + return None + + def _eval_transpose(self): + if all(x.is_real for x in flatten(self.limits)): + return self.func(self.function.transpose(), *self.limits) + return None + + def _eval_factor(self, **hints): + if 1 == len(self.limits): + summand = self.function.factor(**hints) + if summand.is_Mul: + out = sift(summand.args, lambda w: w.is_commutative \ + and not set(self.variables) & w.free_symbols) + return Mul(*out[True])*self.func(Mul(*out[False]), \ + *self.limits) + else: + summand = self.func(self.function, *self.limits[0:-1]).factor() + if not summand.has(self.variables[-1]): + return self.func(1, [self.limits[-1]]).doit()*summand + elif isinstance(summand, Mul): + return self.func(summand, self.limits[-1]).factor() + return self + + def _eval_expand_basic(self, **hints): + summand = self.function.expand(**hints) + force = hints.get('force', False) + if (summand.is_Add and (force or summand.is_commutative and + self.has_finite_limits is not False)): + return Add(*[self.func(i, *self.limits) for i in summand.args]) + elif isinstance(summand, MatrixBase): + return summand.applyfunc(lambda x: self.func(x, *self.limits)) + elif summand != self.function: + return self.func(summand, *self.limits) + return self diff --git a/phivenv/Lib/site-packages/sympy/concrete/gosper.py b/phivenv/Lib/site-packages/sympy/concrete/gosper.py new file mode 100644 index 0000000000000000000000000000000000000000..76eb20ef4f94bc6bff6324a1dcc09f90e05274b3 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/gosper.py @@ -0,0 +1,222 @@ +"""Gosper's algorithm for hypergeometric summation. """ + +from sympy.core import S, Dummy, symbols +from sympy.polys import Poly, parallel_poly_from_expr, factor +from sympy.utilities.iterables import is_sequence + + +def gosper_normal(f, g, n, polys=True): + r""" + Compute the Gosper's normal form of ``f`` and ``g``. + + Explanation + =========== + + Given relatively prime univariate polynomials ``f`` and ``g``, + rewrite their quotient to a normal form defined as follows: + + .. math:: + \frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)} + + where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are + monic polynomials in ``n`` with the following properties: + + 1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}` + 2. `\gcd(B(n), C(n+1)) = 1` + 3. `\gcd(A(n), C(n)) = 1` + + This normal form, or rational factorization in other words, is a + crucial step in Gosper's algorithm and in solving of difference + equations. It can be also used to decide if two hypergeometric + terms are similar or not. + + This procedure will return a tuple containing elements of this + factorization in the form ``(Z*A, B, C)``. + + Examples + ======== + + >>> from sympy.concrete.gosper import gosper_normal + >>> from sympy.abc import n + + >>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False) + (1/4, n + 3/2, n + 1/4) + + """ + (p, q), opt = parallel_poly_from_expr( + (f, g), n, field=True, extension=True) + + a, A = p.LC(), p.monic() + b, B = q.LC(), q.monic() + + C, Z = A.one, a/b + h = Dummy('h') + + D = Poly(n + h, n, h, domain=opt.domain) + + R = A.resultant(B.compose(D)) + roots = {r for r in R.ground_roots().keys() if r.is_Integer and r >= 0} + for i in sorted(roots): + d = A.gcd(B.shift(+i)) + + A = A.quo(d) + B = B.quo(d.shift(-i)) + + for j in range(1, i + 1): + C *= d.shift(-j) + + A = A.mul_ground(Z) + + if not polys: + A = A.as_expr() + B = B.as_expr() + C = C.as_expr() + + return A, B, C + + +def gosper_term(f, n): + r""" + Compute Gosper's hypergeometric term for ``f``. + + Explanation + =========== + + Suppose ``f`` is a hypergeometric term such that: + + .. math:: + s_n = \sum_{k=0}^{n-1} f_k + + and `f_k` does not depend on `n`. Returns a hypergeometric + term `g_n` such that `g_{n+1} - g_n = f_n`. + + Examples + ======== + + >>> from sympy.concrete.gosper import gosper_term + >>> from sympy import factorial + >>> from sympy.abc import n + + >>> gosper_term((4*n + 1)*factorial(n)/factorial(2*n + 1), n) + (-n - 1/2)/(n + 1/4) + + """ + from sympy.simplify import hypersimp + r = hypersimp(f, n) + + if r is None: + return None # 'f' is *not* a hypergeometric term + + p, q = r.as_numer_denom() + + A, B, C = gosper_normal(p, q, n) + B = B.shift(-1) + + N = S(A.degree()) + M = S(B.degree()) + K = S(C.degree()) + + if (N != M) or (A.LC() != B.LC()): + D = {K - max(N, M)} + elif not N: + D = {K - N + 1, S.Zero} + else: + D = {K - N + 1, (B.nth(N - 1) - A.nth(N - 1))/A.LC()} + + for d in set(D): + if not d.is_Integer or d < 0: + D.remove(d) + + if not D: + return None # 'f(n)' is *not* Gosper-summable + + d = max(D) + + coeffs = symbols('c:%s' % (d + 1), cls=Dummy) + domain = A.get_domain().inject(*coeffs) + + x = Poly(coeffs, n, domain=domain) + H = A*x.shift(1) - B*x - C + + from sympy.solvers.solvers import solve + solution = solve(H.coeffs(), coeffs) + + if solution is None: + return None # 'f(n)' is *not* Gosper-summable + + x = x.as_expr().subs(solution) + + for coeff in coeffs: + if coeff not in solution: + x = x.subs(coeff, 0) + + if x.is_zero: + return None # 'f(n)' is *not* Gosper-summable + else: + return B.as_expr()*x/C.as_expr() + + +def gosper_sum(f, k): + r""" + Gosper's hypergeometric summation algorithm. + + Explanation + =========== + + Given a hypergeometric term ``f`` such that: + + .. math :: + s_n = \sum_{k=0}^{n-1} f_k + + and `f(n)` does not depend on `n`, returns `g_{n} - g(0)` where + `g_{n+1} - g_n = f_n`, or ``None`` if `s_n` cannot be expressed + in closed form as a sum of hypergeometric terms. + + Examples + ======== + + >>> from sympy.concrete.gosper import gosper_sum + >>> from sympy import factorial + >>> from sympy.abc import n, k + + >>> f = (4*k + 1)*factorial(k)/factorial(2*k + 1) + >>> gosper_sum(f, (k, 0, n)) + (-factorial(n) + 2*factorial(2*n + 1))/factorial(2*n + 1) + >>> _.subs(n, 2) == sum(f.subs(k, i) for i in [0, 1, 2]) + True + >>> gosper_sum(f, (k, 3, n)) + (-60*factorial(n) + factorial(2*n + 1))/(60*factorial(2*n + 1)) + >>> _.subs(n, 5) == sum(f.subs(k, i) for i in [3, 4, 5]) + True + + References + ========== + + .. [1] Marko Petkovsek, Herbert S. Wilf, Doron Zeilberger, A = B, + AK Peters, Ltd., Wellesley, MA, USA, 1997, pp. 73--100 + + """ + indefinite = False + + if is_sequence(k): + k, a, b = k + else: + indefinite = True + + g = gosper_term(f, k) + + if g is None: + return None + + if indefinite: + result = f*g + else: + result = (f*(g + 1)).subs(k, b) - (f*g).subs(k, a) + + if result is S.NaN: + try: + result = (f*(g + 1)).limit(k, b) - (f*g).limit(k, a) + except NotImplementedError: + result = None + + return factor(result) diff --git a/phivenv/Lib/site-packages/sympy/concrete/guess.py b/phivenv/Lib/site-packages/sympy/concrete/guess.py new file mode 100644 index 0000000000000000000000000000000000000000..90aac54b442ce67c90cbb262e10d525e5f2ba316 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/guess.py @@ -0,0 +1,473 @@ +"""Various algorithms for helping identifying numbers and sequences.""" + + +from sympy.concrete.products import (Product, product) +from sympy.core import Function, S +from sympy.core.add import Add +from sympy.core.numbers import Integer, Rational +from sympy.core.symbol import Symbol, symbols +from sympy.core.sympify import sympify +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.integers import floor +from sympy.integrals.integrals import integrate +from sympy.polys.polyfuncs import rational_interpolate as rinterp +from sympy.polys.polytools import lcm +from sympy.simplify.radsimp import denom +from sympy.utilities import public + + +@public +def find_simple_recurrence_vector(l): + """ + This function is used internally by other functions from the + sympy.concrete.guess module. While most users may want to rather use the + function find_simple_recurrence when looking for recurrence relations + among rational numbers, the current function may still be useful when + some post-processing has to be done. + + Explanation + =========== + + The function returns a vector of length n when a recurrence relation of + order n is detected in the sequence of rational numbers v. + + If the returned vector has a length 1, then the returned value is always + the list [0], which means that no relation has been found. + + While the functions is intended to be used with rational numbers, it should + work for other kinds of real numbers except for some cases involving + quadratic numbers; for that reason it should be used with some caution when + the argument is not a list of rational numbers. + + Examples + ======== + + >>> from sympy.concrete.guess import find_simple_recurrence_vector + >>> from sympy import fibonacci + >>> find_simple_recurrence_vector([fibonacci(k) for k in range(12)]) + [1, -1, -1] + + See Also + ======== + + See the function sympy.concrete.guess.find_simple_recurrence which is more + user-friendly. + + """ + q1 = [0] + q2 = [1] + b, z = 0, len(l) >> 1 + while len(q2) <= z: + while l[b]==0: + b += 1 + if b == len(l): + c = 1 + for x in q2: + c = lcm(c, denom(x)) + if q2[0]*c < 0: c = -c + for k in range(len(q2)): + q2[k] = int(q2[k]*c) + return q2 + a = S.One/l[b] + m = [a] + for k in range(b+1, len(l)): + m.append(-sum(l[j+1]*m[b-j-1] for j in range(b, k))*a) + l, m = m, [0] * max(len(q2), b+len(q1)) + for k, q in enumerate(q2): + m[k] = a*q + for k, q in enumerate(q1): + m[k+b] += q + while m[-1]==0: m.pop() # because trailing zeros can occur + q1, q2, b = q2, m, 1 + return [0] + +@public +def find_simple_recurrence(v, A=Function('a'), N=Symbol('n')): + """ + Detects and returns a recurrence relation from a sequence of several integer + (or rational) terms. The name of the function in the returned expression is + 'a' by default; the main variable is 'n' by default. The smallest index in + the returned expression is always n (and never n-1, n-2, etc.). + + Examples + ======== + + >>> from sympy.concrete.guess import find_simple_recurrence + >>> from sympy import fibonacci + >>> find_simple_recurrence([fibonacci(k) for k in range(12)]) + -a(n) - a(n + 1) + a(n + 2) + + >>> from sympy import Function, Symbol + >>> a = [1, 1, 1] + >>> for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3]) + >>> find_simple_recurrence(a, A=Function('f'), N=Symbol('i')) + -8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3) + + """ + p = find_simple_recurrence_vector(v) + n = len(p) + if n <= 1: return S.Zero + + return Add(*[A(N+n-1-k)*p[k] for k in range(n)]) + + +@public +def rationalize(x, maxcoeff=10000): + """ + Helps identifying a rational number from a float (or mpmath.mpf) value by + using a continued fraction. The algorithm stops as soon as a large partial + quotient is detected (greater than 10000 by default). + + Examples + ======== + + >>> from sympy.concrete.guess import rationalize + >>> from mpmath import cos, pi + >>> rationalize(cos(pi/3)) + 1/2 + + >>> from mpmath import mpf + >>> rationalize(mpf("0.333333333333333")) + 1/3 + + While the function is rather intended to help 'identifying' rational + values, it may be used in some cases for approximating real numbers. + (Though other functions may be more relevant in that case.) + + >>> rationalize(pi, maxcoeff = 250) + 355/113 + + See Also + ======== + + Several other methods can approximate a real number as a rational, like: + + * fractions.Fraction.from_decimal + * fractions.Fraction.from_float + * mpmath.identify + * mpmath.pslq by using the following syntax: mpmath.pslq([x, 1]) + * mpmath.findpoly by using the following syntax: mpmath.findpoly(x, 1) + * sympy.simplify.nsimplify (which is a more general function) + + The main difference between the current function and all these variants is + that control focuses on magnitude of partial quotients here rather than on + global precision of the approximation. If the real is "known to be" a + rational number, the current function should be able to detect it correctly + with the default settings even when denominator is great (unless its + expansion contains unusually big partial quotients) which may occur + when studying sequences of increasing numbers. If the user cares more + on getting simple fractions, other methods may be more convenient. + + """ + p0, p1 = 0, 1 + q0, q1 = 1, 0 + a = floor(x) + while a < maxcoeff or q1==0: + p = a*p1 + p0 + q = a*q1 + q0 + p0, p1 = p1, p + q0, q1 = q1, q + if x==a: break + x = 1/(x-a) + a = floor(x) + return sympify(p) / q + + +@public +def guess_generating_function_rational(v, X=Symbol('x')): + """ + Tries to "guess" a rational generating function for a sequence of rational + numbers v. + + Examples + ======== + + >>> from sympy.concrete.guess import guess_generating_function_rational + >>> from sympy import fibonacci + >>> l = [fibonacci(k) for k in range(5,15)] + >>> guess_generating_function_rational(l) + (3*x + 5)/(-x**2 - x + 1) + + See Also + ======== + + sympy.series.approximants + mpmath.pade + + """ + # a) compute the denominator as q + q = find_simple_recurrence_vector(v) + n = len(q) + if n <= 1: return None + # b) compute the numerator as p + p = [sum(v[i-k]*q[k] for k in range(min(i+1, n))) + for i in range(len(v)>>1)] + return (sum(p[k]*X**k for k in range(len(p))) + / sum(q[k]*X**k for k in range(n))) + + +@public +def guess_generating_function(v, X=Symbol('x'), types=['all'], maxsqrtn=2): + """ + Tries to "guess" a generating function for a sequence of rational numbers v. + Only a few patterns are implemented yet. + + Explanation + =========== + + The function returns a dictionary where keys are the name of a given type of + generating function. Six types are currently implemented: + + type | formal definition + -------+---------------------------------------------------------------- + ogf | f(x) = Sum( a_k * x^k , k: 0..infinity ) + egf | f(x) = Sum( a_k * x^k / k! , k: 0..infinity ) + lgf | f(x) = Sum( (-1)^(k+1) a_k * x^k / k , k: 1..infinity ) + | (with initial index being hold as 1 rather than 0) + hlgf | f(x) = Sum( a_k * x^k / k , k: 1..infinity ) + | (with initial index being hold as 1 rather than 0) + lgdogf | f(x) = derivate( log(Sum( a_k * x^k, k: 0..infinity )), x) + lgdegf | f(x) = derivate( log(Sum( a_k * x^k / k!, k: 0..infinity )), x) + + In order to spare time, the user can select only some types of generating + functions (default being ['all']). While forgetting to use a list in the + case of a single type may seem to work most of the time as in: types='ogf' + this (convenient) syntax may lead to unexpected extra results in some cases. + + Discarding a type when calling the function does not mean that the type will + not be present in the returned dictionary; it only means that no extra + computation will be performed for that type, but the function may still add + it in the result when it can be easily converted from another type. + + Two generating functions (lgdogf and lgdegf) are not even computed if the + initial term of the sequence is 0; it may be useful in that case to try + again after having removed the leading zeros. + + Examples + ======== + + >>> from sympy.concrete.guess import guess_generating_function as ggf + >>> ggf([k+1 for k in range(12)], types=['ogf', 'lgf', 'hlgf']) + {'hlgf': 1/(1 - x), 'lgf': 1/(x + 1), 'ogf': 1/(x**2 - 2*x + 1)} + + >>> from sympy import sympify + >>> l = sympify("[3/2, 11/2, 0, -121/2, -363/2, 121]") + >>> ggf(l) + {'ogf': (x + 3/2)/(11*x**2 - 3*x + 1)} + + >>> from sympy import fibonacci + >>> ggf([fibonacci(k) for k in range(5, 15)], types=['ogf']) + {'ogf': (3*x + 5)/(-x**2 - x + 1)} + + >>> from sympy import factorial + >>> ggf([factorial(k) for k in range(12)], types=['ogf', 'egf', 'lgf']) + {'egf': 1/(1 - x)} + + >>> ggf([k+1 for k in range(12)], types=['egf']) + {'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)} + + N-th root of a rational function can also be detected (below is an example + coming from the sequence A108626 from https://oeis.org). + The greatest n-th root to be tested is specified as maxsqrtn (default 2). + + >>> ggf([1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf'] + sqrt(1/(x**4 + 2*x**2 - 4*x + 1)) + + References + ========== + + .. [1] "Concrete Mathematics", R.L. Graham, D.E. Knuth, O. Patashnik + .. [2] https://oeis.org/wiki/Generating_functions + + """ + # List of all types of all g.f. known by the algorithm + if 'all' in types: + types = ('ogf', 'egf', 'lgf', 'hlgf', 'lgdogf', 'lgdegf') + + result = {} + + # Ordinary Generating Function (ogf) + if 'ogf' in types: + # Perform some convolutions of the sequence with itself + t = [1] + [0]*(len(v) - 1) + for d in range(max(1, maxsqrtn)): + t = [sum(t[n-i]*v[i] for i in range(n+1)) for n in range(len(v))] + g = guess_generating_function_rational(t, X=X) + if g: + result['ogf'] = g**Rational(1, d+1) + break + + # Exponential Generating Function (egf) + if 'egf' in types: + # Transform sequence (division by factorial) + w, f = [], S.One + for i, k in enumerate(v): + f *= i if i else 1 + w.append(k/f) + # Perform some convolutions of the sequence with itself + t = [1] + [0]*(len(w) - 1) + for d in range(max(1, maxsqrtn)): + t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))] + g = guess_generating_function_rational(t, X=X) + if g: + result['egf'] = g**Rational(1, d+1) + break + + # Logarithmic Generating Function (lgf) + if 'lgf' in types: + # Transform sequence (multiplication by (-1)^(n+1) / n) + w, f = [], S.NegativeOne + for i, k in enumerate(v): + f = -f + w.append(f*k/Integer(i+1)) + # Perform some convolutions of the sequence with itself + t = [1] + [0]*(len(w) - 1) + for d in range(max(1, maxsqrtn)): + t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))] + g = guess_generating_function_rational(t, X=X) + if g: + result['lgf'] = g**Rational(1, d+1) + break + + # Hyperbolic logarithmic Generating Function (hlgf) + if 'hlgf' in types: + # Transform sequence (division by n+1) + w = [] + for i, k in enumerate(v): + w.append(k/Integer(i+1)) + # Perform some convolutions of the sequence with itself + t = [1] + [0]*(len(w) - 1) + for d in range(max(1, maxsqrtn)): + t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))] + g = guess_generating_function_rational(t, X=X) + if g: + result['hlgf'] = g**Rational(1, d+1) + break + + # Logarithmic derivative of ordinary generating Function (lgdogf) + if v[0] != 0 and ('lgdogf' in types + or ('ogf' in types and 'ogf' not in result)): + # Transform sequence by computing f'(x)/f(x) + # because log(f(x)) = integrate( f'(x)/f(x) ) + a, w = sympify(v[0]), [] + for n in range(len(v)-1): + w.append( + (v[n+1]*(n+1) - sum(w[-i-1]*v[i+1] for i in range(n)))/a) + # Perform some convolutions of the sequence with itself + t = [1] + [0]*(len(w) - 1) + for d in range(max(1, maxsqrtn)): + t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))] + g = guess_generating_function_rational(t, X=X) + if g: + result['lgdogf'] = g**Rational(1, d+1) + if 'ogf' not in result: + result['ogf'] = exp(integrate(result['lgdogf'], X)) + break + + # Logarithmic derivative of exponential generating Function (lgdegf) + if v[0] != 0 and ('lgdegf' in types + or ('egf' in types and 'egf' not in result)): + # Transform sequence / step 1 (division by factorial) + z, f = [], S.One + for i, k in enumerate(v): + f *= i if i else 1 + z.append(k/f) + # Transform sequence / step 2 by computing f'(x)/f(x) + # because log(f(x)) = integrate( f'(x)/f(x) ) + a, w = z[0], [] + for n in range(len(z)-1): + w.append( + (z[n+1]*(n+1) - sum(w[-i-1]*z[i+1] for i in range(n)))/a) + # Perform some convolutions of the sequence with itself + t = [1] + [0]*(len(w) - 1) + for d in range(max(1, maxsqrtn)): + t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))] + g = guess_generating_function_rational(t, X=X) + if g: + result['lgdegf'] = g**Rational(1, d+1) + if 'egf' not in result: + result['egf'] = exp(integrate(result['lgdegf'], X)) + break + + return result + + +@public +def guess(l, all=False, evaluate=True, niter=2, variables=None): + """ + This function is adapted from the Rate.m package for Mathematica + written by Christian Krattenthaler. + It tries to guess a formula from a given sequence of rational numbers. + + Explanation + =========== + + In order to speed up the process, the 'all' variable is set to False by + default, stopping the computation as some results are returned during an + iteration; the variable can be set to True if more iterations are needed + (other formulas may be found; however they may be equivalent to the first + ones). + + Another option is the 'evaluate' variable (default is True); setting it + to False will leave the involved products unevaluated. + + By default, the number of iterations is set to 2 but a greater value (up + to len(l)-1) can be specified with the optional 'niter' variable. + More and more convoluted results are found when the order of the + iteration gets higher: + + * first iteration returns polynomial or rational functions; + * second iteration returns products of rising factorials and their + inverses; + * third iteration returns products of products of rising factorials + and their inverses; + * etc. + + The returned formulas contain symbols i0, i1, i2, ... where the main + variables is i0 (and auxiliary variables are i1, i2, ...). A list of + other symbols can be provided in the 'variables' option; the length of + the least should be the value of 'niter' (more is acceptable but only + the first symbols will be used); in this case, the main variable will be + the first symbol in the list. + + Examples + ======== + + >>> from sympy.concrete.guess import guess + >>> guess([1,2,6,24,120], evaluate=False) + [Product(i1 + 1, (i1, 1, i0 - 1))] + + >>> from sympy import symbols + >>> r = guess([1,2,7,42,429,7436,218348,10850216], niter=4) + >>> i0 = symbols("i0") + >>> [r[0].subs(i0,n).doit() for n in range(1,10)] + [1, 2, 7, 42, 429, 7436, 218348, 10850216, 911835460] + """ + if any(a==0 for a in l[:-1]): + return [] + N = len(l) + niter = min(N-1, niter) + myprod = product if evaluate else Product + g = [] + res = [] + if variables is None: + symb = symbols('i:'+str(niter)) + else: + symb = variables + for k, s in enumerate(symb): + g.append(l) + n, r = len(l), [] + for i in range(n-2-1, -1, -1): + ri = rinterp(enumerate(g[k][:-1], start=1), i, X=s) + if ((denom(ri).subs({s:n}) != 0) + and (ri.subs({s:n}) - g[k][-1] == 0) + and ri not in r): + r.append(ri) + if r: + for i in range(k-1, -1, -1): + r = [g[i][0] + * myprod(v, (symb[i+1], 1, symb[i]-1)) for v in r] + if not all: return r + res += r + l = [Rational(l[i+1], l[i]) for i in range(N-k-1)] + return res diff --git a/phivenv/Lib/site-packages/sympy/concrete/products.py b/phivenv/Lib/site-packages/sympy/concrete/products.py new file mode 100644 index 0000000000000000000000000000000000000000..dd035551683531f80b0e4467fe1cdfbb6ebbe9ad --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/products.py @@ -0,0 +1,605 @@ +from __future__ import annotations + +from .expr_with_intlimits import ExprWithIntLimits +from .summations import Sum, summation, _dummy_with_inherited_properties_concrete +from sympy.core.expr import Expr +from sympy.core.exprtools import factor_terms +from sympy.core.function import Derivative +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.symbol import Dummy, Symbol +from sympy.functions.combinatorial.factorials import RisingFactorial +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.polys import quo, roots + + +class Product(ExprWithIntLimits): + r""" + Represents unevaluated products. + + Explanation + =========== + + ``Product`` represents a finite or infinite product, with the first + argument being the general form of terms in the series, and the second + argument being ``(dummy_variable, start, end)``, with ``dummy_variable`` + taking all integer values from ``start`` through ``end``. In accordance + with long-standing mathematical convention, the end term is included in + the product. + + Finite products + =============== + + For finite products (and products with symbolic limits assumed to be finite) + we follow the analogue of the summation convention described by Karr [1], + especially definition 3 of section 1.4. The product: + + .. math:: + + \prod_{m \leq i < n} f(i) + + has *the obvious meaning* for `m < n`, namely: + + .. math:: + + \prod_{m \leq i < n} f(i) = f(m) f(m+1) \cdot \ldots \cdot f(n-2) f(n-1) + + with the upper limit value `f(n)` excluded. The product over an empty set is + one if and only if `m = n`: + + .. math:: + + \prod_{m \leq i < n} f(i) = 1 \quad \mathrm{for} \quad m = n + + Finally, for all other products over empty sets we assume the following + definition: + + .. math:: + + \prod_{m \leq i < n} f(i) = \frac{1}{\prod_{n \leq i < m} f(i)} \quad \mathrm{for} \quad m > n + + It is important to note that above we define all products with the upper + limit being exclusive. This is in contrast to the usual mathematical notation, + but does not affect the product convention. Indeed we have: + + .. math:: + + \prod_{m \leq i < n} f(i) = \prod_{i = m}^{n - 1} f(i) + + where the difference in notation is intentional to emphasize the meaning, + with limits typeset on the top being inclusive. + + Examples + ======== + + >>> from sympy.abc import a, b, i, k, m, n, x + >>> from sympy import Product, oo + >>> Product(k, (k, 1, m)) + Product(k, (k, 1, m)) + >>> Product(k, (k, 1, m)).doit() + factorial(m) + >>> Product(k**2,(k, 1, m)) + Product(k**2, (k, 1, m)) + >>> Product(k**2,(k, 1, m)).doit() + factorial(m)**2 + + Wallis' product for pi: + + >>> W = Product(2*i/(2*i-1) * 2*i/(2*i+1), (i, 1, oo)) + >>> W + Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo)) + + Direct computation currently fails: + + >>> W.doit() + Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo)) + + But we can approach the infinite product by a limit of finite products: + + >>> from sympy import limit + >>> W2 = Product(2*i/(2*i-1)*2*i/(2*i+1), (i, 1, n)) + >>> W2 + Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, n)) + >>> W2e = W2.doit() + >>> W2e + 4**n*factorial(n)**2/(2**(2*n)*RisingFactorial(1/2, n)*RisingFactorial(3/2, n)) + >>> limit(W2e, n, oo) + pi/2 + + By the same formula we can compute sin(pi/2): + + >>> from sympy import combsimp, pi, gamma, simplify + >>> P = pi * x * Product(1 - x**2/k**2, (k, 1, n)) + >>> P = P.subs(x, pi/2) + >>> P + pi**2*Product(1 - pi**2/(4*k**2), (k, 1, n))/2 + >>> Pe = P.doit() + >>> Pe + pi**2*RisingFactorial(1 - pi/2, n)*RisingFactorial(1 + pi/2, n)/(2*factorial(n)**2) + >>> limit(Pe, n, oo).gammasimp() + sin(pi**2/2) + >>> Pe.rewrite(gamma) + (-1)**n*pi**2*gamma(pi/2)*gamma(n + 1 + pi/2)/(2*gamma(1 + pi/2)*gamma(-n + pi/2)*gamma(n + 1)**2) + + Products with the lower limit being larger than the upper one: + + >>> Product(1/i, (i, 6, 1)).doit() + 120 + >>> Product(i, (i, 2, 5)).doit() + 120 + + The empty product: + + >>> Product(i, (i, n, n-1)).doit() + 1 + + An example showing that the symbolic result of a product is still + valid for seemingly nonsensical values of the limits. Then the Karr + convention allows us to give a perfectly valid interpretation to + those products by interchanging the limits according to the above rules: + + >>> P = Product(2, (i, 10, n)).doit() + >>> P + 2**(n - 9) + >>> P.subs(n, 5) + 1/16 + >>> Product(2, (i, 10, 5)).doit() + 1/16 + >>> 1/Product(2, (i, 6, 9)).doit() + 1/16 + + An explicit example of the Karr summation convention applied to products: + + >>> P1 = Product(x, (i, a, b)).doit() + >>> P1 + x**(-a + b + 1) + >>> P2 = Product(x, (i, b+1, a-1)).doit() + >>> P2 + x**(a - b - 1) + >>> simplify(P1 * P2) + 1 + + And another one: + + >>> P1 = Product(i, (i, b, a)).doit() + >>> P1 + RisingFactorial(b, a - b + 1) + >>> P2 = Product(i, (i, a+1, b-1)).doit() + >>> P2 + RisingFactorial(a + 1, -a + b - 1) + >>> P1 * P2 + RisingFactorial(b, a - b + 1)*RisingFactorial(a + 1, -a + b - 1) + >>> combsimp(P1 * P2) + 1 + + See Also + ======== + + Sum, summation + product + + References + ========== + + .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM, + Volume 28 Issue 2, April 1981, Pages 305-350 + https://dl.acm.org/doi/10.1145/322248.322255 + .. [2] https://en.wikipedia.org/wiki/Multiplication#Capital_Pi_notation + .. [3] https://en.wikipedia.org/wiki/Empty_product + """ + + __slots__ = () + + limits: tuple[tuple[Symbol, Expr, Expr]] + + def __new__(cls, function, *symbols, **assumptions): + obj = ExprWithIntLimits.__new__(cls, function, *symbols, **assumptions) + return obj + + def _eval_rewrite_as_Sum(self, *args, **kwargs): + return exp(Sum(log(self.function), *self.limits)) + + @property + def term(self): + return self._args[0] + function = term + + def _eval_is_zero(self): + if self.has_empty_sequence: + return False + + z = self.term.is_zero + if z is True: + return True + if self.has_finite_limits: + # A Product is zero only if its term is zero assuming finite limits. + return z + + def _eval_is_extended_real(self): + if self.has_empty_sequence: + return True + + return self.function.is_extended_real + + def _eval_is_positive(self): + if self.has_empty_sequence: + return True + if self.function.is_positive and self.has_finite_limits: + return True + + def _eval_is_nonnegative(self): + if self.has_empty_sequence: + return True + if self.function.is_nonnegative and self.has_finite_limits: + return True + + def _eval_is_extended_nonnegative(self): + if self.has_empty_sequence: + return True + if self.function.is_extended_nonnegative: + return True + + def _eval_is_extended_nonpositive(self): + if self.has_empty_sequence: + return True + + def _eval_is_finite(self): + if self.has_finite_limits and self.function.is_finite: + return True + + def doit(self, **hints): + # first make sure any definite limits have product + # variables with matching assumptions + reps = {} + for xab in self.limits: + d = _dummy_with_inherited_properties_concrete(xab) + if d: + reps[xab[0]] = d + if reps: + undo = {v: k for k, v in reps.items()} + did = self.xreplace(reps).doit(**hints) + if isinstance(did, tuple): # when separate=True + did = tuple([i.xreplace(undo) for i in did]) + else: + did = did.xreplace(undo) + return did + + from sympy.simplify.powsimp import powsimp + f = self.function + for index, limit in enumerate(self.limits): + i, a, b = limit + dif = b - a + if dif.is_integer and dif.is_negative: + a, b = b + 1, a - 1 + f = 1 / f + + g = self._eval_product(f, (i, a, b)) + if g in (None, S.NaN): + return self.func(powsimp(f), *self.limits[index:]) + else: + f = g + + if hints.get('deep', True): + return f.doit(**hints) + else: + return powsimp(f) + + def _eval_conjugate(self): + return self.func(self.function.conjugate(), *self.limits) + + def _eval_product(self, term, limits): + + (k, a, n) = limits + + if k not in term.free_symbols: + if (term - 1).is_zero: + return S.One + return term**(n - a + 1) + + if a == n: + return term.subs(k, a) + + from .delta import deltaproduct, _has_simple_delta + if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): + return deltaproduct(term, limits) + + dif = n - a + definite = dif.is_Integer + if definite and (dif < 100): + return self._eval_product_direct(term, limits) + + elif term.is_polynomial(k): + poly = term.as_poly(k) + + A = B = Q = S.One + + all_roots = roots(poly) + + M = 0 + for r, m in all_roots.items(): + M += m + A *= RisingFactorial(a - r, n - a + 1)**m + Q *= (n - r)**m + + if M < poly.degree(): + arg = quo(poly, Q.as_poly(k)) + B = self.func(arg, (k, a, n)).doit() + + return poly.LC()**(n - a + 1) * A * B + + elif term.is_Add: + factored = factor_terms(term, fraction=True) + if factored.is_Mul: + return self._eval_product(factored, (k, a, n)) + + elif term.is_Mul: + # Factor in part without the summation variable and part with + without_k, with_k = term.as_coeff_mul(k) + + if len(with_k) >= 2: + # More than one term including k, so still a multiplication + exclude, include = [], [] + for t in with_k: + p = self._eval_product(t, (k, a, n)) + + if p is not None: + exclude.append(p) + else: + include.append(t) + + if not exclude: + return None + else: + arg = term._new_rawargs(*include) + A = Mul(*exclude) + B = self.func(arg, (k, a, n)).doit() + return without_k**(n - a + 1)*A * B + else: + # Just a single term + p = self._eval_product(with_k[0], (k, a, n)) + if p is None: + p = self.func(with_k[0], (k, a, n)).doit() + return without_k**(n - a + 1)*p + + + elif term.is_Pow: + if not term.base.has(k): + s = summation(term.exp, (k, a, n)) + + return term.base**s + elif not term.exp.has(k): + p = self._eval_product(term.base, (k, a, n)) + + if p is not None: + return p**term.exp + + elif isinstance(term, Product): + evaluated = term.doit() + f = self._eval_product(evaluated, limits) + if f is None: + return self.func(evaluated, limits) + else: + return f + + if definite: + return self._eval_product_direct(term, limits) + + def _eval_simplify(self, **kwargs): + from sympy.simplify.simplify import product_simplify + rv = product_simplify(self, **kwargs) + return rv.doit() if kwargs['doit'] else rv + + def _eval_transpose(self): + if self.is_commutative: + return self.func(self.function.transpose(), *self.limits) + return None + + def _eval_product_direct(self, term, limits): + (k, a, n) = limits + return Mul(*[term.subs(k, a + i) for i in range(n - a + 1)]) + + def _eval_derivative(self, x): + if isinstance(x, Symbol) and x not in self.free_symbols: + return S.Zero + f, limits = self.function, list(self.limits) + limit = limits.pop(-1) + if limits: + f = self.func(f, *limits) + i, a, b = limit + if x in a.free_symbols or x in b.free_symbols: + return None + h = Dummy() + rv = Sum( Product(f, (i, a, h - 1)) * Product(f, (i, h + 1, b)) * Derivative(f, x, evaluate=True).subs(i, h), (h, a, b)) + return rv + + def is_convergent(self): + r""" + See docs of :obj:`.Sum.is_convergent()` for explanation of convergence + in SymPy. + + Explanation + =========== + + The infinite product: + + .. math:: + + \prod_{1 \leq i < \infty} f(i) + + is defined by the sequence of partial products: + + .. math:: + + \prod_{i=1}^{n} f(i) = f(1) f(2) \cdots f(n) + + as n increases without bound. The product converges to a non-zero + value if and only if the sum: + + .. math:: + + \sum_{1 \leq i < \infty} \log{f(n)} + + converges. + + Examples + ======== + + >>> from sympy import Product, Symbol, cos, pi, exp, oo + >>> n = Symbol('n', integer=True) + >>> Product(n/(n + 1), (n, 1, oo)).is_convergent() + False + >>> Product(1/n**2, (n, 1, oo)).is_convergent() + False + >>> Product(cos(pi/n), (n, 1, oo)).is_convergent() + True + >>> Product(exp(-n**2), (n, 1, oo)).is_convergent() + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Infinite_product + """ + sequence_term = self.function + log_sum = log(sequence_term) + lim = self.limits + try: + is_conv = Sum(log_sum, *lim).is_convergent() + except NotImplementedError: + if Sum(sequence_term - 1, *lim).is_absolutely_convergent() is S.true: + return S.true + raise NotImplementedError("The algorithm to find the product convergence of %s " + "is not yet implemented" % (sequence_term)) + return is_conv + + def reverse_order(expr, *indices): + """ + Reverse the order of a limit in a Product. + + Explanation + =========== + + ``reverse_order(expr, *indices)`` reverses some limits in the expression + ``expr`` which can be either a ``Sum`` or a ``Product``. The selectors in + the argument ``indices`` specify some indices whose limits get reversed. + These selectors are either variable names or numerical indices counted + starting from the inner-most limit tuple. + + Examples + ======== + + >>> from sympy import gamma, Product, simplify, Sum + >>> from sympy.abc import x, y, a, b, c, d + >>> P = Product(x, (x, a, b)) + >>> Pr = P.reverse_order(x) + >>> Pr + Product(1/x, (x, b + 1, a - 1)) + >>> Pr = Pr.doit() + >>> Pr + 1/RisingFactorial(b + 1, a - b - 1) + >>> simplify(Pr.rewrite(gamma)) + Piecewise((gamma(b + 1)/gamma(a), b > -1), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True)) + >>> P = P.doit() + >>> P + RisingFactorial(a, -a + b + 1) + >>> simplify(P.rewrite(gamma)) + Piecewise((gamma(b + 1)/gamma(a), a > 0), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True)) + + While one should prefer variable names when specifying which limits + to reverse, the index counting notation comes in handy in case there + are several symbols with the same name. + + >>> S = Sum(x*y, (x, a, b), (y, c, d)) + >>> S + Sum(x*y, (x, a, b), (y, c, d)) + >>> S0 = S.reverse_order(0) + >>> S0 + Sum(-x*y, (x, b + 1, a - 1), (y, c, d)) + >>> S1 = S0.reverse_order(1) + >>> S1 + Sum(x*y, (x, b + 1, a - 1), (y, d + 1, c - 1)) + + Of course we can mix both notations: + + >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1) + Sum(x*y, (x, b + 1, a - 1), (y, 6, 1)) + >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x) + Sum(x*y, (x, b + 1, a - 1), (y, 6, 1)) + + See Also + ======== + + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index, + reorder_limit, + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder + + References + ========== + + .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM, + Volume 28 Issue 2, April 1981, Pages 305-350 + https://dl.acm.org/doi/10.1145/322248.322255 + + """ + l_indices = list(indices) + + for i, indx in enumerate(l_indices): + if not isinstance(indx, int): + l_indices[i] = expr.index(indx) + + e = 1 + limits = [] + for i, limit in enumerate(expr.limits): + l = limit + if i in l_indices: + e = -e + l = (limit[0], limit[2] + 1, limit[1] - 1) + limits.append(l) + + return Product(expr.function ** e, *limits) + + +def product(*args, **kwargs): + r""" + Compute the product. + + Explanation + =========== + + The notation for symbols is similar to the notation used in Sum or + Integral. product(f, (i, a, b)) computes the product of f with + respect to i from a to b, i.e., + + :: + + b + _____ + product(f(n), (i, a, b)) = | | f(n) + | | + i = a + + If it cannot compute the product, it returns an unevaluated Product object. + Repeated products can be computed by introducing additional symbols tuples:: + + Examples + ======== + + >>> from sympy import product, symbols + >>> i, n, m, k = symbols('i n m k', integer=True) + + >>> product(i, (i, 1, k)) + factorial(k) + >>> product(m, (i, 1, k)) + m**k + >>> product(i, (i, 1, k), (k, 1, n)) + Product(factorial(k), (k, 1, n)) + + """ + + prod = Product(*args, **kwargs) + + if isinstance(prod, Product): + return prod.doit(deep=False) + else: + return prod diff --git a/phivenv/Lib/site-packages/sympy/concrete/summations.py b/phivenv/Lib/site-packages/sympy/concrete/summations.py new file mode 100644 index 0000000000000000000000000000000000000000..96bdbd69b7820798fade4bc5ec145172b95947ef --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/summations.py @@ -0,0 +1,1659 @@ +from __future__ import annotations + +from sympy.calculus.singularities import is_decreasing +from sympy.calculus.accumulationbounds import AccumulationBounds +from .expr_with_intlimits import ExprWithIntLimits +from .expr_with_limits import AddWithLimits +from .gosper import gosper_sum +from sympy.core.expr import Expr +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.function import Derivative, expand +from sympy.core.mul import Mul +from sympy.core.numbers import Float, _illegal +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy, Wild, Symbol, symbols +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.combinatorial.numbers import bernoulli, harmonic +from sympy.functions.elementary.complexes import re +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import cot, csc +from sympy.functions.special.hyper import hyper +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.functions.special.zeta_functions import zeta +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import And, Not +from sympy.polys.partfrac import apart +from sympy.polys.polyerrors import PolynomialError, PolificationFailed +from sympy.polys.polytools import parallel_poly_from_expr, Poly, factor +from sympy.polys.rationaltools import together +from sympy.series.limitseq import limit_seq +from sympy.series.order import O +from sympy.series.residues import residue +from sympy.sets.contains import Contains +from sympy.sets.sets import FiniteSet, Interval +from sympy.utilities.iterables import sift +import itertools + + +class Sum(AddWithLimits, ExprWithIntLimits): + r""" + Represents unevaluated summation. + + Explanation + =========== + + ``Sum`` represents a finite or infinite series, with the first argument + being the general form of terms in the series, and the second argument + being ``(dummy_variable, start, end)``, with ``dummy_variable`` taking + all integer values from ``start`` through ``end``. In accordance with + long-standing mathematical convention, the end term is included in the + summation. + + Finite sums + =========== + + For finite sums (and sums with symbolic limits assumed to be finite) we + follow the summation convention described by Karr [1], especially + definition 3 of section 1.4. The sum: + + .. math:: + + \sum_{m \leq i < n} f(i) + + has *the obvious meaning* for `m < n`, namely: + + .. math:: + + \sum_{m \leq i < n} f(i) = f(m) + f(m+1) + \ldots + f(n-2) + f(n-1) + + with the upper limit value `f(n)` excluded. The sum over an empty set is + zero if and only if `m = n`: + + .. math:: + + \sum_{m \leq i < n} f(i) = 0 \quad \mathrm{for} \quad m = n + + Finally, for all other sums over empty sets we assume the following + definition: + + .. math:: + + \sum_{m \leq i < n} f(i) = - \sum_{n \leq i < m} f(i) \quad \mathrm{for} \quad m > n + + It is important to note that Karr defines all sums with the upper + limit being exclusive. This is in contrast to the usual mathematical notation, + but does not affect the summation convention. Indeed we have: + + .. math:: + + \sum_{m \leq i < n} f(i) = \sum_{i = m}^{n - 1} f(i) + + where the difference in notation is intentional to emphasize the meaning, + with limits typeset on the top being inclusive. + + Examples + ======== + + >>> from sympy.abc import i, k, m, n, x + >>> from sympy import Sum, factorial, oo, IndexedBase, Function + >>> Sum(k, (k, 1, m)) + Sum(k, (k, 1, m)) + >>> Sum(k, (k, 1, m)).doit() + m**2/2 + m/2 + >>> Sum(k**2, (k, 1, m)) + Sum(k**2, (k, 1, m)) + >>> Sum(k**2, (k, 1, m)).doit() + m**3/3 + m**2/2 + m/6 + >>> Sum(x**k, (k, 0, oo)) + Sum(x**k, (k, 0, oo)) + >>> Sum(x**k, (k, 0, oo)).doit() + Piecewise((1/(1 - x), Abs(x) < 1), (Sum(x**k, (k, 0, oo)), True)) + >>> Sum(x**k/factorial(k), (k, 0, oo)).doit() + exp(x) + + Here are examples to do summation with symbolic indices. You + can use either Function of IndexedBase classes: + + >>> f = Function('f') + >>> Sum(f(n), (n, 0, 3)).doit() + f(0) + f(1) + f(2) + f(3) + >>> Sum(f(n), (n, 0, oo)).doit() + Sum(f(n), (n, 0, oo)) + >>> f = IndexedBase('f') + >>> Sum(f[n]**2, (n, 0, 3)).doit() + f[0]**2 + f[1]**2 + f[2]**2 + f[3]**2 + + An example showing that the symbolic result of a summation is still + valid for seemingly nonsensical values of the limits. Then the Karr + convention allows us to give a perfectly valid interpretation to + those sums by interchanging the limits according to the above rules: + + >>> S = Sum(i, (i, 1, n)).doit() + >>> S + n**2/2 + n/2 + >>> S.subs(n, -4) + 6 + >>> Sum(i, (i, 1, -4)).doit() + 6 + >>> Sum(-i, (i, -3, 0)).doit() + 6 + + An explicit example of the Karr summation convention: + + >>> S1 = Sum(i**2, (i, m, m+n-1)).doit() + >>> S1 + m**2*n + m*n**2 - m*n + n**3/3 - n**2/2 + n/6 + >>> S2 = Sum(i**2, (i, m+n, m-1)).doit() + >>> S2 + -m**2*n - m*n**2 + m*n - n**3/3 + n**2/2 - n/6 + >>> S1 + S2 + 0 + >>> S3 = Sum(i, (i, m, m-1)).doit() + >>> S3 + 0 + + See Also + ======== + + summation + Product, sympy.concrete.products.product + + References + ========== + + .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM, + Volume 28 Issue 2, April 1981, Pages 305-350 + https://dl.acm.org/doi/10.1145/322248.322255 + .. [2] https://en.wikipedia.org/wiki/Summation#Capital-sigma_notation + .. [3] https://en.wikipedia.org/wiki/Empty_sum + """ + + __slots__ = () + + limits: tuple[tuple[Symbol, Expr, Expr]] + + def __new__(cls, function, *symbols, **assumptions): + obj = AddWithLimits.__new__(cls, function, *symbols, **assumptions) + if not hasattr(obj, 'limits'): + return obj + if any(len(l) != 3 or None in l for l in obj.limits): + raise ValueError('Sum requires values for lower and upper bounds.') + + return obj + + def _eval_is_zero(self): + # a Sum is only zero if its function is zero or if all terms + # cancel out. This only answers whether the summand is zero; if + # not then None is returned since we don't analyze whether all + # terms cancel out. + if self.function.is_zero or self.has_empty_sequence: + return True + + def _eval_is_extended_real(self): + if self.has_empty_sequence: + return True + return self.function.is_extended_real + + def _eval_is_positive(self): + if self.has_finite_limits and self.has_reversed_limits is False: + return self.function.is_positive + + def _eval_is_negative(self): + if self.has_finite_limits and self.has_reversed_limits is False: + return self.function.is_negative + + def _eval_is_finite(self): + if self.has_finite_limits and self.function.is_finite: + return True + + def doit(self, **hints): + if hints.get('deep', True): + f = self.function.doit(**hints) + else: + f = self.function + + # first make sure any definite limits have summation + # variables with matching assumptions + reps = {} + for xab in self.limits: + d = _dummy_with_inherited_properties_concrete(xab) + if d: + reps[xab[0]] = d + if reps: + undo = {v: k for k, v in reps.items()} + did = self.xreplace(reps).doit(**hints) + if isinstance(did, tuple): # when separate=True + did = tuple([i.xreplace(undo) for i in did]) + elif did is not None: + did = did.xreplace(undo) + else: + did = self + return did + + + if self.function.is_Matrix: + expanded = self.expand() + if self != expanded: + return expanded.doit() + return _eval_matrix_sum(self) + + for n, limit in enumerate(self.limits): + i, a, b = limit + dif = b - a + if dif == -1: + # Any summation over an empty set is zero + return S.Zero + if dif.is_integer and dif.is_negative: + a, b = b + 1, a - 1 + f = -f + + newf = eval_sum(f, (i, a, b)) + if newf is None: + if f == self.function: + zeta_function = self.eval_zeta_function(f, (i, a, b)) + if zeta_function is not None: + return zeta_function + return self + else: + return self.func(f, *self.limits[n:]) + f = newf + + if hints.get('deep', True): + # eval_sum could return partially unevaluated + # result with Piecewise. In this case we won't + # doit() recursively. + if not isinstance(f, Piecewise): + return f.doit(**hints) + + return f + + def eval_zeta_function(self, f, limits): + """ + Check whether the function matches with the zeta function. + + If it matches, then return a `Piecewise` expression because + zeta function does not converge unless `s > 1` and `q > 0` + """ + i, a, b = limits + if a.is_comparable and b.is_comparable and a > b: + return self.eval_zeta_function(f, (i, b + S.One, a - S.One)) + if b is not S.Infinity: + return + w, y, z = Wild('w', exclude=[i]), Wild('y', exclude=[i]), Wild('z', exclude=[i]) + if result := f.match((w * i + y) ** (-z)): + coeff = 1 / result[w] ** result[z] + s = result[z] + q = result[y] / result[w] + a + return Piecewise((coeff * zeta(s, q), + And(Not(Contains(-q, S.Naturals0)), re(s) > S.One)), + (self, True)) + + def _eval_derivative(self, x): + """ + Differentiate wrt x as long as x is not in the free symbols of any of + the upper or lower limits. + + Explanation + =========== + + Sum(a*b*x, (x, 1, a)) can be differentiated wrt x or b but not `a` + since the value of the sum is discontinuous in `a`. In a case + involving a limit variable, the unevaluated derivative is returned. + """ + + # diff already confirmed that x is in the free symbols of self, but we + # don't want to differentiate wrt any free symbol in the upper or lower + # limits + # XXX remove this test for free_symbols when the default _eval_derivative is in + if isinstance(x, Symbol) and x not in self.free_symbols: + return S.Zero + + # get limits and the function + f, limits = self.function, list(self.limits) + + limit = limits.pop(-1) + + if limits: # f is the argument to a Sum + f = self.func(f, *limits) + + _, a, b = limit + if x in a.free_symbols or x in b.free_symbols: + return None + df = Derivative(f, x, evaluate=True) + rv = self.func(df, limit) + return rv + + def _eval_difference_delta(self, n, step): + k, _, upper = self.args[-1] + new_upper = upper.subs(n, n + step) + + if len(self.args) == 2: + f = self.args[0] + else: + f = self.func(*self.args[:-1]) + + return Sum(f, (k, upper + 1, new_upper)).doit() + + def _eval_simplify(self, **kwargs): + + function = self.function + + if kwargs.get('deep', True): + function = function.simplify(**kwargs) + + # split the function into adds + terms = Add.make_args(expand(function)) + s_t = [] # Sum Terms + o_t = [] # Other Terms + + for term in terms: + if term.has(Sum): + # if there is an embedded sum here + # it is of the form x * (Sum(whatever)) + # hence we make a Mul out of it, and simplify all interior sum terms + subterms = Mul.make_args(expand(term)) + out_terms = [] + for subterm in subterms: + # go through each term + if isinstance(subterm, Sum): + # if it's a sum, simplify it + out_terms.append(subterm._eval_simplify(**kwargs)) + else: + # otherwise, add it as is + out_terms.append(subterm) + + # turn it back into a Mul + s_t.append(Mul(*out_terms)) + else: + o_t.append(term) + + # next try to combine any interior sums for further simplification + from sympy.simplify.simplify import factor_sum, sum_combine + result = Add(sum_combine(s_t), *o_t) + + return factor_sum(result, limits=self.limits) + + def is_convergent(self): + r""" + Checks for the convergence of a Sum. + + Explanation + =========== + + We divide the study of convergence of infinite sums and products in + two parts. + + First Part: + One part is the question whether all the terms are well defined, i.e., + they are finite in a sum and also non-zero in a product. Zero + is the analogy of (minus) infinity in products as + :math:`e^{-\infty} = 0`. + + Second Part: + The second part is the question of convergence after infinities, + and zeros in products, have been omitted assuming that their number + is finite. This means that we only consider the tail of the sum or + product, starting from some point after which all terms are well + defined. + + For example, in a sum of the form: + + .. math:: + + \sum_{1 \leq i < \infty} \frac{1}{n^2 + an + b} + + where a and b are numbers. The routine will return true, even if there + are infinities in the term sequence (at most two). An analogous + product would be: + + .. math:: + + \prod_{1 \leq i < \infty} e^{\frac{1}{n^2 + an + b}} + + This is how convergence is interpreted. It is concerned with what + happens at the limit. Finding the bad terms is another independent + matter. + + Note: It is responsibility of user to see that the sum or product + is well defined. + + There are various tests employed to check the convergence like + divergence test, root test, integral test, alternating series test, + comparison tests, Dirichlet tests. It returns true if Sum is convergent + and false if divergent and NotImplementedError if it cannot be checked. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Convergence_tests + + Examples + ======== + + >>> from sympy import factorial, S, Sum, Symbol, oo + >>> n = Symbol('n', integer=True) + >>> Sum(n/(n - 1), (n, 4, 7)).is_convergent() + True + >>> Sum(n/(2*n + 1), (n, 1, oo)).is_convergent() + False + >>> Sum(factorial(n)/5**n, (n, 1, oo)).is_convergent() + False + >>> Sum(1/n**(S(6)/5), (n, 1, oo)).is_convergent() + True + + See Also + ======== + + Sum.is_absolutely_convergent + sympy.concrete.products.Product.is_convergent + """ + p, q, r = symbols('p q r', cls=Wild) + + sym = self.limits[0][0] + lower_limit = self.limits[0][1] + upper_limit = self.limits[0][2] + sequence_term = self.function.simplify() + + if len(sequence_term.free_symbols) > 1: + raise NotImplementedError("convergence checking for more than one symbol " + "containing series is not handled") + + if lower_limit.is_finite and upper_limit.is_finite: + return S.true + + # transform sym -> -sym and swap the upper_limit = S.Infinity + # and lower_limit = - upper_limit + if lower_limit is S.NegativeInfinity: + if upper_limit is S.Infinity: + return Sum(sequence_term, (sym, 0, S.Infinity)).is_convergent() and \ + Sum(sequence_term, (sym, S.NegativeInfinity, 0)).is_convergent() + from sympy.simplify.simplify import simplify + sequence_term = simplify(sequence_term.xreplace({sym: -sym})) + lower_limit = -upper_limit + upper_limit = S.Infinity + + sym_ = Dummy(sym.name, integer=True, positive=True) + sequence_term = sequence_term.xreplace({sym: sym_}) + sym = sym_ + + interval = Interval(lower_limit, upper_limit) + + # Piecewise function handle + if sequence_term.is_Piecewise: + for func, cond in sequence_term.args: + # see if it represents something going to oo + if cond == True or cond.as_set().sup is S.Infinity: + s = Sum(func, (sym, lower_limit, upper_limit)) + return s.is_convergent() + return S.true + + ### -------- Divergence test ----------- ### + try: + lim_val = limit_seq(sequence_term, sym) + if lim_val is not None and lim_val.is_zero is False: + return S.false + except NotImplementedError: + pass + + try: + lim_val_abs = limit_seq(abs(sequence_term), sym) + if lim_val_abs is not None and lim_val_abs.is_zero is False: + return S.false + except NotImplementedError: + pass + + order = O(sequence_term, (sym, S.Infinity)) + + ### --------- p-series test (1/n**p) ---------- ### + p_series_test = order.expr.match(sym**p) + if p_series_test is not None: + if p_series_test[p] < -1: + return S.true + if p_series_test[p] >= -1: + return S.false + + ### ------------- comparison test ------------- ### + # 1/(n**p*log(n)**q*log(log(n))**r) comparison + n_log_test = (order.expr.match(1/(sym**p*log(1/sym)**q*log(-log(1/sym))**r)) or + order.expr.match(1/(sym**p*(-log(1/sym))**q*log(-log(1/sym))**r))) + if n_log_test is not None: + if (n_log_test[p] > 1 or + (n_log_test[p] == 1 and n_log_test[q] > 1) or + (n_log_test[p] == n_log_test[q] == 1 and n_log_test[r] > 1)): + return S.true + return S.false + + ### ------------- Limit comparison test -----------### + # (1/n) comparison + try: + lim_comp = limit_seq(sym*sequence_term, sym) + if lim_comp is not None and lim_comp.is_number and lim_comp > 0: + return S.false + except NotImplementedError: + pass + + ### ----------- ratio test ---------------- ### + next_sequence_term = sequence_term.xreplace({sym: sym + 1}) + from sympy.simplify.combsimp import combsimp + from sympy.simplify.powsimp import powsimp + ratio = combsimp(powsimp(next_sequence_term/sequence_term)) + try: + lim_ratio = limit_seq(ratio, sym) + if lim_ratio is not None and lim_ratio.is_number and lim_ratio is not S.NaN: + if abs(lim_ratio) > 1: + return S.false + if abs(lim_ratio) < 1: + return S.true + except NotImplementedError: + lim_ratio = None + + ### ---------- Raabe's test -------------- ### + if lim_ratio == 1: # ratio test inconclusive + test_val = sym*(sequence_term/ + sequence_term.subs(sym, sym + 1) - 1) + test_val = test_val.gammasimp() + try: + lim_val = limit_seq(test_val, sym) + if lim_val is not None and lim_val.is_number: + if lim_val > 1: + return S.true + if lim_val < 1: + return S.false + except NotImplementedError: + pass + + ### ----------- root test ---------------- ### + # lim = Limit(abs(sequence_term)**(1/sym), sym, S.Infinity) + try: + lim_evaluated = limit_seq(abs(sequence_term)**(1/sym), sym) + if lim_evaluated is not None and lim_evaluated.is_number: + if lim_evaluated < 1: + return S.true + if lim_evaluated > 1: + return S.false + except NotImplementedError: + pass + + ### ------------- alternating series test ----------- ### + dict_val = sequence_term.match(S.NegativeOne**(sym + p)*q) + if not dict_val[p].has(sym) and is_decreasing(dict_val[q], interval): + return S.true + + ### ------------- integral test -------------- ### + check_interval = None + from sympy.solvers.solveset import solveset + maxima = solveset(sequence_term.diff(sym), sym, interval) + if not maxima: + check_interval = interval + elif isinstance(maxima, FiniteSet) and maxima.sup.is_number: + check_interval = Interval(maxima.sup, interval.sup) + if (check_interval is not None and + (is_decreasing(sequence_term, check_interval) or + is_decreasing(-sequence_term, check_interval))): + integral_val = Integral( + sequence_term, (sym, lower_limit, upper_limit)) + try: + integral_val_evaluated = integral_val.doit() + if integral_val_evaluated.is_number: + return S(integral_val_evaluated.is_finite) + except NotImplementedError: + pass + + ### ----- Dirichlet and bounded times convergent tests ----- ### + # TODO + # + # Dirichlet_test + # https://en.wikipedia.org/wiki/Dirichlet%27s_test + # + # Bounded times convergent test + # It is based on comparison theorems for series. + # In particular, if the general term of a series can + # be written as a product of two terms a_n and b_n + # and if a_n is bounded and if Sum(b_n) is absolutely + # convergent, then the original series Sum(a_n * b_n) + # is absolutely convergent and so convergent. + # + # The following code can grows like 2**n where n is the + # number of args in order.expr + # Possibly combined with the potentially slow checks + # inside the loop, could make this test extremely slow + # for larger summation expressions. + + if order.expr.is_Mul: + args = order.expr.args + argset = set(args) + + ### -------------- Dirichlet tests -------------- ### + m = Dummy('m', integer=True) + def _dirichlet_test(g_n): + try: + ing_val = limit_seq(Sum(g_n, (sym, interval.inf, m)).doit(), m) + if ing_val is not None and ing_val.is_finite: + return S.true + except NotImplementedError: + pass + + ### -------- bounded times convergent test ---------### + def _bounded_convergent_test(g1_n, g2_n): + try: + lim_val = limit_seq(g1_n, sym) + if lim_val is not None and (lim_val.is_finite or ( + isinstance(lim_val, AccumulationBounds) + and (lim_val.max - lim_val.min).is_finite)): + if Sum(g2_n, (sym, lower_limit, upper_limit)).is_absolutely_convergent(): + return S.true + except NotImplementedError: + pass + + for n in range(1, len(argset)): + for a_tuple in itertools.combinations(args, n): + b_set = argset - set(a_tuple) + a_n = Mul(*a_tuple) + b_n = Mul(*b_set) + + if is_decreasing(a_n, interval): + dirich = _dirichlet_test(b_n) + if dirich is not None: + return dirich + + bc_test = _bounded_convergent_test(a_n, b_n) + if bc_test is not None: + return bc_test + + _sym = self.limits[0][0] + sequence_term = sequence_term.xreplace({sym: _sym}) + raise NotImplementedError("The algorithm to find the Sum convergence of %s " + "is not yet implemented" % (sequence_term)) + + def is_absolutely_convergent(self): + """ + Checks for the absolute convergence of an infinite series. + + Same as checking convergence of absolute value of sequence_term of + an infinite series. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Absolute_convergence + + Examples + ======== + + >>> from sympy import Sum, Symbol, oo + >>> n = Symbol('n', integer=True) + >>> Sum((-1)**n, (n, 1, oo)).is_absolutely_convergent() + False + >>> Sum((-1)**n/n**2, (n, 1, oo)).is_absolutely_convergent() + True + + See Also + ======== + + Sum.is_convergent + """ + return Sum(abs(self.function), self.limits).is_convergent() + + def euler_maclaurin(self, m=0, n=0, eps=0, eval_integral=True): + """ + Return an Euler-Maclaurin approximation of self, where m is the + number of leading terms to sum directly and n is the number of + terms in the tail. + + With m = n = 0, this is simply the corresponding integral + plus a first-order endpoint correction. + + Returns (s, e) where s is the Euler-Maclaurin approximation + and e is the estimated error (taken to be the magnitude of + the first omitted term in the tail): + + >>> from sympy.abc import k, a, b + >>> from sympy import Sum + >>> Sum(1/k, (k, 2, 5)).doit().evalf() + 1.28333333333333 + >>> s, e = Sum(1/k, (k, 2, 5)).euler_maclaurin() + >>> s + -log(2) + 7/20 + log(5) + >>> from sympy import sstr + >>> print(sstr((s.evalf(), e.evalf()), full_prec=True)) + (1.26629073187415, 0.0175000000000000) + + The endpoints may be symbolic: + + >>> s, e = Sum(1/k, (k, a, b)).euler_maclaurin() + >>> s + -log(a) + log(b) + 1/(2*b) + 1/(2*a) + >>> e + Abs(1/(12*b**2) - 1/(12*a**2)) + + If the function is a polynomial of degree at most 2n+1, the + Euler-Maclaurin formula becomes exact (and e = 0 is returned): + + >>> Sum(k, (k, 2, b)).euler_maclaurin() + (b**2/2 + b/2 - 1, 0) + >>> Sum(k, (k, 2, b)).doit() + b**2/2 + b/2 - 1 + + With a nonzero eps specified, the summation is ended + as soon as the remainder term is less than the epsilon. + """ + m = int(m) + n = int(n) + f = self.function + if len(self.limits) != 1: + raise ValueError("More than 1 limit") + i, a, b = self.limits[0] + if (a > b) == True: + if a - b == 1: + return S.Zero, S.Zero + a, b = b + 1, a - 1 + f = -f + s = S.Zero + if m: + if b.is_Integer and a.is_Integer: + m = min(m, b - a + 1) + if not eps or f.is_polynomial(i): + s = Add(*[f.subs(i, a + k) for k in range(m)]) + else: + term = f.subs(i, a) + if term: + test = abs(term.evalf(3)) < eps + if test == True: + return s, abs(term) + elif not (test == False): + # a symbolic Relational class, can't go further + return term, S.Zero + s = term + for k in range(1, m): + term = f.subs(i, a + k) + if abs(term.evalf(3)) < eps and term != 0: + return s, abs(term) + s += term + if b - a + 1 == m: + return s, S.Zero + a += m + x = Dummy('x') + I = Integral(f.subs(i, x), (x, a, b)) + if eval_integral: + I = I.doit() + s += I + + def fpoint(expr): + if b is S.Infinity: + return expr.subs(i, a), 0 + return expr.subs(i, a), expr.subs(i, b) + fa, fb = fpoint(f) + iterm = (fa + fb)/2 + g = f.diff(i) + for k in range(1, n + 2): + ga, gb = fpoint(g) + term = bernoulli(2*k)/factorial(2*k)*(gb - ga) + if k > n: + break + if eps and term: + term_evalf = term.evalf(3) + if term_evalf is S.NaN: + return S.NaN, S.NaN + if abs(term_evalf) < eps: + break + s += term + g = g.diff(i, 2, simplify=False) + return s + iterm, abs(term) + + + def reverse_order(self, *indices): + """ + Reverse the order of a limit in a Sum. + + Explanation + =========== + + ``reverse_order(self, *indices)`` reverses some limits in the expression + ``self`` which can be either a ``Sum`` or a ``Product``. The selectors in + the argument ``indices`` specify some indices whose limits get reversed. + These selectors are either variable names or numerical indices counted + starting from the inner-most limit tuple. + + Examples + ======== + + >>> from sympy import Sum + >>> from sympy.abc import x, y, a, b, c, d + + >>> Sum(x, (x, 0, 3)).reverse_order(x) + Sum(-x, (x, 4, -1)) + >>> Sum(x*y, (x, 1, 5), (y, 0, 6)).reverse_order(x, y) + Sum(x*y, (x, 6, 0), (y, 7, -1)) + >>> Sum(x, (x, a, b)).reverse_order(x) + Sum(-x, (x, b + 1, a - 1)) + >>> Sum(x, (x, a, b)).reverse_order(0) + Sum(-x, (x, b + 1, a - 1)) + + While one should prefer variable names when specifying which limits + to reverse, the index counting notation comes in handy in case there + are several symbols with the same name. + + >>> S = Sum(x**2, (x, a, b), (x, c, d)) + >>> S + Sum(x**2, (x, a, b), (x, c, d)) + >>> S0 = S.reverse_order(0) + >>> S0 + Sum(-x**2, (x, b + 1, a - 1), (x, c, d)) + >>> S1 = S0.reverse_order(1) + >>> S1 + Sum(x**2, (x, b + 1, a - 1), (x, d + 1, c - 1)) + + Of course we can mix both notations: + + >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1) + Sum(x*y, (x, b + 1, a - 1), (y, 6, 1)) + >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x) + Sum(x*y, (x, b + 1, a - 1), (y, 6, 1)) + + See Also + ======== + + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index, reorder_limit, + sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder + + References + ========== + + .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM, + Volume 28 Issue 2, April 1981, Pages 305-350 + https://dl.acm.org/doi/10.1145/322248.322255 + """ + l_indices = list(indices) + + for i, indx in enumerate(l_indices): + if not isinstance(indx, int): + l_indices[i] = self.index(indx) + + e = 1 + limits = [] + for i, limit in enumerate(self.limits): + l = limit + if i in l_indices: + e = -e + l = (limit[0], limit[2] + 1, limit[1] - 1) + limits.append(l) + + return Sum(e * self.function, *limits) + + def _eval_rewrite_as_Product(self, *args, **kwargs): + from sympy.concrete.products import Product + if self.function.is_extended_real: + return log(Product(exp(self.function), *self.limits)) + + +def summation(f, *symbols, **kwargs): + r""" + Compute the summation of f with respect to symbols. + + Explanation + =========== + + The notation for symbols is similar to the notation used in Integral. + summation(f, (i, a, b)) computes the sum of f with respect to i from a to b, + i.e., + + :: + + b + ____ + \ ` + summation(f, (i, a, b)) = ) f + /___, + i = a + + If it cannot compute the sum, it returns an unevaluated Sum object. + Repeated sums can be computed by introducing additional symbols tuples:: + + Examples + ======== + + >>> from sympy import summation, oo, symbols, log + >>> i, n, m = symbols('i n m', integer=True) + + >>> summation(2*i - 1, (i, 1, n)) + n**2 + >>> summation(1/2**i, (i, 0, oo)) + 2 + >>> summation(1/log(n)**n, (n, 2, oo)) + Sum(log(n)**(-n), (n, 2, oo)) + >>> summation(i, (i, 0, n), (n, 0, m)) + m**3/6 + m**2/2 + m/3 + + >>> from sympy.abc import x + >>> from sympy import factorial + >>> summation(x**n/factorial(n), (n, 0, oo)) + exp(x) + + See Also + ======== + + Sum + Product, sympy.concrete.products.product + + """ + return Sum(f, *symbols, **kwargs).doit(deep=False) + + +def telescopic_direct(L, R, n, limits): + """ + Returns the direct summation of the terms of a telescopic sum + + Explanation + =========== + + L is the term with lower index + R is the term with higher index + n difference between the indexes of L and R + + Examples + ======== + + >>> from sympy.concrete.summations import telescopic_direct + >>> from sympy.abc import k, a, b + >>> telescopic_direct(1/k, -1/(k+2), 2, (k, a, b)) + -1/(b + 2) - 1/(b + 1) + 1/(a + 1) + 1/a + + """ + (i, a, b) = limits + return Add(*[L.subs(i, a + m) + R.subs(i, b - m) for m in range(n)]) + + +def telescopic(L, R, limits): + ''' + Tries to perform the summation using the telescopic property. + + Return None if not possible. + ''' + (i, a, b) = limits + if L.is_Add or R.is_Add: + return None + + # We want to solve(L.subs(i, i + m) + R, m) + # First we try a simple match since this does things that + # solve doesn't do, e.g. solve(cos(k+m)-cos(k), m) gives + # a more complicated solution than m == 0. + + k = Wild("k") + sol = (-R).match(L.subs(i, i + k)) + s = None + if sol and k in sol: + s = sol[k] + if not (s.is_Integer and L.subs(i, i + s) + R == 0): + # invalid match or match didn't work + s = None + + # But there are things that match doesn't do that solve + # can do, e.g. determine that 1/(x + m) = 1/(1 - x) when m = 1 + + if s is None: + m = Dummy('m') + try: + from sympy.solvers.solvers import solve + sol = solve(L.subs(i, i + m) + R, m) or [] + except NotImplementedError: + return None + sol = [si for si in sol if si.is_Integer and + (L.subs(i, i + si) + R).expand().is_zero] + if len(sol) != 1: + return None + s = sol[0] + + if s < 0: + return telescopic_direct(R, L, abs(s), (i, a, b)) + elif s > 0: + return telescopic_direct(L, R, s, (i, a, b)) + + +def eval_sum(f, limits): + (i, a, b) = limits + if f.is_zero: + return S.Zero + if i not in f.free_symbols: + return f*(b - a + 1) + if a == b: + return f.subs(i, a) + if a.is_comparable and b.is_comparable and a > b: + return eval_sum(f, (i, b + S.One, a - S.One)) + if isinstance(f, Piecewise): + if not any(i in arg.args[1].free_symbols for arg in f.args): + # Piecewise conditions do not depend on the dummy summation variable, + # therefore we can fold: Sum(Piecewise((e, c), ...), limits) + # --> Piecewise((Sum(e, limits), c), ...) + newargs = [] + for arg in f.args: + newexpr = eval_sum(arg.expr, limits) + if newexpr is None: + return None + newargs.append((newexpr, arg.cond)) + return f.func(*newargs) + + if f.has(KroneckerDelta): + from .delta import deltasummation, _has_simple_delta + f = f.replace( + lambda x: isinstance(x, Sum), + lambda x: x.factor() + ) + if _has_simple_delta(f, limits[0]): + return deltasummation(f, limits) + + dif = b - a + definite = dif.is_Integer + # Doing it directly may be faster if there are very few terms. + if definite and (dif < 100): + return eval_sum_direct(f, (i, a, b)) + if isinstance(f, Piecewise): + return None + # Try to do it symbolically. Even when the number of terms is + # known, this can save time when b-a is big. + value = eval_sum_symbolic(f.expand(), (i, a, b)) + if value is not None: + return value + # Do it directly + if definite: + return eval_sum_direct(f, (i, a, b)) + + +def eval_sum_direct(expr, limits): + """ + Evaluate expression directly, but perform some simple checks first + to possibly result in a smaller expression and faster execution. + """ + (i, a, b) = limits + + dif = b - a + # Linearity + if expr.is_Mul: + # Try factor out everything not including i + without_i, with_i = expr.as_independent(i) + if without_i != 1: + s = eval_sum_direct(with_i, (i, a, b)) + if s: + r = without_i*s + if r is not S.NaN: + return r + else: + # Try term by term + L, R = expr.as_two_terms() + + if not L.has(i): + sR = eval_sum_direct(R, (i, a, b)) + if sR: + return L*sR + + if not R.has(i): + sL = eval_sum_direct(L, (i, a, b)) + if sL: + return sL*R + + # do this whether its an Add or Mul + # e.g. apart(1/(25*i**2 + 45*i + 14)) and + # apart(1/((5*i + 2)*(5*i + 7))) -> + # -1/(5*(5*i + 7)) + 1/(5*(5*i + 2)) + try: + expr = apart(expr, i) # see if it becomes an Add + except PolynomialError: + pass + + if expr.is_Add: + # Try factor out everything not including i + without_i, with_i = expr.as_independent(i) + if without_i != 0: + s = eval_sum_direct(with_i, (i, a, b)) + if s: + r = without_i*(dif + 1) + s + if r is not S.NaN: + return r + else: + # Try term by term + L, R = expr.as_two_terms() + lsum = eval_sum_direct(L, (i, a, b)) + rsum = eval_sum_direct(R, (i, a, b)) + + if None not in (lsum, rsum): + r = lsum + rsum + if r is not S.NaN: + return r + + return Add(*[expr.subs(i, a + j) for j in range(dif + 1)]) + + +def eval_sum_symbolic(f, limits): + f_orig = f + (i, a, b) = limits + if not f.has(i): + return f*(b - a + 1) + + # Linearity + if f.is_Mul: + # Try factor out everything not including i + without_i, with_i = f.as_independent(i) + if without_i != 1: + s = eval_sum_symbolic(with_i, (i, a, b)) + if s: + r = without_i*s + if r is not S.NaN: + return r + else: + # Try term by term + L, R = f.as_two_terms() + + if not L.has(i): + sR = eval_sum_symbolic(R, (i, a, b)) + if sR: + return L*sR + + if not R.has(i): + sL = eval_sum_symbolic(L, (i, a, b)) + if sL: + return sL*R + + # do this whether its an Add or Mul + # e.g. apart(1/(25*i**2 + 45*i + 14)) and + # apart(1/((5*i + 2)*(5*i + 7))) -> + # -1/(5*(5*i + 7)) + 1/(5*(5*i + 2)) + try: + f = apart(f, i) + except PolynomialError: + pass + + if f.is_Add: + L, R = f.as_two_terms() + lrsum = telescopic(L, R, (i, a, b)) + + if lrsum: + return lrsum + + # Try factor out everything not including i + without_i, with_i = f.as_independent(i) + if without_i != 0: + s = eval_sum_symbolic(with_i, (i, a, b)) + if s: + r = without_i*(b - a + 1) + s + if r is not S.NaN: + return r + else: + # Try term by term + lsum = eval_sum_symbolic(L, (i, a, b)) + rsum = eval_sum_symbolic(R, (i, a, b)) + + if None not in (lsum, rsum): + r = lsum + rsum + if r is not S.NaN: + return r + + + # Polynomial terms with Faulhaber's formula + n = Wild('n') + result = f.match(i**n) + + if result is not None: + n = result[n] + + if n.is_Integer: + if n >= 0: + if (b is S.Infinity and a is not S.NegativeInfinity) or \ + (a is S.NegativeInfinity and b is not S.Infinity): + return S.Infinity + return ((bernoulli(n + 1, b + 1) - bernoulli(n + 1, a))/(n + 1)).expand() + elif a.is_Integer and a >= 1: + if n == -1: + return harmonic(b) - harmonic(a - 1) + else: + return harmonic(b, abs(n)) - harmonic(a - 1, abs(n)) + + if not (a.has(S.Infinity, S.NegativeInfinity) or + b.has(S.Infinity, S.NegativeInfinity)): + # Geometric terms + c1 = Wild('c1', exclude=[i]) + c2 = Wild('c2', exclude=[i]) + c3 = Wild('c3', exclude=[i]) + wexp = Wild('wexp') + + # Here we first attempt powsimp on f for easier matching with the + # exponential pattern, and attempt expansion on the exponent for easier + # matching with the linear pattern. + e = f.powsimp().match(c1 ** wexp) + if e is not None: + e_exp = e.pop(wexp).expand().match(c2*i + c3) + if e_exp is not None: + e.update(e_exp) + + p = (c1**c3).subs(e) + q = (c1**c2).subs(e) + r = p*(q**a - q**(b + 1))/(1 - q) + l = p*(b - a + 1) + return Piecewise((l, Eq(q, S.One)), (r, True)) + + r = gosper_sum(f, (i, a, b)) + + if isinstance(r, (Mul,Add)): + from sympy.simplify.radsimp import denom + from sympy.solvers.solvers import solve + non_limit = r.free_symbols - Tuple(*limits[1:]).free_symbols + den = denom(together(r)) + den_sym = non_limit & den.free_symbols + args = [] + for v in ordered(den_sym): + try: + s = solve(den, v) + m = Eq(v, s[0]) if s else S.false + if m != False: + args.append((Sum(f_orig.subs(*m.args), limits).doit(), m)) + break + except NotImplementedError: + continue + + args.append((r, True)) + return Piecewise(*args) + + if r not in (None, S.NaN): + return r + + h = eval_sum_hyper(f_orig, (i, a, b)) + if h is not None: + return h + + r = eval_sum_residue(f_orig, (i, a, b)) + if r is not None: + return r + + factored = f_orig.factor() + if factored != f_orig: + return eval_sum_symbolic(factored, (i, a, b)) + + +def _eval_sum_hyper(f, i, a): + """ Returns (res, cond). Sums from a to oo. """ + if a != 0: + return _eval_sum_hyper(f.subs(i, i + a), i, 0) + + if f.subs(i, 0) == 0: + from sympy.simplify.simplify import simplify + if simplify(f.subs(i, Dummy('i', integer=True, positive=True))) == 0: + return S.Zero, True + return _eval_sum_hyper(f.subs(i, i + 1), i, 0) + + from sympy.simplify.simplify import hypersimp + hs = hypersimp(f, i) + if hs is None: + return None + + if isinstance(hs, Float): + from sympy.simplify.simplify import nsimplify + hs = nsimplify(hs) + + from sympy.simplify.combsimp import combsimp + from sympy.simplify.hyperexpand import hyperexpand + from sympy.simplify.radsimp import fraction + numer, denom = fraction(factor(hs)) + top, topl = numer.as_coeff_mul(i) + bot, botl = denom.as_coeff_mul(i) + ab = [top, bot] + factors = [topl, botl] + params = [[], []] + for k in range(2): + for fac in factors[k]: + mul = 1 + if fac.is_Pow: + mul = fac.exp + fac = fac.base + if not mul.is_Integer: + return None + p = Poly(fac, i) + if p.degree() != 1: + return None + m, n = p.all_coeffs() + ab[k] *= m**mul + params[k] += [n/m]*mul + + # Add "1" to numerator parameters, to account for implicit n! in + # hypergeometric series. + ap = params[0] + [1] + bq = params[1] + x = ab[0]/ab[1] + h = hyper(ap, bq, x) + f = combsimp(f) + return f.subs(i, 0)*hyperexpand(h), h.convergence_statement + + +def eval_sum_hyper(f, i_a_b): + i, a, b = i_a_b + + if f.is_hypergeometric(i) is False: + return + + if (b - a).is_Integer: + # We are never going to do better than doing the sum in the obvious way + return None + + old_sum = Sum(f, (i, a, b)) + + if b != S.Infinity: + if a is S.NegativeInfinity: + res = _eval_sum_hyper(f.subs(i, -i), i, -b) + if res is not None: + return Piecewise(res, (old_sum, True)) + else: + n_illegal = lambda x: sum(x.count(_) for _ in _illegal) + had = n_illegal(f) + # check that no extra illegals are introduced + res1 = _eval_sum_hyper(f, i, a) + if res1 is None or n_illegal(res1) > had: + return + res2 = _eval_sum_hyper(f, i, b + 1) + if res2 is None or n_illegal(res2) > had: + return + (res1, cond1), (res2, cond2) = res1, res2 + cond = And(cond1, cond2) + if cond == False: + return None + return Piecewise((res1 - res2, cond), (old_sum, True)) + + if a is S.NegativeInfinity: + res1 = _eval_sum_hyper(f.subs(i, -i), i, 1) + res2 = _eval_sum_hyper(f, i, 0) + if res1 is None or res2 is None: + return None + res1, cond1 = res1 + res2, cond2 = res2 + cond = And(cond1, cond2) + if cond == False or cond.as_set() == S.EmptySet: + return None + return Piecewise((res1 + res2, cond), (old_sum, True)) + + # Now b == oo, a != -oo + res = _eval_sum_hyper(f, i, a) + if res is not None: + r, c = res + if c == False: + if r.is_number: + f = f.subs(i, Dummy('i', integer=True, positive=True) + a) + if f.is_positive or f.is_zero: + return S.Infinity + elif f.is_negative: + return S.NegativeInfinity + return None + return Piecewise(res, (old_sum, True)) + + +def eval_sum_residue(f, i_a_b): + r"""Compute the infinite summation with residues + + Notes + ===== + + If $f(n), g(n)$ are polynomials with $\deg(g(n)) - \deg(f(n)) \ge 2$, + some infinite summations can be computed by the following residue + evaluations. + + .. math:: + \sum_{n=-\infty, g(n) \ne 0}^{\infty} \frac{f(n)}{g(n)} = + -\pi \sum_{\alpha|g(\alpha)=0} + \text{Res}(\cot(\pi x) \frac{f(x)}{g(x)}, \alpha) + + .. math:: + \sum_{n=-\infty, g(n) \ne 0}^{\infty} (-1)^n \frac{f(n)}{g(n)} = + -\pi \sum_{\alpha|g(\alpha)=0} + \text{Res}(\csc(\pi x) \frac{f(x)}{g(x)}, \alpha) + + Examples + ======== + + >>> from sympy import Sum, oo, Symbol + >>> x = Symbol('x') + + Doubly infinite series of rational functions. + + >>> Sum(1 / (x**2 + 1), (x, -oo, oo)).doit() + pi/tanh(pi) + + Doubly infinite alternating series of rational functions. + + >>> Sum((-1)**x / (x**2 + 1), (x, -oo, oo)).doit() + pi/sinh(pi) + + Infinite series of even rational functions. + + >>> Sum(1 / (x**2 + 1), (x, 0, oo)).doit() + 1/2 + pi/(2*tanh(pi)) + + Infinite series of alternating even rational functions. + + >>> Sum((-1)**x / (x**2 + 1), (x, 0, oo)).doit() + pi/(2*sinh(pi)) + 1/2 + + This also have heuristics to transform arbitrarily shifted summand or + arbitrarily shifted summation range to the canonical problem the + formula can handle. + + >>> Sum(1 / (x**2 + 2*x + 2), (x, -1, oo)).doit() + 1/2 + pi/(2*tanh(pi)) + >>> Sum(1 / (x**2 + 4*x + 5), (x, -2, oo)).doit() + 1/2 + pi/(2*tanh(pi)) + >>> Sum(1 / (x**2 + 1), (x, 1, oo)).doit() + -1/2 + pi/(2*tanh(pi)) + >>> Sum(1 / (x**2 + 1), (x, 2, oo)).doit() + -1 + pi/(2*tanh(pi)) + + References + ========== + + .. [#] http://www.supermath.info/InfiniteSeriesandtheResidueTheorem.pdf + + .. [#] Asmar N.H., Grafakos L. (2018) Residue Theory. + In: Complex Analysis with Applications. + Undergraduate Texts in Mathematics. Springer, Cham. + https://doi.org/10.1007/978-3-319-94063-2_5 + """ + i, a, b = i_a_b + + # If lower limit > upper limit: Karr Summation Convention + if a.is_comparable and b.is_comparable and a > b: + return eval_sum_residue(f, (i, b + S.One, a - S.One)) + + def is_even_function(numer, denom): + """Test if the rational function is an even function""" + numer_even = all(i % 2 == 0 for (i,) in numer.monoms()) + denom_even = all(i % 2 == 0 for (i,) in denom.monoms()) + numer_odd = all(i % 2 == 1 for (i,) in numer.monoms()) + denom_odd = all(i % 2 == 1 for (i,) in denom.monoms()) + return (numer_even and denom_even) or (numer_odd and denom_odd) + + def match_rational(f, i): + numer, denom = f.as_numer_denom() + try: + (numer, denom), opt = parallel_poly_from_expr((numer, denom), i) + except (PolificationFailed, PolynomialError): + return None + return numer, denom + + def get_poles(denom): + roots = denom.sqf_part().all_roots() + roots = sift(roots, lambda x: x.is_integer) + if None in roots: + return None + int_roots, nonint_roots = roots[True], roots[False] + return int_roots, nonint_roots + + def get_shift(denom): + n = denom.degree(i) + a = denom.coeff_monomial(i**n) + b = denom.coeff_monomial(i**(n-1)) + shift = - b / a / n + return shift + + #Need a dummy symbol with no assumptions set for get_residue_factor + z = Dummy('z') + + def get_residue_factor(numer, denom, alternating): + residue_factor = (numer.as_expr() / denom.as_expr()).subs(i, z) + if not alternating: + residue_factor *= cot(S.Pi * z) + else: + residue_factor *= csc(S.Pi * z) + return residue_factor + + # We don't know how to deal with symbolic constants in summand + if f.free_symbols - {i}: + return None + + if not (a.is_Integer or a in (S.Infinity, S.NegativeInfinity)): + return None + if not (b.is_Integer or b in (S.Infinity, S.NegativeInfinity)): + return None + + # Quick exit heuristic for the sums which doesn't have infinite range + if a != S.NegativeInfinity and b != S.Infinity: + return None + + match = match_rational(f, i) + if match: + alternating = False + numer, denom = match + else: + match = match_rational(f / S.NegativeOne**i, i) + if match: + alternating = True + numer, denom = match + else: + return None + + if denom.degree(i) - numer.degree(i) < 2: + return None + + if (a, b) == (S.NegativeInfinity, S.Infinity): + poles = get_poles(denom) + if poles is None: + return None + int_roots, nonint_roots = poles + + if int_roots: + return None + + residue_factor = get_residue_factor(numer, denom, alternating) + residues = [residue(residue_factor, z, root) for root in nonint_roots] + return -S.Pi * sum(residues) + + if not (a.is_finite and b is S.Infinity): + return None + + if not is_even_function(numer, denom): + # Try shifting summation and check if the summand can be made + # and even function from the origin. + # Sum(f(n), (n, a, b)) => Sum(f(n + s), (n, a - s, b - s)) + shift = get_shift(denom) + + if not shift.is_Integer: + return None + if shift == 0: + return None + + numer = numer.shift(shift) + denom = denom.shift(shift) + + if not is_even_function(numer, denom): + return None + + if alternating: + f = S.NegativeOne**i * (S.NegativeOne**shift * numer.as_expr() / denom.as_expr()) + else: + f = numer.as_expr() / denom.as_expr() + return eval_sum_residue(f, (i, a-shift, b-shift)) + + poles = get_poles(denom) + if poles is None: + return None + int_roots, nonint_roots = poles + + if int_roots: + int_roots = [int(root) for root in int_roots] + int_roots_max = max(int_roots) + int_roots_min = min(int_roots) + # Integer valued poles must be next to each other + # and also symmetric from origin (Because the function is even) + if not len(int_roots) == int_roots_max - int_roots_min + 1: + return None + + # Check whether the summation indices contain poles + if a <= max(int_roots): + return None + + residue_factor = get_residue_factor(numer, denom, alternating) + residues = [residue(residue_factor, z, root) for root in int_roots + nonint_roots] + full_sum = -S.Pi * sum(residues) + + if not int_roots: + # Compute Sum(f, (i, 0, oo)) by adding a extraneous evaluation + # at the origin. + half_sum = (full_sum + f.xreplace({i: 0})) / 2 + + # Add and subtract extraneous evaluations + extraneous_neg = [f.xreplace({i: i0}) for i0 in range(int(a), 0)] + extraneous_pos = [f.xreplace({i: i0}) for i0 in range(0, int(a))] + result = half_sum + sum(extraneous_neg) - sum(extraneous_pos) + + return result + + # Compute Sum(f, (i, min(poles) + 1, oo)) + half_sum = full_sum / 2 + + # Subtract extraneous evaluations + extraneous = [f.xreplace({i: i0}) for i0 in range(max(int_roots) + 1, int(a))] + result = half_sum - sum(extraneous) + + return result + + +def _eval_matrix_sum(expression): + f = expression.function + for limit in expression.limits: + i, a, b = limit + dif = b - a + if dif.is_Integer: + if (dif < 0) == True: + a, b = b + 1, a - 1 + f = -f + + newf = eval_sum_direct(f, (i, a, b)) + if newf is not None: + return newf.doit() + + +def _dummy_with_inherited_properties_concrete(limits): + """ + Return a Dummy symbol that inherits as many assumptions as possible + from the provided symbol and limits. + + If the symbol already has all True assumption shared by the limits + then return None. + """ + x, a, b = limits + l = [a, b] + + assumptions_to_consider = ['extended_nonnegative', 'nonnegative', + 'extended_nonpositive', 'nonpositive', + 'extended_positive', 'positive', + 'extended_negative', 'negative', + 'integer', 'rational', 'finite', + 'zero', 'real', 'extended_real'] + + assumptions_to_keep = {} + assumptions_to_add = {} + for assum in assumptions_to_consider: + assum_true = x._assumptions.get(assum, None) + if assum_true: + assumptions_to_keep[assum] = True + elif all(getattr(i, 'is_' + assum) for i in l): + assumptions_to_add[assum] = True + if assumptions_to_add: + assumptions_to_keep.update(assumptions_to_add) + return Dummy('d', **assumptions_to_keep) diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__init__.py b/phivenv/Lib/site-packages/sympy/concrete/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fdb3cf7e10b8659df988d0801ce41bc849e7d28 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_delta.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_delta.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13a935e69c865dd9b4b8ba258f0f7d2ba6c20798 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_delta.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_gosper.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_gosper.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..044036b9f2b02652290f37f95a3b58ab0cec42f4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_gosper.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_guess.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_guess.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad886a5ca663a8f0e0ef7ea6ddb9d374b08bd9a7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_guess.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_products.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_products.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5b5a664450ba3d5d3592f72777a362f8a00993b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_products.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_sums_products.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_sums_products.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5326970d6f34eb5bd5b2422ab07be0b1dca5daea Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/concrete/tests/__pycache__/test_sums_products.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/test_delta.py b/phivenv/Lib/site-packages/sympy/concrete/tests/test_delta.py new file mode 100644 index 0000000000000000000000000000000000000000..9dc6e88d16346acc7dc775446d7de3f3696d0e03 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/tests/test_delta.py @@ -0,0 +1,499 @@ +from sympy.concrete import Sum +from sympy.concrete.delta import deltaproduct as dp, deltasummation as ds, _extract_delta +from sympy.core import Eq, S, symbols, oo +from sympy.functions import KroneckerDelta as KD, Piecewise, piecewise_fold +from sympy.logic import And +from sympy.testing.pytest import raises + +i, j, k, l, m = symbols("i j k l m", integer=True, finite=True) +x, y = symbols("x y", commutative=False) + + +def test_deltaproduct_trivial(): + assert dp(x, (j, 1, 0)) == 1 + assert dp(x, (j, 1, 3)) == x**3 + assert dp(x + y, (j, 1, 3)) == (x + y)**3 + assert dp(x*y, (j, 1, 3)) == (x*y)**3 + assert dp(KD(i, j), (k, 1, 3)) == KD(i, j) + assert dp(x*KD(i, j), (k, 1, 3)) == x**3*KD(i, j) + assert dp(x*y*KD(i, j), (k, 1, 3)) == (x*y)**3*KD(i, j) + + +def test_deltaproduct_basic(): + assert dp(KD(i, j), (j, 1, 3)) == 0 + assert dp(KD(i, j), (j, 1, 1)) == KD(i, 1) + assert dp(KD(i, j), (j, 2, 2)) == KD(i, 2) + assert dp(KD(i, j), (j, 3, 3)) == KD(i, 3) + assert dp(KD(i, j), (j, 1, k)) == KD(i, 1)*KD(k, 1) + KD(k, 0) + assert dp(KD(i, j), (j, k, 3)) == KD(i, 3)*KD(k, 3) + KD(k, 4) + assert dp(KD(i, j), (j, k, l)) == KD(i, l)*KD(k, l) + KD(k, l + 1) + + +def test_deltaproduct_mul_x_kd(): + assert dp(x*KD(i, j), (j, 1, 3)) == 0 + assert dp(x*KD(i, j), (j, 1, 1)) == x*KD(i, 1) + assert dp(x*KD(i, j), (j, 2, 2)) == x*KD(i, 2) + assert dp(x*KD(i, j), (j, 3, 3)) == x*KD(i, 3) + assert dp(x*KD(i, j), (j, 1, k)) == x*KD(i, 1)*KD(k, 1) + KD(k, 0) + assert dp(x*KD(i, j), (j, k, 3)) == x*KD(i, 3)*KD(k, 3) + KD(k, 4) + assert dp(x*KD(i, j), (j, k, l)) == x*KD(i, l)*KD(k, l) + KD(k, l + 1) + + +def test_deltaproduct_mul_add_x_y_kd(): + assert dp((x + y)*KD(i, j), (j, 1, 3)) == 0 + assert dp((x + y)*KD(i, j), (j, 1, 1)) == (x + y)*KD(i, 1) + assert dp((x + y)*KD(i, j), (j, 2, 2)) == (x + y)*KD(i, 2) + assert dp((x + y)*KD(i, j), (j, 3, 3)) == (x + y)*KD(i, 3) + assert dp((x + y)*KD(i, j), (j, 1, k)) == \ + (x + y)*KD(i, 1)*KD(k, 1) + KD(k, 0) + assert dp((x + y)*KD(i, j), (j, k, 3)) == \ + (x + y)*KD(i, 3)*KD(k, 3) + KD(k, 4) + assert dp((x + y)*KD(i, j), (j, k, l)) == \ + (x + y)*KD(i, l)*KD(k, l) + KD(k, l + 1) + + +def test_deltaproduct_add_kd_kd(): + assert dp(KD(i, k) + KD(j, k), (k, 1, 3)) == 0 + assert dp(KD(i, k) + KD(j, k), (k, 1, 1)) == KD(i, 1) + KD(j, 1) + assert dp(KD(i, k) + KD(j, k), (k, 2, 2)) == KD(i, 2) + KD(j, 2) + assert dp(KD(i, k) + KD(j, k), (k, 3, 3)) == KD(i, 3) + KD(j, 3) + assert dp(KD(i, k) + KD(j, k), (k, 1, l)) == KD(l, 0) + \ + KD(i, 1)*KD(l, 1) + KD(j, 1)*KD(l, 1) + \ + KD(i, 1)*KD(j, 2)*KD(l, 2) + KD(j, 1)*KD(i, 2)*KD(l, 2) + assert dp(KD(i, k) + KD(j, k), (k, l, 3)) == KD(l, 4) + \ + KD(i, 3)*KD(l, 3) + KD(j, 3)*KD(l, 3) + \ + KD(i, 2)*KD(j, 3)*KD(l, 2) + KD(i, 3)*KD(j, 2)*KD(l, 2) + assert dp(KD(i, k) + KD(j, k), (k, l, m)) == KD(l, m + 1) + \ + KD(i, m)*KD(l, m) + KD(j, m)*KD(l, m) + \ + KD(i, m)*KD(j, m - 1)*KD(l, m - 1) + KD(i, m - 1)*KD(j, m)*KD(l, m - 1) + + +def test_deltaproduct_mul_x_add_kd_kd(): + assert dp(x*(KD(i, k) + KD(j, k)), (k, 1, 3)) == 0 + assert dp(x*(KD(i, k) + KD(j, k)), (k, 1, 1)) == x*(KD(i, 1) + KD(j, 1)) + assert dp(x*(KD(i, k) + KD(j, k)), (k, 2, 2)) == x*(KD(i, 2) + KD(j, 2)) + assert dp(x*(KD(i, k) + KD(j, k)), (k, 3, 3)) == x*(KD(i, 3) + KD(j, 3)) + assert dp(x*(KD(i, k) + KD(j, k)), (k, 1, l)) == KD(l, 0) + \ + x*KD(i, 1)*KD(l, 1) + x*KD(j, 1)*KD(l, 1) + \ + x**2*KD(i, 1)*KD(j, 2)*KD(l, 2) + x**2*KD(j, 1)*KD(i, 2)*KD(l, 2) + assert dp(x*(KD(i, k) + KD(j, k)), (k, l, 3)) == KD(l, 4) + \ + x*KD(i, 3)*KD(l, 3) + x*KD(j, 3)*KD(l, 3) + \ + x**2*KD(i, 2)*KD(j, 3)*KD(l, 2) + x**2*KD(i, 3)*KD(j, 2)*KD(l, 2) + assert dp(x*(KD(i, k) + KD(j, k)), (k, l, m)) == KD(l, m + 1) + \ + x*KD(i, m)*KD(l, m) + x*KD(j, m)*KD(l, m) + \ + x**2*KD(i, m - 1)*KD(j, m)*KD(l, m - 1) + \ + x**2*KD(i, m)*KD(j, m - 1)*KD(l, m - 1) + + +def test_deltaproduct_mul_add_x_y_add_kd_kd(): + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 3)) == 0 + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 1)) == \ + (x + y)*(KD(i, 1) + KD(j, 1)) + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 2, 2)) == \ + (x + y)*(KD(i, 2) + KD(j, 2)) + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 3, 3)) == \ + (x + y)*(KD(i, 3) + KD(j, 3)) + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 1, l)) == KD(l, 0) + \ + (x + y)*KD(i, 1)*KD(l, 1) + (x + y)*KD(j, 1)*KD(l, 1) + \ + (x + y)**2*KD(i, 1)*KD(j, 2)*KD(l, 2) + \ + (x + y)**2*KD(j, 1)*KD(i, 2)*KD(l, 2) + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, l, 3)) == KD(l, 4) + \ + (x + y)*KD(i, 3)*KD(l, 3) + (x + y)*KD(j, 3)*KD(l, 3) + \ + (x + y)**2*KD(i, 2)*KD(j, 3)*KD(l, 2) + \ + (x + y)**2*KD(i, 3)*KD(j, 2)*KD(l, 2) + assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, l, m)) == KD(l, m + 1) + \ + (x + y)*KD(i, m)*KD(l, m) + (x + y)*KD(j, m)*KD(l, m) + \ + (x + y)**2*KD(i, m - 1)*KD(j, m)*KD(l, m - 1) + \ + (x + y)**2*KD(i, m)*KD(j, m - 1)*KD(l, m - 1) + + +def test_deltaproduct_add_mul_x_y_mul_x_kd(): + assert dp(x*y + x*KD(i, j), (j, 1, 3)) == (x*y)**3 + \ + x*(x*y)**2*KD(i, 1) + (x*y)*x*(x*y)*KD(i, 2) + (x*y)**2*x*KD(i, 3) + assert dp(x*y + x*KD(i, j), (j, 1, 1)) == x*y + x*KD(i, 1) + assert dp(x*y + x*KD(i, j), (j, 2, 2)) == x*y + x*KD(i, 2) + assert dp(x*y + x*KD(i, j), (j, 3, 3)) == x*y + x*KD(i, 3) + assert dp(x*y + x*KD(i, j), (j, 1, k)) == \ + (x*y)**k + Piecewise( + ((x*y)**(i - 1)*x*(x*y)**(k - i), And(1 <= i, i <= k)), + (0, True) + ) + assert dp(x*y + x*KD(i, j), (j, k, 3)) == \ + (x*y)**(-k + 4) + Piecewise( + ((x*y)**(i - k)*x*(x*y)**(3 - i), And(k <= i, i <= 3)), + (0, True) + ) + assert dp(x*y + x*KD(i, j), (j, k, l)) == \ + (x*y)**(-k + l + 1) + Piecewise( + ((x*y)**(i - k)*x*(x*y)**(l - i), And(k <= i, i <= l)), + (0, True) + ) + + +def test_deltaproduct_mul_x_add_y_kd(): + assert dp(x*(y + KD(i, j)), (j, 1, 3)) == (x*y)**3 + \ + x*(x*y)**2*KD(i, 1) + (x*y)*x*(x*y)*KD(i, 2) + (x*y)**2*x*KD(i, 3) + assert dp(x*(y + KD(i, j)), (j, 1, 1)) == x*(y + KD(i, 1)) + assert dp(x*(y + KD(i, j)), (j, 2, 2)) == x*(y + KD(i, 2)) + assert dp(x*(y + KD(i, j)), (j, 3, 3)) == x*(y + KD(i, 3)) + assert dp(x*(y + KD(i, j)), (j, 1, k)) == \ + (x*y)**k + Piecewise( + ((x*y)**(i - 1)*x*(x*y)**(k - i), And(1 <= i, i <= k)), + (0, True) + ).expand() + assert dp(x*(y + KD(i, j)), (j, k, 3)) == \ + ((x*y)**(-k + 4) + Piecewise( + ((x*y)**(i - k)*x*(x*y)**(3 - i), And(k <= i, i <= 3)), + (0, True) + )).expand() + assert dp(x*(y + KD(i, j)), (j, k, l)) == \ + ((x*y)**(-k + l + 1) + Piecewise( + ((x*y)**(i - k)*x*(x*y)**(l - i), And(k <= i, i <= l)), + (0, True) + )).expand() + + +def test_deltaproduct_mul_x_add_y_twokd(): + assert dp(x*(y + 2*KD(i, j)), (j, 1, 3)) == (x*y)**3 + \ + 2*x*(x*y)**2*KD(i, 1) + 2*x*y*x*x*y*KD(i, 2) + 2*(x*y)**2*x*KD(i, 3) + assert dp(x*(y + 2*KD(i, j)), (j, 1, 1)) == x*(y + 2*KD(i, 1)) + assert dp(x*(y + 2*KD(i, j)), (j, 2, 2)) == x*(y + 2*KD(i, 2)) + assert dp(x*(y + 2*KD(i, j)), (j, 3, 3)) == x*(y + 2*KD(i, 3)) + assert dp(x*(y + 2*KD(i, j)), (j, 1, k)) == \ + (x*y)**k + Piecewise( + (2*(x*y)**(i - 1)*x*(x*y)**(k - i), And(1 <= i, i <= k)), + (0, True) + ).expand() + assert dp(x*(y + 2*KD(i, j)), (j, k, 3)) == \ + ((x*y)**(-k + 4) + Piecewise( + (2*(x*y)**(i - k)*x*(x*y)**(3 - i), And(k <= i, i <= 3)), + (0, True) + )).expand() + assert dp(x*(y + 2*KD(i, j)), (j, k, l)) == \ + ((x*y)**(-k + l + 1) + Piecewise( + (2*(x*y)**(i - k)*x*(x*y)**(l - i), And(k <= i, i <= l)), + (0, True) + )).expand() + + +def test_deltaproduct_mul_add_x_y_add_y_kd(): + assert dp((x + y)*(y + KD(i, j)), (j, 1, 3)) == ((x + y)*y)**3 + \ + (x + y)*((x + y)*y)**2*KD(i, 1) + \ + (x + y)*y*(x + y)**2*y*KD(i, 2) + \ + ((x + y)*y)**2*(x + y)*KD(i, 3) + assert dp((x + y)*(y + KD(i, j)), (j, 1, 1)) == (x + y)*(y + KD(i, 1)) + assert dp((x + y)*(y + KD(i, j)), (j, 2, 2)) == (x + y)*(y + KD(i, 2)) + assert dp((x + y)*(y + KD(i, j)), (j, 3, 3)) == (x + y)*(y + KD(i, 3)) + assert dp((x + y)*(y + KD(i, j)), (j, 1, k)) == \ + ((x + y)*y)**k + Piecewise( + (((x + y)*y)**(-1)*((x + y)*y)**i*(x + y)*((x + y)*y + )**k*((x + y)*y)**(-i), (i >= 1) & (i <= k)), (0, True)) + assert dp((x + y)*(y + KD(i, j)), (j, k, 3)) == ( + (x + y)*y)**4*((x + y)*y)**(-k) + Piecewise((((x + y)*y)**i*( + (x + y)*y)**(-k)*(x + y)*((x + y)*y)**3*((x + y)*y)**(-i), + (i >= k) & (i <= 3)), (0, True)) + assert dp((x + y)*(y + KD(i, j)), (j, k, l)) == \ + (x + y)*y*((x + y)*y)**l*((x + y)*y)**(-k) + Piecewise( + (((x + y)*y)**i*((x + y)*y)**(-k)*(x + y)*((x + y)*y + )**l*((x + y)*y)**(-i), (i >= k) & (i <= l)), (0, True)) + + +def test_deltaproduct_mul_add_x_kd_add_y_kd(): + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 1, 3)) == \ + KD(i, 1)*(KD(i, k) + x)*((KD(i, k) + x)*y)**2 + \ + KD(i, 2)*(KD(i, k) + x)*y*(KD(i, k) + x)**2*y + \ + KD(i, 3)*((KD(i, k) + x)*y)**2*(KD(i, k) + x) + \ + ((KD(i, k) + x)*y)**3 + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 1, 1)) == \ + (x + KD(i, k))*(y + KD(i, 1)) + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 2, 2)) == \ + (x + KD(i, k))*(y + KD(i, 2)) + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 3, 3)) == \ + (x + KD(i, k))*(y + KD(i, 3)) + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 1, k)) == \ + ((KD(i, k) + x)*y)**k + Piecewise( + (((KD(i, k) + x)*y)**(-1)*((KD(i, k) + x)*y)**i*(KD(i, k) + x + )*((KD(i, k) + x)*y)**k*((KD(i, k) + x)*y)**(-i), (i >= 1 + ) & (i <= k)), (0, True)) + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, k, 3)) == ( + (KD(i, k) + x)*y)**4*((KD(i, k) + x)*y)**(-k) + Piecewise( + (((KD(i, k) + x)*y)**i*((KD(i, k) + x)*y)**(-k)*(KD(i, k) + + x)*((KD(i, k) + x)*y)**3*((KD(i, k) + x)*y)**(-i), + (i >= k) & (i <= 3)), (0, True)) + assert dp((x + KD(i, k))*(y + KD(i, j)), (j, k, l)) == ( + KD(i, k) + x)*y*((KD(i, k) + x)*y)**l*((KD(i, k) + x)*y + )**(-k) + Piecewise((((KD(i, k) + x)*y)**i*((KD(i, k) + x + )*y)**(-k)*(KD(i, k) + x)*((KD(i, k) + x)*y)**l*((KD(i, k) + x + )*y)**(-i), (i >= k) & (i <= l)), (0, True)) + + +def test_deltasummation_trivial(): + assert ds(x, (j, 1, 0)) == 0 + assert ds(x, (j, 1, 3)) == 3*x + assert ds(x + y, (j, 1, 3)) == 3*(x + y) + assert ds(x*y, (j, 1, 3)) == 3*x*y + assert ds(KD(i, j), (k, 1, 3)) == 3*KD(i, j) + assert ds(x*KD(i, j), (k, 1, 3)) == 3*x*KD(i, j) + assert ds(x*y*KD(i, j), (k, 1, 3)) == 3*x*y*KD(i, j) + + +def test_deltasummation_basic_numerical(): + n = symbols('n', integer=True, nonzero=True) + assert ds(KD(n, 0), (n, 1, 3)) == 0 + + # return unevaluated, until it gets implemented + assert ds(KD(i**2, j**2), (j, -oo, oo)) == \ + Sum(KD(i**2, j**2), (j, -oo, oo)) + + assert Piecewise((KD(i, k), And(1 <= i, i <= 3)), (0, True)) == \ + ds(KD(i, j)*KD(j, k), (j, 1, 3)) == \ + ds(KD(j, k)*KD(i, j), (j, 1, 3)) + + assert ds(KD(i, k), (k, -oo, oo)) == 1 + assert ds(KD(i, k), (k, 0, oo)) == Piecewise((1, S.Zero <= i), (0, True)) + assert ds(KD(i, k), (k, 1, 3)) == \ + Piecewise((1, And(1 <= i, i <= 3)), (0, True)) + assert ds(k*KD(i, j)*KD(j, k), (k, -oo, oo)) == j*KD(i, j) + assert ds(j*KD(i, j), (j, -oo, oo)) == i + assert ds(i*KD(i, j), (i, -oo, oo)) == j + assert ds(x, (i, 1, 3)) == 3*x + assert ds((i + j)*KD(i, j), (j, -oo, oo)) == 2*i + + +def test_deltasummation_basic_symbolic(): + assert ds(KD(i, j), (j, 1, 3)) == \ + Piecewise((1, And(1 <= i, i <= 3)), (0, True)) + assert ds(KD(i, j), (j, 1, 1)) == Piecewise((1, Eq(i, 1)), (0, True)) + assert ds(KD(i, j), (j, 2, 2)) == Piecewise((1, Eq(i, 2)), (0, True)) + assert ds(KD(i, j), (j, 3, 3)) == Piecewise((1, Eq(i, 3)), (0, True)) + assert ds(KD(i, j), (j, 1, k)) == \ + Piecewise((1, And(1 <= i, i <= k)), (0, True)) + assert ds(KD(i, j), (j, k, 3)) == \ + Piecewise((1, And(k <= i, i <= 3)), (0, True)) + assert ds(KD(i, j), (j, k, l)) == \ + Piecewise((1, And(k <= i, i <= l)), (0, True)) + + +def test_deltasummation_mul_x_kd(): + assert ds(x*KD(i, j), (j, 1, 3)) == \ + Piecewise((x, And(1 <= i, i <= 3)), (0, True)) + assert ds(x*KD(i, j), (j, 1, 1)) == Piecewise((x, Eq(i, 1)), (0, True)) + assert ds(x*KD(i, j), (j, 2, 2)) == Piecewise((x, Eq(i, 2)), (0, True)) + assert ds(x*KD(i, j), (j, 3, 3)) == Piecewise((x, Eq(i, 3)), (0, True)) + assert ds(x*KD(i, j), (j, 1, k)) == \ + Piecewise((x, And(1 <= i, i <= k)), (0, True)) + assert ds(x*KD(i, j), (j, k, 3)) == \ + Piecewise((x, And(k <= i, i <= 3)), (0, True)) + assert ds(x*KD(i, j), (j, k, l)) == \ + Piecewise((x, And(k <= i, i <= l)), (0, True)) + + +def test_deltasummation_mul_add_x_y_kd(): + assert ds((x + y)*KD(i, j), (j, 1, 3)) == \ + Piecewise((x + y, And(1 <= i, i <= 3)), (0, True)) + assert ds((x + y)*KD(i, j), (j, 1, 1)) == \ + Piecewise((x + y, Eq(i, 1)), (0, True)) + assert ds((x + y)*KD(i, j), (j, 2, 2)) == \ + Piecewise((x + y, Eq(i, 2)), (0, True)) + assert ds((x + y)*KD(i, j), (j, 3, 3)) == \ + Piecewise((x + y, Eq(i, 3)), (0, True)) + assert ds((x + y)*KD(i, j), (j, 1, k)) == \ + Piecewise((x + y, And(1 <= i, i <= k)), (0, True)) + assert ds((x + y)*KD(i, j), (j, k, 3)) == \ + Piecewise((x + y, And(k <= i, i <= 3)), (0, True)) + assert ds((x + y)*KD(i, j), (j, k, l)) == \ + Piecewise((x + y, And(k <= i, i <= l)), (0, True)) + + +def test_deltasummation_add_kd_kd(): + assert ds(KD(i, k) + KD(j, k), (k, 1, 3)) == piecewise_fold( + Piecewise((1, And(1 <= i, i <= 3)), (0, True)) + + Piecewise((1, And(1 <= j, j <= 3)), (0, True))) + assert ds(KD(i, k) + KD(j, k), (k, 1, 1)) == piecewise_fold( + Piecewise((1, Eq(i, 1)), (0, True)) + + Piecewise((1, Eq(j, 1)), (0, True))) + assert ds(KD(i, k) + KD(j, k), (k, 2, 2)) == piecewise_fold( + Piecewise((1, Eq(i, 2)), (0, True)) + + Piecewise((1, Eq(j, 2)), (0, True))) + assert ds(KD(i, k) + KD(j, k), (k, 3, 3)) == piecewise_fold( + Piecewise((1, Eq(i, 3)), (0, True)) + + Piecewise((1, Eq(j, 3)), (0, True))) + assert ds(KD(i, k) + KD(j, k), (k, 1, l)) == piecewise_fold( + Piecewise((1, And(1 <= i, i <= l)), (0, True)) + + Piecewise((1, And(1 <= j, j <= l)), (0, True))) + assert ds(KD(i, k) + KD(j, k), (k, l, 3)) == piecewise_fold( + Piecewise((1, And(l <= i, i <= 3)), (0, True)) + + Piecewise((1, And(l <= j, j <= 3)), (0, True))) + assert ds(KD(i, k) + KD(j, k), (k, l, m)) == piecewise_fold( + Piecewise((1, And(l <= i, i <= m)), (0, True)) + + Piecewise((1, And(l <= j, j <= m)), (0, True))) + + +def test_deltasummation_add_mul_x_kd_kd(): + assert ds(x*KD(i, k) + KD(j, k), (k, 1, 3)) == piecewise_fold( + Piecewise((x, And(1 <= i, i <= 3)), (0, True)) + + Piecewise((1, And(1 <= j, j <= 3)), (0, True))) + assert ds(x*KD(i, k) + KD(j, k), (k, 1, 1)) == piecewise_fold( + Piecewise((x, Eq(i, 1)), (0, True)) + + Piecewise((1, Eq(j, 1)), (0, True))) + assert ds(x*KD(i, k) + KD(j, k), (k, 2, 2)) == piecewise_fold( + Piecewise((x, Eq(i, 2)), (0, True)) + + Piecewise((1, Eq(j, 2)), (0, True))) + assert ds(x*KD(i, k) + KD(j, k), (k, 3, 3)) == piecewise_fold( + Piecewise((x, Eq(i, 3)), (0, True)) + + Piecewise((1, Eq(j, 3)), (0, True))) + assert ds(x*KD(i, k) + KD(j, k), (k, 1, l)) == piecewise_fold( + Piecewise((x, And(1 <= i, i <= l)), (0, True)) + + Piecewise((1, And(1 <= j, j <= l)), (0, True))) + assert ds(x*KD(i, k) + KD(j, k), (k, l, 3)) == piecewise_fold( + Piecewise((x, And(l <= i, i <= 3)), (0, True)) + + Piecewise((1, And(l <= j, j <= 3)), (0, True))) + assert ds(x*KD(i, k) + KD(j, k), (k, l, m)) == piecewise_fold( + Piecewise((x, And(l <= i, i <= m)), (0, True)) + + Piecewise((1, And(l <= j, j <= m)), (0, True))) + + +def test_deltasummation_mul_x_add_kd_kd(): + assert ds(x*(KD(i, k) + KD(j, k)), (k, 1, 3)) == piecewise_fold( + Piecewise((x, And(1 <= i, i <= 3)), (0, True)) + + Piecewise((x, And(1 <= j, j <= 3)), (0, True))) + assert ds(x*(KD(i, k) + KD(j, k)), (k, 1, 1)) == piecewise_fold( + Piecewise((x, Eq(i, 1)), (0, True)) + + Piecewise((x, Eq(j, 1)), (0, True))) + assert ds(x*(KD(i, k) + KD(j, k)), (k, 2, 2)) == piecewise_fold( + Piecewise((x, Eq(i, 2)), (0, True)) + + Piecewise((x, Eq(j, 2)), (0, True))) + assert ds(x*(KD(i, k) + KD(j, k)), (k, 3, 3)) == piecewise_fold( + Piecewise((x, Eq(i, 3)), (0, True)) + + Piecewise((x, Eq(j, 3)), (0, True))) + assert ds(x*(KD(i, k) + KD(j, k)), (k, 1, l)) == piecewise_fold( + Piecewise((x, And(1 <= i, i <= l)), (0, True)) + + Piecewise((x, And(1 <= j, j <= l)), (0, True))) + assert ds(x*(KD(i, k) + KD(j, k)), (k, l, 3)) == piecewise_fold( + Piecewise((x, And(l <= i, i <= 3)), (0, True)) + + Piecewise((x, And(l <= j, j <= 3)), (0, True))) + assert ds(x*(KD(i, k) + KD(j, k)), (k, l, m)) == piecewise_fold( + Piecewise((x, And(l <= i, i <= m)), (0, True)) + + Piecewise((x, And(l <= j, j <= m)), (0, True))) + + +def test_deltasummation_mul_add_x_y_add_kd_kd(): + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 3)) == piecewise_fold( + Piecewise((x + y, And(1 <= i, i <= 3)), (0, True)) + + Piecewise((x + y, And(1 <= j, j <= 3)), (0, True))) + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 1)) == piecewise_fold( + Piecewise((x + y, Eq(i, 1)), (0, True)) + + Piecewise((x + y, Eq(j, 1)), (0, True))) + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 2, 2)) == piecewise_fold( + Piecewise((x + y, Eq(i, 2)), (0, True)) + + Piecewise((x + y, Eq(j, 2)), (0, True))) + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 3, 3)) == piecewise_fold( + Piecewise((x + y, Eq(i, 3)), (0, True)) + + Piecewise((x + y, Eq(j, 3)), (0, True))) + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 1, l)) == piecewise_fold( + Piecewise((x + y, And(1 <= i, i <= l)), (0, True)) + + Piecewise((x + y, And(1 <= j, j <= l)), (0, True))) + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, l, 3)) == piecewise_fold( + Piecewise((x + y, And(l <= i, i <= 3)), (0, True)) + + Piecewise((x + y, And(l <= j, j <= 3)), (0, True))) + assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, l, m)) == piecewise_fold( + Piecewise((x + y, And(l <= i, i <= m)), (0, True)) + + Piecewise((x + y, And(l <= j, j <= m)), (0, True))) + + +def test_deltasummation_add_mul_x_y_mul_x_kd(): + assert ds(x*y + x*KD(i, j), (j, 1, 3)) == \ + Piecewise((3*x*y + x, And(1 <= i, i <= 3)), (3*x*y, True)) + assert ds(x*y + x*KD(i, j), (j, 1, 1)) == \ + Piecewise((x*y + x, Eq(i, 1)), (x*y, True)) + assert ds(x*y + x*KD(i, j), (j, 2, 2)) == \ + Piecewise((x*y + x, Eq(i, 2)), (x*y, True)) + assert ds(x*y + x*KD(i, j), (j, 3, 3)) == \ + Piecewise((x*y + x, Eq(i, 3)), (x*y, True)) + assert ds(x*y + x*KD(i, j), (j, 1, k)) == \ + Piecewise((k*x*y + x, And(1 <= i, i <= k)), (k*x*y, True)) + assert ds(x*y + x*KD(i, j), (j, k, 3)) == \ + Piecewise(((4 - k)*x*y + x, And(k <= i, i <= 3)), ((4 - k)*x*y, True)) + assert ds(x*y + x*KD(i, j), (j, k, l)) == Piecewise( + ((l - k + 1)*x*y + x, And(k <= i, i <= l)), ((l - k + 1)*x*y, True)) + + +def test_deltasummation_mul_x_add_y_kd(): + assert ds(x*(y + KD(i, j)), (j, 1, 3)) == \ + Piecewise((3*x*y + x, And(1 <= i, i <= 3)), (3*x*y, True)) + assert ds(x*(y + KD(i, j)), (j, 1, 1)) == \ + Piecewise((x*y + x, Eq(i, 1)), (x*y, True)) + assert ds(x*(y + KD(i, j)), (j, 2, 2)) == \ + Piecewise((x*y + x, Eq(i, 2)), (x*y, True)) + assert ds(x*(y + KD(i, j)), (j, 3, 3)) == \ + Piecewise((x*y + x, Eq(i, 3)), (x*y, True)) + assert ds(x*(y + KD(i, j)), (j, 1, k)) == \ + Piecewise((k*x*y + x, And(1 <= i, i <= k)), (k*x*y, True)) + assert ds(x*(y + KD(i, j)), (j, k, 3)) == \ + Piecewise(((4 - k)*x*y + x, And(k <= i, i <= 3)), ((4 - k)*x*y, True)) + assert ds(x*(y + KD(i, j)), (j, k, l)) == Piecewise( + ((l - k + 1)*x*y + x, And(k <= i, i <= l)), ((l - k + 1)*x*y, True)) + + +def test_deltasummation_mul_x_add_y_twokd(): + assert ds(x*(y + 2*KD(i, j)), (j, 1, 3)) == \ + Piecewise((3*x*y + 2*x, And(1 <= i, i <= 3)), (3*x*y, True)) + assert ds(x*(y + 2*KD(i, j)), (j, 1, 1)) == \ + Piecewise((x*y + 2*x, Eq(i, 1)), (x*y, True)) + assert ds(x*(y + 2*KD(i, j)), (j, 2, 2)) == \ + Piecewise((x*y + 2*x, Eq(i, 2)), (x*y, True)) + assert ds(x*(y + 2*KD(i, j)), (j, 3, 3)) == \ + Piecewise((x*y + 2*x, Eq(i, 3)), (x*y, True)) + assert ds(x*(y + 2*KD(i, j)), (j, 1, k)) == \ + Piecewise((k*x*y + 2*x, And(1 <= i, i <= k)), (k*x*y, True)) + assert ds(x*(y + 2*KD(i, j)), (j, k, 3)) == Piecewise( + ((4 - k)*x*y + 2*x, And(k <= i, i <= 3)), ((4 - k)*x*y, True)) + assert ds(x*(y + 2*KD(i, j)), (j, k, l)) == Piecewise( + ((l - k + 1)*x*y + 2*x, And(k <= i, i <= l)), ((l - k + 1)*x*y, True)) + + +def test_deltasummation_mul_add_x_y_add_y_kd(): + assert ds((x + y)*(y + KD(i, j)), (j, 1, 3)) == Piecewise( + (3*(x + y)*y + x + y, And(1 <= i, i <= 3)), (3*(x + y)*y, True)) + assert ds((x + y)*(y + KD(i, j)), (j, 1, 1)) == \ + Piecewise(((x + y)*y + x + y, Eq(i, 1)), ((x + y)*y, True)) + assert ds((x + y)*(y + KD(i, j)), (j, 2, 2)) == \ + Piecewise(((x + y)*y + x + y, Eq(i, 2)), ((x + y)*y, True)) + assert ds((x + y)*(y + KD(i, j)), (j, 3, 3)) == \ + Piecewise(((x + y)*y + x + y, Eq(i, 3)), ((x + y)*y, True)) + assert ds((x + y)*(y + KD(i, j)), (j, 1, k)) == Piecewise( + (k*(x + y)*y + x + y, And(1 <= i, i <= k)), (k*(x + y)*y, True)) + assert ds((x + y)*(y + KD(i, j)), (j, k, 3)) == Piecewise( + ((4 - k)*(x + y)*y + x + y, And(k <= i, i <= 3)), + ((4 - k)*(x + y)*y, True)) + assert ds((x + y)*(y + KD(i, j)), (j, k, l)) == Piecewise( + ((l - k + 1)*(x + y)*y + x + y, And(k <= i, i <= l)), + ((l - k + 1)*(x + y)*y, True)) + + +def test_deltasummation_mul_add_x_kd_add_y_kd(): + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 1, 3)) == piecewise_fold( + Piecewise((KD(i, k) + x, And(1 <= i, i <= 3)), (0, True)) + + 3*(KD(i, k) + x)*y) + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 1, 1)) == piecewise_fold( + Piecewise((KD(i, k) + x, Eq(i, 1)), (0, True)) + + (KD(i, k) + x)*y) + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 2, 2)) == piecewise_fold( + Piecewise((KD(i, k) + x, Eq(i, 2)), (0, True)) + + (KD(i, k) + x)*y) + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 3, 3)) == piecewise_fold( + Piecewise((KD(i, k) + x, Eq(i, 3)), (0, True)) + + (KD(i, k) + x)*y) + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 1, k)) == piecewise_fold( + Piecewise((KD(i, k) + x, And(1 <= i, i <= k)), (0, True)) + + k*(KD(i, k) + x)*y) + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, k, 3)) == piecewise_fold( + Piecewise((KD(i, k) + x, And(k <= i, i <= 3)), (0, True)) + + (4 - k)*(KD(i, k) + x)*y) + assert ds((x + KD(i, k))*(y + KD(i, j)), (j, k, l)) == piecewise_fold( + Piecewise((KD(i, k) + x, And(k <= i, i <= l)), (0, True)) + + (l - k + 1)*(KD(i, k) + x)*y) + + +def test_extract_delta(): + raises(ValueError, lambda: _extract_delta(KD(i, j) + KD(k, l), i)) diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/test_gosper.py b/phivenv/Lib/site-packages/sympy/concrete/tests/test_gosper.py new file mode 100644 index 0000000000000000000000000000000000000000..77b642a9b7cd55f96840a8e20e517206b6a6f8f0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/tests/test_gosper.py @@ -0,0 +1,204 @@ +"""Tests for Gosper's algorithm for hypergeometric summation. """ + +from sympy.core.numbers import (Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.combinatorial.factorials import (binomial, factorial) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.gamma_functions import gamma +from sympy.polys.polytools import Poly +from sympy.simplify.simplify import simplify +from sympy.concrete.gosper import gosper_normal, gosper_sum, gosper_term +from sympy.abc import a, b, j, k, m, n, r, x + + +def test_gosper_normal(): + eq = 4*n + 5, 2*(4*n + 1)*(2*n + 3), n + assert gosper_normal(*eq) == \ + (Poly(Rational(1, 4), n), Poly(n + Rational(3, 2)), Poly(n + Rational(1, 4))) + assert gosper_normal(*eq, polys=False) == \ + (Rational(1, 4), n + Rational(3, 2), n + Rational(1, 4)) + + +def test_gosper_term(): + assert gosper_term((4*k + 1)*factorial( + k)/factorial(2*k + 1), k) == (-k - S.Half)/(k + Rational(1, 4)) + + +def test_gosper_sum(): + assert gosper_sum(1, (k, 0, n)) == 1 + n + assert gosper_sum(k, (k, 0, n)) == n*(1 + n)/2 + assert gosper_sum(k**2, (k, 0, n)) == n*(1 + n)*(1 + 2*n)/6 + assert gosper_sum(k**3, (k, 0, n)) == n**2*(1 + n)**2/4 + + assert gosper_sum(2**k, (k, 0, n)) == 2*2**n - 1 + + assert gosper_sum(factorial(k), (k, 0, n)) is None + assert gosper_sum(binomial(n, k), (k, 0, n)) is None + + assert gosper_sum(factorial(k)/k**2, (k, 0, n)) is None + assert gosper_sum((k - 3)*factorial(k), (k, 0, n)) is None + + assert gosper_sum(k*factorial(k), k) == factorial(k) + assert gosper_sum( + k*factorial(k), (k, 0, n)) == n*factorial(n) + factorial(n) - 1 + + assert gosper_sum((-1)**k*binomial(n, k), (k, 0, n)) == 0 + assert gosper_sum(( + -1)**k*binomial(n, k), (k, 0, m)) == -(-1)**m*(m - n)*binomial(n, m)/n + + assert gosper_sum((4*k + 1)*factorial(k)/factorial(2*k + 1), (k, 0, n)) == \ + (2*factorial(2*n + 1) - factorial(n))/factorial(2*n + 1) + + # issue 6033: + assert gosper_sum( + n*(n + a + b)*a**n*b**n/(factorial(n + a)*factorial(n + b)), \ + (n, 0, m)).simplify() == -exp(m*log(a) + m*log(b))*gamma(a + 1) \ + *gamma(b + 1)/(gamma(a)*gamma(b)*gamma(a + m + 1)*gamma(b + m + 1)) \ + + 1/(gamma(a)*gamma(b)) + + +def test_gosper_sum_indefinite(): + assert gosper_sum(k, k) == k*(k - 1)/2 + assert gosper_sum(k**2, k) == k*(k - 1)*(2*k - 1)/6 + + assert gosper_sum(1/(k*(k + 1)), k) == -1/k + assert gosper_sum(-(27*k**4 + 158*k**3 + 430*k**2 + 678*k + 445)*gamma(2*k + + 4)/(3*(3*k + 7)*gamma(3*k + 6)), k) == \ + (3*k + 5)*(k**2 + 2*k + 5)*gamma(2*k + 4)/gamma(3*k + 6) + + +def test_gosper_sum_parametric(): + assert gosper_sum(binomial(S.Half, m - j + 1)*binomial(S.Half, m + j), (j, 1, n)) == \ + n*(1 + m - n)*(-1 + 2*m + 2*n)*binomial(S.Half, 1 + m - n)* \ + binomial(S.Half, m + n)/(m*(1 + 2*m)) + + +def test_gosper_sum_algebraic(): + assert gosper_sum( + n**2 + sqrt(2), (n, 0, m)) == (m + 1)*(2*m**2 + m + 6*sqrt(2))/6 + + +def test_gosper_sum_iterated(): + f1 = binomial(2*k, k)/4**k + f2 = (1 + 2*n)*binomial(2*n, n)/4**n + f3 = (1 + 2*n)*(3 + 2*n)*binomial(2*n, n)/(3*4**n) + f4 = (1 + 2*n)*(3 + 2*n)*(5 + 2*n)*binomial(2*n, n)/(15*4**n) + f5 = (1 + 2*n)*(3 + 2*n)*(5 + 2*n)*(7 + 2*n)*binomial(2*n, n)/(105*4**n) + + assert gosper_sum(f1, (k, 0, n)) == f2 + assert gosper_sum(f2, (n, 0, n)) == f3 + assert gosper_sum(f3, (n, 0, n)) == f4 + assert gosper_sum(f4, (n, 0, n)) == f5 + +# the AeqB tests test expressions given in +# www.math.upenn.edu/~wilf/AeqB.pdf + + +def test_gosper_sum_AeqB_part1(): + f1a = n**4 + f1b = n**3*2**n + f1c = 1/(n**2 + sqrt(5)*n - 1) + f1d = n**4*4**n/binomial(2*n, n) + f1e = factorial(3*n)/(factorial(n)*factorial(n + 1)*factorial(n + 2)*27**n) + f1f = binomial(2*n, n)**2/((n + 1)*4**(2*n)) + f1g = (4*n - 1)*binomial(2*n, n)**2/((2*n - 1)**2*4**(2*n)) + f1h = n*factorial(n - S.Half)**2/factorial(n + 1)**2 + + g1a = m*(m + 1)*(2*m + 1)*(3*m**2 + 3*m - 1)/30 + g1b = 26 + 2**(m + 1)*(m**3 - 3*m**2 + 9*m - 13) + g1c = (m + 1)*(m*(m**2 - 7*m + 3)*sqrt(5) - ( + 3*m**3 - 7*m**2 + 19*m - 6))/(2*m**3*sqrt(5) + m**4 + 5*m**2 - 1)/6 + g1d = Rational(-2, 231) + 2*4**m*(m + 1)*(63*m**4 + 112*m**3 + 18*m**2 - + 22*m + 3)/(693*binomial(2*m, m)) + g1e = Rational(-9, 2) + (81*m**2 + 261*m + 200)*factorial( + 3*m + 2)/(40*27**m*factorial(m)*factorial(m + 1)*factorial(m + 2)) + g1f = (2*m + 1)**2*binomial(2*m, m)**2/(4**(2*m)*(m + 1)) + g1g = -binomial(2*m, m)**2/4**(2*m) + g1h = 4*pi -(2*m + 1)**2*(3*m + 4)*factorial(m - S.Half)**2/factorial(m + 1)**2 + + g = gosper_sum(f1a, (n, 0, m)) + assert g is not None and simplify(g - g1a) == 0 + g = gosper_sum(f1b, (n, 0, m)) + assert g is not None and simplify(g - g1b) == 0 + g = gosper_sum(f1c, (n, 0, m)) + assert g is not None and simplify(g - g1c) == 0 + g = gosper_sum(f1d, (n, 0, m)) + assert g is not None and simplify(g - g1d) == 0 + g = gosper_sum(f1e, (n, 0, m)) + assert g is not None and simplify(g - g1e) == 0 + g = gosper_sum(f1f, (n, 0, m)) + assert g is not None and simplify(g - g1f) == 0 + g = gosper_sum(f1g, (n, 0, m)) + assert g is not None and simplify(g - g1g) == 0 + g = gosper_sum(f1h, (n, 0, m)) + # need to call rewrite(gamma) here because we have terms involving + # factorial(1/2) + assert g is not None and simplify(g - g1h).rewrite(gamma) == 0 + + +def test_gosper_sum_AeqB_part2(): + f2a = n**2*a**n + f2b = (n - r/2)*binomial(r, n) + f2c = factorial(n - 1)**2/(factorial(n - x)*factorial(n + x)) + + g2a = -a*(a + 1)/(a - 1)**3 + a**( + m + 1)*(a**2*m**2 - 2*a*m**2 + m**2 - 2*a*m + 2*m + a + 1)/(a - 1)**3 + g2b = (m - r)*binomial(r, m)/2 + ff = factorial(1 - x)*factorial(1 + x) + g2c = 1/ff*( + 1 - 1/x**2) + factorial(m)**2/(x**2*factorial(m - x)*factorial(m + x)) + + g = gosper_sum(f2a, (n, 0, m)) + assert g is not None and simplify(g - g2a) == 0 + g = gosper_sum(f2b, (n, 0, m)) + assert g is not None and simplify(g - g2b) == 0 + g = gosper_sum(f2c, (n, 1, m)) + assert g is not None and simplify(g - g2c) == 0 + + +def test_gosper_nan(): + a = Symbol('a', positive=True) + b = Symbol('b', positive=True) + n = Symbol('n', integer=True) + m = Symbol('m', integer=True) + f2d = n*(n + a + b)*a**n*b**n/(factorial(n + a)*factorial(n + b)) + g2d = 1/(factorial(a - 1)*factorial( + b - 1)) - a**(m + 1)*b**(m + 1)/(factorial(a + m)*factorial(b + m)) + g = gosper_sum(f2d, (n, 0, m)) + assert simplify(g - g2d) == 0 + + +def test_gosper_sum_AeqB_part3(): + f3a = 1/n**4 + f3b = (6*n + 3)/(4*n**4 + 8*n**3 + 8*n**2 + 4*n + 3) + f3c = 2**n*(n**2 - 2*n - 1)/(n**2*(n + 1)**2) + f3d = n**2*4**n/((n + 1)*(n + 2)) + f3e = 2**n/(n + 1) + f3f = 4*(n - 1)*(n**2 - 2*n - 1)/(n**2*(n + 1)**2*(n - 2)**2*(n - 3)**2) + f3g = (n**4 - 14*n**2 - 24*n - 9)*2**n/(n**2*(n + 1)**2*(n + 2)**2* + (n + 3)**2) + + # g3a -> no closed form + g3b = m*(m + 2)/(2*m**2 + 4*m + 3) + g3c = 2**m/m**2 - 2 + g3d = Rational(2, 3) + 4**(m + 1)*(m - 1)/(m + 2)/3 + # g3e -> no closed form + g3f = -(Rational(-1, 16) + 1/((m - 2)**2*(m + 1)**2)) # the AeqB key is wrong + g3g = Rational(-2, 9) + 2**(m + 1)/((m + 1)**2*(m + 3)**2) + + g = gosper_sum(f3a, (n, 1, m)) + assert g is None + g = gosper_sum(f3b, (n, 1, m)) + assert g is not None and simplify(g - g3b) == 0 + g = gosper_sum(f3c, (n, 1, m - 1)) + assert g is not None and simplify(g - g3c) == 0 + g = gosper_sum(f3d, (n, 1, m)) + assert g is not None and simplify(g - g3d) == 0 + g = gosper_sum(f3e, (n, 0, m - 1)) + assert g is None + g = gosper_sum(f3f, (n, 4, m)) + assert g is not None and simplify(g - g3f) == 0 + g = gosper_sum(f3g, (n, 1, m)) + assert g is not None and simplify(g - g3g) == 0 diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/test_guess.py b/phivenv/Lib/site-packages/sympy/concrete/tests/test_guess.py new file mode 100644 index 0000000000000000000000000000000000000000..5ac5d02b89ad62a70a29bd450b71b284b6aea76d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/tests/test_guess.py @@ -0,0 +1,82 @@ +from sympy.concrete.guess import ( + find_simple_recurrence_vector, + find_simple_recurrence, + rationalize, + guess_generating_function_rational, + guess_generating_function, + guess + ) +from sympy.concrete.products import Product +from sympy.core.function import Function +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import (RisingFactorial, factorial) +from sympy.functions.combinatorial.numbers import fibonacci +from sympy.functions.elementary.exponential import exp + + +def test_find_simple_recurrence_vector(): + assert find_simple_recurrence_vector( + [fibonacci(k) for k in range(12)]) == [1, -1, -1] + + +def test_find_simple_recurrence(): + a = Function('a') + n = Symbol('n') + assert find_simple_recurrence([fibonacci(k) for k in range(12)]) == ( + -a(n) - a(n + 1) + a(n + 2)) + + f = Function('a') + i = Symbol('n') + a = [1, 1, 1] + for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3]) + assert find_simple_recurrence(a, A=f, N=i) == ( + -8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3)) + assert find_simple_recurrence([0, 2, 15, 74, 12, 3, 0, + 1, 2, 85, 4, 5, 63]) == 0 + + +def test_rationalize(): + from mpmath import cos, pi, mpf + assert rationalize(cos(pi/3)) == S.Half + assert rationalize(mpf("0.333333333333333")) == Rational(1, 3) + assert rationalize(mpf("-0.333333333333333")) == Rational(-1, 3) + assert rationalize(pi, maxcoeff = 250) == Rational(355, 113) + + +def test_guess_generating_function_rational(): + x = Symbol('x') + assert guess_generating_function_rational([fibonacci(k) + for k in range(5, 15)]) == ((3*x + 5)/(-x**2 - x + 1)) + + +def test_guess_generating_function(): + x = Symbol('x') + assert guess_generating_function([fibonacci(k) + for k in range(5, 15)])['ogf'] == ((3*x + 5)/(-x**2 - x + 1)) + assert guess_generating_function( + [1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf'] == ( + (1/(x**4 + 2*x**2 - 4*x + 1))**S.Half) + assert guess_generating_function(sympify( + "[3/2, 11/2, 0, -121/2, -363/2, 121, 4719/2, 11495/2, -8712, -178717/2]") + )['ogf'] == (x + Rational(3, 2))/(11*x**2 - 3*x + 1) + assert guess_generating_function([factorial(k) for k in range(12)], + types=['egf'])['egf'] == 1/(-x + 1) + assert guess_generating_function([k+1 for k in range(12)], + types=['egf']) == {'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)} + + +def test_guess(): + i0, i1 = symbols('i0 i1') + assert guess([1, 2, 6, 24, 120], evaluate=False) == [Product(i1 + 1, (i1, 1, i0 - 1))] + assert guess([1, 2, 6, 24, 120]) == [RisingFactorial(2, i0 - 1)] + assert guess([1, 2, 7, 42, 429, 7436, 218348, 10850216], niter=4) == [ + 2**(i0 - 1)*(Rational(27, 16))**(i0**2/2 - 3*i0/2 + + 1)*Product(RisingFactorial(Rational(5, 3), i1 - 1)*RisingFactorial(Rational(7, 3), i1 + - 1)/(RisingFactorial(Rational(3, 2), i1 - 1)*RisingFactorial(Rational(5, 2), i1 - + 1)), (i1, 1, i0 - 1))] + assert guess([1, 0, 2]) == [] + x, y = symbols('x y') + assert guess([1, 2, 6, 24, 120], variables=[x, y]) == [RisingFactorial(2, x - 1)] diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/test_products.py b/phivenv/Lib/site-packages/sympy/concrete/tests/test_products.py new file mode 100644 index 0000000000000000000000000000000000000000..9be053a7040014c6ed38c1279a609fcb2426258e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/tests/test_products.py @@ -0,0 +1,410 @@ +from sympy.concrete.products import (Product, product) +from sympy.concrete.summations import Sum +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.combinatorial.factorials import (rf, factorial) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.simplify.combsimp import combsimp +from sympy.simplify.simplify import simplify +from sympy.testing.pytest import raises + +a, k, n, m, x = symbols('a,k,n,m,x', integer=True) +f = Function('f') + + +def test_karr_convention(): + # Test the Karr product convention that we want to hold. + # See his paper "Summation in Finite Terms" for a detailed + # reasoning why we really want exactly this definition. + # The convention is described for sums on page 309 and + # essentially in section 1.4, definition 3. For products + # we can find in analogy: + # + # \prod_{m <= i < n} f(i) 'has the obvious meaning' for m < n + # \prod_{m <= i < n} f(i) = 0 for m = n + # \prod_{m <= i < n} f(i) = 1 / \prod_{n <= i < m} f(i) for m > n + # + # It is important to note that he defines all products with + # the upper limit being *exclusive*. + # In contrast, SymPy and the usual mathematical notation has: + # + # prod_{i = a}^b f(i) = f(a) * f(a+1) * ... * f(b-1) * f(b) + # + # with the upper limit *inclusive*. So translating between + # the two we find that: + # + # \prod_{m <= i < n} f(i) = \prod_{i = m}^{n-1} f(i) + # + # where we intentionally used two different ways to typeset the + # products and its limits. + + i = Symbol("i", integer=True) + k = Symbol("k", integer=True) + j = Symbol("j", integer=True, positive=True) + + # A simple example with a concrete factors and symbolic limits. + + # The normal product: m = k and n = k + j and therefore m < n: + m = k + n = k + j + + a = m + b = n - 1 + S1 = Product(i**2, (i, a, b)).doit() + + # The reversed product: m = k + j and n = k and therefore m > n: + m = k + j + n = k + + a = m + b = n - 1 + S2 = Product(i**2, (i, a, b)).doit() + + assert S1 * S2 == 1 + + # Test the empty product: m = k and n = k and therefore m = n: + m = k + n = k + + a = m + b = n - 1 + Sz = Product(i**2, (i, a, b)).doit() + + assert Sz == 1 + + # Another example this time with an unspecified factor and + # numeric limits. (We can not do both tests in the same example.) + f = Function("f") + + # The normal product with m < n: + m = 2 + n = 11 + + a = m + b = n - 1 + S1 = Product(f(i), (i, a, b)).doit() + + # The reversed product with m > n: + m = 11 + n = 2 + + a = m + b = n - 1 + S2 = Product(f(i), (i, a, b)).doit() + + assert simplify(S1 * S2) == 1 + + # Test the empty product with m = n: + m = 5 + n = 5 + + a = m + b = n - 1 + Sz = Product(f(i), (i, a, b)).doit() + + assert Sz == 1 + + +def test_karr_proposition_2a(): + # Test Karr, page 309, proposition 2, part a + i, u, v = symbols('i u v', integer=True) + + def test_the_product(m, n): + # g + g = i**3 + 2*i**2 - 3*i + # f = Delta g + f = simplify(g.subs(i, i+1) / g) + # The product + a = m + b = n - 1 + P = Product(f, (i, a, b)).doit() + # Test if Product_{m <= i < n} f(i) = g(n) / g(m) + assert combsimp(P / (g.subs(i, n) / g.subs(i, m))) == 1 + + # m < n + test_the_product(u, u + v) + # m = n + test_the_product(u, u) + # m > n + test_the_product(u + v, u) + + +def test_karr_proposition_2b(): + # Test Karr, page 309, proposition 2, part b + i, u, v, w = symbols('i u v w', integer=True) + + def test_the_product(l, n, m): + # Productmand + s = i**3 + # First product + a = l + b = n - 1 + S1 = Product(s, (i, a, b)).doit() + # Second product + a = l + b = m - 1 + S2 = Product(s, (i, a, b)).doit() + # Third product + a = m + b = n - 1 + S3 = Product(s, (i, a, b)).doit() + # Test if S1 = S2 * S3 as required + assert combsimp(S1 / (S2 * S3)) == 1 + + # l < m < n + test_the_product(u, u + v, u + v + w) + # l < m = n + test_the_product(u, u + v, u + v) + # l < m > n + test_the_product(u, u + v + w, v) + # l = m < n + test_the_product(u, u, u + v) + # l = m = n + test_the_product(u, u, u) + # l = m > n + test_the_product(u + v, u + v, u) + # l > m < n + test_the_product(u + v, u, u + w) + # l > m = n + test_the_product(u + v, u, u) + # l > m > n + test_the_product(u + v + w, u + v, u) + + +def test_simple_products(): + assert product(2, (k, a, n)) == 2**(n - a + 1) + assert product(k, (k, 1, n)) == factorial(n) + assert product(k**3, (k, 1, n)) == factorial(n)**3 + + assert product(k + 1, (k, 0, n - 1)) == factorial(n) + assert product(k + 1, (k, a, n - 1)) == rf(1 + a, n - a) + + assert product(cos(k), (k, 0, 5)) == cos(1)*cos(2)*cos(3)*cos(4)*cos(5) + assert product(cos(k), (k, 3, 5)) == cos(3)*cos(4)*cos(5) + assert product(cos(k), (k, 1, Rational(5, 2))) != cos(1)*cos(2) + + assert isinstance(product(k**k, (k, 1, n)), Product) + + assert Product(x**k, (k, 1, n)).variables == [k] + + raises(ValueError, lambda: Product(n)) + raises(ValueError, lambda: Product(n, k)) + raises(ValueError, lambda: Product(n, k, 1)) + raises(ValueError, lambda: Product(n, k, 1, 10)) + raises(ValueError, lambda: Product(n, (k, 1))) + + assert product(1, (n, 1, oo)) == 1 # issue 8301 + assert product(2, (n, 1, oo)) is oo + assert product(-1, (n, 1, oo)).func is Product + + +def test_multiple_products(): + assert product(x, (n, 1, k), (k, 1, m)) == x**(m**2/2 + m/2) + assert product(f(n), ( + n, 1, m), (m, 1, k)) == Product(f(n), (n, 1, m), (m, 1, k)).doit() + assert Product(f(n), (m, 1, k), (n, 1, k)).doit() == \ + Product(Product(f(n), (m, 1, k)), (n, 1, k)).doit() == \ + product(f(n), (m, 1, k), (n, 1, k)) == \ + product(product(f(n), (m, 1, k)), (n, 1, k)) == \ + Product(f(n)**k, (n, 1, k)) + assert Product( + x, (x, 1, k), (k, 1, n)).doit() == Product(factorial(k), (k, 1, n)) + + assert Product(x**k, (n, 1, k), (k, 1, m)).variables == [n, k] + + +def test_rational_products(): + assert product(1 + 1/k, (k, 1, n)) == rf(2, n)/factorial(n) + + +def test_special_products(): + # Wallis product + assert product((4*k)**2 / (4*k**2 - 1), (k, 1, n)) == \ + 4**n*factorial(n)**2/rf(S.Half, n)/rf(Rational(3, 2), n) + + # Euler's product formula for sin + assert product(1 + a/k**2, (k, 1, n)) == \ + rf(1 - sqrt(-a), n)*rf(1 + sqrt(-a), n)/factorial(n)**2 + + +def test__eval_product(): + from sympy.abc import i, n + # issue 4809 + a = Function('a') + assert product(2*a(i), (i, 1, n)) == 2**n * Product(a(i), (i, 1, n)) + # issue 4810 + assert product(2**i, (i, 1, n)) == 2**(n*(n + 1)/2) + k, m = symbols('k m', integer=True) + assert product(2**i, (i, k, m)) == 2**(-k**2/2 + k/2 + m**2/2 + m/2) + n = Symbol('n', negative=True, integer=True) + p = Symbol('p', positive=True, integer=True) + assert product(2**i, (i, n, p)) == 2**(-n**2/2 + n/2 + p**2/2 + p/2) + assert product(2**i, (i, p, n)) == 2**(n**2/2 + n/2 - p**2/2 + p/2) + + +def test_product_pow(): + # issue 4817 + assert product(2**f(k), (k, 1, n)) == 2**Sum(f(k), (k, 1, n)) + assert product(2**(2*f(k)), (k, 1, n)) == 2**Sum(2*f(k), (k, 1, n)) + + +def test_infinite_product(): + # issue 5737 + assert isinstance(Product(2**(1/factorial(n)), (n, 0, oo)), Product) + + +def test_conjugate_transpose(): + p = Product(x**k, (k, 1, 3)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + A, B = symbols("A B", commutative=False) + p = Product(A*B**k, (k, 1, 3)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + p = Product(B**k*A, (k, 1, 3)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + +def test_simplify_prod(): + y, t, b, c, v, d = symbols('y, t, b, c, v, d', integer = True) + + _simplify = lambda e: simplify(e, doit=False) + assert _simplify(Product(x*y, (x, n, m), (y, a, k)) * \ + Product(y, (x, n, m), (y, a, k))) == \ + Product(x*y**2, (x, n, m), (y, a, k)) + assert _simplify(3 * y* Product(x, (x, n, m)) * Product(x, (x, m + 1, a))) \ + == 3 * y * Product(x, (x, n, a)) + assert _simplify(Product(x, (x, k + 1, a)) * Product(x, (x, n, k))) == \ + Product(x, (x, n, a)) + assert _simplify(Product(x, (x, k + 1, a)) * Product(x + 1, (x, n, k))) == \ + Product(x, (x, k + 1, a)) * Product(x + 1, (x, n, k)) + assert _simplify(Product(x, (t, a, b)) * Product(y, (t, a, b)) * \ + Product(x, (t, b+1, c))) == Product(x*y, (t, a, b)) * \ + Product(x, (t, b+1, c)) + assert _simplify(Product(x, (t, a, b)) * Product(x, (t, b+1, c)) * \ + Product(y, (t, a, b))) == Product(x*y, (t, a, b)) * \ + Product(x, (t, b+1, c)) + assert _simplify(Product(sin(t)**2 + cos(t)**2 + 1, (t, a, b))) == \ + Product(2, (t, a, b)) + assert _simplify(Product(sin(t)**2 + cos(t)**2 - 1, (t, a, b))) == \ + Product(0, (t, a, b)) + assert _simplify(Product(v*Product(sin(t)**2 + cos(t)**2, (t, a, b)), + (v, c, d))) == Product(v*Product(1, (t, a, b)), (v, c, d)) + + +def test_change_index(): + b, y, c, d, z = symbols('b, y, c, d, z', integer = True) + + assert Product(x, (x, a, b)).change_index(x, x + 1, y) == \ + Product(y - 1, (y, a + 1, b + 1)) + assert Product(x**2, (x, a, b)).change_index(x, x - 1) == \ + Product((x + 1)**2, (x, a - 1, b - 1)) + assert Product(x**2, (x, a, b)).change_index(x, -x, y) == \ + Product((-y)**2, (y, -b, -a)) + assert Product(x, (x, a, b)).change_index(x, -x - 1) == \ + Product(-x - 1, (x, - b - 1, -a - 1)) + assert Product(x*y, (x, a, b), (y, c, d)).change_index(x, x - 1, z) == \ + Product((z + 1)*y, (z, a - 1, b - 1), (y, c, d)) + + +def test_reorder(): + b, y, c, d, z = symbols('b, y, c, d, z', integer = True) + + assert Product(x*y, (x, a, b), (y, c, d)).reorder((0, 1)) == \ + Product(x*y, (y, c, d), (x, a, b)) + assert Product(x, (x, a, b), (x, c, d)).reorder((0, 1)) == \ + Product(x, (x, c, d), (x, a, b)) + assert Product(x*y + z, (x, a, b), (z, m, n), (y, c, d)).reorder(\ + (2, 0), (0, 1)) == Product(x*y + z, (z, m, n), (y, c, d), (x, a, b)) + assert Product(x*y*z, (x, a, b), (y, c, d), (z, m, n)).reorder(\ + (0, 1), (1, 2), (0, 2)) == \ + Product(x*y*z, (x, a, b), (z, m, n), (y, c, d)) + assert Product(x*y*z, (x, a, b), (y, c, d), (z, m, n)).reorder(\ + (x, y), (y, z), (x, z)) == \ + Product(x*y*z, (x, a, b), (z, m, n), (y, c, d)) + assert Product(x*y, (x, a, b), (y, c, d)).reorder((x, 1)) == \ + Product(x*y, (y, c, d), (x, a, b)) + assert Product(x*y, (x, a, b), (y, c, d)).reorder((y, x)) == \ + Product(x*y, (y, c, d), (x, a, b)) + + +def test_Product_is_convergent(): + assert Product(1/n**2, (n, 1, oo)).is_convergent() is S.false + assert Product(exp(1/n**2), (n, 1, oo)).is_convergent() is S.true + assert Product(1/n, (n, 1, oo)).is_convergent() is S.false + assert Product(1 + 1/n, (n, 1, oo)).is_convergent() is S.false + assert Product(1 + 1/n**2, (n, 1, oo)).is_convergent() is S.true + + +def test_reverse_order(): + x, y, a, b, c, d= symbols('x, y, a, b, c, d', integer = True) + + assert Product(x, (x, 0, 3)).reverse_order(0) == Product(1/x, (x, 4, -1)) + assert Product(x*y, (x, 1, 5), (y, 0, 6)).reverse_order(0, 1) == \ + Product(x*y, (x, 6, 0), (y, 7, -1)) + assert Product(x, (x, 1, 2)).reverse_order(0) == Product(1/x, (x, 3, 0)) + assert Product(x, (x, 1, 3)).reverse_order(0) == Product(1/x, (x, 4, 0)) + assert Product(x, (x, 1, a)).reverse_order(0) == Product(1/x, (x, a + 1, 0)) + assert Product(x, (x, a, 5)).reverse_order(0) == Product(1/x, (x, 6, a - 1)) + assert Product(x, (x, a + 1, a + 5)).reverse_order(0) == \ + Product(1/x, (x, a + 6, a)) + assert Product(x, (x, a + 1, a + 2)).reverse_order(0) == \ + Product(1/x, (x, a + 3, a)) + assert Product(x, (x, a + 1, a + 1)).reverse_order(0) == \ + Product(1/x, (x, a + 2, a)) + assert Product(x, (x, a, b)).reverse_order(0) == Product(1/x, (x, b + 1, a - 1)) + assert Product(x, (x, a, b)).reverse_order(x) == Product(1/x, (x, b + 1, a - 1)) + assert Product(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1) == \ + Product(x*y, (x, b + 1, a - 1), (y, 6, 1)) + assert Product(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x) == \ + Product(x*y, (x, b + 1, a - 1), (y, 6, 1)) + + +def test_issue_9983(): + n = Symbol('n', integer=True, positive=True) + p = Product(1 + 1/n**Rational(2, 3), (n, 1, oo)) + assert p.is_convergent() is S.false + assert product(1 + 1/n**Rational(2, 3), (n, 1, oo)) == p.doit() + + +def test_issue_13546(): + n = Symbol('n') + k = Symbol('k') + p = Product(n + 1 / 2**k, (k, 0, n-1)).doit() + assert p.subs(n, 2).doit() == Rational(15, 2) + + +def test_issue_14036(): + a, n = symbols('a n') + assert product(1 - a**2 / (n*pi)**2, [n, 1, oo]) != 0 + + +def test_rewrite_Sum(): + assert Product(1 - S.Half**2/k**2, (k, 1, oo)).rewrite(Sum) == \ + exp(Sum(log(1 - 1/(4*k**2)), (k, 1, oo))) + + +def test_KroneckerDelta_Product(): + y = Symbol('y') + assert Product(x*KroneckerDelta(x, y), (x, 0, 1)).doit() == 0 + + +def test_issue_20848(): + _i = Dummy('i') + t, y, z = symbols('t y z') + assert diff(Product(x, (y, 1, z)), x).as_dummy() == Sum(Product(x, (y, 1, _i - 1))*Product(x, (y, _i + 1, z)), (_i, 1, z)).as_dummy() + assert diff(Product(x, (y, 1, z)), x).doit() == x**(z - 1)*z + assert diff(Product(x, (y, x, z)), x) == Derivative(Product(x, (y, x, z)), x) + assert diff(Product(t, (x, 1, z)), x) == S(0) + assert Product(sin(n*x), (n, -1, 1)).diff(x).doit() == S(0) diff --git a/phivenv/Lib/site-packages/sympy/concrete/tests/test_sums_products.py b/phivenv/Lib/site-packages/sympy/concrete/tests/test_sums_products.py new file mode 100644 index 0000000000000000000000000000000000000000..b190afe0bd403819d3525453879d7d5d39e20a56 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/concrete/tests/test_sums_products.py @@ -0,0 +1,1676 @@ +from math import prod + +from sympy.concrete.expr_with_intlimits import ReorderError +from sympy.concrete.products import (Product, product) +from sympy.concrete.summations import (Sum, summation, telescopic, + eval_sum_residue, _dummy_with_inherited_properties_concrete) +from sympy.core.function import (Derivative, Function) +from sympy.core import (Catalan, EulerGamma) +from sympy.core.facts import InconsistentAssumptions +from sympy.core.mod import Mod +from sympy.core.numbers import (E, I, Rational, nan, oo, pi) +from sympy.core.relational import Eq, Ne +from sympy.core.numbers import Float +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import (rf, binomial, factorial) +from sympy.functions.combinatorial.numbers import harmonic +from sympy.functions.elementary.complexes import Abs, re +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (sinh, tanh) +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (cos, sin, atan) +from sympy.functions.special.gamma_functions import (gamma, lowergamma) +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.functions.special.zeta_functions import zeta +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import And, Or +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.expressions.special import Identity +from sympy.matrices import (Matrix, SparseMatrix, + ImmutableDenseMatrix, ImmutableSparseMatrix, diag) +from sympy.sets.contains import Contains +from sympy.sets.fancysets import Range +from sympy.sets.sets import Interval +from sympy.simplify.combsimp import combsimp +from sympy.simplify.simplify import simplify +from sympy.tensor.indexed import (Idx, Indexed, IndexedBase) +from sympy.testing.pytest import XFAIL, raises, slow +from sympy.abc import a, b, c, d, k, m, x, y, z + +n = Symbol('n', integer=True) +f, g = symbols('f g', cls=Function) + +def test_karr_convention(): + # Test the Karr summation convention that we want to hold. + # See his paper "Summation in Finite Terms" for a detailed + # reasoning why we really want exactly this definition. + # The convention is described on page 309 and essentially + # in section 1.4, definition 3: + # + # \sum_{m <= i < n} f(i) 'has the obvious meaning' for m < n + # \sum_{m <= i < n} f(i) = 0 for m = n + # \sum_{m <= i < n} f(i) = - \sum_{n <= i < m} f(i) for m > n + # + # It is important to note that he defines all sums with + # the upper limit being *exclusive*. + # In contrast, SymPy and the usual mathematical notation has: + # + # sum_{i = a}^b f(i) = f(a) + f(a+1) + ... + f(b-1) + f(b) + # + # with the upper limit *inclusive*. So translating between + # the two we find that: + # + # \sum_{m <= i < n} f(i) = \sum_{i = m}^{n-1} f(i) + # + # where we intentionally used two different ways to typeset the + # sum and its limits. + + i = Symbol("i", integer=True) + k = Symbol("k", integer=True) + j = Symbol("j", integer=True) + + # A simple example with a concrete summand and symbolic limits. + + # The normal sum: m = k and n = k + j and therefore m < n: + m = k + n = k + j + + a = m + b = n - 1 + S1 = Sum(i**2, (i, a, b)).doit() + + # The reversed sum: m = k + j and n = k and therefore m > n: + m = k + j + n = k + + a = m + b = n - 1 + S2 = Sum(i**2, (i, a, b)).doit() + + assert simplify(S1 + S2) == 0 + + # Test the empty sum: m = k and n = k and therefore m = n: + m = k + n = k + + a = m + b = n - 1 + Sz = Sum(i**2, (i, a, b)).doit() + + assert Sz == 0 + + # Another example this time with an unspecified summand and + # numeric limits. (We can not do both tests in the same example.) + + # The normal sum with m < n: + m = 2 + n = 11 + + a = m + b = n - 1 + S1 = Sum(f(i), (i, a, b)).doit() + + # The reversed sum with m > n: + m = 11 + n = 2 + + a = m + b = n - 1 + S2 = Sum(f(i), (i, a, b)).doit() + + assert simplify(S1 + S2) == 0 + + # Test the empty sum with m = n: + m = 5 + n = 5 + + a = m + b = n - 1 + Sz = Sum(f(i), (i, a, b)).doit() + + assert Sz == 0 + + e = Piecewise((exp(-i), Mod(i, 2) > 0), (0, True)) + s = Sum(e, (i, 0, 11)) + assert s.n(3) == s.doit().n(3) + + # issue #27893 + n = Symbol('n', integer=True) + assert Sum(1/(x**2 + 1), (x, oo, 0)).doit(deep=False) == Rational(-1, 2) + pi / (2 * tanh(pi)) + assert Sum(c**x/factorial(x), (x, oo, 0)).doit(deep=False).simplify() == exp(c) - 1 # exponential series + assert Sum((-1)**x/x, (x, oo,0)).doit() == -log(2) # alternating harmnic series + assert Sum((1/2)**x,(x, oo, -1)).doit() == S(2) # geometric series + assert Sum(1/x, (x, oo, 0)).doit() == oo # harmonic series, divergent + assert Sum((-1)**x/(2*x+1), (x, oo, -1)).doit() == pi/4 # leibniz series + assert Sum((((-1)**x) * c**(2*x+1)) / factorial(2*x+1), (x, oo, -1)).doit() == sin(c) # sinusoidal series + assert Sum((((-1)**x) * c**(2*x+1)) / (2*x+1), (x, 0, oo)).doit() \ + == Piecewise((atan(c), Ne(c**2, -1) & (Abs(c**2) <= 1)), \ + (Sum((-1)**x*c**(2*x + 1)/(2*x + 1), (x, 0, oo)), True)) # arctangent series + assert Sum(binomial(n, x) * c**x, (x, 0, oo)).doit() \ + == Piecewise(((c + 1)**n, \ + ((n <= -1) & (Abs(c) < 1)) \ + | ((n > 0) & (Abs(c) <= 1)) \ + | ((n <= 0) & (n > -1) & Ne(c, -1) & (Abs(c) <= 1))), \ + (Sum(c**x*binomial(n, x), (x, 0, oo)), True)) # binomial series + assert Sum(1/x**n, (x, oo, 0)).doit() \ + == Piecewise((zeta(n), n > 1), (Sum(x**(-n), (x, oo, 0)), True)) # Euler's zeta function + +def test_karr_proposition_2a(): + # Test Karr, page 309, proposition 2, part a + i = Symbol("i", integer=True) + u = Symbol("u", integer=True) + v = Symbol("v", integer=True) + + def test_the_sum(m, n): + # g + g = i**3 + 2*i**2 - 3*i + # f = Delta g + f = simplify(g.subs(i, i+1) - g) + # The sum + a = m + b = n - 1 + S = Sum(f, (i, a, b)).doit() + # Test if Sum_{m <= i < n} f(i) = g(n) - g(m) + assert simplify(S - (g.subs(i, n) - g.subs(i, m))) == 0 + + # m < n + test_the_sum(u, u+v) + # m = n + test_the_sum(u, u ) + # m > n + test_the_sum(u+v, u ) + + +def test_karr_proposition_2b(): + # Test Karr, page 309, proposition 2, part b + i = Symbol("i", integer=True) + u = Symbol("u", integer=True) + v = Symbol("v", integer=True) + w = Symbol("w", integer=True) + + def test_the_sum(l, n, m): + # Summand + s = i**3 + # First sum + a = l + b = n - 1 + S1 = Sum(s, (i, a, b)).doit() + # Second sum + a = l + b = m - 1 + S2 = Sum(s, (i, a, b)).doit() + # Third sum + a = m + b = n - 1 + S3 = Sum(s, (i, a, b)).doit() + # Test if S1 = S2 + S3 as required + assert S1 - (S2 + S3) == 0 + + # l < m < n + test_the_sum(u, u+v, u+v+w) + # l < m = n + test_the_sum(u, u+v, u+v ) + # l < m > n + test_the_sum(u, u+v+w, v ) + # l = m < n + test_the_sum(u, u, u+v ) + # l = m = n + test_the_sum(u, u, u ) + # l = m > n + test_the_sum(u+v, u+v, u ) + # l > m < n + test_the_sum(u+v, u, u+w ) + # l > m = n + test_the_sum(u+v, u, u ) + # l > m > n + test_the_sum(u+v+w, u+v, u ) + + +def test_arithmetic_sums(): + assert summation(1, (n, a, b)) == b - a + 1 + assert Sum(S.NaN, (n, a, b)) is S.NaN + assert Sum(x, (n, a, a)).doit() == x + assert Sum(x, (x, a, a)).doit() == a + assert Sum(x, (n, 1, a)).doit() == a*x + assert Sum(x, (x, Range(1, 11))).doit() == 55 + assert Sum(x, (x, Range(1, 11, 2))).doit() == 25 + assert Sum(x, (x, Range(1, 10, 2))) == Sum(x, (x, Range(9, 0, -2))) + lo, hi = 1, 2 + s1 = Sum(n, (n, lo, hi)) + s2 = Sum(n, (n, hi, lo)) + assert s1 != s2 + assert s1.doit() == 3 and s2.doit() == 0 + lo, hi = x, x + 1 + s1 = Sum(n, (n, lo, hi)) + s2 = Sum(n, (n, hi, lo)) + assert s1 != s2 + assert s1.doit() == 2*x + 1 and s2.doit() == 0 + assert Sum(Integral(x, (x, 1, y)) + x, (x, 1, 2)).doit() == \ + y**2 + 2 + assert summation(1, (n, 1, 10)) == 10 + assert summation(2*n, (n, 0, 10**10)) == 100000000010000000000 + assert summation(4*n*m, (n, a, 1), (m, 1, d)).expand() == \ + 2*d + 2*d**2 + a*d + a*d**2 - d*a**2 - a**2*d**2 + assert summation(cos(n), (n, -2, 1)) == cos(-2) + cos(-1) + cos(0) + cos(1) + assert summation(cos(n), (n, x, x + 2)) == cos(x) + cos(x + 1) + cos(x + 2) + assert isinstance(summation(cos(n), (n, x, x + S.Half)), Sum) + assert summation(k, (k, 0, oo)) is oo + assert summation(k, (k, Range(1, 11))) == 55 + + +def test_polynomial_sums(): + assert summation(n**2, (n, 3, 8)) == 199 + assert summation(n, (n, a, b)) == \ + ((a + b)*(b - a + 1)/2).expand() + assert summation(n**2, (n, 1, b)) == \ + ((2*b**3 + 3*b**2 + b)/6).expand() + assert summation(n**3, (n, 1, b)) == \ + ((b**4 + 2*b**3 + b**2)/4).expand() + assert summation(n**6, (n, 1, b)) == \ + ((6*b**7 + 21*b**6 + 21*b**5 - 7*b**3 + b)/42).expand() + + +def test_geometric_sums(): + assert summation(pi**n, (n, 0, b)) == (1 - pi**(b + 1)) / (1 - pi) + assert summation(2 * 3**n, (n, 0, b)) == 3**(b + 1) - 1 + assert summation(S.Half**n, (n, 1, oo)) == 1 + assert summation(2**n, (n, 0, b)) == 2**(b + 1) - 1 + assert summation(2**n, (n, 1, oo)) is oo + assert summation(2**(-n), (n, 1, oo)) == 1 + assert summation(3**(-n), (n, 4, oo)) == Rational(1, 54) + assert summation(2**(-4*n + 3), (n, 1, oo)) == Rational(8, 15) + assert summation(2**(n + 1), (n, 1, b)).expand() == 4*(2**b - 1) + + # issue 6664: + assert summation(x**n, (n, 0, oo)) == \ + Piecewise((1/(-x + 1), Abs(x) < 1), (Sum(x**n, (n, 0, oo)), True)) + + assert summation(-2**n, (n, 0, oo)) is -oo + assert summation(I**n, (n, 0, oo)) == Sum(I**n, (n, 0, oo)) + + # issue 6802: + assert summation((-1)**(2*x + 2), (x, 0, n)) == n + 1 + assert summation((-2)**(2*x + 2), (x, 0, n)) == 4*4**(n + 1)/S(3) - Rational(4, 3) + assert summation((-1)**x, (x, 0, n)) == -(-1)**(n + 1)/S(2) + S.Half + assert summation(y**x, (x, a, b)) == \ + Piecewise((-a + b + 1, Eq(y, 1)), ((y**a - y**(b + 1))/(-y + 1), True)) + assert summation((-2)**(y*x + 2), (x, 0, n)) == \ + 4*Piecewise((n + 1, Eq((-2)**y, 1)), + ((-(-2)**(y*(n + 1)) + 1)/(-(-2)**y + 1), True)) + + # issue 8251: + assert summation((1/(n + 1)**2)*n**2, (n, 0, oo)) is oo + + #issue 9908: + assert Sum(1/(n**3 - 1), (n, -oo, -2)).doit() == summation(1/(n**3 - 1), (n, -oo, -2)) + + #issue 11642: + result = Sum(0.5**n, (n, 1, oo)).doit() + assert result == 1.0 + assert result.is_Float + + result = Sum(0.25**n, (n, 1, oo)).doit() + assert result == 1/3. + assert result.is_Float + + result = Sum(0.99999**n, (n, 1, oo)).doit() + assert result == 99999.0 + assert result.is_Float + + result = Sum(S.Half**n, (n, 1, oo)).doit() + assert result == 1 + assert not result.is_Float + + result = Sum(Rational(3, 5)**n, (n, 1, oo)).doit() + assert result == Rational(3, 2) + assert not result.is_Float + + assert Sum(1.0**n, (n, 1, oo)).doit() is oo + assert Sum(2.43**n, (n, 1, oo)).doit() is oo + + # Issue 13979 + i, k, q = symbols('i k q', integer=True) + result = summation( + exp(-2*I*pi*k*i/n) * exp(2*I*pi*q*i/n) / n, (i, 0, n - 1) + ) + assert result.simplify() == Piecewise( + (1, Eq(exp(-2*I*pi*(k - q)/n), 1)), (0, True) + ) + + #Issue 23491 + assert Sum(1/(n**2 + 1), (n, 1, oo)).doit() == S(-1)/2 + pi/(2*tanh(pi)) + +def test_harmonic_sums(): + assert summation(1/k, (k, 0, n)) == Sum(1/k, (k, 0, n)) + assert summation(1/k, (k, 1, n)) == harmonic(n) + assert summation(n/k, (k, 1, n)) == n*harmonic(n) + assert summation(1/k, (k, 5, n)) == harmonic(n) - harmonic(4) + + +def test_composite_sums(): + f = S.Half*(7 - 6*n + Rational(1, 7)*n**3) + s = summation(f, (n, a, b)) + assert not isinstance(s, Sum) + A = 0 + for i in range(-3, 5): + A += f.subs(n, i) + B = s.subs(a, -3).subs(b, 4) + assert A == B + + +def test_hypergeometric_sums(): + assert summation( + binomial(2*k, k)/4**k, (k, 0, n)) == (1 + 2*n)*binomial(2*n, n)/4**n + assert summation(binomial(2*k, k)/5**k, (k, -oo, oo)) == sqrt(5) + + +def test_other_sums(): + f = m**2 + m*exp(m) + g = 3*exp(Rational(3, 2))/2 + exp(S.Half)/2 - exp(Rational(-1, 2))/2 - 3*exp(Rational(-3, 2))/2 + 5 + + assert summation(f, (m, Rational(-3, 2), Rational(3, 2))) == g + assert summation(f, (m, -1.5, 1.5)).evalf().epsilon_eq(g.evalf(), 1e-10) + +fac = factorial + + +def NS(e, n=15, **options): + return str(sympify(e).evalf(n, **options)) + + +def test_evalf_fast_series(): + # Euler transformed series for sqrt(1+x) + assert NS(Sum( + fac(2*n + 1)/fac(n)**2/2**(3*n + 1), (n, 0, oo)), 100) == NS(sqrt(2), 100) + + # Some series for exp(1) + estr = NS(E, 100) + assert NS(Sum(1/fac(n), (n, 0, oo)), 100) == estr + assert NS(1/Sum((1 - 2*n)/fac(2*n), (n, 0, oo)), 100) == estr + assert NS(Sum((2*n + 1)/fac(2*n), (n, 0, oo)), 100) == estr + assert NS(Sum((4*n + 3)/2**(2*n + 1)/fac(2*n + 1), (n, 0, oo))**2, 100) == estr + + pistr = NS(pi, 100) + # Ramanujan series for pi + assert NS(9801/sqrt(8)/Sum(fac( + 4*n)*(1103 + 26390*n)/fac(n)**4/396**(4*n), (n, 0, oo)), 100) == pistr + assert NS(1/Sum( + binomial(2*n, n)**3 * (42*n + 5)/2**(12*n + 4), (n, 0, oo)), 100) == pistr + # Machin's formula for pi + assert NS(16*Sum((-1)**n/(2*n + 1)/5**(2*n + 1), (n, 0, oo)) - + 4*Sum((-1)**n/(2*n + 1)/239**(2*n + 1), (n, 0, oo)), 100) == pistr + + # Apery's constant + astr = NS(zeta(3), 100) + P = 126392*n**5 + 412708*n**4 + 531578*n**3 + 336367*n**2 + 104000* \ + n + 12463 + assert NS(Sum((-1)**n * P / 24 * (fac(2*n + 1)*fac(2*n)*fac( + n))**3 / fac(3*n + 2) / fac(4*n + 3)**3, (n, 0, oo)), 100) == astr + assert NS(Sum((-1)**n * (205*n**2 + 250*n + 77)/64 * fac(n)**10 / + fac(2*n + 1)**5, (n, 0, oo)), 100) == astr + + +def test_evalf_fast_series_issue_4021(): + # Catalan's constant + assert NS(Sum((-1)**(n - 1)*2**(8*n)*(40*n**2 - 24*n + 3)*fac(2*n)**3* + fac(n)**2/n**3/(2*n - 1)/fac(4*n)**2, (n, 1, oo))/64, 100) == \ + NS(Catalan, 100) + astr = NS(zeta(3), 100) + assert NS(5*Sum( + (-1)**(n - 1)*fac(n)**2 / n**3 / fac(2*n), (n, 1, oo))/2, 100) == astr + assert NS(Sum((-1)**(n - 1)*(56*n**2 - 32*n + 5) / (2*n - 1)**2 * fac(n - 1) + **3 / fac(3*n), (n, 1, oo))/4, 100) == astr + + +def test_evalf_slow_series(): + assert NS(Sum((-1)**n / n, (n, 1, oo)), 15) == NS(-log(2), 15) + assert NS(Sum((-1)**n / n, (n, 1, oo)), 50) == NS(-log(2), 50) + assert NS(Sum(1/n**2, (n, 1, oo)), 15) == NS(pi**2/6, 15) + assert NS(Sum(1/n**2, (n, 1, oo)), 100) == NS(pi**2/6, 100) + assert NS(Sum(1/n**2, (n, 1, oo)), 500) == NS(pi**2/6, 500) + assert NS(Sum((-1)**n / (2*n + 1)**3, (n, 0, oo)), 15) == NS(pi**3/32, 15) + assert NS(Sum((-1)**n / (2*n + 1)**3, (n, 0, oo)), 50) == NS(pi**3/32, 50) + + +def test_evalf_oo_to_oo(): + # There used to be an error in certain cases + # Does not evaluate, but at least do not throw an error + # Evaluates symbolically to 0, which is not correct + assert Sum(1/(n**2+1), (n, -oo, oo)).evalf() == Sum(1/(n**2+1), (n, -oo, oo)) + # This evaluates if from 1 to oo and symbolically + assert Sum(1/(factorial(abs(n))), (n, -oo, -1)).evalf() == Sum(1/(factorial(abs(n))), (n, -oo, -1)) + + +def test_euler_maclaurin(): + # Exact polynomial sums with E-M + def check_exact(f, a, b, m, n): + A = Sum(f, (k, a, b)) + s, e = A.euler_maclaurin(m, n) + assert (e == 0) and (s.expand() == A.doit()) + check_exact(k**4, a, b, 0, 2) + check_exact(k**4 + 2*k, a, b, 1, 2) + check_exact(k**4 + k**2, a, b, 1, 5) + check_exact(k**5, 2, 6, 1, 2) + check_exact(k**5, 2, 6, 1, 3) + assert Sum(x-1, (x, 0, 2)).euler_maclaurin(m=30, n=30, eps=2**-15) == (0, 0) + # Not exact + assert Sum(k**6, (k, a, b)).euler_maclaurin(0, 2)[1] != 0 + # Numerical test + for mi, ni in [(2, 4), (2, 20), (10, 20), (18, 20)]: + A = Sum(1/k**3, (k, 1, oo)) + s, e = A.euler_maclaurin(mi, ni) + assert abs((s - zeta(3)).evalf()) < e.evalf() + + raises(ValueError, lambda: Sum(1, (x, 0, 1), (k, 0, 1)).euler_maclaurin()) + + +@slow +def test_evalf_euler_maclaurin(): + assert NS(Sum(1/k**k, (k, 1, oo)), 15) == '1.29128599706266' + assert NS(Sum(1/k**k, (k, 1, oo)), + 50) == '1.2912859970626635404072825905956005414986193682745' + assert NS(Sum(1/k - log(1 + 1/k), (k, 1, oo)), 15) == NS(EulerGamma, 15) + assert NS(Sum(1/k - log(1 + 1/k), (k, 1, oo)), 50) == NS(EulerGamma, 50) + assert NS(Sum(log(k)/k**2, (k, 1, oo)), 15) == '0.937548254315844' + assert NS(Sum(log(k)/k**2, (k, 1, oo)), + 50) == '0.93754825431584375370257409456786497789786028861483' + assert NS(Sum(1/k, (k, 1000000, 2000000)), 15) == '0.693147930560008' + assert NS(Sum(1/k, (k, 1000000, 2000000)), + 50) == '0.69314793056000780941723211364567656807940638436025' + + +def test_evalf_symbolic(): + # issue 6328 + expr = Sum(f(x), (x, 1, 3)) + Sum(g(x), (x, 1, 3)) + assert expr.evalf() == expr + + +def test_evalf_issue_3273(): + assert Sum(0, (k, 1, oo)).evalf() == 0 + + +def test_simple_products(): + assert Product(S.NaN, (x, 1, 3)) is S.NaN + assert product(S.NaN, (x, 1, 3)) is S.NaN + assert Product(x, (n, a, a)).doit() == x + assert Product(x, (x, a, a)).doit() == a + assert Product(x, (y, 1, a)).doit() == x**a + + lo, hi = 1, 2 + s1 = Product(n, (n, lo, hi)) + s2 = Product(n, (n, hi, lo)) + assert s1 != s2 + # This IS correct according to Karr product convention + assert s1.doit() == 2 + assert s2.doit() == 1 + + lo, hi = x, x + 1 + s1 = Product(n, (n, lo, hi)) + s2 = Product(n, (n, hi, lo)) + s3 = 1 / Product(n, (n, hi + 1, lo - 1)) + assert s1 != s2 + # This IS correct according to Karr product convention + assert s1.doit() == x*(x + 1) + assert s2.doit() == 1 + assert s3.doit() == x*(x + 1) + + assert Product(Integral(2*x, (x, 1, y)) + 2*x, (x, 1, 2)).doit() == \ + (y**2 + 1)*(y**2 + 3) + assert product(2, (n, a, b)) == 2**(b - a + 1) + assert product(n, (n, 1, b)) == factorial(b) + assert product(n**3, (n, 1, b)) == factorial(b)**3 + assert product(3**(2 + n), (n, a, b)) \ + == 3**(2*(1 - a + b) + b/2 + (b**2)/2 + a/2 - (a**2)/2) + assert product(cos(n), (n, 3, 5)) == cos(3)*cos(4)*cos(5) + assert product(cos(n), (n, x, x + 2)) == cos(x)*cos(x + 1)*cos(x + 2) + assert isinstance(product(cos(n), (n, x, x + S.Half)), Product) + # If Product managed to evaluate this one, it most likely got it wrong! + assert isinstance(Product(n**n, (n, 1, b)), Product) + + +def test_rational_products(): + assert combsimp(product(1 + 1/n, (n, a, b))) == (1 + b)/a + assert combsimp(product(n + 1, (n, a, b))) == gamma(2 + b)/gamma(1 + a) + assert combsimp(product((n + 1)/(n - 1), (n, a, b))) == b*(1 + b)/(a*(a - 1)) + assert combsimp(product(n/(n + 1)/(n + 2), (n, a, b))) == \ + a*gamma(a + 2)/(b + 1)/gamma(b + 3) + assert combsimp(product(n*(n + 1)/(n - 1)/(n - 2), (n, a, b))) == \ + b**2*(b - 1)*(1 + b)/(a - 1)**2/(a*(a - 2)) + + +def test_wallis_product(): + # Wallis product, given in two different forms to ensure that Product + # can factor simple rational expressions + A = Product(4*n**2 / (4*n**2 - 1), (n, 1, b)) + B = Product((2*n)*(2*n)/(2*n - 1)/(2*n + 1), (n, 1, b)) + R = pi*gamma(b + 1)**2/(2*gamma(b + S.Half)*gamma(b + Rational(3, 2))) + assert simplify(A.doit()) == R + assert simplify(B.doit()) == R + # This one should eventually also be doable (Euler's product formula for sin) + # assert Product(1+x/n**2, (n, 1, b)) == ... + + +def test_telescopic_sums(): + #checks also input 2 of comment 1 issue 4127 + assert Sum(1/k - 1/(k + 1), (k, 1, n)).doit() == 1 - 1/(1 + n) + assert Sum( + f(k) - f(k + 2), (k, m, n)).doit() == -f(1 + n) - f(2 + n) + f(m) + f(1 + m) + assert Sum(cos(k) - cos(k + 3), (k, 1, n)).doit() == -cos(1 + n) - \ + cos(2 + n) - cos(3 + n) + cos(1) + cos(2) + cos(3) + + # dummy variable shouldn't matter + assert telescopic(1/m, -m/(1 + m), (m, n - 1, n)) == \ + telescopic(1/k, -k/(1 + k), (k, n - 1, n)) + + assert Sum(1/x/(x - 1), (x, a, b)).doit() == 1/(a - 1) - 1/b + eq = 1/((5*n + 2)*(5*(n + 1) + 2)) + assert Sum(eq, (n, 0, oo)).doit() == S(1)/10 + nz = symbols('nz', nonzero=True) + v = Sum(eq.subs(5, nz), (n, 0, oo)).doit() + assert v.subs(nz, 5).simplify() == S(1)/10 + # check that apart is being used in non-symbolic case + s = Sum(eq, (n, 0, k)).doit() + v = Sum(eq, (n, 0, 10**100)).doit() + assert v == s.subs(k, 10**100) + + +def test_sum_reconstruct(): + s = Sum(n**2, (n, -1, 1)) + assert s == Sum(*s.args) + raises(ValueError, lambda: Sum(x, x)) + raises(ValueError, lambda: Sum(x, (x, 1))) + + +def test_limit_subs(): + for F in (Sum, Product, Integral): + assert F(a*exp(a), (a, -2, 2)) == F(a*exp(a), (a, -b, b)).subs(b, 2) + assert F(a, (a, F(b, (b, 1, 2)), 4)).subs(F(b, (b, 1, 2)), c) == \ + F(a, (a, c, 4)) + assert F(x, (x, 1, x + y)).subs(x, 1) == F(x, (x, 1, y + 1)) + + +def test_function_subs(): + S = Sum(x*f(y),(x,0,oo),(y,0,oo)) + assert S.subs(f(y),y) == Sum(x*y,(x,0,oo),(y,0,oo)) + assert S.subs(f(x),x) == S + raises(ValueError, lambda: S.subs(f(y),x+y) ) + S = Sum(x*log(y),(x,0,oo),(y,0,oo)) + assert S.subs(log(y),y) == S + S = Sum(x*f(y),(x,0,oo),(y,0,oo)) + assert S.subs(f(y),y) == Sum(x*y,(x,0,oo),(y,0,oo)) + + +def test_equality(): + # if this fails remove special handling below + raises(ValueError, lambda: Sum(x, x)) + r = symbols('x', real=True) + for F in (Sum, Product, Integral): + try: + assert F(x, x) != F(y, y) + assert F(x, (x, 1, 2)) != F(x, x) + assert F(x, (x, x)) != F(x, x) # or else they print the same + assert F(1, x) != F(1, y) + except ValueError: + pass + assert F(a, (x, 1, 2)) != F(a, (x, 1, 3)) # diff limit + assert F(a, (x, 1, x)) != F(a, (y, 1, y)) + assert F(a, (x, 1, 2)) != F(b, (x, 1, 2)) # diff expression + assert F(x, (x, 1, 2)) != F(r, (r, 1, 2)) # diff assumptions + assert F(1, (x, 1, x)) != F(1, (y, 1, x)) # only dummy is diff + assert F(1, (x, 1, x)).dummy_eq(F(1, (y, 1, x))) + + # issue 5265 + assert Sum(x, (x, 1, x)).subs(x, a) == Sum(x, (x, 1, a)) + + +def test_Sum_doit(): + assert Sum(n*Integral(a**2), (n, 0, 2)).doit() == a**3 + assert Sum(n*Integral(a**2), (n, 0, 2)).doit(deep=False) == \ + 3*Integral(a**2) + assert summation(n*Integral(a**2), (n, 0, 2)) == 3*Integral(a**2) + + # test nested sum evaluation + s = Sum( Sum( Sum(2,(z,1,n+1)), (y,x+1,n)), (x,1,n)) + assert 0 == (s.doit() - n*(n+1)*(n-1)).factor() + + # Integer assumes finite + assert Sum(KroneckerDelta(x, y), (x, -oo, oo)).doit() == Piecewise((1, And(-oo < y, y < oo)), (0, True)) + assert Sum(KroneckerDelta(m, n), (m, -oo, oo)).doit() == 1 + assert Sum(m*KroneckerDelta(x, y), (x, -oo, oo)).doit() == Piecewise((m, And(-oo < y, y < oo)), (0, True)) + assert Sum(x*KroneckerDelta(m, n), (m, -oo, oo)).doit() == x + assert Sum(Sum(KroneckerDelta(m, n), (m, 1, 3)), (n, 1, 3)).doit() == 3 + assert Sum(Sum(KroneckerDelta(k, m), (m, 1, 3)), (n, 1, 3)).doit() == \ + 3 * Piecewise((1, And(1 <= k, k <= 3)), (0, True)) + assert Sum(f(n) * Sum(KroneckerDelta(m, n), (m, 0, oo)), (n, 1, 3)).doit() == \ + f(1) + f(2) + f(3) + assert Sum(f(n) * Sum(KroneckerDelta(m, n), (m, 0, oo)), (n, 1, oo)).doit() == \ + Sum(f(n), (n, 1, oo)) + + # issue 2597 + nmax = symbols('N', integer=True, positive=True) + pw = Piecewise((1, And(1 <= n, n <= nmax)), (0, True)) + assert Sum(pw, (n, 1, nmax)).doit() == Sum(Piecewise((1, nmax >= n), + (0, True)), (n, 1, nmax)) + + q, s = symbols('q, s') + assert summation(1/n**(2*s), (n, 1, oo)) == Piecewise((zeta(2*s), 2*re(s) > 1), + (Sum(n**(-2*s), (n, 1, oo)), True)) + assert summation(1/(n+1)**s, (n, 0, oo)) == Piecewise((zeta(s), re(s) > 1), + (Sum((n + 1)**(-s), (n, 0, oo)), True)) + assert summation(1/(n+q)**s, (n, 0, oo)) == Piecewise( + (zeta(s, q), And(~Contains(-q, S.Naturals0), re(s) > 1)), + (Sum((n + q)**(-s), (n, 0, oo)), True)) + assert summation(1/(n+q)**s, (n, q, oo)) == Piecewise( + (zeta(s, 2*q), And(~Contains(-2*q, S.Naturals0), re(s) > 1)), + (Sum((n + q)**(-s), (n, q, oo)), True)) + assert summation(1/n**2, (n, 1, oo)) == zeta(2) + assert summation(1/n**s, (n, 0, oo)) == Sum(n**(-s), (n, 0, oo)) + assert summation(1/(n+1)**(2+I), (n, 0, oo)) == zeta(2+I) + t = symbols('t', real=True, positive=True) + assert summation(1/(n+I)**(t+1), (n, 0, oo)) == zeta(t+1, I) + + +def test_Product_doit(): + assert Product(n*Integral(a**2), (n, 1, 3)).doit() == 2 * a**9 / 9 + assert Product(n*Integral(a**2), (n, 1, 3)).doit(deep=False) == \ + 6*Integral(a**2)**3 + assert product(n*Integral(a**2), (n, 1, 3)) == 6*Integral(a**2)**3 + + +def test_Sum_interface(): + assert isinstance(Sum(0, (n, 0, 2)), Sum) + assert Sum(nan, (n, 0, 2)) is nan + assert Sum(nan, (n, 0, oo)) is nan + assert Sum(0, (n, 0, 2)).doit() == 0 + assert isinstance(Sum(0, (n, 0, oo)), Sum) + assert Sum(0, (n, 0, oo)).doit() == 0 + raises(ValueError, lambda: Sum(1)) + raises(ValueError, lambda: summation(1)) + + +def test_diff(): + assert Sum(x, (x, 1, 2)).diff(x) == 0 + assert Sum(x*y, (x, 1, 2)).diff(x) == 0 + assert Sum(x*y, (y, 1, 2)).diff(x) == Sum(y, (y, 1, 2)) + e = Sum(x*y, (x, 1, a)) + assert e.diff(a) == Derivative(e, a) + assert Sum(x*y, (x, 1, 3), (a, 2, 5)).diff(y).doit() == \ + Sum(x*y, (x, 1, 3), (a, 2, 5)).doit().diff(y) == 24 + assert Sum(x, (x, 1, 2)).diff(y) == 0 + + +def test_hypersum(): + assert simplify(summation(x**n/fac(n), (n, 1, oo))) == -1 + exp(x) + assert summation((-1)**n * x**(2*n) / fac(2*n), (n, 0, oo)) == cos(x) + assert simplify(summation((-1)**n*x**(2*n + 1) / + factorial(2*n + 1), (n, 3, oo))) == -x + sin(x) + x**3/6 - x**5/120 + + assert summation(1/(n + 2)**3, (n, 1, oo)) == Rational(-9, 8) + zeta(3) + assert summation(1/n**4, (n, 1, oo)) == pi**4/90 + + s = summation(x**n*n, (n, -oo, 0)) + assert s.is_Piecewise + assert s.args[0].args[0] == -1/(x*(1 - 1/x)**2) + assert s.args[0].args[1] == (abs(1/x) < 1) + + m = Symbol('n', integer=True, positive=True) + assert summation(binomial(m, k), (k, 0, m)) == 2**m + + +def test_issue_4170(): + assert summation(1/factorial(k), (k, 0, oo)) == E + + +def test_is_commutative(): + from sympy.physics.secondquant import NO, F, Fd + m = Symbol('m', commutative=False) + for f in (Sum, Product, Integral): + assert f(z, (z, 1, 1)).is_commutative is True + assert f(z*y, (z, 1, 6)).is_commutative is True + assert f(m*x, (x, 1, 2)).is_commutative is False + + assert f(NO(Fd(x)*F(y))*z, (z, 1, 2)).is_commutative is False + + +def test_is_zero(): + for func in [Sum, Product]: + assert func(0, (x, 1, 1)).is_zero is True + assert func(x, (x, 1, 1)).is_zero is None + + assert Sum(0, (x, 1, 0)).is_zero is True + assert Product(0, (x, 1, 0)).is_zero is False + + +def test_is_number(): + # is number should not rely on evaluation or assumptions, + # it should be equivalent to `not foo.free_symbols` + assert Sum(1, (x, 1, 1)).is_number is True + assert Sum(1, (x, 1, x)).is_number is False + assert Sum(0, (x, y, z)).is_number is False + assert Sum(x, (y, 1, 2)).is_number is False + assert Sum(x, (y, 1, 1)).is_number is False + assert Sum(x, (x, 1, 2)).is_number is True + assert Sum(x*y, (x, 1, 2), (y, 1, 3)).is_number is True + + assert Product(2, (x, 1, 1)).is_number is True + assert Product(2, (x, 1, y)).is_number is False + assert Product(0, (x, y, z)).is_number is False + assert Product(1, (x, y, z)).is_number is False + assert Product(x, (y, 1, x)).is_number is False + assert Product(x, (y, 1, 2)).is_number is False + assert Product(x, (y, 1, 1)).is_number is False + assert Product(x, (x, 1, 2)).is_number is True + + +def test_free_symbols(): + for func in [Sum, Product]: + assert func(1, (x, 1, 2)).free_symbols == set() + assert func(0, (x, 1, y)).free_symbols == {y} + assert func(2, (x, 1, y)).free_symbols == {y} + assert func(x, (x, 1, 2)).free_symbols == set() + assert func(x, (x, 1, y)).free_symbols == {y} + assert func(x, (y, 1, y)).free_symbols == {x, y} + assert func(x, (y, 1, 2)).free_symbols == {x} + assert func(x, (y, 1, 1)).free_symbols == {x} + assert func(x, (y, 1, z)).free_symbols == {x, z} + assert func(x, (x, 1, y), (y, 1, 2)).free_symbols == set() + assert func(x, (x, 1, y), (y, 1, z)).free_symbols == {z} + assert func(x, (x, 1, y), (y, 1, y)).free_symbols == {y} + assert func(x, (y, 1, y), (y, 1, z)).free_symbols == {x, z} + assert Sum(1, (x, 1, y)).free_symbols == {y} + # free_symbols answers whether the object *as written* has free symbols, + # not whether the evaluated expression has free symbols + assert Product(1, (x, 1, y)).free_symbols == {y} + # don't count free symbols that are not independent of integration + # variable(s) + assert func(f(x), (f(x), 1, 2)).free_symbols == set() + assert func(f(x), (f(x), 1, x)).free_symbols == {x} + assert func(f(x), (f(x), 1, y)).free_symbols == {y} + assert func(f(x), (z, 1, y)).free_symbols == {x, y} + + +def test_conjugate_transpose(): + A, B = symbols("A B", commutative=False) + p = Sum(A*B**n, (n, 1, 3)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + p = Sum(B**n*A, (n, 1, 3)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + +def test_noncommutativity_honoured(): + A, B = symbols("A B", commutative=False) + M = symbols('M', integer=True, positive=True) + p = Sum(A*B**n, (n, 1, M)) + assert p.doit() == A*Piecewise((M, Eq(B, 1)), + ((B - B**(M + 1))*(1 - B)**(-1), True)) + + p = Sum(B**n*A, (n, 1, M)) + assert p.doit() == Piecewise((M, Eq(B, 1)), + ((B - B**(M + 1))*(1 - B)**(-1), True))*A + + p = Sum(B**n*A*B**n, (n, 1, M)) + assert p.doit() == p + + +def test_issue_4171(): + assert summation(factorial(2*k + 1)/factorial(2*k), (k, 0, oo)) is oo + assert summation(2*k + 1, (k, 0, oo)) is oo + + +def test_issue_6273(): + assert Sum(x, (x, 1, n)).n(2, subs={n: 1}) == Float(1, 2) + + +def test_issue_6274(): + assert Sum(x, (x, 1, 0)).doit() == 0 + assert NS(Sum(x, (x, 1, 0))) == '0' + assert Sum(n, (n, 10, 5)).doit() == -30 + assert NS(Sum(n, (n, 10, 5))) == '-30.0000000000000' + + +def test_simplify_sum(): + y, t, v = symbols('y, t, v') + + _simplify = lambda e: simplify(e, doit=False) + assert _simplify(Sum(x*y, (x, n, m), (y, a, k)) + \ + Sum(y, (x, n, m), (y, a, k))) == Sum(y * (x + 1), (x, n, m), (y, a, k)) + assert _simplify(Sum(x, (x, n, m)) + Sum(x, (x, m + 1, a))) == \ + Sum(x, (x, n, a)) + assert _simplify(Sum(x, (x, k + 1, a)) + Sum(x, (x, n, k))) == \ + Sum(x, (x, n, a)) + assert _simplify(Sum(x, (x, k + 1, a)) + Sum(x + 1, (x, n, k))) == \ + Sum(x, (x, n, a)) + Sum(1, (x, n, k)) + assert _simplify(Sum(x, (x, 0, 3)) * 3 + 3 * Sum(x, (x, 4, 6)) + \ + 4 * Sum(z, (z, 0, 1))) == 4*Sum(z, (z, 0, 1)) + 3*Sum(x, (x, 0, 6)) + assert _simplify(3*Sum(x**2, (x, a, b)) + Sum(x, (x, a, b))) == \ + Sum(x*(3*x + 1), (x, a, b)) + assert _simplify(Sum(x**3, (x, n, k)) * 3 + 3 * Sum(x, (x, n, k)) + \ + 4 * y * Sum(z, (z, n, k))) + 1 == \ + 4*y*Sum(z, (z, n, k)) + 3*Sum(x**3 + x, (x, n, k)) + 1 + assert _simplify(Sum(x, (x, a, b)) + 1 + Sum(x, (x, b + 1, c))) == \ + 1 + Sum(x, (x, a, c)) + assert _simplify(Sum(x, (t, a, b)) + Sum(y, (t, a, b)) + \ + Sum(x, (t, b+1, c))) == x * Sum(1, (t, a, c)) + y * Sum(1, (t, a, b)) + assert _simplify(Sum(x, (t, a, b)) + Sum(x, (t, b+1, c)) + \ + Sum(y, (t, a, b))) == x * Sum(1, (t, a, c)) + y * Sum(1, (t, a, b)) + assert _simplify(Sum(x, (t, a, b)) + 2 * Sum(x, (t, b+1, c))) == \ + _simplify(Sum(x, (t, a, b)) + Sum(x, (t, b+1, c)) + Sum(x, (t, b+1, c))) + assert _simplify(Sum(x, (x, a, b))*Sum(x**2, (x, a, b))) == \ + Sum(x, (x, a, b)) * Sum(x**2, (x, a, b)) + assert _simplify(Sum(x, (t, a, b)) + Sum(y, (t, a, b)) + Sum(z, (t, a, b))) \ + == (x + y + z) * Sum(1, (t, a, b)) # issue 8596 + assert _simplify(Sum(x, (t, a, b)) + Sum(y, (t, a, b)) + Sum(z, (t, a, b)) + \ + Sum(v, (t, a, b))) == (x + y + z + v) * Sum(1, (t, a, b)) # issue 8596 + assert _simplify(Sum(x * y, (x, a, b)) / (3 * y)) == \ + (Sum(x, (x, a, b)) / 3) + assert _simplify(Sum(f(x) * y * z, (x, a, b)) / (y * z)) \ + == Sum(f(x), (x, a, b)) + assert _simplify(Sum(c * x, (x, a, b)) - c * Sum(x, (x, a, b))) == 0 + assert _simplify(c * (Sum(x, (x, a, b)) + y)) == c * (y + Sum(x, (x, a, b))) + assert _simplify(c * (Sum(x, (x, a, b)) + y * Sum(x, (x, a, b)))) == \ + c * (y + 1) * Sum(x, (x, a, b)) + assert _simplify(Sum(Sum(c * x, (x, a, b)), (y, a, b))) == \ + c * Sum(x, (x, a, b), (y, a, b)) + assert _simplify(Sum((3 + y) * Sum(c * x, (x, a, b)), (y, a, b))) == \ + c * Sum((3 + y), (y, a, b)) * Sum(x, (x, a, b)) + assert _simplify(Sum((3 + t) * Sum(c * t, (x, a, b)), (y, a, b))) == \ + c*t*(t + 3)*Sum(1, (x, a, b))*Sum(1, (y, a, b)) + assert _simplify(Sum(Sum(d * t, (x, a, b - 1)) + \ + Sum(d * t, (x, b, c)), (t, a, b))) == \ + d * Sum(1, (x, a, c)) * Sum(t, (t, a, b)) + assert _simplify(Sum(sin(t)**2 + cos(t)**2 + 1, (t, a, b))) == \ + 2 * Sum(1, (t, a, b)) + + +def test_change_index(): + b, v, w = symbols('b, v, w', integer = True) + + assert Sum(x, (x, a, b)).change_index(x, x + 1, y) == \ + Sum(y - 1, (y, a + 1, b + 1)) + assert Sum(x**2, (x, a, b)).change_index( x, x - 1) == \ + Sum((x+1)**2, (x, a - 1, b - 1)) + assert Sum(x**2, (x, a, b)).change_index( x, -x, y) == \ + Sum((-y)**2, (y, -b, -a)) + assert Sum(x, (x, a, b)).change_index( x, -x - 1) == \ + Sum(-x - 1, (x, -b - 1, -a - 1)) + assert Sum(x*y, (x, a, b), (y, c, d)).change_index( x, x - 1, z) == \ + Sum((z + 1)*y, (z, a - 1, b - 1), (y, c, d)) + assert Sum(x, (x, a, b)).change_index( x, x + v) == \ + Sum(-v + x, (x, a + v, b + v)) + assert Sum(x, (x, a, b)).change_index( x, -x - v) == \ + Sum(-v - x, (x, -b - v, -a - v)) + assert Sum(x, (x, a, b)).change_index(x, w*x, v) == \ + Sum(v/w, (v, b*w, a*w)) + raises(ValueError, lambda: Sum(x, (x, a, b)).change_index(x, 2*x)) + + +def test_reorder(): + b, y, c, d, z = symbols('b, y, c, d, z', integer = True) + + assert Sum(x*y, (x, a, b), (y, c, d)).reorder((0, 1)) == \ + Sum(x*y, (y, c, d), (x, a, b)) + assert Sum(x, (x, a, b), (x, c, d)).reorder((0, 1)) == \ + Sum(x, (x, c, d), (x, a, b)) + assert Sum(x*y + z, (x, a, b), (z, m, n), (y, c, d)).reorder(\ + (2, 0), (0, 1)) == Sum(x*y + z, (z, m, n), (y, c, d), (x, a, b)) + assert Sum(x*y*z, (x, a, b), (y, c, d), (z, m, n)).reorder(\ + (0, 1), (1, 2), (0, 2)) == Sum(x*y*z, (x, a, b), (z, m, n), (y, c, d)) + assert Sum(x*y*z, (x, a, b), (y, c, d), (z, m, n)).reorder(\ + (x, y), (y, z), (x, z)) == Sum(x*y*z, (x, a, b), (z, m, n), (y, c, d)) + assert Sum(x*y, (x, a, b), (y, c, d)).reorder((x, 1)) == \ + Sum(x*y, (y, c, d), (x, a, b)) + assert Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x)) == \ + Sum(x*y, (y, c, d), (x, a, b)) + + +def test_reverse_order(): + assert Sum(x, (x, 0, 3)).reverse_order(0) == Sum(-x, (x, 4, -1)) + assert Sum(x*y, (x, 1, 5), (y, 0, 6)).reverse_order(0, 1) == \ + Sum(x*y, (x, 6, 0), (y, 7, -1)) + assert Sum(x, (x, 1, 2)).reverse_order(0) == Sum(-x, (x, 3, 0)) + assert Sum(x, (x, 1, 3)).reverse_order(0) == Sum(-x, (x, 4, 0)) + assert Sum(x, (x, 1, a)).reverse_order(0) == Sum(-x, (x, a + 1, 0)) + assert Sum(x, (x, a, 5)).reverse_order(0) == Sum(-x, (x, 6, a - 1)) + assert Sum(x, (x, a + 1, a + 5)).reverse_order(0) == \ + Sum(-x, (x, a + 6, a)) + assert Sum(x, (x, a + 1, a + 2)).reverse_order(0) == \ + Sum(-x, (x, a + 3, a)) + assert Sum(x, (x, a + 1, a + 1)).reverse_order(0) == \ + Sum(-x, (x, a + 2, a)) + assert Sum(x, (x, a, b)).reverse_order(0) == Sum(-x, (x, b + 1, a - 1)) + assert Sum(x, (x, a, b)).reverse_order(x) == Sum(-x, (x, b + 1, a - 1)) + assert Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1) == \ + Sum(x*y, (x, b + 1, a - 1), (y, 6, 1)) + assert Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x) == \ + Sum(x*y, (x, b + 1, a - 1), (y, 6, 1)) + + +def test_issue_7097(): + assert sum(x**n/n for n in range(1, 401)) == summation(x**n/n, (n, 1, 400)) + + +def test_factor_expand_subs(): + # test factoring + assert Sum(4 * x, (x, 1, y)).factor() == 4 * Sum(x, (x, 1, y)) + assert Sum(x * a, (x, 1, y)).factor() == a * Sum(x, (x, 1, y)) + assert Sum(4 * x * a, (x, 1, y)).factor() == 4 * a * Sum(x, (x, 1, y)) + assert Sum(4 * x * y, (x, 1, y)).factor() == 4 * y * Sum(x, (x, 1, y)) + + # test expand + _x = Symbol('x', zero=False) + assert Sum(x+1,(x,1,y)).expand() == Sum(x,(x,1,y)) + Sum(1,(x,1,y)) + assert Sum(x+a*x**2,(x,1,y)).expand() == Sum(x,(x,1,y)) + Sum(a*x**2,(x,1,y)) + assert Sum(_x**(n + 1)*(n + 1), (n, -1, oo)).expand() \ + == Sum(n*_x*_x**n + _x*_x**n, (n, -1, oo)) + assert Sum(x**(n + 1)*(n + 1), (n, -1, oo)).expand(power_exp=False) \ + == Sum(n*x**(n + 1) + x**(n + 1), (n, -1, oo)) + assert Sum(x**(n + 1)*(n + 1), (n, -1, oo)).expand(force=True) \ + == Sum(x*x**n, (n, -1, oo)) + Sum(n*x*x**n, (n, -1, oo)) + assert Sum(a*n+a*n**2,(n,0,4)).expand() \ + == Sum(a*n,(n,0,4)) + Sum(a*n**2,(n,0,4)) + assert Sum(_x**a*_x**n,(x,0,3)) \ + == Sum(_x**(a+n),(x,0,3)).expand(power_exp=True) + _a, _n = symbols('a n', positive=True) + assert Sum(x**(_a+_n),(x,0,3)).expand(power_exp=True) \ + == Sum(x**_a*x**_n, (x, 0, 3)) + assert Sum(x**(_a-_n),(x,0,3)).expand(power_exp=True) \ + == Sum(x**(_a-_n),(x,0,3)).expand(power_exp=False) + + # test subs + assert Sum(1/(1+a*x**2),(x,0,3)).subs([(a,3)]) == Sum(1/(1+3*x**2),(x,0,3)) + assert Sum(x*y,(x,0,y),(y,0,x)).subs([(x,3)]) == Sum(x*y,(x,0,y),(y,0,3)) + assert Sum(x,(x,1,10)).subs([(x,y-2)]) == Sum(x,(x,1,10)) + assert Sum(1/x,(x,1,10)).subs([(x,(3+n)**3)]) == Sum(1/x,(x,1,10)) + assert Sum(1/x,(x,1,10)).subs([(x,3*x-2)]) == Sum(1/x,(x,1,10)) + + +def test_distribution_over_equality(): + assert Product(Eq(x*2, f(x)), (x, 1, 3)).doit() == Eq(48, f(1)*f(2)*f(3)) + assert Sum(Eq(f(x), x**2), (x, 0, y)) == \ + Eq(Sum(f(x), (x, 0, y)), Sum(x**2, (x, 0, y))) + + +def test_issue_2787(): + n, k = symbols('n k', positive=True, integer=True) + p = symbols('p', positive=True) + binomial_dist = binomial(n, k)*p**k*(1 - p)**(n - k) + s = Sum(binomial_dist*k, (k, 0, n)) + res = s.doit().simplify() + ans = Piecewise( + (n*p, x), + (Sum(k*p**k*binomial(n, k)*(1 - p)**(n - k), (k, 0, n)), + True)).subs(x, (Eq(n, 1) | (n > 1)) & (p/Abs(p - 1) <= 1)) + ans2 = Piecewise( + (n*p, x), + (factorial(n)*Sum(p**k*(1 - p)**(-k + n)/ + (factorial(-k + n)*factorial(k - 1)), (k, 0, n)), + True)).subs(x, (Eq(n, 1) | (n > 1)) & (p/Abs(p - 1) <= 1)) + assert res in [ans, ans2] # XXX system dependent + # Issue #17165: make sure that another simplify does not complicate + # the result by much. Why didn't first simplify replace + # Eq(n, 1) | (n > 1) with True? + assert res.simplify().count_ops() <= res.count_ops() + 2 + + +def test_issue_4668(): + assert summation(1/n, (n, 2, oo)) is oo + + +def test_matrix_sum(): + A = Matrix([[0, 1], [n, 0]]) + + result = Sum(A, (n, 0, 3)).doit() + assert result == Matrix([[0, 4], [6, 0]]) + assert result.__class__ == ImmutableDenseMatrix + + A = SparseMatrix([[0, 1], [n, 0]]) + + result = Sum(A, (n, 0, 3)).doit() + assert result.__class__ == ImmutableSparseMatrix + + +def test_failing_matrix_sum(): + n = Symbol('n') + # TODO Implement matrix geometric series summation. + A = Matrix([[0, 1, 0], [-1, 0, 0], [0, 0, 0]]) + assert Sum(A ** n, (n, 1, 4)).doit() == \ + Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + # issue sympy/sympy#16989 + assert summation(A**n, (n, 1, 1)) == A + + +def test_indexed_idx_sum(): + i = symbols('i', cls=Idx) + r = Indexed('r', i) + assert Sum(r, (i, 0, 3)).doit() == sum(r.xreplace({i: j}) for j in range(4)) + assert Product(r, (i, 0, 3)).doit() == prod([r.xreplace({i: j}) for j in range(4)]) + + j = symbols('j', integer=True) + assert Sum(r, (i, j, j+2)).doit() == sum(r.xreplace({i: j+k}) for k in range(3)) + assert Product(r, (i, j, j+2)).doit() == prod([r.xreplace({i: j+k}) for k in range(3)]) + + k = Idx('k', range=(1, 3)) + A = IndexedBase('A') + assert Sum(A[k], k).doit() == sum(A[Idx(j, (1, 3))] for j in range(1, 4)) + assert Product(A[k], k).doit() == prod([A[Idx(j, (1, 3))] for j in range(1, 4)]) + + raises(ValueError, lambda: Sum(A[k], (k, 1, 4))) + raises(ValueError, lambda: Sum(A[k], (k, 0, 3))) + raises(ValueError, lambda: Sum(A[k], (k, 2, oo))) + + raises(ValueError, lambda: Product(A[k], (k, 1, 4))) + raises(ValueError, lambda: Product(A[k], (k, 0, 3))) + raises(ValueError, lambda: Product(A[k], (k, 2, oo))) + + +@slow +def test_is_convergent(): + # divergence tests -- + assert Sum(n/(2*n + 1), (n, 1, oo)).is_convergent() is S.false + assert Sum(factorial(n)/5**n, (n, 1, oo)).is_convergent() is S.false + assert Sum(3**(-2*n - 1)*n**n, (n, 1, oo)).is_convergent() is S.false + assert Sum((-1)**n*n, (n, 3, oo)).is_convergent() is S.false + assert Sum((-1)**n, (n, 1, oo)).is_convergent() is S.false + assert Sum(log(1/n), (n, 2, oo)).is_convergent() is S.false + assert Sum(sin(n), (n, 1, oo)).is_convergent() is S.false + + # Raabe's test -- + assert Sum(Product((3*m),(m,1,n))/Product((3*m+4),(m,1,n)),(n,1,oo)).is_convergent() is S.true + + # root test -- + assert Sum((-12)**n/n, (n, 1, oo)).is_convergent() is S.false + + # integral test -- + + # p-series test -- + assert Sum(1/(n**2 + 1), (n, 1, oo)).is_convergent() is S.true + assert Sum(1/n**Rational(6, 5), (n, 1, oo)).is_convergent() is S.true + assert Sum(2/(n*sqrt(n - 1)), (n, 2, oo)).is_convergent() is S.true + assert Sum(1/(sqrt(n)*sqrt(n)), (n, 2, oo)).is_convergent() is S.false + assert Sum(factorial(n) / factorial(n+2), (n, 1, oo)).is_convergent() is S.true + assert Sum(rf(5,n)/rf(7,n),(n,1,oo)).is_convergent() is S.true + assert Sum((rf(1, n)*rf(2, n))/(rf(3, n)*factorial(n)),(n,1,oo)).is_convergent() is S.false + + # comparison test -- + assert Sum(1/(n + log(n)), (n, 1, oo)).is_convergent() is S.false + assert Sum(1/(n**2*log(n)), (n, 2, oo)).is_convergent() is S.true + assert Sum(1/(n*log(n)), (n, 2, oo)).is_convergent() is S.false + assert Sum(2/(n*log(n)*log(log(n))**2), (n, 5, oo)).is_convergent() is S.true + assert Sum(2/(n*log(n)**2), (n, 2, oo)).is_convergent() is S.true + assert Sum((n - 1)/(n**2*log(n)**3), (n, 2, oo)).is_convergent() is S.true + assert Sum(1/(n*log(n)*log(log(n))), (n, 5, oo)).is_convergent() is S.false + assert Sum((n - 1)/(n*log(n)**3), (n, 3, oo)).is_convergent() is S.false + assert Sum(2/(n**2*log(n)), (n, 2, oo)).is_convergent() is S.true + assert Sum(1/(n*sqrt(log(n))*log(log(n))), (n, 100, oo)).is_convergent() is S.false + assert Sum(log(log(n))/(n*log(n)**2), (n, 100, oo)).is_convergent() is S.true + assert Sum(log(n)/n**2, (n, 5, oo)).is_convergent() is S.true + + # alternating series tests -- + assert Sum((-1)**(n - 1)/(n**2 - 1), (n, 3, oo)).is_convergent() is S.true + + # with -negativeInfinite Limits + assert Sum(1/(n**2 + 1), (n, -oo, 1)).is_convergent() is S.true + assert Sum(1/(n - 1), (n, -oo, -1)).is_convergent() is S.false + assert Sum(1/(n**2 - 1), (n, -oo, -5)).is_convergent() is S.true + assert Sum(1/(n**2 - 1), (n, -oo, 2)).is_convergent() is S.true + assert Sum(1/(n**2 - 1), (n, -oo, oo)).is_convergent() is S.true + + # piecewise functions + f = Piecewise((n**(-2), n <= 1), (n**2, n > 1)) + assert Sum(f, (n, 1, oo)).is_convergent() is S.false + assert Sum(f, (n, -oo, oo)).is_convergent() is S.false + assert Sum(f, (n, 1, 100)).is_convergent() is S.true + #assert Sum(f, (n, -oo, 1)).is_convergent() is S.true + + # integral test + + assert Sum(log(n)/n**3, (n, 1, oo)).is_convergent() is S.true + assert Sum(-log(n)/n**3, (n, 1, oo)).is_convergent() is S.true + # the following function has maxima located at (x, y) = + # (1.2, 0.43), (3.0, -0.25) and (6.8, 0.050) + eq = (x - 2)*(x**2 - 6*x + 4)*exp(-x) + assert Sum(eq, (x, 1, oo)).is_convergent() is S.true + assert Sum(eq, (x, 1, 2)).is_convergent() is S.true + assert Sum(1/(x**3), (x, 1, oo)).is_convergent() is S.true + assert Sum(1/(x**S.Half), (x, 1, oo)).is_convergent() is S.false + + # issue 19545 + assert Sum(1/n - 3/(3*n +2), (n, 1, oo)).is_convergent() is S.true + + # issue 19836 + assert Sum(4/(n + 2) - 5/(n + 1) + 1/n,(n, 7, oo)).is_convergent() is S.true + + +def test_is_absolutely_convergent(): + assert Sum((-1)**n, (n, 1, oo)).is_absolutely_convergent() is S.false + assert Sum((-1)**n/n**2, (n, 1, oo)).is_absolutely_convergent() is S.true + + +@XFAIL +def test_convergent_failing(): + # dirichlet tests + assert Sum(sin(n)/n, (n, 1, oo)).is_convergent() is S.true + assert Sum(sin(2*n)/n, (n, 1, oo)).is_convergent() is S.true + + +def test_issue_6966(): + i, k, m = symbols('i k m', integer=True) + z_i, q_i = symbols('z_i q_i') + a_k = Sum(-q_i*z_i/k,(i,1,m)) + b_k = a_k.diff(z_i) + assert isinstance(b_k, Sum) + assert b_k == Sum(-q_i/k,(i,1,m)) + + +def test_issue_10156(): + cx = Sum(2*y**2*x, (x, 1,3)) + e = 2*y*Sum(2*cx*x**2, (x, 1, 9)) + assert e.factor() == \ + 8*y**3*Sum(x, (x, 1, 3))*Sum(x**2, (x, 1, 9)) + + +def test_issue_10973(): + assert Sum((-n + (n**3 + 1)**(S(1)/3))/log(n), (n, 1, oo)).is_convergent() is S.true + + +def test_issue_14103(): + assert Sum(sin(n)**2 + cos(n)**2 - 1, (n, 1, oo)).is_convergent() is S.true + assert Sum(sin(pi*n), (n, 1, oo)).is_convergent() is S.true + + +def test_issue_14129(): + x = Symbol('x', zero=False) + assert Sum( k*x**k, (k, 0, n-1)).doit() == \ + Piecewise((n**2/2 - n/2, Eq(x, 1)), ((n*x*x**n - + n*x**n - x*x**n + x)/(x - 1)**2, True)) + assert Sum( x**k, (k, 0, n-1)).doit() == \ + Piecewise((n, Eq(x, 1)), ((-x**n + 1)/(-x + 1), True)) + assert Sum( k*(x/y+x)**k, (k, 0, n-1)).doit() == \ + Piecewise((n*(n - 1)/2, Eq(x, y/(y + 1))), + (x*(y + 1)*(n*x*y*(x + x/y)**(n - 1) + + n*x*(x + x/y)**(n - 1) - n*y*(x + x/y)**(n - 1) - + x*y*(x + x/y)**(n - 1) - x*(x + x/y)**(n - 1) + y)/ + (x*y + x - y)**2, True)) + + +def test_issue_14112(): + assert Sum((-1)**n/sqrt(n), (n, 1, oo)).is_absolutely_convergent() is S.false + assert Sum((-1)**(2*n)/n, (n, 1, oo)).is_convergent() is S.false + assert Sum((-2)**n + (-3)**n, (n, 1, oo)).is_convergent() is S.false + + +def test_issue_14219(): + A = diag(0, 2, -3) + res = diag(1, 15, -20) + assert Sum(A**n, (n, 0, 3)).doit() == res + + +def test_sin_times_absolutely_convergent(): + assert Sum(sin(n) / n**3, (n, 1, oo)).is_convergent() is S.true + assert Sum(sin(n) * log(n) / n**3, (n, 1, oo)).is_convergent() is S.true + + +def test_issue_14111(): + assert Sum(1/log(log(n)), (n, 22, oo)).is_convergent() is S.false + + +def test_issue_14484(): + assert Sum(sin(n)/log(log(n)), (n, 22, oo)).is_convergent() is S.false + + +def test_issue_14640(): + i, n = symbols("i n", integer=True) + a, b, c = symbols("a b c", zero=False) + + assert Sum(a**-i/(a - b), (i, 0, n)).doit() == Sum( + 1/(a*a**i - a**i*b), (i, 0, n)).doit() == Piecewise( + (n + 1, Eq(1/a, 1)), + ((-a**(-n - 1) + 1)/(1 - 1/a), True))/(a - b) + + assert Sum((b*a**i - c*a**i)**-2, (i, 0, n)).doit() == Piecewise( + (n + 1, Eq(a**(-2), 1)), + ((-a**(-2*n - 2) + 1)/(1 - 1/a**2), True))/(b - c)**2 + + s = Sum(i*(a**(n - i) - b**(n - i))/(a - b), (i, 0, n)).doit() + assert not s.has(Sum) + assert s.subs({a: 2, b: 3, n: 5}) == 122 + + +def test_issue_15943(): + s = Sum(binomial(n, k)*factorial(n - k), (k, 0, n)).doit().rewrite(gamma) + assert s == -E*(n + 1)*gamma(n + 1)*lowergamma(n + 1, 1)/gamma(n + 2 + ) + E*gamma(n + 1) + assert s.simplify() == E*(factorial(n) - lowergamma(n + 1, 1)) + + +def test_Sum_dummy_eq(): + assert not Sum(x, (x, a, b)).dummy_eq(1) + assert not Sum(x, (x, a, b)).dummy_eq(Sum(x, (x, a, b), (a, 1, 2))) + assert not Sum(x, (x, a, b)).dummy_eq(Sum(x, (x, a, c))) + assert Sum(x, (x, a, b)).dummy_eq(Sum(x, (x, a, b))) + d = Dummy() + assert Sum(x, (x, a, d)).dummy_eq(Sum(x, (x, a, c)), c) + assert not Sum(x, (x, a, d)).dummy_eq(Sum(x, (x, a, c))) + assert Sum(x, (x, a, c)).dummy_eq(Sum(y, (y, a, c))) + assert Sum(x, (x, a, d)).dummy_eq(Sum(y, (y, a, c)), c) + assert not Sum(x, (x, a, d)).dummy_eq(Sum(y, (y, a, c))) + + +def test_issue_15852(): + assert summation(x**y*y, (y, -oo, oo)).doit() == Sum(x**y*y, (y, -oo, oo)) + + +def test_exceptions(): + S = Sum(x, (x, a, b)) + raises(ValueError, lambda: S.change_index(x, x**2, y)) + S = Sum(x, (x, a, b), (x, 1, 4)) + raises(ValueError, lambda: S.index(x)) + S = Sum(x, (x, a, b), (y, 1, 4)) + raises(ValueError, lambda: S.reorder([x])) + S = Sum(x, (x, y, b), (y, 1, 4)) + raises(ReorderError, lambda: S.reorder_limit(0, 1)) + S = Sum(x*y, (x, a, b), (y, 1, 4)) + raises(NotImplementedError, lambda: S.is_convergent()) + + +def test_sumproducts_assumptions(): + M = Symbol('M', integer=True, positive=True) + + m = Symbol('m', integer=True) + for func in [Sum, Product]: + assert func(m, (m, -M, M)).is_positive is None + assert func(m, (m, -M, M)).is_nonpositive is None + assert func(m, (m, -M, M)).is_negative is None + assert func(m, (m, -M, M)).is_nonnegative is None + assert func(m, (m, -M, M)).is_finite is True + + m = Symbol('m', integer=True, nonnegative=True) + for func in [Sum, Product]: + assert func(m, (m, 0, M)).is_positive is None + assert func(m, (m, 0, M)).is_nonpositive is None + assert func(m, (m, 0, M)).is_negative is False + assert func(m, (m, 0, M)).is_nonnegative is True + assert func(m, (m, 0, M)).is_finite is True + + m = Symbol('m', integer=True, positive=True) + for func in [Sum, Product]: + assert func(m, (m, 1, M)).is_positive is True + assert func(m, (m, 1, M)).is_nonpositive is False + assert func(m, (m, 1, M)).is_negative is False + assert func(m, (m, 1, M)).is_nonnegative is True + assert func(m, (m, 1, M)).is_finite is True + + m = Symbol('m', integer=True, negative=True) + assert Sum(m, (m, -M, -1)).is_positive is False + assert Sum(m, (m, -M, -1)).is_nonpositive is True + assert Sum(m, (m, -M, -1)).is_negative is True + assert Sum(m, (m, -M, -1)).is_nonnegative is False + assert Sum(m, (m, -M, -1)).is_finite is True + assert Product(m, (m, -M, -1)).is_positive is None + assert Product(m, (m, -M, -1)).is_nonpositive is None + assert Product(m, (m, -M, -1)).is_negative is None + assert Product(m, (m, -M, -1)).is_nonnegative is None + assert Product(m, (m, -M, -1)).is_finite is True + + m = Symbol('m', integer=True, nonpositive=True) + assert Sum(m, (m, -M, 0)).is_positive is False + assert Sum(m, (m, -M, 0)).is_nonpositive is True + assert Sum(m, (m, -M, 0)).is_negative is None + assert Sum(m, (m, -M, 0)).is_nonnegative is None + assert Sum(m, (m, -M, 0)).is_finite is True + assert Product(m, (m, -M, 0)).is_positive is None + assert Product(m, (m, -M, 0)).is_nonpositive is None + assert Product(m, (m, -M, 0)).is_negative is None + assert Product(m, (m, -M, 0)).is_nonnegative is None + assert Product(m, (m, -M, 0)).is_finite is True + + m = Symbol('m', integer=True) + assert Sum(2, (m, 0, oo)).is_positive is None + assert Sum(2, (m, 0, oo)).is_nonpositive is None + assert Sum(2, (m, 0, oo)).is_negative is None + assert Sum(2, (m, 0, oo)).is_nonnegative is None + assert Sum(2, (m, 0, oo)).is_finite is None + + assert Product(2, (m, 0, oo)).is_positive is None + assert Product(2, (m, 0, oo)).is_nonpositive is None + assert Product(2, (m, 0, oo)).is_negative is False + assert Product(2, (m, 0, oo)).is_nonnegative is None + assert Product(2, (m, 0, oo)).is_finite is None + + assert Product(0, (x, M, M-1)).is_positive is True + assert Product(0, (x, M, M-1)).is_finite is True + + +def test_expand_with_assumptions(): + M = Symbol('M', integer=True, positive=True) + x = Symbol('x', positive=True) + m = Symbol('m', nonnegative=True) + assert log(Product(x**m, (m, 0, M))).expand() == Sum(m*log(x), (m, 0, M)) + assert log(Product(exp(x**m), (m, 0, M))).expand() == Sum(x**m, (m, 0, M)) + assert log(Product(x**m, (m, 0, M))).rewrite(Sum).expand() == Sum(m*log(x), (m, 0, M)) + assert log(Product(exp(x**m), (m, 0, M))).rewrite(Sum).expand() == Sum(x**m, (m, 0, M)) + + n = Symbol('n', nonnegative=True) + i, j = symbols('i,j', positive=True, integer=True) + x, y = symbols('x,y', positive=True) + assert log(Product(x**i*y**j, (i, 1, n), (j, 1, m))).expand() \ + == Sum(i*log(x) + j*log(y), (i, 1, n), (j, 1, m)) + + m = Symbol('m', nonnegative=True, integer=True) + s = Sum(x**m, (m, 0, M)) + s_as_product = s.rewrite(Product) + assert s_as_product.has(Product) + assert s_as_product == log(Product(exp(x**m), (m, 0, M))) + assert s_as_product.expand() == s + s5 = s.subs(M, 5) + s5_as_product = s5.rewrite(Product) + assert s5_as_product.has(Product) + assert s5_as_product.doit().expand() == s5.doit() + + +def test_has_finite_limits(): + x = Symbol('x') + assert Sum(1, (x, 1, 9)).has_finite_limits is True + assert Sum(1, (x, 1, oo)).has_finite_limits is False + M = Symbol('M') + assert Sum(1, (x, 1, M)).has_finite_limits is None + M = Symbol('M', positive=True) + assert Sum(1, (x, 1, M)).has_finite_limits is True + x = Symbol('x', positive=True) + M = Symbol('M') + assert Sum(1, (x, 1, M)).has_finite_limits is True + + assert Sum(1, (x, 1, M), (y, -oo, oo)).has_finite_limits is False + +def test_has_reversed_limits(): + assert Sum(1, (x, 1, 1)).has_reversed_limits is False + assert Sum(1, (x, 1, 9)).has_reversed_limits is False + assert Sum(1, (x, 1, -9)).has_reversed_limits is True + assert Sum(1, (x, 1, 0)).has_reversed_limits is True + assert Sum(1, (x, 1, oo)).has_reversed_limits is False + M = Symbol('M') + assert Sum(1, (x, 1, M)).has_reversed_limits is None + M = Symbol('M', positive=True, integer=True) + assert Sum(1, (x, 1, M)).has_reversed_limits is False + assert Sum(1, (x, 1, M), (y, -oo, oo)).has_reversed_limits is False + M = Symbol('M', negative=True) + assert Sum(1, (x, 1, M)).has_reversed_limits is True + + assert Sum(1, (x, 1, M), (y, -oo, oo)).has_reversed_limits is True + assert Sum(1, (x, oo, oo)).has_reversed_limits is None + + +def test_has_empty_sequence(): + assert Sum(1, (x, 1, 1)).has_empty_sequence is False + assert Sum(1, (x, 1, 9)).has_empty_sequence is False + assert Sum(1, (x, 1, -9)).has_empty_sequence is False + assert Sum(1, (x, 1, 0)).has_empty_sequence is True + assert Sum(1, (x, y, y - 1)).has_empty_sequence is True + assert Sum(1, (x, 3, 2), (y, -oo, oo)).has_empty_sequence is True + assert Sum(1, (y, -oo, oo), (x, 3, 2)).has_empty_sequence is True + assert Sum(1, (x, oo, oo)).has_empty_sequence is False + + +def test_empty_sequence(): + assert Product(x*y, (x, -oo, oo), (y, 1, 0)).doit() == 1 + assert Product(x*y, (y, 1, 0), (x, -oo, oo)).doit() == 1 + assert Sum(x, (x, -oo, oo), (y, 1, 0)).doit() == 0 + assert Sum(x, (y, 1, 0), (x, -oo, oo)).doit() == 0 + + +def test_issue_8016(): + k = Symbol('k', integer=True) + n, m = symbols('n, m', integer=True, positive=True) + s = Sum(binomial(m, k)*binomial(m, n - k)*(-1)**k, (k, 0, n)) + assert s.doit().simplify() == \ + cos(pi*n/2)*gamma(m + 1)/gamma(n/2 + 1)/gamma(m - n/2 + 1) + + +def test_issue_14313(): + assert Sum(S.Half**floor(n/2), (n, 1, oo)).is_convergent() + + +def test_issue_14563(): + # The assertion was failing due to no assumptions methods in Sums and Product + assert 1 % Sum(1, (x, 0, 1)) == 1 + + +def test_issue_16735(): + assert Sum(5**n/gamma(n+1), (n, 1, oo)).is_convergent() is S.true + + +def test_issue_14871(): + assert Sum((Rational(1, 10))**n*rf(0, n)/factorial(n), (n, 0, oo)).rewrite(factorial).doit() == 1 + + +def test_issue_17165(): + n = symbols("n", integer=True) + x = symbols('x') + s = (x*Sum(x**n, (n, -1, oo))) + ssimp = s.doit().simplify() + + assert ssimp == Piecewise((-1/(x - 1), (x > -1) & (x < 1)), + (x*Sum(x**n, (n, -1, oo)), True)), ssimp + assert ssimp.simplify() == ssimp + + +def test_issue_19379(): + assert Sum(factorial(n)/factorial(n + 2), (n, 1, oo)).is_convergent() is S.true + + +def test_issue_20777(): + assert Sum(exp(x*sin(n/m)), (n, 1, m)).doit() == Sum(exp(x*sin(n/m)), (n, 1, m)) + + +def test__dummy_with_inherited_properties_concrete(): + x = Symbol('x') + + from sympy.core.containers import Tuple + d = _dummy_with_inherited_properties_concrete(Tuple(x, 0, 5)) + assert d.is_real + assert d.is_integer + assert d.is_nonnegative + assert d.is_extended_nonnegative + + d = _dummy_with_inherited_properties_concrete(Tuple(x, 1, 9)) + assert d.is_real + assert d.is_integer + assert d.is_positive + assert d.is_odd is None + + d = _dummy_with_inherited_properties_concrete(Tuple(x, -5, 5)) + assert d.is_real + assert d.is_integer + assert d.is_positive is None + assert d.is_extended_nonnegative is None + assert d.is_odd is None + + d = _dummy_with_inherited_properties_concrete(Tuple(x, -1.5, 1.5)) + assert d.is_real + assert d.is_integer is None + assert d.is_positive is None + assert d.is_extended_nonnegative is None + + N = Symbol('N', integer=True, positive=True) + d = _dummy_with_inherited_properties_concrete(Tuple(x, 2, N)) + assert d.is_real + assert d.is_positive + assert d.is_integer + + # Return None if no assumptions are added + N = Symbol('N', integer=True, positive=True) + d = _dummy_with_inherited_properties_concrete(Tuple(N, 2, 4)) + assert d is None + + x = Symbol('x', negative=True) + raises(InconsistentAssumptions, + lambda: _dummy_with_inherited_properties_concrete(Tuple(x, 1, 5))) + + +def test_matrixsymbol_summation_numerical_limits(): + A = MatrixSymbol('A', 3, 3) + n = Symbol('n', integer=True) + + assert Sum(A**n, (n, 0, 2)).doit() == Identity(3) + A + A**2 + assert Sum(A, (n, 0, 2)).doit() == 3*A + assert Sum(n*A, (n, 0, 2)).doit() == 3*A + + B = Matrix([[0, n, 0], [-1, 0, 0], [0, 0, 2]]) + ans = Matrix([[0, 6, 0], [-4, 0, 0], [0, 0, 8]]) + 4*A + assert Sum(A+B, (n, 0, 3)).doit() == ans + ans = A*Matrix([[0, 6, 0], [-4, 0, 0], [0, 0, 8]]) + assert Sum(A*B, (n, 0, 3)).doit() == ans + + ans = (A**2*Matrix([[-2, 0, 0], [0,-2, 0], [0, 0, 4]]) + + A**3*Matrix([[0, -9, 0], [3, 0, 0], [0, 0, 8]]) + + A*Matrix([[0, 1, 0], [-1, 0, 0], [0, 0, 2]])) + assert Sum(A**n*B**n, (n, 1, 3)).doit() == ans + + +def test_issue_21651(): + i = Symbol('i') + a = Sum(floor(2*2**(-i)), (i, S.One, 2)) + assert a.doit() == S.One + + +@XFAIL +def test_matrixsymbol_summation_symbolic_limits(): + N = Symbol('N', integer=True, positive=True) + + A = MatrixSymbol('A', 3, 3) + n = Symbol('n', integer=True) + assert Sum(A, (n, 0, N)).doit() == (N+1)*A + assert Sum(n*A, (n, 0, N)).doit() == (N**2/2+N/2)*A + + +def test_summation_by_residues(): + x = Symbol('x') + + # Examples from Nakhle H. Asmar, Loukas Grafakos, + # Complex Analysis with Applications + assert eval_sum_residue(1 / (x**2 + 1), (x, -oo, oo)) == pi/tanh(pi) + assert eval_sum_residue(1 / x**6, (x, S(1), oo)) == pi**6/945 + assert eval_sum_residue(1 / (x**2 + 9), (x, -oo, oo)) == pi/(3*tanh(3*pi)) + assert eval_sum_residue(1 / (x**2 + 1)**2, (x, -oo, oo)).cancel() == \ + (-pi**2*tanh(pi)**2 + pi*tanh(pi) + pi**2)/(2*tanh(pi)**2) + assert eval_sum_residue(x**2 / (x**2 + 1)**2, (x, -oo, oo)).cancel() == \ + (-pi**2 + pi*tanh(pi) + pi**2*tanh(pi)**2)/(2*tanh(pi)**2) + assert eval_sum_residue(1 / (4*x**2 - 1), (x, -oo, oo)) == 0 + assert eval_sum_residue(x**2 / (x**2 - S(1)/4)**2, (x, -oo, oo)) == pi**2/2 + assert eval_sum_residue(1 / (4*x**2 - 1)**2, (x, -oo, oo)) == pi**2/8 + assert eval_sum_residue(1 / ((x - S(1)/2)**2 + 1), (x, -oo, oo)) == pi*tanh(pi) + assert eval_sum_residue(1 / x**2, (x, S(1), oo)) == pi**2/6 + assert eval_sum_residue(1 / x**4, (x, S(1), oo)) == pi**4/90 + assert eval_sum_residue(1 / x**2 / (x**2 + 4), (x, S(1), oo)) == \ + -pi*(-pi/12 - 1/(16*pi) + 1/(8*tanh(2*pi)))/2 + + # Some examples made from 1 / (x**2 + 1) + assert eval_sum_residue(1 / (x**2 + 1), (x, S(0), oo)) == \ + S(1)/2 + pi/(2*tanh(pi)) + assert eval_sum_residue(1 / (x**2 + 1), (x, S(1), oo)) == \ + -S(1)/2 + pi/(2*tanh(pi)) + assert eval_sum_residue(1 / (x**2 + 1), (x, S(-1), oo)) == \ + 1 + pi/(2*tanh(pi)) + assert eval_sum_residue((-1)**x / (x**2 + 1), (x, -oo, oo)) == \ + pi/sinh(pi) + assert eval_sum_residue((-1)**x / (x**2 + 1), (x, S(0), oo)) == \ + pi/(2*sinh(pi)) + S(1)/2 + assert eval_sum_residue((-1)**x / (x**2 + 1), (x, S(1), oo)) == \ + -S(1)/2 + pi/(2*sinh(pi)) + assert eval_sum_residue((-1)**x / (x**2 + 1), (x, S(-1), oo)) == \ + pi/(2*sinh(pi)) + + # Some examples made from shifting of 1 / (x**2 + 1) + assert eval_sum_residue(1 / (x**2 + 2*x + 2), (x, S(-1), oo)) == S(1)/2 + pi/(2*tanh(pi)) + assert eval_sum_residue(1 / (x**2 + 4*x + 5), (x, S(-2), oo)) == S(1)/2 + pi/(2*tanh(pi)) + assert eval_sum_residue(1 / (x**2 - 2*x + 2), (x, S(1), oo)) == S(1)/2 + pi/(2*tanh(pi)) + assert eval_sum_residue(1 / (x**2 - 4*x + 5), (x, S(2), oo)) == S(1)/2 + pi/(2*tanh(pi)) + assert eval_sum_residue((-1)**x * -1 / (x**2 + 2*x + 2), (x, S(-1), oo)) == S(1)/2 + pi/(2*sinh(pi)) + assert eval_sum_residue((-1)**x * -1 / (x**2 -2*x + 2), (x, S(1), oo)) == S(1)/2 + pi/(2*sinh(pi)) + + # Some examples made from 1 / x**2 + assert eval_sum_residue(1 / x**2, (x, S(2), oo)) == -1 + pi**2/6 + assert eval_sum_residue(1 / x**2, (x, S(3), oo)) == -S(5)/4 + pi**2/6 + assert eval_sum_residue((-1)**x / x**2, (x, S(1), oo)) == -pi**2/12 + assert eval_sum_residue((-1)**x / x**2, (x, S(2), oo)) == 1 - pi**2/12 + + +@slow +def test_summation_by_residues_failing(): + x = Symbol('x') + + # Failing because of the bug in residue computation + assert eval_sum_residue(x**2 / (x**4 + 1), (x, S(1), oo)) + assert eval_sum_residue(1 / ((x - 1)*(x - 2) + 1), (x, -oo, oo)) != 0 + + +def test_process_limits(): + from sympy.concrete.expr_with_limits import _process_limits + + # these should be (x, Range(3)) not Range(3) + raises(ValueError, lambda: _process_limits( + Range(3), discrete=True)) + raises(ValueError, lambda: _process_limits( + Range(3), discrete=False)) + # these should be (x, union) not union + # (but then we would get a TypeError because we don't + # handle non-contiguous sets: see below use of `union`) + union = Or(x < 1, x > 3).as_set() + raises(ValueError, lambda: _process_limits( + union, discrete=True)) + raises(ValueError, lambda: _process_limits( + union, discrete=False)) + + # error not triggered if not needed + assert _process_limits((x, 1, 2)) == ([(x, 1, 2)], 1) + + # this equivalence is used to detect Reals in _process_limits + assert isinstance(S.Reals, Interval) + + C = Integral # continuous limits + assert C(x, x >= 5) == C(x, (x, 5, oo)) + assert C(x, x < 3) == C(x, (x, -oo, 3)) + ans = C(x, (x, 0, 3)) + assert C(x, And(x >= 0, x < 3)) == ans + assert C(x, (x, Interval.Ropen(0, 3))) == ans + raises(TypeError, lambda: C(x, (x, Range(3)))) + + # discrete limits + for D in (Sum, Product): + r, ans = Range(3, 10, 2), D(2*x + 3, (x, 0, 3)) + assert D(x, (x, r)) == ans + assert D(x, (x, r.reversed)) == ans + r, ans = Range(3, oo, 2), D(2*x + 3, (x, 0, oo)) + assert D(x, (x, r)) == ans + assert D(x, (x, r.reversed)) == ans + r, ans = Range(-oo, 5, 2), D(3 - 2*x, (x, 0, oo)) + assert D(x, (x, r)) == ans + assert D(x, (x, r.reversed)) == ans + raises(TypeError, lambda: D(x, x > 0)) + raises(ValueError, lambda: D(x, Interval(1, 3))) + raises(NotImplementedError, lambda: D(x, (x, union))) + + +def test_pr_22677(): + b = Symbol('b', integer=True, positive=True) + assert Sum(1/x**2,(x, 0, b)).doit() == Sum(x**(-2), (x, 0, b)) + assert Sum(1/(x - b)**2,(x, 0, b-1)).doit() == Sum( + (-b + x)**(-2), (x, 0, b - 1)) + + +def test_issue_23952(): + p, q = symbols("p q", real=True, nonnegative=True) + k1, k2 = symbols("k1 k2", integer=True, nonnegative=True) + n = Symbol("n", integer=True, positive=True) + expr = Sum(abs(k1 - k2)*p**k1 *(1 - q)**(n - k2), + (k1, 0, n), (k2, 0, n)) + assert expr.subs(p,0).subs(q,1).subs(n, 3).doit() == 3 diff --git a/phivenv/Lib/site-packages/sympy/conftest.py b/phivenv/Lib/site-packages/sympy/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..788ec7ed8a38de422b76c4ac19057513670d2d38 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/conftest.py @@ -0,0 +1,96 @@ +import sys + +sys._running_pytest = True # type: ignore +from sympy.external.importtools import version_tuple + +import pytest +from sympy.core.cache import clear_cache, USE_CACHE +from sympy.external.gmpy import GROUND_TYPES +from sympy.utilities.misc import ARCH +import re + +try: + import hypothesis + + hypothesis.settings.register_profile("sympy_hypothesis_profile", deadline=None) + hypothesis.settings.load_profile("sympy_hypothesis_profile") +except ImportError: + raise ImportError( + "hypothesis is a required dependency to run the SymPy test suite. " + "Install it with 'pip install hypothesis' or 'conda install -c conda-forge hypothesis'" + ) + + +sp = re.compile(r"([0-9]+)/([1-9][0-9]*)") + + +def process_split(config, items): + split = config.getoption("--split") + if not split: + return + m = sp.match(split) + if not m: + raise ValueError( + "split must be a string of the form a/b " "where a and b are ints." + ) + i, t = map(int, m.groups()) + start, end = (i - 1) * len(items) // t, i * len(items) // t + + if i < t: + # remove elements from end of list first + del items[end:] + del items[:start] + + +def pytest_report_header(config): + s = "architecture: %s\n" % ARCH + s += "cache: %s\n" % USE_CACHE + version = "" + if GROUND_TYPES == "gmpy": + import gmpy2 + + version = gmpy2.version() + elif GROUND_TYPES == "flint": + try: + from flint import __version__ + except ImportError: + version = "unknown" + else: + version = f'(python-flint=={__version__})' + s += "ground types: %s %s\n" % (GROUND_TYPES, version) + return s + + +def pytest_terminal_summary(terminalreporter): + if terminalreporter.stats.get("error", None) or terminalreporter.stats.get( + "failed", None + ): + terminalreporter.write_sep(" ", "DO *NOT* COMMIT!", red=True, bold=True) + + +def pytest_addoption(parser): + parser.addoption("--split", action="store", default="", help="split tests") + + +def pytest_collection_modifyitems(config, items): + """pytest hook.""" + # handle splits + process_split(config, items) + + +@pytest.fixture(autouse=True, scope="module") +def file_clear_cache(): + clear_cache() + + +@pytest.fixture(autouse=True, scope="module") +def check_disabled(request): + if getattr(request.module, "disabled", False): + pytest.skip("test requirements not met.") + elif getattr(request.module, "ipython", False): + # need to check version and options for ipython tests + if ( + version_tuple(pytest.__version__) < version_tuple("2.6.3") + and pytest.config.getvalue("-s") != "no" + ): + pytest.skip("run py.test with -s or upgrade to newer version.") diff --git a/phivenv/Lib/site-packages/sympy/core/__init__.py b/phivenv/Lib/site-packages/sympy/core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4c03c9fc11479bb6d93a3bff3dfd0992ef994a19 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/__init__.py @@ -0,0 +1,103 @@ +"""Core module. Provides the basic operations needed in sympy. +""" + +from .sympify import sympify, SympifyError +from .cache import cacheit +from .assumptions import assumptions, check_assumptions, failing_assumptions, common_assumptions +from .basic import Basic, Atom +from .singleton import S +from .expr import Expr, AtomicExpr, UnevaluatedExpr +from .symbol import Symbol, Wild, Dummy, symbols, var +from .numbers import Number, Float, Rational, Integer, NumberSymbol, \ + RealNumber, igcd, ilcm, seterr, E, I, nan, oo, pi, zoo, \ + AlgebraicNumber, comp, mod_inverse +from .power import Pow +from .intfunc import integer_nthroot, integer_log, num_digits, trailing +from .mul import Mul, prod +from .add import Add +from .mod import Mod +from .relational import ( Rel, Eq, Ne, Lt, Le, Gt, Ge, + Equality, GreaterThan, LessThan, Unequality, StrictGreaterThan, + StrictLessThan ) +from .multidimensional import vectorize +from .function import Lambda, WildFunction, Derivative, diff, FunctionClass, \ + Function, Subs, expand, PoleError, count_ops, \ + expand_mul, expand_log, expand_func, \ + expand_trig, expand_complex, expand_multinomial, nfloat, \ + expand_power_base, expand_power_exp, arity +from .evalf import PrecisionExhausted, N +from .containers import Tuple, Dict +from .exprtools import gcd_terms, factor_terms, factor_nc +from .parameters import evaluate +from .kind import UndefinedKind, NumberKind, BooleanKind +from .traversal import preorder_traversal, bottom_up, use, postorder_traversal +from .sorting import default_sort_key, ordered + +# expose singletons +Catalan = S.Catalan +EulerGamma = S.EulerGamma +GoldenRatio = S.GoldenRatio +TribonacciConstant = S.TribonacciConstant + +__all__ = [ + 'sympify', 'SympifyError', + + 'cacheit', + + 'assumptions', 'check_assumptions', 'failing_assumptions', + 'common_assumptions', + + 'Basic', 'Atom', + + 'S', + + 'Expr', 'AtomicExpr', 'UnevaluatedExpr', + + 'Symbol', 'Wild', 'Dummy', 'symbols', 'var', + + 'Number', 'Float', 'Rational', 'Integer', 'NumberSymbol', 'RealNumber', + 'igcd', 'ilcm', 'seterr', 'E', 'I', 'nan', 'oo', 'pi', 'zoo', + 'AlgebraicNumber', 'comp', 'mod_inverse', + + 'Pow', + + 'integer_nthroot', 'integer_log', 'num_digits', 'trailing', + + 'Mul', 'prod', + + 'Add', + + 'Mod', + + 'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', 'Equality', 'GreaterThan', + 'LessThan', 'Unequality', 'StrictGreaterThan', 'StrictLessThan', + + 'vectorize', + + 'Lambda', 'WildFunction', 'Derivative', 'diff', 'FunctionClass', + 'Function', 'Subs', 'expand', 'PoleError', 'count_ops', 'expand_mul', + 'expand_log', 'expand_func', 'expand_trig', 'expand_complex', + 'expand_multinomial', 'nfloat', 'expand_power_base', 'expand_power_exp', + 'arity', + + 'PrecisionExhausted', 'N', + + 'evalf', # The module? + + 'Tuple', 'Dict', + + 'gcd_terms', 'factor_terms', 'factor_nc', + + 'evaluate', + + 'Catalan', + 'EulerGamma', + 'GoldenRatio', + 'TribonacciConstant', + + 'UndefinedKind', 'NumberKind', 'BooleanKind', + + 'preorder_traversal', 'bottom_up', 'use', 'postorder_traversal', + + 'default_sort_key', 'ordered', +] diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c6885bfc6dfd9eedd1330e1d41e9235332224a0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/_print_helpers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/_print_helpers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef0ef2703600251516763207050f2595e65865a4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/_print_helpers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/add.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/add.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..538633ccdaee1ad89353e95865bbe6399e0ecb25 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/add.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/alphabets.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/alphabets.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4133b946b110653a3fe5e517bedf04f51f66bb8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/alphabets.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/assumptions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/assumptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5d6328c803d22b2e56a04e854f7964939ffc436 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/assumptions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/assumptions_generated.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/assumptions_generated.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..893444b0719477bb72d09a6a66308e4f8847bcbe Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/assumptions_generated.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/backend.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/backend.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4caccd63fcb5e9018e9c8f1ba04c210a3a1d81a6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/backend.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/basic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/basic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f927ea7200986ba57371f529dcaf6ad1150a9bae Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/basic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/cache.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/cache.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..077ddb87da8e9659b3dc13f447eeb227a6c4715c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/cache.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/compatibility.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/compatibility.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f348f4e7477306f268e8fe969b3ccdf47a64a9a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/compatibility.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/containers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/containers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f1c0ee986363954e0f496f88c284ff42bc41bbb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/containers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/core.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/core.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..680bf785519d21ed177ff4bda0182e38154dbd09 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/core.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/coreerrors.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/coreerrors.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..410aa50e744229e01b96aebd65e9c57458e321ab Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/coreerrors.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/decorators.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/decorators.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e690eeb02e6729f3ee534c3f883960caa1478fd Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/decorators.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/evalf.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/evalf.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cc4aa92538d04bf42656e75661d1cb6944c45fa Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/evalf.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/exprtools.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/exprtools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6e47cb2dd76d18f1ebcb22360abc42e3363ba9d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/exprtools.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/facts.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/facts.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b0605d33757b1d89a7c70fabab12c5137d5c6a8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/facts.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/intfunc.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/intfunc.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f36ea5ea72eec99b7dc0f55be24ab34065d0d7eb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/intfunc.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/kind.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/kind.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24744e38a82334d789eebbc9d5bd2bfdaffe6887 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/kind.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/logic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/logic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebe16257ffd77b83cd815d7d689865b32c280902 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/logic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/mod.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/mod.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83b8c9d2272a33549d06dc0d90eaba9673bdda94 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/mod.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/mul.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/mul.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26af53959272a94ef1ec55c7f242a2d139035c0d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/mul.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/multidimensional.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/multidimensional.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..699f66028b34888ea117507d1410f1f05b53fa58 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/multidimensional.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/operations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/operations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..331377ae4962f7098505ad583404368f8d28ed3c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/operations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/parameters.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/parameters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2697a65163052c21ee9017f6e9cf5fd640fc1fe2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/parameters.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/power.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/power.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2de95f9bf0f0f92f85f72e74e2261ab19a840177 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/power.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/random.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/random.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bb2d063799840c8f212decdea8d4ce787b0867b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/random.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/relational.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/relational.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..123c19515e5fbe44ff377d45c26fbc04d7cc60e4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/relational.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/rules.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/rules.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5be0c7220959032153a9bd4bce07087fc1e8be4b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/rules.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/singleton.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/singleton.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..169e319aac8fca4ef4a997c467c09abc50ff7689 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/singleton.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/sorting.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/sorting.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6648f891b7627d4d34a32d7f2a5f378145b81ad2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/sorting.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/symbol.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/symbol.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb292763d9518a4b0a3c3d7b9e56279d180f155d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/symbol.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/sympify.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/sympify.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5e0358eabb1d7c3563355c2ff01dfb514f56712 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/sympify.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/trace.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/trace.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49f1ae7e15c08b258c9da13651836a5099854994 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/trace.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/__pycache__/traversal.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/__pycache__/traversal.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ea9bcc4f156b8f75f6c41d1470bdc052dc2d637 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/__pycache__/traversal.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/_print_helpers.py b/phivenv/Lib/site-packages/sympy/core/_print_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..d704ed220d444e2d8510b280dca85c8ae6149d4c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/_print_helpers.py @@ -0,0 +1,65 @@ +""" +Base class to provide str and repr hooks that `init_printing` can overwrite. + +This is exposed publicly in the `printing.defaults` module, +but cannot be defined there without causing circular imports. +""" + +class Printable: + """ + The default implementation of printing for SymPy classes. + + This implements a hack that allows us to print elements of built-in + Python containers in a readable way. Natively Python uses ``repr()`` + even if ``str()`` was explicitly requested. Mix in this trait into + a class to get proper default printing. + + This also adds support for LaTeX printing in jupyter notebooks. + """ + + # Since this class is used as a mixin we set empty slots. That means that + # instances of any subclasses that use slots will not need to have a + # __dict__. + __slots__ = () + + # Note, we always use the default ordering (lex) in __str__ and __repr__, + # regardless of the global setting. See issue 5487. + def __str__(self): + from sympy.printing.str import sstr + return sstr(self, order=None) + + __repr__ = __str__ + + def _repr_disabled(self): + """ + No-op repr function used to disable jupyter display hooks. + + When :func:`sympy.init_printing` is used to disable certain display + formats, this function is copied into the appropriate ``_repr_*_`` + attributes. + + While we could just set the attributes to `None``, doing it this way + allows derived classes to call `super()`. + """ + return None + + # We don't implement _repr_png_ here because it would add a large amount of + # data to any notebook containing SymPy expressions, without adding + # anything useful to the notebook. It can still enabled manually, e.g., + # for the qtconsole, with init_printing(). + _repr_png_ = _repr_disabled + + _repr_svg_ = _repr_disabled + + def _repr_latex_(self): + """ + IPython/Jupyter LaTeX printing + + To change the behavior of this (e.g., pass in some settings to LaTeX), + use init_printing(). init_printing() will also enable LaTeX printing + for built in numeric types like ints and container types that contain + SymPy objects, like lists and dictionaries of expressions. + """ + from sympy.printing.latex import latex + s = latex(self, mode='plain') + return "$\\displaystyle %s$" % s diff --git a/phivenv/Lib/site-packages/sympy/core/add.py b/phivenv/Lib/site-packages/sympy/core/add.py new file mode 100644 index 0000000000000000000000000000000000000000..2d280f3286c34cd0dea14bf61194ed03ee6bf6ae --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/add.py @@ -0,0 +1,1280 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar +from collections import defaultdict +from functools import reduce +from operator import attrgetter +from .basic import _args_sortkey +from .parameters import global_parameters +from .logic import _fuzzy_group, fuzzy_or, fuzzy_not +from .singleton import S +from .operations import AssocOp, AssocOpDispatcher +from .cache import cacheit +from .intfunc import ilcm, igcd +from .expr import Expr +from .kind import UndefinedKind +from sympy.utilities.iterables import is_sequence, sift + + +if TYPE_CHECKING: + from sympy.core.numbers import Number + from sympy.series.order import Order + + +def _could_extract_minus_sign(expr): + # assume expr is Add-like + # We choose the one with less arguments with minus signs + negative_args = sum(1 for i in expr.args + if i.could_extract_minus_sign()) + positive_args = len(expr.args) - negative_args + if positive_args > negative_args: + return False + elif positive_args < negative_args: + return True + # choose based on .sort_key() to prefer + # x - 1 instead of 1 - x and + # 3 - sqrt(2) instead of -3 + sqrt(2) + return bool(expr.sort_key() < (-expr).sort_key()) + + +def _addsort(args): + # in-place sorting of args + args.sort(key=_args_sortkey) + + +def _unevaluated_Add(*args): + """Return a well-formed unevaluated Add: Numbers are collected and + put in slot 0 and args are sorted. Use this when args have changed + but you still want to return an unevaluated Add. + + Examples + ======== + + >>> from sympy.core.add import _unevaluated_Add as uAdd + >>> from sympy import S, Add + >>> from sympy.abc import x, y + >>> a = uAdd(*[S(1.0), x, S(2)]) + >>> a.args[0] + 3.00000000000000 + >>> a.args[1] + x + + Beyond the Number being in slot 0, there is no other assurance of + order for the arguments since they are hash sorted. So, for testing + purposes, output produced by this in some other function can only + be tested against the output of this function or as one of several + options: + + >>> opts = (Add(x, y, evaluate=False), Add(y, x, evaluate=False)) + >>> a = uAdd(x, y) + >>> assert a in opts and a == uAdd(x, y) + >>> uAdd(x + 1, x + 2) + x + x + 3 + """ + args = list(args) + newargs = [] + co = S.Zero + while args: + a = args.pop() + if a.is_Add: + # this will keep nesting from building up + # so that x + (x + 1) -> x + x + 1 (3 args) + args.extend(a.args) + elif a.is_Number: + co += a + else: + newargs.append(a) + _addsort(newargs) + if co: + newargs.insert(0, co) + return Add._from_args(newargs) + + +class Add(Expr, AssocOp): + """ + Expression representing addition operation for algebraic group. + + .. deprecated:: 1.7 + + Using arguments that aren't subclasses of :class:`~.Expr` in core + operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is + deprecated. See :ref:`non-expr-args-deprecated` for details. + + Every argument of ``Add()`` must be ``Expr``. Infix operator ``+`` + on most scalar objects in SymPy calls this class. + + Another use of ``Add()`` is to represent the structure of abstract + addition so that its arguments can be substituted to return different + class. Refer to examples section for this. + + ``Add()`` evaluates the argument unless ``evaluate=False`` is passed. + The evaluation logic includes: + + 1. Flattening + ``Add(x, Add(y, z))`` -> ``Add(x, y, z)`` + + 2. Identity removing + ``Add(x, 0, y)`` -> ``Add(x, y)`` + + 3. Coefficient collecting by ``.as_coeff_Mul()`` + ``Add(x, 2*x)`` -> ``Mul(3, x)`` + + 4. Term sorting + ``Add(y, x, 2)`` -> ``Add(2, x, y)`` + + If no argument is passed, identity element 0 is returned. If single + element is passed, that element is returned. + + Note that ``Add(*args)`` is more efficient than ``sum(args)`` because + it flattens the arguments. ``sum(a, b, c, ...)`` recursively adds the + arguments as ``a + (b + (c + ...))``, which has quadratic complexity. + On the other hand, ``Add(a, b, c, d)`` does not assume nested + structure, making the complexity linear. + + Since addition is group operation, every argument should have the + same :obj:`sympy.core.kind.Kind()`. + + Examples + ======== + + >>> from sympy import Add, I + >>> from sympy.abc import x, y + >>> Add(x, 1) + x + 1 + >>> Add(x, x) + 2*x + >>> 2*x**2 + 3*x + I*y + 2*y + 2*x/5 + 1.0*y + 1 + 2*x**2 + 17*x/5 + 3.0*y + I*y + 1 + + If ``evaluate=False`` is passed, result is not evaluated. + + >>> Add(1, 2, evaluate=False) + 1 + 2 + >>> Add(x, x, evaluate=False) + x + x + + ``Add()`` also represents the general structure of addition operation. + + >>> from sympy import MatrixSymbol + >>> A,B = MatrixSymbol('A', 2,2), MatrixSymbol('B', 2,2) + >>> expr = Add(x,y).subs({x:A, y:B}) + >>> expr + A + B + >>> type(expr) + + + Note that the printers do not display in args order. + + >>> Add(x, 1) + x + 1 + >>> Add(x, 1).args + (1, x) + + See Also + ======== + + MatAdd + + """ + + __slots__ = () + + is_Add = True + + _args_type = Expr + + identity: ClassVar[Expr] + + if TYPE_CHECKING: + + def __new__(cls, *args: Expr | complex, evaluate: bool=True) -> Expr: # type: ignore + ... + + @property + def args(self) -> tuple[Expr, ...]: + ... + + @classmethod + def flatten(cls, seq: list[Expr]) -> tuple[list[Expr], list[Expr], None]: + """ + Takes the sequence "seq" of nested Adds and returns a flatten list. + + Returns: (commutative_part, noncommutative_part, order_symbols) + + Applies associativity, all terms are commutable with respect to + addition. + + NB: the removal of 0 is already handled by AssocOp.__new__ + + See Also + ======== + + sympy.core.mul.Mul.flatten + + """ + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.matrices.expressions import MatrixExpr + from sympy.tensor.tensor import TensExpr, TensAdd + rv = None + if len(seq) == 2: + a, b = seq + if b.is_Rational: + a, b = b, a + if a.is_Rational: + if b.is_Mul: + rv = [a, b], [], None + if rv: + if all(s.is_commutative for s in rv[0]): + return rv + return [], rv[0], None + + # term -> coeff + # e.g. x**2 -> 5 for ... + 5*x**2 + ... + terms: dict[Expr, Number] = {} + + # coefficient (Number or zoo) to always be in slot 0 + # e.g. 3 + ... + coeff: Expr = S.Zero + + order_factors: list[Order] = [] + + extra: list[MatrixExpr] = [] + + for o in seq: + + # O(x) + if o.is_Order: + if o.expr.is_zero: # type: ignore + continue + if any(o1.contains(o) for o1 in order_factors): + continue + order_factors = [o1 for o1 in order_factors if not o.contains(o1)] # type: ignore + order_factors = [o] + order_factors # type: ignore + continue + + # 3 or NaN + elif o.is_Number: + if (o is S.NaN or coeff is S.ComplexInfinity and + o.is_finite is False) and not extra: + # we know for sure the result will be nan + return [S.NaN], [], None + if coeff.is_Number or isinstance(coeff, AccumBounds): + coeff += o + if coeff is S.NaN and not extra: + # we know for sure the result will be nan + return [S.NaN], [], None + continue + + elif isinstance(o, AccumBounds): + coeff = o.__add__(coeff) + continue + + elif isinstance(o, MatrixExpr): + # can't add 0 to Matrix so make sure coeff is not 0 + extra.append(o) + continue + + elif isinstance(o, TensExpr): + coeff = TensAdd(o, coeff).doit(deep=False) + continue + + elif o is S.ComplexInfinity: + if coeff.is_finite is False and not extra: + # we know for sure the result will be nan + return [S.NaN], [], None + coeff = S.ComplexInfinity + continue + + # Add([...]) + elif o.is_Add: + # NB: here we assume Add is always commutative + o_args: tuple[Expr, ...] = o.args # type: ignore + seq.extend(o_args) # TODO zerocopy? + continue + + # Mul([...]) + elif o.is_Mul: + c, s = o.as_coeff_Mul() + + # check for unevaluated Pow, e.g. 2**3 or 2**(-1/2) + elif o.is_Pow: + b, e = o.as_base_exp() + if b.is_Number and (e.is_Integer or + (e.is_Rational and e.is_negative)): + seq.append(b**e) + continue + c, s = S.One, o + + else: + # everything else + c = S.One + s = o + + # now we have: + # o = c*s, where + # + # c is a Number + # s is an expression with number factor extracted + # let's collect terms with the same s, so e.g. + # 2*x**2 + 3*x**2 -> 5*x**2 + if s in terms: + terms[s] += c + if terms[s] is S.NaN and not extra: + # we know for sure the result will be nan + return [S.NaN], [], None + else: + terms[s] = c + + # now let's construct new args: + # [2*x**2, x**3, 7*x**4, pi, ...] + newseq = [] + noncommutative = False + for s, c in terms.items(): + # 0*s + if c.is_zero: + continue + # 1*s + elif c is S.One: + newseq.append(s) + # c*s + else: + if s.is_Mul: + # Mul, already keeps its arguments in perfect order. + # so we can simply put c in slot0 and go the fast way. + # + # XXX: This breaks VectorMul unless it overrides + # _new_rawargs + cs = s._new_rawargs(*((c,) + s.args)) # type: ignore + newseq.append(cs) + elif s.is_Add: + # we just re-create the unevaluated Mul + newseq.append(Mul(c, s, evaluate=False)) + else: + # alternatively we have to call all Mul's machinery (slow) + newseq.append(Mul(c, s)) + + noncommutative = noncommutative or not s.is_commutative + + # oo, -oo + if coeff is S.Infinity: + newseq = [f for f in newseq if not (f.is_extended_nonnegative or f.is_real)] + + elif coeff is S.NegativeInfinity: + newseq = [f for f in newseq if not (f.is_extended_nonpositive or f.is_real)] + + if coeff is S.ComplexInfinity: + # zoo might be + # infinite_real + finite_im + # finite_real + infinite_im + # infinite_real + infinite_im + # addition of a finite real or imaginary number won't be able to + # change the zoo nature; adding an infinite qualtity would result + # in a NaN condition if it had sign opposite of the infinite + # portion of zoo, e.g., infinite_real - infinite_real. + newseq = [c for c in newseq if not (c.is_finite and + c.is_extended_real is not None)] + + # process O(x) + if order_factors: + newseq2 = [] + for t in newseq: + # x + O(x) -> O(x) + if not any(o.contains(t) for o in order_factors): + newseq2.append(t) + newseq = newseq2 + order_factors # type: ignore + # 1 + O(1) -> O(1) + for o in order_factors: + if o.contains(coeff): + coeff = S.Zero + break + + # order args canonically + _addsort(newseq) + + # current code expects coeff to be first + if coeff is not S.Zero: + newseq.insert(0, coeff) + + if extra: + newseq += extra + noncommutative = True + + # we are done + if noncommutative: + return [], newseq, None + else: + return newseq, [], None + + @classmethod + def class_key(cls): + return 3, 1, cls.__name__ + + @property + def kind(self): + k = attrgetter('kind') + kinds = map(k, self.args) + kinds = frozenset(kinds) + if len(kinds) != 1: + # Since addition is group operator, kind must be same. + # We know that this is unexpected signature, so return this. + result = UndefinedKind + else: + result, = kinds + return result + + def could_extract_minus_sign(self): + return _could_extract_minus_sign(self) + + @cacheit + def as_coeff_add(self, *deps): + """ + Returns a tuple (coeff, args) where self is treated as an Add and coeff + is the Number term and args is a tuple of all other terms. + + Examples + ======== + + >>> from sympy.abc import x + >>> (7 + 3*x).as_coeff_add() + (7, (3*x,)) + >>> (7*x).as_coeff_add() + (0, (7*x,)) + """ + if deps: + l1, l2 = sift(self.args, lambda x: x.has_free(*deps), binary=True) + return self._new_rawargs(*l2), tuple(l1) + coeff, notrat = self.args[0].as_coeff_add() + if coeff is not S.Zero: + return coeff, notrat + self.args[1:] + return S.Zero, self.args + + def as_coeff_Add(self, rational=False, deps=None) -> tuple[Number, Expr]: + """ + Efficiently extract the coefficient of a summation. + """ + coeff, args = self.args[0], self.args[1:] + + if coeff.is_Number and not rational or coeff.is_Rational: + return coeff, self._new_rawargs(*args) # type: ignore + return S.Zero, self + + # Note, we intentionally do not implement Add.as_coeff_mul(). Rather, we + # let Expr.as_coeff_mul() just always return (S.One, self) for an Add. See + # issue 5524. + + def _eval_power(self, expt): + from .evalf import pure_complex + from .relational import is_eq + if len(self.args) == 2 and any(_.is_infinite for _ in self.args): + if expt.is_zero is False and is_eq(expt, S.One) is False: + # looking for literal a + I*b + a, b = self.args + if a.coeff(S.ImaginaryUnit): + a, b = b, a + ico = b.coeff(S.ImaginaryUnit) + if ico and ico.is_extended_real and a.is_extended_real: + if expt.is_extended_negative: + return S.Zero + if expt.is_extended_positive: + return S.ComplexInfinity + return + if expt.is_Rational and self.is_number: + ri = pure_complex(self) + if ri: + r, i = ri + if expt.q == 2: + from sympy.functions.elementary.miscellaneous import sqrt + D = sqrt(r**2 + i**2) + if D.is_Rational: + from .exprtools import factor_terms + from sympy.functions.elementary.complexes import sign + from .function import expand_multinomial + # (r, i, D) is a Pythagorean triple + root = sqrt(factor_terms((D - r)/2))**expt.p + return root*expand_multinomial(( + # principle value + (D + r)/abs(i) + sign(i)*S.ImaginaryUnit)**expt.p) + elif expt == -1: + return _unevaluated_Mul( + r - i*S.ImaginaryUnit, + 1/(r**2 + i**2)) + + @cacheit + def _eval_derivative(self, s): + return self.func(*[a.diff(s) for a in self.args]) + + def _eval_nseries(self, x, n, logx, cdir=0): + terms = [t.nseries(x, n=n, logx=logx, cdir=cdir) for t in self.args] + return self.func(*terms) + + def _matches_simple(self, expr, repl_dict): + # handle (w+3).matches('x+5') -> {w: x+2} + coeff, terms = self.as_coeff_add() + if len(terms) == 1: + return terms[0].matches(expr - coeff, repl_dict) + return + + def matches(self, expr, repl_dict=None, old=False): + return self._matches_commutative(expr, repl_dict, old) + + @staticmethod + def _combine_inverse(lhs, rhs): + """ + Returns lhs - rhs, but treats oo like a symbol so oo - oo + returns 0, instead of a nan. + """ + from sympy.simplify.simplify import signsimp + inf = (S.Infinity, S.NegativeInfinity) + if lhs.has(*inf) or rhs.has(*inf): + from .symbol import Dummy + oo = Dummy('oo') + reps = { + S.Infinity: oo, + S.NegativeInfinity: -oo} + ireps = {v: k for k, v in reps.items()} + eq = lhs.xreplace(reps) - rhs.xreplace(reps) + if eq.has(oo): + eq = eq.replace( + lambda x: x.is_Pow and x.base is oo, + lambda x: x.base) + rv = eq.xreplace(ireps) + else: + rv = lhs - rhs + srv = signsimp(rv) + return srv if srv.is_Number else rv + + @cacheit + def as_two_terms(self): + """Return head and tail of self. + + This is the most efficient way to get the head and tail of an + expression. + + - if you want only the head, use self.args[0]; + - if you want to process the arguments of the tail then use + self.as_coef_add() which gives the head and a tuple containing + the arguments of the tail when treated as an Add. + - if you want the coefficient when self is treated as a Mul + then use self.as_coeff_mul()[0] + + >>> from sympy.abc import x, y + >>> (3*x - 2*y + 5).as_two_terms() + (5, 3*x - 2*y) + """ + return self.args[0], self._new_rawargs(*self.args[1:]) + + def as_numer_denom(self) -> tuple[Expr, Expr]: + """ + Decomposes an expression to its numerator part and its + denominator part. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> (x*y/z).as_numer_denom() + (x*y, z) + >>> (x*(y + 1)/y**7).as_numer_denom() + (x*(y + 1), y**7) + + See Also + ======== + + sympy.core.expr.Expr.as_numer_denom + """ + # clear rational denominator + content, expr = self.primitive() + if not isinstance(expr, Add): + return Mul(content, expr, evaluate=False).as_numer_denom() + ncon, dcon = content.as_numer_denom() + + # collect numerators and denominators of the terms + nd = defaultdict(list) + for f in expr.args: + ni, di = f.as_numer_denom() + nd[di].append(ni) + + # check for quick exit + if len(nd) == 1: + d, n = nd.popitem() + return self.func( + *[_keep_coeff(ncon, ni) for ni in n]), _keep_coeff(dcon, d) + + # sum up the terms having a common denominator + nd2 = {d: self.func(*n) if len(n) > 1 else n[0] for d, n in nd.items()} + + # assemble single numerator and denominator + denoms, numers = [list(i) for i in zip(*iter(nd2.items()))] + n, d = self.func(*[Mul(*(denoms[:i] + [numers[i]] + denoms[i + 1:])) + for i in range(len(numers))]), Mul(*denoms) + + return _keep_coeff(ncon, n), _keep_coeff(dcon, d) + + def _eval_is_polynomial(self, syms): + return all(term._eval_is_polynomial(syms) for term in self.args) + + def _eval_is_rational_function(self, syms): + return all(term._eval_is_rational_function(syms) for term in self.args) + + def _eval_is_meromorphic(self, x, a): + return _fuzzy_group((arg.is_meromorphic(x, a) for arg in self.args), + quick_exit=True) + + def _eval_is_algebraic_expr(self, syms): + return all(term._eval_is_algebraic_expr(syms) for term in self.args) + + # assumption methods + _eval_is_real = lambda self: _fuzzy_group( + (a.is_real for a in self.args), quick_exit=True) + _eval_is_extended_real = lambda self: _fuzzy_group( + (a.is_extended_real for a in self.args), quick_exit=True) + _eval_is_complex = lambda self: _fuzzy_group( + (a.is_complex for a in self.args), quick_exit=True) + _eval_is_antihermitian = lambda self: _fuzzy_group( + (a.is_antihermitian for a in self.args), quick_exit=True) + _eval_is_finite = lambda self: _fuzzy_group( + (a.is_finite for a in self.args), quick_exit=True) + _eval_is_hermitian = lambda self: _fuzzy_group( + (a.is_hermitian for a in self.args), quick_exit=True) + _eval_is_integer = lambda self: _fuzzy_group( + (a.is_integer for a in self.args), quick_exit=True) + _eval_is_rational = lambda self: _fuzzy_group( + (a.is_rational for a in self.args), quick_exit=True) + _eval_is_algebraic = lambda self: _fuzzy_group( + (a.is_algebraic for a in self.args), quick_exit=True) + _eval_is_commutative = lambda self: _fuzzy_group( + a.is_commutative for a in self.args) + + def _eval_is_infinite(self): + sawinf = False + for a in self.args: + ainf = a.is_infinite + if ainf is None: + return None + elif ainf is True: + # infinite+infinite might not be infinite + if sawinf is True: + return None + sawinf = True + return sawinf + + def _eval_is_imaginary(self): + nz = [] + im_I = [] + for a in self.args: + if a.is_extended_real: + if a.is_zero: + pass + elif a.is_zero is False: + nz.append(a) + else: + return + elif a.is_imaginary: + im_I.append(a*S.ImaginaryUnit) + elif a.is_Mul and S.ImaginaryUnit in a.args: + coeff, ai = a.as_coeff_mul(S.ImaginaryUnit) + if ai == (S.ImaginaryUnit,) and coeff.is_extended_real: + im_I.append(-coeff) + else: + return + else: + return + b = self.func(*nz) + if b != self: + if b.is_zero: + return fuzzy_not(self.func(*im_I).is_zero) + elif b.is_zero is False: + return False + + def _eval_is_zero(self): + if self.is_commutative is False: + # issue 10528: there is no way to know if a nc symbol + # is zero or not + return + nz = [] + z = 0 + im_or_z = False + im = 0 + for a in self.args: + if a.is_extended_real: + if a.is_zero: + z += 1 + elif a.is_zero is False: + nz.append(a) + else: + return + elif a.is_imaginary: + im += 1 + elif a.is_Mul and S.ImaginaryUnit in a.args: + coeff, ai = a.as_coeff_mul(S.ImaginaryUnit) + if ai == (S.ImaginaryUnit,) and coeff.is_extended_real: + im_or_z = True + else: + return + else: + return + if z == len(self.args): + return True + if len(nz) in [0, len(self.args)]: + return None + b = self.func(*nz) + if b.is_zero: + if not im_or_z: + if im == 0: + return True + elif im == 1: + return False + if b.is_zero is False: + return False + + def _eval_is_odd(self): + l = [f for f in self.args if not (f.is_even is True)] + if not l: + return False + if l[0].is_odd: + return self._new_rawargs(*l[1:]).is_even + + def _eval_is_irrational(self): + for t in self.args: + a = t.is_irrational + if a: + others = list(self.args) + others.remove(t) + if all(x.is_rational is True for x in others): + return True + return None + if a is None: + return + return False + + def _all_nonneg_or_nonppos(self): + nn = np = 0 + for a in self.args: + if a.is_nonnegative: + if np: + return False + nn = 1 + elif a.is_nonpositive: + if nn: + return False + np = 1 + else: + break + else: + return True + + def _eval_is_extended_positive(self): + if self.is_number: + return super()._eval_is_extended_positive() + c, a = self.as_coeff_Add() + if not c.is_zero: + from .exprtools import _monotonic_sign + v = _monotonic_sign(a) + if v is not None: + s = v + c + if s != self and s.is_extended_positive and a.is_extended_nonnegative: + return True + if len(self.free_symbols) == 1: + v = _monotonic_sign(self) + if v is not None and v != self and v.is_extended_positive: + return True + pos = nonneg = nonpos = unknown_sign = False + saw_INF = set() + args = [a for a in self.args if not a.is_zero] + if not args: + return False + for a in args: + ispos = a.is_extended_positive + infinite = a.is_infinite + if infinite: + saw_INF.add(fuzzy_or((ispos, a.is_extended_nonnegative))) + if True in saw_INF and False in saw_INF: + return + if ispos: + pos = True + continue + elif a.is_extended_nonnegative: + nonneg = True + continue + elif a.is_extended_nonpositive: + nonpos = True + continue + + if infinite is None: + return + unknown_sign = True + + if saw_INF: + if len(saw_INF) > 1: + return + return saw_INF.pop() + elif unknown_sign: + return + elif not nonpos and not nonneg and pos: + return True + elif not nonpos and pos: + return True + elif not pos and not nonneg: + return False + + def _eval_is_extended_nonnegative(self): + if not self.is_number: + c, a = self.as_coeff_Add() + if not c.is_zero and a.is_extended_nonnegative: + from .exprtools import _monotonic_sign + v = _monotonic_sign(a) + if v is not None: + s = v + c + if s != self and s.is_extended_nonnegative: + return True + if len(self.free_symbols) == 1: + v = _monotonic_sign(self) + if v is not None and v != self and v.is_extended_nonnegative: + return True + + def _eval_is_extended_nonpositive(self): + if not self.is_number: + c, a = self.as_coeff_Add() + if not c.is_zero and a.is_extended_nonpositive: + from .exprtools import _monotonic_sign + v = _monotonic_sign(a) + if v is not None: + s = v + c + if s != self and s.is_extended_nonpositive: + return True + if len(self.free_symbols) == 1: + v = _monotonic_sign(self) + if v is not None and v != self and v.is_extended_nonpositive: + return True + + def _eval_is_extended_negative(self): + if self.is_number: + return super()._eval_is_extended_negative() + c, a = self.as_coeff_Add() + if not c.is_zero: + from .exprtools import _monotonic_sign + v = _monotonic_sign(a) + if v is not None: + s = v + c + if s != self and s.is_extended_negative and a.is_extended_nonpositive: + return True + if len(self.free_symbols) == 1: + v = _monotonic_sign(self) + if v is not None and v != self and v.is_extended_negative: + return True + neg = nonpos = nonneg = unknown_sign = False + saw_INF = set() + args = [a for a in self.args if not a.is_zero] + if not args: + return False + for a in args: + isneg = a.is_extended_negative + infinite = a.is_infinite + if infinite: + saw_INF.add(fuzzy_or((isneg, a.is_extended_nonpositive))) + if True in saw_INF and False in saw_INF: + return + if isneg: + neg = True + continue + elif a.is_extended_nonpositive: + nonpos = True + continue + elif a.is_extended_nonnegative: + nonneg = True + continue + + if infinite is None: + return + unknown_sign = True + + if saw_INF: + if len(saw_INF) > 1: + return + return saw_INF.pop() + elif unknown_sign: + return + elif not nonneg and not nonpos and neg: + return True + elif not nonneg and neg: + return True + elif not neg and not nonpos: + return False + + def _eval_subs(self, old, new): + if not old.is_Add: + if old is S.Infinity and -old in self.args: + # foo - oo is foo + (-oo) internally + return self.xreplace({-old: -new}) + return None + + coeff_self, terms_self = self.as_coeff_Add() + coeff_old, terms_old = old.as_coeff_Add() + + if coeff_self.is_Rational and coeff_old.is_Rational: + if terms_self == terms_old: # (2 + a).subs( 3 + a, y) -> -1 + y + return self.func(new, coeff_self, -coeff_old) + if terms_self == -terms_old: # (2 + a).subs(-3 - a, y) -> -1 - y + return self.func(-new, coeff_self, coeff_old) + + if coeff_self.is_Rational and coeff_old.is_Rational \ + or coeff_self == coeff_old: + args_old, args_self = self.func.make_args( + terms_old), self.func.make_args(terms_self) + if len(args_old) < len(args_self): # (a+b+c).subs(b+c,x) -> a+x + self_set = set(args_self) + old_set = set(args_old) + + if old_set < self_set: + ret_set = self_set - old_set + return self.func(new, coeff_self, -coeff_old, + *[s._subs(old, new) for s in ret_set]) + + args_old = self.func.make_args( + -terms_old) # (a+b+c+d).subs(-b-c,x) -> a-x+d + old_set = set(args_old) + if old_set < self_set: + ret_set = self_set - old_set + return self.func(-new, coeff_self, coeff_old, + *[s._subs(old, new) for s in ret_set]) + + def removeO(self): + args = [a for a in self.args if not a.is_Order] + return self._new_rawargs(*args) + + def getO(self): + args = [a for a in self.args if a.is_Order] + if args: + return self._new_rawargs(*args) + + @cacheit + def extract_leading_order(self, symbols, point=None): + """ + Returns the leading term and its order. + + Examples + ======== + + >>> from sympy.abc import x + >>> (x + 1 + 1/x**5).extract_leading_order(x) + ((x**(-5), O(x**(-5))),) + >>> (1 + x).extract_leading_order(x) + ((1, O(1)),) + >>> (x + x**2).extract_leading_order(x) + ((x, O(x)),) + + """ + from sympy.series.order import Order + lst = [] + symbols = list(symbols if is_sequence(symbols) else [symbols]) + if not point: + point = [0]*len(symbols) + seq = [(f, Order(f, *zip(symbols, point))) for f in self.args] + for ef, of in seq: + for e, o in lst: + if o.contains(of) and o != of: + of = None + break + if of is None: + continue + new_lst = [(ef, of)] + for e, o in lst: + if of.contains(o) and o != of: + continue + new_lst.append((e, o)) + lst = new_lst + return tuple(lst) + + def as_real_imag(self, deep=True, **hints): + """ + Return a tuple representing a complex number. + + Examples + ======== + + >>> from sympy import I + >>> (7 + 9*I).as_real_imag() + (7, 9) + >>> ((1 + I)/(1 - I)).as_real_imag() + (0, 1) + >>> ((1 + 2*I)*(1 + 3*I)).as_real_imag() + (-5, 5) + """ + sargs = self.args + re_part, im_part = [], [] + for term in sargs: + re, im = term.as_real_imag(deep=deep) + re_part.append(re) + im_part.append(im) + return (self.func(*re_part), self.func(*im_part)) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.core.symbol import Dummy, Symbol + from sympy.series.order import Order + from sympy.functions.elementary.exponential import log + from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold + from .function import expand_mul + + o = self.getO() + if o is None: + o = Order(0) + old = self.removeO() + + if old.has(Piecewise): + old = piecewise_fold(old) + + # This expansion is the last part of expand_log. expand_log also calls + # expand_mul with factor=True, which would be more expensive + if any(isinstance(a, log) for a in self.args): + logflags = {"deep": True, "log": True, "mul": False, "power_exp": False, + "power_base": False, "multinomial": False, "basic": False, "force": False, + "factor": False} + old = old.expand(**logflags) + expr = expand_mul(old) + + if not expr.is_Add: + return expr.as_leading_term(x, logx=logx, cdir=cdir) + + infinite = [t for t in expr.args if t.is_infinite] + + _logx = Dummy('logx') if logx is None else logx + leading_terms = [t.as_leading_term(x, logx=_logx, cdir=cdir) for t in expr.args] + + min, new_expr = Order(0), S.Zero + + try: + for term in leading_terms: + order = Order(term, x) + if not min or order not in min: + min = order + new_expr = term + elif min in order: + new_expr += term + + except TypeError: + return expr + + if logx is None: + new_expr = new_expr.subs(_logx, log(x)) + + is_zero = new_expr.is_zero + if is_zero is None: + new_expr = new_expr.trigsimp().cancel() + is_zero = new_expr.is_zero + if is_zero is True: + # simple leading term analysis gave us cancelled terms but we have to send + # back a term, so compute the leading term (via series) + try: + n0 = min.getn() + except NotImplementedError: + n0 = S.One + if n0.has(Symbol): + n0 = S.One + res = Order(1) + incr = S.One + while res.is_Order: + res = old._eval_nseries(x, n=n0+incr, logx=logx, cdir=cdir).cancel().powsimp().trigsimp() + incr *= 2 + return res.as_leading_term(x, logx=logx, cdir=cdir) + + elif new_expr is S.NaN: + return old.func._from_args(infinite) + o + + else: + return new_expr + + def _eval_adjoint(self): + return self.func(*[t.adjoint() for t in self.args]) + + def _eval_conjugate(self): + return self.func(*[t.conjugate() for t in self.args]) + + def _eval_transpose(self): + return self.func(*[t.transpose() for t in self.args]) + + def primitive(self): + """ + Return ``(R, self/R)`` where ``R``` is the Rational GCD of ``self```. + + ``R`` is collected only from the leading coefficient of each term. + + Examples + ======== + + >>> from sympy.abc import x, y + + >>> (2*x + 4*y).primitive() + (2, x + 2*y) + + >>> (2*x/3 + 4*y/9).primitive() + (2/9, 3*x + 2*y) + + >>> (2*x/3 + 4.2*y).primitive() + (1/3, 2*x + 12.6*y) + + No subprocessing of term factors is performed: + + >>> ((2 + 2*x)*x + 2).primitive() + (1, x*(2*x + 2) + 2) + + Recursive processing can be done with the ``as_content_primitive()`` + method: + + >>> ((2 + 2*x)*x + 2).as_content_primitive() + (2, x*(x + 1) + 1) + + See also: primitive() function in polytools.py + + """ + + terms = [] + inf = False + for a in self.args: + c, m = a.as_coeff_Mul() + if not c.is_Rational: + c = S.One + m = a + inf = inf or m is S.ComplexInfinity + terms.append((c.p, c.q, m)) + + if not inf: + ngcd = reduce(igcd, [t[0] for t in terms], 0) + dlcm = reduce(ilcm, [t[1] for t in terms], 1) + else: + ngcd = reduce(igcd, [t[0] for t in terms if t[1]], 0) + dlcm = reduce(ilcm, [t[1] for t in terms if t[1]], 1) + + if ngcd == dlcm == 1: + return S.One, self + if not inf: + for i, (p, q, term) in enumerate(terms): + terms[i] = _keep_coeff(Rational((p//ngcd)*(dlcm//q)), term) + else: + for i, (p, q, term) in enumerate(terms): + if q: + terms[i] = _keep_coeff(Rational((p//ngcd)*(dlcm//q)), term) + else: + terms[i] = _keep_coeff(Rational(p, q), term) + + # we don't need a complete re-flattening since no new terms will join + # so we just use the same sort as is used in Add.flatten. When the + # coefficient changes, the ordering of terms may change, e.g. + # (3*x, 6*y) -> (2*y, x) + # + # We do need to make sure that term[0] stays in position 0, however. + # + if terms[0].is_Number or terms[0] is S.ComplexInfinity: + c = terms.pop(0) + else: + c = None + _addsort(terms) + if c: + terms.insert(0, c) + return Rational(ngcd, dlcm), self._new_rawargs(*terms) + + def as_content_primitive(self, radical=False, clear=True): + """Return the tuple (R, self/R) where R is the positive Rational + extracted from self. If radical is True (default is False) then + common radicals will be removed and included as a factor of the + primitive expression. + + Examples + ======== + + >>> from sympy import sqrt + >>> (3 + 3*sqrt(2)).as_content_primitive() + (3, 1 + sqrt(2)) + + Radical content can also be factored out of the primitive: + + >>> (2*sqrt(2) + 4*sqrt(10)).as_content_primitive(radical=True) + (2, sqrt(2)*(1 + 2*sqrt(5))) + + See docstring of Expr.as_content_primitive for more examples. + """ + con, prim = self.func(*[_keep_coeff(*a.as_content_primitive( + radical=radical, clear=clear)) for a in self.args]).primitive() + if not clear and not con.is_Integer and prim.is_Add: + con, d = con.as_numer_denom() + _p = prim/d + if any(a.as_coeff_Mul()[0].is_Integer for a in _p.args): + prim = _p + else: + con /= d + if radical and prim.is_Add: + # look for common radicals that can be removed + args = prim.args + rads = [] + common_q = None + for m in args: + term_rads = defaultdict(list) + for ai in Mul.make_args(m): + if ai.is_Pow: + b, e = ai.as_base_exp() + if e.is_Rational and b.is_Integer: + term_rads[e.q].append(abs(int(b))**e.p) + if not term_rads: + break + if common_q is None: + common_q = set(term_rads.keys()) + else: + common_q = common_q & set(term_rads.keys()) + if not common_q: + break + rads.append(term_rads) + else: + # process rads + # keep only those in common_q + for r in rads: + for q in list(r.keys()): + if q not in common_q: + r.pop(q) + for q in r: + r[q] = Mul(*r[q]) + # find the gcd of bases for each q + G = [] + for q in common_q: + g = reduce(igcd, [r[q] for r in rads], 0) + if g != 1: + G.append(g**Rational(1, q)) + if G: + G = Mul(*G) + args = [ai/G for ai in args] + prim = G*prim.func(*args) + + return con, prim + + @property + def _sorted_args(self): + from .sorting import default_sort_key + return tuple(sorted(self.args, key=default_sort_key)) + + def _eval_difference_delta(self, n, step): + from sympy.series.limitseq import difference_delta as dd + return self.func(*[dd(a, n, step) for a in self.args]) + + @property + def _mpc_(self): + """ + Convert self to an mpmath mpc if possible + """ + from .numbers import Float + re_part, rest = self.as_coeff_Add() + im_part, imag_unit = rest.as_coeff_Mul() + if not imag_unit == S.ImaginaryUnit: + # ValueError may seem more reasonable but since it's a @property, + # we need to use AttributeError to keep from confusing things like + # hasattr. + raise AttributeError("Cannot convert Add to mpc. Must be of the form Number + Number*I") + + return (Float(re_part)._mpf_, Float(im_part)._mpf_) + + def __neg__(self): + if not global_parameters.distribute: + return super().__neg__() + return Mul(S.NegativeOne, self) + +add = AssocOpDispatcher('add') + +from .mul import Mul, _keep_coeff, _unevaluated_Mul +from .numbers import Rational diff --git a/phivenv/Lib/site-packages/sympy/core/alphabets.py b/phivenv/Lib/site-packages/sympy/core/alphabets.py new file mode 100644 index 0000000000000000000000000000000000000000..1ea2ae1c410ccd30e7ec9551f4cd8b19a36cdba1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/alphabets.py @@ -0,0 +1,4 @@ +greeks = ('alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', + 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', + 'xi', 'omicron', 'pi', 'rho', 'sigma', 'tau', 'upsilon', + 'phi', 'chi', 'psi', 'omega') diff --git a/phivenv/Lib/site-packages/sympy/core/assumptions.py b/phivenv/Lib/site-packages/sympy/core/assumptions.py new file mode 100644 index 0000000000000000000000000000000000000000..677e86c5e39390b0b188a5158dd2fabfbac4c760 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/assumptions.py @@ -0,0 +1,692 @@ +""" +This module contains the machinery handling assumptions. +Do also consider the guide :ref:`assumptions-guide`. + +All symbolic objects have assumption attributes that can be accessed via +``.is_`` attribute. + +Assumptions determine certain properties of symbolic objects and can +have 3 possible values: ``True``, ``False``, ``None``. ``True`` is returned if the +object has the property and ``False`` is returned if it does not or cannot +(i.e. does not make sense): + + >>> from sympy import I + >>> I.is_algebraic + True + >>> I.is_real + False + >>> I.is_prime + False + +When the property cannot be determined (or when a method is not +implemented) ``None`` will be returned. For example, a generic symbol, ``x``, +may or may not be positive so a value of ``None`` is returned for ``x.is_positive``. + +By default, all symbolic values are in the largest set in the given context +without specifying the property. For example, a symbol that has a property +being integer, is also real, complex, etc. + +Here follows a list of possible assumption names: + +.. glossary:: + + commutative + object commutes with any other object with + respect to multiplication operation. See [12]_. + + complex + object can have only values from the set + of complex numbers. See [13]_. + + imaginary + object value is a number that can be written as a real + number multiplied by the imaginary unit ``I``. See + [3]_. Please note that ``0`` is not considered to be an + imaginary number, see + `issue #7649 `_. + + real + object can have only values from the set + of real numbers. + + extended_real + object can have only values from the set + of real numbers, ``oo`` and ``-oo``. + + integer + object can have only values from the set + of integers. + + odd + even + object can have only values from the set of + odd (even) integers [2]_. + + prime + object is a natural number greater than 1 that has + no positive divisors other than 1 and itself. See [6]_. + + composite + object is a positive integer that has at least one positive + divisor other than 1 or the number itself. See [4]_. + + zero + object has the value of 0. + + nonzero + object is a real number that is not zero. + + rational + object can have only values from the set + of rationals. + + algebraic + object can have only values from the set + of algebraic numbers [11]_. + + transcendental + object can have only values from the set + of transcendental numbers [10]_. + + irrational + object value cannot be represented exactly by :class:`~.Rational`, see [5]_. + + finite + infinite + object absolute value is bounded (arbitrarily large). + See [7]_, [8]_, [9]_. + + negative + nonnegative + object can have only negative (nonnegative) + values [1]_. + + positive + nonpositive + object can have only positive (nonpositive) values. + + extended_negative + extended_nonnegative + extended_positive + extended_nonpositive + extended_nonzero + as without the extended part, but also including infinity with + corresponding sign, e.g., extended_positive includes ``oo`` + + hermitian + antihermitian + object belongs to the field of Hermitian + (antihermitian) operators. + +Examples +======== + + >>> from sympy import Symbol + >>> x = Symbol('x', real=True); x + x + >>> x.is_real + True + >>> x.is_complex + True + +See Also +======== + +.. seealso:: + + :py:class:`sympy.core.numbers.ImaginaryUnit` + :py:class:`sympy.core.numbers.Zero` + :py:class:`sympy.core.numbers.One` + :py:class:`sympy.core.numbers.Infinity` + :py:class:`sympy.core.numbers.NegativeInfinity` + :py:class:`sympy.core.numbers.ComplexInfinity` + +Notes +===== + +The fully-resolved assumptions for any SymPy expression +can be obtained as follows: + + >>> from sympy.core.assumptions import assumptions + >>> x = Symbol('x',positive=True) + >>> assumptions(x + I) + {'commutative': True, 'complex': True, 'composite': False, 'even': + False, 'extended_negative': False, 'extended_nonnegative': False, + 'extended_nonpositive': False, 'extended_nonzero': False, + 'extended_positive': False, 'extended_real': False, 'finite': True, + 'imaginary': False, 'infinite': False, 'integer': False, 'irrational': + False, 'negative': False, 'noninteger': False, 'nonnegative': False, + 'nonpositive': False, 'nonzero': False, 'odd': False, 'positive': + False, 'prime': False, 'rational': False, 'real': False, 'zero': + False} + +Developers Notes +================ + +The current (and possibly incomplete) values are stored +in the ``obj._assumptions dictionary``; queries to getter methods +(with property decorators) or attributes of objects/classes +will return values and update the dictionary. + + >>> eq = x**2 + I + >>> eq._assumptions + {} + >>> eq.is_finite + True + >>> eq._assumptions + {'finite': True, 'infinite': False} + +For a :class:`~.Symbol`, there are two locations for assumptions that may +be of interest. The ``assumptions0`` attribute gives the full set of +assumptions derived from a given set of initial assumptions. The +latter assumptions are stored as ``Symbol._assumptions_orig`` + + >>> Symbol('x', prime=True, even=True)._assumptions_orig + {'even': True, 'prime': True} + +The ``_assumptions_orig`` are not necessarily canonical nor are they filtered +in any way: they records the assumptions used to instantiate a Symbol and (for +storage purposes) represent a more compact representation of the assumptions +needed to recreate the full set in ``Symbol.assumptions0``. + + +References +========== + +.. [1] https://en.wikipedia.org/wiki/Negative_number +.. [2] https://en.wikipedia.org/wiki/Parity_%28mathematics%29 +.. [3] https://en.wikipedia.org/wiki/Imaginary_number +.. [4] https://en.wikipedia.org/wiki/Composite_number +.. [5] https://en.wikipedia.org/wiki/Irrational_number +.. [6] https://en.wikipedia.org/wiki/Prime_number +.. [7] https://en.wikipedia.org/wiki/Finite +.. [8] https://docs.python.org/3/library/math.html#math.isfinite +.. [9] https://numpy.org/doc/stable/reference/generated/numpy.isfinite.html +.. [10] https://en.wikipedia.org/wiki/Transcendental_number +.. [11] https://en.wikipedia.org/wiki/Algebraic_number +.. [12] https://en.wikipedia.org/wiki/Commutative_property +.. [13] https://en.wikipedia.org/wiki/Complex_number + +""" + +from sympy.utilities.exceptions import sympy_deprecation_warning + +from .facts import FactRules, FactKB +from .sympify import sympify + +from sympy.core.random import _assumptions_shuffle as shuffle +from sympy.core.assumptions_generated import generated_assumptions as _assumptions + +def _load_pre_generated_assumption_rules() -> FactRules: + """ Load the assumption rules from pre-generated data + + To update the pre-generated data, see :method::`_generate_assumption_rules` + """ + _assume_rules=FactRules._from_python(_assumptions) + return _assume_rules + +def _generate_assumption_rules(): + """ Generate the default assumption rules + + This method should only be called to update the pre-generated + assumption rules. + + To update the pre-generated assumptions run: bin/ask_update.py + + """ + _assume_rules = FactRules([ + + 'integer -> rational', + 'rational -> real', + 'rational -> algebraic', + 'algebraic -> complex', + 'transcendental == complex & !algebraic', + 'real -> hermitian', + 'imaginary -> complex', + 'imaginary -> antihermitian', + 'extended_real -> commutative', + 'complex -> commutative', + 'complex -> finite', + + 'odd == integer & !even', + 'even == integer & !odd', + + 'real -> complex', + 'extended_real -> real | infinite', + 'real == extended_real & finite', + + 'extended_real == extended_negative | zero | extended_positive', + 'extended_negative == extended_nonpositive & extended_nonzero', + 'extended_positive == extended_nonnegative & extended_nonzero', + + 'extended_nonpositive == extended_real & !extended_positive', + 'extended_nonnegative == extended_real & !extended_negative', + + 'real == negative | zero | positive', + 'negative == nonpositive & nonzero', + 'positive == nonnegative & nonzero', + + 'nonpositive == real & !positive', + 'nonnegative == real & !negative', + + 'positive == extended_positive & finite', + 'negative == extended_negative & finite', + 'nonpositive == extended_nonpositive & finite', + 'nonnegative == extended_nonnegative & finite', + 'nonzero == extended_nonzero & finite', + + 'zero -> even & finite', + 'zero == extended_nonnegative & extended_nonpositive', + 'zero == nonnegative & nonpositive', + 'nonzero -> real', + + 'prime -> integer & positive', + 'composite -> integer & positive & !prime', + '!composite -> !positive | !even | prime', + + 'irrational == real & !rational', + + 'imaginary -> !extended_real', + + 'infinite == !finite', + 'noninteger == extended_real & !integer', + 'extended_nonzero == extended_real & !zero', + ]) + return _assume_rules + + +_assume_rules = _load_pre_generated_assumption_rules() +_assume_defined = _assume_rules.defined_facts.copy() +_assume_defined.add('polar') +_assume_defined = frozenset(_assume_defined) + + +def assumptions(expr, _check=None): + """return the T/F assumptions of ``expr``""" + n = sympify(expr) + if n.is_Symbol: + rv = n.assumptions0 # are any important ones missing? + if _check is not None: + rv = {k: rv[k] for k in set(rv) & set(_check)} + return rv + rv = {} + for k in _assume_defined if _check is None else _check: + v = getattr(n, 'is_{}'.format(k)) + if v is not None: + rv[k] = v + return rv + + +def common_assumptions(exprs, check=None): + """return those assumptions which have the same True or False + value for all the given expressions. + + Examples + ======== + + >>> from sympy.core import common_assumptions + >>> from sympy import oo, pi, sqrt + >>> common_assumptions([-4, 0, sqrt(2), 2, pi, oo]) + {'commutative': True, 'composite': False, + 'extended_real': True, 'imaginary': False, 'odd': False} + + By default, all assumptions are tested; pass an iterable of the + assumptions to limit those that are reported: + + >>> common_assumptions([0, 1, 2], ['positive', 'integer']) + {'integer': True} + """ + check = _assume_defined if check is None else set(check) + if not check or not exprs: + return {} + + # get all assumptions for each + assume = [assumptions(i, _check=check) for i in sympify(exprs)] + # focus on those of interest that are True + for i, e in enumerate(assume): + assume[i] = {k: e[k] for k in set(e) & check} + # what assumptions are in common? + common = set.intersection(*[set(i) for i in assume]) + # which ones hold the same value + a = assume[0] + return {k: a[k] for k in common if all(a[k] == b[k] + for b in assume)} + + +def failing_assumptions(expr, **assumptions): + """ + Return a dictionary containing assumptions with values not + matching those of the passed assumptions. + + Examples + ======== + + >>> from sympy import failing_assumptions, Symbol + + >>> x = Symbol('x', positive=True) + >>> y = Symbol('y') + >>> failing_assumptions(6*x + y, positive=True) + {'positive': None} + + >>> failing_assumptions(x**2 - 1, positive=True) + {'positive': None} + + If *expr* satisfies all of the assumptions, an empty dictionary is returned. + + >>> failing_assumptions(x**2, positive=True) + {} + + """ + expr = sympify(expr) + failed = {} + for k in assumptions: + test = getattr(expr, 'is_%s' % k, None) + if test is not assumptions[k]: + failed[k] = test + return failed # {} or {assumption: value != desired} + + +def check_assumptions(expr, against=None, **assume): + """ + Checks whether assumptions of ``expr`` match the T/F assumptions + given (or possessed by ``against``). True is returned if all + assumptions match; False is returned if there is a mismatch and + the assumption in ``expr`` is not None; else None is returned. + + Explanation + =========== + + *assume* is a dict of assumptions with True or False values + + Examples + ======== + + >>> from sympy import Symbol, pi, I, exp, check_assumptions + >>> check_assumptions(-5, integer=True) + True + >>> check_assumptions(pi, real=True, integer=False) + True + >>> check_assumptions(pi, negative=True) + False + >>> check_assumptions(exp(I*pi/7), real=False) + True + >>> x = Symbol('x', positive=True) + >>> check_assumptions(2*x + 1, positive=True) + True + >>> check_assumptions(-2*x - 5, positive=True) + False + + To check assumptions of *expr* against another variable or expression, + pass the expression or variable as ``against``. + + >>> check_assumptions(2*x + 1, x) + True + + To see if a number matches the assumptions of an expression, pass + the number as the first argument, else its specific assumptions + may not have a non-None value in the expression: + + >>> check_assumptions(x, 3) + >>> check_assumptions(3, x) + True + + ``None`` is returned if ``check_assumptions()`` could not conclude. + + >>> check_assumptions(2*x - 1, x) + + >>> z = Symbol('z') + >>> check_assumptions(z, real=True) + + See Also + ======== + + failing_assumptions + + """ + expr = sympify(expr) + if against is not None: + if assume: + raise ValueError( + 'Expecting `against` or `assume`, not both.') + assume = assumptions(against) + known = True + for k, v in assume.items(): + if v is None: + continue + e = getattr(expr, 'is_' + k, None) + if e is None: + known = None + elif v != e: + return False + return known + + +class StdFactKB(FactKB): + """A FactKB specialized for the built-in rules + + This is the only kind of FactKB that Basic objects should use. + """ + def __init__(self, facts=None): + super().__init__(_assume_rules) + # save a copy of the facts dict + if not facts: + self._generator = {} + elif not isinstance(facts, FactKB): + self._generator = facts.copy() + else: + self._generator = facts.generator + if facts: + self.deduce_all_facts(facts) + + def copy(self): + return self.__class__(self) + + @property + def generator(self): + return self._generator.copy() + + +def as_property(fact): + """Convert a fact name to the name of the corresponding property""" + return 'is_%s' % fact + + +def make_property(fact): + """Create the automagic property corresponding to a fact.""" + + def getit(self): + try: + return self._assumptions[fact] + except KeyError: + if self._assumptions is self.default_assumptions: + self._assumptions = self.default_assumptions.copy() + return _ask(fact, self) + + getit.func_name = as_property(fact) + return property(getit) + + +def _ask(fact, obj): + """ + Find the truth value for a property of an object. + + This function is called when a request is made to see what a fact + value is. + + For this we use several techniques: + + First, the fact-evaluation function is tried, if it exists (for + example _eval_is_integer). Then we try related facts. For example + + rational --> integer + + another example is joined rule: + + integer & !odd --> even + + so in the latter case if we are looking at what 'even' value is, + 'integer' and 'odd' facts will be asked. + + In all cases, when we settle on some fact value, its implications are + deduced, and the result is cached in ._assumptions. + """ + # FactKB which is dict-like and maps facts to their known values: + assumptions = obj._assumptions + + # A dict that maps facts to their handlers: + handler_map = obj._prop_handler + + # This is our queue of facts to check: + facts_to_check = [fact] + facts_queued = {fact} + + # Loop over the queue as it extends + for fact_i in facts_to_check: + + # If fact_i has already been determined then we don't need to rerun the + # handler. There is a potential race condition for multithreaded code + # though because it's possible that fact_i was checked in another + # thread. The main logic of the loop below would potentially skip + # checking assumptions[fact] in this case so we check it once after the + # loop to be sure. + if fact_i in assumptions: + continue + + # Now we call the associated handler for fact_i if it exists. + fact_i_value = None + handler_i = handler_map.get(fact_i) + if handler_i is not None: + fact_i_value = handler_i(obj) + + # If we get a new value for fact_i then we should update our knowledge + # of fact_i as well as any related facts that can be inferred using the + # inference rules connecting the fact_i and any other fact values that + # are already known. + if fact_i_value is not None: + assumptions.deduce_all_facts(((fact_i, fact_i_value),)) + + # Usually if assumptions[fact] is now not None then that is because of + # the call to deduce_all_facts above. The handler for fact_i returned + # True or False and knowing fact_i (which is equal to fact in the first + # iteration) implies knowing a value for fact. It is also possible + # though that independent code e.g. called indirectly by the handler or + # called in another thread in a multithreaded context might have + # resulted in assumptions[fact] being set. Either way we return it. + fact_value = assumptions.get(fact) + if fact_value is not None: + return fact_value + + # Extend the queue with other facts that might determine fact_i. Here + # we randomise the order of the facts that are checked. This should not + # lead to any non-determinism if all handlers are logically consistent + # with the inference rules for the facts. Non-deterministic assumptions + # queries can result from bugs in the handlers that are exposed by this + # call to shuffle. These are pushed to the back of the queue meaning + # that the inference graph is traversed in breadth-first order. + new_facts_to_check = list(_assume_rules.prereq[fact_i] - facts_queued) + shuffle(new_facts_to_check) + facts_to_check.extend(new_facts_to_check) + facts_queued.update(new_facts_to_check) + + # The above loop should be able to handle everything fine in a + # single-threaded context but in multithreaded code it is possible that + # this thread skipped computing a particular fact that was computed in + # another thread (due to the continue). In that case it is possible that + # fact was inferred and is now stored in the assumptions dict but it wasn't + # checked for in the body of the loop. This is an obscure case but to make + # sure we catch it we check once here at the end of the loop. + if fact in assumptions: + return assumptions[fact] + + # This query can not be answered. It's possible that e.g. another thread + # has already stored None for fact but assumptions._tell does not mind if + # we call _tell twice setting the same value. If this raises + # InconsistentAssumptions then it probably means that another thread + # attempted to compute this and got a value of True or False rather than + # None. In that case there must be a bug in at least one of the handlers. + # If the handlers are all deterministic and are consistent with the + # inference rules then the same value should be computed for fact in all + # threads. + assumptions._tell(fact, None) + return None + + +def _prepare_class_assumptions(cls): + """Precompute class level assumptions and generate handlers. + + This is called by Basic.__init_subclass__ each time a Basic subclass is + defined. + """ + + local_defs = {} + for k in _assume_defined: + attrname = as_property(k) + v = cls.__dict__.get(attrname, '') + if isinstance(v, (bool, int, type(None))): + if v is not None: + v = bool(v) + local_defs[k] = v + + defs = {} + for base in reversed(cls.__bases__): + assumptions = getattr(base, '_explicit_class_assumptions', None) + if assumptions is not None: + defs.update(assumptions) + defs.update(local_defs) + + cls._explicit_class_assumptions = defs + cls.default_assumptions = StdFactKB(defs) + + cls._prop_handler = {} + for k in _assume_defined: + eval_is_meth = getattr(cls, '_eval_is_%s' % k, None) + if eval_is_meth is not None: + cls._prop_handler[k] = eval_is_meth + + # Put definite results directly into the class dict, for speed + for k, v in cls.default_assumptions.items(): + setattr(cls, as_property(k), v) + + # protection e.g. for Integer.is_even=F <- (Rational.is_integer=F) + derived_from_bases = set() + for base in cls.__bases__: + default_assumptions = getattr(base, 'default_assumptions', None) + # is an assumption-aware class + if default_assumptions is not None: + derived_from_bases.update(default_assumptions) + + for fact in derived_from_bases - set(cls.default_assumptions): + pname = as_property(fact) + if pname not in cls.__dict__: + setattr(cls, pname, make_property(fact)) + + # Finally, add any missing automagic property (e.g. for Basic) + for fact in _assume_defined: + pname = as_property(fact) + if not hasattr(cls, pname): + setattr(cls, pname, make_property(fact)) + + +# XXX: ManagedProperties used to be the metaclass for Basic but now Basic does +# not use a metaclass. We leave this here for backwards compatibility for now +# in case someone has been using the ManagedProperties class in downstream +# code. The reason that it might have been used is that when subclassing a +# class and wanting to use a metaclass the metaclass must be a subclass of the +# metaclass for the class that is being subclassed. Anyone wanting to subclass +# Basic and use a metaclass in their subclass would have needed to subclass +# ManagedProperties. Here ManagedProperties is not the metaclass for Basic any +# more but it should still be usable as a metaclass for Basic subclasses since +# it is a subclass of type which is now the metaclass for Basic. +class ManagedProperties(type): + def __init__(cls, *args, **kwargs): + msg = ("The ManagedProperties metaclass. " + "Basic does not use metaclasses any more") + sympy_deprecation_warning(msg, + deprecated_since_version="1.12", + active_deprecations_target='managedproperties') + + # Here we still call this function in case someone is using + # ManagedProperties for something that is not a Basic subclass. For + # Basic subclasses this function is now called by __init_subclass__ and + # so this metaclass is not needed any more. + _prepare_class_assumptions(cls) diff --git a/phivenv/Lib/site-packages/sympy/core/assumptions_generated.py b/phivenv/Lib/site-packages/sympy/core/assumptions_generated.py new file mode 100644 index 0000000000000000000000000000000000000000..b4b2597a72b500155370db385b58e61f0f951984 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/assumptions_generated.py @@ -0,0 +1,1615 @@ +""" +Do NOT manually edit this file. +Instead, run ./bin/ask_update.py. +""" + +defined_facts = [ + 'algebraic', + 'antihermitian', + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', +] # defined_facts + + +full_implications = dict( [ + # Implications of algebraic = True: + (('algebraic', True), set( ( + ('commutative', True), + ('complex', True), + ('finite', True), + ('infinite', False), + ('transcendental', False), + ) ), + ), + # Implications of algebraic = False: + (('algebraic', False), set( ( + ('composite', False), + ('even', False), + ('integer', False), + ('odd', False), + ('prime', False), + ('rational', False), + ('zero', False), + ) ), + ), + # Implications of antihermitian = True: + (('antihermitian', True), set( ( + ) ), + ), + # Implications of antihermitian = False: + (('antihermitian', False), set( ( + ('imaginary', False), + ) ), + ), + # Implications of commutative = True: + (('commutative', True), set( ( + ) ), + ), + # Implications of commutative = False: + (('commutative', False), set( ( + ('algebraic', False), + ('complex', False), + ('composite', False), + ('even', False), + ('extended_negative', False), + ('extended_nonnegative', False), + ('extended_nonpositive', False), + ('extended_nonzero', False), + ('extended_positive', False), + ('extended_real', False), + ('imaginary', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('noninteger', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of complex = True: + (('complex', True), set( ( + ('commutative', True), + ('finite', True), + ('infinite', False), + ) ), + ), + # Implications of complex = False: + (('complex', False), set( ( + ('algebraic', False), + ('composite', False), + ('even', False), + ('imaginary', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of composite = True: + (('composite', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('extended_negative', False), + ('extended_nonnegative', True), + ('extended_nonpositive', False), + ('extended_nonzero', True), + ('extended_positive', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('integer', True), + ('irrational', False), + ('negative', False), + ('noninteger', False), + ('nonnegative', True), + ('nonpositive', False), + ('nonzero', True), + ('positive', True), + ('prime', False), + ('rational', True), + ('real', True), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of composite = False: + (('composite', False), set( ( + ) ), + ), + # Implications of even = True: + (('even', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('integer', True), + ('irrational', False), + ('noninteger', False), + ('odd', False), + ('rational', True), + ('real', True), + ('transcendental', False), + ) ), + ), + # Implications of even = False: + (('even', False), set( ( + ('zero', False), + ) ), + ), + # Implications of extended_negative = True: + (('extended_negative', True), set( ( + ('commutative', True), + ('composite', False), + ('extended_nonnegative', False), + ('extended_nonpositive', True), + ('extended_nonzero', True), + ('extended_positive', False), + ('extended_real', True), + ('imaginary', False), + ('nonnegative', False), + ('positive', False), + ('prime', False), + ('zero', False), + ) ), + ), + # Implications of extended_negative = False: + (('extended_negative', False), set( ( + ('negative', False), + ) ), + ), + # Implications of extended_nonnegative = True: + (('extended_nonnegative', True), set( ( + ('commutative', True), + ('extended_negative', False), + ('extended_real', True), + ('imaginary', False), + ('negative', False), + ) ), + ), + # Implications of extended_nonnegative = False: + (('extended_nonnegative', False), set( ( + ('composite', False), + ('extended_positive', False), + ('nonnegative', False), + ('positive', False), + ('prime', False), + ('zero', False), + ) ), + ), + # Implications of extended_nonpositive = True: + (('extended_nonpositive', True), set( ( + ('commutative', True), + ('composite', False), + ('extended_positive', False), + ('extended_real', True), + ('imaginary', False), + ('positive', False), + ('prime', False), + ) ), + ), + # Implications of extended_nonpositive = False: + (('extended_nonpositive', False), set( ( + ('extended_negative', False), + ('negative', False), + ('nonpositive', False), + ('zero', False), + ) ), + ), + # Implications of extended_nonzero = True: + (('extended_nonzero', True), set( ( + ('commutative', True), + ('extended_real', True), + ('imaginary', False), + ('zero', False), + ) ), + ), + # Implications of extended_nonzero = False: + (('extended_nonzero', False), set( ( + ('composite', False), + ('extended_negative', False), + ('extended_positive', False), + ('negative', False), + ('nonzero', False), + ('positive', False), + ('prime', False), + ) ), + ), + # Implications of extended_positive = True: + (('extended_positive', True), set( ( + ('commutative', True), + ('extended_negative', False), + ('extended_nonnegative', True), + ('extended_nonpositive', False), + ('extended_nonzero', True), + ('extended_real', True), + ('imaginary', False), + ('negative', False), + ('nonpositive', False), + ('zero', False), + ) ), + ), + # Implications of extended_positive = False: + (('extended_positive', False), set( ( + ('composite', False), + ('positive', False), + ('prime', False), + ) ), + ), + # Implications of extended_real = True: + (('extended_real', True), set( ( + ('commutative', True), + ('imaginary', False), + ) ), + ), + # Implications of extended_real = False: + (('extended_real', False), set( ( + ('composite', False), + ('even', False), + ('extended_negative', False), + ('extended_nonnegative', False), + ('extended_nonpositive', False), + ('extended_nonzero', False), + ('extended_positive', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('noninteger', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('zero', False), + ) ), + ), + # Implications of finite = True: + (('finite', True), set( ( + ('infinite', False), + ) ), + ), + # Implications of finite = False: + (('finite', False), set( ( + ('algebraic', False), + ('complex', False), + ('composite', False), + ('even', False), + ('imaginary', False), + ('infinite', True), + ('integer', False), + ('irrational', False), + ('negative', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of hermitian = True: + (('hermitian', True), set( ( + ) ), + ), + # Implications of hermitian = False: + (('hermitian', False), set( ( + ('composite', False), + ('even', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('zero', False), + ) ), + ), + # Implications of imaginary = True: + (('imaginary', True), set( ( + ('antihermitian', True), + ('commutative', True), + ('complex', True), + ('composite', False), + ('even', False), + ('extended_negative', False), + ('extended_nonnegative', False), + ('extended_nonpositive', False), + ('extended_nonzero', False), + ('extended_positive', False), + ('extended_real', False), + ('finite', True), + ('infinite', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('noninteger', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('zero', False), + ) ), + ), + # Implications of imaginary = False: + (('imaginary', False), set( ( + ) ), + ), + # Implications of infinite = True: + (('infinite', True), set( ( + ('algebraic', False), + ('complex', False), + ('composite', False), + ('even', False), + ('finite', False), + ('imaginary', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('real', False), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of infinite = False: + (('infinite', False), set( ( + ('finite', True), + ) ), + ), + # Implications of integer = True: + (('integer', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('irrational', False), + ('noninteger', False), + ('rational', True), + ('real', True), + ('transcendental', False), + ) ), + ), + # Implications of integer = False: + (('integer', False), set( ( + ('composite', False), + ('even', False), + ('odd', False), + ('prime', False), + ('zero', False), + ) ), + ), + # Implications of irrational = True: + (('irrational', True), set( ( + ('commutative', True), + ('complex', True), + ('composite', False), + ('even', False), + ('extended_nonzero', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('integer', False), + ('noninteger', True), + ('nonzero', True), + ('odd', False), + ('prime', False), + ('rational', False), + ('real', True), + ('zero', False), + ) ), + ), + # Implications of irrational = False: + (('irrational', False), set( ( + ) ), + ), + # Implications of negative = True: + (('negative', True), set( ( + ('commutative', True), + ('complex', True), + ('composite', False), + ('extended_negative', True), + ('extended_nonnegative', False), + ('extended_nonpositive', True), + ('extended_nonzero', True), + ('extended_positive', False), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('nonnegative', False), + ('nonpositive', True), + ('nonzero', True), + ('positive', False), + ('prime', False), + ('real', True), + ('zero', False), + ) ), + ), + # Implications of negative = False: + (('negative', False), set( ( + ) ), + ), + # Implications of noninteger = True: + (('noninteger', True), set( ( + ('commutative', True), + ('composite', False), + ('even', False), + ('extended_nonzero', True), + ('extended_real', True), + ('imaginary', False), + ('integer', False), + ('odd', False), + ('prime', False), + ('zero', False), + ) ), + ), + # Implications of noninteger = False: + (('noninteger', False), set( ( + ) ), + ), + # Implications of nonnegative = True: + (('nonnegative', True), set( ( + ('commutative', True), + ('complex', True), + ('extended_negative', False), + ('extended_nonnegative', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('negative', False), + ('real', True), + ) ), + ), + # Implications of nonnegative = False: + (('nonnegative', False), set( ( + ('composite', False), + ('positive', False), + ('prime', False), + ('zero', False), + ) ), + ), + # Implications of nonpositive = True: + (('nonpositive', True), set( ( + ('commutative', True), + ('complex', True), + ('composite', False), + ('extended_nonpositive', True), + ('extended_positive', False), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('positive', False), + ('prime', False), + ('real', True), + ) ), + ), + # Implications of nonpositive = False: + (('nonpositive', False), set( ( + ('negative', False), + ('zero', False), + ) ), + ), + # Implications of nonzero = True: + (('nonzero', True), set( ( + ('commutative', True), + ('complex', True), + ('extended_nonzero', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('real', True), + ('zero', False), + ) ), + ), + # Implications of nonzero = False: + (('nonzero', False), set( ( + ('composite', False), + ('negative', False), + ('positive', False), + ('prime', False), + ) ), + ), + # Implications of odd = True: + (('odd', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('even', False), + ('extended_nonzero', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('integer', True), + ('irrational', False), + ('noninteger', False), + ('nonzero', True), + ('rational', True), + ('real', True), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of odd = False: + (('odd', False), set( ( + ) ), + ), + # Implications of positive = True: + (('positive', True), set( ( + ('commutative', True), + ('complex', True), + ('extended_negative', False), + ('extended_nonnegative', True), + ('extended_nonpositive', False), + ('extended_nonzero', True), + ('extended_positive', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('negative', False), + ('nonnegative', True), + ('nonpositive', False), + ('nonzero', True), + ('real', True), + ('zero', False), + ) ), + ), + # Implications of positive = False: + (('positive', False), set( ( + ('composite', False), + ('prime', False), + ) ), + ), + # Implications of prime = True: + (('prime', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('composite', False), + ('extended_negative', False), + ('extended_nonnegative', True), + ('extended_nonpositive', False), + ('extended_nonzero', True), + ('extended_positive', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('integer', True), + ('irrational', False), + ('negative', False), + ('noninteger', False), + ('nonnegative', True), + ('nonpositive', False), + ('nonzero', True), + ('positive', True), + ('rational', True), + ('real', True), + ('transcendental', False), + ('zero', False), + ) ), + ), + # Implications of prime = False: + (('prime', False), set( ( + ) ), + ), + # Implications of rational = True: + (('rational', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('irrational', False), + ('real', True), + ('transcendental', False), + ) ), + ), + # Implications of rational = False: + (('rational', False), set( ( + ('composite', False), + ('even', False), + ('integer', False), + ('odd', False), + ('prime', False), + ('zero', False), + ) ), + ), + # Implications of real = True: + (('real', True), set( ( + ('commutative', True), + ('complex', True), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ) ), + ), + # Implications of real = False: + (('real', False), set( ( + ('composite', False), + ('even', False), + ('integer', False), + ('irrational', False), + ('negative', False), + ('nonnegative', False), + ('nonpositive', False), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', False), + ('zero', False), + ) ), + ), + # Implications of transcendental = True: + (('transcendental', True), set( ( + ('algebraic', False), + ('commutative', True), + ('complex', True), + ('composite', False), + ('even', False), + ('finite', True), + ('infinite', False), + ('integer', False), + ('odd', False), + ('prime', False), + ('rational', False), + ('zero', False), + ) ), + ), + # Implications of transcendental = False: + (('transcendental', False), set( ( + ) ), + ), + # Implications of zero = True: + (('zero', True), set( ( + ('algebraic', True), + ('commutative', True), + ('complex', True), + ('composite', False), + ('even', True), + ('extended_negative', False), + ('extended_nonnegative', True), + ('extended_nonpositive', True), + ('extended_nonzero', False), + ('extended_positive', False), + ('extended_real', True), + ('finite', True), + ('hermitian', True), + ('imaginary', False), + ('infinite', False), + ('integer', True), + ('irrational', False), + ('negative', False), + ('noninteger', False), + ('nonnegative', True), + ('nonpositive', True), + ('nonzero', False), + ('odd', False), + ('positive', False), + ('prime', False), + ('rational', True), + ('real', True), + ('transcendental', False), + ) ), + ), + # Implications of zero = False: + (('zero', False), set( ( + ) ), + ), + ] ) # full_implications + + +prereq = { + + # facts that could determine the value of algebraic + 'algebraic': { + 'commutative', + 'complex', + 'composite', + 'even', + 'finite', + 'infinite', + 'integer', + 'odd', + 'prime', + 'rational', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of antihermitian + 'antihermitian': { + 'imaginary', + }, + + # facts that could determine the value of commutative + 'commutative': { + 'algebraic', + 'complex', + 'composite', + 'even', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'imaginary', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of complex + 'complex': { + 'algebraic', + 'commutative', + 'composite', + 'even', + 'finite', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of composite + 'composite': { + 'algebraic', + 'commutative', + 'complex', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of even + 'even': { + 'algebraic', + 'commutative', + 'complex', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'noninteger', + 'odd', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of extended_negative + 'extended_negative': { + 'commutative', + 'composite', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'imaginary', + 'negative', + 'nonnegative', + 'positive', + 'prime', + 'zero', + }, + + # facts that could determine the value of extended_nonnegative + 'extended_nonnegative': { + 'commutative', + 'composite', + 'extended_negative', + 'extended_positive', + 'extended_real', + 'imaginary', + 'negative', + 'nonnegative', + 'positive', + 'prime', + 'zero', + }, + + # facts that could determine the value of extended_nonpositive + 'extended_nonpositive': { + 'commutative', + 'composite', + 'extended_negative', + 'extended_positive', + 'extended_real', + 'imaginary', + 'negative', + 'nonpositive', + 'positive', + 'prime', + 'zero', + }, + + # facts that could determine the value of extended_nonzero + 'extended_nonzero': { + 'commutative', + 'composite', + 'extended_negative', + 'extended_positive', + 'extended_real', + 'imaginary', + 'irrational', + 'negative', + 'noninteger', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'zero', + }, + + # facts that could determine the value of extended_positive + 'extended_positive': { + 'commutative', + 'composite', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_real', + 'imaginary', + 'negative', + 'nonpositive', + 'positive', + 'prime', + 'zero', + }, + + # facts that could determine the value of extended_real + 'extended_real': { + 'commutative', + 'composite', + 'even', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'imaginary', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'zero', + }, + + # facts that could determine the value of finite + 'finite': { + 'algebraic', + 'complex', + 'composite', + 'even', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of hermitian + 'hermitian': { + 'composite', + 'even', + 'integer', + 'irrational', + 'negative', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'zero', + }, + + # facts that could determine the value of imaginary + 'imaginary': { + 'antihermitian', + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'zero', + }, + + # facts that could determine the value of infinite + 'infinite': { + 'algebraic', + 'complex', + 'composite', + 'even', + 'finite', + 'imaginary', + 'integer', + 'irrational', + 'negative', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of integer + 'integer': { + 'algebraic', + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'irrational', + 'noninteger', + 'odd', + 'prime', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of irrational + 'irrational': { + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'odd', + 'prime', + 'rational', + 'real', + 'zero', + }, + + # facts that could determine the value of negative + 'negative': { + 'commutative', + 'complex', + 'composite', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'positive', + 'prime', + 'real', + 'zero', + }, + + # facts that could determine the value of noninteger + 'noninteger': { + 'commutative', + 'composite', + 'even', + 'extended_real', + 'imaginary', + 'integer', + 'irrational', + 'odd', + 'prime', + 'zero', + }, + + # facts that could determine the value of nonnegative + 'nonnegative': { + 'commutative', + 'complex', + 'composite', + 'extended_negative', + 'extended_nonnegative', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'negative', + 'positive', + 'prime', + 'real', + 'zero', + }, + + # facts that could determine the value of nonpositive + 'nonpositive': { + 'commutative', + 'complex', + 'composite', + 'extended_nonpositive', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'negative', + 'positive', + 'prime', + 'real', + 'zero', + }, + + # facts that could determine the value of nonzero + 'nonzero': { + 'commutative', + 'complex', + 'composite', + 'extended_nonzero', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'irrational', + 'negative', + 'odd', + 'positive', + 'prime', + 'real', + 'zero', + }, + + # facts that could determine the value of odd + 'odd': { + 'algebraic', + 'commutative', + 'complex', + 'even', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'noninteger', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of positive + 'positive': { + 'commutative', + 'complex', + 'composite', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'negative', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'prime', + 'real', + 'zero', + }, + + # facts that could determine the value of prime + 'prime': { + 'algebraic', + 'commutative', + 'complex', + 'composite', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'positive', + 'rational', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of rational + 'rational': { + 'algebraic', + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'odd', + 'prime', + 'real', + 'transcendental', + 'zero', + }, + + # facts that could determine the value of real + 'real': { + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'zero', + }, + + # facts that could determine the value of transcendental + 'transcendental': { + 'algebraic', + 'commutative', + 'complex', + 'composite', + 'even', + 'finite', + 'infinite', + 'integer', + 'odd', + 'prime', + 'rational', + 'zero', + }, + + # facts that could determine the value of zero + 'zero': { + 'algebraic', + 'commutative', + 'complex', + 'composite', + 'even', + 'extended_negative', + 'extended_nonnegative', + 'extended_nonpositive', + 'extended_nonzero', + 'extended_positive', + 'extended_real', + 'finite', + 'hermitian', + 'imaginary', + 'infinite', + 'integer', + 'irrational', + 'negative', + 'noninteger', + 'nonnegative', + 'nonpositive', + 'nonzero', + 'odd', + 'positive', + 'prime', + 'rational', + 'real', + 'transcendental', + }, + +} # prereq + + +# Note: the order of the beta rules is used in the beta_triggers +beta_rules = [ + + # Rules implying composite = True + ({('even', True), ('positive', True), ('prime', False)}, + ('composite', True)), + + # Rules implying even = False + ({('composite', False), ('positive', True), ('prime', False)}, + ('even', False)), + + # Rules implying even = True + ({('integer', True), ('odd', False)}, + ('even', True)), + + # Rules implying extended_negative = True + ({('extended_positive', False), ('extended_real', True), ('zero', False)}, + ('extended_negative', True)), + ({('extended_nonpositive', True), ('extended_nonzero', True)}, + ('extended_negative', True)), + + # Rules implying extended_nonnegative = True + ({('extended_negative', False), ('extended_real', True)}, + ('extended_nonnegative', True)), + + # Rules implying extended_nonpositive = True + ({('extended_positive', False), ('extended_real', True)}, + ('extended_nonpositive', True)), + + # Rules implying extended_nonzero = True + ({('extended_real', True), ('zero', False)}, + ('extended_nonzero', True)), + + # Rules implying extended_positive = True + ({('extended_negative', False), ('extended_real', True), ('zero', False)}, + ('extended_positive', True)), + ({('extended_nonnegative', True), ('extended_nonzero', True)}, + ('extended_positive', True)), + + # Rules implying extended_real = False + ({('infinite', False), ('real', False)}, + ('extended_real', False)), + ({('extended_negative', False), ('extended_positive', False), ('zero', False)}, + ('extended_real', False)), + + # Rules implying infinite = True + ({('extended_real', True), ('real', False)}, + ('infinite', True)), + + # Rules implying irrational = True + ({('rational', False), ('real', True)}, + ('irrational', True)), + + # Rules implying negative = True + ({('positive', False), ('real', True), ('zero', False)}, + ('negative', True)), + ({('nonpositive', True), ('nonzero', True)}, + ('negative', True)), + ({('extended_negative', True), ('finite', True)}, + ('negative', True)), + + # Rules implying noninteger = True + ({('extended_real', True), ('integer', False)}, + ('noninteger', True)), + + # Rules implying nonnegative = True + ({('negative', False), ('real', True)}, + ('nonnegative', True)), + ({('extended_nonnegative', True), ('finite', True)}, + ('nonnegative', True)), + + # Rules implying nonpositive = True + ({('positive', False), ('real', True)}, + ('nonpositive', True)), + ({('extended_nonpositive', True), ('finite', True)}, + ('nonpositive', True)), + + # Rules implying nonzero = True + ({('extended_nonzero', True), ('finite', True)}, + ('nonzero', True)), + + # Rules implying odd = True + ({('even', False), ('integer', True)}, + ('odd', True)), + + # Rules implying positive = False + ({('composite', False), ('even', True), ('prime', False)}, + ('positive', False)), + + # Rules implying positive = True + ({('negative', False), ('real', True), ('zero', False)}, + ('positive', True)), + ({('nonnegative', True), ('nonzero', True)}, + ('positive', True)), + ({('extended_positive', True), ('finite', True)}, + ('positive', True)), + + # Rules implying prime = True + ({('composite', False), ('even', True), ('positive', True)}, + ('prime', True)), + + # Rules implying real = False + ({('negative', False), ('positive', False), ('zero', False)}, + ('real', False)), + + # Rules implying real = True + ({('extended_real', True), ('infinite', False)}, + ('real', True)), + ({('extended_real', True), ('finite', True)}, + ('real', True)), + + # Rules implying transcendental = True + ({('algebraic', False), ('complex', True)}, + ('transcendental', True)), + + # Rules implying zero = True + ({('extended_negative', False), ('extended_positive', False), ('extended_real', True)}, + ('zero', True)), + ({('negative', False), ('positive', False), ('real', True)}, + ('zero', True)), + ({('extended_nonnegative', True), ('extended_nonpositive', True)}, + ('zero', True)), + ({('nonnegative', True), ('nonpositive', True)}, + ('zero', True)), + +] # beta_rules +beta_triggers = { + ('algebraic', False): [32, 11, 3, 8, 29, 14, 25, 13, 17, 7], + ('algebraic', True): [10, 30, 31, 27, 16, 21, 19, 22], + ('antihermitian', False): [], + ('commutative', False): [], + ('complex', False): [10, 12, 11, 3, 8, 17, 7], + ('complex', True): [32, 10, 30, 31, 27, 16, 21, 19, 22], + ('composite', False): [1, 28, 24], + ('composite', True): [23, 2], + ('even', False): [23, 11, 3, 8, 29, 14, 25, 7], + ('even', True): [3, 33, 8, 6, 5, 14, 34, 25, 20, 18, 27, 16, 21, 19, 22, 0, 28, 24, 7], + ('extended_negative', False): [11, 33, 8, 5, 29, 34, 25, 18], + ('extended_negative', True): [30, 12, 31, 29, 14, 20, 16, 21, 22, 17], + ('extended_nonnegative', False): [11, 3, 6, 29, 14, 20, 7], + ('extended_nonnegative', True): [30, 12, 31, 33, 8, 9, 6, 29, 34, 25, 18, 19, 35, 17, 7], + ('extended_nonpositive', False): [11, 8, 5, 29, 25, 18, 7], + ('extended_nonpositive', True): [30, 12, 31, 3, 33, 4, 5, 29, 14, 34, 20, 21, 35, 17, 7], + ('extended_nonzero', False): [11, 33, 6, 5, 29, 34, 20, 18], + ('extended_nonzero', True): [30, 12, 31, 3, 8, 4, 9, 6, 5, 29, 14, 25, 22, 17], + ('extended_positive', False): [11, 3, 33, 6, 29, 14, 34, 20], + ('extended_positive', True): [30, 12, 31, 29, 25, 18, 27, 19, 22, 17], + ('extended_real', False): [], + ('extended_real', True): [30, 12, 31, 3, 33, 8, 6, 5, 17, 7], + ('finite', False): [11, 3, 8, 17, 7], + ('finite', True): [10, 30, 31, 27, 16, 21, 19, 22], + ('hermitian', False): [10, 12, 11, 3, 8, 17, 7], + ('imaginary', True): [32], + ('infinite', False): [10, 30, 31, 27, 16, 21, 19, 22], + ('infinite', True): [11, 3, 8, 17, 7], + ('integer', False): [11, 3, 8, 29, 14, 25, 17, 7], + ('integer', True): [23, 2, 3, 33, 8, 6, 5, 14, 34, 25, 20, 18, 27, 16, 21, 19, 22, 7], + ('irrational', True): [32, 3, 8, 4, 9, 6, 5, 14, 25, 15, 26, 20, 18, 27, 16, 21, 19], + ('negative', False): [29, 34, 25, 18], + ('negative', True): [32, 13, 17], + ('noninteger', True): [30, 12, 31, 3, 8, 4, 9, 6, 5, 29, 14, 25, 22], + ('nonnegative', False): [11, 3, 8, 29, 14, 20, 7], + ('nonnegative', True): [32, 33, 8, 9, 6, 34, 25, 26, 20, 27, 21, 22, 35, 36, 13, 17, 7], + ('nonpositive', False): [11, 3, 8, 29, 25, 18, 7], + ('nonpositive', True): [32, 3, 33, 4, 5, 14, 34, 15, 18, 16, 19, 22, 35, 36, 13, 17, 7], + ('nonzero', False): [29, 34, 20, 18], + ('nonzero', True): [32, 3, 8, 4, 9, 6, 5, 14, 25, 15, 26, 20, 18, 27, 16, 21, 19, 13, 17], + ('odd', False): [2], + ('odd', True): [3, 8, 4, 9, 6, 5, 14, 25, 15, 26, 20, 18, 27, 16, 21, 19], + ('positive', False): [29, 14, 34, 20], + ('positive', True): [32, 0, 1, 28, 13, 17], + ('prime', False): [0, 1, 24], + ('prime', True): [23, 2], + ('rational', False): [11, 3, 8, 29, 14, 25, 13, 17, 7], + ('rational', True): [3, 33, 8, 6, 5, 14, 34, 25, 20, 18, 27, 16, 21, 19, 22, 17, 7], + ('real', False): [10, 12, 11, 3, 8, 17, 7], + ('real', True): [32, 3, 33, 8, 6, 5, 14, 34, 25, 20, 18, 27, 16, 21, 19, 22, 13, 17, 7], + ('transcendental', True): [10, 30, 31, 11, 3, 8, 29, 14, 25, 27, 16, 21, 19, 22, 13, 17, 7], + ('zero', False): [11, 3, 8, 29, 14, 25, 7], + ('zero', True): [], +} # beta_triggers + + +generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications, + 'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers} diff --git a/phivenv/Lib/site-packages/sympy/core/backend.py b/phivenv/Lib/site-packages/sympy/core/backend.py new file mode 100644 index 0000000000000000000000000000000000000000..34a4e05a4a4ac50d0830960cb324871a20e9a12d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/backend.py @@ -0,0 +1,120 @@ +import os +USE_SYMENGINE = os.getenv('USE_SYMENGINE', '0') +USE_SYMENGINE = USE_SYMENGINE.lower() in ('1', 't', 'true') # type: ignore + +if USE_SYMENGINE: + from symengine import (Symbol, Integer, sympify as sympify_symengine, S, + SympifyError, exp, log, gamma, sqrt, I, E, pi, Matrix, + sin, cos, tan, cot, csc, sec, asin, acos, atan, acot, acsc, asec, + sinh, cosh, tanh, coth, asinh, acosh, atanh, acoth, + lambdify, symarray, diff, zeros, eye, diag, ones, + expand, Function, symbols, var, Add, Mul, Derivative, + ImmutableMatrix, MatrixBase, Rational, Basic) + from symengine.lib.symengine_wrapper import gcd as igcd + from symengine import AppliedUndef + + def sympify(a, *, strict=False): + """ + Notes + ===== + + SymEngine's ``sympify`` does not accept keyword arguments and is + therefore not compatible with SymPy's ``sympify`` with ``strict=True`` + (which ensures that only the types for which an explicit conversion has + been defined are converted). This wrapper adds an additional parameter + ``strict`` (with default ``False``) that will raise a ``SympifyError`` + if ``strict=True`` and the argument passed to the parameter ``a`` is a + string. + + See Also + ======== + + sympify: Converts an arbitrary expression to a type that can be used + inside SymPy. + + """ + # The parameter ``a`` is used for this function to keep compatibility + # with the SymEngine docstring. + if strict and isinstance(a, str): + raise SympifyError(a) + return sympify_symengine(a) + + # Keep the SymEngine docstring and append the additional "Notes" and "See + # Also" sections. Replacement of spaces is required to correctly format the + # indentation of the combined docstring. + sympify.__doc__ = ( + sympify_symengine.__doc__ + + sympify.__doc__.replace(' ', ' ') # type: ignore + ) +else: + from sympy.core.add import Add + from sympy.core.basic import Basic + from sympy.core.function import (diff, Function, AppliedUndef, + expand, Derivative) + from sympy.core.mul import Mul + from sympy.core.intfunc import igcd + from sympy.core.numbers import pi, I, Integer, Rational, E + from sympy.core.singleton import S + from sympy.core.symbol import Symbol, var, symbols + from sympy.core.sympify import SympifyError, sympify + from sympy.functions.elementary.exponential import log, exp + from sympy.functions.elementary.hyperbolic import (coth, sinh, + acosh, acoth, tanh, asinh, atanh, cosh) + from sympy.functions.elementary.miscellaneous import sqrt + from sympy.functions.elementary.trigonometric import (csc, + asec, cos, atan, sec, acot, asin, tan, sin, cot, acsc, acos) + from sympy.functions.special.gamma_functions import gamma + from sympy.matrices.dense import (eye, zeros, diag, Matrix, + ones, symarray) + from sympy.matrices.immutable import ImmutableMatrix + from sympy.matrices.matrixbase import MatrixBase + from sympy.utilities.lambdify import lambdify + + +# +# XXX: Handling of immutable and mutable matrices in SymEngine is inconsistent +# with SymPy's matrix classes in at least SymEngine version 0.7.0. Until that +# is fixed the function below is needed for consistent behaviour when +# attempting to simplify a matrix. +# +# Expected behaviour of a SymPy mutable/immutable matrix .simplify() method: +# +# Matrix.simplify() : works in place, returns None +# ImmutableMatrix.simplify() : returns a simplified copy +# +# In SymEngine both mutable and immutable matrices simplify in place and return +# None. This is inconsistent with the matrix being "immutable" and also the +# returned None leads to problems in the mechanics module. +# +# The simplify function should not be used because simplify(M) sympifies the +# matrix M and the SymEngine matrices all sympify to SymPy matrices. If we want +# to work with SymEngine matrices then we need to use their .simplify() method +# but that method does not work correctly with immutable matrices. +# +# The _simplify_matrix function can be removed when the SymEngine bug is fixed. +# Since this should be a temporary problem we do not make this function part of +# the public API. +# +# SymEngine issue: https://github.com/symengine/symengine.py/issues/363 +# + +def _simplify_matrix(M): + """Return a simplified copy of the matrix M""" + if not isinstance(M, (Matrix, ImmutableMatrix)): + raise TypeError("The matrix M must be an instance of Matrix or ImmutableMatrix") + Mnew = M.as_mutable() # makes a copy if mutable + Mnew.simplify() + if isinstance(M, ImmutableMatrix): + Mnew = Mnew.as_immutable() + return Mnew + + +__all__ = [ + 'Symbol', 'Integer', 'sympify', 'S', 'SympifyError', 'exp', 'log', + 'gamma', 'sqrt', 'I', 'E', 'pi', 'Matrix', 'sin', 'cos', 'tan', 'cot', + 'csc', 'sec', 'asin', 'acos', 'atan', 'acot', 'acsc', 'asec', 'sinh', + 'cosh', 'tanh', 'coth', 'asinh', 'acosh', 'atanh', 'acoth', 'lambdify', + 'symarray', 'diff', 'zeros', 'eye', 'diag', 'ones', 'expand', 'Function', + 'symbols', 'var', 'Add', 'Mul', 'Derivative', 'ImmutableMatrix', + 'MatrixBase', 'Rational', 'Basic', 'igcd', 'AppliedUndef', +] diff --git a/phivenv/Lib/site-packages/sympy/core/basic.py b/phivenv/Lib/site-packages/sympy/core/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..92f6e710113ce0523ad15100abb0acd00f03f741 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/basic.py @@ -0,0 +1,2355 @@ +"""Base class for all the objects in SymPy""" +from __future__ import annotations + +from collections import Counter +from collections.abc import Mapping, Iterable +from itertools import zip_longest +from functools import cmp_to_key +from typing import TYPE_CHECKING, overload + +from .assumptions import _prepare_class_assumptions +from .cache import cacheit +from .sympify import _sympify, sympify, SympifyError, _external_converter +from .sorting import ordered +from .kind import Kind, UndefinedKind +from ._print_helpers import Printable + +from sympy.utilities.decorator import deprecated +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import iterable, numbered_symbols +from sympy.utilities.misc import filldedent, func_name + + +if TYPE_CHECKING: + from typing import ClassVar, TypeVar, Any + from typing_extensions import Self + from .assumptions import StdFactKB + from .symbol import Symbol + + Tbasic = TypeVar("Tbasic", bound='Basic') + + +def as_Basic(expr): + """Return expr as a Basic instance using strict sympify + or raise a TypeError; this is just a wrapper to _sympify, + raising a TypeError instead of a SympifyError.""" + try: + return _sympify(expr) + except SympifyError: + raise TypeError( + 'Argument must be a Basic object, not `%s`' % func_name( + expr)) + + +# Key for sorting commutative args in canonical order +# by name. This is used for canonical ordering of the +# args for Add and Mul *if* the names of both classes +# being compared appear here. Some things in this list +# are not spelled the same as their name so they do not, +# in effect, appear here. See Basic.compare. +ordering_of_classes = [ + # singleton numbers + 'Zero', 'One', 'Half', 'Infinity', 'NaN', 'NegativeOne', 'NegativeInfinity', + # numbers + 'Integer', 'Rational', 'Float', + # singleton symbols + 'Exp1', 'Pi', 'ImaginaryUnit', + # symbols + 'Symbol', 'Wild', + # arithmetic operations + 'Pow', 'Mul', 'Add', + # function values + 'Derivative', 'Integral', + # defined singleton functions + 'Abs', 'Sign', 'Sqrt', + 'Floor', 'Ceiling', + 'Re', 'Im', 'Arg', + 'Conjugate', + 'Exp', 'Log', + 'Sin', 'Cos', 'Tan', 'Cot', 'ASin', 'ACos', 'ATan', 'ACot', + 'Sinh', 'Cosh', 'Tanh', 'Coth', 'ASinh', 'ACosh', 'ATanh', 'ACoth', + 'RisingFactorial', 'FallingFactorial', + 'factorial', 'binomial', + 'Gamma', 'LowerGamma', 'UpperGamma', 'PolyGamma', + 'Erf', + # special polynomials + 'Chebyshev', 'Chebyshev2', + # undefined functions + 'Function', 'WildFunction', + # anonymous functions + 'Lambda', + # Landau O symbol + 'Order', + # relational operations + 'Equality', 'Unequality', 'StrictGreaterThan', 'StrictLessThan', + 'GreaterThan', 'LessThan', +] + +def _cmp_name(x: type, y: type) -> int: + """return -1, 0, 1 if the name of x is before that of y. + A string comparison is done if either name does not appear + in `ordering_of_classes`. This is the helper for + ``Basic.compare`` + + Examples + ======== + + >>> from sympy import cos, tan, sin + >>> from sympy.core import basic + >>> save = basic.ordering_of_classes + >>> basic.ordering_of_classes = () + >>> basic._cmp_name(cos, tan) + -1 + >>> basic.ordering_of_classes = ["tan", "sin", "cos"] + >>> basic._cmp_name(cos, tan) + 1 + >>> basic._cmp_name(sin, cos) + -1 + >>> basic.ordering_of_classes = save + + """ + n1 = x.__name__ + n2 = y.__name__ + if n1 == n2: + return 0 + + # If the other object is not a Basic subclass, then we are not equal to it. + if not issubclass(y, Basic): + return -1 + + UNKNOWN = len(ordering_of_classes) + 1 + try: + i1 = ordering_of_classes.index(n1) + except ValueError: + i1 = UNKNOWN + try: + i2 = ordering_of_classes.index(n2) + except ValueError: + i2 = UNKNOWN + if i1 == UNKNOWN and i2 == UNKNOWN: + return (n1 > n2) - (n1 < n2) + return (i1 > i2) - (i1 < i2) + + + +@cacheit +def _get_postprocessors(clsname, arg_type): + # Since only Add, Mul, Pow can be clsname, this cache + # is not quadratic. + postprocessors = set() + mappings = _get_postprocessors_for_type(arg_type) + for mapping in mappings: + f = mapping.get(clsname, None) + if f is not None: + postprocessors.update(f) + return postprocessors + +@cacheit +def _get_postprocessors_for_type(arg_type): + return tuple( + Basic._constructor_postprocessor_mapping[cls] + for cls in arg_type.mro() + if cls in Basic._constructor_postprocessor_mapping + ) + + +class Basic(Printable): + """ + Base class for all SymPy objects. + + Notes and conventions + ===================== + + 1) Always use ``.args``, when accessing parameters of some instance: + + >>> from sympy import cot + >>> from sympy.abc import x, y + + >>> cot(x).args + (x,) + + >>> cot(x).args[0] + x + + >>> (x*y).args + (x, y) + + >>> (x*y).args[1] + y + + + 2) Never use internal methods or variables (the ones prefixed with ``_``): + + >>> cot(x)._args # do not use this, use cot(x).args instead + (x,) + + + 3) By "SymPy object" we mean something that can be returned by + ``sympify``. But not all objects one encounters using SymPy are + subclasses of Basic. For example, mutable objects are not: + + >>> from sympy import Basic, Matrix, sympify + >>> A = Matrix([[1, 2], [3, 4]]).as_mutable() + >>> isinstance(A, Basic) + False + + >>> B = sympify(A) + >>> isinstance(B, Basic) + True + """ + __slots__ = ('_mhash', # hash value + '_args', # arguments + '_assumptions' + ) + + _args: tuple[Basic, ...] + _mhash: int | None + + @property + def __sympy__(self): + return True + + def __init_subclass__(cls): + # Initialize the default_assumptions FactKB and also any assumptions + # property methods. This method will only be called for subclasses of + # Basic but not for Basic itself so we call + # _prepare_class_assumptions(Basic) below the class definition. + super().__init_subclass__() + _prepare_class_assumptions(cls) + + # To be overridden with True in the appropriate subclasses + is_number = False + is_Atom = False + is_Symbol = False + is_symbol = False + is_Indexed = False + is_Dummy = False + is_Wild = False + is_Function = False + is_Add = False + is_Mul = False + is_Pow = False + is_Number = False + is_Float = False + is_Rational = False + is_Integer = False + is_NumberSymbol = False + is_Order = False + is_Derivative = False + is_Piecewise = False + is_Poly = False + is_AlgebraicNumber = False + is_Relational = False + is_Equality = False + is_Boolean = False + is_Not = False + is_Matrix = False + is_Vector = False + is_Point = False + is_MatAdd = False + is_MatMul = False + + default_assumptions: ClassVar[StdFactKB] + + is_composite: bool | None + is_noninteger: bool | None + is_extended_positive: bool | None + is_negative: bool | None + is_complex: bool | None + is_extended_nonpositive: bool | None + is_integer: bool | None + is_positive: bool | None + is_rational: bool | None + is_extended_nonnegative: bool | None + is_infinite: bool | None + is_antihermitian: bool | None + is_extended_negative: bool | None + is_extended_real: bool | None + is_finite: bool | None + is_polar: bool | None + is_imaginary: bool | None + is_transcendental: bool | None + is_extended_nonzero: bool | None + is_nonzero: bool | None + is_odd: bool | None + is_algebraic: bool | None + is_prime: bool | None + is_commutative: bool | None + is_nonnegative: bool | None + is_nonpositive: bool | None + is_hermitian: bool | None + is_irrational: bool | None + is_real: bool | None + is_zero: bool | None + is_even: bool | None + + kind: Kind = UndefinedKind + + def __new__(cls, *args): + obj = object.__new__(cls) + obj._assumptions = cls.default_assumptions + obj._mhash = None # will be set by __hash__ method. + + obj._args = args # all items in args must be Basic objects + return obj + + def copy(self): + return self.func(*self.args) + + def __getnewargs__(self): + return self.args + + def __getstate__(self): + return None + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + def __reduce_ex__(self, protocol): + if protocol < 2: + msg = "Only pickle protocol 2 or higher is supported by SymPy" + raise NotImplementedError(msg) + return super().__reduce_ex__(protocol) + + def __hash__(self) -> int: + # hash cannot be cached using cache_it because infinite recurrence + # occurs as hash is needed for setting cache dictionary keys + h = self._mhash + if h is None: + h = hash((type(self).__name__,) + self._hashable_content()) + self._mhash = h + return h + + def _hashable_content(self): + """Return a tuple of information about self that can be used to + compute the hash. If a class defines additional attributes, + like ``name`` in Symbol, then this method should be updated + accordingly to return such relevant attributes. + + Defining more than _hashable_content is necessary if __eq__ has + been defined by a class. See note about this in Basic.__eq__.""" + return self._args + + @property + def assumptions0(self): + """ + Return object `type` assumptions. + + For example: + + Symbol('x', real=True) + Symbol('x', integer=True) + + are different objects. In other words, besides Python type (Symbol in + this case), the initial assumptions are also forming their typeinfo. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.abc import x + >>> x.assumptions0 + {'commutative': True} + >>> x = Symbol("x", positive=True) + >>> x.assumptions0 + {'commutative': True, 'complex': True, 'extended_negative': False, + 'extended_nonnegative': True, 'extended_nonpositive': False, + 'extended_nonzero': True, 'extended_positive': True, 'extended_real': + True, 'finite': True, 'hermitian': True, 'imaginary': False, + 'infinite': False, 'negative': False, 'nonnegative': True, + 'nonpositive': False, 'nonzero': True, 'positive': True, 'real': + True, 'zero': False} + """ + return {} + + def compare(self, other): + """ + Return -1, 0, 1 if the object is less than, equal, + or greater than other in a canonical sense. + Non-Basic are always greater than Basic. + If both names of the classes being compared appear + in the `ordering_of_classes` then the ordering will + depend on the appearance of the names there. + If either does not appear in that list, then the + comparison is based on the class name. + If the names are the same then a comparison is made + on the length of the hashable content. + Items of the equal-lengthed contents are then + successively compared using the same rules. If there + is never a difference then 0 is returned. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> x.compare(y) + -1 + >>> x.compare(x) + 0 + >>> y.compare(x) + 1 + + """ + # all redefinitions of __cmp__ method should start with the + # following lines: + if self is other: + return 0 + n1 = self.__class__ + n2 = other.__class__ + c = _cmp_name(n1, n2) + if c: + return c + # + st = self._hashable_content() + ot = other._hashable_content() + len_st = len(st) + len_ot = len(ot) + c = (len_st > len_ot) - (len_st < len_ot) + if c: + return c + for l, r in zip(st, ot): + if isinstance(l, Basic): + c = l.compare(r) + elif isinstance(l, frozenset): + l = Basic(*l) if isinstance(l, frozenset) else l + r = Basic(*r) if isinstance(r, frozenset) else r + c = l.compare(r) + else: + c = (l > r) - (l < r) + if c: + return c + return 0 + + @classmethod + def fromiter(cls, args, **assumptions): + """ + Create a new object from an iterable. + + This is a convenience function that allows one to create objects from + any iterable, without having to convert to a list or tuple first. + + Examples + ======== + + >>> from sympy import Tuple + >>> Tuple.fromiter(i for i in range(5)) + (0, 1, 2, 3, 4) + + """ + return cls(*tuple(args), **assumptions) + + @classmethod + def class_key(cls) -> tuple[int, int, str]: + """Nice order of classes.""" + return 5, 0, cls.__name__ + + @cacheit + def sort_key(self, order=None): + """ + Return a sort key. + + Examples + ======== + + >>> from sympy import S, I + + >>> sorted([S(1)/2, I, -I], key=lambda x: x.sort_key()) + [1/2, -I, I] + + >>> S("[x, 1/x, 1/x**2, x**2, x**(1/2), x**(1/4), x**(3/2)]") + [x, 1/x, x**(-2), x**2, sqrt(x), x**(1/4), x**(3/2)] + >>> sorted(_, key=lambda x: x.sort_key()) + [x**(-2), 1/x, x**(1/4), sqrt(x), x, x**(3/2), x**2] + + """ + + # XXX: remove this when issue 5169 is fixed + def inner_key(arg): + if isinstance(arg, Basic): + return arg.sort_key(order) + else: + return arg + + args = self._sorted_args + args = len(args), tuple([inner_key(arg) for arg in args]) + return self.class_key(), args, S.One.sort_key(), S.One + + def _do_eq_sympify(self, other): + """Returns a boolean indicating whether a == b when either a + or b is not a Basic. This is only done for types that were either + added to `converter` by a 3rd party or when the object has `_sympy_` + defined. This essentially reuses the code in `_sympify` that is + specific for this use case. Non-user defined types that are meant + to work with SymPy should be handled directly in the __eq__ methods + of the `Basic` classes it could equate to and not be converted. Note + that after conversion, `==` is used again since it is not + necessarily clear whether `self` or `other`'s __eq__ method needs + to be used.""" + for superclass in type(other).__mro__: + conv = _external_converter.get(superclass) + if conv is not None: + return self == conv(other) + if hasattr(other, '_sympy_'): + return self == other._sympy_() + return NotImplemented + + def __eq__(self, other): + """Return a boolean indicating whether a == b on the basis of + their symbolic trees. + + This is the same as a.compare(b) == 0 but faster. + + Notes + ===== + + If a class that overrides __eq__() needs to retain the + implementation of __hash__() from a parent class, the + interpreter must be told this explicitly by setting + __hash__ : Callable[[object], int] = .__hash__. + Otherwise the inheritance of __hash__() will be blocked, + just as if __hash__ had been explicitly set to None. + + References + ========== + + from https://docs.python.org/dev/reference/datamodel.html#object.__hash__ + """ + if self is other: + return True + + if not isinstance(other, Basic): + return self._do_eq_sympify(other) + + # check for pure number expr + if not (self.is_Number and other.is_Number) and ( + type(self) != type(other)): + return False + a, b = self._hashable_content(), other._hashable_content() + if a != b: + return False + # check number *in* an expression + for a, b in zip(a, b): + if not isinstance(a, Basic): + continue + if a.is_Number and type(a) != type(b): + return False + return True + + def __ne__(self, other): + """``a != b`` -> Compare two symbolic trees and see whether they are different + + this is the same as: + + ``a.compare(b) != 0`` + + but faster + """ + return not self == other + + def dummy_eq(self, other, symbol=None): + """ + Compare two expressions and handle dummy symbols. + + Examples + ======== + + >>> from sympy import Dummy + >>> from sympy.abc import x, y + + >>> u = Dummy('u') + + >>> (u**2 + 1).dummy_eq(x**2 + 1) + True + >>> (u**2 + 1) == (x**2 + 1) + False + + >>> (u**2 + y).dummy_eq(x**2 + y, x) + True + >>> (u**2 + y).dummy_eq(x**2 + y, y) + False + + """ + s = self.as_dummy() + o = _sympify(other) + o = o.as_dummy() + + dummy_symbols = [i for i in s.free_symbols if i.is_Dummy] + + if len(dummy_symbols) == 1: + dummy = dummy_symbols.pop() + else: + return s == o + + if symbol is None: + symbols = o.free_symbols + + if len(symbols) == 1: + symbol = symbols.pop() + else: + return s == o + + tmp = dummy.__class__() + + return s.xreplace({dummy: tmp}) == o.xreplace({symbol: tmp}) + + @overload + def atoms(self) -> set[Basic]: ... + @overload + def atoms(self, *types: Tbasic | type[Tbasic]) -> set[Tbasic]: ... + + def atoms(self, *types: Tbasic | type[Tbasic]) -> set[Basic] | set[Tbasic]: + """Returns the atoms that form the current object. + + By default, only objects that are truly atomic and cannot + be divided into smaller pieces are returned: symbols, numbers, + and number symbols like I and pi. It is possible to request + atoms of any type, however, as demonstrated below. + + Examples + ======== + + >>> from sympy import I, pi, sin + >>> from sympy.abc import x, y + >>> (1 + x + 2*sin(y + I*pi)).atoms() + {1, 2, I, pi, x, y} + + If one or more types are given, the results will contain only + those types of atoms. + + >>> from sympy import Number, NumberSymbol, Symbol + >>> (1 + x + 2*sin(y + I*pi)).atoms(Symbol) + {x, y} + + >>> (1 + x + 2*sin(y + I*pi)).atoms(Number) + {1, 2} + + >>> (1 + x + 2*sin(y + I*pi)).atoms(Number, NumberSymbol) + {1, 2, pi} + + >>> (1 + x + 2*sin(y + I*pi)).atoms(Number, NumberSymbol, I) + {1, 2, I, pi} + + Note that I (imaginary unit) and zoo (complex infinity) are special + types of number symbols and are not part of the NumberSymbol class. + + The type can be given implicitly, too: + + >>> (1 + x + 2*sin(y + I*pi)).atoms(x) # x is a Symbol + {x, y} + + Be careful to check your assumptions when using the implicit option + since ``S(1).is_Integer = True`` but ``type(S(1))`` is ``One``, a special type + of SymPy atom, while ``type(S(2))`` is type ``Integer`` and will find all + integers in an expression: + + >>> from sympy import S + >>> (1 + x + 2*sin(y + I*pi)).atoms(S(1)) + {1} + + >>> (1 + x + 2*sin(y + I*pi)).atoms(S(2)) + {1, 2} + + Finally, arguments to atoms() can select more than atomic atoms: any + SymPy type (loaded in core/__init__.py) can be listed as an argument + and those types of "atoms" as found in scanning the arguments of the + expression recursively: + + >>> from sympy import Function, Mul + >>> from sympy.core.function import AppliedUndef + >>> f = Function('f') + >>> (1 + f(x) + 2*sin(y + I*pi)).atoms(Function) + {f(x), sin(y + I*pi)} + >>> (1 + f(x) + 2*sin(y + I*pi)).atoms(AppliedUndef) + {f(x)} + + >>> (1 + x + 2*sin(y + I*pi)).atoms(Mul) + {I*pi, 2*sin(y + I*pi)} + + """ + nodes = _preorder_traversal(self) + if types: + types2 = tuple([t if isinstance(t, type) else type(t) for t in types]) + return {node for node in nodes if isinstance(node, types2)} + else: + return {node for node in nodes if not node.args} + + @property + def free_symbols(self) -> set[Basic]: + """Return from the atoms of self those which are free symbols. + + Not all free symbols are ``Symbol`` (see examples) + + For most expressions, all symbols are free symbols. For some classes + this is not true. e.g. Integrals use Symbols for the dummy variables + which are bound variables, so Integral has a method to return all + symbols except those. Derivative keeps track of symbols with respect + to which it will perform a derivative; those are + bound variables, too, so it has its own free_symbols method. + + Any other method that uses bound variables should implement a + free_symbols method. + + Examples + ======== + + >>> from sympy import Derivative, Integral, IndexedBase + >>> from sympy.abc import x, y, n + >>> (x + 1).free_symbols + {x} + >>> Integral(x, y).free_symbols + {x, y} + + Not all free symbols are actually symbols: + + >>> IndexedBase('F')[0].free_symbols + {F, F[0]} + + The symbols of differentiation are not included unless they + appear in the expression being differentiated. + + >>> Derivative(x + y, y).free_symbols + {x, y} + >>> Derivative(x, y).free_symbols + {x} + >>> Derivative(x, (y, n)).free_symbols + {n, x} + + If you want to know if a symbol is in the variables of the + Derivative you can do so as follows: + + >>> Derivative(x, y).has_free(y) + True + """ + empty: set[Basic] = set() + return empty.union(*(a.free_symbols for a in self.args)) + + @property + def expr_free_symbols(self): + sympy_deprecation_warning(""" + The expr_free_symbols property is deprecated. Use free_symbols to get + the free symbols of an expression. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-expr-free-symbols") + return set() + + def as_dummy(self) -> "Self": + """Return the expression with any objects having structurally + bound symbols replaced with unique, canonical symbols within + the object in which they appear and having only the default + assumption for commutativity being True. When applied to a + symbol a new symbol having only the same commutativity will be + returned. + + Examples + ======== + + >>> from sympy import Integral, Symbol + >>> from sympy.abc import x + >>> r = Symbol('r', real=True) + >>> Integral(r, (r, x)).as_dummy() + Integral(_0, (_0, x)) + >>> _.variables[0].is_real is None + True + >>> r.as_dummy() + _r + + Notes + ===== + + Any object that has structurally bound variables should have + a property, ``bound_symbols`` that returns those symbols + appearing in the object. + """ + from .symbol import Dummy, Symbol + def can(x): + # mask free that shadow bound + free = x.free_symbols + bound = set(x.bound_symbols) + d = {i: Dummy() for i in bound & free} + x = x.subs(d) + # replace bound with canonical names + x = x.xreplace(x.canonical_variables) + # return after undoing masking + return x.xreplace({v: k for k, v in d.items()}) + if not self.has(Symbol): + return self + return self.replace( + lambda x: hasattr(x, 'bound_symbols'), + can, + simultaneous=False) # type:ignore + + @property + def canonical_variables(self) -> dict[Basic, Symbol]: + """Return a dictionary mapping any variable defined in + ``self.bound_symbols`` to Symbols that do not clash + with any free symbols in the expression. + + Examples + ======== + + >>> from sympy import Lambda + >>> from sympy.abc import x + >>> Lambda(x, 2*x).canonical_variables + {x: _0} + """ + bound: list[Basic] | None = getattr(self, 'bound_symbols', None) + if bound is None: + return {} + dums = numbered_symbols('_') + reps = {} + # watch out for free symbol that are not in bound symbols; + # those that are in bound symbols are about to get changed + + # XXX: free_symbols only returns particular kinds of expressions that + # generally have a .name attribute. There is not a proper class/type + # that represents this. + names = {i.name for i in self.free_symbols - set(bound)} # type: ignore + for b in bound: + d = next(dums) + if b.is_Symbol: + while d.name in names: + d = next(dums) + reps[b] = d + return reps + + def rcall(self, *args): + """Apply on the argument recursively through the expression tree. + + This method is used to simulate a common abuse of notation for + operators. For instance, in SymPy the following will not work: + + ``(x+Lambda(y, 2*y))(z) == x+2*z``, + + however, you can use: + + >>> from sympy import Lambda + >>> from sympy.abc import x, y, z + >>> (x + Lambda(y, 2*y)).rcall(z) + x + 2*z + """ + if callable(self): + return self(*args) + elif self.args: + newargs = [sub.rcall(*args) for sub in self.args] + return self.func(*newargs) + else: + return self + + def is_hypergeometric(self, k): + from sympy.simplify.simplify import hypersimp + from sympy.functions.elementary.piecewise import Piecewise + if self.has(Piecewise): + return None + return hypersimp(self, k) is not None + + @property + def is_comparable(self): + """Return True if self can be computed to a real number + (or already is a real number) with precision, else False. + + Examples + ======== + + >>> from sympy import exp_polar, pi, I + >>> (I*exp_polar(I*pi/2)).is_comparable + True + >>> (I*exp_polar(I*pi*2)).is_comparable + False + + A False result does not mean that `self` cannot be rewritten + into a form that would be comparable. For example, the + difference computed below is zero but without simplification + it does not evaluate to a zero with precision: + + >>> e = 2**pi*(1 + 2**pi) + >>> dif = e - e.expand() + >>> dif.is_comparable + False + >>> dif.n(2)._prec + 1 + + """ + return self._eval_is_comparable() + + def _eval_is_comparable(self) -> bool: + # Expr.is_comparable overrides this + return False + + @property + def func(self): + """ + The top-level function in an expression. + + The following should hold for all objects:: + + >> x == x.func(*x.args) + + Examples + ======== + + >>> from sympy.abc import x + >>> a = 2*x + >>> a.func + + >>> a.args + (2, x) + >>> a.func(*a.args) + 2*x + >>> a == a.func(*a.args) + True + + """ + return self.__class__ + + @property + def args(self) -> tuple[Basic, ...]: + """Returns a tuple of arguments of 'self'. + + Examples + ======== + + >>> from sympy import cot + >>> from sympy.abc import x, y + + >>> cot(x).args + (x,) + + >>> cot(x).args[0] + x + + >>> (x*y).args + (x, y) + + >>> (x*y).args[1] + y + + Notes + ===== + + Never use self._args, always use self.args. + Only use _args in __new__ when creating a new function. + Do not override .args() from Basic (so that it is easy to + change the interface in the future if needed). + """ + return self._args + + @property + def _sorted_args(self): + """ + The same as ``args``. Derived classes which do not fix an + order on their arguments should override this method to + produce the sorted representation. + """ + return self.args + + def as_content_primitive(self, radical=False, clear=True): + """A stub to allow Basic args (like Tuple) to be skipped when computing + the content and primitive components of an expression. + + See Also + ======== + + sympy.core.expr.Expr.as_content_primitive + """ + return S.One, self + + @overload + def subs(self, arg1: Mapping[Basic | complex, Basic | complex], arg2: None=None, **kwargs: Any) -> Basic: ... + @overload + def subs(self, arg1: Iterable[tuple[Basic | complex, Basic | complex]], arg2: None=None, **kwargs: Any) -> Basic: ... + @overload + def subs(self, arg1: Basic | complex, arg2: Basic | complex, **kwargs: Any) -> Basic: ... + + def subs(self, arg1: Mapping[Basic | complex, Basic | complex] + | Iterable[tuple[Basic | complex, Basic | complex]] | Basic | complex, + arg2: Basic | complex | None = None, **kwargs: Any) -> Basic: + """ + Substitutes old for new in an expression after sympifying args. + + `args` is either: + - two arguments, e.g. foo.subs(old, new) + - one iterable argument, e.g. foo.subs(iterable). The iterable may be + o an iterable container with (old, new) pairs. In this case the + replacements are processed in the order given with successive + patterns possibly affecting replacements already made. + o a dict or set whose key/value items correspond to old/new pairs. + In this case the old/new pairs will be sorted by op count and in + case of a tie, by number of args and the default_sort_key. The + resulting sorted list is then processed as an iterable container + (see previous). + + If the keyword ``simultaneous`` is True, the subexpressions will not be + evaluated until all the substitutions have been made. + + Examples + ======== + + >>> from sympy import pi, exp, limit, oo + >>> from sympy.abc import x, y + >>> (1 + x*y).subs(x, pi) + pi*y + 1 + >>> (1 + x*y).subs({x:pi, y:2}) + 1 + 2*pi + >>> (1 + x*y).subs([(x, pi), (y, 2)]) + 1 + 2*pi + >>> reps = [(y, x**2), (x, 2)] + >>> (x + y).subs(reps) + 6 + >>> (x + y).subs(reversed(reps)) + x**2 + 2 + + >>> (x**2 + x**4).subs(x**2, y) + y**2 + y + + To replace only the x**2 but not the x**4, use xreplace: + + >>> (x**2 + x**4).xreplace({x**2: y}) + x**4 + y + + To delay evaluation until all substitutions have been made, + set the keyword ``simultaneous`` to True: + + >>> (x/y).subs([(x, 0), (y, 0)]) + 0 + >>> (x/y).subs([(x, 0), (y, 0)], simultaneous=True) + nan + + This has the added feature of not allowing subsequent substitutions + to affect those already made: + + >>> ((x + y)/y).subs({x + y: y, y: x + y}) + 1 + >>> ((x + y)/y).subs({x + y: y, y: x + y}, simultaneous=True) + y/(x + y) + + In order to obtain a canonical result, unordered iterables are + sorted by count_op length, number of arguments and by the + default_sort_key to break any ties. All other iterables are left + unsorted. + + >>> from sympy import sqrt, sin, cos + >>> from sympy.abc import a, b, c, d, e + + >>> A = (sqrt(sin(2*x)), a) + >>> B = (sin(2*x), b) + >>> C = (cos(2*x), c) + >>> D = (x, d) + >>> E = (exp(x), e) + + >>> expr = sqrt(sin(2*x))*sin(exp(x)*x)*cos(2*x) + sin(2*x) + + >>> expr.subs(dict([A, B, C, D, E])) + a*c*sin(d*e) + b + + The resulting expression represents a literal replacement of the + old arguments with the new arguments. This may not reflect the + limiting behavior of the expression: + + >>> (x**3 - 3*x).subs({x: oo}) + nan + + >>> limit(x**3 - 3*x, x, oo) + oo + + If the substitution will be followed by numerical + evaluation, it is better to pass the substitution to + evalf as + + >>> (1/x).evalf(subs={x: 3.0}, n=21) + 0.333333333333333333333 + + rather than + + >>> (1/x).subs({x: 3.0}).evalf(21) + 0.333333333333333314830 + + as the former will ensure that the desired level of precision is + obtained. + + See Also + ======== + replace: replacement capable of doing wildcard-like matching, + parsing of match, and conditional replacements + xreplace: exact node replacement in expr tree; also capable of + using matching rules + sympy.core.evalf.EvalfMixin.evalf: calculates the given formula to a desired level of precision + + """ + from .containers import Dict + from .symbol import Dummy, Symbol + from .numbers import _illegal + + items: Iterable[tuple[Basic | complex, Basic | complex]] + + unordered = False + if arg2 is None: + + if isinstance(arg1, set): + items = arg1 + unordered = True + elif isinstance(arg1, (Dict, Mapping)): + unordered = True + items = arg1.items() # type: ignore + elif not iterable(arg1): + raise ValueError(filldedent(""" + When a single argument is passed to subs + it should be a dictionary of old: new pairs or an iterable + of (old, new) tuples.""")) + else: + items = arg1 # type: ignore + else: + items = [(arg1, arg2)] # type: ignore + + def sympify_old(old) -> Basic: + if isinstance(old, str): + # Use Symbol rather than parse_expr for old + return Symbol(old) + elif isinstance(old, type): + # Allow a type e.g. Function('f') or sin + return sympify(old, strict=False) + else: + return sympify(old, strict=True) + + def sympify_new(new) -> Basic: + if isinstance(new, (str, type)): + # Allow a type or parse a string input + return sympify(new, strict=False) + else: + return sympify(new, strict=True) + + sequence = [(sympify_old(s1), sympify_new(s2)) for s1, s2 in items] + + # skip if there is no change + sequence = [(s1, s2) for s1, s2 in sequence if not _aresame(s1, s2)] + + simultaneous = kwargs.pop('simultaneous', False) + + if unordered: + from .sorting import _nodes, default_sort_key + sequence_dict = dict(sequence) + # order so more complex items are first and items + # of identical complexity are ordered so + # f(x) < f(y) < x < y + # \___ 2 __/ \_1_/ <- number of nodes + # + # For more complex ordering use an unordered sequence. + k = list(ordered(sequence_dict, default=False, keys=( + lambda x: -_nodes(x), + default_sort_key, + ))) + sequence = [(k, sequence_dict[k]) for k in k] + # do infinities first + if not simultaneous: + redo = [i for i, seq in enumerate(sequence) if seq[1] in _illegal] + for i in reversed(redo): + sequence.insert(0, sequence.pop(i)) + + if simultaneous: # XXX should this be the default for dict subs? + reps = {} + rv = self + kwargs['hack2'] = True + m = Dummy('subs_m') + for old, new in sequence: + com = new.is_commutative + if com is None: + com = True + d = Dummy('subs_d', commutative=com) + # using d*m so Subs will be used on dummy variables + # in things like Derivative(f(x, y), x) in which x + # is both free and bound + rv = rv._subs(old, d*m, **kwargs) + if not isinstance(rv, Basic): + break + reps[d] = new + reps[m] = S.One # get rid of m + return rv.xreplace(reps) + else: + rv = self + for old, new in sequence: + rv = rv._subs(old, new, **kwargs) + if not isinstance(rv, Basic): + break + return rv + + @cacheit + def _subs(self, old, new, **hints): + """Substitutes an expression old -> new. + + If self is not equal to old then _eval_subs is called. + If _eval_subs does not want to make any special replacement + then a None is received which indicates that the fallback + should be applied wherein a search for replacements is made + amongst the arguments of self. + + >>> from sympy import Add + >>> from sympy.abc import x, y, z + + Examples + ======== + + Add's _eval_subs knows how to target x + y in the following + so it makes the change: + + >>> (x + y + z).subs(x + y, 1) + z + 1 + + Add's _eval_subs does not need to know how to find x + y in + the following: + + >>> Add._eval_subs(z*(x + y) + 3, x + y, 1) is None + True + + The returned None will cause the fallback routine to traverse the args and + pass the z*(x + y) arg to Mul where the change will take place and the + substitution will succeed: + + >>> (z*(x + y) + 3).subs(x + y, 1) + z + 3 + + ** Developers Notes ** + + An _eval_subs routine for a class should be written if: + + 1) any arguments are not instances of Basic (e.g. bool, tuple); + + 2) some arguments should not be targeted (as in integration + variables); + + 3) if there is something other than a literal replacement + that should be attempted (as in Piecewise where the condition + may be updated without doing a replacement). + + If it is overridden, here are some special cases that might arise: + + 1) If it turns out that no special change was made and all + the original sub-arguments should be checked for + replacements then None should be returned. + + 2) If it is necessary to do substitutions on a portion of + the expression then _subs should be called. _subs will + handle the case of any sub-expression being equal to old + (which usually would not be the case) while its fallback + will handle the recursion into the sub-arguments. For + example, after Add's _eval_subs removes some matching terms + it must process the remaining terms so it calls _subs + on each of the un-matched terms and then adds them + onto the terms previously obtained. + + 3) If the initial expression should remain unchanged then + the original expression should be returned. (Whenever an + expression is returned, modified or not, no further + substitution of old -> new is attempted.) Sum's _eval_subs + routine uses this strategy when a substitution is attempted + on any of its summation variables. + """ + + def fallback(self, old, new): + """ + Try to replace old with new in any of self's arguments. + """ + hit = False + args = list(self.args) + for i, arg in enumerate(args): + if not hasattr(arg, '_eval_subs'): + continue + arg = arg._subs(old, new, **hints) + if not _aresame(arg, args[i]): + hit = True + args[i] = arg + if hit: + rv = self.func(*args) + hack2 = hints.get('hack2', False) + if hack2 and self.is_Mul and not rv.is_Mul: # 2-arg hack + coeff = S.One + nonnumber = [] + for i in args: + if i.is_Number: + coeff *= i + else: + nonnumber.append(i) + nonnumber = self.func(*nonnumber) + if coeff is S.One: + return nonnumber + else: + return self.func(coeff, nonnumber, evaluate=False) + return rv + return self + + if _aresame(self, old): + return new + + rv = self._eval_subs(old, new) + if rv is None: + rv = fallback(self, old, new) + return rv + + def _eval_subs(self, old, new) -> Basic | None: + """Override this stub if you want to do anything more than + attempt a replacement of old with new in the arguments of self. + + See also + ======== + + _subs + """ + return None + + def xreplace(self, rule): + """ + Replace occurrences of objects within the expression. + + Parameters + ========== + + rule : dict-like + Expresses a replacement rule + + Returns + ======= + + xreplace : the result of the replacement + + Examples + ======== + + >>> from sympy import symbols, pi, exp + >>> x, y, z = symbols('x y z') + >>> (1 + x*y).xreplace({x: pi}) + pi*y + 1 + >>> (1 + x*y).xreplace({x: pi, y: 2}) + 1 + 2*pi + + Replacements occur only if an entire node in the expression tree is + matched: + + >>> (x*y + z).xreplace({x*y: pi}) + z + pi + >>> (x*y*z).xreplace({x*y: pi}) + x*y*z + >>> (2*x).xreplace({2*x: y, x: z}) + y + >>> (2*2*x).xreplace({2*x: y, x: z}) + 4*z + >>> (x + y + 2).xreplace({x + y: 2}) + x + y + 2 + >>> (x + 2 + exp(x + 2)).xreplace({x + 2: y}) + x + exp(y) + 2 + + xreplace does not differentiate between free and bound symbols. In the + following, subs(x, y) would not change x since it is a bound symbol, + but xreplace does: + + >>> from sympy import Integral + >>> Integral(x, (x, 1, 2*x)).xreplace({x: y}) + Integral(y, (y, 1, 2*y)) + + Trying to replace x with an expression raises an error: + + >>> Integral(x, (x, 1, 2*x)).xreplace({x: 2*y}) # doctest: +SKIP + ValueError: Invalid limits given: ((2*y, 1, 4*y),) + + See Also + ======== + replace: replacement capable of doing wildcard-like matching, + parsing of match, and conditional replacements + subs: substitution of subexpressions as defined by the objects + themselves. + + """ + value, _ = self._xreplace(rule) + return value + + def _xreplace(self, rule): + """ + Helper for xreplace. Tracks whether a replacement actually occurred. + """ + if self in rule: + return rule[self], True + elif rule: + args = [] + changed = False + for a in self.args: + _xreplace = getattr(a, '_xreplace', None) + if _xreplace is not None: + a_xr = _xreplace(rule) + args.append(a_xr[0]) + changed |= a_xr[1] + else: + args.append(a) + args = tuple(args) + if changed: + return self.func(*args), True + return self, False + + @cacheit + def has(self, *patterns): + """ + Test whether any subexpression matches any of the patterns. + + Examples + ======== + + >>> from sympy import sin + >>> from sympy.abc import x, y, z + >>> (x**2 + sin(x*y)).has(z) + False + >>> (x**2 + sin(x*y)).has(x, y, z) + True + >>> x.has(x) + True + + Note ``has`` is a structural algorithm with no knowledge of + mathematics. Consider the following half-open interval: + + >>> from sympy import Interval + >>> i = Interval.Lopen(0, 5); i + Interval.Lopen(0, 5) + >>> i.args + (0, 5, True, False) + >>> i.has(4) # there is no "4" in the arguments + False + >>> i.has(0) # there *is* a "0" in the arguments + True + + Instead, use ``contains`` to determine whether a number is in the + interval or not: + + >>> i.contains(4) + True + >>> i.contains(0) + False + + + Note that ``expr.has(*patterns)`` is exactly equivalent to + ``any(expr.has(p) for p in patterns)``. In particular, ``False`` is + returned when the list of patterns is empty. + + >>> x.has() + False + + """ + return self._has(iterargs, *patterns) + + def has_xfree(self, s: set[Basic]): + """Return True if self has any of the patterns in s as a + free argument, else False. This is like `Basic.has_free` + but this will only report exact argument matches. + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.abc import x, y + >>> f = Function('f') + >>> f(x).has_xfree({f}) + False + >>> f(x).has_xfree({f(x)}) + True + >>> f(x + 1).has_xfree({x}) + True + >>> f(x + 1).has_xfree({x + 1}) + True + >>> f(x + y + 1).has_xfree({x + 1}) + False + """ + # protect O(1) containment check by requiring: + if type(s) is not set: + raise TypeError('expecting set argument') + return any(a in s for a in iterfreeargs(self)) + + @cacheit + def has_free(self, *patterns): + """Return True if self has object(s) ``x`` as a free expression + else False. + + Examples + ======== + + >>> from sympy import Integral, Function + >>> from sympy.abc import x, y + >>> f = Function('f') + >>> g = Function('g') + >>> expr = Integral(f(x), (f(x), 1, g(y))) + >>> expr.free_symbols + {y} + >>> expr.has_free(g(y)) + True + >>> expr.has_free(*(x, f(x))) + False + + This works for subexpressions and types, too: + + >>> expr.has_free(g) + True + >>> (x + y + 1).has_free(y + 1) + True + """ + if not patterns: + return False + p0 = patterns[0] + if len(patterns) == 1 and iterable(p0) and not isinstance(p0, Basic): + # Basic can contain iterables (though not non-Basic, ideally) + # but don't encourage mixed passing patterns + raise TypeError(filldedent(''' + Expecting 1 or more Basic args, not a single + non-Basic iterable. Don't forget to unpack + iterables: `eq.has_free(*patterns)`''')) + # try quick test first + s = set(patterns) + rv = self.has_xfree(s) + if rv: + return rv + # now try matching through slower _has + return self._has(iterfreeargs, *patterns) + + def _has(self, iterargs, *patterns): + # separate out types and unhashable objects + type_set = set() # only types + p_set = set() # hashable non-types + for p in patterns: + if isinstance(p, type) and issubclass(p, Basic): + type_set.add(p) + continue + if not isinstance(p, Basic): + try: + p = _sympify(p) + except SympifyError: + continue # Basic won't have this in it + p_set.add(p) # fails if object defines __eq__ but + # doesn't define __hash__ + types = tuple(type_set) # + for i in iterargs(self): # + if i in p_set: # <--- here, too + return True + if isinstance(i, types): + return True + + # use matcher if defined, e.g. operations defines + # matcher that checks for exact subset containment, + # (x + y + 1).has(x + 1) -> True + for i in p_set - type_set: # types don't have matchers + if not hasattr(i, '_has_matcher'): + continue + match = i._has_matcher() + if any(match(arg) for arg in iterargs(self)): + return True + + # no success + return False + + def replace(self, query, value, map=False, simultaneous=True, exact=None) -> Basic: + """ + Replace matching subexpressions of ``self`` with ``value``. + + If ``map = True`` then also return the mapping {old: new} where ``old`` + was a sub-expression found with query and ``new`` is the replacement + value for it. If the expression itself does not match the query, then + the returned value will be ``self.xreplace(map)`` otherwise it should + be ``self.subs(ordered(map.items()))``. + + Traverses an expression tree and performs replacement of matching + subexpressions from the bottom to the top of the tree. The default + approach is to do the replacement in a simultaneous fashion so + changes made are targeted only once. If this is not desired or causes + problems, ``simultaneous`` can be set to False. + + In addition, if an expression containing more than one Wild symbol + is being used to match subexpressions and the ``exact`` flag is None + it will be set to True so the match will only succeed if all non-zero + values are received for each Wild that appears in the match pattern. + Setting this to False accepts a match of 0; while setting it True + accepts all matches that have a 0 in them. See example below for + cautions. + + The list of possible combinations of queries and replacement values + is listed below: + + Examples + ======== + + Initial setup + + >>> from sympy import log, sin, cos, tan, Wild, Mul, Add + >>> from sympy.abc import x, y + >>> f = log(sin(x)) + tan(sin(x**2)) + + 1.1. type -> type + obj.replace(type, newtype) + + When object of type ``type`` is found, replace it with the + result of passing its argument(s) to ``newtype``. + + >>> f.replace(sin, cos) + log(cos(x)) + tan(cos(x**2)) + >>> sin(x).replace(sin, cos, map=True) + (cos(x), {sin(x): cos(x)}) + >>> (x*y).replace(Mul, Add) + x + y + + 1.2. type -> func + obj.replace(type, func) + + When object of type ``type`` is found, apply ``func`` to its + argument(s). ``func`` must be written to handle the number + of arguments of ``type``. + + >>> f.replace(sin, lambda arg: sin(2*arg)) + log(sin(2*x)) + tan(sin(2*x**2)) + >>> (x*y).replace(Mul, lambda *args: sin(2*Mul(*args))) + sin(2*x*y) + + 2.1. pattern -> expr + obj.replace(pattern(wild), expr(wild)) + + Replace subexpressions matching ``pattern`` with the expression + written in terms of the Wild symbols in ``pattern``. + + >>> a, b = map(Wild, 'ab') + >>> f.replace(sin(a), tan(a)) + log(tan(x)) + tan(tan(x**2)) + >>> f.replace(sin(a), tan(a/2)) + log(tan(x/2)) + tan(tan(x**2/2)) + >>> f.replace(sin(a), a) + log(x) + tan(x**2) + >>> (x*y).replace(a*x, a) + y + + Matching is exact by default when more than one Wild symbol + is used: matching fails unless the match gives non-zero + values for all Wild symbols: + + >>> (2*x + y).replace(a*x + b, b - a) + y - 2 + >>> (2*x).replace(a*x + b, b - a) + 2*x + + When set to False, the results may be non-intuitive: + + >>> (2*x).replace(a*x + b, b - a, exact=False) + 2/x + + 2.2. pattern -> func + obj.replace(pattern(wild), lambda wild: expr(wild)) + + All behavior is the same as in 2.1 but now a function in terms of + pattern variables is used rather than an expression: + + >>> f.replace(sin(a), lambda a: sin(2*a)) + log(sin(2*x)) + tan(sin(2*x**2)) + + 3.1. func -> func + obj.replace(filter, func) + + Replace subexpression ``e`` with ``func(e)`` if ``filter(e)`` + is True. + + >>> g = 2*sin(x**3) + >>> g.replace(lambda expr: expr.is_Number, lambda expr: expr**2) + 4*sin(x**9) + + The expression itself is also targeted by the query but is done in + such a fashion that changes are not made twice. + + >>> e = x*(x*y + 1) + >>> e.replace(lambda x: x.is_Mul, lambda x: 2*x) + 2*x*(2*x*y + 1) + + When matching a single symbol, `exact` will default to True, but + this may or may not be the behavior that is desired: + + Here, we want `exact=False`: + + >>> from sympy import Function + >>> f = Function('f') + >>> e = f(1) + f(0) + >>> q = f(a), lambda a: f(a + 1) + >>> e.replace(*q, exact=False) + f(1) + f(2) + >>> e.replace(*q, exact=True) + f(0) + f(2) + + But here, the nature of matching makes selecting + the right setting tricky: + + >>> e = x**(1 + y) + >>> (x**(1 + y)).replace(x**(1 + a), lambda a: x**-a, exact=False) + x + >>> (x**(1 + y)).replace(x**(1 + a), lambda a: x**-a, exact=True) + x**(-x - y + 1) + >>> (x**y).replace(x**(1 + a), lambda a: x**-a, exact=False) + x + >>> (x**y).replace(x**(1 + a), lambda a: x**-a, exact=True) + x**(1 - y) + + It is probably better to use a different form of the query + that describes the target expression more precisely: + + >>> (1 + x**(1 + y)).replace( + ... lambda x: x.is_Pow and x.exp.is_Add and x.exp.args[0] == 1, + ... lambda x: x.base**(1 - (x.exp - 1))) + ... + x**(1 - y) + 1 + + See Also + ======== + + subs: substitution of subexpressions as defined by the objects + themselves. + xreplace: exact node replacement in expr tree; also capable of + using matching rules + + """ + + try: + query = _sympify(query) + except SympifyError: + pass + try: + value = _sympify(value) + except SympifyError: + pass + if isinstance(query, type): + _query = lambda expr: isinstance(expr, query) + + if isinstance(value, type): + _value = lambda expr, result: value(*expr.args) + elif callable(value): + _value = lambda expr, result: value(*expr.args) + else: + raise TypeError( + "given a type, replace() expects another " + "type or a callable") + elif isinstance(query, Basic): + _query = lambda expr: expr.match(query) + if exact is None: + from .symbol import Wild + exact = (len(query.atoms(Wild)) > 1) + + if isinstance(value, Basic): + if exact: + _value = lambda expr, result: (value.subs(result) + if all(result.values()) else expr) + else: + _value = lambda expr, result: value.subs(result) + elif callable(value): + # match dictionary keys get the trailing underscore stripped + # from them and are then passed as keywords to the callable; + # if ``exact`` is True, only accept match if there are no null + # values amongst those matched. + if exact: + _value = lambda expr, result: (value(** + {str(k)[:-1]: v for k, v in result.items()}) + if all(val for val in result.values()) else expr) + else: + _value = lambda expr, result: value(** + {str(k)[:-1]: v for k, v in result.items()}) + else: + raise TypeError( + "given an expression, replace() expects " + "another expression or a callable") + elif callable(query): + _query = query + + if callable(value): + _value = lambda expr, result: value(expr) + else: + raise TypeError( + "given a callable, replace() expects " + "another callable") + else: + raise TypeError( + "first argument to replace() must be a " + "type, an expression or a callable") + + def walk(rv, F): + """Apply ``F`` to args and then to result. + """ + args = getattr(rv, 'args', None) + if args is not None: + if args: + newargs = tuple([walk(a, F) for a in args]) + if args != newargs: + rv = rv.func(*newargs) + if simultaneous: + # if rv is something that was already + # matched (that was changed) then skip + # applying F again + for i, e in enumerate(args): + if rv == e and e != newargs[i]: + return rv + rv = F(rv) + return rv + + mapping = {} # changes that took place + + def rec_replace(expr): + result = _query(expr) + if result or result == {}: + v = _value(expr, result) + if v is not None and v != expr: + if map: + mapping[expr] = v + expr = v + return expr + + rv = walk(self, rec_replace) + return (rv, mapping) if map else rv # type: ignore + + def find(self, query, group=False): + """Find all subexpressions matching a query.""" + query = _make_find_query(query) + results = list(filter(query, _preorder_traversal(self))) + + if not group: + return set(results) + return dict(Counter(results)) + + def count(self, query): + """Count the number of matching subexpressions.""" + query = _make_find_query(query) + return sum(bool(query(sub)) for sub in _preorder_traversal(self)) + + def matches(self, expr, repl_dict=None, old=False): + """ + Helper method for match() that looks for a match between Wild symbols + in self and expressions in expr. + + Examples + ======== + + >>> from sympy import symbols, Wild, Basic + >>> a, b, c = symbols('a b c') + >>> x = Wild('x') + >>> Basic(a + x, x).matches(Basic(a + b, c)) is None + True + >>> Basic(a + x, x).matches(Basic(a + b + c, b + c)) + {x_: b + c} + """ + expr = sympify(expr) + if not isinstance(expr, self.__class__): + return None + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + if self == expr: + return repl_dict + + if len(self.args) != len(expr.args): + return None + + d = repl_dict # already a copy + for arg, other_arg in zip(self.args, expr.args): + if arg == other_arg: + continue + if arg.is_Relational: + try: + d = arg.xreplace(d).matches(other_arg, d, old=old) + except TypeError: # Should be InvalidComparisonError when introduced + d = None + else: + d = arg.xreplace(d).matches(other_arg, d, old=old) + if d is None: + return None + return d + + def match(self, pattern, old=False): + """ + Pattern matching. + + Wild symbols match all. + + Return ``None`` when expression (self) does not match with pattern. + Otherwise return a dictionary such that:: + + pattern.xreplace(self.match(pattern)) == self + + Examples + ======== + + >>> from sympy import Wild, Sum + >>> from sympy.abc import x, y + >>> p = Wild("p") + >>> q = Wild("q") + >>> r = Wild("r") + >>> e = (x+y)**(x+y) + >>> e.match(p**p) + {p_: x + y} + >>> e.match(p**q) + {p_: x + y, q_: x + y} + >>> e = (2*x)**2 + >>> e.match(p*q**r) + {p_: 4, q_: x, r_: 2} + >>> (p*q**r).xreplace(e.match(p*q**r)) + 4*x**2 + + Since match is purely structural expressions that are equivalent up to + bound symbols will not match: + + >>> print(Sum(x, (x, 1, 2)).match(Sum(y, (y, 1, p)))) + None + + An expression with bound symbols can be matched if the pattern uses + a distinct ``Wild`` for each bound symbol: + + >>> Sum(x, (x, 1, 2)).match(Sum(q, (q, 1, p))) + {p_: 2, q_: x} + + The ``old`` flag will give the old-style pattern matching where + expressions and patterns are essentially solved to give the match. Both + of the following give None unless ``old=True``: + + >>> (x - 2).match(p - x, old=True) + {p_: 2*x - 2} + >>> (2/x).match(p*x, old=True) + {p_: 2/x**2} + + See Also + ======== + + matches: pattern.matches(expr) is the same as expr.match(pattern) + xreplace: exact structural replacement + replace: structural replacement with pattern matching + Wild: symbolic placeholders for expressions in pattern matching + """ + pattern = sympify(pattern) + return pattern.matches(self, old=old) + + def count_ops(self, visual=False): + """Wrapper for count_ops that returns the operation count.""" + from .function import count_ops + return count_ops(self, visual) + + def doit(self, **hints): + """Evaluate objects that are not evaluated by default like limits, + integrals, sums and products. All objects of this kind will be + evaluated recursively, unless some species were excluded via 'hints' + or unless the 'deep' hint was set to 'False'. + + >>> from sympy import Integral + >>> from sympy.abc import x + + >>> 2*Integral(x, x) + 2*Integral(x, x) + + >>> (2*Integral(x, x)).doit() + x**2 + + >>> (2*Integral(x, x)).doit(deep=False) + 2*Integral(x, x) + + """ + if hints.get('deep', True): + terms = [term.doit(**hints) if isinstance(term, Basic) else term + for term in self.args] + return self.func(*terms) + else: + return self + + def simplify(self, **kwargs) -> Basic: + """See the simplify function in sympy.simplify""" + from sympy.simplify.simplify import simplify + return simplify(self, **kwargs) + + def refine(self, assumption=True): + """See the refine function in sympy.assumptions""" + from sympy.assumptions.refine import refine + return refine(self, assumption) + + def _eval_derivative_n_times(self, s, n): + # This is the default evaluator for derivatives (as called by `diff` + # and `Derivative`), it will attempt a loop to derive the expression + # `n` times by calling the corresponding `_eval_derivative` method, + # while leaving the derivative unevaluated if `n` is symbolic. This + # method should be overridden if the object has a closed form for its + # symbolic n-th derivative. + from .numbers import Integer + if isinstance(n, (int, Integer)): + obj = self + for i in range(n): + prev = obj + obj = obj._eval_derivative(s) + if obj is None: + return None + elif obj == prev: + break + return obj + else: + return None + + def rewrite(self, *args, deep=True, **hints): + """ + Rewrite *self* using a defined rule. + + Rewriting transforms an expression to another, which is mathematically + equivalent but structurally different. For example you can rewrite + trigonometric functions as complex exponentials or combinatorial + functions as gamma function. + + This method takes a *pattern* and a *rule* as positional arguments. + *pattern* is optional parameter which defines the types of expressions + that will be transformed. If it is not passed, all possible expressions + will be rewritten. *rule* defines how the expression will be rewritten. + + Parameters + ========== + + args : Expr + A *rule*, or *pattern* and *rule*. + - *pattern* is a type or an iterable of types. + - *rule* can be any object. + + deep : bool, optional + If ``True``, subexpressions are recursively transformed. Default is + ``True``. + + Examples + ======== + + If *pattern* is unspecified, all possible expressions are transformed. + + >>> from sympy import cos, sin, exp, I + >>> from sympy.abc import x + >>> expr = cos(x) + I*sin(x) + >>> expr.rewrite(exp) + exp(I*x) + + Pattern can be a type or an iterable of types. + + >>> expr.rewrite(sin, exp) + exp(I*x)/2 + cos(x) - exp(-I*x)/2 + >>> expr.rewrite([cos,], exp) + exp(I*x)/2 + I*sin(x) + exp(-I*x)/2 + >>> expr.rewrite([cos, sin], exp) + exp(I*x) + + Rewriting behavior can be implemented by defining ``_eval_rewrite()`` + method. + + >>> from sympy import Expr, sqrt, pi + >>> class MySin(Expr): + ... def _eval_rewrite(self, rule, args, **hints): + ... x, = args + ... if rule == cos: + ... return cos(pi/2 - x, evaluate=False) + ... if rule == sqrt: + ... return sqrt(1 - cos(x)**2) + >>> MySin(MySin(x)).rewrite(cos) + cos(-cos(-x + pi/2) + pi/2) + >>> MySin(x).rewrite(sqrt) + sqrt(1 - cos(x)**2) + + Defining ``_eval_rewrite_as_[...]()`` method is supported for backwards + compatibility reason. This may be removed in the future and using it is + discouraged. + + >>> class MySin(Expr): + ... def _eval_rewrite_as_cos(self, *args, **hints): + ... x, = args + ... return cos(pi/2 - x, evaluate=False) + >>> MySin(x).rewrite(cos) + cos(-x + pi/2) + + """ + if not args: + return self + + hints.update(deep=deep) + + pattern = args[:-1] + rule = args[-1] + + # Special case: map `abs` to `Abs` + if rule is abs: + from sympy.functions.elementary.complexes import Abs + rule = Abs + + # support old design by _eval_rewrite_as_[...] method + if isinstance(rule, str): + method = "_eval_rewrite_as_%s" % rule + elif hasattr(rule, "__name__"): + # rule is class or function + clsname = rule.__name__ + method = "_eval_rewrite_as_%s" % clsname + else: + # rule is instance + clsname = rule.__class__.__name__ + method = "_eval_rewrite_as_%s" % clsname + + if pattern: + if iterable(pattern[0]): + pattern = pattern[0] + pattern = tuple(p for p in pattern if self.has(p)) + if not pattern: + return self + # hereafter, empty pattern is interpreted as all pattern. + + return self._rewrite(pattern, rule, method, **hints) + + def _rewrite(self, pattern, rule, method, **hints): + deep = hints.pop('deep', True) + if deep: + args = [a._rewrite(pattern, rule, method, **hints) + for a in self.args] + else: + args = self.args + if not pattern or any(isinstance(self, p) for p in pattern): + meth = getattr(self, method, None) + if meth is not None: + rewritten = meth(*args, **hints) + else: + rewritten = self._eval_rewrite(rule, args, **hints) + if rewritten is not None: + return rewritten + if not args: + return self + return self.func(*args) + + def _eval_rewrite(self, rule, args, **hints): + return None + + _constructor_postprocessor_mapping = {} # type: ignore + + @classmethod + def _exec_constructor_postprocessors(cls, obj): + # WARNING: This API is experimental. + + # This is an experimental API that introduces constructor + # postprosessors for SymPy Core elements. If an argument of a SymPy + # expression has a `_constructor_postprocessor_mapping` attribute, it will + # be interpreted as a dictionary containing lists of postprocessing + # functions for matching expression node names. + + clsname = obj.__class__.__name__ + postprocessors = {f for i in obj.args + for f in _get_postprocessors(clsname, type(i))} + for f in postprocessors: + obj = f(obj) + + return obj + + def _sage_(self): + """ + Convert *self* to a symbolic expression of SageMath. + + This version of the method is merely a placeholder. + """ + old_method = self._sage_ + from sage.interfaces.sympy import sympy_init # type: ignore + sympy_init() # may monkey-patch _sage_ method into self's class or superclasses + if old_method == self._sage_: + raise NotImplementedError('conversion to SageMath is not implemented') + else: + # call the freshly monkey-patched method + return self._sage_() + + def could_extract_minus_sign(self) -> bool: + return False # see Expr.could_extract_minus_sign + + def is_same(a, b, approx=None): + """Return True if a and b are structurally the same, else False. + If `approx` is supplied, it will be used to test whether two + numbers are the same or not. By default, only numbers of the + same type will compare equal, so S.Half != Float(0.5). + + Examples + ======== + + In SymPy (unlike Python) two numbers do not compare the same if they are + not of the same type: + + >>> from sympy import S + >>> 2.0 == S(2) + False + >>> 0.5 == S.Half + False + + By supplying a function with which to compare two numbers, such + differences can be ignored. e.g. `equal_valued` will return True + for decimal numbers having a denominator that is a power of 2, + regardless of precision. + + >>> from sympy import Float + >>> from sympy.core.numbers import equal_valued + >>> (S.Half/4).is_same(Float(0.125, 1), equal_valued) + True + >>> Float(1, 2).is_same(Float(1, 10), equal_valued) + True + + But decimals without a power of 2 denominator will compare + as not being the same. + + >>> Float(0.1, 9).is_same(Float(0.1, 10), equal_valued) + False + + But arbitrary differences can be ignored by supplying a function + to test the equivalence of two numbers: + + >>> import math + >>> Float(0.1, 9).is_same(Float(0.1, 10), math.isclose) + True + + Other objects might compare the same even though types are not the + same. This routine will only return True if two expressions are + identical in terms of class types. + + >>> from sympy import eye, Basic + >>> eye(1) == S(eye(1)) # mutable vs immutable + True + >>> Basic.is_same(eye(1), S(eye(1))) + False + + """ + from .numbers import Number + from .traversal import postorder_traversal as pot + for t in zip_longest(pot(a), pot(b)): + if None in t: + return False + a, b = t + if isinstance(a, Number): + if not isinstance(b, Number): + return False + if approx: + return approx(a, b) + if not (a == b and a.__class__ == b.__class__): + return False + return True + +_aresame = Basic.is_same # for sake of others importing this + +# key used by Mul and Add to make canonical args +_args_sortkey = cmp_to_key(Basic.compare) + +# For all Basic subclasses _prepare_class_assumptions is called by +# Basic.__init_subclass__ but that method is not called for Basic itself so we +# call the function here instead. +_prepare_class_assumptions(Basic) + + +class Atom(Basic): + """ + A parent class for atomic things. An atom is an expression with no subexpressions. + + Examples + ======== + + Symbol, Number, Rational, Integer, ... + But not: Add, Mul, Pow, ... + """ + + is_Atom = True + + __slots__ = () + + def matches(self, expr, repl_dict=None, old=False): + if self == expr: + if repl_dict is None: + return {} + return repl_dict.copy() + + def xreplace(self, rule, hack2=False): + return rule.get(self, self) + + def doit(self, **hints): + return self + + @classmethod + def class_key(cls): + return 2, 0, cls.__name__ + + @cacheit + def sort_key(self, order=None): + return self.class_key(), (1, (str(self),)), S.One.sort_key(), S.One + + def _eval_simplify(self, **kwargs): + return self + + @property + def _sorted_args(self): + # this is here as a safeguard against accidentally using _sorted_args + # on Atoms -- they cannot be rebuilt as atom.func(*atom._sorted_args) + # since there are no args. So the calling routine should be checking + # to see that this property is not called for Atoms. + raise AttributeError('Atoms have no args. It might be necessary' + ' to make a check for Atoms in the calling code.') + + +def _atomic(e, recursive=False): + """Return atom-like quantities as far as substitution is + concerned: Derivatives, Functions and Symbols. Do not + return any 'atoms' that are inside such quantities unless + they also appear outside, too, unless `recursive` is True. + + Examples + ======== + + >>> from sympy import Derivative, Function, cos + >>> from sympy.abc import x, y + >>> from sympy.core.basic import _atomic + >>> f = Function('f') + >>> _atomic(x + y) + {x, y} + >>> _atomic(x + f(y)) + {x, f(y)} + >>> _atomic(Derivative(f(x), x) + cos(x) + y) + {y, cos(x), Derivative(f(x), x)} + + """ + pot = _preorder_traversal(e) + seen = set() + if isinstance(e, Basic): + free = getattr(e, "free_symbols", None) + if free is None: + return {e} + else: + return set() + from .symbol import Symbol + from .function import Derivative, Function + atoms = set() + for p in pot: + if p in seen: + pot.skip() + continue + seen.add(p) + if isinstance(p, Symbol) and p in free: + atoms.add(p) + elif isinstance(p, (Derivative, Function)): + if not recursive: + pot.skip() + atoms.add(p) + return atoms + + +def _make_find_query(query): + """Convert the argument of Basic.find() into a callable""" + try: + query = _sympify(query) + except SympifyError: + pass + if isinstance(query, type): + return lambda expr: isinstance(expr, query) + elif isinstance(query, Basic): + return lambda expr: expr.match(query) is not None + return query + +# Delayed to avoid cyclic import +from .singleton import S +from .traversal import (preorder_traversal as _preorder_traversal, + iterargs, iterfreeargs) + +preorder_traversal = deprecated( + """ + Using preorder_traversal from the sympy.core.basic submodule is + deprecated. + + Instead, use preorder_traversal from the top-level sympy namespace, like + + sympy.preorder_traversal + """, + deprecated_since_version="1.10", + active_deprecations_target="deprecated-traversal-functions-moved", +)(_preorder_traversal) diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__init__.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5ef77c1fa537156156bcec4404255baa31fe771 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_arit.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_arit.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4db342dd60181264d8a891f1b4b24ac513fadd8e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_arit.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_assumptions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_assumptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3607de23ba28ecef84c8abfcddf2cadd65921f2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_assumptions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_basic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_basic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70210491e0c472dd7aabab2cff5c3a176e378244 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_basic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_expand.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_expand.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce48f3e952e2375efe775d9981f4136a5eb1a5ac Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_expand.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_numbers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_numbers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0a7ccaebc59b215a7c6c113a63e234d655b9292 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_numbers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_sympify.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_sympify.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2fce6d9d298e7e573ebbb2fb11bc98ca8e8606b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/benchmarks/__pycache__/bench_sympify.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_arit.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_arit.py new file mode 100644 index 0000000000000000000000000000000000000000..39860943b763a30cf4f91578dbac37dc7e6e444e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_arit.py @@ -0,0 +1,43 @@ +from sympy.core import Add, Mul, symbols + +x, y, z = symbols('x,y,z') + + +def timeit_neg(): + -x + + +def timeit_Add_x1(): + x + 1 + + +def timeit_Add_1x(): + 1 + x + + +def timeit_Add_x05(): + x + 0.5 + + +def timeit_Add_xy(): + x + y + + +def timeit_Add_xyz(): + Add(*[x, y, z]) + + +def timeit_Mul_xy(): + x*y + + +def timeit_Mul_xyz(): + Mul(*[x, y, z]) + + +def timeit_Div_xy(): + x/y + + +def timeit_Div_2y(): + 2/y diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_assumptions.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_assumptions.py new file mode 100644 index 0000000000000000000000000000000000000000..1a8e47928b76034dd1d7ba8b8f87bd527bb1cdeb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_assumptions.py @@ -0,0 +1,12 @@ +from sympy.core import Symbol, Integer + +x = Symbol('x') +i3 = Integer(3) + + +def timeit_x_is_integer(): + x.is_integer + + +def timeit_Integer_is_irrational(): + i3.is_irrational diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_basic.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..df2a382ecbd3cf6eb1f8555577dabb5e07c6643b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_basic.py @@ -0,0 +1,15 @@ +from sympy.core import symbols, S + +x, y = symbols('x,y') + + +def timeit_Symbol_meth_lookup(): + x.diff # no call, just method lookup + + +def timeit_S_lookup(): + S.Exp1 + + +def timeit_Symbol_eq_xy(): + x == y diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_expand.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_expand.py new file mode 100644 index 0000000000000000000000000000000000000000..4f5ac513e368cb7e9b542926bc25a5695de6d914 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_expand.py @@ -0,0 +1,23 @@ +from sympy.core import symbols, I + +x, y, z = symbols('x,y,z') + +p = 3*x**2*y*z**7 + 7*x*y*z**2 + 4*x + x*y**4 +e = (x + y + z + 1)**32 + + +def timeit_expand_nothing_todo(): + p.expand() + + +def bench_expand_32(): + """(x+y+z+1)**32 -> expand""" + e.expand() + + +def timeit_expand_complex_number_1(): + ((2 + 3*I)**1000).expand(complex=True) + + +def timeit_expand_complex_number_2(): + ((2 + 3*I/4)**1000).expand(complex=True) diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_numbers.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..5c7484c389232b3622fb4b6724e4ab8534dde382 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_numbers.py @@ -0,0 +1,92 @@ +from sympy.core.numbers import Integer, Rational, pi, oo +from sympy.core.intfunc import integer_nthroot, igcd +from sympy.core.singleton import S + +i3 = Integer(3) +i4 = Integer(4) +r34 = Rational(3, 4) +q45 = Rational(4, 5) + + +def timeit_Integer_create(): + Integer(2) + + +def timeit_Integer_int(): + int(i3) + + +def timeit_neg_one(): + -S.One + + +def timeit_Integer_neg(): + -i3 + + +def timeit_Integer_abs(): + abs(i3) + + +def timeit_Integer_sub(): + i3 - i3 + + +def timeit_abs_pi(): + abs(pi) + + +def timeit_neg_oo(): + -oo + + +def timeit_Integer_add_i1(): + i3 + 1 + + +def timeit_Integer_add_ij(): + i3 + i4 + + +def timeit_Integer_add_Rational(): + i3 + r34 + + +def timeit_Integer_mul_i4(): + i3*4 + + +def timeit_Integer_mul_ij(): + i3*i4 + + +def timeit_Integer_mul_Rational(): + i3*r34 + + +def timeit_Integer_eq_i3(): + i3 == 3 + + +def timeit_Integer_ed_Rational(): + i3 == r34 + + +def timeit_integer_nthroot(): + integer_nthroot(100, 2) + + +def timeit_number_igcd_23_17(): + igcd(23, 17) + + +def timeit_number_igcd_60_3600(): + igcd(60, 3600) + + +def timeit_Rational_add_r1(): + r34 + 1 + + +def timeit_Rational_add_rq(): + r34 + q45 diff --git a/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_sympify.py b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_sympify.py new file mode 100644 index 0000000000000000000000000000000000000000..d8cc0abc1e35439a1a495454abf87769d5b40d04 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/benchmarks/bench_sympify.py @@ -0,0 +1,11 @@ +from sympy.core import sympify, Symbol + +x = Symbol('x') + + +def timeit_sympify_1(): + sympify(1) + + +def timeit_sympify_x(): + sympify(x) diff --git a/phivenv/Lib/site-packages/sympy/core/cache.py b/phivenv/Lib/site-packages/sympy/core/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..ec11600a5e40ad446a6e5dde8820d46ea915b06a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/cache.py @@ -0,0 +1,210 @@ +""" Caching facility for SymPy """ +from importlib import import_module +from typing import Callable + +class _cache(list): + """ List of cached functions """ + + def print_cache(self): + """print cache info""" + + for item in self: + name = item.__name__ + myfunc = item + while hasattr(myfunc, '__wrapped__'): + if hasattr(myfunc, 'cache_info'): + info = myfunc.cache_info() + break + else: + myfunc = myfunc.__wrapped__ + else: + info = None + + print(name, info) + + def clear_cache(self): + """clear cache content""" + for item in self: + myfunc = item + while hasattr(myfunc, '__wrapped__'): + if hasattr(myfunc, 'cache_clear'): + myfunc.cache_clear() + break + else: + myfunc = myfunc.__wrapped__ + + +# global cache registry: +CACHE = _cache() +# make clear and print methods available +print_cache = CACHE.print_cache +clear_cache = CACHE.clear_cache + +from functools import lru_cache, wraps + +def __cacheit(maxsize): + """caching decorator. + + important: the result of cached function must be *immutable* + + + Examples + ======== + + >>> from sympy import cacheit + >>> @cacheit + ... def f(a, b): + ... return a+b + + >>> @cacheit + ... def f(a, b): # noqa: F811 + ... return [a, b] # <-- WRONG, returns mutable object + + to force cacheit to check returned results mutability and consistency, + set environment variable SYMPY_USE_CACHE to 'debug' + """ + def func_wrapper(func): + cfunc = lru_cache(maxsize, typed=True)(func) + + @wraps(func) + def wrapper(*args, **kwargs): + try: + retval = cfunc(*args, **kwargs) + except TypeError as e: + if not e.args or not e.args[0].startswith('unhashable type:'): + raise + retval = func(*args, **kwargs) + return retval + + wrapper.cache_info = cfunc.cache_info + wrapper.cache_clear = cfunc.cache_clear + + CACHE.append(wrapper) + return wrapper + + return func_wrapper +######################################## + + +def __cacheit_nocache(func): + return func + + +def __cacheit_debug(maxsize): + """cacheit + code to check cache consistency""" + def func_wrapper(func): + cfunc = __cacheit(maxsize)(func) + + @wraps(func) + def wrapper(*args, **kw_args): + # always call function itself and compare it with cached version + r1 = func(*args, **kw_args) + r2 = cfunc(*args, **kw_args) + + # try to see if the result is immutable + # + # this works because: + # + # hash([1,2,3]) -> raise TypeError + # hash({'a':1, 'b':2}) -> raise TypeError + # hash((1,[2,3])) -> raise TypeError + # + # hash((1,2,3)) -> just computes the hash + hash(r1), hash(r2) + + # also see if returned values are the same + if r1 != r2: + raise RuntimeError("Returned values are not the same") + return r1 + return wrapper + return func_wrapper + + +def _getenv(key, default=None): + from os import getenv + return getenv(key, default) + +# SYMPY_USE_CACHE=yes/no/debug +USE_CACHE = _getenv('SYMPY_USE_CACHE', 'yes').lower() +# SYMPY_CACHE_SIZE=some_integer/None +# special cases : +# SYMPY_CACHE_SIZE=0 -> No caching +# SYMPY_CACHE_SIZE=None -> Unbounded caching +scs = _getenv('SYMPY_CACHE_SIZE', '1000') +if scs.lower() == 'none': + SYMPY_CACHE_SIZE = None +else: + try: + SYMPY_CACHE_SIZE = int(scs) + except ValueError: + raise RuntimeError( + 'SYMPY_CACHE_SIZE must be a valid integer or None. ' + \ + 'Got: %s' % SYMPY_CACHE_SIZE) + +if USE_CACHE == 'no': + cacheit = __cacheit_nocache +elif USE_CACHE == 'yes': + cacheit = __cacheit(SYMPY_CACHE_SIZE) +elif USE_CACHE == 'debug': + cacheit = __cacheit_debug(SYMPY_CACHE_SIZE) # a lot slower +else: + raise RuntimeError( + 'unrecognized value for SYMPY_USE_CACHE: %s' % USE_CACHE) + + +def cached_property(func): + '''Decorator to cache property method''' + attrname = '__' + func.__name__ + _cached_property_sentinel = object() + def propfunc(self): + val = getattr(self, attrname, _cached_property_sentinel) + if val is _cached_property_sentinel: + val = func(self) + setattr(self, attrname, val) + return val + return property(propfunc) + + +def lazy_function(module : str, name : str) -> Callable: + """Create a lazy proxy for a function in a module. + + The module containing the function is not imported until the function is used. + + """ + func = None + + def _get_function(): + nonlocal func + if func is None: + func = getattr(import_module(module), name) + return func + + # The metaclass is needed so that help() shows the docstring + class LazyFunctionMeta(type): + @property + def __doc__(self): + docstring = _get_function().__doc__ + docstring += f"\n\nNote: this is a {self.__class__.__name__} wrapper of '{module}.{name}'" + return docstring + + class LazyFunction(metaclass=LazyFunctionMeta): + def __call__(self, *args, **kwargs): + # inline get of function for performance gh-23832 + nonlocal func + if func is None: + func = getattr(import_module(module), name) + return func(*args, **kwargs) + + @property + def __doc__(self): + docstring = _get_function().__doc__ + docstring += f"\n\nNote: this is a {self.__class__.__name__} wrapper of '{module}.{name}'" + return docstring + + def __str__(self): + return _get_function().__str__() + + def __repr__(self): + return f"<{__class__.__name__} object at 0x{id(self):x}>: wrapping '{module}.{name}'" + + return LazyFunction() diff --git a/phivenv/Lib/site-packages/sympy/core/compatibility.py b/phivenv/Lib/site-packages/sympy/core/compatibility.py new file mode 100644 index 0000000000000000000000000000000000000000..637a2698dbb39a042d3d664404bb0a4cba7fd004 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/compatibility.py @@ -0,0 +1,35 @@ +""" +.. deprecated:: 1.10 + + ``sympy.core.compatibility`` is deprecated. See + :ref:`sympy-core-compatibility`. + +Reimplementations of constructs introduced in later versions of Python than +we support. Also some functions that are needed SymPy-wide and are located +here for easy import. + +""" + + +from sympy.utilities.exceptions import sympy_deprecation_warning + +sympy_deprecation_warning(""" +The sympy.core.compatibility submodule is deprecated. + +This module was only ever intended for internal use. Some of the functions +that were in this module are available from the top-level SymPy namespace, +i.e., + + from sympy import ordered, default_sort_key + +The remaining were only intended for internal SymPy use and should not be used +by user code. +""", + deprecated_since_version="1.10", + active_deprecations_target="deprecated-sympy-core-compatibility", + ) + + +from .sorting import ordered, _nodes, default_sort_key # noqa:F401 +from sympy.utilities.misc import as_int as _as_int # noqa:F401 +from sympy.utilities.iterables import iterable, is_sequence, NotIterable # noqa:F401 diff --git a/phivenv/Lib/site-packages/sympy/core/containers.py b/phivenv/Lib/site-packages/sympy/core/containers.py new file mode 100644 index 0000000000000000000000000000000000000000..35352009e87f3a7809a53031080cefdadb6528be --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/containers.py @@ -0,0 +1,415 @@ +"""Module for SymPy containers + + (SymPy objects that store other SymPy objects) + + The containers implemented in this module are subclassed to Basic. + They are supposed to work seamlessly within the SymPy framework. +""" + +from __future__ import annotations + +from collections import OrderedDict +from collections.abc import MutableSet +from typing import Any, Callable + +from .basic import Basic +from .sorting import default_sort_key, ordered +from .sympify import _sympify, sympify, _sympy_converter, SympifyError +from sympy.core.kind import Kind +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import as_int + + +class Tuple(Basic): + """ + Wrapper around the builtin tuple object. + + Explanation + =========== + + The Tuple is a subclass of Basic, so that it works well in the + SymPy framework. The wrapped tuple is available as self.args, but + you can also access elements or slices with [:] syntax. + + Parameters + ========== + + sympify : bool + If ``False``, ``sympify`` is not called on ``args``. This + can be used for speedups for very large tuples where the + elements are known to already be SymPy objects. + + Examples + ======== + + >>> from sympy import Tuple, symbols + >>> a, b, c, d = symbols('a b c d') + >>> Tuple(a, b, c)[1:] + (b, c) + >>> Tuple(a, b, c).subs(a, d) + (d, b, c) + + """ + + def __new__(cls, *args, **kwargs): + if kwargs.get('sympify', True): + args = (sympify(arg) for arg in args) + obj = Basic.__new__(cls, *args) + return obj + + def __getitem__(self, i): + if isinstance(i, slice): + indices = i.indices(len(self)) + return Tuple(*(self.args[j] for j in range(*indices))) + return self.args[i] + + def __len__(self): + return len(self.args) + + def __contains__(self, item): + return item in self.args + + def __iter__(self): + return iter(self.args) + + def __add__(self, other): + if isinstance(other, Tuple): + return Tuple(*(self.args + other.args)) + elif isinstance(other, tuple): + return Tuple(*(self.args + other)) + else: + return NotImplemented + + def __radd__(self, other): + if isinstance(other, Tuple): + return Tuple(*(other.args + self.args)) + elif isinstance(other, tuple): + return Tuple(*(other + self.args)) + else: + return NotImplemented + + def __mul__(self, other): + try: + n = as_int(other) + except ValueError: + raise TypeError("Can't multiply sequence by non-integer of type '%s'" % type(other)) + return self.func(*(self.args*n)) + + __rmul__ = __mul__ + + def __eq__(self, other): + if isinstance(other, Basic): + return super().__eq__(other) + return self.args == other + + def __ne__(self, other): + if isinstance(other, Basic): + return super().__ne__(other) + return self.args != other + + def __hash__(self): + return hash(self.args) + + def _to_mpmath(self, prec): + return tuple(a._to_mpmath(prec) for a in self.args) + + def __lt__(self, other): + return _sympify(self.args < other.args) + + def __le__(self, other): + return _sympify(self.args <= other.args) + + # XXX: Basic defines count() as something different, so we can't + # redefine it here. Originally this lead to cse() test failure. + def tuple_count(self, value) -> int: + """Return number of occurrences of value.""" + return self.args.count(value) + + def index(self, value, start=None, stop=None): + """Searches and returns the first index of the value.""" + # XXX: One would expect: + # + # return self.args.index(value, start, stop) + # + # here. Any trouble with that? Yes: + # + # >>> (1,).index(1, None, None) + # Traceback (most recent call last): + # File "", line 1, in + # TypeError: slice indices must be integers or None or have an __index__ method + # + # See: http://bugs.python.org/issue13340 + + if start is None and stop is None: + return self.args.index(value) + elif stop is None: + return self.args.index(value, start) + else: + return self.args.index(value, start, stop) + + @property + def kind(self): + """ + The kind of a Tuple instance. + + The kind of a Tuple is always of :class:`TupleKind` but + parametrised by the number of elements and the kind of each element. + + Examples + ======== + + >>> from sympy import Tuple, Matrix + >>> Tuple(1, 2).kind + TupleKind(NumberKind, NumberKind) + >>> Tuple(Matrix([1, 2]), 1).kind + TupleKind(MatrixKind(NumberKind), NumberKind) + >>> Tuple(1, 2).kind.element_kind + (NumberKind, NumberKind) + + See Also + ======== + + sympy.matrices.kind.MatrixKind + sympy.core.kind.NumberKind + """ + return TupleKind(*(i.kind for i in self.args)) + +_sympy_converter[tuple] = lambda tup: Tuple(*tup) + + + + + +def tuple_wrapper(method): + """ + Decorator that converts any tuple in the function arguments into a Tuple. + + Explanation + =========== + + The motivation for this is to provide simple user interfaces. The user can + call a function with regular tuples in the argument, and the wrapper will + convert them to Tuples before handing them to the function. + + Explanation + =========== + + >>> from sympy.core.containers import tuple_wrapper + >>> def f(*args): + ... return args + >>> g = tuple_wrapper(f) + + The decorated function g sees only the Tuple argument: + + >>> g(0, (1, 2), 3) + (0, (1, 2), 3) + + """ + def wrap_tuples(*args, **kw_args): + newargs = [] + for arg in args: + if isinstance(arg, tuple): + newargs.append(Tuple(*arg)) + else: + newargs.append(arg) + return method(*newargs, **kw_args) + return wrap_tuples + + +class Dict(Basic): + """ + Wrapper around the builtin dict object. + + Explanation + =========== + + The Dict is a subclass of Basic, so that it works well in the + SymPy framework. Because it is immutable, it may be included + in sets, but its values must all be given at instantiation and + cannot be changed afterwards. Otherwise it behaves identically + to the Python dict. + + Examples + ======== + + >>> from sympy import Dict, Symbol + + >>> D = Dict({1: 'one', 2: 'two'}) + >>> for key in D: + ... if key == 1: + ... print('%s %s' % (key, D[key])) + 1 one + + The args are sympified so the 1 and 2 are Integers and the values + are Symbols. Queries automatically sympify args so the following work: + + >>> 1 in D + True + >>> D.has(Symbol('one')) # searches keys and values + True + >>> 'one' in D # not in the keys + False + >>> D[1] + one + + """ + + elements: frozenset[Tuple] + _dict: dict[Basic, Basic] + + def __new__(cls, *args): + if len(args) == 1 and isinstance(args[0], (dict, Dict)): + items = [Tuple(k, v) for k, v in args[0].items()] + elif iterable(args) and all(len(arg) == 2 for arg in args): + items = [Tuple(k, v) for k, v in args] + else: + raise TypeError('Pass Dict args as Dict((k1, v1), ...) or Dict({k1: v1, ...})') + elements = frozenset(items) + obj = Basic.__new__(cls, *ordered(items)) + obj.elements = elements + obj._dict = dict(items) # In case Tuple decides it wants to sympify + return obj + + def __getitem__(self, key): + """x.__getitem__(y) <==> x[y]""" + try: + key = _sympify(key) + except SympifyError: + raise KeyError(key) + + return self._dict[key] + + def __setitem__(self, key, value): + raise NotImplementedError("SymPy Dicts are Immutable") + + def items(self): + '''Returns a set-like object providing a view on dict's items. + ''' + return self._dict.items() + + def keys(self): + '''Returns the list of the dict's keys.''' + return self._dict.keys() + + def values(self): + '''Returns the list of the dict's values.''' + return self._dict.values() + + def __iter__(self): + '''x.__iter__() <==> iter(x)''' + return iter(self._dict) + + def __len__(self): + '''x.__len__() <==> len(x)''' + return self._dict.__len__() + + def get(self, key, default=None): + '''Returns the value for key if the key is in the dictionary.''' + try: + key = _sympify(key) + except SympifyError: + return default + return self._dict.get(key, default) + + def __contains__(self, key): + '''D.__contains__(k) -> True if D has a key k, else False''' + try: + key = _sympify(key) + except SympifyError: + return False + return key in self._dict + + def __lt__(self, other): + return _sympify(self.args < other.args) + + @property + def _sorted_args(self): + return tuple(sorted(self.args, key=default_sort_key)) + + def __eq__(self, other): + if isinstance(other, dict): + return self == Dict(other) + return super().__eq__(other) + + __hash__ : Callable[[Basic], Any] = Basic.__hash__ + +# this handles dict, defaultdict, OrderedDict +_sympy_converter[dict] = lambda d: Dict(*d.items()) + +class OrderedSet(MutableSet): + def __init__(self, iterable=None): + if iterable: + self.map = OrderedDict((item, None) for item in iterable) + else: + self.map = OrderedDict() + + def __len__(self): + return len(self.map) + + def __contains__(self, key): + return key in self.map + + def add(self, key): + self.map[key] = None + + def discard(self, key): + self.map.pop(key) + + def pop(self, last=True): + return self.map.popitem(last=last)[0] + + def __iter__(self): + yield from self.map.keys() + + def __repr__(self): + if not self.map: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self.map.keys())) + + def intersection(self, other): + return self.__class__([val for val in self if val in other]) + + def difference(self, other): + return self.__class__([val for val in self if val not in other]) + + def update(self, iterable): + for val in iterable: + self.add(val) + +class TupleKind(Kind): + """ + TupleKind is a subclass of Kind, which is used to define Kind of ``Tuple``. + + Parameters of TupleKind will be kinds of all the arguments in Tuples, for + example + + Parameters + ========== + + args : tuple(element_kind) + element_kind is kind of element. + args is tuple of kinds of element + + Examples + ======== + + >>> from sympy import Tuple + >>> Tuple(1, 2).kind + TupleKind(NumberKind, NumberKind) + >>> Tuple(1, 2).kind.element_kind + (NumberKind, NumberKind) + + See Also + ======== + + sympy.core.kind.NumberKind + MatrixKind + sympy.sets.sets.SetKind + """ + def __new__(cls, *args): + obj = super().__new__(cls, *args) + obj.element_kind = args + return obj + + def __repr__(self): + return "TupleKind{}".format(self.element_kind) diff --git a/phivenv/Lib/site-packages/sympy/core/core.py b/phivenv/Lib/site-packages/sympy/core/core.py new file mode 100644 index 0000000000000000000000000000000000000000..8a45bb06919d7a8ef88e2c9958decac705c0b8ee --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/core.py @@ -0,0 +1,21 @@ +""" The core's core. """ +from __future__ import annotations + + +class Registry: + """ + Base class for registry objects. + + Registries map a name to an object using attribute notation. Registry + classes behave singletonically: all their instances share the same state, + which is stored in the class object. + + All subclasses should set `__slots__ = ()`. + """ + __slots__ = () + + def __setattr__(self, name, obj): + setattr(self.__class__, name, obj) + + def __delattr__(self, name): + delattr(self.__class__, name) diff --git a/phivenv/Lib/site-packages/sympy/core/coreerrors.py b/phivenv/Lib/site-packages/sympy/core/coreerrors.py new file mode 100644 index 0000000000000000000000000000000000000000..d2dbdd5227d7b0495145072d31bd993f13f31f0d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/coreerrors.py @@ -0,0 +1,23 @@ +"""Definitions of common exceptions for :mod:`sympy.core` module. """ + +from typing import Callable + + +class BaseCoreError(Exception): + """Base class for core related exceptions. """ + + +class NonCommutativeExpression(BaseCoreError): + """Raised when expression didn't have commutative property. """ + + +class LazyExceptionMessage: + """Wrapper class that lets you specify an expensive to compute + error message that is only evaluated if the error is rendered.""" + callback: Callable[[], str] + + def __init__(self, callback: Callable[[], str]): + self.callback = callback + + def __str__(self): + return self.callback() diff --git a/phivenv/Lib/site-packages/sympy/core/decorators.py b/phivenv/Lib/site-packages/sympy/core/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..afd6ae0c72dc32d260586c6411507e4859a9f8ff --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/decorators.py @@ -0,0 +1,250 @@ +""" +SymPy core decorators. + +The purpose of this module is to expose decorators without any other +dependencies, so that they can be easily imported anywhere in sympy/core. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from functools import wraps +from .sympify import SympifyError, sympify + + +if TYPE_CHECKING: + from typing import Callable, TypeVar, Union + T1 = TypeVar('T1') + T2 = TypeVar('T2') + T3 = TypeVar('T3') + + +def _sympifyit(arg, retval=None) -> Callable[[Callable[[T1, T2], T3]], Callable[[T1, T2], T3]]: + """ + decorator to smartly _sympify function arguments + + Explanation + =========== + + @_sympifyit('other', NotImplemented) + def add(self, other): + ... + + In add, other can be thought of as already being a SymPy object. + + If it is not, the code is likely to catch an exception, then other will + be explicitly _sympified, and the whole code restarted. + + if _sympify(arg) fails, NotImplemented will be returned + + See also + ======== + + __sympifyit + """ + def deco(func): + return __sympifyit(func, arg, retval) + + return deco + + +def __sympifyit(func, arg, retval=None): + """Decorator to _sympify `arg` argument for function `func`. + + Do not use directly -- use _sympifyit instead. + """ + + # we support f(a,b) only + if not func.__code__.co_argcount: + raise LookupError("func not found") + # only b is _sympified + assert func.__code__.co_varnames[1] == arg + if retval is None: + @wraps(func) + def __sympifyit_wrapper(a, b): + return func(a, sympify(b, strict=True)) + + else: + @wraps(func) + def __sympifyit_wrapper(a, b): + try: + # If an external class has _op_priority, it knows how to deal + # with SymPy objects. Otherwise, it must be converted. + if not hasattr(b, '_op_priority'): + b = sympify(b, strict=True) + return func(a, b) + except SympifyError: + return retval + + return __sympifyit_wrapper + + +def call_highest_priority(method_name: str + ) -> Callable[[Callable[[T1, T2], T3]], Callable[[T1, T2], T3]]: + """A decorator for binary special methods to handle _op_priority. + + Explanation + =========== + + Binary special methods in Expr and its subclasses use a special attribute + '_op_priority' to determine whose special method will be called to + handle the operation. In general, the object having the highest value of + '_op_priority' will handle the operation. Expr and subclasses that define + custom binary special methods (__mul__, etc.) should decorate those + methods with this decorator to add the priority logic. + + The ``method_name`` argument is the name of the method of the other class + that will be called. Use this decorator in the following manner:: + + # Call other.__rmul__ if other._op_priority > self._op_priority + @call_highest_priority('__rmul__') + def __mul__(self, other): + ... + + # Call other.__mul__ if other._op_priority > self._op_priority + @call_highest_priority('__mul__') + def __rmul__(self, other): + ... + """ + def priority_decorator(func: Callable[[T1, T2], T3]) -> Callable[[T1, T2], T3]: + @wraps(func) + def binary_op_wrapper(self: T1, other: T2) -> T3: + if hasattr(other, '_op_priority'): + if other._op_priority > self._op_priority: # type: ignore + f: Union[Callable[[T1], T3], None] = getattr(other, method_name, None) + if f is not None: + return f(self) + return func(self, other) + return binary_op_wrapper + return priority_decorator + + +def sympify_method_args(cls: type[T1]) -> type[T1]: + '''Decorator for a class with methods that sympify arguments. + + Explanation + =========== + + The sympify_method_args decorator is to be used with the sympify_return + decorator for automatic sympification of method arguments. This is + intended for the common idiom of writing a class like : + + Examples + ======== + + >>> from sympy import Basic, SympifyError, S + >>> from sympy.core.sympify import _sympify + + >>> class MyTuple(Basic): + ... def __add__(self, other): + ... try: + ... other = _sympify(other) + ... except SympifyError: + ... return NotImplemented + ... if not isinstance(other, MyTuple): + ... return NotImplemented + ... return MyTuple(*(self.args + other.args)) + + >>> MyTuple(S(1), S(2)) + MyTuple(S(3), S(4)) + MyTuple(1, 2, 3, 4) + + In the above it is important that we return NotImplemented when other is + not sympifiable and also when the sympified result is not of the expected + type. This allows the MyTuple class to be used cooperatively with other + classes that overload __add__ and want to do something else in combination + with instance of Tuple. + + Using this decorator the above can be written as + + >>> from sympy.core.decorators import sympify_method_args, sympify_return + + >>> @sympify_method_args + ... class MyTuple(Basic): + ... @sympify_return([('other', 'MyTuple')], NotImplemented) + ... def __add__(self, other): + ... return MyTuple(*(self.args + other.args)) + + >>> MyTuple(S(1), S(2)) + MyTuple(S(3), S(4)) + MyTuple(1, 2, 3, 4) + + The idea here is that the decorators take care of the boiler-plate code + for making this happen in each method that potentially needs to accept + unsympified arguments. Then the body of e.g. the __add__ method can be + written without needing to worry about calling _sympify or checking the + type of the resulting object. + + The parameters for sympify_return are a list of tuples of the form + (parameter_name, expected_type) and the value to return (e.g. + NotImplemented). The expected_type parameter can be a type e.g. Tuple or a + string 'Tuple'. Using a string is useful for specifying a Type within its + class body (as in the above example). + + Notes: Currently sympify_return only works for methods that take a single + argument (not including self). Specifying an expected_type as a string + only works for the class in which the method is defined. + ''' + # Extract the wrapped methods from each of the wrapper objects created by + # the sympify_return decorator. Doing this here allows us to provide the + # cls argument which is used for forward string referencing. + for attrname, obj in cls.__dict__.items(): + if isinstance(obj, _SympifyWrapper): + setattr(cls, attrname, obj.make_wrapped(cls)) + return cls + + +def sympify_return(*args): + '''Function/method decorator to sympify arguments automatically + + See the docstring of sympify_method_args for explanation. + ''' + # Store a wrapper object for the decorated method + def wrapper(func: Callable[[T1, T2], T3]) -> Callable[[T1, T2], T3]: + return _SympifyWrapper(func, args) # type: ignore + return wrapper + + +class _SympifyWrapper: + '''Internal class used by sympify_return and sympify_method_args''' + + def __init__(self, func, args): + self.func = func + self.args = args + + def make_wrapped(self, cls): + func = self.func + parameters, retval = self.args + + # XXX: Handle more than one parameter? + [(parameter, expectedcls)] = parameters + + # Handle forward references to the current class using strings + if expectedcls == cls.__name__: + expectedcls = cls + + # Raise RuntimeError since this is a failure at import time and should + # not be recoverable. + nargs = func.__code__.co_argcount + # we support f(a, b) only + if nargs != 2: + raise RuntimeError('sympify_return can only be used with 2 argument functions') + # only b is _sympified + if func.__code__.co_varnames[1] != parameter: + raise RuntimeError('parameter name mismatch "%s" in %s' % + (parameter, func.__name__)) + + @wraps(func) + def _func(self, other): + # XXX: The check for _op_priority here should be removed. It is + # needed to stop mutable matrices from being sympified to + # immutable matrices which breaks things in quantum... + if not hasattr(other, '_op_priority'): + try: + other = sympify(other, strict=True) + except SympifyError: + return retval + if not isinstance(other, expectedcls): + return retval + return func(self, other) + + return _func diff --git a/phivenv/Lib/site-packages/sympy/core/evalf.py b/phivenv/Lib/site-packages/sympy/core/evalf.py new file mode 100644 index 0000000000000000000000000000000000000000..55a981090360556b357ec1cade5576e226633be8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/evalf.py @@ -0,0 +1,1808 @@ +""" +Adaptive numerical evaluation of SymPy expressions, using mpmath +for mathematical functions. +""" +from __future__ import annotations +from typing import Callable, TYPE_CHECKING, Any, overload, Type + +import math + +import mpmath.libmp as libmp +from mpmath import ( + make_mpc, make_mpf, mp, mpc, mpf, nsum, quadts, quadosc, workprec) +from mpmath import inf as mpmath_inf +from mpmath.libmp import (from_int, from_man_exp, from_rational, fhalf, + fnan, finf, fninf, fnone, fone, fzero, mpf_abs, mpf_add, + mpf_atan, mpf_atan2, mpf_cmp, mpf_cos, mpf_e, mpf_exp, mpf_log, mpf_lt, + mpf_mul, mpf_neg, mpf_pi, mpf_pow, mpf_pow_int, mpf_shift, mpf_sin, + mpf_sqrt, normalize, round_nearest, to_int, to_str, mpf_tan) +from mpmath.libmp import bitcount as mpmath_bitcount +from mpmath.libmp.backend import MPZ +from mpmath.libmp.libmpc import _infs_nan +from mpmath.libmp.libmpf import dps_to_prec, prec_to_dps + +from .sympify import sympify +from .singleton import S +from sympy.external.gmpy import SYMPY_INTS +from sympy.utilities.iterables import is_sequence +from sympy.utilities.lambdify import lambdify +from sympy.utilities.misc import as_int + +if TYPE_CHECKING: + from sympy.core.expr import Expr + from sympy.core.add import Add + from sympy.core.mul import Mul + from sympy.core.power import Pow + from sympy.core.symbol import Symbol + from sympy.integrals.integrals import Integral + from sympy.concrete.summations import Sum + from sympy.concrete.products import Product + from sympy.functions.elementary.exponential import exp, log + from sympy.functions.elementary.complexes import Abs, re, im + from sympy.functions.elementary.integers import ceiling, floor + from sympy.functions.elementary.trigonometric import atan + from .numbers import Float, Rational, Integer, AlgebraicNumber, Number + +LG10 = math.log2(10) +rnd = round_nearest + + +def bitcount(n): + """Return smallest integer, b, such that |n|/2**b < 1. + """ + return mpmath_bitcount(abs(int(n))) + +# Used in a few places as placeholder values to denote exponents and +# precision levels, e.g. of exact numbers. Must be careful to avoid +# passing these to mpmath functions or returning them in final results. +INF = float(mpmath_inf) +MINUS_INF = float(-mpmath_inf) + +# ~= 100 digits. Real men set this to INF. +DEFAULT_MAXPREC = 333 + + +class PrecisionExhausted(ArithmeticError): + pass + +#----------------------------------------------------------------------------# +# # +# Helper functions for arithmetic and complex parts # +# # +#----------------------------------------------------------------------------# + +""" +An mpf value tuple is a tuple of integers (sign, man, exp, bc) +representing a floating-point number: [1, -1][sign]*man*2**exp where +sign is 0 or 1 and bc should correspond to the number of bits used to +represent the mantissa (man) in binary notation, e.g. +""" + +MPF_TUP = tuple[int, int, int, int] # mpf value tuple + +""" +Explanation +=========== + +>>> from sympy.core.evalf import bitcount +>>> sign, man, exp, bc = 0, 5, 1, 3 +>>> n = [1, -1][sign]*man*2**exp +>>> n, bitcount(man) +(10, 3) + +A temporary result is a tuple (re, im, re_acc, im_acc) where +re and im are nonzero mpf value tuples representing approximate +numbers, or None to denote exact zeros. + +re_acc, im_acc are integers denoting log2(e) where e is the estimated +relative accuracy of the respective complex part, but may be anything +if the corresponding complex part is None. + +""" +TMP_RES = Any # temporary result, should be some variant of +# tUnion[tTuple[Optional[MPF_TUP], Optional[MPF_TUP], +# Optional[int], Optional[int]], +# 'ComplexInfinity'] +# but mypy reports error because it doesn't know as we know +# 1. re and re_acc are either both None or both MPF_TUP +# 2. sometimes the result can't be zoo + +# type of the "options" parameter in internal evalf functions +OPT_DICT = dict[str, Any] + + +def fastlog(x: MPF_TUP | None) -> int | Any: + """Fast approximation of log2(x) for an mpf value tuple x. + + Explanation + =========== + + Calculated as exponent + width of mantissa. This is an + approximation for two reasons: 1) it gives the ceil(log2(abs(x))) + value and 2) it is too high by 1 in the case that x is an exact + power of 2. Although this is easy to remedy by testing to see if + the odd mpf mantissa is 1 (indicating that one was dealing with + an exact power of 2) that would decrease the speed and is not + necessary as this is only being used as an approximation for the + number of bits in x. The correct return value could be written as + "x[2] + (x[3] if x[1] != 1 else 0)". + Since mpf tuples always have an odd mantissa, no check is done + to see if the mantissa is a multiple of 2 (in which case the + result would be too large by 1). + + Examples + ======== + + >>> from sympy import log + >>> from sympy.core.evalf import fastlog, bitcount + >>> s, m, e = 0, 5, 1 + >>> bc = bitcount(m) + >>> n = [1, -1][s]*m*2**e + >>> n, (log(n)/log(2)).evalf(2), fastlog((s, m, e, bc)) + (10, 3.3, 4) + """ + + if not x or x == fzero: + return MINUS_INF + return x[2] + x[3] + + +def pure_complex(v: Expr, or_real=False) -> tuple[Number, Number] | None: + """Return a and b if v matches a + I*b where b is not zero and + a and b are Numbers, else None. If `or_real` is True then 0 will + be returned for `b` if `v` is a real number. + + Examples + ======== + + >>> from sympy.core.evalf import pure_complex + >>> from sympy import sqrt, I, S + >>> a, b, surd = S(2), S(3), sqrt(2) + >>> pure_complex(a) + >>> pure_complex(a, or_real=True) + (2, 0) + >>> pure_complex(surd) + >>> pure_complex(a + b*I) + (2, 3) + >>> pure_complex(I) + (0, 1) + """ + h, t = v.as_coeff_Add() + if t: + c, i = t.as_coeff_Mul() + if i is S.ImaginaryUnit: + return h, c + elif or_real: + return h, S.Zero + return None + + +# I don't know what this is, see function scaled_zero below +SCALED_ZERO_TUP = tuple[list[int], int, int, int] + + + +@overload +def scaled_zero(mag: SCALED_ZERO_TUP, sign=1) -> MPF_TUP: + ... +@overload +def scaled_zero(mag: int, sign=1) -> tuple[SCALED_ZERO_TUP, int]: + ... +def scaled_zero(mag: SCALED_ZERO_TUP | int, sign=1) -> \ + MPF_TUP | tuple[SCALED_ZERO_TUP, int]: + """Return an mpf representing a power of two with magnitude ``mag`` + and -1 for precision. Or, if ``mag`` is a scaled_zero tuple, then just + remove the sign from within the list that it was initially wrapped + in. + + Examples + ======== + + >>> from sympy.core.evalf import scaled_zero + >>> from sympy import Float + >>> z, p = scaled_zero(100) + >>> z, p + (([0], 1, 100, 1), -1) + >>> ok = scaled_zero(z) + >>> ok + (0, 1, 100, 1) + >>> Float(ok) + 1.26765060022823e+30 + >>> Float(ok, p) + 0.e+30 + >>> ok, p = scaled_zero(100, -1) + >>> Float(scaled_zero(ok), p) + -0.e+30 + """ + if isinstance(mag, tuple) and len(mag) == 4 and iszero(mag, scaled=True): + return (mag[0][0],) + mag[1:] + elif isinstance(mag, SYMPY_INTS): + if sign not in [-1, 1]: + raise ValueError('sign must be +/-1') + rv, p = mpf_shift(fone, mag), -1 + s = 0 if sign == 1 else 1 + rv = ([s],) + rv[1:] + return rv, p + else: + raise ValueError('scaled zero expects int or scaled_zero tuple.') + + +def iszero(mpf: MPF_TUP | SCALED_ZERO_TUP | None, scaled=False) -> bool | None: + if not scaled: + return not mpf or not mpf[1] and not mpf[-1] + return mpf and isinstance(mpf[0], list) and mpf[1] == mpf[-1] == 1 + + +def complex_accuracy(result: TMP_RES) -> int | Any: + """ + Returns relative accuracy of a complex number with given accuracies + for the real and imaginary parts. The relative accuracy is defined + in the complex norm sense as ||z|+|error|| / |z| where error + is equal to (real absolute error) + (imag absolute error)*i. + + The full expression for the (logarithmic) error can be approximated + easily by using the max norm to approximate the complex norm. + + In the worst case (re and im equal), this is wrong by a factor + sqrt(2), or by log2(sqrt(2)) = 0.5 bit. + """ + if result is S.ComplexInfinity: + return INF + re, im, re_acc, im_acc = result + if not im: + if not re: + return INF + return re_acc + if not re: + return im_acc + re_size = fastlog(re) + im_size = fastlog(im) + absolute_error = max(re_size - re_acc, im_size - im_acc) + relative_error = absolute_error - max(re_size, im_size) + return -relative_error + + +def get_abs(expr: Expr, prec: int, options: OPT_DICT) -> TMP_RES: + result = evalf(expr, prec + 2, options) + if result is S.ComplexInfinity: + return finf, None, prec, None + re, im, re_acc, im_acc = result + if not re: + re, re_acc, im, im_acc = im, im_acc, re, re_acc + if im: + if expr.is_number: + abs_expr, _, acc, _ = evalf(abs(N(expr, prec + 2)), + prec + 2, options) + return abs_expr, None, acc, None + else: + if 'subs' in options: + return libmp.mpc_abs((re, im), prec), None, re_acc, None + return abs(expr), None, prec, None + elif re: + return mpf_abs(re), None, re_acc, None + else: + return None, None, None, None + + +def get_complex_part(expr: Expr, no: int, prec: int, options: OPT_DICT) -> TMP_RES: + """no = 0 for real part, no = 1 for imaginary part""" + workprec = prec + i = 0 + while 1: + res = evalf(expr, workprec, options) + if res is S.ComplexInfinity: + return fnan, None, prec, None + value, accuracy = res[no::2] + # XXX is the last one correct? Consider re((1+I)**2).n() + if (not value) or accuracy >= prec or -value[2] > prec: + return value, None, accuracy, None + workprec += max(30, 2**i) + i += 1 + + +def evalf_abs(expr: 'Abs', prec: int, options: OPT_DICT) -> TMP_RES: + return get_abs(expr.args[0], prec, options) + + +def evalf_re(expr: 're', prec: int, options: OPT_DICT) -> TMP_RES: + return get_complex_part(expr.args[0], 0, prec, options) + + +def evalf_im(expr: 'im', prec: int, options: OPT_DICT) -> TMP_RES: + return get_complex_part(expr.args[0], 1, prec, options) + + +def finalize_complex(re: MPF_TUP, im: MPF_TUP, prec: int) -> TMP_RES: + if re == fzero and im == fzero: + raise ValueError("got complex zero with unknown accuracy") + elif re == fzero: + return None, im, None, prec + elif im == fzero: + return re, None, prec, None + + size_re = fastlog(re) + size_im = fastlog(im) + if size_re > size_im: + re_acc = prec + im_acc = prec + min(-(size_re - size_im), 0) + else: + im_acc = prec + re_acc = prec + min(-(size_im - size_re), 0) + return re, im, re_acc, im_acc + + +def chop_parts(value: TMP_RES, prec: int) -> TMP_RES: + """ + Chop off tiny real or complex parts. + """ + if value is S.ComplexInfinity: + return value + re, im, re_acc, im_acc = value + # Method 1: chop based on absolute value + if re and re not in _infs_nan and (fastlog(re) < -prec + 4): + re, re_acc = None, None + if im and im not in _infs_nan and (fastlog(im) < -prec + 4): + im, im_acc = None, None + # Method 2: chop if inaccurate and relatively small + if re and im: + delta = fastlog(re) - fastlog(im) + if re_acc < 2 and (delta - re_acc <= -prec + 4): + re, re_acc = None, None + if im_acc < 2 and (delta - im_acc >= prec - 4): + im, im_acc = None, None + return re, im, re_acc, im_acc + + +def check_target(expr: Expr, result: TMP_RES, prec: int): + a = complex_accuracy(result) + if a < prec: + raise PrecisionExhausted("Failed to distinguish the expression: \n\n%s\n\n" + "from zero. Try simplifying the input, using chop=True, or providing " + "a higher maxn for evalf" % (expr)) + + +def get_integer_part(expr: Expr, no: int, options: OPT_DICT, return_ints=False) -> \ + TMP_RES | tuple[int, int]: + """ + With no = 1, computes ceiling(expr) + With no = -1, computes floor(expr) + + Note: this function either gives the exact result or signals failure. + """ + from sympy.functions.elementary.complexes import re, im + # The expression is likely less than 2^30 or so + assumed_size = 30 + result = evalf(expr, assumed_size, options) + if result is S.ComplexInfinity: + raise ValueError("Cannot get integer part of Complex Infinity") + ire, iim, ire_acc, iim_acc = result + + # We now know the size, so we can calculate how much extra precision + # (if any) is needed to get within the nearest integer + if ire and iim: + gap = max(fastlog(ire) - ire_acc, fastlog(iim) - iim_acc) + elif ire: + gap = fastlog(ire) - ire_acc + elif iim: + gap = fastlog(iim) - iim_acc + else: + # ... or maybe the expression was exactly zero + if return_ints: + return 0, 0 + else: + return None, None, None, None + + margin = 10 + + if gap >= -margin: + prec = margin + assumed_size + gap + ire, iim, ire_acc, iim_acc = evalf( + expr, prec, options) + else: + prec = assumed_size + + # We can now easily find the nearest integer, but to find floor/ceil, we + # must also calculate whether the difference to the nearest integer is + # positive or negative (which may fail if very close). + def calc_part(re_im: Expr, nexpr: MPF_TUP): + from .add import Add + _, _, exponent, _ = nexpr + is_int = exponent == 0 + nint = int(to_int(nexpr, rnd)) + if is_int: + # make sure that we had enough precision to distinguish + # between nint and the re or im part (re_im) of expr that + # was passed to calc_part + ire, iim, ire_acc, iim_acc = evalf( + re_im - nint, 10, options) # don't need much precision + assert not iim + size = -fastlog(ire) + 2 # -ve b/c ire is less than 1 + if size > prec: + ire, iim, ire_acc, iim_acc = evalf( + re_im, size, options) + assert not iim + nexpr = ire + nint = int(to_int(nexpr, rnd)) + _, _, new_exp, _ = ire + is_int = new_exp == 0 + if not is_int: + # if there are subs and they all contain integer re/im parts + # then we can (hopefully) safely substitute them into the + # expression + s = options.get('subs', False) + if s: + # use strict=False with as_int because we take + # 2.0 == 2 + def is_int_reim(x): + """Check for integer or integer + I*integer.""" + try: + as_int(x, strict=False) + return True + except ValueError: + try: + [as_int(i, strict=False) for i in x.as_real_imag()] + return True + except ValueError: + return False + + if all(is_int_reim(v) for v in s.values()): + re_im = re_im.subs(s) + + re_im = Add(re_im, -nint, evaluate=False) + x, _, x_acc, _ = evalf(re_im, 10, options) + try: + check_target(re_im, (x, None, x_acc, None), 3) + except PrecisionExhausted: + if not re_im.equals(0): + raise PrecisionExhausted + x = fzero + nint += int(no*(mpf_cmp(x or fzero, fzero) == no)) + nint = from_int(nint) + return nint, INF + + re_, im_, re_acc, im_acc = None, None, None, None + + if ire is not None and ire != fzero: + re_, re_acc = calc_part(re(expr, evaluate=False), ire) + if iim is not None and iim != fzero: + im_, im_acc = calc_part(im(expr, evaluate=False), iim) + + if return_ints: + return int(to_int(re_ or fzero)), int(to_int(im_ or fzero)) + return re_, im_, re_acc, im_acc + + +def evalf_ceiling(expr: 'ceiling', prec: int, options: OPT_DICT) -> TMP_RES: + return get_integer_part(expr.args[0], 1, options) + + +def evalf_floor(expr: 'floor', prec: int, options: OPT_DICT) -> TMP_RES: + return get_integer_part(expr.args[0], -1, options) + + +def evalf_float(expr: 'Float', prec: int, options: OPT_DICT) -> TMP_RES: + return expr._mpf_, None, prec, None + + +def evalf_rational(expr: 'Rational', prec: int, options: OPT_DICT) -> TMP_RES: + return from_rational(expr.p, expr.q, prec), None, prec, None + + +def evalf_integer(expr: 'Integer', prec: int, options: OPT_DICT) -> TMP_RES: + return from_int(expr.p, prec), None, prec, None + +#----------------------------------------------------------------------------# +# # +# Arithmetic operations # +# # +#----------------------------------------------------------------------------# + + +def add_terms(terms: list, prec: int, target_prec: int) -> \ + tuple[MPF_TUP | SCALED_ZERO_TUP | None, int | None]: + """ + Helper for evalf_add. Adds a list of (mpfval, accuracy) terms. + + Returns + ======= + + - None, None if there are no non-zero terms; + - terms[0] if there is only 1 term; + - scaled_zero if the sum of the terms produces a zero by cancellation + e.g. mpfs representing 1 and -1 would produce a scaled zero which need + special handling since they are not actually zero and they are purposely + malformed to ensure that they cannot be used in anything but accuracy + calculations; + - a tuple that is scaled to target_prec that corresponds to the + sum of the terms. + + The returned mpf tuple will be normalized to target_prec; the input + prec is used to define the working precision. + + XXX explain why this is needed and why one cannot just loop using mpf_add + """ + + terms = [t for t in terms if not iszero(t[0])] + if not terms: + return None, None + elif len(terms) == 1: + return terms[0] + + # see if any argument is NaN or oo and thus warrants a special return + special = [] + from .numbers import Float + for t in terms: + arg = Float._new(t[0], 1) + if arg is S.NaN or arg.is_infinite: + special.append(arg) + if special: + from .add import Add + rv = evalf(Add(*special), prec + 4, {}) + return rv[0], rv[2] + + working_prec = 2*prec + sum_man, sum_exp = 0, 0 + absolute_err: list[int] = [] + + for x, accuracy in terms: + sign, man, exp, bc = x + if sign: + man = -man + absolute_err.append(bc + exp - accuracy) + delta = exp - sum_exp + if exp >= sum_exp: + # x much larger than existing sum? + # first: quick test + if ((delta > working_prec) and + ((not sum_man) or + delta - bitcount(abs(sum_man)) > working_prec)): + sum_man = man + sum_exp = exp + else: + sum_man += (man << delta) + else: + delta = -delta + # x much smaller than existing sum? + if delta - bc > working_prec: + if not sum_man: + sum_man, sum_exp = man, exp + else: + sum_man = (sum_man << delta) + man + sum_exp = exp + absolute_error = max(absolute_err) + if not sum_man: + return scaled_zero(absolute_error) + if sum_man < 0: + sum_sign = 1 + sum_man = -sum_man + else: + sum_sign = 0 + sum_bc = bitcount(sum_man) + sum_accuracy = sum_exp + sum_bc - absolute_error + r = normalize(sum_sign, sum_man, sum_exp, sum_bc, target_prec, + rnd), sum_accuracy + return r + + +def evalf_add(v: 'Add', prec: int, options: OPT_DICT) -> TMP_RES: + res = pure_complex(v) + if res: + h, c = res + re, _, re_acc, _ = evalf(h, prec, options) + im, _, im_acc, _ = evalf(c, prec, options) + return re, im, re_acc, im_acc + + oldmaxprec = options.get('maxprec', DEFAULT_MAXPREC) + + i = 0 + target_prec = prec + while 1: + options['maxprec'] = min(oldmaxprec, 2*prec) + + terms = [evalf(arg, prec + 10, options) for arg in v.args] + n = terms.count(S.ComplexInfinity) + if n >= 2: + return fnan, None, prec, None + re, re_acc = add_terms( + [a[0::2] for a in terms if isinstance(a, tuple) and a[0]], prec, target_prec) + im, im_acc = add_terms( + [a[1::2] for a in terms if isinstance(a, tuple) and a[1]], prec, target_prec) + if n == 1: + if re in (finf, fninf, fnan) or im in (finf, fninf, fnan): + return fnan, None, prec, None + return S.ComplexInfinity + acc = complex_accuracy((re, im, re_acc, im_acc)) + if acc >= target_prec: + if options.get('verbose'): + print("ADD: wanted", target_prec, "accurate bits, got", re_acc, im_acc) + break + else: + if (prec - target_prec) > options['maxprec']: + break + + prec = prec + max(10 + 2**i, target_prec - acc) + i += 1 + if options.get('verbose'): + print("ADD: restarting with prec", prec) + + options['maxprec'] = oldmaxprec + if iszero(re, scaled=True): + re = scaled_zero(re) + if iszero(im, scaled=True): + im = scaled_zero(im) + return re, im, re_acc, im_acc + + +def evalf_mul(v: 'Mul', prec: int, options: OPT_DICT) -> TMP_RES: + res = pure_complex(v) + if res: + # the only pure complex that is a mul is h*I + _, h = res + im, _, im_acc, _ = evalf(h, prec, options) + return None, im, None, im_acc + args = list(v.args) + + # see if any argument is NaN or oo and thus warrants a special return + has_zero = False + special = [] + from .numbers import Float + for arg in args: + result = evalf(arg, prec, options) + if result is S.ComplexInfinity: + special.append(result) + continue + if result[0] is None: + if result[1] is None: + has_zero = True + continue + num = Float._new(result[0], 1) + if num is S.NaN: + return fnan, None, prec, None + if num.is_infinite: + special.append(num) + if special: + if has_zero: + return fnan, None, prec, None + from .mul import Mul + return evalf(Mul(*special), prec + 4, {}) + if has_zero: + return None, None, None, None + + # With guard digits, multiplication in the real case does not destroy + # accuracy. This is also true in the complex case when considering the + # total accuracy; however accuracy for the real or imaginary parts + # separately may be lower. + acc = prec + + # XXX: big overestimate + working_prec = prec + len(args) + 5 + + # Empty product is 1 + start = man, exp, bc = MPZ(1), 0, 1 + + # First, we multiply all pure real or pure imaginary numbers. + # direction tells us that the result should be multiplied by + # I**direction; all other numbers get put into complex_factors + # to be multiplied out after the first phase. + last = len(args) + direction = 0 + args.append(S.One) + complex_factors = [] + + for i, arg in enumerate(args): + if i != last and pure_complex(arg): + args[-1] = (args[-1]*arg).expand() + continue + elif i == last and arg is S.One: + continue + re, im, re_acc, im_acc = evalf(arg, working_prec, options) + if re and im: + complex_factors.append((re, im, re_acc, im_acc)) + continue + elif re: + (s, m, e, b), w_acc = re, re_acc + elif im: + (s, m, e, b), w_acc = im, im_acc + direction += 1 + else: + return None, None, None, None + direction += 2*s + man *= m + exp += e + bc += b + while bc > 3*working_prec: + man >>= working_prec + exp += working_prec + bc -= working_prec + acc = min(acc, w_acc) + sign = (direction & 2) >> 1 + if not complex_factors: + v = normalize(sign, man, exp, bitcount(man), prec, rnd) + # multiply by i + if direction & 1: + return None, v, None, acc + else: + return v, None, acc, None + else: + # initialize with the first term + if (man, exp, bc) != start: + # there was a real part; give it an imaginary part + re, im = (sign, man, exp, bitcount(man)), (0, MPZ(0), 0, 0) + i0 = 0 + else: + # there is no real part to start (other than the starting 1) + wre, wim, wre_acc, wim_acc = complex_factors[0] + acc = min(acc, + complex_accuracy((wre, wim, wre_acc, wim_acc))) + re = wre + im = wim + i0 = 1 + + for wre, wim, wre_acc, wim_acc in complex_factors[i0:]: + # acc is the overall accuracy of the product; we aren't + # computing exact accuracies of the product. + acc = min(acc, + complex_accuracy((wre, wim, wre_acc, wim_acc))) + + use_prec = working_prec + A = mpf_mul(re, wre, use_prec) + B = mpf_mul(mpf_neg(im), wim, use_prec) + C = mpf_mul(re, wim, use_prec) + D = mpf_mul(im, wre, use_prec) + re = mpf_add(A, B, use_prec) + im = mpf_add(C, D, use_prec) + if options.get('verbose'): + print("MUL: wanted", prec, "accurate bits, got", acc) + # multiply by I + if direction & 1: + re, im = mpf_neg(im), re + return re, im, acc, acc + + +def evalf_pow(v: 'Pow', prec: int, options) -> TMP_RES: + + target_prec = prec + base, exp = v.args + + # We handle x**n separately. This has two purposes: 1) it is much + # faster, because we avoid calling evalf on the exponent, and 2) it + # allows better handling of real/imaginary parts that are exactly zero + if exp.is_Integer: + p: int = exp.p # type: ignore + # Exact + if not p: + return fone, None, prec, None + # Exponentiation by p magnifies relative error by |p|, so the + # base must be evaluated with increased precision if p is large + prec += int(math.log2(abs(p))) + result = evalf(base, prec + 5, options) + if result is S.ComplexInfinity: + if p < 0: + return None, None, None, None + return result + re, im, re_acc, im_acc = result + # Real to integer power + if re and not im: + return mpf_pow_int(re, p, target_prec), None, target_prec, None + # (x*I)**n = I**n * x**n + if im and not re: + z = mpf_pow_int(im, p, target_prec) + case = p % 4 + if case == 0: + return z, None, target_prec, None + if case == 1: + return None, z, None, target_prec + if case == 2: + return mpf_neg(z), None, target_prec, None + if case == 3: + return None, mpf_neg(z), None, target_prec + # Zero raised to an integer power + if not re: + if p < 0: + return S.ComplexInfinity + return None, None, None, None + # General complex number to arbitrary integer power + re, im = libmp.mpc_pow_int((re, im), p, prec) + # Assumes full accuracy in input + return finalize_complex(re, im, target_prec) + + result = evalf(base, prec + 5, options) + if result is S.ComplexInfinity: + if exp.is_Rational: + if exp < 0: + return None, None, None, None + return result + raise NotImplementedError + + # Pure square root + if exp is S.Half: + xre, xim, _, _ = result + # General complex square root + if xim: + re, im = libmp.mpc_sqrt((xre or fzero, xim), prec) + return finalize_complex(re, im, prec) + if not xre: + return None, None, None, None + # Square root of a negative real number + if mpf_lt(xre, fzero): + return None, mpf_sqrt(mpf_neg(xre), prec), None, prec + # Positive square root + return mpf_sqrt(xre, prec), None, prec, None + + # We first evaluate the exponent to find its magnitude + # This determines the working precision that must be used + prec += 10 + result = evalf(exp, prec, options) + if result is S.ComplexInfinity: + return fnan, None, prec, None + yre, yim, _, _ = result + # Special cases: x**0 + if not (yre or yim): + return fone, None, prec, None + + ysize = fastlog(yre) + # Restart if too big + # XXX: prec + ysize might exceed maxprec + if ysize > 5: + prec += ysize + yre, yim, _, _ = evalf(exp, prec, options) + + # Pure exponential function; no need to evalf the base + if base is S.Exp1: + if yim: + re, im = libmp.mpc_exp((yre or fzero, yim), prec) + return finalize_complex(re, im, target_prec) + return mpf_exp(yre, target_prec), None, target_prec, None + + xre, xim, _, _ = evalf(base, prec + 5, options) + # 0**y + if not (xre or xim): + if yim: + return fnan, None, prec, None + if yre[0] == 1: # y < 0 + return S.ComplexInfinity + return None, None, None, None + + # (real ** complex) or (complex ** complex) + if yim: + re, im = libmp.mpc_pow( + (xre or fzero, xim or fzero), (yre or fzero, yim), + target_prec) + return finalize_complex(re, im, target_prec) + # complex ** real + if xim: + re, im = libmp.mpc_pow_mpf((xre or fzero, xim), yre, target_prec) + return finalize_complex(re, im, target_prec) + # negative ** real + elif mpf_lt(xre, fzero): + re, im = libmp.mpc_pow_mpf((xre, fzero), yre, target_prec) + return finalize_complex(re, im, target_prec) + # positive ** real + else: + return mpf_pow(xre, yre, target_prec), None, target_prec, None + + +#----------------------------------------------------------------------------# +# # +# Special functions # +# # +#----------------------------------------------------------------------------# + + +def evalf_exp(expr: 'exp', prec: int, options: OPT_DICT) -> TMP_RES: + from .power import Pow + return evalf_pow(Pow(S.Exp1, expr.exp, evaluate=False), prec, options) + + +def evalf_trig(v: Expr, prec: int, options: OPT_DICT) -> TMP_RES: + """ + This function handles sin , cos and tan of complex arguments. + + """ + from sympy.functions.elementary.trigonometric import cos, sin, tan + if isinstance(v, cos): + func = mpf_cos + elif isinstance(v, sin): + func = mpf_sin + elif isinstance(v,tan): + func = mpf_tan + else: + raise NotImplementedError + arg = v.args[0] + # 20 extra bits is possibly overkill. It does make the need + # to restart very unlikely + xprec = prec + 20 + re, im, re_acc, im_acc = evalf(arg, xprec, options) + if im: + if 'subs' in options: + v = v.subs(options['subs']) + return evalf(v._eval_evalf(prec), prec, options) + if not re: + if isinstance(v, cos): + return fone, None, prec, None + elif isinstance(v, sin): + return None, None, None, None + elif isinstance(v,tan): + return None, None, None, None + else: + raise NotImplementedError + # For trigonometric functions, we are interested in the + # fixed-point (absolute) accuracy of the argument. + xsize = fastlog(re) + # Magnitude <= 1.0. OK to compute directly, because there is no + # danger of hitting the first root of cos (with sin, magnitude + # <= 2.0 would actually be ok) + if xsize < 1: + return func(re, prec, rnd), None, prec, None + # Very large + if xsize >= 10: + xprec = prec + xsize + re, im, re_acc, im_acc = evalf(arg, xprec, options) + # Need to repeat in case the argument is very close to a + # multiple of pi (or pi/2), hitting close to a root + while 1: + y = func(re, prec, rnd) + ysize = fastlog(y) + gap = -ysize + accuracy = (xprec - xsize) - gap + if accuracy < prec: + if options.get('verbose'): + print("SIN/COS/TAN", accuracy, "wanted", prec, "gap", gap) + print(to_str(y, 10)) + if xprec > options.get('maxprec', DEFAULT_MAXPREC): + return y, None, accuracy, None + xprec += gap + re, im, re_acc, im_acc = evalf(arg, xprec, options) + continue + else: + return y, None, prec, None + + +def evalf_log(expr: 'log', prec: int, options: OPT_DICT) -> TMP_RES: + if len(expr.args)>1: + expr = expr.doit() + return evalf(expr, prec, options) + arg = expr.args[0] + workprec = prec + 10 + result = evalf(arg, workprec, options) + if result is S.ComplexInfinity: + return result + xre, xim, xacc, _ = result + + # evalf can return NoneTypes if chop=True + # issue 18516, 19623 + if xre is xim is None: + # Dear reviewer, I do not know what -inf is; + # it looks to be (1, 0, -789, -3) + # but I'm not sure in general, + # so we just let mpmath figure + # it out by taking log of 0 directly. + # It would be better to return -inf instead. + xre = fzero + + if xim: + from sympy.functions.elementary.complexes import Abs + from sympy.functions.elementary.exponential import log + + # XXX: use get_abs etc instead + re = evalf_log( + log(Abs(arg, evaluate=False), evaluate=False), prec, options) + im = mpf_atan2(xim, xre or fzero, prec) + return re[0], im, re[2], prec + + imaginary_term = (mpf_cmp(xre, fzero) < 0) + + re = mpf_log(mpf_abs(xre), prec, rnd) + size = fastlog(re) + if prec - size > workprec and re != fzero: + from .add import Add + # We actually need to compute 1+x accurately, not x + add = Add(S.NegativeOne, arg, evaluate=False) + xre, xim, _, _ = evalf_add(add, prec, options) + prec2 = workprec - fastlog(xre) + # xre is now x - 1 so we add 1 back here to calculate x + re = mpf_log(mpf_abs(mpf_add(xre, fone, prec2)), prec, rnd) + + re_acc = prec + + if imaginary_term: + return re, mpf_pi(prec), re_acc, prec + else: + return re, None, re_acc, None + + +def evalf_atan(v: 'atan', prec: int, options: OPT_DICT) -> TMP_RES: + arg = v.args[0] + xre, xim, reacc, imacc = evalf(arg, prec + 5, options) + if xre is xim is None: + return (None,)*4 + if xim: + raise NotImplementedError + return mpf_atan(xre, prec, rnd), None, prec, None + + +def evalf_subs(prec: int, subs: dict) -> dict: + """ Change all Float entries in `subs` to have precision prec. """ + newsubs = {} + for a, b in subs.items(): + b = S(b) + if b.is_Float: + b = b._eval_evalf(prec) + newsubs[a] = b + return newsubs + + +def evalf_piecewise(expr: Expr, prec: int, options: OPT_DICT) -> TMP_RES: + from .numbers import Float, Integer + if 'subs' in options: + expr = expr.subs(evalf_subs(prec, options['subs'])) + newopts = options.copy() + del newopts['subs'] + if hasattr(expr, 'func'): + return evalf(expr, prec, newopts) + if isinstance(expr, float): + return evalf(Float(expr), prec, newopts) + if isinstance(expr, int): + return evalf(Integer(expr), prec, newopts) + + # We still have undefined symbols + raise NotImplementedError + + +def evalf_alg_num(a: 'AlgebraicNumber', prec: int, options: OPT_DICT) -> TMP_RES: + return evalf(a.to_root(), prec, options) + +#----------------------------------------------------------------------------# +# # +# High-level operations # +# # +#----------------------------------------------------------------------------# + + +def as_mpmath(x: Any, prec: int, options: OPT_DICT) -> mpc | mpf: + from .numbers import Infinity, NegativeInfinity, Zero + x = sympify(x) + if isinstance(x, Zero) or x == 0.0: + return mpf(0) + if isinstance(x, Infinity): + return mpf('inf') + if isinstance(x, NegativeInfinity): + return mpf('-inf') + # XXX + result = evalf(x, prec, options) + return quad_to_mpmath(result) + + +def do_integral(expr: 'Integral', prec: int, options: OPT_DICT) -> TMP_RES: + func = expr.args[0] + x, xlow, xhigh = expr.args[1] + if xlow == xhigh: + xlow = xhigh = 0 + elif x not in func.free_symbols: + # only the difference in limits matters in this case + # so if there is a symbol in common that will cancel + # out when taking the difference, then use that + # difference + if xhigh.free_symbols & xlow.free_symbols: + diff = xhigh - xlow + if diff.is_number: + xlow, xhigh = 0, diff + + oldmaxprec = options.get('maxprec', DEFAULT_MAXPREC) + options['maxprec'] = min(oldmaxprec, 2*prec) + + with workprec(prec + 5): + xlow = as_mpmath(xlow, prec + 15, options) + xhigh = as_mpmath(xhigh, prec + 15, options) + + # Integration is like summation, and we can phone home from + # the integrand function to update accuracy summation style + # Note that this accuracy is inaccurate, since it fails + # to account for the variable quadrature weights, + # but it is better than nothing + + from sympy.functions.elementary.trigonometric import cos, sin + from .symbol import Wild + + have_part = [False, False] + max_real_term: float | int = MINUS_INF + max_imag_term: float | int = MINUS_INF + + def f(t: Expr) -> mpc | mpf: + nonlocal max_real_term, max_imag_term + re, im, re_acc, im_acc = evalf(func, mp.prec, {'subs': {x: t}}) + + have_part[0] = re or have_part[0] + have_part[1] = im or have_part[1] + + max_real_term = max(max_real_term, fastlog(re)) + max_imag_term = max(max_imag_term, fastlog(im)) + + if im: + return mpc(re or fzero, im) + return mpf(re or fzero) + + if options.get('quad') == 'osc': + A = Wild('A', exclude=[x]) + B = Wild('B', exclude=[x]) + D = Wild('D') + m = func.match(cos(A*x + B)*D) + if not m: + m = func.match(sin(A*x + B)*D) + if not m: + raise ValueError("An integrand of the form sin(A*x+B)*f(x) " + "or cos(A*x+B)*f(x) is required for oscillatory quadrature") + period = as_mpmath(2*S.Pi/m[A], prec + 15, options) + result = quadosc(f, [xlow, xhigh], period=period) + # XXX: quadosc does not do error detection yet + quadrature_error = MINUS_INF + else: + result, quadrature_err = quadts(f, [xlow, xhigh], error=1) + quadrature_error = fastlog(quadrature_err._mpf_) + + options['maxprec'] = oldmaxprec + + if have_part[0]: + re: MPF_TUP | None = result.real._mpf_ + re_acc: int | None + if re == fzero: + re_s, re_acc = scaled_zero(int(-max(prec, max_real_term, quadrature_error))) + re = scaled_zero(re_s) # handled ok in evalf_integral + else: + re_acc = int(-max(max_real_term - fastlog(re) - prec, quadrature_error)) + else: + re, re_acc = None, None + + if have_part[1]: + im: MPF_TUP | None = result.imag._mpf_ + im_acc: int | None + if im == fzero: + im_s, im_acc = scaled_zero(int(-max(prec, max_imag_term, quadrature_error))) + im = scaled_zero(im_s) # handled ok in evalf_integral + else: + im_acc = int(-max(max_imag_term - fastlog(im) - prec, quadrature_error)) + else: + im, im_acc = None, None + + result = re, im, re_acc, im_acc + return result + + +def evalf_integral(expr: 'Integral', prec: int, options: OPT_DICT) -> TMP_RES: + limits = expr.limits + if len(limits) != 1 or len(limits[0]) != 3: + raise NotImplementedError + workprec = prec + i = 0 + maxprec = options.get('maxprec', INF) + while 1: + result = do_integral(expr, workprec, options) + accuracy = complex_accuracy(result) + if accuracy >= prec: # achieved desired precision + break + if workprec >= maxprec: # can't increase accuracy any more + break + if accuracy == -1: + # maybe the answer really is zero and maybe we just haven't increased + # the precision enough. So increase by doubling to not take too long + # to get to maxprec. + workprec *= 2 + else: + workprec += max(prec, 2**i) + workprec = min(workprec, maxprec) + i += 1 + return result + + +def check_convergence(numer: Expr, denom: Expr, n: Symbol) -> tuple[int, Any, Any]: + """ + Returns + ======= + + (h, g, p) where + -- h is: + > 0 for convergence of rate 1/factorial(n)**h + < 0 for divergence of rate factorial(n)**(-h) + = 0 for geometric or polynomial convergence or divergence + + -- abs(g) is: + > 1 for geometric convergence of rate 1/h**n + < 1 for geometric divergence of rate h**n + = 1 for polynomial convergence or divergence + + (g < 0 indicates an alternating series) + + -- p is: + > 1 for polynomial convergence of rate 1/n**h + <= 1 for polynomial divergence of rate n**(-h) + + """ + from sympy.polys.polytools import Poly + npol = Poly(numer, n) + dpol = Poly(denom, n) + p = npol.degree() + q = dpol.degree() + rate = q - p + if rate: + return rate, None, None + constant = dpol.LC() / npol.LC() + from .numbers import equal_valued + if not equal_valued(abs(constant), 1): + return rate, constant, None + if npol.degree() == dpol.degree() == 0: + return rate, constant, 0 + pc = npol.all_coeffs()[1] + qc = dpol.all_coeffs()[1] + return rate, constant, (qc - pc)/dpol.LC() + + +def hypsum(expr: Expr, n: Symbol, start: int, prec: int) -> mpf: + """ + Sum a rapidly convergent infinite hypergeometric series with + given general term, e.g. e = hypsum(1/factorial(n), n). The + quotient between successive terms must be a quotient of integer + polynomials. + """ + from .numbers import Float, equal_valued + from sympy.simplify.simplify import hypersimp + + if prec == float('inf'): + raise NotImplementedError('does not support inf prec') + + if start: + expr = expr.subs(n, n + start) + hs = hypersimp(expr, n) + if hs is None: + raise NotImplementedError("a hypergeometric series is required") + num, den = hs.as_numer_denom() + + func1 = lambdify(n, num) + func2 = lambdify(n, den) + + h, g, p = check_convergence(num, den, n) + + if h < 0: + raise ValueError("Sum diverges like (n!)^%i" % (-h)) + + eterm = expr.subs(n, 0) + if not eterm.is_Rational: + raise NotImplementedError("Non rational term functionality is not implemented.") + + term: Rational = eterm # type: ignore + + # Direct summation if geometric or faster + if h > 0 or (h == 0 and abs(g) > 1): + term = (MPZ(term.p) << prec) // term.q + s = term + k = 1 + while abs(term) > 5: + term *= MPZ(func1(k - 1)) + term //= MPZ(func2(k - 1)) + s += term + k += 1 + return from_man_exp(s, -prec) + else: + alt = g < 0 + if abs(g) < 1: + raise ValueError("Sum diverges like (%i)^n" % abs(1/g)) + if p < 1 or (equal_valued(p, 1) and not alt): + raise ValueError("Sum diverges like n^%i" % (-p)) + # We have polynomial convergence: use Richardson extrapolation + vold = None + ndig = prec_to_dps(prec) + while True: + # Need to use at least quad precision because a lot of cancellation + # might occur in the extrapolation process; we check the answer to + # make sure that the desired precision has been reached, too. + prec2 = 4*prec + term0 = (MPZ(term.p) << prec2) // term.q + + def summand(k, _term=[term0]): + if k: + k = int(k) + _term[0] *= MPZ(func1(k - 1)) + _term[0] //= MPZ(func2(k - 1)) + return make_mpf(from_man_exp(_term[0], -prec2)) + + with workprec(prec): + v = nsum(summand, [0, mpmath_inf], method='richardson') + vf = Float(v, ndig) + if vold is not None and vold == vf: + break + prec += prec # double precision each time + vold = vf + + return v._mpf_ + + +def evalf_prod(expr: 'Product', prec: int, options: OPT_DICT) -> TMP_RES: + if all((l[1] - l[2]).is_Integer for l in expr.limits): + result = evalf(expr.doit(), prec=prec, options=options) + else: + from sympy.concrete.summations import Sum + result = evalf(expr.rewrite(Sum), prec=prec, options=options) + return result + + +def evalf_sum(expr: 'Sum', prec: int, options: OPT_DICT) -> TMP_RES: + from .numbers import Float + if 'subs' in options: + expr = expr.subs(options['subs']) # type: ignore + func = expr.function + limits = expr.limits + if len(limits) != 1 or len(limits[0]) != 3: + raise NotImplementedError + if func.is_zero: + return None, None, prec, None + prec2 = prec + 10 + try: + n, a, b = limits[0] + if b is not S.Infinity or a is S.NegativeInfinity or a != int(a): + raise NotImplementedError + # Use fast hypergeometric summation if possible + v = hypsum(func, n, int(a), prec2) + delta = prec - fastlog(v) + if fastlog(v) < -10: + v = hypsum(func, n, int(a), delta) + return v, None, min(prec, delta), None + except NotImplementedError: + # Euler-Maclaurin summation for general series + eps = Float(2.0)**(-prec) + for i in range(1, 5): + m = n = 2**i * prec + s, err = expr.euler_maclaurin(m=m, n=n, eps=eps, + eval_integral=False) + err = err.evalf() + if err is S.NaN: + raise NotImplementedError + if err <= eps: + break + err = fastlog(evalf(abs(err), 20, options)[0]) + re, im, re_acc, im_acc = evalf(s, prec2, options) + if re_acc is None: + re_acc = -err + if im_acc is None: + im_acc = -err + return re, im, re_acc, im_acc + + +#----------------------------------------------------------------------------# +# # +# Symbolic interface # +# # +#----------------------------------------------------------------------------# + +def evalf_symbol(x: Expr, prec: int, options: OPT_DICT) -> TMP_RES: + val = options['subs'][x] + if isinstance(val, mpf): + if not val: + return None, None, None, None + return val._mpf_, None, prec, None + else: + if '_cache' not in options: + options['_cache'] = {} + cache = options['_cache'] + cached, cached_prec = cache.get(x, (None, MINUS_INF)) + if cached_prec >= prec: + return cached + v = evalf(sympify(val), prec, options) + cache[x] = (v, prec) + return v + +evalf_table: dict[Type[Expr], Callable[[Expr, int, OPT_DICT], TMP_RES]] = {} + + +def _create_evalf_table(): + global evalf_table + from sympy.concrete.products import Product + from sympy.concrete.summations import Sum + from .add import Add + from .mul import Mul + from .numbers import Exp1, Float, Half, ImaginaryUnit, Integer, NaN, NegativeOne, One, Pi, Rational, \ + Zero, ComplexInfinity, AlgebraicNumber + from .power import Pow + from .symbol import Dummy, Symbol + from sympy.functions.elementary.complexes import Abs, im, re + from sympy.functions.elementary.exponential import exp, log + from sympy.functions.elementary.integers import ceiling, floor + from sympy.functions.elementary.piecewise import Piecewise + from sympy.functions.elementary.trigonometric import atan, cos, sin, tan + from sympy.integrals.integrals import Integral + evalf_table = { + Symbol: evalf_symbol, + Dummy: evalf_symbol, + Float: evalf_float, + Rational: evalf_rational, + Integer: evalf_integer, + Zero: lambda x, prec, options: (None, None, prec, None), + One: lambda x, prec, options: (fone, None, prec, None), + Half: lambda x, prec, options: (fhalf, None, prec, None), + Pi: lambda x, prec, options: (mpf_pi(prec), None, prec, None), + Exp1: lambda x, prec, options: (mpf_e(prec), None, prec, None), + ImaginaryUnit: lambda x, prec, options: (None, fone, None, prec), + NegativeOne: lambda x, prec, options: (fnone, None, prec, None), + ComplexInfinity: lambda x, prec, options: S.ComplexInfinity, + NaN: lambda x, prec, options: (fnan, None, prec, None), + + exp: evalf_exp, + + cos: evalf_trig, + sin: evalf_trig, + tan: evalf_trig, + + Add: evalf_add, + Mul: evalf_mul, + Pow: evalf_pow, + + log: evalf_log, + atan: evalf_atan, + Abs: evalf_abs, + + re: evalf_re, + im: evalf_im, + floor: evalf_floor, + ceiling: evalf_ceiling, + + Integral: evalf_integral, + Sum: evalf_sum, + Product: evalf_prod, + Piecewise: evalf_piecewise, + + AlgebraicNumber: evalf_alg_num, + } + + +def evalf(x: Expr, prec: int, options: OPT_DICT) -> TMP_RES: + """ + Evaluate the ``Expr`` instance, ``x`` + to a binary precision of ``prec``. This + function is supposed to be used internally. + + Parameters + ========== + + x : Expr + The formula to evaluate to a float. + prec : int + The binary precision that the output should have. + options : dict + A dictionary with the same entries as + ``EvalfMixin.evalf`` and in addition, + ``maxprec`` which is the maximum working precision. + + Returns + ======= + + An optional tuple, ``(re, im, re_acc, im_acc)`` + which are the real, imaginary, real accuracy + and imaginary accuracy respectively. ``re`` is + an mpf value tuple and so is ``im``. ``re_acc`` + and ``im_acc`` are ints. + + NB: all these return values can be ``None``. + If all values are ``None``, then that represents 0. + Note that 0 is also represented as ``fzero = (0, 0, 0, 0)``. + """ + from sympy.functions.elementary.complexes import re as re_, im as im_ + try: + rf = evalf_table[type(x)] + r = rf(x, prec, options) + except KeyError: + # Fall back to ordinary evalf if possible + if 'subs' in options: + x = x.subs(evalf_subs(prec, options['subs'])) + xe = x._eval_evalf(prec) + if xe is None: + raise NotImplementedError + as_real_imag = getattr(xe, "as_real_imag", None) + if as_real_imag is None: + raise NotImplementedError # e.g. FiniteSet(-1.0, 1.0).evalf() + re, im = as_real_imag() + if re.has(re_) or im.has(im_): + raise NotImplementedError + if not re: + re = None + reprec = None + elif re.is_number: + re = re._to_mpmath(prec, allow_ints=False)._mpf_ + reprec = prec + else: + raise NotImplementedError + if not im: + im = None + imprec = None + elif im.is_number: + im = im._to_mpmath(prec, allow_ints=False)._mpf_ + imprec = prec + else: + raise NotImplementedError + r = re, im, reprec, imprec + + if options.get("verbose"): + print("### input", x) + print("### output", to_str(r[0] or fzero, 50) if isinstance(r, tuple) else r) + print("### raw", r) # r[0], r[2] + print() + chop = options.get('chop', False) + if chop: + if chop is True: + chop_prec = prec + else: + # convert (approximately) from given tolerance; + # the formula here will will make 1e-i rounds to 0 for + # i in the range +/-27 while 2e-i will not be chopped + chop_prec = int(round(-3.321*math.log10(chop) + 2.5)) + if chop_prec == 3: + chop_prec -= 1 + r = chop_parts(r, chop_prec) + if options.get("strict"): + check_target(x, r, prec) + return r + + +def quad_to_mpmath(q, ctx=None): + """Turn the quad returned by ``evalf`` into an ``mpf`` or ``mpc``. """ + mpc = make_mpc if ctx is None else ctx.make_mpc + mpf = make_mpf if ctx is None else ctx.make_mpf + if q is S.ComplexInfinity: + raise NotImplementedError + re, im, _, _ = q + if im: + if not re: + re = fzero + return mpc((re, im)) + elif re: + return mpf(re) + else: + return mpf(fzero) + + +class EvalfMixin: + """Mixin class adding evalf capability.""" + + __slots__: tuple[str, ...] = () + + def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False): + """ + Evaluate the given formula to an accuracy of *n* digits. + + Parameters + ========== + + subs : dict, optional + Substitute numerical values for symbols, e.g. + ``subs={x:3, y:1+pi}``. The substitutions must be given as a + dictionary. + + maxn : int, optional + Allow a maximum temporary working precision of maxn digits. + + chop : bool or number, optional + Specifies how to replace tiny real or imaginary parts in + subresults by exact zeros. + + When ``True`` the chop value defaults to standard precision. + + Otherwise the chop value is used to determine the + magnitude of "small" for purposes of chopping. + + >>> from sympy import N + >>> x = 1e-4 + >>> N(x, chop=True) + 0.000100000000000000 + >>> N(x, chop=1e-5) + 0.000100000000000000 + >>> N(x, chop=1e-4) + 0 + + strict : bool, optional + Raise ``PrecisionExhausted`` if any subresult fails to + evaluate to full accuracy, given the available maxprec. + + quad : str, optional + Choose algorithm for numerical quadrature. By default, + tanh-sinh quadrature is used. For oscillatory + integrals on an infinite interval, try ``quad='osc'``. + + verbose : bool, optional + Print debug information. + + Notes + ===== + + When Floats are naively substituted into an expression, + precision errors may adversely affect the result. For example, + adding 1e16 (a Float) to 1 will truncate to 1e16; if 1e16 is + then subtracted, the result will be 0. + That is exactly what happens in the following: + + >>> from sympy.abc import x, y, z + >>> values = {x: 1e16, y: 1, z: 1e16} + >>> (x + y - z).subs(values) + 0 + + Using the subs argument for evalf is the accurate way to + evaluate such an expression: + + >>> (x + y - z).evalf(subs=values) + 1.00000000000000 + """ + from .numbers import Float, Number + n = n if n is not None else 15 + + if subs and is_sequence(subs): + raise TypeError('subs must be given as a dictionary') + + # for sake of sage that doesn't like evalf(1) + if n == 1 and isinstance(self, Number): + from .expr import _mag + rv = self.evalf(2, subs, maxn, chop, strict, quad, verbose) + m = _mag(rv) + rv = rv.round(1 - m) + return rv + + if not evalf_table: + _create_evalf_table() + prec = dps_to_prec(n) + options = {'maxprec': max(prec, int(maxn*LG10)), 'chop': chop, + 'strict': strict, 'verbose': verbose} + if subs is not None: + options['subs'] = subs + if quad is not None: + options['quad'] = quad + try: + result = evalf(self, prec + 4, options) + except NotImplementedError: + # Fall back to the ordinary evalf + if hasattr(self, 'subs') and subs is not None: # issue 20291 + v = self.subs(subs)._eval_evalf(prec) + else: + v = self._eval_evalf(prec) + if v is None: + return self + elif not v.is_number: + return v + try: + # If the result is numerical, normalize it + result = evalf(v, prec, options) + except NotImplementedError: + # Probably contains symbols or unknown functions + return v + if result is S.ComplexInfinity: + return result + re, im, re_acc, im_acc = result + if re is S.NaN or im is S.NaN: + return S.NaN + if re: + p = max(min(prec, re_acc), 1) + re = Float._new(re, p) + else: + re = S.Zero + if im: + p = max(min(prec, im_acc), 1) + im = Float._new(im, p) + return re + im*S.ImaginaryUnit + else: + return re + + n = evalf + + def _evalf(self, prec: int) -> Expr: + """Helper for evalf. Does the same thing but takes binary precision""" + r = self._eval_evalf(prec) + if r is None: + r = self # type: ignore + return r # type: ignore + + def _eval_evalf(self, prec: int) -> Expr | None: + return None + + def _to_mpmath(self, prec, allow_ints=True): + # mpmath functions accept ints as input + errmsg = "cannot convert to mpmath number" + if allow_ints and self.is_Integer: + return self.p + if hasattr(self, '_as_mpf_val'): + return make_mpf(self._as_mpf_val(prec)) + try: + result = evalf(self, prec, {}) + return quad_to_mpmath(result) + except NotImplementedError: + v = self._eval_evalf(prec) + if v is None: + raise ValueError(errmsg) + if v.is_Float: + return make_mpf(v._mpf_) + # Number + Number*I is also fine + re, im = v.as_real_imag() + if allow_ints and re.is_Integer: + re = from_int(re.p) + elif re.is_Float: + re = re._mpf_ + else: + raise ValueError(errmsg) + if allow_ints and im.is_Integer: + im = from_int(im.p) + elif im.is_Float: + im = im._mpf_ + else: + raise ValueError(errmsg) + return make_mpc((re, im)) + + +def N(x, n=15, **options): + r""" + Calls x.evalf(n, \*\*options). + + Explanations + ============ + + Both .n() and N() are equivalent to .evalf(); use the one that you like better. + See also the docstring of .evalf() for information on the options. + + Examples + ======== + + >>> from sympy import Sum, oo, N + >>> from sympy.abc import k + >>> Sum(1/k**k, (k, 1, oo)) + Sum(k**(-k), (k, 1, oo)) + >>> N(_, 4) + 1.291 + + """ + # by using rational=True, any evaluation of a string + # will be done using exact values for the Floats + return sympify(x, rational=True).evalf(n, **options) + + +def _evalf_with_bounded_error(x: Expr, eps: Expr | None = None, + m: int = 0, + options: OPT_DICT | None = None) -> TMP_RES: + """ + Evaluate *x* to within a bounded absolute error. + + Parameters + ========== + + x : Expr + The quantity to be evaluated. + eps : Expr, None, optional (default=None) + Positive real upper bound on the acceptable error. + m : int, optional (default=0) + If *eps* is None, then use 2**(-m) as the upper bound on the error. + options: OPT_DICT + As in the ``evalf`` function. + + Returns + ======= + + A tuple ``(re, im, re_acc, im_acc)``, as returned by ``evalf``. + + See Also + ======== + + evalf + + """ + if eps is not None: + if not (eps.is_Rational or eps.is_Float) or not eps > 0: + raise ValueError("eps must be positive") + r, _, _, _ = evalf(1/eps, 1, {}) + m = fastlog(r) + + c, d, _, _ = evalf(x, 1, {}) + # Note: If x = a + b*I, then |a| <= 2|c| and |b| <= 2|d|, with equality + # only in the zero case. + # If a is non-zero, then |c| = 2**nc for some integer nc, and c has + # bitcount 1. Therefore 2**fastlog(c) = 2**(nc+1) = 2|c| is an upper bound + # on |a|. Likewise for b and d. + nr, ni = fastlog(c), fastlog(d) + n = max(nr, ni) + 1 + # If x is 0, then n is MINUS_INF, and p will be 1. Otherwise, + # n - 1 bits get us past the integer parts of a and b, and +1 accounts for + # the factor of <= sqrt(2) that is |x|/max(|a|, |b|). + p = max(1, m + n + 1) + + options = options or {} + return evalf(x, p, options) diff --git a/phivenv/Lib/site-packages/sympy/core/expr.py b/phivenv/Lib/site-packages/sympy/core/expr.py new file mode 100644 index 0000000000000000000000000000000000000000..e66ff239a679942f2cc95c3f66af1fc13f7229d9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/expr.py @@ -0,0 +1,4194 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, overload +from collections.abc import Iterable, Mapping +from functools import reduce +import re + +from .sympify import sympify, _sympify +from .basic import Basic, Atom +from .singleton import S +from .evalf import EvalfMixin, pure_complex, DEFAULT_MAXPREC +from .decorators import call_highest_priority, sympify_method_args, sympify_return +from .cache import cacheit +from .logic import fuzzy_or, fuzzy_not +from .intfunc import mod_inverse +from .sorting import default_sort_key +from .kind import NumberKind +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.misc import as_int, func_name, filldedent +from sympy.utilities.iterables import has_variety, sift +from mpmath.libmp import mpf_log, prec_to_dps +from mpmath.libmp.libintmath import giant_steps + + +if TYPE_CHECKING: + from typing import Any + from typing_extensions import Self + from .numbers import Number + +from collections import defaultdict + + +def _corem(eq, c): # helper for extract_additively + # return co, diff from co*c + diff + co = [] + non = [] + for i in Add.make_args(eq): + ci = i.coeff(c) + if not ci: + non.append(i) + else: + co.append(ci) + return Add(*co), Add(*non) + + +@sympify_method_args +class Expr(Basic, EvalfMixin): + """ + Base class for algebraic expressions. + + Explanation + =========== + + Everything that requires arithmetic operations to be defined + should subclass this class, instead of Basic (which should be + used only for argument storage and expression manipulation, i.e. + pattern matching, substitutions, etc). + + If you want to override the comparisons of expressions: + Should use _eval_is_ge for inequality, or _eval_is_eq, with multiple dispatch. + _eval_is_ge return true if x >= y, false if x < y, and None if the two types + are not comparable or the comparison is indeterminate + + See Also + ======== + + sympy.core.basic.Basic + """ + + __slots__: tuple[str, ...] = () + + if TYPE_CHECKING: + + def __new__(cls, *args: Basic) -> Self: + ... + + @overload # type: ignore + def subs(self, arg1: Mapping[Basic | complex, Expr | complex], arg2: None=None) -> Expr: ... + @overload + def subs(self, arg1: Iterable[tuple[Basic | complex, Expr | complex]], arg2: None=None, **kwargs: Any) -> Expr: ... + @overload + def subs(self, arg1: Expr | complex, arg2: Expr | complex) -> Expr: ... + @overload + def subs(self, arg1: Mapping[Basic | complex, Basic | complex], arg2: None=None, **kwargs: Any) -> Basic: ... + @overload + def subs(self, arg1: Iterable[tuple[Basic | complex, Basic | complex]], arg2: None=None, **kwargs: Any) -> Basic: ... + @overload + def subs(self, arg1: Basic | complex, arg2: Basic | complex, **kwargs: Any) -> Basic: ... + + def subs(self, arg1: Mapping[Basic | complex, Basic | complex] | Basic | complex, # type: ignore + arg2: Basic | complex | None = None, **kwargs: Any) -> Basic: + ... + + def simplify(self, **kwargs) -> Expr: + ... + + def evalf(self, n: int = 15, subs: dict[Basic, Basic | float] | None = None, + maxn: int = 100, chop: bool = False, strict: bool = False, + quad: str | None = None, verbose: bool = False) -> Expr: + ... + + n = evalf + + is_scalar = True # self derivative is 1 + + @property + def _diff_wrt(self): + """Return True if one can differentiate with respect to this + object, else False. + + Explanation + =========== + + Subclasses such as Symbol, Function and Derivative return True + to enable derivatives wrt them. The implementation in Derivative + separates the Symbol and non-Symbol (_diff_wrt=True) variables and + temporarily converts the non-Symbols into Symbols when performing + the differentiation. By default, any object deriving from Expr + will behave like a scalar with self.diff(self) == 1. If this is + not desired then the object must also set `is_scalar = False` or + else define an _eval_derivative routine. + + Note, see the docstring of Derivative for how this should work + mathematically. In particular, note that expr.subs(yourclass, Symbol) + should be well-defined on a structural level, or this will lead to + inconsistent results. + + Examples + ======== + + >>> from sympy import Expr + >>> e = Expr() + >>> e._diff_wrt + False + >>> class MyScalar(Expr): + ... _diff_wrt = True + ... + >>> MyScalar().diff(MyScalar()) + 1 + >>> class MySymbol(Expr): + ... _diff_wrt = True + ... is_scalar = False + ... + >>> MySymbol().diff(MySymbol()) + Derivative(MySymbol(), MySymbol()) + """ + return False + + @cacheit + def sort_key(self, order=None): + + coeff, expr = self.as_coeff_Mul() + + if expr.is_Pow: + base, exp = expr.as_base_exp() + if base is S.Exp1: + # If we remove this, many doctests will go crazy: + # (keeps E**x sorted like the exp(x) function, + # part of exp(x) to E**x transition) + base, exp = Function("exp")(exp), S.One + expr = base + else: + exp = S.One + + if expr.is_Dummy: + args = (expr.sort_key(),) + elif expr.is_Atom: + args = (str(expr),) + else: + if expr.is_Add: + args = expr.as_ordered_terms(order=order) + elif expr.is_Mul: + args = expr.as_ordered_factors(order=order) + else: + args = expr.args + + args = tuple( + [ default_sort_key(arg, order=order) for arg in args ]) + + args = (len(args), tuple(args)) + exp = exp.sort_key(order=order) + + return expr.class_key(), args, exp, coeff + + def _hashable_content(self): + """Return a tuple of information about self that can be used to + compute the hash. If a class defines additional attributes, + like ``name`` in Symbol, then this method should be updated + accordingly to return such relevant attributes. + Defining more than _hashable_content is necessary if __eq__ has + been defined by a class. See note about this in Basic.__eq__.""" + return self._args + + # *************** + # * Arithmetics * + # *************** + # Expr and its subclasses use _op_priority to determine which object + # passed to a binary special method (__mul__, etc.) will handle the + # operation. In general, the 'call_highest_priority' decorator will choose + # the object with the highest _op_priority to handle the call. + # Custom subclasses that want to define their own binary special methods + # should set an _op_priority value that is higher than the default. + # + # **NOTE**: + # This is a temporary fix, and will eventually be replaced with + # something better and more powerful. See issue 5510. + _op_priority = 10.0 + + @property + def _add_handler(self): + return Add + + @property + def _mul_handler(self): + return Mul + + def __pos__(self) -> Expr: + return self + + def __neg__(self) -> Expr: + # Mul has its own __neg__ routine, so we just + # create a 2-args Mul with the -1 in the canonical + # slot 0. + c = self.is_commutative + return Mul._from_args((S.NegativeOne, self), c) + + def __abs__(self) -> Expr: + from sympy.functions.elementary.complexes import Abs + return Abs(self) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__radd__') + def __add__(self, other) -> Expr: + return Add(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__add__') + def __radd__(self, other) -> Expr: + return Add(other, self) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rsub__') + def __sub__(self, other) -> Expr: + return Add(self, -other) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__sub__') + def __rsub__(self, other) -> Expr: + return Add(other, -self) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rmul__') + def __mul__(self, other) -> Expr: + return Mul(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__mul__') + def __rmul__(self, other) -> Expr: + return Mul(other, self) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rpow__') + def _pow(self, other): + return Pow(self, other) + + def __pow__(self, other, mod=None) -> Expr: + if mod is None: + return self._pow(other) + try: + _self, other, mod = as_int(self), as_int(other), as_int(mod) + if other >= 0: + return _sympify(pow(_self, other, mod)) + else: + return _sympify(mod_inverse(pow(_self, -other, mod), mod)) + except ValueError: + power = self._pow(other) + try: + return power%mod + except TypeError: + return NotImplemented + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__pow__') + def __rpow__(self, other) -> Expr: + return Pow(other, self) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rtruediv__') + def __truediv__(self, other) -> Expr: + denom = Pow(other, S.NegativeOne) + if self is S.One: + return denom + else: + return Mul(self, denom) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__truediv__') + def __rtruediv__(self, other) -> Expr: + denom = Pow(self, S.NegativeOne) + if other is S.One: + return denom + else: + return Mul(other, denom) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rmod__') + def __mod__(self, other) -> Expr: + return Mod(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__mod__') + def __rmod__(self, other) -> Expr: + return Mod(other, self) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rfloordiv__') + def __floordiv__(self, other) -> Expr: + from sympy.functions.elementary.integers import floor + return floor(self / other) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__floordiv__') + def __rfloordiv__(self, other) -> Expr: + from sympy.functions.elementary.integers import floor + return floor(other / self) + + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__rdivmod__') + def __divmod__(self, other) -> tuple[Expr, Expr]: + from sympy.functions.elementary.integers import floor + return floor(self / other), Mod(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + @call_highest_priority('__divmod__') + def __rdivmod__(self, other) -> tuple[Expr, Expr]: + from sympy.functions.elementary.integers import floor + return floor(other / self), Mod(other, self) + + def __int__(self) -> int: + if not self.is_number: + raise TypeError("Cannot convert symbols to int") + r = self.round(2) + if not r.is_Number: + raise TypeError("Cannot convert complex to int") + if r in (S.NaN, S.Infinity, S.NegativeInfinity): + raise TypeError("Cannot convert %s to int" % r) + i = int(r) + if not i: + return i + if int_valued(r): + # non-integer self should pass one of these tests + if (self > i) is S.true: + return i + if (self < i) is S.true: + return i - 1 + ok = self.equals(i) + if ok is None: + raise TypeError('cannot compute int value accurately') + if ok: + return i + # off by one + return i - (1 if i > 0 else -1) + return i + + def __float__(self) -> float: + # Don't bother testing if it's a number; if it's not this is going + # to fail, and if it is we still need to check that it evalf'ed to + # a number. + result = self.evalf() + if result.is_Number: + return float(result) + if result.is_number and result.as_real_imag()[1]: + raise TypeError("Cannot convert complex to float") + raise TypeError("Cannot convert expression to float") + + def __complex__(self) -> complex: + result = self.evalf() + re, im = result.as_real_imag() + return complex(float(re), float(im)) + + @sympify_return([('other', 'Expr')], NotImplemented) + def __ge__(self, other): + from .relational import GreaterThan + return GreaterThan(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + def __le__(self, other): + from .relational import LessThan + return LessThan(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + def __gt__(self, other): + from .relational import StrictGreaterThan + return StrictGreaterThan(self, other) + + @sympify_return([('other', 'Expr')], NotImplemented) + def __lt__(self, other): + from .relational import StrictLessThan + return StrictLessThan(self, other) + + def __trunc__(self): + if not self.is_number: + raise TypeError("Cannot truncate symbols and expressions") + else: + return Integer(self) + + def __format__(self, format_spec: str): + if self.is_number: + mt = re.match(r'\+?\d*\.(\d+)f', format_spec) + if mt: + prec = int(mt.group(1)) + rounded = self.round(prec) + if rounded.is_Integer: + return format(int(rounded), format_spec) + if rounded.is_Float: + return format(rounded, format_spec) + return super().__format__(format_spec) + + @staticmethod + def _from_mpmath(x, prec): + if hasattr(x, "_mpf_"): + return Float._new(x._mpf_, prec) + elif hasattr(x, "_mpc_"): + re, im = x._mpc_ + re = Float._new(re, prec) + im = Float._new(im, prec)*S.ImaginaryUnit + return re + im + else: + raise TypeError("expected mpmath number (mpf or mpc)") + + @property + def is_number(self): + """Returns True if ``self`` has no free symbols and no + undefined functions (AppliedUndef, to be precise). It will be + faster than ``if not self.free_symbols``, however, since + ``is_number`` will fail as soon as it hits a free symbol + or undefined function. + + Examples + ======== + + >>> from sympy import Function, Integral, cos, sin, pi + >>> from sympy.abc import x + >>> f = Function('f') + + >>> x.is_number + False + >>> f(1).is_number + False + >>> (2*x).is_number + False + >>> (2 + Integral(2, x)).is_number + False + >>> (2 + Integral(2, (x, 1, 2))).is_number + True + + Not all numbers are Numbers in the SymPy sense: + + >>> pi.is_number, pi.is_Number + (True, False) + + If something is a number it should evaluate to a number with + real and imaginary parts that are Numbers; the result may not + be comparable, however, since the real and/or imaginary part + of the result may not have precision. + + >>> cos(1).is_number and cos(1).is_comparable + True + + >>> z = cos(1)**2 + sin(1)**2 - 1 + >>> z.is_number + True + >>> z.is_comparable + False + + See Also + ======== + + sympy.core.basic.Basic.is_comparable + """ + return all(obj.is_number for obj in self.args) + + def _eval_is_comparable(self): + # Basic._eval_is_comparable always returns False, so we override it + # here + is_extended_real = self.is_extended_real + if is_extended_real is False: + return False + if not self.is_number: + return False + + # XXX: as_real_imag() can be a very expensive operation. It should not + # be used here because is_comparable is used implicitly in many places. + # Probably this method should just return self.evalf(2).is_Number. + + n, i = self.as_real_imag() + + if not n.is_Number: + n = n.evalf(2) + if not n.is_Number: + return False + + if not i.is_Number: + i = i.evalf(2) + if not i.is_Number: + return False + + if i: + # if _prec = 1 we can't decide and if not, + # the answer is False because numbers with + # imaginary parts can't be compared + # so return False + return False + else: + return n._prec != 1 + + def _random(self, n=None, re_min=-1, im_min=-1, re_max=1, im_max=1): + """Return self evaluated, if possible, replacing free symbols with + random complex values, if necessary. + + Explanation + =========== + + The random complex value for each free symbol is generated + by the random_complex_number routine giving real and imaginary + parts in the range given by the re_min, re_max, im_min, and im_max + values. The returned value is evaluated to a precision of n + (if given) else the maximum of 15 and the precision needed + to get more than 1 digit of precision. If the expression + could not be evaluated to a number, or could not be evaluated + to more than 1 digit of precision, then None is returned. + + Examples + ======== + + >>> from sympy import sqrt + >>> from sympy.abc import x, y + >>> x._random() # doctest: +SKIP + 0.0392918155679172 + 0.916050214307199*I + >>> x._random(2) # doctest: +SKIP + -0.77 - 0.87*I + >>> (x + y/2)._random(2) # doctest: +SKIP + -0.57 + 0.16*I + >>> sqrt(2)._random(2) + 1.4 + + See Also + ======== + + sympy.core.random.random_complex_number + """ + + free = self.free_symbols + prec = 1 + if free: + from sympy.core.random import random_complex_number + a, c, b, d = re_min, re_max, im_min, im_max + reps = dict(list(zip(free, [random_complex_number(a, b, c, d, rational=True) + for zi in free]))) + try: + nmag = abs(self.evalf(2, subs=reps)) + except (ValueError, TypeError): + # if an out of range value resulted in evalf problems + # then return None -- XXX is there a way to know how to + # select a good random number for a given expression? + # e.g. when calculating n! negative values for n should not + # be used + return None + else: + reps = {} + nmag = abs(self.evalf(2)) + + if not hasattr(nmag, '_prec'): + # e.g. exp_polar(2*I*pi) doesn't evaluate but is_number is True + return None + + if nmag._prec == 1: + # increase the precision up to the default maximum + # precision to see if we can get any significance + + # evaluate + for prec in giant_steps(2, DEFAULT_MAXPREC): + nmag = abs(self.evalf(prec, subs=reps)) + if nmag._prec != 1: + break + + if nmag._prec != 1: + if n is None: + n = max(prec, 15) + return self.evalf(n, subs=reps) + + # never got any significance + return None + + def is_constant(self, *wrt, **flags): + """Return True if self is constant, False if not, or None if + the constancy could not be determined conclusively. + + Explanation + =========== + + If an expression has no free symbols then it is a constant. If + there are free symbols it is possible that the expression is a + constant, perhaps (but not necessarily) zero. To test such + expressions, a few strategies are tried: + + 1) numerical evaluation at two random points. If two such evaluations + give two different values and the values have a precision greater than + 1 then self is not constant. If the evaluations agree or could not be + obtained with any precision, no decision is made. The numerical testing + is done only if ``wrt`` is different than the free symbols. + + 2) differentiation with respect to variables in 'wrt' (or all free + symbols if omitted) to see if the expression is constant or not. This + will not always lead to an expression that is zero even though an + expression is constant (see added test in test_expr.py). If + all derivatives are zero then self is constant with respect to the + given symbols. + + 3) finding out zeros of denominator expression with free_symbols. + It will not be constant if there are zeros. It gives more negative + answers for expression that are not constant. + + If neither evaluation nor differentiation can prove the expression is + constant, None is returned unless two numerical values happened to be + the same and the flag ``failing_number`` is True -- in that case the + numerical value will be returned. + + If flag simplify=False is passed, self will not be simplified; + the default is True since self should be simplified before testing. + + Examples + ======== + + >>> from sympy import cos, sin, Sum, S, pi + >>> from sympy.abc import a, n, x, y + >>> x.is_constant() + False + >>> S(2).is_constant() + True + >>> Sum(x, (x, 1, 10)).is_constant() + True + >>> Sum(x, (x, 1, n)).is_constant() + False + >>> Sum(x, (x, 1, n)).is_constant(y) + True + >>> Sum(x, (x, 1, n)).is_constant(n) + False + >>> Sum(x, (x, 1, n)).is_constant(x) + True + >>> eq = a*cos(x)**2 + a*sin(x)**2 - a + >>> eq.is_constant() + True + >>> eq.subs({x: pi, a: 2}) == eq.subs({x: pi, a: 3}) == 0 + True + + >>> (0**x).is_constant() + False + >>> x.is_constant() + False + >>> (x**x).is_constant() + False + >>> one = cos(x)**2 + sin(x)**2 + >>> one.is_constant() + True + >>> ((one - 1)**(x + 1)).is_constant() in (True, False) # could be 0 or 1 + True + """ + + simplify = flags.get('simplify', True) + + if self.is_number: + return True + free = self.free_symbols + if not free: + return True # assume f(1) is some constant + + # if we are only interested in some symbols and they are not in the + # free symbols then this expression is constant wrt those symbols + wrt = set(wrt) + if wrt and not wrt & free: + return True + wrt = wrt or free + + # simplify unless this has already been done + expr = self + if simplify: + expr = expr.simplify() + + # is_zero should be a quick assumptions check; it can be wrong for + # numbers (see test_is_not_constant test), giving False when it + # shouldn't, but hopefully it will never give True unless it is sure. + if expr.is_zero: + return True + + # Don't attempt substitution or differentiation with non-number symbols + wrt_number = {sym for sym in wrt if sym.kind is NumberKind} + + # try numerical evaluation to see if we get two different values + failing_number = None + if wrt_number == free: + # try 0 (for a) and 1 (for b) + try: + a = expr.subs(list(zip(free, [0]*len(free))), + simultaneous=True) + if a is S.NaN: + # evaluation may succeed when substitution fails + a = expr._random(None, 0, 0, 0, 0) + except ZeroDivisionError: + a = None + if a is not None and a is not S.NaN: + try: + b = expr.subs(list(zip(free, [1]*len(free))), + simultaneous=True) + if b is S.NaN: + # evaluation may succeed when substitution fails + b = expr._random(None, 1, 0, 1, 0) + except ZeroDivisionError: + b = None + if b is not None and b is not S.NaN and b.equals(a) is False: + return False + # try random real + b = expr._random(None, -1, 0, 1, 0) + if b is not None and b is not S.NaN and b.equals(a) is False: + return False + # try random complex + b = expr._random() + if b is not None and b is not S.NaN: + if b.equals(a) is False: + return False + failing_number = a if a.is_number else b + + # now we will test each wrt symbol (or all free symbols) to see if the + # expression depends on them or not using differentiation. This is + # not sufficient for all expressions, however, so we don't return + # False if we get a derivative other than 0 with free symbols. + for w in wrt_number: + deriv = expr.diff(w) + if simplify: + deriv = deriv.simplify() + if deriv != 0: + if not (pure_complex(deriv, or_real=True)): + if flags.get('failing_number', False): + return failing_number + return False + from sympy.solvers.solvers import denoms + return fuzzy_not(fuzzy_or(den.is_zero for den in denoms(self))) + + def equals(self, other, failing_expression=False): + """Return True if self == other, False if it does not, or None. If + failing_expression is True then the expression which did not simplify + to a 0 will be returned instead of None. + + Explanation + =========== + + If ``self`` is a Number (or complex number) that is not zero, then + the result is False. + + If ``self`` is a number and has not evaluated to zero, evalf will be + used to test whether the expression evaluates to zero. If it does so + and the result has significance (i.e. the precision is either -1, for + a Rational result, or is greater than 1) then the evalf value will be + used to return True or False. + + """ + from sympy.simplify.simplify import nsimplify, simplify + from sympy.solvers.solvers import solve + from sympy.polys.polyerrors import NotAlgebraic + from sympy.polys.numberfields import minimal_polynomial + + other = sympify(other) + + if not isinstance(other, Expr): + return False + + if self == other: + return True + + # they aren't the same so see if we can make the difference 0; + # don't worry about doing simplification steps one at a time + # because if the expression ever goes to 0 then the subsequent + # simplification steps that are done will be very fast. + diff = factor_terms(simplify(self - other), radical=True) + + if not diff: + return True + + if not diff.has(Add, Mod): + # if there is no expanding to be done after simplifying + # then this can't be a zero + return False + + factors = diff.as_coeff_mul()[1] + if len(factors) > 1: # avoid infinity recursion + fac_zero = [fac.equals(0) for fac in factors] + if None not in fac_zero: # every part can be decided + return any(fac_zero) + + constant = diff.is_constant(simplify=False, failing_number=True) + + if constant is False: + return False + + if not diff.is_number: + if constant is None: + # e.g. unless the right simplification is done, a symbolic + # zero is possible (see expression of issue 6829: without + # simplification constant will be None). + return + + if constant is True: + # this gives a number whether there are free symbols or not + ndiff = diff._random() + # is_comparable will work whether the result is real + # or complex; it could be None, however. + if ndiff and ndiff.is_comparable: + return False + + # sometimes we can use a simplified result to give a clue as to + # what the expression should be; if the expression is *not* zero + # then we should have been able to compute that and so now + # we can just consider the cases where the approximation appears + # to be zero -- we try to prove it via minimal_polynomial. + # + # removed + # ns = nsimplify(diff) + # if diff.is_number and (not ns or ns == diff): + # + # The thought was that if it nsimplifies to 0 that's a sure sign + # to try the following to prove it; or if it changed but wasn't + # zero that might be a sign that it's not going to be easy to + # prove. But tests seem to be working without that logic. + # + if diff.is_number: + # try to prove via self-consistency + surds = [s for s in diff.atoms(Pow) if s.args[0].is_Integer] + # it seems to work better to try big ones first + surds.sort(key=lambda x: -x.args[0]) + for s in surds: + try: + # simplify is False here -- this expression has already + # been identified as being hard to identify as zero; + # we will handle the checking ourselves using nsimplify + # to see if we are in the right ballpark or not and if so + # *then* the simplification will be attempted. + sol = solve(diff, s, simplify=False) + if sol: + if s in sol: + # the self-consistent result is present + return True + if all(si.is_Integer for si in sol): + # perfect powers are removed at instantiation + # so surd s cannot be an integer + return False + if all(i.is_algebraic is False for i in sol): + # a surd is algebraic + return False + if any(si in surds for si in sol): + # it wasn't equal to s but it is in surds + # and different surds are not equal + return False + if any(nsimplify(s - si) == 0 and + simplify(s - si) == 0 for si in sol): + return True + if s.is_real: + if any(nsimplify(si, [s]) == s and simplify(si) == s + for si in sol): + return True + except NotImplementedError: + pass + + # try to prove with minimal_polynomial but know when + # *not* to use this or else it can take a long time. e.g. issue 8354 + if True: # change True to condition that assures non-hang + try: + mp = minimal_polynomial(diff) + if mp.is_Symbol: + return True + return False + except (NotAlgebraic, NotImplementedError): + pass + + # diff has not simplified to zero; constant is either None, True + # or the number with significance (is_comparable) that was randomly + # calculated twice as the same value. + if constant not in (True, None) and constant != 0: + return False + + if failing_expression: + return diff + return None + + def _eval_is_extended_positive_negative(self, positive): + from sympy.polys.numberfields import minimal_polynomial + from sympy.polys.polyerrors import NotAlgebraic + if self.is_number: + # check to see that we can get a value + try: + n2 = self._eval_evalf(2) + # XXX: This shouldn't be caught here + # Catches ValueError: hypsum() failed to converge to the requested + # 34 bits of accuracy + except ValueError: + return None + if n2 is None: + return None + if getattr(n2, '_prec', 1) == 1: # no significance + return None + if n2 is S.NaN: + return None + + f = self.evalf(2) + if f.is_Float: + match = f, S.Zero + else: + match = pure_complex(f) + if match is None: + return False + r, i = match + if not (i.is_Number and r.is_Number): + return False + if r._prec != 1 and i._prec != 1: + return bool(not i and ((r > 0) if positive else (r < 0))) + elif r._prec == 1 and (not i or i._prec == 1) and \ + self._eval_is_algebraic() and not self.has(Function): + try: + if minimal_polynomial(self).is_Symbol: + return False + except (NotAlgebraic, NotImplementedError): + pass + + def _eval_is_extended_positive(self): + return self._eval_is_extended_positive_negative(positive=True) + + def _eval_is_extended_negative(self): + return self._eval_is_extended_positive_negative(positive=False) + + def _eval_interval(self, x, a, b): + """ + Returns evaluation over an interval. For most functions this is: + + self.subs(x, b) - self.subs(x, a), + + possibly using limit() if NaN is returned from subs, or if + singularities are found between a and b. + + If b or a is None, it only evaluates -self.subs(x, a) or self.subs(b, x), + respectively. + + """ + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.functions.elementary.exponential import log + from sympy.series.limits import limit, Limit + from sympy.sets.sets import Interval + from sympy.solvers.solveset import solveset + + if (a is None and b is None): + raise ValueError('Both interval ends cannot be None.') + + def _eval_endpoint(left): + c = a if left else b + if c is None: + return S.Zero + else: + C = self.subs(x, c) + if C.has(S.NaN, S.Infinity, S.NegativeInfinity, + S.ComplexInfinity, AccumBounds): + if (a < b) != False: + C = limit(self, x, c, "+" if left else "-") + else: + C = limit(self, x, c, "-" if left else "+") + + if isinstance(C, Limit): + raise NotImplementedError("Could not compute limit") + return C + + if a == b: + return S.Zero + + A = _eval_endpoint(left=True) + if A is S.NaN: + return A + + B = _eval_endpoint(left=False) + + if (a and b) is None: + return B - A + + value = B - A + + if a.is_comparable and b.is_comparable: + if a < b: + domain = Interval(a, b) + else: + domain = Interval(b, a) + # check the singularities of self within the interval + # if singularities is a ConditionSet (not iterable), catch the exception and pass + singularities = solveset(self.cancel().as_numer_denom()[1], x, + domain=domain) + for logterm in self.atoms(log): + singularities = singularities | solveset(logterm.args[0], x, + domain=domain) + try: + for s in singularities: + if value is S.NaN: + # no need to keep adding, it will stay NaN + break + if not s.is_comparable: + continue + if (a < s) == (s < b) == True: + value += -limit(self, x, s, "+") + limit(self, x, s, "-") + elif (b < s) == (s < a) == True: + value += limit(self, x, s, "+") - limit(self, x, s, "-") + except TypeError: + pass + + return value + + def _eval_power(self, expt) -> Expr | None: + # subclass to compute self**other for cases when + # other is not NaN, 0, or 1 + return None + + def _eval_conjugate(self): + if self.is_extended_real: + return self + elif self.is_imaginary: + return -self + + def conjugate(self): + """Returns the complex conjugate of 'self'.""" + from sympy.functions.elementary.complexes import conjugate as c + return c(self) + + def dir(self, x, cdir): + if self.is_zero: + return S.Zero + from sympy.functions.elementary.exponential import log + minexp = S.Zero + arg = self + while arg: + minexp += S.One + arg = arg.diff(x) + coeff = arg.subs(x, 0) + if coeff is S.NaN: + coeff = arg.limit(x, 0) + if coeff is S.ComplexInfinity: + try: + coeff, _ = arg.leadterm(x) + if coeff.has(log(x)): + raise ValueError() + except ValueError: + coeff = arg.limit(x, 0) + if coeff != S.Zero: + break + return coeff*cdir**minexp + + def _eval_transpose(self): + from sympy.functions.elementary.complexes import conjugate + if self.is_commutative: + return self + elif self.is_hermitian: + return conjugate(self) + elif self.is_antihermitian: + return -conjugate(self) + + def transpose(self): + from sympy.functions.elementary.complexes import transpose + return transpose(self) + + def _eval_adjoint(self): + from sympy.functions.elementary.complexes import conjugate, transpose + if self.is_hermitian: + return self + elif self.is_antihermitian: + return -self + obj = self._eval_conjugate() + if obj is not None: + return transpose(obj) + obj = self._eval_transpose() + if obj is not None: + return conjugate(obj) + + def adjoint(self): + from sympy.functions.elementary.complexes import adjoint + return adjoint(self) + + @classmethod + def _parse_order(cls, order): + """Parse and configure the ordering of terms. """ + from sympy.polys.orderings import monomial_key + + startswith = getattr(order, "startswith", None) + if startswith is None: + reverse = False + else: + reverse = startswith('rev-') + if reverse: + order = order[4:] + + monom_key = monomial_key(order) + + def neg(monom): + return tuple([neg(m) if isinstance(m, tuple) else -m for m in monom]) + + def key(term): + _, ((re, im), monom, ncpart) = term + + monom = neg(monom_key(monom)) + ncpart = tuple([e.sort_key(order=order) for e in ncpart]) + coeff = ((bool(im), im), (re, im)) + + return monom, ncpart, coeff + + return key, reverse + + def as_ordered_factors(self, order=None): + """Return list of ordered factors (if Mul) else [self].""" + return [self] + + def as_poly(self, *gens, **args): + """Converts ``self`` to a polynomial or returns ``None``. + + Explanation + =========== + + >>> from sympy import sin + >>> from sympy.abc import x, y + + >>> print((x**2 + x*y).as_poly()) + Poly(x**2 + x*y, x, y, domain='ZZ') + + >>> print((x**2 + x*y).as_poly(x, y)) + Poly(x**2 + x*y, x, y, domain='ZZ') + + >>> print((x**2 + sin(y)).as_poly(x, y)) + None + + """ + from sympy.polys.polyerrors import PolynomialError, GeneratorsNeeded + from sympy.polys.polytools import Poly + + try: + poly = Poly(self, *gens, **args) + + if not poly.is_Poly: + return None + else: + return poly + except (PolynomialError, GeneratorsNeeded): + # PolynomialError is caught for e.g. exp(x).as_poly(x) + # GeneratorsNeeded is caught for e.g. S(2).as_poly() + return None + + def as_ordered_terms(self, order=None, data=False): + """ + Transform an expression to an ordered list of terms. + + Examples + ======== + + >>> from sympy import sin, cos + >>> from sympy.abc import x + + >>> (sin(x)**2*cos(x) + sin(x)**2 + 1).as_ordered_terms() + [sin(x)**2*cos(x), sin(x)**2, 1] + + """ + + from .numbers import Number, NumberSymbol + + if order is None and self.is_Add: + # Spot the special case of Add(Number, Mul(Number, expr)) with the + # first number positive and the second number negative + key = lambda x:not isinstance(x, (Number, NumberSymbol)) + add_args = sorted(Add.make_args(self), key=key) + if (len(add_args) == 2 + and isinstance(add_args[0], (Number, NumberSymbol)) + and isinstance(add_args[1], Mul)): + mul_args = sorted(Mul.make_args(add_args[1]), key=key) + if (len(mul_args) == 2 + and isinstance(mul_args[0], Number) + and add_args[0].is_positive + and mul_args[0].is_negative): + return add_args + + key, reverse = self._parse_order(order) + terms, gens = self.as_terms() + + if not any(term.is_Order for term, _ in terms): + ordered = sorted(terms, key=key, reverse=reverse) + else: + _terms, _order = [], [] + + for term, repr in terms: + if not term.is_Order: + _terms.append((term, repr)) + else: + _order.append((term, repr)) + + ordered = sorted(_terms, key=key, reverse=True) \ + + sorted(_order, key=key, reverse=True) + + if data: + return ordered, gens + else: + return [term for term, _ in ordered] + + def as_terms(self): + """Transform an expression to a list of terms. """ + from .exprtools import decompose_power + + gens, terms = set(), [] + + for term in Add.make_args(self): + coeff, _term = term.as_coeff_Mul() + + coeff = complex(coeff) + cpart, ncpart = {}, [] + + if _term is not S.One: + for factor in Mul.make_args(_term): + if factor.is_number: + try: + coeff *= complex(factor) + except (TypeError, ValueError): + pass + else: + continue + + if factor.is_commutative: + base, exp = decompose_power(factor) + + cpart[base] = exp + gens.add(base) + else: + ncpart.append(factor) + + coeff = coeff.real, coeff.imag + ncpart = tuple(ncpart) + + terms.append((term, (coeff, cpart, ncpart))) + + gens = sorted(gens, key=default_sort_key) + + k, indices = len(gens), {} + + for i, g in enumerate(gens): + indices[g] = i + + result = [] + + for term, (coeff, cpart, ncpart) in terms: + monom = [0]*k + + for base, exp in cpart.items(): + monom[indices[base]] = exp + + result.append((term, (coeff, tuple(monom), ncpart))) + + return result, gens + + def removeO(self) -> Expr: + """Removes the additive O(..) symbol if there is one""" + return self + + def getO(self) -> Expr | None: + """Returns the additive O(..) symbol if there is one, else None.""" + return None + + def getn(self): + """ + Returns the order of the expression. + + Explanation + =========== + + The order is determined either from the O(...) term. If there + is no O(...) term, it returns None. + + Examples + ======== + + >>> from sympy import O + >>> from sympy.abc import x + >>> (1 + x + O(x**2)).getn() + 2 + >>> (1 + x).getn() + + """ + o = self.getO() + if o is None: + return None + elif o.is_Order: + o = o.expr + if o is S.One: + return S.Zero + if o.is_Symbol: + return S.One + if o.is_Pow: + return o.args[1] + if o.is_Mul: # x**n*log(x)**n or x**n/log(x)**n + for oi in o.args: + if oi.is_Symbol: + return S.One + if oi.is_Pow: + from .symbol import Dummy, Symbol + syms = oi.atoms(Symbol) + if len(syms) == 1: + x = syms.pop() + oi = oi.subs(x, Dummy('x', positive=True)) + if oi.base.is_Symbol and oi.exp.is_Rational: + return abs(oi.exp) + + raise NotImplementedError('not sure of order of %s' % o) + + def count_ops(self, visual=False): + from .function import count_ops + return count_ops(self, visual) + + def args_cnc(self, cset=False, warn=True, split_1=True): + """Return [commutative factors, non-commutative factors] of self. + + Explanation + =========== + + self is treated as a Mul and the ordering of the factors is maintained. + If ``cset`` is True the commutative factors will be returned in a set. + If there were repeated factors (as may happen with an unevaluated Mul) + then an error will be raised unless it is explicitly suppressed by + setting ``warn`` to False. + + Note: -1 is always separated from a Number unless split_1 is False. + + Examples + ======== + + >>> from sympy import symbols, oo + >>> A, B = symbols('A B', commutative=0) + >>> x, y = symbols('x y') + >>> (-2*x*y).args_cnc() + [[-1, 2, x, y], []] + >>> (-2.5*x).args_cnc() + [[-1, 2.5, x], []] + >>> (-2*x*A*B*y).args_cnc() + [[-1, 2, x, y], [A, B]] + >>> (-2*x*A*B*y).args_cnc(split_1=False) + [[-2, x, y], [A, B]] + >>> (-2*x*y).args_cnc(cset=True) + [{-1, 2, x, y}, []] + + The arg is always treated as a Mul: + + >>> (-2 + x + A).args_cnc() + [[], [x - 2 + A]] + >>> (-oo).args_cnc() # -oo is a singleton + [[-1, oo], []] + """ + args = list(Mul.make_args(self)) + + for i, mi in enumerate(args): + if not mi.is_commutative: + c = args[:i] + nc = args[i:] + break + else: + c = args + nc = [] + + if c and split_1 and ( + c[0].is_Number and + c[0].is_extended_negative and + c[0] is not S.NegativeOne): + c[:1] = [S.NegativeOne, -c[0]] + + if cset: + clen = len(c) + c = set(c) + if clen and warn and len(c) != clen: + raise ValueError('repeated commutative arguments: %s' % + [ci for ci in c if list(self.args).count(ci) > 1]) + return [c, nc] + + def coeff(self, x: Expr, n=1, right=False, _first=True): + """ + Returns the coefficient from the term(s) containing ``x**n``. If ``n`` + is zero then all terms independent of ``x`` will be returned. + + Explanation + =========== + + When ``x`` is noncommutative, the coefficient to the left (default) or + right of ``x`` can be returned. The keyword 'right' is ignored when + ``x`` is commutative. + + Examples + ======== + + >>> from sympy import symbols + >>> from sympy.abc import x, y, z + + You can select terms that have an explicit negative in front of them: + + >>> (-x + 2*y).coeff(-1) + x + >>> (x - 2*y).coeff(-1) + 2*y + + You can select terms with no Rational coefficient: + + >>> (x + 2*y).coeff(1) + x + >>> (3 + 2*x + 4*x**2).coeff(1) + 0 + + You can select terms independent of x by making n=0; in this case + expr.as_independent(x)[0] is returned (and 0 will be returned instead + of None): + + >>> (3 + 2*x + 4*x**2).coeff(x, 0) + 3 + >>> eq = ((x + 1)**3).expand() + 1 + >>> eq + x**3 + 3*x**2 + 3*x + 2 + >>> [eq.coeff(x, i) for i in reversed(range(4))] + [1, 3, 3, 2] + >>> eq -= 2 + >>> [eq.coeff(x, i) for i in reversed(range(4))] + [1, 3, 3, 0] + + You can select terms that have a numerical term in front of them: + + >>> (-x - 2*y).coeff(2) + -y + >>> from sympy import sqrt + >>> (x + sqrt(2)*x).coeff(sqrt(2)) + x + + The matching is exact: + + >>> (3 + 2*x + 4*x**2).coeff(x) + 2 + >>> (3 + 2*x + 4*x**2).coeff(x**2) + 4 + >>> (3 + 2*x + 4*x**2).coeff(x**3) + 0 + >>> (z*(x + y)**2).coeff((x + y)**2) + z + >>> (z*(x + y)**2).coeff(x + y) + 0 + + In addition, no factoring is done, so 1 + z*(1 + y) is not obtained + from the following: + + >>> (x + z*(x + x*y)).coeff(x) + 1 + + If such factoring is desired, factor_terms can be used first: + + >>> from sympy import factor_terms + >>> factor_terms(x + z*(x + x*y)).coeff(x) + z*(y + 1) + 1 + + >>> n, m, o = symbols('n m o', commutative=False) + >>> n.coeff(n) + 1 + >>> (3*n).coeff(n) + 3 + >>> (n*m + m*n*m).coeff(n) # = (1 + m)*n*m + 1 + m + >>> (n*m + m*n*m).coeff(n, right=True) # = (1 + m)*n*m + m + + If there is more than one possible coefficient 0 is returned: + + >>> (n*m + m*n).coeff(n) + 0 + + If there is only one possible coefficient, it is returned: + + >>> (n*m + x*m*n).coeff(m*n) + x + >>> (n*m + x*m*n).coeff(m*n, right=1) + 1 + + See Also + ======== + + as_coefficient: separate the expression into a coefficient and factor + as_coeff_Add: separate the additive constant from an expression + as_coeff_Mul: separate the multiplicative constant from an expression + as_independent: separate x-dependent terms/factors from others + sympy.polys.polytools.Poly.coeff_monomial: efficiently find the single coefficient of a monomial in Poly + sympy.polys.polytools.Poly.nth: like coeff_monomial but powers of monomial terms are used + """ + x = sympify(x) + if not isinstance(x, Basic): + return S.Zero + + n = as_int(n) + + if not x: + return S.Zero + + if x == self: + if n == 1: + return S.One + return S.Zero + + co2: list[Expr] + + if x is S.One: + co2 = [a for a in Add.make_args(self) if a.as_coeff_Mul()[0] is S.One] + if not co2: + return S.Zero + return Add(*co2) + + if n == 0: + if x.is_Add and self.is_Add: + c = self.coeff(x, right=right) + if not c: + return S.Zero + if not right: + return self - Add(*[a*x for a in Add.make_args(c)]) + return self - Add(*[x*a for a in Add.make_args(c)]) + return self.as_independent(x, as_Add=True)[0] + + # continue with the full method, looking for this power of x: + x = x**n + + def incommon(l1, l2): + if not l1 or not l2: + return [] + n = min(len(l1), len(l2)) + for i in range(n): + if l1[i] != l2[i]: + return l1[:i] + return l1[:] + + def find(l, sub, first=True): + """ Find where list sub appears in list l. When ``first`` is True + the first occurrence from the left is returned, else the last + occurrence is returned. Return None if sub is not in l. + + Examples + ======== + + >> l = range(5)*2 + >> find(l, [2, 3]) + 2 + >> find(l, [2, 3], first=0) + 7 + >> find(l, [2, 4]) + None + + """ + if not sub or not l or len(sub) > len(l): + return None + n = len(sub) + if not first: + l.reverse() + sub.reverse() + for i in range(len(l) - n + 1): + if all(l[i + j] == sub[j] for j in range(n)): + break + else: + i = None + if not first: + l.reverse() + sub.reverse() + if i is not None and not first: + i = len(l) - (i + n) + return i + + co2 = [] + co: list[tuple[set[Expr], list[Expr]]] = [] + args = Add.make_args(self) + self_c = self.is_commutative + x_c = x.is_commutative + if self_c and not x_c: + return S.Zero + if _first and self.is_Add and not self_c and not x_c: + # get the part that depends on x exactly + xargs = Mul.make_args(x) + d = Add(*[i for i in Add.make_args(self.as_independent(x)[1]) + if all(xi in Mul.make_args(i) for xi in xargs)]) + rv = d.coeff(x, right=right, _first=False) + if not rv.is_Add or not right: + return rv + c_part, nc_part = zip(*[i.args_cnc() for i in rv.args]) + if has_variety(c_part): + return rv + return Add(*[Mul._from_args(i) for i in nc_part]) + + one_c = self_c or x_c + xargs, nx = x.args_cnc(cset=True, warn=bool(not x_c)) + # find the parts that pass the commutative terms + for a in args: + margs, nc = a.args_cnc(cset=True, warn=bool(not self_c)) + if nc is None: + nc = [] + if len(xargs) > len(margs): + continue + resid = margs.difference(xargs) + if len(resid) + len(xargs) == len(margs): + if one_c: + co2.append(Mul(*(list(resid) + nc))) + else: + co.append((resid, nc)) + if one_c: + if co2 == []: + return S.Zero + elif co2: + return Add(*co2) + else: # both nc + # now check the non-comm parts + if not co: + return S.Zero + if all(n == co[0][1] for r, n in co): + ii = find(co[0][1], nx, right) + if ii is not None: + if not right: + return Mul(Add(*[Mul(*r) for r, c in co]), Mul(*co[0][1][:ii])) + else: + return Mul(*co[0][1][ii + len(nx):]) + beg = reduce(incommon, (n[1] for n in co)) + if beg: + ii = find(beg, nx, right) + if ii is not None: + if not right: + gcdc = co[0][0] + for i in range(1, len(co)): + gcdc = gcdc.intersection(co[i][0]) + if not gcdc: + break + return Mul(*(list(gcdc) + beg[:ii])) + else: + m = ii + len(nx) + return Add(*[Mul(*(list(r) + n[m:])) for r, n in co]) + end = list(reversed( + reduce(incommon, (list(reversed(n[1])) for n in co)))) + if end: + ii = find(end, nx, right) + if ii is not None: + if not right: + return Add(*[Mul(*(list(r) + n[:-len(end) + ii])) for r, n in co]) + else: + return Mul(*end[ii + len(nx):]) + # look for single match + hit = None + for i, (r, n) in enumerate(co): + ii = find(n, nx, right) + if ii is not None: + if not hit: + hit = ii, r, n + else: + break + else: + if hit: + ii, r, n = hit + if not right: + return Mul(*(list(r) + n[:ii])) + else: + return Mul(*n[ii + len(nx):]) + + return S.Zero + + def as_expr(self, *gens): + """ + Convert a polynomial to a SymPy expression. + + Examples + ======== + + >>> from sympy import sin + >>> from sympy.abc import x, y + + >>> f = (x**2 + x*y).as_poly(x, y) + >>> f.as_expr() + x**2 + x*y + + >>> sin(x).as_expr() + sin(x) + + """ + return self + + def as_coefficient(self, expr: Expr) -> Expr | None: + """ + Extracts symbolic coefficient at the given expression. In + other words, this functions separates 'self' into the product + of 'expr' and 'expr'-free coefficient. If such separation + is not possible it will return None. + + Examples + ======== + + >>> from sympy import E, pi, sin, I, Poly + >>> from sympy.abc import x + + >>> E.as_coefficient(E) + 1 + >>> (2*E).as_coefficient(E) + 2 + >>> (2*sin(E)*E).as_coefficient(E) + + Two terms have E in them so a sum is returned. (If one were + desiring the coefficient of the term exactly matching E then + the constant from the returned expression could be selected. + Or, for greater precision, a method of Poly can be used to + indicate the desired term from which the coefficient is + desired.) + + >>> (2*E + x*E).as_coefficient(E) + x + 2 + >>> _.args[0] # just want the exact match + 2 + >>> p = Poly(2*E + x*E); p + Poly(x*E + 2*E, x, E, domain='ZZ') + >>> p.coeff_monomial(E) + 2 + >>> p.nth(0, 1) + 2 + + Since the following cannot be written as a product containing + E as a factor, None is returned. (If the coefficient ``2*x`` is + desired then the ``coeff`` method should be used.) + + >>> (2*E*x + x).as_coefficient(E) + >>> (2*E*x + x).coeff(E) + 2*x + + >>> (E*(x + 1) + x).as_coefficient(E) + + >>> (2*pi*I).as_coefficient(pi*I) + 2 + >>> (2*I).as_coefficient(pi*I) + + See Also + ======== + + coeff: return sum of terms have a given factor + as_coeff_Add: separate the additive constant from an expression + as_coeff_Mul: separate the multiplicative constant from an expression + as_independent: separate x-dependent terms/factors from others + sympy.polys.polytools.Poly.coeff_monomial: efficiently find the single coefficient of a monomial in Poly + sympy.polys.polytools.Poly.nth: like coeff_monomial but powers of monomial terms are used + + + """ + + r = self.extract_multiplicatively(expr) + if r and not r.has(expr): + return r + else: + return None + + def as_independent(self, *deps, **hint) -> tuple[Expr, Expr]: + """ + A mostly naive separation of a Mul or Add into arguments that are not + are dependent on deps. To obtain as complete a separation of variables + as possible, use a separation method first, e.g.: + + * separatevars() to change Mul, Add and Pow (including exp) into Mul + * .expand(mul=True) to change Add or Mul into Add + * .expand(log=True) to change log expr into an Add + + The only non-naive thing that is done here is to respect noncommutative + ordering of variables and to always return (0, 0) for `self` of zero + regardless of hints. + + For nonzero `self`, the returned tuple (i, d) has the + following interpretation: + + * i will has no variable that appears in deps + * d will either have terms that contain variables that are in deps, or + be equal to 0 (when self is an Add) or 1 (when self is a Mul) + * if self is an Add then self = i + d + * if self is a Mul then self = i*d + * otherwise (self, S.One) or (S.One, self) is returned. + + To force the expression to be treated as an Add, use the hint as_Add=True + + Examples + ======== + + -- self is an Add + + >>> from sympy import sin, cos, exp + >>> from sympy.abc import x, y, z + + >>> (x + x*y).as_independent(x) + (0, x*y + x) + >>> (x + x*y).as_independent(y) + (x, x*y) + >>> (2*x*sin(x) + y + x + z).as_independent(x) + (y + z, 2*x*sin(x) + x) + >>> (2*x*sin(x) + y + x + z).as_independent(x, y) + (z, 2*x*sin(x) + x + y) + + -- self is a Mul + + >>> (x*sin(x)*cos(y)).as_independent(x) + (cos(y), x*sin(x)) + + non-commutative terms cannot always be separated out when self is a Mul + + >>> from sympy import symbols + >>> n1, n2, n3 = symbols('n1 n2 n3', commutative=False) + >>> (n1 + n1*n2).as_independent(n2) + (n1, n1*n2) + >>> (n2*n1 + n1*n2).as_independent(n2) + (0, n1*n2 + n2*n1) + >>> (n1*n2*n3).as_independent(n1) + (1, n1*n2*n3) + >>> (n1*n2*n3).as_independent(n2) + (n1, n2*n3) + >>> ((x-n1)*(x-y)).as_independent(x) + (1, (x - y)*(x - n1)) + + -- self is anything else: + + >>> (sin(x)).as_independent(x) + (1, sin(x)) + >>> (sin(x)).as_independent(y) + (sin(x), 1) + >>> exp(x+y).as_independent(x) + (1, exp(x + y)) + + -- force self to be treated as an Add: + + >>> (3*x).as_independent(x, as_Add=True) + (0, 3*x) + + -- force self to be treated as a Mul: + + >>> (3+x).as_independent(x, as_Add=False) + (1, x + 3) + >>> (-3+x).as_independent(x, as_Add=False) + (1, x - 3) + + Note how the below differs from the above in making the + constant on the dep term positive. + + >>> (y*(-3+x)).as_independent(x) + (y, x - 3) + + -- use .as_independent() for true independence testing instead + of .has(). The former considers only symbols in the free + symbols while the latter considers all symbols + + >>> from sympy import Integral + >>> I = Integral(x, (x, 1, 2)) + >>> I.has(x) + True + >>> x in I.free_symbols + False + >>> I.as_independent(x) == (I, 1) + True + >>> (I + x).as_independent(x) == (I, x) + True + + Note: when trying to get independent terms, a separation method + might need to be used first. In this case, it is important to keep + track of what you send to this routine so you know how to interpret + the returned values + + >>> from sympy import separatevars, log + >>> separatevars(exp(x+y)).as_independent(x) + (exp(y), exp(x)) + >>> (x + x*y).as_independent(y) + (x, x*y) + >>> separatevars(x + x*y).as_independent(y) + (x, y + 1) + >>> (x*(1 + y)).as_independent(y) + (x, y + 1) + >>> (x*(1 + y)).expand(mul=True).as_independent(y) + (x, x*y) + >>> a, b=symbols('a b', positive=True) + >>> (log(a*b).expand(log=True)).as_independent(b) + (log(a), log(b)) + + See Also + ======== + + separatevars + expand_log + sympy.core.add.Add.as_two_terms + sympy.core.mul.Mul.as_two_terms + as_coeff_mul + """ + from .symbol import Symbol + from .add import _unevaluated_Add + from .mul import _unevaluated_Mul + + if self is S.Zero: + return (self, self) + + func = self.func + want: type[Add] | type[Mul] + if hint.get('as_Add', isinstance(self, Add) ): + want = Add + else: + want = Mul + + # sift out deps into symbolic and other and ignore + # all symbols but those that are in the free symbols + sym = set() + other = [] + for d in deps: + if isinstance(d, Symbol): # Symbol.is_Symbol is True + sym.add(d) + else: + other.append(d) + + def has(e): + """return the standard has() if there are no literal symbols, else + check to see that symbol-deps are in the free symbols.""" + has_other = e.has(*other) + if not sym: + return has_other + return has_other or e.has(*(e.free_symbols & sym)) + + if (want is not func or + func is not Add and func is not Mul): + if has(self): + return (want.identity, self) + else: + return (self, want.identity) + else: + if func is Add: + args = list(self.args) + else: + args, nc = self.args_cnc() + + d = sift(args, has) + depend = d[True] + indep = d[False] + if func is Add: # all terms were treated as commutative + return (Add(*indep), _unevaluated_Add(*depend)) + else: # handle noncommutative by stopping at first dependent term + for i, n in enumerate(nc): + if has(n): + depend.extend(nc[i:]) + break + indep.append(n) + return Mul(*indep), _unevaluated_Mul(*depend) + + def as_real_imag(self, deep=True, **hints) -> tuple[Expr, Expr]: + """Performs complex expansion on 'self' and returns a tuple + containing collected both real and imaginary parts. This + method cannot be confused with re() and im() functions, + which does not perform complex expansion at evaluation. + + However it is possible to expand both re() and im() + functions and get exactly the same results as with + a single call to this function. + + >>> from sympy import symbols, I + + >>> x, y = symbols('x,y', real=True) + + >>> (x + y*I).as_real_imag() + (x, y) + + >>> from sympy.abc import z, w + + >>> (z + w*I).as_real_imag() + (re(z) - im(w), re(w) + im(z)) + + """ + if hints.get('ignore') == self: + return None # type: ignore + else: + from sympy.functions.elementary.complexes import im, re + return (re(self), im(self)) + + def as_powers_dict(self): + """Return self as a dictionary of factors with each factor being + treated as a power. The keys are the bases of the factors and the + values, the corresponding exponents. The resulting dictionary should + be used with caution if the expression is a Mul and contains non- + commutative factors since the order that they appeared will be lost in + the dictionary. + + See Also + ======== + as_ordered_factors: An alternative for noncommutative applications, + returning an ordered list of factors. + args_cnc: Similar to as_ordered_factors, but guarantees separation + of commutative and noncommutative factors. + """ + d = defaultdict(int) + d.update([self.as_base_exp()]) + return d + + def as_coefficients_dict(self, *syms): + """Return a dictionary mapping terms to their Rational coefficient. + Since the dictionary is a defaultdict, inquiries about terms which + were not present will return a coefficient of 0. + + If symbols ``syms`` are provided, any multiplicative terms + independent of them will be considered a coefficient and a + regular dictionary of syms-dependent generators as keys and + their corresponding coefficients as values will be returned. + + Examples + ======== + + >>> from sympy.abc import a, x, y + >>> (3*x + a*x + 4).as_coefficients_dict() + {1: 4, x: 3, a*x: 1} + >>> _[a] + 0 + >>> (3*a*x).as_coefficients_dict() + {a*x: 3} + >>> (3*a*x).as_coefficients_dict(x) + {x: 3*a} + >>> (3*a*x).as_coefficients_dict(y) + {1: 3*a*x} + + """ + d = defaultdict(list) + if not syms: + for ai in Add.make_args(self): + c, m = ai.as_coeff_Mul() + d[m].append(c) + for k, v in d.items(): + if len(v) == 1: + d[k] = v[0] + else: + d[k] = Add(*v) + else: + ind, dep = self.as_independent(*syms, as_Add=True) + for i in Add.make_args(dep): + if i.is_Mul: + c, x = i.as_coeff_mul(*syms) + if c is S.One: + d[i].append(c) + else: + d[i._new_rawargs(*x)].append(c) + elif i: + d[i].append(S.One) + d = {k: Add(*d[k]) for k in d} + if ind is not S.Zero: + d.update({S.One: ind}) + di = defaultdict(int) + di.update(d) + return di + + def as_base_exp(self) -> tuple[Expr, Expr]: + # a -> b ** e + return self, S.One + + def as_coeff_mul(self, *deps, **kwargs) -> tuple[Expr, tuple[Expr, ...]]: + """Return the tuple (c, args) where self is written as a Mul, ``m``. + + c should be a Rational multiplied by any factors of the Mul that are + independent of deps. + + args should be a tuple of all other factors of m; args is empty + if self is a Number or if self is independent of deps (when given). + + This should be used when you do not know if self is a Mul or not but + you want to treat self as a Mul or if you want to process the + individual arguments of the tail of self as a Mul. + + - if you know self is a Mul and want only the head, use self.args[0]; + - if you do not want to process the arguments of the tail but need the + tail then use self.as_two_terms() which gives the head and tail; + - if you want to split self into an independent and dependent parts + use ``self.as_independent(*deps)`` + + >>> from sympy import S + >>> from sympy.abc import x, y + >>> (S(3)).as_coeff_mul() + (3, ()) + >>> (3*x*y).as_coeff_mul() + (3, (x, y)) + >>> (3*x*y).as_coeff_mul(x) + (3*y, (x,)) + >>> (3*y).as_coeff_mul(x) + (3*y, ()) + """ + if deps: + if not self.has(*deps): + return self, () + return S.One, (self,) + + def as_coeff_add(self, *deps) -> tuple[Expr, tuple[Expr, ...]]: + """Return the tuple (c, args) where self is written as an Add, ``a``. + + c should be a Rational added to any terms of the Add that are + independent of deps. + + args should be a tuple of all other terms of ``a``; args is empty + if self is a Number or if self is independent of deps (when given). + + This should be used when you do not know if self is an Add or not but + you want to treat self as an Add or if you want to process the + individual arguments of the tail of self as an Add. + + - if you know self is an Add and want only the head, use self.args[0]; + - if you do not want to process the arguments of the tail but need the + tail then use self.as_two_terms() which gives the head and tail. + - if you want to split self into an independent and dependent parts + use ``self.as_independent(*deps)`` + + >>> from sympy import S + >>> from sympy.abc import x, y + >>> (S(3)).as_coeff_add() + (3, ()) + >>> (3 + x).as_coeff_add() + (3, (x,)) + >>> (3 + x + y).as_coeff_add(x) + (y + 3, (x,)) + >>> (3 + y).as_coeff_add(x) + (y + 3, ()) + + """ + if deps: + if not self.has_free(*deps): + return self, () + return S.Zero, (self,) + + def primitive(self) -> tuple[Number, Expr]: + """Return the positive Rational that can be extracted non-recursively + from every term of self (i.e., self is treated like an Add). This is + like the as_coeff_Mul() method but primitive always extracts a positive + Rational (never a negative or a Float). + + Examples + ======== + + >>> from sympy.abc import x + >>> (3*(x + 1)**2).primitive() + (3, (x + 1)**2) + >>> a = (6*x + 2); a.primitive() + (2, 3*x + 1) + >>> b = (x/2 + 3); b.primitive() + (1/2, x + 6) + >>> (a*b).primitive() == (1, a*b) + True + """ + if not self: + return S.One, S.Zero + c, r = self.as_coeff_Mul(rational=True) + if c.is_negative: + c, r = -c, -r + return c, r + + def as_content_primitive(self, radical=False, clear=True): + """This method should recursively remove a Rational from all arguments + and return that (content) and the new self (primitive). The content + should always be positive and ``Mul(*foo.as_content_primitive()) == foo``. + The primitive need not be in canonical form and should try to preserve + the underlying structure if possible (i.e. expand_mul should not be + applied to self). + + Examples + ======== + + >>> from sympy import sqrt + >>> from sympy.abc import x, y, z + + >>> eq = 2 + 2*x + 2*y*(3 + 3*y) + + The as_content_primitive function is recursive and retains structure: + + >>> eq.as_content_primitive() + (2, x + 3*y*(y + 1) + 1) + + Integer powers will have Rationals extracted from the base: + + >>> ((2 + 6*x)**2).as_content_primitive() + (4, (3*x + 1)**2) + >>> ((2 + 6*x)**(2*y)).as_content_primitive() + (1, (2*(3*x + 1))**(2*y)) + + Terms may end up joining once their as_content_primitives are added: + + >>> ((5*(x*(1 + y)) + 2*x*(3 + 3*y))).as_content_primitive() + (11, x*(y + 1)) + >>> ((3*(x*(1 + y)) + 2*x*(3 + 3*y))).as_content_primitive() + (9, x*(y + 1)) + >>> ((3*(z*(1 + y)) + 2.0*x*(3 + 3*y))).as_content_primitive() + (1, 6.0*x*(y + 1) + 3*z*(y + 1)) + >>> ((5*(x*(1 + y)) + 2*x*(3 + 3*y))**2).as_content_primitive() + (121, x**2*(y + 1)**2) + >>> ((x*(1 + y) + 0.4*x*(3 + 3*y))**2).as_content_primitive() + (1, 4.84*x**2*(y + 1)**2) + + Radical content can also be factored out of the primitive: + + >>> (2*sqrt(2) + 4*sqrt(10)).as_content_primitive(radical=True) + (2, sqrt(2)*(1 + 2*sqrt(5))) + + If clear=False (default is True) then content will not be removed + from an Add if it can be distributed to leave one or more + terms with integer coefficients. + + >>> (x/2 + y).as_content_primitive() + (1/2, x + 2*y) + >>> (x/2 + y).as_content_primitive(clear=False) + (1, x/2 + y) + """ + return S.One, self + + def as_numer_denom(self) -> tuple[Expr, Expr]: + """Return the numerator and the denominator of an expression. + + expression -> a/b -> a, b + + This is just a stub that should be defined by + an object's class methods to get anything else. + + See Also + ======== + + normal: return ``a/b`` instead of ``(a, b)`` + + """ + return self, S.One + + def normal(self): + """Return the expression as a fraction. + + expression -> a/b + + See Also + ======== + + as_numer_denom: return ``(a, b)`` instead of ``a/b`` + + """ + from .mul import _unevaluated_Mul + n, d = self.as_numer_denom() + if d is S.One: + return n + if d.is_Number: + return _unevaluated_Mul(n, 1/d) + else: + return n/d + + def extract_multiplicatively(self, c: Expr) -> Expr | None: + """Return None if it's not possible to make self in the form + c * something in a nice way, i.e. preserving the properties + of arguments of self. + + Examples + ======== + + >>> from sympy import symbols, Rational + + >>> x, y = symbols('x,y', real=True) + + >>> ((x*y)**3).extract_multiplicatively(x**2 * y) + x*y**2 + + >>> ((x*y)**3).extract_multiplicatively(x**4 * y) + + >>> (2*x).extract_multiplicatively(2) + x + + >>> (2*x).extract_multiplicatively(3) + + >>> (Rational(1, 2)*x).extract_multiplicatively(3) + x/6 + + """ + from sympy.functions.elementary.exponential import exp + from .add import _unevaluated_Add + c = sympify(c) + if self is S.NaN: + return None + if c is S.One: + return self + elif c == self: + return S.One + + if c.is_Add: + cc, pc = c.primitive() + if cc is not S.One: + c = Mul(cc, pc, evaluate=False) + + if c.is_Mul: + a, b = c.as_two_terms() # type: ignore + x = self.extract_multiplicatively(a) + if x is not None: + return x.extract_multiplicatively(b) + else: + return x + + quotient = self / c + if self.is_Number: + if self is S.Infinity: + if c.is_positive: + return S.Infinity + elif self is S.NegativeInfinity: + if c.is_negative: + return S.Infinity + elif c.is_positive: + return S.NegativeInfinity + elif self is S.ComplexInfinity: + if not c.is_zero: + return S.ComplexInfinity + elif self.is_Integer: + if not quotient.is_Integer: + return None + elif self.is_positive and quotient.is_negative: + return None + else: + return quotient + elif self.is_Rational: + if not quotient.is_Rational: + return None + elif self.is_positive and quotient.is_negative: + return None + else: + return quotient + elif self.is_Float: + if not quotient.is_Float: + return None + elif self.is_positive and quotient.is_negative: + return None + else: + return quotient + elif self.is_NumberSymbol or self.is_Symbol or self is S.ImaginaryUnit: + if quotient.is_Mul and len(quotient.args) == 2: + if quotient.args[0].is_Integer and quotient.args[0].is_positive and quotient.args[1] == self: + return quotient + elif quotient.is_Integer and c.is_Number: + return quotient + elif self.is_Add: + cs, ps = self.primitive() + # assert cs >= 1 + if c.is_Number and c is not S.NegativeOne: + # assert c != 1 (handled at top) + if cs is not S.One: + if c.is_negative: + xc = cs.extract_multiplicatively(-c) + if xc is not None: + xc = -xc + else: + xc = cs.extract_multiplicatively(c) + if xc is not None: + return xc*ps # rely on 2-arg Mul to restore Add + return None # |c| != 1 can only be extracted from cs + if c == ps: + return cs + # check args of ps + newargs = [] + arg: Expr + for arg in ps.args: # type: ignore + newarg = arg.extract_multiplicatively(c) + if newarg is None: + return None # all or nothing + newargs.append(newarg) + if cs is not S.One: + args = [cs*t for t in newargs] + # args may be in different order + return _unevaluated_Add(*args) + else: + return Add._from_args(newargs) + elif self.is_Mul: + args: list[Expr] = list(self.args) # type: ignore + for i, arg in enumerate(args): + newarg = arg.extract_multiplicatively(c) + if newarg is not None: + args[i] = newarg + return Mul(*args) + elif self.is_Pow or isinstance(self, exp): + sb, se = self.as_base_exp() + cb, ce = c.as_base_exp() + if cb == sb: + new_exp = se.extract_additively(ce) + if new_exp is not None: + return Pow(sb, new_exp) + elif c == sb: + new_exp = se.extract_additively(1) + if new_exp is not None: + return Pow(sb, new_exp) + + return None + + def extract_additively(self, c): + """Return self - c if it's possible to subtract c from self and + make all matching coefficients move towards zero, else return None. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> e = 2*x + 3 + >>> e.extract_additively(x + 1) + x + 2 + >>> e.extract_additively(3*x) + >>> e.extract_additively(4) + >>> (y*(x + 1)).extract_additively(x + 1) + >>> ((x + 1)*(x + 2*y + 1) + 3).extract_additively(x + 1) + (x + 1)*(x + 2*y) + 3 + + See Also + ======== + extract_multiplicatively + coeff + as_coefficient + + """ + + c = sympify(c) + if self is S.NaN: + return None + if c.is_zero: + return self + elif c == self: + return S.Zero + elif self == S.Zero: + return None + + if self.is_Number: + if not c.is_Number: + return None + co = self + diff = co - c + # XXX should we match types? i.e should 3 - .1 succeed? + if (co > 0 and diff >= 0 and diff < co or + co < 0 and diff <= 0 and diff > co): + return diff + return None + + if c.is_Number: + co, t = self.as_coeff_Add() + xa = co.extract_additively(c) + if xa is None: + return None + return xa + t + + # handle the args[0].is_Number case separately + # since we will have trouble looking for the coeff of + # a number. + if c.is_Add and c.args[0].is_Number: + # whole term as a term factor + co = self.coeff(c) + xa0 = (co.extract_additively(1) or 0)*c + if xa0: + diff = self - co*c + return (xa0 + (diff.extract_additively(c) or diff)) or None + # term-wise + h, t = c.as_coeff_Add() + sh, st = self.as_coeff_Add() + xa = sh.extract_additively(h) + if xa is None: + return None + xa2 = st.extract_additively(t) + if xa2 is None: + return None + return xa + xa2 + + # whole term as a term factor + co, diff = _corem(self, c) + xa0 = (co.extract_additively(1) or 0)*c + if xa0: + return (xa0 + (diff.extract_additively(c) or diff)) or None + # term-wise + coeffs = [] + for a in Add.make_args(c): + ac, at = a.as_coeff_Mul() + co = self.coeff(at) + if not co: + return None + coc, cot = co.as_coeff_Add() + xa = coc.extract_additively(ac) + if xa is None: + return None + self -= co*at + coeffs.append((cot + xa)*at) + coeffs.append(self) + return Add(*coeffs) + + @property + def expr_free_symbols(self): + """ + Like ``free_symbols``, but returns the free symbols only if + they are contained in an expression node. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> (x + y).expr_free_symbols # doctest: +SKIP + {x, y} + + If the expression is contained in a non-expression object, do not return + the free symbols. Compare: + + >>> from sympy import Tuple + >>> t = Tuple(x + y) + >>> t.expr_free_symbols # doctest: +SKIP + set() + >>> t.free_symbols + {x, y} + """ + sympy_deprecation_warning(""" + The expr_free_symbols property is deprecated. Use free_symbols to get + the free symbols of an expression. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-expr-free-symbols") + return {j for i in self.args for j in i.expr_free_symbols} + + def could_extract_minus_sign(self) -> bool: + """Return True if self has -1 as a leading factor or has + more literal negative signs than positive signs in a sum, + otherwise False. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> e = x - y + >>> {i.could_extract_minus_sign() for i in (e, -e)} + {False, True} + + Though the ``y - x`` is considered like ``-(x - y)``, since it + is in a product without a leading factor of -1, the result is + false below: + + >>> (x*(y - x)).could_extract_minus_sign() + False + + To put something in canonical form wrt to sign, use `signsimp`: + + >>> from sympy import signsimp + >>> signsimp(x*(y - x)) + -x*(x - y) + >>> _.could_extract_minus_sign() + True + """ + return False + + def extract_branch_factor(self, allow_half=False): + """ + Try to write self as ``exp_polar(2*pi*I*n)*z`` in a nice way. + Return (z, n). + + >>> from sympy import exp_polar, I, pi + >>> from sympy.abc import x, y + >>> exp_polar(I*pi).extract_branch_factor() + (exp_polar(I*pi), 0) + >>> exp_polar(2*I*pi).extract_branch_factor() + (1, 1) + >>> exp_polar(-pi*I).extract_branch_factor() + (exp_polar(I*pi), -1) + >>> exp_polar(3*pi*I + x).extract_branch_factor() + (exp_polar(x + I*pi), 1) + >>> (y*exp_polar(-5*pi*I)*exp_polar(3*pi*I + 2*pi*x)).extract_branch_factor() + (y*exp_polar(2*pi*x), -1) + >>> exp_polar(-I*pi/2).extract_branch_factor() + (exp_polar(-I*pi/2), 0) + + If allow_half is True, also extract exp_polar(I*pi): + + >>> exp_polar(I*pi).extract_branch_factor(allow_half=True) + (1, 1/2) + >>> exp_polar(2*I*pi).extract_branch_factor(allow_half=True) + (1, 1) + >>> exp_polar(3*I*pi).extract_branch_factor(allow_half=True) + (1, 3/2) + >>> exp_polar(-I*pi).extract_branch_factor(allow_half=True) + (1, -1/2) + """ + from sympy.functions.elementary.exponential import exp_polar + from sympy.functions.elementary.integers import ceiling + + n = S.Zero + res = S.One + args = Mul.make_args(self) + exps = [] + for arg in args: + if isinstance(arg, exp_polar): + exps += [arg.exp] + else: + res *= arg + piimult = S.Zero + extras = [] + + ipi = S.Pi*S.ImaginaryUnit + while exps: + exp = exps.pop() + if exp.is_Add: + exps += exp.args + continue + if exp.is_Mul: + coeff = exp.as_coefficient(ipi) + if coeff is not None: + piimult += coeff + continue + extras += [exp] + if piimult.is_number: + coeff = piimult + tail = () + else: + coeff, tail = piimult.as_coeff_add(*piimult.free_symbols) + # round down to nearest multiple of 2 + branchfact = ceiling(coeff/2 - S.Half)*2 + n += branchfact/2 + c = coeff - branchfact + if allow_half: + nc = c.extract_additively(1) + if nc is not None: + n += S.Half + c = nc + newexp = ipi*Add(*((c, ) + tail)) + Add(*extras) + if newexp != 0: + res *= exp_polar(newexp) + return res, n + + def is_polynomial(self, *syms): + r""" + Return True if self is a polynomial in syms and False otherwise. + + This checks if self is an exact polynomial in syms. This function + returns False for expressions that are "polynomials" with symbolic + exponents. Thus, you should be able to apply polynomial algorithms to + expressions for which this returns True, and Poly(expr, \*syms) should + work if and only if expr.is_polynomial(\*syms) returns True. The + polynomial does not have to be in expanded form. If no symbols are + given, all free symbols in the expression will be used. + + This is not part of the assumptions system. You cannot do + Symbol('z', polynomial=True). + + Examples + ======== + + >>> from sympy import Symbol, Function + >>> x = Symbol('x') + >>> ((x**2 + 1)**4).is_polynomial(x) + True + >>> ((x**2 + 1)**4).is_polynomial() + True + >>> (2**x + 1).is_polynomial(x) + False + >>> (2**x + 1).is_polynomial(2**x) + True + >>> f = Function('f') + >>> (f(x) + 1).is_polynomial(x) + False + >>> (f(x) + 1).is_polynomial(f(x)) + True + >>> (1/f(x) + 1).is_polynomial(f(x)) + False + + >>> n = Symbol('n', nonnegative=True, integer=True) + >>> (x**n + 1).is_polynomial(x) + False + + This function does not attempt any nontrivial simplifications that may + result in an expression that does not appear to be a polynomial to + become one. + + >>> from sympy import sqrt, factor, cancel + >>> y = Symbol('y', positive=True) + >>> a = sqrt(y**2 + 2*y + 1) + >>> a.is_polynomial(y) + False + >>> factor(a) + y + 1 + >>> factor(a).is_polynomial(y) + True + + >>> b = (y**2 + 2*y + 1)/(y + 1) + >>> b.is_polynomial(y) + False + >>> cancel(b) + y + 1 + >>> cancel(b).is_polynomial(y) + True + + See also .is_rational_function() + + """ + if syms: + syms = set(map(sympify, syms)) + else: + syms = self.free_symbols + if not syms: + return True + + return self._eval_is_polynomial(syms) + + def _eval_is_polynomial(self, syms) -> bool | None: + if self in syms: + return True + if not self.has_free(*syms): + # constant polynomial + return True + # subclasses should return True or False + return None + + def is_rational_function(self, *syms): + """ + Test whether function is a ratio of two polynomials in the given + symbols, syms. When syms is not given, all free symbols will be used. + The rational function does not have to be in expanded or in any kind of + canonical form. + + This function returns False for expressions that are "rational + functions" with symbolic exponents. Thus, you should be able to call + .as_numer_denom() and apply polynomial algorithms to the result for + expressions for which this returns True. + + This is not part of the assumptions system. You cannot do + Symbol('z', rational_function=True). + + Examples + ======== + + >>> from sympy import Symbol, sin + >>> from sympy.abc import x, y + + >>> (x/y).is_rational_function() + True + + >>> (x**2).is_rational_function() + True + + >>> (x/sin(y)).is_rational_function(y) + False + + >>> n = Symbol('n', integer=True) + >>> (x**n + 1).is_rational_function(x) + False + + This function does not attempt any nontrivial simplifications that may + result in an expression that does not appear to be a rational function + to become one. + + >>> from sympy import sqrt, factor + >>> y = Symbol('y', positive=True) + >>> a = sqrt(y**2 + 2*y + 1)/y + >>> a.is_rational_function(y) + False + >>> factor(a) + (y + 1)/y + >>> factor(a).is_rational_function(y) + True + + See also is_algebraic_expr(). + + """ + if syms: + syms = set(map(sympify, syms)) + else: + syms = self.free_symbols + if not syms: + return self not in _illegal + + return self._eval_is_rational_function(syms) + + def _eval_is_rational_function(self, syms) -> bool | None: + if self in syms: + return True + if not self.has_xfree(syms): + return True + # subclasses should return True or False + return None + + def is_meromorphic(self, x, a): + """ + This tests whether an expression is meromorphic as + a function of the given symbol ``x`` at the point ``a``. + + This method is intended as a quick test that will return + None if no decision can be made without simplification or + more detailed analysis. + + Examples + ======== + + >>> from sympy import zoo, log, sin, sqrt + >>> from sympy.abc import x + + >>> f = 1/x**2 + 1 - 2*x**3 + >>> f.is_meromorphic(x, 0) + True + >>> f.is_meromorphic(x, 1) + True + >>> f.is_meromorphic(x, zoo) + True + + >>> g = x**log(3) + >>> g.is_meromorphic(x, 0) + False + >>> g.is_meromorphic(x, 1) + True + >>> g.is_meromorphic(x, zoo) + False + + >>> h = sin(1/x)*x**2 + >>> h.is_meromorphic(x, 0) + False + >>> h.is_meromorphic(x, 1) + True + >>> h.is_meromorphic(x, zoo) + True + + Multivalued functions are considered meromorphic when their + branches are meromorphic. Thus most functions are meromorphic + everywhere except at essential singularities and branch points. + In particular, they will be meromorphic also on branch cuts + except at their endpoints. + + >>> log(x).is_meromorphic(x, -1) + True + >>> log(x).is_meromorphic(x, 0) + False + >>> sqrt(x).is_meromorphic(x, -1) + True + >>> sqrt(x).is_meromorphic(x, 0) + False + + """ + if not x.is_symbol: + raise TypeError("{} should be of symbol type".format(x)) + a = sympify(a) + + return self._eval_is_meromorphic(x, a) + + def _eval_is_meromorphic(self, x, a) -> bool | None: + if self == x: + return True + if not self.has_free(x): + return True + # subclasses should return True or False + return None + + def is_algebraic_expr(self, *syms): + """ + This tests whether a given expression is algebraic or not, in the + given symbols, syms. When syms is not given, all free symbols + will be used. The rational function does not have to be in expanded + or in any kind of canonical form. + + This function returns False for expressions that are "algebraic + expressions" with symbolic exponents. This is a simple extension to the + is_rational_function, including rational exponentiation. + + Examples + ======== + + >>> from sympy import Symbol, sqrt + >>> x = Symbol('x', real=True) + >>> sqrt(1 + x).is_rational_function() + False + >>> sqrt(1 + x).is_algebraic_expr() + True + + This function does not attempt any nontrivial simplifications that may + result in an expression that does not appear to be an algebraic + expression to become one. + + >>> from sympy import exp, factor + >>> a = sqrt(exp(x)**2 + 2*exp(x) + 1)/(exp(x) + 1) + >>> a.is_algebraic_expr(x) + False + >>> factor(a).is_algebraic_expr() + True + + See Also + ======== + + is_rational_function + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Algebraic_expression + + """ + if syms: + syms = set(map(sympify, syms)) + else: + syms = self.free_symbols + if not syms: + return True + + return self._eval_is_algebraic_expr(syms) + + def _eval_is_algebraic_expr(self, syms) -> bool | None: + if self in syms: + return True + if not self.has_free(*syms): + return True + # subclasses should return True or False + return None + + ################################################################################### + ##################### SERIES, LEADING TERM, LIMIT, ORDER METHODS ################## + ################################################################################### + + def series(self, x=None, x0=0, n=6, dir="+", logx=None, cdir=0): + """ + Series expansion of "self" around ``x = x0`` yielding either terms of + the series one by one (the lazy series given when n=None), else + all the terms at once when n != None. + + Returns the series expansion of "self" around the point ``x = x0`` + with respect to ``x`` up to ``O((x - x0)**n, x, x0)`` (default n is 6). + + If ``x=None`` and ``self`` is univariate, the univariate symbol will + be supplied, otherwise an error will be raised. + + Parameters + ========== + + expr : Expression + The expression whose series is to be expanded. + + x : Symbol + It is the variable of the expression to be calculated. + + x0 : Value + The value around which ``x`` is calculated. Can be any value + from ``-oo`` to ``oo``. + + n : Value + The value used to represent the order in terms of ``x**n``, + up to which the series is to be expanded. + + dir : String, optional + The series-expansion can be bi-directional. If ``dir="+"``, + then (x->x0+). If ``dir="-", then (x->x0-). For infinite + ``x0`` (``oo`` or ``-oo``), the ``dir`` argument is determined + from the direction of the infinity (i.e., ``dir="-"`` for + ``oo``). + + logx : optional + It is used to replace any log(x) in the returned series with a + symbolic value rather than evaluating the actual value. + + cdir : optional + It stands for complex direction, and indicates the direction + from which the expansion needs to be evaluated. + + Examples + ======== + + >>> from sympy import cos, exp, tan + >>> from sympy.abc import x, y + >>> cos(x).series() + 1 - x**2/2 + x**4/24 + O(x**6) + >>> cos(x).series(n=4) + 1 - x**2/2 + O(x**4) + >>> cos(x).series(x, x0=1, n=2) + cos(1) - (x - 1)*sin(1) + O((x - 1)**2, (x, 1)) + >>> e = cos(x + exp(y)) + >>> e.series(y, n=2) + cos(x + 1) - y*sin(x + 1) + O(y**2) + >>> e.series(x, n=2) + cos(exp(y)) - x*sin(exp(y)) + O(x**2) + + If ``n=None`` then a generator of the series terms will be returned. + + >>> term=cos(x).series(n=None) + >>> [next(term) for i in range(2)] + [1, -x**2/2] + + For ``dir=+`` (default) the series is calculated from the right and + for ``dir=-`` the series from the left. For smooth functions this + flag will not alter the results. + + >>> abs(x).series(dir="+") + x + >>> abs(x).series(dir="-") + -x + >>> f = tan(x) + >>> f.series(x, 2, 6, "+") + tan(2) + (1 + tan(2)**2)*(x - 2) + (x - 2)**2*(tan(2)**3 + tan(2)) + + (x - 2)**3*(1/3 + 4*tan(2)**2/3 + tan(2)**4) + (x - 2)**4*(tan(2)**5 + + 5*tan(2)**3/3 + 2*tan(2)/3) + (x - 2)**5*(2/15 + 17*tan(2)**2/15 + + 2*tan(2)**4 + tan(2)**6) + O((x - 2)**6, (x, 2)) + + >>> f.series(x, 2, 3, "-") + tan(2) + (2 - x)*(-tan(2)**2 - 1) + (2 - x)**2*(tan(2)**3 + tan(2)) + + O((x - 2)**3, (x, 2)) + + For rational expressions this method may return original expression without the Order term. + >>> (1/x).series(x, n=8) + 1/x + + Returns + ======= + + Expr : Expression + Series expansion of the expression about x0 + + Raises + ====== + + TypeError + If "n" and "x0" are infinity objects + + PoleError + If "x0" is an infinity object + + """ + if x is None: + syms = self.free_symbols + if not syms: + return self + elif len(syms) > 1: + raise ValueError('x must be given for multivariate functions.') + x = syms.pop() + + from .symbol import Dummy, Symbol + if isinstance(x, Symbol): + dep = x in self.free_symbols + else: + d = Dummy() + dep = d in self.xreplace({x: d}).free_symbols + if not dep: + if n is None: + return (s for s in [self]) + else: + return self + + if len(dir) != 1 or dir not in '+-': + raise ValueError("Dir must be '+' or '-'") + + if n is not None: + n = int(n) + if n < 0: + raise ValueError("Number of terms should be nonnegative") + + x0 = sympify(x0) + cdir = sympify(cdir) + from sympy.functions.elementary.complexes import im, sign + + if not cdir.is_zero: + if cdir.is_real: + dir = '+' if cdir.is_positive else '-' + else: + dir = '+' if im(cdir).is_positive else '-' + else: + if x0 and x0.is_infinite: + cdir = sign(x0).simplify() + elif str(dir) == "+": + cdir = S.One + elif str(dir) == "-": + cdir = S.NegativeOne + elif cdir == S.Zero: + cdir = S.One + + cdir = cdir/abs(cdir) + + if x0 and x0.is_infinite: + from .function import PoleError + try: + s = self.subs(x, cdir/x).series(x, n=n, dir='+', cdir=1) + if n is None: + return (si.subs(x, cdir/x) for si in s) + return s.subs(x, cdir/x) + except PoleError: + s = self.subs(x, cdir*x).aseries(x, n=n) + return s.subs(x, cdir*x) + + # use rep to shift origin to x0 and change sign (if dir is negative) + # and undo the process with rep2 + if x0 or cdir != 1: + s = self.subs({x: x0 + cdir*x}).series(x, x0=0, n=n, dir='+', logx=logx, cdir=1) + if n is None: # lseries... + return (si.subs({x: x/cdir - x0/cdir}) for si in s) + return s.subs({x: x/cdir - x0/cdir}) + + # from here on it's x0=0 and dir='+' handling + + if x.is_positive is x.is_negative is None or x.is_Symbol is not True: + # replace x with an x that has a positive assumption + xpos = Dummy('x', positive=True) + rv = self.subs(x, xpos).series(xpos, x0, n, dir, logx=logx, cdir=cdir) + if n is None: + return (s.subs(xpos, x) for s in rv) + else: + return rv.subs(xpos, x) + + from sympy.series.order import Order + if n is not None: # nseries handling + s1 = self._eval_nseries(x, n=n, logx=logx, cdir=cdir) + o = s1.getO() or S.Zero + if o: + # make sure the requested order is returned + ngot = o.getn() + if ngot > n: + # leave o in its current form (e.g. with x*log(x)) so + # it eats terms properly, then replace it below + if n != 0: + s1 += o.subs(x, x**Rational(n, ngot)) + else: + s1 += Order(1, x) + elif ngot < n: + # increase the requested number of terms to get the desired + # number keep increasing (up to 9) until the received order + # is different than the original order and then predict how + # many additional terms are needed + from sympy.functions.elementary.integers import ceiling + for more in range(1, 9): + s1 = self._eval_nseries(x, n=n + more, logx=logx, cdir=cdir) + newn = s1.getn() + if newn != ngot: + ndo = n + ceiling((n - ngot)*more/(newn - ngot)) + s1 = self._eval_nseries(x, n=ndo, logx=logx, cdir=cdir) + while s1.getn() < n: + s1 = self._eval_nseries(x, n=ndo, logx=logx, cdir=cdir) + ndo += 1 + break + else: + raise ValueError('Could not calculate %s terms for %s' + % (str(n), self)) + s1 += Order(x**n, x) + o = s1.getO() + s1 = s1.removeO() + elif s1.has(Order): + # asymptotic expansion + return s1 + else: + o = Order(x**n, x) + s1done = s1.doit() + try: + if (s1done + o).removeO() == s1done: + o = S.Zero + except NotImplementedError: + return s1 + + try: + from sympy.simplify.radsimp import collect + return collect(s1, x) + o + except NotImplementedError: + return s1 + o + + else: # lseries handling + def yield_lseries(s): + """Return terms of lseries one at a time.""" + for si in s: + if not si.is_Add: + yield si + continue + # yield terms 1 at a time if possible + # by increasing order until all the + # terms have been returned + yielded = 0 + o = Order(si, x)*x + ndid = 0 + ndo = len(si.args) + while 1: + do = (si - yielded + o).removeO() + o *= x + if not do or do.is_Order: + continue + if do.is_Add: + ndid += len(do.args) + else: + ndid += 1 + yield do + if ndid == ndo: + break + yielded += do + + return yield_lseries(self.removeO()._eval_lseries(x, logx=logx, cdir=cdir)) + + def aseries(self, x=None, n=6, bound=0, hir=False): + """Asymptotic Series expansion of self. + This is equivalent to ``self.series(x, oo, n)``. + + Parameters + ========== + + self : Expression + The expression whose series is to be expanded. + + x : Symbol + It is the variable of the expression to be calculated. + + n : Value + The value used to represent the order in terms of ``x**n``, + up to which the series is to be expanded. + + hir : Boolean + Set this parameter to be True to produce hierarchical series. + It stops the recursion at an early level and may provide nicer + and more useful results. + + bound : Value, Integer + Use the ``bound`` parameter to give limit on rewriting + coefficients in its normalised form. + + Examples + ======== + + >>> from sympy import sin, exp + >>> from sympy.abc import x + + >>> e = sin(1/x + exp(-x)) - sin(1/x) + + >>> e.aseries(x) + (1/(24*x**4) - 1/(2*x**2) + 1 + O(x**(-6), (x, oo)))*exp(-x) + + >>> e.aseries(x, n=3, hir=True) + -exp(-2*x)*sin(1/x)/2 + exp(-x)*cos(1/x) + O(exp(-3*x), (x, oo)) + + >>> e = exp(exp(x)/(1 - 1/x)) + + >>> e.aseries(x) + exp(exp(x)/(1 - 1/x)) + + >>> e.aseries(x, bound=3) # doctest: +SKIP + exp(exp(x)/x**2)*exp(exp(x)/x)*exp(-exp(x) + exp(x)/(1 - 1/x) - exp(x)/x - exp(x)/x**2)*exp(exp(x)) + + For rational expressions this method may return original expression without the Order term. + >>> (1/x).aseries(x, n=8) + 1/x + + Returns + ======= + + Expr + Asymptotic series expansion of the expression. + + Notes + ===== + + This algorithm is directly induced from the limit computational algorithm provided by Gruntz. + It majorly uses the mrv and rewrite sub-routines. The overall idea of this algorithm is first + to look for the most rapidly varying subexpression w of a given expression f and then expands f + in a series in w. Then same thing is recursively done on the leading coefficient + till we get constant coefficients. + + If the most rapidly varying subexpression of a given expression f is f itself, + the algorithm tries to find a normalised representation of the mrv set and rewrites f + using this normalised representation. + + If the expansion contains an order term, it will be either ``O(x ** (-n))`` or ``O(w ** (-n))`` + where ``w`` belongs to the most rapidly varying expression of ``self``. + + References + ========== + + .. [1] Gruntz, Dominik. A new algorithm for computing asymptotic series. + In: Proc. 1993 Int. Symp. Symbolic and Algebraic Computation. 1993. + pp. 239-244. + .. [2] Gruntz thesis - p90 + .. [3] https://en.wikipedia.org/wiki/Asymptotic_expansion + + See Also + ======== + + Expr.aseries: See the docstring of this function for complete details of this wrapper. + """ + + from .symbol import Dummy + + if x.is_positive is x.is_negative is None: + xpos = Dummy('x', positive=True) + return self.subs(x, xpos).aseries(xpos, n, bound, hir).subs(xpos, x) + + from .function import PoleError + from sympy.series.gruntz import mrv, rewrite + + try: + om, exps = mrv(self, x) + except PoleError: + return self + + # We move one level up by replacing `x` by `exp(x)`, and then + # computing the asymptotic series for f(exp(x)). Then asymptotic series + # can be obtained by moving one-step back, by replacing x by ln(x). + + from sympy.functions.elementary.exponential import exp, log + from sympy.series.order import Order + + if x in om: + s = self.subs(x, exp(x)).aseries(x, n, bound, hir).subs(x, log(x)) + if s.getO(): + return s + Order(1/x**n, (x, S.Infinity)) + return s + + k = Dummy('k', positive=True) + # f is rewritten in terms of omega + func, logw = rewrite(exps, om, x, k) + + if self in om: + if bound <= 0: + return self + s = (self.exp).aseries(x, n, bound=bound) + s = s.func(*[t.removeO() for t in s.args]) + try: + res = exp(s.subs(x, 1/x).as_leading_term(x).subs(x, 1/x)) + except PoleError: + res = self + + func = exp(self.args[0] - res.args[0]) / k + logw = log(1/res) + + s = func.series(k, 0, n) + from sympy.core.function import expand_mul + s = expand_mul(s) + # Hierarchical series + if hir: + return s.subs(k, exp(logw)) + + o = s.getO() + terms = sorted(Add.make_args(s.removeO()), key=lambda i: int(i.as_coeff_exponent(k)[1])) + s = S.Zero + has_ord = False + + # Then we recursively expand these coefficients one by one into + # their asymptotic series in terms of their most rapidly varying subexpressions. + for t in terms: + coeff, expo = t.as_coeff_exponent(k) + if coeff.has(x): + # Recursive step + snew = coeff.aseries(x, n, bound=bound-1) + if has_ord and snew.getO(): + break + elif snew.getO(): + has_ord = True + s += (snew * k**expo) + else: + s += t + + if not o or has_ord: + return s.subs(k, exp(logw)) + return (s + o).subs(k, exp(logw)) + + + def taylor_term(self, n, x, *previous_terms): + """General method for the taylor term. + + This method is slow, because it differentiates n-times. Subclasses can + redefine it to make it faster by using the "previous_terms". + """ + from .symbol import Dummy + from sympy.functions.combinatorial.factorials import factorial + + x = sympify(x) + _x = Dummy('x') + return self.subs(x, _x).diff(_x, n).subs(_x, x).subs(x, 0) * x**n / factorial(n) + + def lseries(self, x=None, x0=0, dir='+', logx=None, cdir=0): + """ + Wrapper for series yielding an iterator of the terms of the series. + + Note: an infinite series will yield an infinite iterator. The following, + for exaxmple, will never terminate. It will just keep printing terms + of the sin(x) series:: + + for term in sin(x).lseries(x): + print term + + The advantage of lseries() over nseries() is that many times you are + just interested in the next term in the series (i.e. the first term for + example), but you do not know how many you should ask for in nseries() + using the "n" parameter. + + See also nseries(). + """ + return self.series(x, x0, n=None, dir=dir, logx=logx, cdir=cdir) + + def _eval_lseries(self, x, logx=None, cdir=0): + # default implementation of lseries is using nseries(), and adaptively + # increasing the "n". As you can see, it is not very efficient, because + # we are calculating the series over and over again. Subclasses should + # override this method and implement much more efficient yielding of + # terms. + n = 0 + series = self._eval_nseries(x, n=n, logx=logx, cdir=cdir) + + while series.is_Order: + n += 1 + series = self._eval_nseries(x, n=n, logx=logx, cdir=cdir) + + e = series.removeO() + yield e + if e is S.Zero: + return + + while 1: + while 1: + n += 1 + series = self._eval_nseries(x, n=n, logx=logx, cdir=cdir).removeO() + if e != series: + break + if (series - self).cancel() is S.Zero: + return + yield series - e + e = series + + def nseries(self, x=None, x0=0, n=6, dir='+', logx=None, cdir=0): + """ + Wrapper to _eval_nseries if assumptions allow, else to series. + + If x is given, x0 is 0, dir='+', and self has x, then _eval_nseries is + called. This calculates "n" terms in the innermost expressions and + then builds up the final series just by "cross-multiplying" everything + out. + + The optional ``logx`` parameter can be used to replace any log(x) in the + returned series with a symbolic value to avoid evaluating log(x) at 0. A + symbol to use in place of log(x) should be provided. + + Advantage -- it's fast, because we do not have to determine how many + terms we need to calculate in advance. + + Disadvantage -- you may end up with less terms than you may have + expected, but the O(x**n) term appended will always be correct and + so the result, though perhaps shorter, will also be correct. + + If any of those assumptions is not met, this is treated like a + wrapper to series which will try harder to return the correct + number of terms. + + See also lseries(). + + Examples + ======== + + >>> from sympy import sin, log, Symbol + >>> from sympy.abc import x, y + >>> sin(x).nseries(x, 0, 6) + x - x**3/6 + x**5/120 + O(x**6) + >>> log(x+1).nseries(x, 0, 5) + x - x**2/2 + x**3/3 - x**4/4 + O(x**5) + + Handling of the ``logx`` parameter --- in the following example the + expansion fails since ``sin`` does not have an asymptotic expansion + at -oo (the limit of log(x) as x approaches 0): + + >>> e = sin(log(x)) + >>> e.nseries(x, 0, 6) + Traceback (most recent call last): + ... + PoleError: ... + ... + >>> logx = Symbol('logx') + >>> e.nseries(x, 0, 6, logx=logx) + sin(logx) + + In the following example, the expansion works but only returns self + unless the ``logx`` parameter is used: + + >>> e = x**y + >>> e.nseries(x, 0, 2) + x**y + >>> e.nseries(x, 0, 2, logx=logx) + exp(logx*y) + + """ + if x and x not in self.free_symbols: + return self + if x is None or x0 or dir != '+': # {see XPOS above} or (x.is_positive == x.is_negative == None): + return self.series(x, x0, n, dir, cdir=cdir) + else: + return self._eval_nseries(x, n=n, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir): + """ + Return terms of series for self up to O(x**n) at x=0 + from the positive direction. + + This is a method that should be overridden in subclasses. Users should + never call this method directly (use .nseries() instead), so you do not + have to write docstrings for _eval_nseries(). + """ + raise NotImplementedError(filldedent(""" + The _eval_nseries method should be added to + %s to give terms up to O(x**n) at x=0 + from the positive direction so it is available when + nseries calls it.""" % self.func) + ) + + def limit(self, x, xlim, dir='+'): + """ Compute limit x->xlim. + """ + from sympy.series.limits import limit + return limit(self, x, xlim, dir) + + @cacheit + def as_leading_term(self, *symbols, logx=None, cdir=0): + """ + Returns the leading (nonzero) term of the series expansion of self. + + The _eval_as_leading_term routines are used to do this, and they must + always return a non-zero value. + + Examples + ======== + + >>> from sympy.abc import x + >>> (1 + x + x**2).as_leading_term(x) + 1 + >>> (1/x**2 + x + x**2).as_leading_term(x) + x**(-2) + + """ + if len(symbols) > 1: + c = self + for x in symbols: + c = c.as_leading_term(x, logx=logx, cdir=cdir) + return c + elif not symbols: + return self + x = sympify(symbols[0]) + cdir = sympify(cdir) + if not x.is_symbol: + raise ValueError('expecting a Symbol but got %s' % x) + if x not in self.free_symbols: + return self + obj = self._eval_as_leading_term(x, logx=logx, cdir=cdir) + if obj is not None: + from sympy.simplify.powsimp import powsimp + return powsimp(obj, deep=True, combine='exp') + raise NotImplementedError('as_leading_term(%s, %s)' % (self, x)) + + def _eval_as_leading_term(self, x, logx, cdir): + return self + + def as_coeff_exponent(self, x) -> tuple[Expr, Expr]: + """ ``c*x**e -> c,e`` where x can be any symbolic expression. + """ + from sympy.simplify.radsimp import collect + s = collect(self, x) + c, p = s.as_coeff_mul(x) + if len(p) == 1: + b, e = p[0].as_base_exp() + if b == x: + return c, e + return s, S.Zero + + def leadterm(self, x, logx=None, cdir=0): + """ + Returns the leading term a*x**b as a tuple (a, b). + + Examples + ======== + + >>> from sympy.abc import x + >>> (1+x+x**2).leadterm(x) + (1, 0) + >>> (1/x**2+x+x**2).leadterm(x) + (1, -2) + + """ + from .symbol import Dummy + from sympy.functions.elementary.exponential import log + l = self.as_leading_term(x, logx=logx, cdir=cdir) + d = Dummy('logx') + if l.has(log(x)): + l = l.subs(log(x), d) + c, e = l.as_coeff_exponent(x) + if x in c.free_symbols: + raise ValueError(filldedent(""" + cannot compute leadterm(%s, %s). The coefficient + should have been free of %s but got %s""" % (self, x, x, c))) + c = c.subs(d, log(x)) + return c, e + + def as_coeff_Mul(self, rational: bool = False) -> tuple['Number', Expr]: + """Efficiently extract the coefficient of a product.""" + return S.One, self + + def as_coeff_Add(self, rational=False) -> tuple['Number', Expr]: + """Efficiently extract the coefficient of a summation.""" + return S.Zero, self + + def fps(self, x=None, x0=0, dir=1, hyper=True, order=4, rational=True, + full=False): + """ + Compute formal power power series of self. + + See the docstring of the :func:`fps` function in sympy.series.formal for + more information. + """ + from sympy.series.formal import fps + + return fps(self, x, x0, dir, hyper, order, rational, full) + + def fourier_series(self, limits=None): + """Compute fourier sine/cosine series of self. + + See the docstring of the :func:`fourier_series` in sympy.series.fourier + for more information. + """ + from sympy.series.fourier import fourier_series + + return fourier_series(self, limits) + + ################################################################################### + ##################### DERIVATIVE, INTEGRAL, FUNCTIONAL METHODS #################### + ################################################################################### + + def diff(self, *symbols, **assumptions): + assumptions.setdefault("evaluate", True) + return _derivative_dispatch(self, *symbols, **assumptions) + + ########################################################################### + ###################### EXPRESSION EXPANSION METHODS ####################### + ########################################################################### + + # Relevant subclasses should override _eval_expand_hint() methods. See + # the docstring of expand() for more info. + + def _eval_expand_complex(self, **hints): + real, imag = self.as_real_imag(**hints) + return real + S.ImaginaryUnit*imag + + @staticmethod + def _expand_hint(expr, hint, deep=True, **hints): + """ + Helper for ``expand()``. Recursively calls ``expr._eval_expand_hint()``. + + Returns ``(expr, hit)``, where expr is the (possibly) expanded + ``expr`` and ``hit`` is ``True`` if ``expr`` was truly expanded and + ``False`` otherwise. + """ + hit = False + # XXX: Hack to support non-Basic args + # | + # V + if deep and getattr(expr, 'args', ()) and not expr.is_Atom: + sargs = [] + for arg in expr.args: + arg, arghit = Expr._expand_hint(arg, hint, **hints) + hit |= arghit + sargs.append(arg) + + if hit: + expr = expr.func(*sargs) + + if hasattr(expr, hint): + newexpr = getattr(expr, hint)(**hints) + if newexpr != expr: + return (newexpr, True) + + return (expr, hit) + + @cacheit + def expand(self, deep=True, modulus=None, power_base=True, power_exp=True, + mul=True, log=True, multinomial=True, basic=True, **hints): + """ + Expand an expression using hints. + + See the docstring of the expand() function in sympy.core.function for + more information. + + """ + from sympy.simplify.radsimp import fraction + + hints.update(power_base=power_base, power_exp=power_exp, mul=mul, + log=log, multinomial=multinomial, basic=basic) + + expr = self + # default matches fraction's default + _fraction = lambda x: fraction(x, hints.get('exact', False)) + if hints.pop('frac', False): + n, d = [a.expand(deep=deep, modulus=modulus, **hints) + for a in _fraction(self)] + return n/d + elif hints.pop('denom', False): + n, d = _fraction(self) + return n/d.expand(deep=deep, modulus=modulus, **hints) + elif hints.pop('numer', False): + n, d = _fraction(self) + return n.expand(deep=deep, modulus=modulus, **hints)/d + + # Although the hints are sorted here, an earlier hint may get applied + # at a given node in the expression tree before another because of how + # the hints are applied. e.g. expand(log(x*(y + z))) -> log(x*y + + # x*z) because while applying log at the top level, log and mul are + # applied at the deeper level in the tree so that when the log at the + # upper level gets applied, the mul has already been applied at the + # lower level. + + # Additionally, because hints are only applied once, the expression + # may not be expanded all the way. For example, if mul is applied + # before multinomial, x*(x + 1)**2 won't be expanded all the way. For + # now, we just use a special case to make multinomial run before mul, + # so that at least polynomials will be expanded all the way. In the + # future, smarter heuristics should be applied. + # TODO: Smarter heuristics + + def _expand_hint_key(hint): + """Make multinomial come before mul""" + if hint == 'mul': + return 'mulz' + return hint + + for hint in sorted(hints.keys(), key=_expand_hint_key): + use_hint = hints[hint] + if use_hint: + hint = '_eval_expand_' + hint + expr, hit = Expr._expand_hint(expr, hint, deep=deep, **hints) + + while True: + was = expr + if hints.get('multinomial', False): + expr, _ = Expr._expand_hint( + expr, '_eval_expand_multinomial', deep=deep, **hints) + if hints.get('mul', False): + expr, _ = Expr._expand_hint( + expr, '_eval_expand_mul', deep=deep, **hints) + if hints.get('log', False): + expr, _ = Expr._expand_hint( + expr, '_eval_expand_log', deep=deep, **hints) + if expr == was: + break + + if modulus is not None: + modulus = sympify(modulus) + + if not modulus.is_Integer or modulus <= 0: + raise ValueError( + "modulus must be a positive integer, got %s" % modulus) + + terms = [] + + for term in Add.make_args(expr): + coeff, tail = term.as_coeff_Mul(rational=True) + + coeff %= modulus + + if coeff: + terms.append(coeff*tail) + + expr = Add(*terms) + + return expr + + ########################################################################### + ################### GLOBAL ACTION VERB WRAPPER METHODS #################### + ########################################################################### + + def integrate(self, *args, **kwargs): + """See the integrate function in sympy.integrals""" + from sympy.integrals.integrals import integrate + return integrate(self, *args, **kwargs) + + def nsimplify(self, constants=(), tolerance=None, full=False): + """See the nsimplify function in sympy.simplify""" + from sympy.simplify.simplify import nsimplify + return nsimplify(self, constants, tolerance, full) + + def separate(self, deep=False, force=False): + """See the separate function in sympy.simplify""" + from .function import expand_power_base + return expand_power_base(self, deep=deep, force=force) + + def collect(self, syms, func=None, evaluate=True, exact=False, distribute_order_term=True): + """See the collect function in sympy.simplify""" + from sympy.simplify.radsimp import collect + return collect(self, syms, func, evaluate, exact, distribute_order_term) + + def together(self, *args, **kwargs): + """See the together function in sympy.polys""" + from sympy.polys.rationaltools import together + return together(self, *args, **kwargs) + + def apart(self, x=None, **args): + """See the apart function in sympy.polys""" + from sympy.polys.partfrac import apart + return apart(self, x, **args) + + def ratsimp(self): + """See the ratsimp function in sympy.simplify""" + from sympy.simplify.ratsimp import ratsimp + return ratsimp(self) + + def trigsimp(self, **args): + """See the trigsimp function in sympy.simplify""" + from sympy.simplify.trigsimp import trigsimp + return trigsimp(self, **args) + + def radsimp(self, **kwargs): + """See the radsimp function in sympy.simplify""" + from sympy.simplify.radsimp import radsimp + return radsimp(self, **kwargs) + + def powsimp(self, *args, **kwargs): + """See the powsimp function in sympy.simplify""" + from sympy.simplify.powsimp import powsimp + return powsimp(self, *args, **kwargs) + + def combsimp(self): + """See the combsimp function in sympy.simplify""" + from sympy.simplify.combsimp import combsimp + return combsimp(self) + + def gammasimp(self): + """See the gammasimp function in sympy.simplify""" + from sympy.simplify.gammasimp import gammasimp + return gammasimp(self) + + def factor(self, *gens, **args): + """See the factor() function in sympy.polys.polytools""" + from sympy.polys.polytools import factor + return factor(self, *gens, **args) + + def cancel(self, *gens, **args): + """See the cancel function in sympy.polys""" + from sympy.polys.polytools import cancel + return cancel(self, *gens, **args) + + def invert(self, g, *gens, **args): + """Return the multiplicative inverse of ``self`` mod ``g`` + where ``self`` (and ``g``) may be symbolic expressions). + + See Also + ======== + sympy.core.intfunc.mod_inverse, sympy.polys.polytools.invert + """ + if self.is_number and getattr(g, 'is_number', True): + return mod_inverse(self, g) + from sympy.polys.polytools import invert + return invert(self, g, *gens, **args) + + def round(self, n=None): + """Return x rounded to the given decimal place. + + If a complex number would results, apply round to the real + and imaginary components of the number. + + Examples + ======== + + >>> from sympy import pi, E, I, S, Number + >>> pi.round() + 3 + >>> pi.round(2) + 3.14 + >>> (2*pi + E*I).round() + 6 + 3*I + + The round method has a chopping effect: + + >>> (2*pi + I/10).round() + 6 + >>> (pi/10 + 2*I).round() + 2*I + >>> (pi/10 + E*I).round(2) + 0.31 + 2.72*I + + Notes + ===== + + The Python ``round`` function uses the SymPy ``round`` method so it + will always return a SymPy number (not a Python float or int): + + >>> isinstance(round(S(123), -2), Number) + True + """ + x = self + + if not x.is_number: + raise TypeError("Cannot round symbolic expression") + if not x.is_Atom: + if not pure_complex(x.n(2), or_real=True): + raise TypeError( + 'Expected a number but got %s:' % func_name(x)) + elif x in _illegal: + return x + if not (xr := x.is_extended_real): + r, i = x.as_real_imag() + if xr is False: + return r.round(n) + S.ImaginaryUnit*i.round(n) + if i.equals(0): + return r.round(n) + if not x: + return S.Zero if n is None else x + + p = as_int(n or 0) + + if x.is_Integer: + return Integer(round(int(x), p)) + + digits_to_decimal = _mag(x) # _mag(12) = 2, _mag(.012) = -1 + allow = digits_to_decimal + p + precs = [f._prec for f in x.atoms(Float)] + dps = prec_to_dps(max(precs)) if precs else None + if dps is None: + # assume everything is exact so use the Python + # float default or whatever was requested + dps = max(15, allow) + else: + allow = min(allow, dps) + # this will shift all digits to right of decimal + # and give us dps to work with as an int + shift = -digits_to_decimal + dps + extra = 1 # how far we look past known digits + # NOTE + # mpmath will calculate the binary representation to + # an arbitrary number of digits but we must base our + # answer on a finite number of those digits, e.g. + # .575 2589569785738035/2**52 in binary. + # mpmath shows us that the first 18 digits are + # >>> Float(.575).n(18) + # 0.574999999999999956 + # The default precision is 15 digits and if we ask + # for 15 we get + # >>> Float(.575).n(15) + # 0.575000000000000 + # mpmath handles rounding at the 15th digit. But we + # need to be careful since the user might be asking + # for rounding at the last digit and our semantics + # are to round toward the even final digit when there + # is a tie. So the extra digit will be used to make + # that decision. In this case, the value is the same + # to 15 digits: + # >>> Float(.575).n(16) + # 0.5750000000000000 + # Now converting this to the 15 known digits gives + # 575000000000000.0 + # which rounds to integer + # 5750000000000000 + # And now we can round to the desired digt, e.g. at + # the second from the left and we get + # 5800000000000000 + # and rescaling that gives + # 0.58 + # as the final result. + # If the value is made slightly less than 0.575 we might + # still obtain the same value: + # >>> Float(.575-1e-16).n(16)*10**15 + # 574999999999999.8 + # What 15 digits best represents the known digits (which are + # to the left of the decimal? 5750000000000000, the same as + # before. The only way we will round down (in this case) is + # if we declared that we had more than 15 digits of precision. + # For example, if we use 16 digits of precision, the integer + # we deal with is + # >>> Float(.575-1e-16).n(17)*10**16 + # 5749999999999998.4 + # and this now rounds to 5749999999999998 and (if we round to + # the 2nd digit from the left) we get 5700000000000000. + # + xf = x.n(dps + extra)*Pow(10, shift) + if xf.is_Number and xf._prec == 1: # xf.is_Add will raise below + # is x == 0? + if x.equals(0): + return Float(0) + raise ValueError('not computing with precision') + xi = Integer(xf) + # use the last digit to select the value of xi + # nearest to x before rounding at the desired digit + sign = 1 if x > 0 else -1 + dif2 = sign*(xf - xi).n(extra) + if dif2 < 0: + raise NotImplementedError( + 'not expecting int(x) to round away from 0') + if dif2 > .5: + xi += sign # round away from 0 + elif dif2 == .5: + xi += sign if xi%2 else -sign # round toward even + # shift p to the new position + ip = p - shift + # let Python handle the int rounding then rescale + xr = round(xi.p, ip) + # restore scale + rv = Rational(xr, Pow(10, shift)) + # return Float or Integer + if rv.is_Integer: + if n is None: # the single-arg case + return rv + # use str or else it won't be a float + return Float(str(rv), dps) # keep same precision + else: + if not allow and rv > self: + allow += 1 + return Float(rv, allow) + + __round__ = round + + def _eval_derivative_matrix_lines(self, x): + from sympy.matrices.expressions.matexpr import _LeftRightArgs + return [_LeftRightArgs([S.One, S.One], higher=self._eval_derivative(x))] + + +class AtomicExpr(Atom, Expr): + """ + A parent class for object which are both atoms and Exprs. + + For example: Symbol, Number, Rational, Integer, ... + But not: Add, Mul, Pow, ... + """ + is_number = False + is_Atom = True + + __slots__ = () + + def _eval_derivative(self, s): + if self == s: + return S.One + return S.Zero + + def _eval_derivative_n_times(self, s, n): + from .containers import Tuple + from sympy.matrices.expressions.matexpr import MatrixExpr + from sympy.matrices.matrixbase import MatrixBase + if isinstance(s, (MatrixBase, Tuple, Iterable, MatrixExpr)): + return super()._eval_derivative_n_times(s, n) + from .relational import Eq + from sympy.functions.elementary.piecewise import Piecewise + if self == s: + return Piecewise((self, Eq(n, 0)), (1, Eq(n, 1)), (0, True)) + else: + return Piecewise((self, Eq(n, 0)), (0, True)) + + def _eval_is_polynomial(self, syms): + return True + + def _eval_is_rational_function(self, syms): + return self not in _illegal + + def _eval_is_meromorphic(self, x, a): + from sympy.calculus.accumulationbounds import AccumBounds + return (not self.is_Number or self.is_finite) and not isinstance(self, AccumBounds) + + def _eval_is_algebraic_expr(self, syms): + return True + + def _eval_nseries(self, x, n, logx, cdir=0): + return self + + @property + def expr_free_symbols(self): + sympy_deprecation_warning(""" + The expr_free_symbols property is deprecated. Use free_symbols to get + the free symbols of an expression. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-expr-free-symbols") + return {self} + + +def _mag(x): + r"""Return integer $i$ such that $0.1 \le x/10^i < 1$ + + Examples + ======== + + >>> from sympy.core.expr import _mag + >>> from sympy import Float + >>> _mag(Float(.1)) + 0 + >>> _mag(Float(.01)) + -1 + >>> _mag(Float(1234)) + 4 + """ + from math import log10, ceil, log + xpos = abs(x.n()) + if not xpos: + return S.Zero + try: + mag_first_dig = int(ceil(log10(xpos))) + except (ValueError, OverflowError): + mag_first_dig = int(ceil(Float(mpf_log(xpos._mpf_, 53))/log(10))) + # check that we aren't off by 1 + if (xpos/S(10)**mag_first_dig) >= 1: + assert 1 <= (xpos/S(10)**mag_first_dig) < 10 + mag_first_dig += 1 + return mag_first_dig + + +class UnevaluatedExpr(Expr): + """ + Expression that is not evaluated unless released. + + Examples + ======== + + >>> from sympy import UnevaluatedExpr + >>> from sympy.abc import x + >>> x*(1/x) + 1 + >>> x*UnevaluatedExpr(1/x) + x*1/x + + """ + + def __new__(cls, arg, **kwargs): + arg = _sympify(arg) + obj = Expr.__new__(cls, arg, **kwargs) + return obj + + def doit(self, **hints): + if hints.get("deep", True): + return self.args[0].doit(**hints) + else: + return self.args[0] + + + +def unchanged(func, *args): + """Return True if `func` applied to the `args` is unchanged. + Can be used instead of `assert foo == foo`. + + Examples + ======== + + >>> from sympy import Piecewise, cos, pi + >>> from sympy.core.expr import unchanged + >>> from sympy.abc import x + + >>> unchanged(cos, 1) # instead of assert cos(1) == cos(1) + True + + >>> unchanged(cos, pi) + False + + Comparison of args uses the builtin capabilities of the object's + arguments to test for equality so args can be defined loosely. Here, + the ExprCondPair arguments of Piecewise compare as equal to the + tuples that can be used to create the Piecewise: + + >>> unchanged(Piecewise, (x, x > 1), (0, True)) + True + """ + f = func(*args) + return f.func == func and f.args == args + + +class ExprBuilder: + def __init__(self, op, args=None, validator=None, check=True): + if not hasattr(op, "__call__"): + raise TypeError("op {} needs to be callable".format(op)) + self.op = op + if args is None: + self.args = [] + else: + self.args = args + self.validator = validator + if (validator is not None) and check: + self.validate() + + @staticmethod + def _build_args(args): + return [i.build() if isinstance(i, ExprBuilder) else i for i in args] + + def validate(self): + if self.validator is None: + return + args = self._build_args(self.args) + self.validator(*args) + + def build(self, check=True): + args = self._build_args(self.args) + if self.validator and check: + self.validator(*args) + return self.op(*args) + + def append_argument(self, arg, check=True): + self.args.append(arg) + if self.validator and check: + self.validate(*self.args) + + def __getitem__(self, item): + if item == 0: + return self.op + else: + return self.args[item-1] + + def __repr__(self): + return str(self.build()) + + def search_element(self, elem): + for i, arg in enumerate(self.args): + if isinstance(arg, ExprBuilder): + ret = arg.search_index(elem) + if ret is not None: + return (i,) + ret + elif id(arg) == id(elem): + return (i,) + return None + + +from .mul import Mul +from .add import Add +from .power import Pow +from .function import Function, _derivative_dispatch +from .mod import Mod +from .exprtools import factor_terms +from .numbers import Float, Integer, Rational, _illegal, int_valued diff --git a/phivenv/Lib/site-packages/sympy/core/exprtools.py b/phivenv/Lib/site-packages/sympy/core/exprtools.py new file mode 100644 index 0000000000000000000000000000000000000000..4868e4416a72e91dc12c9113d90b9c31c5e26011 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/exprtools.py @@ -0,0 +1,1573 @@ +"""Tools for manipulating of large commutative expressions. """ + +from __future__ import annotations + +from .add import Add +from .mul import Mul, _keep_coeff +from .power import Pow +from .basic import Basic +from .expr import Expr +from .function import expand_power_exp +from .sympify import sympify +from .numbers import Rational, Integer, Number, I, equal_valued +from .singleton import S +from .sorting import default_sort_key, ordered +from .symbol import Dummy +from .traversal import preorder_traversal +from .coreerrors import NonCommutativeExpression +from .containers import Tuple, Dict +from sympy.external.gmpy import SYMPY_INTS +from sympy.utilities.iterables import (common_prefix, common_suffix, + variations, iterable, is_sequence) + +from collections import defaultdict + + +_eps = Dummy(positive=True) + + +def _isnumber(i): + return isinstance(i, (SYMPY_INTS, float)) or i.is_Number + + +def _monotonic_sign(self): + """Return the value closest to 0 that ``self`` may have if all symbols + are signed and the result is uniformly the same sign for all values of symbols. + If a symbol is only signed but not known to be an + integer or the result is 0 then a symbol representative of the sign of self + will be returned. Otherwise, None is returned if a) the sign could be positive + or negative or b) self is not in one of the following forms: + + - L(x, y, ...) + A: a function linear in all symbols x, y, ... with an + additive constant; if A is zero then the function can be a monomial whose + sign is monotonic over the range of the variables, e.g. (x + 1)**3 if x is + nonnegative. + - A/L(x, y, ...) + B: the inverse of a function linear in all symbols x, y, ... + that does not have a sign change from positive to negative for any set + of values for the variables. + - M(x, y, ...) + A: a monomial M whose factors are all signed and a constant, A. + - A/M(x, y, ...) + B: the inverse of a monomial and constants A and B. + - P(x): a univariate polynomial + + Examples + ======== + + >>> from sympy.core.exprtools import _monotonic_sign as F + >>> from sympy import Dummy + >>> nn = Dummy(integer=True, nonnegative=True) + >>> p = Dummy(integer=True, positive=True) + >>> p2 = Dummy(integer=True, positive=True) + >>> F(nn + 1) + 1 + >>> F(p - 1) + _nneg + >>> F(nn*p + 1) + 1 + >>> F(p2*p + 1) + 2 + >>> F(nn - 1) # could be negative, zero or positive + """ + if not self.is_extended_real: + return + + if (-self).is_Symbol: + rv = _monotonic_sign(-self) + return rv if rv is None else -rv + + if not self.is_Add and self.as_numer_denom()[1].is_number: + s = self + if s.is_prime: + if s.is_odd: + return Integer(3) + else: + return Integer(2) + elif s.is_composite: + if s.is_odd: + return Integer(9) + else: + return Integer(4) + elif s.is_positive: + if s.is_even: + if s.is_prime is False: + return Integer(4) + else: + return Integer(2) + elif s.is_integer: + return S.One + else: + return _eps + elif s.is_extended_negative: + if s.is_even: + return Integer(-2) + elif s.is_integer: + return S.NegativeOne + else: + return -_eps + if s.is_zero or s.is_extended_nonpositive or s.is_extended_nonnegative: + return S.Zero + return None + + # univariate polynomial + free = self.free_symbols + if len(free) == 1: + if self.is_polynomial(): + from sympy.polys.polytools import real_roots + from sympy.polys.polyroots import roots + from sympy.polys.polyerrors import PolynomialError + x = free.pop() + x0 = _monotonic_sign(x) + if x0 in (_eps, -_eps): + x0 = S.Zero + if x0 is not None: + d = self.diff(x) + if d.is_number: + currentroots = [] + else: + try: + currentroots = real_roots(d) + except (PolynomialError, NotImplementedError): + currentroots = [r for r in roots(d, x) if r.is_extended_real] + y = self.subs(x, x0) + if x.is_nonnegative and all( + (r - x0).is_nonpositive for r in currentroots): + if y.is_nonnegative and d.is_positive: + if y: + return y if y.is_positive else Dummy('pos', positive=True) + else: + return Dummy('nneg', nonnegative=True) + if y.is_nonpositive and d.is_negative: + if y: + return y if y.is_negative else Dummy('neg', negative=True) + else: + return Dummy('npos', nonpositive=True) + elif x.is_nonpositive and all( + (r - x0).is_nonnegative for r in currentroots): + if y.is_nonnegative and d.is_negative: + if y: + return Dummy('pos', positive=True) + else: + return Dummy('nneg', nonnegative=True) + if y.is_nonpositive and d.is_positive: + if y: + return Dummy('neg', negative=True) + else: + return Dummy('npos', nonpositive=True) + else: + n, d = self.as_numer_denom() + den = None + if n.is_number: + den = _monotonic_sign(d) + elif not d.is_number: + if _monotonic_sign(n) is not None: + den = _monotonic_sign(d) + if den is not None and (den.is_positive or den.is_negative): + v = n*den + if v.is_positive: + return Dummy('pos', positive=True) + elif v.is_nonnegative: + return Dummy('nneg', nonnegative=True) + elif v.is_negative: + return Dummy('neg', negative=True) + elif v.is_nonpositive: + return Dummy('npos', nonpositive=True) + return None + + # multivariate + c, a = self.as_coeff_Add() + v = None + if not a.is_polynomial(): + # F/A or A/F where A is a number and F is a signed, rational monomial + n, d = a.as_numer_denom() + if not (n.is_number or d.is_number): + return + if ( + a.is_Mul or a.is_Pow) and \ + a.is_rational and \ + all(p.exp.is_Integer for p in a.atoms(Pow) if p.is_Pow) and \ + (a.is_positive or a.is_negative): + v = S.One + for ai in Mul.make_args(a): + if ai.is_number: + v *= ai + continue + reps = {} + for x in ai.free_symbols: + reps[x] = _monotonic_sign(x) + if reps[x] is None: + return + v *= ai.subs(reps) + elif c: + # signed linear expression + if not any(p for p in a.atoms(Pow) if not p.is_number) and (a.is_nonpositive or a.is_nonnegative): + free = list(a.free_symbols) + p = {} + for i in free: + v = _monotonic_sign(i) + if v is None: + return + p[i] = v or (_eps if i.is_nonnegative else -_eps) + v = a.xreplace(p) + if v is not None: + rv = v + c + if v.is_nonnegative and rv.is_positive: + return rv.subs(_eps, 0) + if v.is_nonpositive and rv.is_negative: + return rv.subs(_eps, 0) + + +def decompose_power(expr: Expr) -> tuple[Expr, int]: + """ + Decompose power into symbolic base and integer exponent. + + Examples + ======== + + >>> from sympy.core.exprtools import decompose_power + >>> from sympy.abc import x, y + >>> from sympy import exp + + >>> decompose_power(x) + (x, 1) + >>> decompose_power(x**2) + (x, 2) + >>> decompose_power(exp(2*y/3)) + (exp(y/3), 2) + + """ + base, exp = expr.as_base_exp() + + if exp.is_Number: + if exp.is_Rational: + if not exp.is_Integer: + base = Pow(base, Rational(1, exp.q)) # type: ignore + e = exp.p # type: ignore + else: + base, e = expr, 1 + else: + exp, tail = exp.as_coeff_Mul(rational=True) + + if exp is S.NegativeOne: + base, e = Pow(base, tail), -1 + elif exp is not S.One: + # todo: after dropping python 3.7 support, use overload and Literal + # in as_coeff_Mul to make exp Rational, and remove these 2 ignores + tail = _keep_coeff(Rational(1, exp.q), tail) # type: ignore + base, e = Pow(base, tail), exp.p # type: ignore + else: + base, e = expr, 1 + + return base, e + + +def decompose_power_rat(expr: Expr) -> tuple[Expr, Rational]: + """ + Decompose power into symbolic base and rational exponent; + if the exponent is not a Rational, then separate only the + integer coefficient. + + Examples + ======== + + >>> from sympy.core.exprtools import decompose_power_rat + >>> from sympy.abc import x + >>> from sympy import sqrt, exp + + >>> decompose_power_rat(sqrt(x)) + (x, 1/2) + >>> decompose_power_rat(exp(-3*x/2)) + (exp(x/2), -3) + + """ + base, exp = expr.as_base_exp() + if not exp.is_Rational: + base, exp_i = decompose_power(expr) + exp = Integer(exp_i) + return base, exp # type: ignore + + +class Factors: + """Efficient representation of ``f_1*f_2*...*f_n``.""" + + __slots__ = ('factors', 'gens') + + def __init__(self, factors=None): # Factors + """Initialize Factors from dict or expr. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x + >>> from sympy import I + >>> e = 2*x**3 + >>> Factors(e) + Factors({2: 1, x: 3}) + >>> Factors(e.as_powers_dict()) + Factors({2: 1, x: 3}) + >>> f = _ + >>> f.factors # underlying dictionary + {2: 1, x: 3} + >>> f.gens # base of each factor + frozenset({2, x}) + >>> Factors(0) + Factors({0: 1}) + >>> Factors(I) + Factors({I: 1}) + + Notes + ===== + + Although a dictionary can be passed, only minimal checking is + performed: powers of -1 and I are made canonical. + + """ + if isinstance(factors, (SYMPY_INTS, float)): + factors = S(factors) + if isinstance(factors, Factors): + factors = factors.factors.copy() + elif factors in (None, S.One): + factors = {} + elif factors is S.Zero or factors == 0: + factors = {S.Zero: S.One} + elif isinstance(factors, Number): + n = factors + factors = {} + if n < 0: + factors[S.NegativeOne] = S.One + n = -n + if n is not S.One: + if n.is_Float or n.is_Integer or n is S.Infinity: + factors[n] = S.One + elif n.is_Rational: + # since we're processing Numbers, the denominator is + # stored with a negative exponent; all other factors + # are left . + if n.p != 1: + factors[Integer(n.p)] = S.One + factors[Integer(n.q)] = S.NegativeOne + else: + raise ValueError('Expected Float|Rational|Integer, not %s' % n) + elif isinstance(factors, Basic) and not factors.args: + factors = {factors: S.One} + elif isinstance(factors, Expr): + c, nc = factors.args_cnc() + i = c.count(I) + for _ in range(i): + c.remove(I) + factors = dict(Mul._from_args(c).as_powers_dict()) + # Handle all rational Coefficients + for f in list(factors.keys()): + if isinstance(f, Rational) and not isinstance(f, Integer): + p, q = Integer(f.p), Integer(f.q) + factors[p] = (factors[p] if p in factors else S.Zero) + factors[f] + factors[q] = (factors[q] if q in factors else S.Zero) - factors[f] + factors.pop(f) + if i: + factors[I] = factors.get(I, S.Zero) + i + if nc: + factors[Mul(*nc, evaluate=False)] = S.One + else: + factors = factors.copy() # /!\ should be dict-like + + # tidy up -/+1 and I exponents if Rational + + handle = [k for k in factors if k is I or k in (-1, 1)] + if handle: + i1 = S.One + for k in handle: + if not _isnumber(factors[k]): + continue + i1 *= k**factors.pop(k) + if i1 is not S.One: + for a in i1.args if i1.is_Mul else [i1]: # at worst, -1.0*I*(-1)**e + if a is S.NegativeOne: + factors[a] = S.One + elif a is I: + factors[I] = S.One + elif a.is_Pow: + factors[a.base] = factors.get(a.base, S.Zero) + a.exp + elif equal_valued(a, 1): + factors[a] = S.One + elif equal_valued(a, -1): + factors[-a] = S.One + factors[S.NegativeOne] = S.One + else: + raise ValueError('unexpected factor in i1: %s' % a) + + self.factors = factors + keys = getattr(factors, 'keys', None) + if keys is None: + raise TypeError('expecting Expr or dictionary') + self.gens = frozenset(keys()) + + def __hash__(self): # Factors + keys = tuple(ordered(self.factors.keys())) + values = [self.factors[k] for k in keys] + return hash((keys, values)) + + def __repr__(self): # Factors + return "Factors({%s})" % ', '.join( + ['%s: %s' % (k, v) for k, v in ordered(self.factors.items())]) + + @property + def is_zero(self): # Factors + """ + >>> from sympy.core.exprtools import Factors + >>> Factors(0).is_zero + True + """ + f = self.factors + return len(f) == 1 and S.Zero in f + + @property + def is_one(self): # Factors + """ + >>> from sympy.core.exprtools import Factors + >>> Factors(1).is_one + True + """ + return not self.factors + + def as_expr(self): # Factors + """Return the underlying expression. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y + >>> Factors((x*y**2).as_powers_dict()).as_expr() + x*y**2 + + """ + + args = [] + for factor, exp in self.factors.items(): + if exp != 1: + if isinstance(exp, Integer): + b, e = factor.as_base_exp() + e = _keep_coeff(exp, e) + args.append(b**e) + else: + args.append(factor**exp) + else: + args.append(factor) + return Mul(*args) + + def mul(self, other): # Factors + """Return Factors of ``self * other``. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y, z + >>> a = Factors((x*y**2).as_powers_dict()) + >>> b = Factors((x*y/z).as_powers_dict()) + >>> a.mul(b) + Factors({x: 2, y: 3, z: -1}) + >>> a*b + Factors({x: 2, y: 3, z: -1}) + """ + if not isinstance(other, Factors): + other = Factors(other) + if any(f.is_zero for f in (self, other)): + return Factors(S.Zero) + factors = dict(self.factors) + + for factor, exp in other.factors.items(): + if factor in factors: + exp = factors[factor] + exp + + if not exp: + del factors[factor] + continue + + factors[factor] = exp + + return Factors(factors) + + def normal(self, other): + """Return ``self`` and ``other`` with ``gcd`` removed from each. + The only differences between this and method ``div`` is that this + is 1) optimized for the case when there are few factors in common and + 2) this does not raise an error if ``other`` is zero. + + See Also + ======== + div + + """ + if not isinstance(other, Factors): + other = Factors(other) + if other.is_zero: + return (Factors(), Factors(S.Zero)) + if self.is_zero: + return (Factors(S.Zero), Factors()) + + self_factors = dict(self.factors) + other_factors = dict(other.factors) + + for factor, self_exp in self.factors.items(): + try: + other_exp = other.factors[factor] + except KeyError: + continue + + exp = self_exp - other_exp + + if not exp: + del self_factors[factor] + del other_factors[factor] + elif _isnumber(exp): + if exp > 0: + self_factors[factor] = exp + del other_factors[factor] + else: + del self_factors[factor] + other_factors[factor] = -exp + else: + r = self_exp.extract_additively(other_exp) + if r is not None: + if r: + self_factors[factor] = r + del other_factors[factor] + else: # should be handled already + del self_factors[factor] + del other_factors[factor] + else: + sc, sa = self_exp.as_coeff_Add() + if sc: + oc, oa = other_exp.as_coeff_Add() + diff = sc - oc + if diff > 0: + self_factors[factor] -= oc + other_exp = oa + elif diff < 0: + self_factors[factor] -= sc + other_factors[factor] -= sc + other_exp = oa - diff + else: + self_factors[factor] = sa + other_exp = oa + if other_exp: + other_factors[factor] = other_exp + else: + del other_factors[factor] + + return Factors(self_factors), Factors(other_factors) + + def div(self, other): # Factors + """Return ``self`` and ``other`` with ``gcd`` removed from each. + This is optimized for the case when there are many factors in common. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y, z + >>> from sympy import S + + >>> a = Factors((x*y**2).as_powers_dict()) + >>> a.div(a) + (Factors({}), Factors({})) + >>> a.div(x*z) + (Factors({y: 2}), Factors({z: 1})) + + The ``/`` operator only gives ``quo``: + + >>> a/x + Factors({y: 2}) + + Factors treats its factors as though they are all in the numerator, so + if you violate this assumption the results will be correct but will + not strictly correspond to the numerator and denominator of the ratio: + + >>> a.div(x/z) + (Factors({y: 2}), Factors({z: -1})) + + Factors is also naive about bases: it does not attempt any denesting + of Rational-base terms, for example the following does not become + 2**(2*x)/2. + + >>> Factors(2**(2*x + 2)).div(S(8)) + (Factors({2: 2*x + 2}), Factors({8: 1})) + + factor_terms can clean up such Rational-bases powers: + + >>> from sympy import factor_terms + >>> n, d = Factors(2**(2*x + 2)).div(S(8)) + >>> n.as_expr()/d.as_expr() + 2**(2*x + 2)/8 + >>> factor_terms(_) + 2**(2*x)/2 + + """ + quo, rem = dict(self.factors), {} + + if not isinstance(other, Factors): + other = Factors(other) + if other.is_zero: + raise ZeroDivisionError + if self.is_zero: + return (Factors(S.Zero), Factors()) + + for factor, exp in other.factors.items(): + if factor in quo: + d = quo[factor] - exp + if _isnumber(d): + if d <= 0: + del quo[factor] + + if d >= 0: + if d: + quo[factor] = d + + continue + + exp = -d + + else: + r = quo[factor].extract_additively(exp) + if r is not None: + if r: + quo[factor] = r + else: # should be handled already + del quo[factor] + else: + other_exp = exp + sc, sa = quo[factor].as_coeff_Add() + if sc: + oc, oa = other_exp.as_coeff_Add() + diff = sc - oc + if diff > 0: + quo[factor] -= oc + other_exp = oa + elif diff < 0: + quo[factor] -= sc + other_exp = oa - diff + else: + quo[factor] = sa + other_exp = oa + if other_exp: + rem[factor] = other_exp + else: + assert factor not in rem + continue + + rem[factor] = exp + + return Factors(quo), Factors(rem) + + def quo(self, other): # Factors + """Return numerator Factor of ``self / other``. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y, z + >>> a = Factors((x*y**2).as_powers_dict()) + >>> b = Factors((x*y/z).as_powers_dict()) + >>> a.quo(b) # same as a/b + Factors({y: 1}) + """ + return self.div(other)[0] + + def rem(self, other): # Factors + """Return denominator Factors of ``self / other``. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y, z + >>> a = Factors((x*y**2).as_powers_dict()) + >>> b = Factors((x*y/z).as_powers_dict()) + >>> a.rem(b) + Factors({z: -1}) + >>> a.rem(a) + Factors({}) + """ + return self.div(other)[1] + + def pow(self, other): # Factors + """Return self raised to a non-negative integer power. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y + >>> a = Factors((x*y**2).as_powers_dict()) + >>> a**2 + Factors({x: 2, y: 4}) + + """ + if isinstance(other, Factors): + other = other.as_expr() + if other.is_Integer: + other = int(other) + if isinstance(other, SYMPY_INTS) and other >= 0: + factors = {} + + if other: + for factor, exp in self.factors.items(): + factors[factor] = exp*other + + return Factors(factors) + else: + raise ValueError("expected non-negative integer, got %s" % other) + + def gcd(self, other): # Factors + """Return Factors of ``gcd(self, other)``. The keys are + the intersection of factors with the minimum exponent for + each factor. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y, z + >>> a = Factors((x*y**2).as_powers_dict()) + >>> b = Factors((x*y/z).as_powers_dict()) + >>> a.gcd(b) + Factors({x: 1, y: 1}) + """ + if not isinstance(other, Factors): + other = Factors(other) + if other.is_zero: + return Factors(self.factors) + + factors = {} + + for factor, exp in self.factors.items(): + factor, exp = sympify(factor), sympify(exp) + if factor in other.factors: + lt = (exp - other.factors[factor]).is_negative + if lt == True: + factors[factor] = exp + elif lt == False: + factors[factor] = other.factors[factor] + + return Factors(factors) + + def lcm(self, other): # Factors + """Return Factors of ``lcm(self, other)`` which are + the union of factors with the maximum exponent for + each factor. + + Examples + ======== + + >>> from sympy.core.exprtools import Factors + >>> from sympy.abc import x, y, z + >>> a = Factors((x*y**2).as_powers_dict()) + >>> b = Factors((x*y/z).as_powers_dict()) + >>> a.lcm(b) + Factors({x: 1, y: 2, z: -1}) + """ + if not isinstance(other, Factors): + other = Factors(other) + if any(f.is_zero for f in (self, other)): + return Factors(S.Zero) + + factors = dict(self.factors) + + for factor, exp in other.factors.items(): + if factor in factors: + exp = max(exp, factors[factor]) + + factors[factor] = exp + + return Factors(factors) + + def __mul__(self, other): # Factors + return self.mul(other) + + def __divmod__(self, other): # Factors + return self.div(other) + + def __truediv__(self, other): # Factors + return self.quo(other) + + def __mod__(self, other): # Factors + return self.rem(other) + + def __pow__(self, other): # Factors + return self.pow(other) + + def __eq__(self, other): # Factors + if not isinstance(other, Factors): + other = Factors(other) + return self.factors == other.factors + + def __ne__(self, other): # Factors + return not self == other + + +class Term: + """Efficient representation of ``coeff*(numer/denom)``. """ + + __slots__ = ('coeff', 'numer', 'denom') + + def __init__(self, term, numer=None, denom=None): # Term + if numer is None and denom is None: + if not term.is_commutative: + raise NonCommutativeExpression( + 'commutative expression expected') + + coeff, factors = term.as_coeff_mul() + numer, denom = defaultdict(int), defaultdict(int) + + for factor in factors: + base, exp = decompose_power(factor) + + if base.is_Add: + cont, base = base.primitive() + coeff *= cont**exp + + if exp > 0: + numer[base] += exp + else: + denom[base] += -exp + + numer = Factors(numer) + denom = Factors(denom) + else: + coeff = term + + if numer is None: + numer = Factors() + + if denom is None: + denom = Factors() + + self.coeff = coeff + self.numer = numer + self.denom = denom + + def __hash__(self): # Term + return hash((self.coeff, self.numer, self.denom)) + + def __repr__(self): # Term + return "Term(%s, %s, %s)" % (self.coeff, self.numer, self.denom) + + def as_expr(self): # Term + return self.coeff*(self.numer.as_expr()/self.denom.as_expr()) + + def mul(self, other): # Term + coeff = self.coeff*other.coeff + numer = self.numer.mul(other.numer) + denom = self.denom.mul(other.denom) + + numer, denom = numer.normal(denom) + + return Term(coeff, numer, denom) + + def inv(self): # Term + return Term(1/self.coeff, self.denom, self.numer) + + def quo(self, other): # Term + return self.mul(other.inv()) + + def pow(self, other): # Term + if other < 0: + return self.inv().pow(-other) + else: + return Term(self.coeff ** other, + self.numer.pow(other), + self.denom.pow(other)) + + def gcd(self, other): # Term + return Term(self.coeff.gcd(other.coeff), + self.numer.gcd(other.numer), + self.denom.gcd(other.denom)) + + def lcm(self, other): # Term + return Term(self.coeff.lcm(other.coeff), + self.numer.lcm(other.numer), + self.denom.lcm(other.denom)) + + def __mul__(self, other): # Term + if isinstance(other, Term): + return self.mul(other) + else: + return NotImplemented + + def __truediv__(self, other): # Term + if isinstance(other, Term): + return self.quo(other) + else: + return NotImplemented + + def __pow__(self, other): # Term + if isinstance(other, SYMPY_INTS): + return self.pow(other) + else: + return NotImplemented + + def __eq__(self, other): # Term + return (self.coeff == other.coeff and + self.numer == other.numer and + self.denom == other.denom) + + def __ne__(self, other): # Term + return not self == other + + +def _gcd_terms(terms, isprimitive=False, fraction=True): + """Helper function for :func:`gcd_terms`. + + Parameters + ========== + + isprimitive : boolean, optional + If ``isprimitive`` is True then the call to primitive + for an Add will be skipped. This is useful when the + content has already been extracted. + + fraction : boolean, optional + If ``fraction`` is True then the expression will appear over a common + denominator, the lcm of all term denominators. + """ + + if isinstance(terms, Basic) and not isinstance(terms, Tuple): + terms = Add.make_args(terms) + + terms = list(map(Term, [t for t in terms if t])) + + # there is some simplification that may happen if we leave this + # here rather than duplicate it before the mapping of Term onto + # the terms + if len(terms) == 0: + return S.Zero, S.Zero, S.One + + if len(terms) == 1: + cont = terms[0].coeff + numer = terms[0].numer.as_expr() + denom = terms[0].denom.as_expr() + + else: + cont = terms[0] + for term in terms[1:]: + cont = cont.gcd(term) + + for i, term in enumerate(terms): + terms[i] = term.quo(cont) + + if fraction: + denom = terms[0].denom + + for term in terms[1:]: + denom = denom.lcm(term.denom) + + numers = [] + for term in terms: + numer = term.numer.mul(denom.quo(term.denom)) + numers.append(term.coeff*numer.as_expr()) + else: + numers = [t.as_expr() for t in terms] + denom = Term(S.One).numer + + cont = cont.as_expr() + numer = Add(*numers) + denom = denom.as_expr() + + if not isprimitive and numer.is_Add: + _cont, numer = numer.primitive() + cont *= _cont + + return cont, numer, denom + + +def gcd_terms(terms, isprimitive=False, clear=True, fraction=True): + """Compute the GCD of ``terms`` and put them together. + + Parameters + ========== + + terms : Expr + Can be an expression or a non-Basic sequence of expressions + which will be handled as though they are terms from a sum. + + isprimitive : bool, optional + If ``isprimitive`` is True the _gcd_terms will not run the primitive + method on the terms. + + clear : bool, optional + It controls the removal of integers from the denominator of an Add + expression. When True (default), all numerical denominator will be cleared; + when False the denominators will be cleared only if all terms had numerical + denominators other than 1. + + fraction : bool, optional + When True (default), will put the expression over a common + denominator. + + Examples + ======== + + >>> from sympy import gcd_terms + >>> from sympy.abc import x, y + + >>> gcd_terms((x + 1)**2*y + (x + 1)*y**2) + y*(x + 1)*(x + y + 1) + >>> gcd_terms(x/2 + 1) + (x + 2)/2 + >>> gcd_terms(x/2 + 1, clear=False) + x/2 + 1 + >>> gcd_terms(x/2 + y/2, clear=False) + (x + y)/2 + >>> gcd_terms(x/2 + 1/x) + (x**2 + 2)/(2*x) + >>> gcd_terms(x/2 + 1/x, fraction=False) + (x + 2/x)/2 + >>> gcd_terms(x/2 + 1/x, fraction=False, clear=False) + x/2 + 1/x + + >>> gcd_terms(x/2/y + 1/x/y) + (x**2 + 2)/(2*x*y) + >>> gcd_terms(x/2/y + 1/x/y, clear=False) + (x**2/2 + 1)/(x*y) + >>> gcd_terms(x/2/y + 1/x/y, clear=False, fraction=False) + (x/2 + 1/x)/y + + The ``clear`` flag was ignored in this case because the returned + expression was a rational expression, not a simple sum. + + See Also + ======== + + factor_terms, sympy.polys.polytools.terms_gcd + + """ + def mask(terms): + """replace nc portions of each term with a unique Dummy symbols + and return the replacements to restore them""" + args = [(a, []) if a.is_commutative else a.args_cnc() for a in terms] + reps = [] + for i, (c, nc) in enumerate(args): + if nc: + nc = Mul(*nc) + d = Dummy() + reps.append((d, nc)) + c.append(d) + args[i] = Mul(*c) + else: + args[i] = c + return args, dict(reps) + + isadd = isinstance(terms, Add) + addlike = isadd or not isinstance(terms, Basic) and \ + is_sequence(terms, include=set) and \ + not isinstance(terms, Dict) + + if addlike: + if isadd: # i.e. an Add + terms = list(terms.args) + else: + terms = sympify(terms) + terms, reps = mask(terms) + cont, numer, denom = _gcd_terms(terms, isprimitive, fraction) + numer = numer.xreplace(reps) + coeff, factors = cont.as_coeff_Mul() + if not clear: + c, _coeff = coeff.as_coeff_Mul() + if not c.is_Integer and not clear and numer.is_Add: + n, d = c.as_numer_denom() + _numer = numer/d + if any(a.as_coeff_Mul()[0].is_Integer + for a in _numer.args): + numer = _numer + coeff = n*_coeff + return _keep_coeff(coeff, factors*numer/denom, clear=clear) + + if not isinstance(terms, Basic): + return terms + + if terms.is_Atom: + return terms + + if terms.is_Mul: + c, args = terms.as_coeff_mul() + return _keep_coeff(c, Mul(*[gcd_terms(i, isprimitive, clear, fraction) + for i in args]), clear=clear) + + def handle(a): + # don't treat internal args like terms of an Add + if not isinstance(a, Expr): + if isinstance(a, Basic): + if not a.args: + return a + return a.func(*[handle(i) for i in a.args]) + return type(a)([handle(i) for i in a]) + return gcd_terms(a, isprimitive, clear, fraction) + + if isinstance(terms, Dict): + return Dict(*[(k, handle(v)) for k, v in terms.args]) + return terms.func(*[handle(i) for i in terms.args]) + + +def _factor_sum_int(expr, **kwargs): + """Return Sum or Integral object with factors that are not + in the wrt variables removed. In cases where there are additive + terms in the function of the object that are independent, the + object will be separated into two objects. + + Examples + ======== + + >>> from sympy import Sum, factor_terms + >>> from sympy.abc import x, y + >>> factor_terms(Sum(x + y, (x, 1, 3))) + y*Sum(1, (x, 1, 3)) + Sum(x, (x, 1, 3)) + >>> factor_terms(Sum(x*y, (x, 1, 3))) + y*Sum(x, (x, 1, 3)) + + Notes + ===== + + If a function in the summand or integrand is replaced + with a symbol, then this simplification should not be + done or else an incorrect result will be obtained when + the symbol is replaced with an expression that depends + on the variables of summation/integration: + + >>> eq = Sum(y, (x, 1, 3)) + >>> factor_terms(eq).subs(y, x).doit() + 3*x + >>> eq.subs(y, x).doit() + 6 + """ + result = expr.function + if result == 0: + return S.Zero + limits = expr.limits + + # get the wrt variables + wrt = {i.args[0] for i in limits} + + # factor out any common terms that are independent of wrt + f = factor_terms(result, **kwargs) + i, d = f.as_independent(*wrt) + if isinstance(f, Add): + return i * expr.func(1, *limits) + expr.func(d, *limits) + else: + return i * expr.func(d, *limits) + + +def factor_terms(expr: Expr | complex, radical=False, clear=False, fraction=False, sign=True) -> Expr: + """Remove common factors from terms in all arguments without + changing the underlying structure of the expr. No expansion or + simplification (and no processing of non-commutatives) is performed. + + Parameters + ========== + + radical: bool, optional + If radical=True then a radical common to all terms will be factored + out of any Add sub-expressions of the expr. + + clear : bool, optional + If clear=False (default) then coefficients will not be separated + from a single Add if they can be distributed to leave one or more + terms with integer coefficients. + + fraction : bool, optional + If fraction=True (default is False) then a common denominator will be + constructed for the expression. + + sign : bool, optional + If sign=True (default) then even if the only factor in common is a -1, + it will be factored out of the expression. + + Examples + ======== + + >>> from sympy import factor_terms, Symbol + >>> from sympy.abc import x, y + >>> factor_terms(x + x*(2 + 4*y)**3) + x*(8*(2*y + 1)**3 + 1) + >>> A = Symbol('A', commutative=False) + >>> factor_terms(x*A + x*A + x*y*A) + x*(y*A + 2*A) + + When ``clear`` is False, a rational will only be factored out of an + Add expression if all terms of the Add have coefficients that are + fractions: + + >>> factor_terms(x/2 + 1, clear=False) + x/2 + 1 + >>> factor_terms(x/2 + 1, clear=True) + (x + 2)/2 + + If a -1 is all that can be factored out, to *not* factor it out, the + flag ``sign`` must be False: + + >>> factor_terms(-x - y) + -(x + y) + >>> factor_terms(-x - y, sign=False) + -x - y + >>> factor_terms(-2*x - 2*y, sign=False) + -2*(x + y) + + See Also + ======== + + gcd_terms, sympy.polys.polytools.terms_gcd + + """ + def do(expr): + from sympy.concrete.summations import Sum + from sympy.integrals.integrals import Integral + is_iterable = iterable(expr) + + if not isinstance(expr, Basic) or expr.is_Atom: + if is_iterable: + return type(expr)([do(i) for i in expr]) + return expr + + if expr.is_Pow or expr.is_Function or \ + is_iterable or not hasattr(expr, 'args_cnc'): + args = expr.args + newargs = tuple([do(i) for i in args]) + if newargs == args: + return expr + return expr.func(*newargs) + + if isinstance(expr, (Sum, Integral)): + return _factor_sum_int(expr, + radical=radical, clear=clear, + fraction=fraction, sign=sign) + + cont, p = expr.as_content_primitive(radical=radical, clear=clear) + if p.is_Add: + list_args = [do(a) for a in Add.make_args(p)] + # get a common negative (if there) which gcd_terms does not remove + if not any(a.as_coeff_Mul()[0].extract_multiplicatively(-1) is None + for a in list_args): + cont = -cont + list_args = [-a for a in list_args] + # watch out for exp(-(x+2)) which gcd_terms will change to exp(-x-2) + special = {} + for i, a in enumerate(list_args): + b, e = a.as_base_exp() + if e.is_Mul and e != Mul(*e.args): + list_args[i] = Dummy() + special[list_args[i]] = a + # rebuild p not worrying about the order which gcd_terms will fix + p = Add._from_args(list_args) + p = gcd_terms(p, + isprimitive=True, + clear=clear, + fraction=fraction).xreplace(special) + elif p.args: + p = p.func( + *[do(a) for a in p.args]) + rv = _keep_coeff(cont, p, clear=clear, sign=sign) + return rv + expr2 = sympify(expr) + return do(expr2) + + +def _mask_nc(eq, name=None): + """ + Return ``eq`` with non-commutative objects replaced with Dummy + symbols. A dictionary that can be used to restore the original + values is returned: if it is None, the expression is noncommutative + and cannot be made commutative. The third value returned is a list + of any non-commutative symbols that appear in the returned equation. + + Explanation + =========== + + All non-commutative objects other than Symbols are replaced with + a non-commutative Symbol. Identical objects will be identified + by identical symbols. + + If there is only 1 non-commutative object in an expression it will + be replaced with a commutative symbol. Otherwise, the non-commutative + entities are retained and the calling routine should handle + replacements in this case since some care must be taken to keep + track of the ordering of symbols when they occur within Muls. + + Parameters + ========== + + name : str + ``name``, if given, is the name that will be used with numbered Dummy + variables that will replace the non-commutative objects and is mainly + used for doctesting purposes. + + Examples + ======== + + >>> from sympy.physics.secondquant import Commutator, NO, F, Fd + >>> from sympy import symbols + >>> from sympy.core.exprtools import _mask_nc + >>> from sympy.abc import x, y + >>> A, B, C = symbols('A,B,C', commutative=False) + + One nc-symbol: + + >>> _mask_nc(A**2 - x**2, 'd') + (_d0**2 - x**2, {_d0: A}, []) + + Multiple nc-symbols: + + >>> _mask_nc(A**2 - B**2, 'd') + (A**2 - B**2, {}, [A, B]) + + An nc-object with nc-symbols but no others outside of it: + + >>> _mask_nc(1 + x*Commutator(A, B), 'd') + (_d0*x + 1, {_d0: Commutator(A, B)}, []) + >>> _mask_nc(NO(Fd(x)*F(y)), 'd') + (_d0, {_d0: NO(CreateFermion(x)*AnnihilateFermion(y))}, []) + + Multiple nc-objects: + + >>> eq = x*Commutator(A, B) + x*Commutator(A, C)*Commutator(A, B) + >>> _mask_nc(eq, 'd') + (x*_d0 + x*_d1*_d0, {_d0: Commutator(A, B), _d1: Commutator(A, C)}, [_d0, _d1]) + + Multiple nc-objects and nc-symbols: + + >>> eq = A*Commutator(A, B) + B*Commutator(A, C) + >>> _mask_nc(eq, 'd') + (A*_d0 + B*_d1, {_d0: Commutator(A, B), _d1: Commutator(A, C)}, [_d0, _d1, A, B]) + + """ + name = name or 'mask' + # Make Dummy() append sequential numbers to the name + + def numbered_names(): + i = 0 + while True: + yield name + str(i) + i += 1 + + names = numbered_names() + + def Dummy(*args, **kwargs): + from .symbol import Dummy + return Dummy(next(names), *args, **kwargs) + + expr = eq + if expr.is_commutative: + return eq, {}, [] + + # identify nc-objects; symbols and other + rep = [] + nc_obj = set() + nc_syms = set() + pot = preorder_traversal(expr, keys=default_sort_key) + for a in pot: + if any(a == r[0] for r in rep): + pot.skip() + elif not a.is_commutative: + if a.is_symbol: + nc_syms.add(a) + pot.skip() + elif not (a.is_Add or a.is_Mul or a.is_Pow): + nc_obj.add(a) + pot.skip() + + # If there is only one nc symbol or object, it can be factored regularly + # but polys is going to complain, so replace it with a Dummy. + if len(nc_obj) == 1 and not nc_syms: + rep.append((nc_obj.pop(), Dummy())) + elif len(nc_syms) == 1 and not nc_obj: + rep.append((nc_syms.pop(), Dummy())) + + # Any remaining nc-objects will be replaced with an nc-Dummy and + # identified as an nc-Symbol to watch out for + nc_obj = sorted(nc_obj, key=default_sort_key) + for n in nc_obj: + nc = Dummy(commutative=False) + rep.append((n, nc)) + nc_syms.add(nc) + expr = expr.subs(rep) + + nc_syms = list(nc_syms) + nc_syms.sort(key=default_sort_key) + return expr, {v: k for k, v in rep}, nc_syms + + +def factor_nc(expr): + """Return the factored form of ``expr`` while handling non-commutative + expressions. + + Examples + ======== + + >>> from sympy import factor_nc, Symbol + >>> from sympy.abc import x + >>> A = Symbol('A', commutative=False) + >>> B = Symbol('B', commutative=False) + >>> factor_nc((x**2 + 2*A*x + A**2).expand()) + (x + A)**2 + >>> factor_nc(((x + A)*(x + B)).expand()) + (x + A)*(x + B) + """ + expr = sympify(expr) + if not isinstance(expr, Expr) or not expr.args: + return expr + if not expr.is_Add: + return expr.func(*[factor_nc(a) for a in expr.args]) + expr = expr.func(*[expand_power_exp(i) for i in expr.args]) + + from sympy.polys.polytools import gcd, factor + expr, rep, nc_symbols = _mask_nc(expr) + + if rep: + return factor(expr).subs(rep) + else: + args = [a.args_cnc() for a in Add.make_args(expr)] + c = g = l = r = S.One + hit = False + # find any commutative gcd term + for i, a in enumerate(args): + if i == 0: + c = Mul._from_args(a[0]) + elif a[0]: + c = gcd(c, Mul._from_args(a[0])) + else: + c = S.One + if c is not S.One: + hit = True + c, g = c.as_coeff_Mul() + if g is not S.One: + for i, (cc, _) in enumerate(args): + cc = list(Mul.make_args(Mul._from_args(list(cc))/g)) + args[i][0] = cc + for i, (cc, _) in enumerate(args): + if cc: + cc[0] = cc[0]/c + else: + cc = [1/c] + args[i][0] = cc + # find any noncommutative common prefix + for i, a in enumerate(args): + if i == 0: + n = a[1][:] + else: + n = common_prefix(n, a[1]) + if not n: + # is there a power that can be extracted? + if not args[0][1]: + break + b, e = args[0][1][0].as_base_exp() + ok = False + if e.is_Integer: + for t in args: + if not t[1]: + break + bt, et = t[1][0].as_base_exp() + if et.is_Integer and bt == b: + e = min(e, et) + else: + break + else: + ok = hit = True + l = b**e + il = b**-e + for _ in args: + _[1][0] = il*_[1][0] + break + if not ok: + break + else: + hit = True + lenn = len(n) + l = Mul(*n) + for _ in args: + _[1] = _[1][lenn:] + # find any noncommutative common suffix + for i, a in enumerate(args): + if i == 0: + n = a[1][:] + else: + n = common_suffix(n, a[1]) + if not n: + # is there a power that can be extracted? + if not args[0][1]: + break + b, e = args[0][1][-1].as_base_exp() + ok = False + if e.is_Integer: + for t in args: + if not t[1]: + break + bt, et = t[1][-1].as_base_exp() + if et.is_Integer and bt == b: + e = min(e, et) + else: + break + else: + ok = hit = True + r = b**e + il = b**-e + for _ in args: + _[1][-1] = _[1][-1]*il + break + if not ok: + break + else: + hit = True + lenn = len(n) + r = Mul(*n) + for _ in args: + _[1] = _[1][:len(_[1]) - lenn] + if hit: + mid = Add(*[Mul(*cc)*Mul(*nc) for cc, nc in args]) + else: + mid = expr + + from sympy.simplify.powsimp import powsimp + + # sort the symbols so the Dummys would appear in the same + # order as the original symbols, otherwise you may introduce + # a factor of -1, e.g. A**2 - B**2) -- {A:y, B:x} --> y**2 - x**2 + # and the former factors into two terms, (A - B)*(A + B) while the + # latter factors into 3 terms, (-1)*(x - y)*(x + y) + rep1 = [(n, Dummy()) for n in sorted(nc_symbols, key=default_sort_key)] + unrep1 = [(v, k) for k, v in rep1] + unrep1.reverse() + new_mid, r2, _ = _mask_nc(mid.subs(rep1)) + new_mid = powsimp(factor(new_mid)) + + new_mid = new_mid.subs(r2).subs(unrep1) + + if new_mid.is_Pow: + return _keep_coeff(c, g*l*new_mid*r) + + if new_mid.is_Mul: + def _pemexpand(expr): + "Expand with the minimal set of hints necessary to check the result." + return expr.expand(deep=True, mul=True, power_exp=True, + power_base=False, basic=False, multinomial=True, log=False) + # XXX TODO there should be a way to inspect what order the terms + # must be in and just select the plausible ordering without + # checking permutations + cfac = [] + ncfac = [] + for f in new_mid.args: + if f.is_commutative: + cfac.append(f) + else: + b, e = f.as_base_exp() + if e.is_Integer: + ncfac.extend([b]*e) + else: + ncfac.append(f) + pre_mid = g*Mul(*cfac)*l + target = _pemexpand(expr/c) + for s in variations(ncfac, len(ncfac)): + ok = pre_mid*Mul(*s)*r + if _pemexpand(ok) == target: + return _keep_coeff(c, ok) + + # mid was an Add that didn't factor successfully + return _keep_coeff(c, g*l*mid*r) diff --git a/phivenv/Lib/site-packages/sympy/core/facts.py b/phivenv/Lib/site-packages/sympy/core/facts.py new file mode 100644 index 0000000000000000000000000000000000000000..0b98d9b14bbac661d3c0fd1d1fd87977a792fb74 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/facts.py @@ -0,0 +1,634 @@ +r"""This is rule-based deduction system for SymPy + +The whole thing is split into two parts + + - rules compilation and preparation of tables + - runtime inference + +For rule-based inference engines, the classical work is RETE algorithm [1], +[2] Although we are not implementing it in full (or even significantly) +it's still worth a read to understand the underlying ideas. + +In short, every rule in a system of rules is one of two forms: + + - atom -> ... (alpha rule) + - And(atom1, atom2, ...) -> ... (beta rule) + + +The major complexity is in efficient beta-rules processing and usually for an +expert system a lot of effort goes into code that operates on beta-rules. + + +Here we take minimalistic approach to get something usable first. + + - (preparation) of alpha- and beta- networks, everything except + - (runtime) FactRules.deduce_all_facts + + _____________________________________ + ( Kirr: I've never thought that doing ) + ( logic stuff is that difficult... ) + ------------------------------------- + o ^__^ + o (oo)\_______ + (__)\ )\/\ + ||----w | + || || + + +Some references on the topic +---------------------------- + +[1] https://en.wikipedia.org/wiki/Rete_algorithm +[2] http://reports-archive.adm.cs.cmu.edu/anon/1995/CMU-CS-95-113.pdf + +https://en.wikipedia.org/wiki/Propositional_formula +https://en.wikipedia.org/wiki/Inference_rule +https://en.wikipedia.org/wiki/List_of_rules_of_inference +""" + +from collections import defaultdict +from typing import Iterator + +from .logic import Logic, And, Or, Not + + +def _base_fact(atom): + """Return the literal fact of an atom. + + Effectively, this merely strips the Not around a fact. + """ + if isinstance(atom, Not): + return atom.arg + else: + return atom + + +def _as_pair(atom): + if isinstance(atom, Not): + return (atom.arg, False) + else: + return (atom, True) + +# XXX this prepares forward-chaining rules for alpha-network + + +def transitive_closure(implications): + """ + Computes the transitive closure of a list of implications + + Uses Warshall's algorithm, as described at + http://www.cs.hope.edu/~cusack/Notes/Notes/DiscreteMath/Warshall.pdf. + """ + full_implications = set(implications) + literals = set().union(*map(set, full_implications)) + + for k in literals: + for i in literals: + if (i, k) in full_implications: + for j in literals: + if (k, j) in full_implications: + full_implications.add((i, j)) + + return full_implications + + +def deduce_alpha_implications(implications): + """deduce all implications + + Description by example + ---------------------- + + given set of logic rules: + + a -> b + b -> c + + we deduce all possible rules: + + a -> b, c + b -> c + + + implications: [] of (a,b) + return: {} of a -> set([b, c, ...]) + """ + implications = implications + [(Not(j), Not(i)) for (i, j) in implications] + res = defaultdict(set) + full_implications = transitive_closure(implications) + for a, b in full_implications: + if a == b: + continue # skip a->a cyclic input + + res[a].add(b) + + # Clean up tautologies and check consistency + for a, impl in res.items(): + impl.discard(a) + na = Not(a) + if na in impl: + raise ValueError( + 'implications are inconsistent: %s -> %s %s' % (a, na, impl)) + + return res + + +def apply_beta_to_alpha_route(alpha_implications, beta_rules): + """apply additional beta-rules (And conditions) to already-built + alpha implication tables + + TODO: write about + + - static extension of alpha-chains + - attaching refs to beta-nodes to alpha chains + + + e.g. + + alpha_implications: + + a -> [b, !c, d] + b -> [d] + ... + + + beta_rules: + + &(b,d) -> e + + + then we'll extend a's rule to the following + + a -> [b, !c, d, e] + """ + x_impl = {} + for x in alpha_implications.keys(): + x_impl[x] = (set(alpha_implications[x]), []) + for bcond, bimpl in beta_rules: + for bk in bcond.args: + if bk in x_impl: + continue + x_impl[bk] = (set(), []) + + # static extensions to alpha rules: + # A: x -> a,b B: &(a,b) -> c ==> A: x -> a,b,c + seen_static_extension = True + while seen_static_extension: + seen_static_extension = False + + for bcond, bimpl in beta_rules: + if not isinstance(bcond, And): + raise TypeError("Cond is not And") + bargs = set(bcond.args) + for x, (ximpls, bb) in x_impl.items(): + x_all = ximpls | {x} + # A: ... -> a B: &(...) -> a is non-informative + if bimpl not in x_all and bargs.issubset(x_all): + ximpls.add(bimpl) + + # we introduced new implication - now we have to restore + # completeness of the whole set. + bimpl_impl = x_impl.get(bimpl) + if bimpl_impl is not None: + ximpls |= bimpl_impl[0] + seen_static_extension = True + + # attach beta-nodes which can be possibly triggered by an alpha-chain + for bidx, (bcond, bimpl) in enumerate(beta_rules): + bargs = set(bcond.args) + for x, (ximpls, bb) in x_impl.items(): + x_all = ximpls | {x} + # A: ... -> a B: &(...) -> a (non-informative) + if bimpl in x_all: + continue + # A: x -> a... B: &(!a,...) -> ... (will never trigger) + # A: x -> a... B: &(...) -> !a (will never trigger) + if any(Not(xi) in bargs or Not(xi) == bimpl for xi in x_all): + continue + + if bargs & x_all: + bb.append(bidx) + + return x_impl + + +def rules_2prereq(rules): + """build prerequisites table from rules + + Description by example + ---------------------- + + given set of logic rules: + + a -> b, c + b -> c + + we build prerequisites (from what points something can be deduced): + + b <- a + c <- a, b + + rules: {} of a -> [b, c, ...] + return: {} of c <- [a, b, ...] + + Note however, that this prerequisites may be *not* enough to prove a + fact. An example is 'a -> b' rule, where prereq(a) is b, and prereq(b) + is a. That's because a=T -> b=T, and b=F -> a=F, but a=F -> b=? + """ + prereq = defaultdict(set) + for (a, _), impl in rules.items(): + if isinstance(a, Not): + a = a.args[0] + for (i, _) in impl: + if isinstance(i, Not): + i = i.args[0] + prereq[i].add(a) + return prereq + +################ +# RULES PROVER # +################ + + +class TautologyDetected(Exception): + """(internal) Prover uses it for reporting detected tautology""" + pass + + +class Prover: + """ai - prover of logic rules + + given a set of initial rules, Prover tries to prove all possible rules + which follow from given premises. + + As a result proved_rules are always either in one of two forms: alpha or + beta: + + Alpha rules + ----------- + + This are rules of the form:: + + a -> b & c & d & ... + + + Beta rules + ---------- + + This are rules of the form:: + + &(a,b,...) -> c & d & ... + + + i.e. beta rules are join conditions that say that something follows when + *several* facts are true at the same time. + """ + + def __init__(self): + self.proved_rules = [] + self._rules_seen = set() + + def split_alpha_beta(self): + """split proved rules into alpha and beta chains""" + rules_alpha = [] # a -> b + rules_beta = [] # &(...) -> b + for a, b in self.proved_rules: + if isinstance(a, And): + rules_beta.append((a, b)) + else: + rules_alpha.append((a, b)) + return rules_alpha, rules_beta + + @property + def rules_alpha(self): + return self.split_alpha_beta()[0] + + @property + def rules_beta(self): + return self.split_alpha_beta()[1] + + def process_rule(self, a, b): + """process a -> b rule""" # TODO write more? + if (not a) or isinstance(b, bool): + return + if isinstance(a, bool): + return + if (a, b) in self._rules_seen: + return + else: + self._rules_seen.add((a, b)) + + # this is the core of processing + try: + self._process_rule(a, b) + except TautologyDetected: + pass + + def _process_rule(self, a, b): + # right part first + + # a -> b & c --> a -> b ; a -> c + # (?) FIXME this is only correct when b & c != null ! + + if isinstance(b, And): + sorted_bargs = sorted(b.args, key=str) + for barg in sorted_bargs: + self.process_rule(a, barg) + + # a -> b | c --> !b & !c -> !a + # --> a & !b -> c + # --> a & !c -> b + elif isinstance(b, Or): + sorted_bargs = sorted(b.args, key=str) + # detect tautology first + if not isinstance(a, Logic): # Atom + # tautology: a -> a|c|... + if a in sorted_bargs: + raise TautologyDetected(a, b, 'a -> a|c|...') + self.process_rule(And(*[Not(barg) for barg in b.args]), Not(a)) + + for bidx in range(len(sorted_bargs)): + barg = sorted_bargs[bidx] + brest = sorted_bargs[:bidx] + sorted_bargs[bidx + 1:] + self.process_rule(And(a, Not(barg)), Or(*brest)) + + # left part + + # a & b -> c --> IRREDUCIBLE CASE -- WE STORE IT AS IS + # (this will be the basis of beta-network) + elif isinstance(a, And): + sorted_aargs = sorted(a.args, key=str) + if b in sorted_aargs: + raise TautologyDetected(a, b, 'a & b -> a') + self.proved_rules.append((a, b)) + # XXX NOTE at present we ignore !c -> !a | !b + + elif isinstance(a, Or): + sorted_aargs = sorted(a.args, key=str) + if b in sorted_aargs: + raise TautologyDetected(a, b, 'a | b -> a') + for aarg in sorted_aargs: + self.process_rule(aarg, b) + + else: + # both `a` and `b` are atoms + self.proved_rules.append((a, b)) # a -> b + self.proved_rules.append((Not(b), Not(a))) # !b -> !a + +######################################## + + +class FactRules: + """Rules that describe how to deduce facts in logic space + + When defined, these rules allow implications to quickly be determined + for a set of facts. For this precomputed deduction tables are used. + see `deduce_all_facts` (forward-chaining) + + Also it is possible to gather prerequisites for a fact, which is tried + to be proven. (backward-chaining) + + + Definition Syntax + ----------------- + + a -> b -- a=T -> b=T (and automatically b=F -> a=F) + a -> !b -- a=T -> b=F + a == b -- a -> b & b -> a + a -> b & c -- a=T -> b=T & c=T + # TODO b | c + + + Internals + --------- + + .full_implications[k, v]: all the implications of fact k=v + .beta_triggers[k, v]: beta rules that might be triggered when k=v + .prereq -- {} k <- [] of k's prerequisites + + .defined_facts -- set of defined fact names + """ + + def __init__(self, rules): + """Compile rules into internal lookup tables""" + + if isinstance(rules, str): + rules = rules.splitlines() + + # --- parse and process rules --- + P = Prover() + + for rule in rules: + # XXX `a` is hardcoded to be always atom + a, op, b = rule.split(None, 2) + + a = Logic.fromstring(a) + b = Logic.fromstring(b) + + if op == '->': + P.process_rule(a, b) + elif op == '==': + P.process_rule(a, b) + P.process_rule(b, a) + else: + raise ValueError('unknown op %r' % op) + + # --- build deduction networks --- + self.beta_rules = [] + for bcond, bimpl in P.rules_beta: + self.beta_rules.append( + ({_as_pair(a) for a in bcond.args}, _as_pair(bimpl))) + + # deduce alpha implications + impl_a = deduce_alpha_implications(P.rules_alpha) + + # now: + # - apply beta rules to alpha chains (static extension), and + # - further associate beta rules to alpha chain (for inference + # at runtime) + impl_ab = apply_beta_to_alpha_route(impl_a, P.rules_beta) + + # extract defined fact names + self.defined_facts = {_base_fact(k) for k in impl_ab.keys()} + + # build rels (forward chains) + full_implications = defaultdict(set) + beta_triggers = defaultdict(set) + for k, (impl, betaidxs) in impl_ab.items(): + full_implications[_as_pair(k)] = {_as_pair(i) for i in impl} + beta_triggers[_as_pair(k)] = betaidxs + + self.full_implications = full_implications + self.beta_triggers = beta_triggers + + # build prereq (backward chains) + prereq = defaultdict(set) + rel_prereq = rules_2prereq(full_implications) + for k, pitems in rel_prereq.items(): + prereq[k] |= pitems + self.prereq = prereq + + def _to_python(self) -> str: + """ Generate a string with plain python representation of the instance """ + return '\n'.join(self.print_rules()) + + @classmethod + def _from_python(cls, data : dict): + """ Generate an instance from the plain python representation """ + self = cls('') + for key in ['full_implications', 'beta_triggers', 'prereq']: + d=defaultdict(set) + d.update(data[key]) + setattr(self, key, d) + self.beta_rules = data['beta_rules'] + self.defined_facts = set(data['defined_facts']) + + return self + + def _defined_facts_lines(self): + yield 'defined_facts = [' + for fact in sorted(self.defined_facts): + yield f' {fact!r},' + yield '] # defined_facts' + + def _full_implications_lines(self): + yield 'full_implications = dict( [' + for fact in sorted(self.defined_facts): + for value in (True, False): + yield f' # Implications of {fact} = {value}:' + yield f' (({fact!r}, {value!r}), set( (' + implications = self.full_implications[(fact, value)] + for implied in sorted(implications): + yield f' {implied!r},' + yield ' ) ),' + yield ' ),' + yield ' ] ) # full_implications' + + def _prereq_lines(self): + yield 'prereq = {' + yield '' + for fact in sorted(self.prereq): + yield f' # facts that could determine the value of {fact}' + yield f' {fact!r}: {{' + for pfact in sorted(self.prereq[fact]): + yield f' {pfact!r},' + yield ' },' + yield '' + yield '} # prereq' + + def _beta_rules_lines(self): + reverse_implications = defaultdict(list) + for n, (pre, implied) in enumerate(self.beta_rules): + reverse_implications[implied].append((pre, n)) + + yield '# Note: the order of the beta rules is used in the beta_triggers' + yield 'beta_rules = [' + yield '' + m = 0 + indices = {} + for implied in sorted(reverse_implications): + fact, value = implied + yield f' # Rules implying {fact} = {value}' + for pre, n in reverse_implications[implied]: + indices[n] = m + m += 1 + setstr = ", ".join(map(str, sorted(pre))) + yield f' ({{{setstr}}},' + yield f' {implied!r}),' + yield '' + yield '] # beta_rules' + + yield 'beta_triggers = {' + for query in sorted(self.beta_triggers): + fact, value = query + triggers = [indices[n] for n in self.beta_triggers[query]] + yield f' {query!r}: {triggers!r},' + yield '} # beta_triggers' + + def print_rules(self) -> Iterator[str]: + """ Returns a generator with lines to represent the facts and rules """ + yield from self._defined_facts_lines() + yield '' + yield '' + yield from self._full_implications_lines() + yield '' + yield '' + yield from self._prereq_lines() + yield '' + yield '' + yield from self._beta_rules_lines() + yield '' + yield '' + yield "generated_assumptions = {'defined_facts': defined_facts, 'full_implications': full_implications," + yield " 'prereq': prereq, 'beta_rules': beta_rules, 'beta_triggers': beta_triggers}" + + +class InconsistentAssumptions(ValueError): + def __str__(self): + kb, fact, value = self.args + return "%s, %s=%s" % (kb, fact, value) + + +class FactKB(dict): + """ + A simple propositional knowledge base relying on compiled inference rules. + """ + def __str__(self): + return '{\n%s}' % ',\n'.join( + ["\t%s: %s" % i for i in sorted(self.items())]) + + def __init__(self, rules): + self.rules = rules + + def _tell(self, k, v): + """Add fact k=v to the knowledge base. + + Returns True if the KB has actually been updated, False otherwise. + """ + if k in self and self[k] is not None: + if self[k] == v: + return False + else: + raise InconsistentAssumptions(self, k, v) + else: + self[k] = v + return True + + # ********************************************* + # * This is the workhorse, so keep it *fast*. * + # ********************************************* + def deduce_all_facts(self, facts): + """ + Update the KB with all the implications of a list of facts. + + Facts can be specified as a dictionary or as a list of (key, value) + pairs. + """ + # keep frequently used attributes locally, so we'll avoid extra + # attribute access overhead + full_implications = self.rules.full_implications + beta_triggers = self.rules.beta_triggers + beta_rules = self.rules.beta_rules + + if isinstance(facts, dict): + facts = facts.items() + + while facts: + beta_maytrigger = set() + + # --- alpha chains --- + for k, v in facts: + if not self._tell(k, v) or v is None: + continue + + # lookup routing tables + for key, value in full_implications[k, v]: + self._tell(key, value) + + beta_maytrigger.update(beta_triggers[k, v]) + + # --- beta chains --- + facts = [] + for bidx in beta_maytrigger: + bcond, bimpl = beta_rules[bidx] + if all(self.get(k) is v for k, v in bcond): + facts.append(bimpl) diff --git a/phivenv/Lib/site-packages/sympy/core/function.py b/phivenv/Lib/site-packages/sympy/core/function.py new file mode 100644 index 0000000000000000000000000000000000000000..ac850845e0bb2aaf9b535635567d6e2629527ad7 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/function.py @@ -0,0 +1,3423 @@ +""" +There are three types of functions implemented in SymPy: + + 1) defined functions (in the sense that they can be evaluated) like + exp or sin; they have a name and a body: + f = exp + 2) undefined function which have a name but no body. Undefined + functions can be defined using a Function class as follows: + f = Function('f') + (the result will be a Function instance) + 3) anonymous function (or lambda function) which have a body (defined + with dummy variables) but have no name: + f = Lambda(x, exp(x)*x) + f = Lambda((x, y), exp(x)*y) + The fourth type of functions are composites, like (sin + cos)(x); these work in + SymPy core, but are not yet part of SymPy. + + Examples + ======== + + >>> import sympy + >>> f = sympy.Function("f") + >>> from sympy.abc import x + >>> f(x) + f(x) + >>> print(sympy.srepr(f(x).func)) + Function('f') + >>> f(x).args + (x,) + +""" + +from __future__ import annotations + +from typing import Any +from collections.abc import Iterable +import copyreg + +from .add import Add +from .basic import Basic, _atomic +from .cache import cacheit +from .containers import Tuple, Dict +from .decorators import _sympifyit +from .evalf import pure_complex +from .expr import Expr, AtomicExpr +from .logic import fuzzy_and, fuzzy_or, fuzzy_not, FuzzyBool +from .mul import Mul +from .numbers import Rational, Float, Integer +from .operations import LatticeOp +from .parameters import global_parameters +from .rules import Transform +from .singleton import S +from .sympify import sympify, _sympify + +from .sorting import default_sort_key, ordered +from sympy.utilities.exceptions import (sympy_deprecation_warning, + SymPyDeprecationWarning, ignore_warnings) +from sympy.utilities.iterables import (has_dups, sift, iterable, + is_sequence, uniq, topological_sort) +from sympy.utilities.lambdify import MPMATH_TRANSLATIONS +from sympy.utilities.misc import as_int, filldedent, func_name + +import mpmath +from mpmath.libmp.libmpf import prec_to_dps + +import inspect +from collections import Counter + +def _coeff_isneg(a): + """Return True if the leading Number is negative. + + Examples + ======== + + >>> from sympy.core.function import _coeff_isneg + >>> from sympy import S, Symbol, oo, pi + >>> _coeff_isneg(-3*pi) + True + >>> _coeff_isneg(S(3)) + False + >>> _coeff_isneg(-oo) + True + >>> _coeff_isneg(Symbol('n', negative=True)) # coeff is 1 + False + + For matrix expressions: + + >>> from sympy import MatrixSymbol, sqrt + >>> A = MatrixSymbol("A", 3, 3) + >>> _coeff_isneg(-sqrt(2)*A) + True + >>> _coeff_isneg(sqrt(2)*A) + False + """ + + if a.is_MatMul: + a = a.args[0] + if a.is_Mul: + a = a.args[0] + return a.is_Number and a.is_extended_negative + + +class PoleError(Exception): + pass + + +class ArgumentIndexError(ValueError): + def __str__(self): + return ("Invalid operation with argument number %s for Function %s" % + (self.args[1], self.args[0])) + + +class BadSignatureError(TypeError): + '''Raised when a Lambda is created with an invalid signature''' + pass + + +class BadArgumentsError(TypeError): + '''Raised when a Lambda is called with an incorrect number of arguments''' + pass + + +# Python 3 version that does not raise a Deprecation warning +def arity(cls): + """Return the arity of the function if it is known, else None. + + Explanation + =========== + + When default values are specified for some arguments, they are + optional and the arity is reported as a tuple of possible values. + + Examples + ======== + + >>> from sympy import arity, log + >>> arity(lambda x: x) + 1 + >>> arity(log) + (1, 2) + >>> arity(lambda *x: sum(x)) is None + True + """ + eval_ = getattr(cls, 'eval', cls) + + parameters = inspect.signature(eval_).parameters.items() + if [p for _, p in parameters if p.kind == p.VAR_POSITIONAL]: + return + p_or_k = [p for _, p in parameters if p.kind == p.POSITIONAL_OR_KEYWORD] + # how many have no default and how many have a default value + no, yes = map(len, sift(p_or_k, + lambda p:p.default == p.empty, binary=True)) + return no if not yes else tuple(range(no, no + yes + 1)) + +class FunctionClass(type): + """ + Base class for function classes. FunctionClass is a subclass of type. + + Use Function('' [ , signature ]) to create + undefined function classes. + """ + _new = type.__new__ + + def __init__(cls, *args, **kwargs): + # honor kwarg value or class-defined value before using + # the number of arguments in the eval function (if present) + nargs = kwargs.pop('nargs', cls.__dict__.get('nargs', arity(cls))) + if nargs is None and 'nargs' not in cls.__dict__: + for supcls in cls.__mro__: + if hasattr(supcls, '_nargs'): + nargs = supcls._nargs + break + else: + continue + + # Canonicalize nargs here; change to set in nargs. + if is_sequence(nargs): + if not nargs: + raise ValueError(filldedent(''' + Incorrectly specified nargs as %s: + if there are no arguments, it should be + `nargs = 0`; + if there are any number of arguments, + it should be + `nargs = None`''' % str(nargs))) + nargs = tuple(ordered(set(nargs))) + elif nargs is not None: + nargs = (as_int(nargs),) + cls._nargs = nargs + + # When __init__ is called from UndefinedFunction it is called with + # just one arg but when it is called from subclassing Function it is + # called with the usual (name, bases, namespace) type() signature. + if len(args) == 3: + namespace = args[2] + if 'eval' in namespace and not isinstance(namespace['eval'], classmethod): + raise TypeError("eval on Function subclasses should be a class method (defined with @classmethod)") + + @property + def __signature__(self): + """ + Allow Python 3's inspect.signature to give a useful signature for + Function subclasses. + """ + # Python 3 only, but backports (like the one in IPython) still might + # call this. + try: + from inspect import signature + except ImportError: + return None + + # TODO: Look at nargs + return signature(self.eval) + + @property + def free_symbols(self): + return set() + + @property + def xreplace(self): + # Function needs args so we define a property that returns + # a function that takes args...and then use that function + # to return the right value + return lambda rule, **_: rule.get(self, self) + + @property + def nargs(self): + """Return a set of the allowed number of arguments for the function. + + Examples + ======== + + >>> from sympy import Function + >>> f = Function('f') + + If the function can take any number of arguments, the set of whole + numbers is returned: + + >>> Function('f').nargs + Naturals0 + + If the function was initialized to accept one or more arguments, a + corresponding set will be returned: + + >>> Function('f', nargs=1).nargs + {1} + >>> Function('f', nargs=(2, 1)).nargs + {1, 2} + + The undefined function, after application, also has the nargs + attribute; the actual number of arguments is always available by + checking the ``args`` attribute: + + >>> f = Function('f') + >>> f(1).nargs + Naturals0 + >>> len(f(1).args) + 1 + """ + from sympy.sets.sets import FiniteSet + # XXX it would be nice to handle this in __init__ but there are import + # problems with trying to import FiniteSet there + return FiniteSet(*self._nargs) if self._nargs else S.Naturals0 + + def _valid_nargs(self, n : int) -> bool: + """ Return True if the specified integer is a valid number of arguments + + The number of arguments n is guaranteed to be an integer and positive + + """ + if self._nargs: + return n in self._nargs + + nargs = self.nargs + return nargs is S.Naturals0 or n in nargs + + def __repr__(cls): + return cls.__name__ + + +class Application(Basic, metaclass=FunctionClass): + """ + Base class for applied functions. + + Explanation + =========== + + Instances of Application represent the result of applying an application of + any type to any object. + """ + + is_Function = True + + @cacheit + def __new__(cls, *args, **options): + from sympy.sets.fancysets import Naturals0 + from sympy.sets.sets import FiniteSet + + args = list(map(sympify, args)) + evaluate = options.pop('evaluate', global_parameters.evaluate) + # WildFunction (and anything else like it) may have nargs defined + # and we throw that value away here + options.pop('nargs', None) + + if options: + raise ValueError("Unknown options: %s" % options) + + if evaluate: + evaluated = cls.eval(*args) + if evaluated is not None: + return evaluated + + obj = super().__new__(cls, *args, **options) + + # make nargs uniform here + sentinel = object() + objnargs = getattr(obj, "nargs", sentinel) + if objnargs is not sentinel: + # things passing through here: + # - functions subclassed from Function (e.g. myfunc(1).nargs) + # - functions like cos(1).nargs + # - AppliedUndef with given nargs like Function('f', nargs=1)(1).nargs + # Canonicalize nargs here + if is_sequence(objnargs): + nargs = tuple(ordered(set(objnargs))) + elif objnargs is not None: + nargs = (as_int(objnargs),) + else: + nargs = None + else: + # things passing through here: + # - WildFunction('f').nargs + # - AppliedUndef with no nargs like Function('f')(1).nargs + nargs = obj._nargs # note the underscore here + # convert to FiniteSet + obj.nargs = FiniteSet(*nargs) if nargs else Naturals0() + return obj + + @classmethod + def eval(cls, *args): + """ + Returns a canonical form of cls applied to arguments args. + + Explanation + =========== + + The ``eval()`` method is called when the class ``cls`` is about to be + instantiated and it should return either some simplified instance + (possible of some other class), or if the class ``cls`` should be + unmodified, return None. + + Examples of ``eval()`` for the function "sign" + + .. code-block:: python + + @classmethod + def eval(cls, arg): + if arg is S.NaN: + return S.NaN + if arg.is_zero: return S.Zero + if arg.is_positive: return S.One + if arg.is_negative: return S.NegativeOne + if isinstance(arg, Mul): + coeff, terms = arg.as_coeff_Mul(rational=True) + if coeff is not S.One: + return cls(coeff) * cls(terms) + + """ + return + + @property + def func(self): + return self.__class__ + + def _eval_subs(self, old, new): + if (old.is_Function and new.is_Function and + callable(old) and callable(new) and + old == self.func and len(self.args) in new.nargs): + return new(*[i._subs(old, new) for i in self.args]) + + +class Function(Application, Expr): + r""" + Base class for applied mathematical functions. + + It also serves as a constructor for undefined function classes. + + See the :ref:`custom-functions` guide for details on how to subclass + ``Function`` and what methods can be defined. + + Examples + ======== + + **Undefined Functions** + + To create an undefined function, pass a string of the function name to + ``Function``. + + >>> from sympy import Function, Symbol + >>> x = Symbol('x') + >>> f = Function('f') + >>> g = Function('g')(x) + >>> f + f + >>> f(x) + f(x) + >>> g + g(x) + >>> f(x).diff(x) + Derivative(f(x), x) + >>> g.diff(x) + Derivative(g(x), x) + + Assumptions can be passed to ``Function`` the same as with a + :class:`~.Symbol`. Alternatively, you can use a ``Symbol`` with + assumptions for the function name and the function will inherit the name + and assumptions associated with the ``Symbol``: + + >>> f_real = Function('f', real=True) + >>> f_real(x).is_real + True + >>> f_real_inherit = Function(Symbol('f', real=True)) + >>> f_real_inherit(x).is_real + True + + Note that assumptions on a function are unrelated to the assumptions on + the variables it is called on. If you want to add a relationship, subclass + ``Function`` and define custom assumptions handler methods. See the + :ref:`custom-functions-assumptions` section of the :ref:`custom-functions` + guide for more details. + + **Custom Function Subclasses** + + The :ref:`custom-functions` guide has several + :ref:`custom-functions-complete-examples` of how to subclass ``Function`` + to create a custom function. + + """ + + @property + def _diff_wrt(self): + return False + + @cacheit + def __new__(cls, *args, **options) -> type[AppliedUndef]: # type: ignore + # Handle calls like Function('f') + if cls is Function: + return UndefinedFunction(*args, **options) # type: ignore + else: + return cls._new_(*args, **options) # type: ignore + + @classmethod + def _new_(cls, *args, **options) -> Expr: + n = len(args) + + if not cls._valid_nargs(n): + # XXX: exception message must be in exactly this format to + # make it work with NumPy's functions like vectorize(). See, + # for example, https://github.com/numpy/numpy/issues/1697. + # The ideal solution would be just to attach metadata to + # the exception and change NumPy to take advantage of this. + temp = ('%(name)s takes %(qual)s %(args)s ' + 'argument%(plural)s (%(given)s given)') + raise TypeError(temp % { + 'name': cls, + 'qual': 'exactly' if len(cls.nargs) == 1 else 'at least', + 'args': min(cls.nargs), + 'plural': 's'*(min(cls.nargs) != 1), + 'given': n}) + + evaluate = options.get('evaluate', global_parameters.evaluate) + result = super().__new__(cls, *args, **options) + if evaluate and isinstance(result, cls) and result.args: + _should_evalf = [cls._should_evalf(a) for a in result.args] + pr2 = min(_should_evalf) + if pr2 > 0: + pr = max(_should_evalf) + result = result.evalf(prec_to_dps(pr)) + + return _sympify(result) + + @classmethod + def _should_evalf(cls, arg): + """ + Decide if the function should automatically evalf(). + + Explanation + =========== + + By default (in this implementation), this happens if (and only if) the + ARG is a floating point number (including complex numbers). + This function is used by __new__. + + Returns the precision to evalf to, or -1 if it should not evalf. + """ + if arg.is_Float: + return arg._prec + if not arg.is_Add: + return -1 + m = pure_complex(arg) + if m is None: + return -1 + # the elements of m are of type Number, so have a _prec + return max(m[0]._prec, m[1]._prec) + + @classmethod + def class_key(cls): + from sympy.sets.fancysets import Naturals0 + funcs = { + 'exp': 10, + 'log': 11, + 'sin': 20, + 'cos': 21, + 'tan': 22, + 'cot': 23, + 'sinh': 30, + 'cosh': 31, + 'tanh': 32, + 'coth': 33, + 'conjugate': 40, + 're': 41, + 'im': 42, + 'arg': 43, + } + name = cls.__name__ + + try: + i = funcs[name] + except KeyError: + i = 0 if isinstance(cls.nargs, Naturals0) else 10000 + + return 4, i, name + + def _eval_evalf(self, prec): + + def _get_mpmath_func(fname): + """Lookup mpmath function based on name""" + if isinstance(self, AppliedUndef): + # Shouldn't lookup in mpmath but might have ._imp_ + return None + + if not hasattr(mpmath, fname): + fname = MPMATH_TRANSLATIONS.get(fname, None) + if fname is None: + return None + return getattr(mpmath, fname) + + _eval_mpmath = getattr(self, '_eval_mpmath', None) + if _eval_mpmath is None: + func = _get_mpmath_func(self.func.__name__) + args = self.args + else: + func, args = _eval_mpmath() + + # Fall-back evaluation + if func is None: + imp = getattr(self, '_imp_', None) + if imp is None: + return None + try: + return Float(imp(*[i.evalf(prec) for i in self.args]), prec) + except (TypeError, ValueError): + return None + + # Convert all args to mpf or mpc + # Convert the arguments to *higher* precision than requested for the + # final result. + # XXX + 5 is a guess, it is similar to what is used in evalf.py. Should + # we be more intelligent about it? + try: + args = [arg._to_mpmath(prec + 5) for arg in args] + def bad(m): + from mpmath import mpf, mpc + # the precision of an mpf value is the last element + # if that is 1 (and m[1] is not 1 which would indicate a + # power of 2), then the eval failed; so check that none of + # the arguments failed to compute to a finite precision. + # Note: An mpc value has two parts, the re and imag tuple; + # check each of those parts, too. Anything else is allowed to + # pass + if isinstance(m, mpf): + m = m._mpf_ + return m[1] !=1 and m[-1] == 1 + elif isinstance(m, mpc): + m, n = m._mpc_ + return m[1] !=1 and m[-1] == 1 and \ + n[1] !=1 and n[-1] == 1 + else: + return False + if any(bad(a) for a in args): + raise ValueError # one or more args failed to compute with significance + except ValueError: + return + + with mpmath.workprec(prec): + v = func(*args) + + return Expr._from_mpmath(v, prec) + + def _eval_derivative(self, s): + # f(x).diff(s) -> x.diff(s) * f.fdiff(1)(s) + i = 0 + l = [] + for a in self.args: + i += 1 + da = a.diff(s) + if da.is_zero: + continue + try: + df = self.fdiff(i) + except ArgumentIndexError: + df = Function.fdiff(self, i) + l.append(df * da) + return Add(*l) + + def _eval_is_commutative(self): + return fuzzy_and(a.is_commutative for a in self.args) + + def _eval_is_meromorphic(self, x, a): + if not self.args: + return True + if any(arg.has(x) for arg in self.args[1:]): + return False + + arg = self.args[0] + if not arg._eval_is_meromorphic(x, a): + return None + + return fuzzy_not(type(self).is_singular(arg.subs(x, a))) + + _singularities: FuzzyBool | tuple[Expr, ...] = None + + @classmethod + def is_singular(cls, a): + """ + Tests whether the argument is an essential singularity + or a branch point, or the functions is non-holomorphic. + """ + ss = cls._singularities + if ss in (True, None, False): + return ss + + return fuzzy_or(a.is_infinite if s is S.ComplexInfinity + else (a - s).is_zero for s in ss) + + def _eval_aseries(self, n, args0, x, logx): + """ + Compute an asymptotic expansion around args0, in terms of self.args. + This function is only used internally by _eval_nseries and should not + be called directly; derived classes can overwrite this to implement + asymptotic expansions. + """ + raise PoleError(filldedent(''' + Asymptotic expansion of %s around %s is + not implemented.''' % (type(self), args0))) + + def _eval_nseries(self, x, n, logx, cdir=0): + """ + This function does compute series for multivariate functions, + but the expansion is always in terms of *one* variable. + + Examples + ======== + + >>> from sympy import atan2 + >>> from sympy.abc import x, y + >>> atan2(x, y).series(x, n=2) + atan2(0, y) + x/y + O(x**2) + >>> atan2(x, y).series(y, n=2) + -y/x + atan2(x, 0) + O(y**2) + + This function also computes asymptotic expansions, if necessary + and possible: + + >>> from sympy import loggamma + >>> loggamma(1/x)._eval_nseries(x,0,None) + -1/x - log(x)/x + log(x)/2 + O(1) + + """ + from .symbol import uniquely_named_symbol + from sympy.series.order import Order + from sympy.sets.sets import FiniteSet + args = self.args + args0 = [t.limit(x, 0) for t in args] + if any(t.is_finite is False for t in args0): + from .numbers import oo, zoo, nan + a = [t.as_leading_term(x, logx=logx) for t in args] + a0 = [t.limit(x, 0) for t in a] + if any(t.has(oo, -oo, zoo, nan) for t in a0): + return self._eval_aseries(n, args0, x, logx) + # Careful: the argument goes to oo, but only logarithmically so. We + # are supposed to do a power series expansion "around the + # logarithmic term". e.g. + # f(1+x+log(x)) + # -> f(1+logx) + x*f'(1+logx) + O(x**2) + # where 'logx' is given in the argument + a = [t._eval_nseries(x, n, logx) for t in args] + z = [r - r0 for (r, r0) in zip(a, a0)] + p = [Dummy() for _ in z] + q = [] + v = None + for ai, zi, pi in zip(a0, z, p): + if zi.has(x): + if v is not None: + raise NotImplementedError + q.append(ai + pi) + v = pi + else: + q.append(ai) + e1 = self.func(*q) + if v is None: + return e1 + s = e1._eval_nseries(v, n, logx) + o = s.getO() + s = s.removeO() + s = s.subs(v, zi).expand() + Order(o.expr.subs(v, zi), x) + return s + if (self.func.nargs is S.Naturals0 + or (self.func.nargs == FiniteSet(1) and args0[0]) + or any(c > 1 for c in self.func.nargs)): + e = self + e1 = e.expand() + if e == e1: + #for example when e = sin(x+1) or e = sin(cos(x)) + #let's try the general algorithm + if len(e.args) == 1: + # issue 14411 + e = e.func(e.args[0].cancel()) + term = e.subs(x, S.Zero) + if term.is_finite is False or term is S.NaN: + raise PoleError("Cannot expand %s around 0" % (self)) + series = term + fact = S.One + + _x = uniquely_named_symbol('xi', self) + e = e.subs(x, _x) + for i in range(1, n): + fact *= Rational(i) + e = e.diff(_x) + subs = e.subs(_x, S.Zero) + if subs is S.NaN: + # try to evaluate a limit if we have to + subs = e.limit(_x, S.Zero) + if subs.is_finite is False: + raise PoleError("Cannot expand %s around 0" % (self)) + term = subs*(x**i)/fact + term = term.expand() + series += term + return series + Order(x**n, x) + return e1.nseries(x, n=n, logx=logx) + arg = self.args[0] + l = [] + g = None + # try to predict a number of terms needed + nterms = n + 2 + cf = Order(arg.as_leading_term(x), x).getn() + if cf != 0: + nterms = (n/cf).ceiling() + for i in range(nterms): + g = self.taylor_term(i, arg, g) + g = g.nseries(x, n=n, logx=logx) + l.append(g) + return Add(*l) + Order(x**n, x) + + def fdiff(self, argindex=1): + """ + Returns the first derivative of the function. + """ + if not (1 <= argindex <= len(self.args)): + raise ArgumentIndexError(self, argindex) + ix = argindex - 1 + A = self.args[ix] + if A._diff_wrt: + if len(self.args) == 1 or not A.is_Symbol: + return _derivative_dispatch(self, A) + for i, v in enumerate(self.args): + if i != ix and A in v.free_symbols: + # it can't be in any other argument's free symbols + # issue 8510 + break + else: + return _derivative_dispatch(self, A) + + # See issue 4624 and issue 4719, 5600 and 8510 + D = Dummy('xi_%i' % argindex, dummy_index=hash(A)) + args = self.args[:ix] + (D,) + self.args[ix + 1:] + return Subs(Derivative(self.func(*args), D), D, A) + + def _eval_as_leading_term(self, x, logx, cdir): + """Stub that should be overridden by new Functions to return + the first non-zero term in a series if ever an x-dependent + argument whose leading term vanishes as x -> 0 might be encountered. + See, for example, cos._eval_as_leading_term. + """ + from sympy.series.order import Order + args = [a.as_leading_term(x, logx=logx) for a in self.args] + o = Order(1, x) + if any(x in a.free_symbols and o.contains(a) for a in args): + # Whereas x and any finite number are contained in O(1, x), + # expressions like 1/x are not. If any arg simplified to a + # vanishing expression as x -> 0 (like x or x**2, but not + # 3, 1/x, etc...) then the _eval_as_leading_term is needed + # to supply the first non-zero term of the series, + # + # e.g. expression leading term + # ---------- ------------ + # cos(1/x) cos(1/x) + # cos(cos(x)) cos(1) + # cos(x) 1 <- _eval_as_leading_term needed + # sin(x) x <- _eval_as_leading_term needed + # + raise NotImplementedError( + '%s has no _eval_as_leading_term routine' % self.func) + else: + return self + + +class DefinedFunction(Function): + """Base class for defined functions like ``sin``, ``cos``, ...""" + + @cacheit + def __new__(cls, *args, **options) -> Expr: # type: ignore + return cls._new_(*args, **options) + + +class AppliedUndef(Function): + """ + Base class for expressions resulting from the application of an undefined + function. + """ + + is_number = False + + name: str + + def __new__(cls, *args, **options) -> Expr: # type: ignore + args = tuple(map(sympify, args)) + u = [a.name for a in args if isinstance(a, UndefinedFunction)] + if u: + raise TypeError('Invalid argument: expecting an expression, not UndefinedFunction%s: %s' % ( + 's'*(len(u) > 1), ', '.join(u))) + obj: Expr = super().__new__(cls, *args, **options) # type: ignore + return obj + + def _eval_as_leading_term(self, x, logx, cdir): + return self + + @property + def _diff_wrt(self): + """ + Allow derivatives wrt to undefined functions. + + Examples + ======== + + >>> from sympy import Function, Symbol + >>> f = Function('f') + >>> x = Symbol('x') + >>> f(x)._diff_wrt + True + >>> f(x).diff(x) + Derivative(f(x), x) + """ + return True + + +class UndefSageHelper: + """ + Helper to facilitate Sage conversion. + """ + def __get__(self, ins, typ): + import sage.all as sage + if ins is None: + return lambda: sage.function(typ.__name__) + else: + args = [arg._sage_() for arg in ins.args] + return lambda : sage.function(ins.__class__.__name__)(*args) + +_undef_sage_helper = UndefSageHelper() + + +class UndefinedFunction(FunctionClass): + """ + The (meta)class of undefined functions. + """ + name: str + _sage_: UndefSageHelper + + def __new__(mcl, name, bases=(AppliedUndef,), __dict__=None, **kwargs) -> type[AppliedUndef]: + from .symbol import _filter_assumptions + # Allow Function('f', real=True) + # and/or Function(Symbol('f', real=True)) + assumptions, kwargs = _filter_assumptions(kwargs) + if isinstance(name, Symbol): + assumptions = name._merge(assumptions) + name = name.name + elif not isinstance(name, str): + raise TypeError('expecting string or Symbol for name') + else: + commutative = assumptions.get('commutative', None) + assumptions = Symbol(name, **assumptions).assumptions0 + if commutative is None: + assumptions.pop('commutative') + __dict__ = __dict__ or {} + # put the `is_*` for into __dict__ + __dict__.update({'is_%s' % k: v for k, v in assumptions.items()}) + # You can add other attributes, although they do have to be hashable + # (but seriously, if you want to add anything other than assumptions, + # just subclass Function) + __dict__.update(kwargs) + # add back the sanitized assumptions without the is_ prefix + kwargs.update(assumptions) + # Save these for __eq__ + __dict__.update({'_kwargs': kwargs}) + # do this for pickling + __dict__['__module__'] = None + obj = super().__new__(mcl, name, bases, __dict__) # type: ignore + obj.name = name + obj._sage_ = _undef_sage_helper + return obj # type: ignore + + def __instancecheck__(cls, instance): + return cls in type(instance).__mro__ + + _kwargs: dict[str, bool | None] = {} + + def __hash__(self): + return hash((self.class_key(), frozenset(self._kwargs.items()))) + + def __eq__(self, other): + return (isinstance(other, self.__class__) and + self.class_key() == other.class_key() and + self._kwargs == other._kwargs) + + def __ne__(self, other): + return not self == other + + @property + def _diff_wrt(self): + return False + + +# Using copyreg is the only way to make a dynamically generated instance of a +# metaclass picklable without using a custom pickler. It is not possible to +# define e.g. __reduce__ on the metaclass because obj.__reduce__ will retrieve +# the __reduce__ method for reducing instances of the type rather than for the +# type itself. +def _reduce_undef(f): + return (_rebuild_undef, (f.name, f._kwargs)) + +def _rebuild_undef(name, kwargs): + return Function(name, **kwargs) + +copyreg.pickle(UndefinedFunction, _reduce_undef) + + +# XXX: The type: ignore on WildFunction is because mypy complains: +# +# sympy/core/function.py:939: error: Cannot determine type of 'sort_key' in +# base class 'Expr' +# +# Somehow this is because of the @cacheit decorator but it is not clear how to +# fix it. + + +class WildFunction(Function, AtomicExpr): # type: ignore + """ + A WildFunction function matches any function (with its arguments). + + Examples + ======== + + >>> from sympy import WildFunction, Function, cos + >>> from sympy.abc import x, y + >>> F = WildFunction('F') + >>> f = Function('f') + >>> F.nargs + Naturals0 + >>> x.match(F) + >>> F.match(F) + {F_: F_} + >>> f(x).match(F) + {F_: f(x)} + >>> cos(x).match(F) + {F_: cos(x)} + >>> f(x, y).match(F) + {F_: f(x, y)} + + To match functions with a given number of arguments, set ``nargs`` to the + desired value at instantiation: + + >>> F = WildFunction('F', nargs=2) + >>> F.nargs + {2} + >>> f(x).match(F) + >>> f(x, y).match(F) + {F_: f(x, y)} + + To match functions with a range of arguments, set ``nargs`` to a tuple + containing the desired number of arguments, e.g. if ``nargs = (1, 2)`` + then functions with 1 or 2 arguments will be matched. + + >>> F = WildFunction('F', nargs=(1, 2)) + >>> F.nargs + {1, 2} + >>> f(x).match(F) + {F_: f(x)} + >>> f(x, y).match(F) + {F_: f(x, y)} + >>> f(x, y, 1).match(F) + + """ + + # XXX: What is this class attribute used for? + include: set[Any] = set() + + def __init__(cls, name, **assumptions): + from sympy.sets.sets import Set, FiniteSet + cls.name = name + nargs = assumptions.pop('nargs', S.Naturals0) + if not isinstance(nargs, Set): + # Canonicalize nargs here. See also FunctionClass. + if is_sequence(nargs): + nargs = tuple(ordered(set(nargs))) + elif nargs is not None: + nargs = (as_int(nargs),) + nargs = FiniteSet(*nargs) + cls.nargs = nargs + + def matches(self, expr, repl_dict=None, old=False): + if not isinstance(expr, (AppliedUndef, Function)): + return None + if len(expr.args) not in self.nargs: + return None + + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + repl_dict[self] = expr + return repl_dict + + +class Derivative(Expr): + """ + Carries out differentiation of the given expression with respect to symbols. + + Examples + ======== + + >>> from sympy import Derivative, Function, symbols, Subs + >>> from sympy.abc import x, y + >>> f, g = symbols('f g', cls=Function) + + >>> Derivative(x**2, x, evaluate=True) + 2*x + + Denesting of derivatives retains the ordering of variables: + + >>> Derivative(Derivative(f(x, y), y), x) + Derivative(f(x, y), y, x) + + Contiguously identical symbols are merged into a tuple giving + the symbol and the count: + + >>> Derivative(f(x), x, x, y, x) + Derivative(f(x), (x, 2), y, x) + + If the derivative cannot be performed, and evaluate is True, the + order of the variables of differentiation will be made canonical: + + >>> Derivative(f(x, y), y, x, evaluate=True) + Derivative(f(x, y), x, y) + + Derivatives with respect to undefined functions can be calculated: + + >>> Derivative(f(x)**2, f(x), evaluate=True) + 2*f(x) + + Such derivatives will show up when the chain rule is used to + evaluate a derivative: + + >>> f(g(x)).diff(x) + Derivative(f(g(x)), g(x))*Derivative(g(x), x) + + Substitution is used to represent derivatives of functions with + arguments that are not symbols or functions: + + >>> f(2*x + 3).diff(x) == 2*Subs(f(y).diff(y), y, 2*x + 3) + True + + Notes + ===== + + Simplification of high-order derivatives: + + Because there can be a significant amount of simplification that can be + done when multiple differentiations are performed, results will be + automatically simplified in a fairly conservative fashion unless the + keyword ``simplify`` is set to False. + + >>> from sympy import sqrt, diff, Function, symbols + >>> from sympy.abc import x, y, z + >>> f, g = symbols('f,g', cls=Function) + + >>> e = sqrt((x + 1)**2 + x) + >>> diff(e, (x, 5), simplify=False).count_ops() + 136 + >>> diff(e, (x, 5)).count_ops() + 30 + + Ordering of variables: + + If evaluate is set to True and the expression cannot be evaluated, the + list of differentiation symbols will be sorted, that is, the expression is + assumed to have continuous derivatives up to the order asked. + + Derivative wrt non-Symbols: + + For the most part, one may not differentiate wrt non-symbols. + For example, we do not allow differentiation wrt `x*y` because + there are multiple ways of structurally defining where x*y appears + in an expression: a very strict definition would make + (x*y*z).diff(x*y) == 0. Derivatives wrt defined functions (like + cos(x)) are not allowed, either: + + >>> (x*y*z).diff(x*y) + Traceback (most recent call last): + ... + ValueError: Can't calculate derivative wrt x*y. + + To make it easier to work with variational calculus, however, + derivatives wrt AppliedUndef and Derivatives are allowed. + For example, in the Euler-Lagrange method one may write + F(t, u, v) where u = f(t) and v = f'(t). These variables can be + written explicitly as functions of time:: + + >>> from sympy.abc import t + >>> F = Function('F') + >>> U = f(t) + >>> V = U.diff(t) + + The derivative wrt f(t) can be obtained directly: + + >>> direct = F(t, U, V).diff(U) + + When differentiation wrt a non-Symbol is attempted, the non-Symbol + is temporarily converted to a Symbol while the differentiation + is performed and the same answer is obtained: + + >>> indirect = F(t, U, V).subs(U, x).diff(x).subs(x, U) + >>> assert direct == indirect + + The implication of this non-symbol replacement is that all + functions are treated as independent of other functions and the + symbols are independent of the functions that contain them:: + + >>> x.diff(f(x)) + 0 + >>> g(x).diff(f(x)) + 0 + + It also means that derivatives are assumed to depend only + on the variables of differentiation, not on anything contained + within the expression being differentiated:: + + >>> F = f(x) + >>> Fx = F.diff(x) + >>> Fx.diff(F) # derivative depends on x, not F + 0 + >>> Fxx = Fx.diff(x) + >>> Fxx.diff(Fx) # derivative depends on x, not Fx + 0 + + The last example can be made explicit by showing the replacement + of Fx in Fxx with y: + + >>> Fxx.subs(Fx, y) + Derivative(y, x) + + Since that in itself will evaluate to zero, differentiating + wrt Fx will also be zero: + + >>> _.doit() + 0 + + Replacing undefined functions with concrete expressions + + One must be careful to replace undefined functions with expressions + that contain variables consistent with the function definition and + the variables of differentiation or else insconsistent result will + be obtained. Consider the following example: + + >>> eq = f(x)*g(y) + >>> eq.subs(f(x), x*y).diff(x, y).doit() + y*Derivative(g(y), y) + g(y) + >>> eq.diff(x, y).subs(f(x), x*y).doit() + y*Derivative(g(y), y) + + The results differ because `f(x)` was replaced with an expression + that involved both variables of differentiation. In the abstract + case, differentiation of `f(x)` by `y` is 0; in the concrete case, + the presence of `y` made that derivative nonvanishing and produced + the extra `g(y)` term. + + Defining differentiation for an object + + An object must define ._eval_derivative(symbol) method that returns + the differentiation result. This function only needs to consider the + non-trivial case where expr contains symbol and it should call the diff() + method internally (not _eval_derivative); Derivative should be the only + one to call _eval_derivative. + + Any class can allow derivatives to be taken with respect to + itself (while indicating its scalar nature). See the + docstring of Expr._diff_wrt. + + See Also + ======== + _sort_variable_count + """ + + is_Derivative = True + + @property + def _diff_wrt(self): + """An expression may be differentiated wrt a Derivative if + it is in elementary form. + + Examples + ======== + + >>> from sympy import Function, Derivative, cos + >>> from sympy.abc import x + >>> f = Function('f') + + >>> Derivative(f(x), x)._diff_wrt + True + >>> Derivative(cos(x), x)._diff_wrt + False + >>> Derivative(x + 1, x)._diff_wrt + False + + A Derivative might be an unevaluated form of what will not be + a valid variable of differentiation if evaluated. For example, + + >>> Derivative(f(f(x)), x).doit() + Derivative(f(x), x)*Derivative(f(f(x)), f(x)) + + Such an expression will present the same ambiguities as arise + when dealing with any other product, like ``2*x``, so ``_diff_wrt`` + is False: + + >>> Derivative(f(f(x)), x)._diff_wrt + False + """ + return self.expr._diff_wrt and isinstance(self.doit(), Derivative) + + def __new__(cls, expr, *variables, **kwargs): + expr = sympify(expr) + if not isinstance(expr, Basic): + raise TypeError(f"Cannot represent derivative of {type(expr)}") + symbols_or_none = getattr(expr, "free_symbols", None) + has_symbol_set = isinstance(symbols_or_none, set) + + if not has_symbol_set: + raise ValueError(filldedent(''' + Since there are no variables in the expression %s, + it cannot be differentiated.''' % expr)) + + # determine value for variables if it wasn't given + if not variables: + variables = expr.free_symbols + if len(variables) != 1: + if expr.is_number: + return S.Zero + if len(variables) == 0: + raise ValueError(filldedent(''' + Since there are no variables in the expression, + the variable(s) of differentiation must be supplied + to differentiate %s''' % expr)) + else: + raise ValueError(filldedent(''' + Since there is more than one variable in the + expression, the variable(s) of differentiation + must be supplied to differentiate %s''' % expr)) + + # Split the list of variables into a list of the variables we are diff + # wrt, where each element of the list has the form (s, count) where + # s is the entity to diff wrt and count is the order of the + # derivative. + variable_count = [] + array_likes = (tuple, list, Tuple) + + from sympy.tensor.array import Array, NDimArray + + for i, v in enumerate(variables): + if isinstance(v, UndefinedFunction): + raise TypeError( + "cannot differentiate wrt " + "UndefinedFunction: %s" % v) + + if isinstance(v, array_likes): + if len(v) == 0: + # Ignore empty tuples: Derivative(expr, ... , (), ... ) + continue + if isinstance(v[0], array_likes): + # Derive by array: Derivative(expr, ... , [[x, y, z]], ... ) + if len(v) == 1: + v = Array(v[0]) + count = 1 + else: + v, count = v + v = Array(v) + else: + v, count = v + if count == 0: + continue + variable_count.append(Tuple(v, count)) + continue + + v = sympify(v) + if isinstance(v, Integer): + if i == 0: + raise ValueError("First variable cannot be a number: %i" % v) + count = v + prev, prevcount = variable_count[-1] + if prevcount != 1: + raise TypeError("tuple {} followed by number {}".format((prev, prevcount), v)) + if count == 0: + variable_count.pop() + else: + variable_count[-1] = Tuple(prev, count) + else: + count = 1 + variable_count.append(Tuple(v, count)) + + # light evaluation of contiguous, identical + # items: (x, 1), (x, 1) -> (x, 2) + merged = [] + for t in variable_count: + v, c = t + if c.is_negative: + raise ValueError( + 'order of differentiation must be nonnegative') + if merged and merged[-1][0] == v: + c += merged[-1][1] + if not c: + merged.pop() + else: + merged[-1] = Tuple(v, c) + else: + merged.append(t) + variable_count = merged + + # sanity check of variables of differentation; we waited + # until the counts were computed since some variables may + # have been removed because the count was 0 + for v, c in variable_count: + # v must have _diff_wrt True + if not v._diff_wrt: + __ = '' # filler to make error message neater + raise ValueError(filldedent(''' + Can't calculate derivative wrt %s.%s''' % (v, + __))) + + # We make a special case for 0th derivative, because there is no + # good way to unambiguously print this. + if len(variable_count) == 0: + return expr + + evaluate = kwargs.get('evaluate', False) + + if evaluate: + if isinstance(expr, Derivative): + expr = expr.canonical + variable_count = [ + (v.canonical if isinstance(v, Derivative) else v, c) + for v, c in variable_count] + + # Look for a quick exit if there are symbols that don't appear in + # expression at all. Note, this cannot check non-symbols like + # Derivatives as those can be created by intermediate + # derivatives. + zero = False + free = expr.free_symbols + from sympy.matrices.expressions.matexpr import MatrixExpr + + for v, c in variable_count: + vfree = v.free_symbols + if c.is_positive and vfree: + if isinstance(v, AppliedUndef): + # these match exactly since + # x.diff(f(x)) == g(x).diff(f(x)) == 0 + # and are not created by differentiation + D = Dummy() + if not expr.xreplace({v: D}).has(D): + zero = True + break + elif isinstance(v, MatrixExpr): + zero = False + break + elif isinstance(v, Symbol) and v not in free: + zero = True + break + else: + if not free & vfree: + # e.g. v is IndexedBase or Matrix + zero = True + break + if zero: + return cls._get_zero_with_shape_like(expr) + + # make the order of symbols canonical + #TODO: check if assumption of discontinuous derivatives exist + variable_count = cls._sort_variable_count(variable_count) + + # denest + if isinstance(expr, Derivative): + variable_count = list(expr.variable_count) + variable_count + expr = expr.expr + return _derivative_dispatch(expr, *variable_count, **kwargs) + + # we return here if evaluate is False or if there is no + # _eval_derivative method + if not evaluate or not hasattr(expr, '_eval_derivative'): + # return an unevaluated Derivative + if evaluate and variable_count == [(expr, 1)] and expr.is_scalar: + # special hack providing evaluation for classes + # that have defined is_scalar=True but have no + # _eval_derivative defined + return S.One + return Expr.__new__(cls, expr, *variable_count) + + # evaluate the derivative by calling _eval_derivative method + # of expr for each variable + # ------------------------------------------------------------- + nderivs = 0 # how many derivatives were performed + unhandled = [] + from sympy.matrices.matrixbase import MatrixBase + for i, (v, count) in enumerate(variable_count): + + old_expr = expr + old_v = None + + is_symbol = v.is_symbol or isinstance(v, + (Iterable, Tuple, MatrixBase, NDimArray)) + + if not is_symbol: + old_v = v + v = Dummy('xi') + expr = expr.xreplace({old_v: v}) + # Derivatives and UndefinedFunctions are independent + # of all others + clashing = not (isinstance(old_v, (Derivative, AppliedUndef))) + if v not in expr.free_symbols and not clashing: + return expr.diff(v) # expr's version of 0 + if not old_v.is_scalar and not hasattr( + old_v, '_eval_derivative'): + # special hack providing evaluation for classes + # that have defined is_scalar=True but have no + # _eval_derivative defined + expr *= old_v.diff(old_v) + + obj = cls._dispatch_eval_derivative_n_times(expr, v, count) + if obj is not None and obj.is_zero: + return obj + + nderivs += count + + if old_v is not None: + if obj is not None: + # remove the dummy that was used + obj = obj.subs(v, old_v) + # restore expr + expr = old_expr + + if obj is None: + # we've already checked for quick-exit conditions + # that give 0 so the remaining variables + # are contained in the expression but the expression + # did not compute a derivative so we stop taking + # derivatives + unhandled = variable_count[i:] + break + + expr = obj + + # what we have so far can be made canonical + expr = expr.replace( + lambda x: isinstance(x, Derivative), + lambda x: x.canonical) + + if unhandled: + if isinstance(expr, Derivative): + unhandled = list(expr.variable_count) + unhandled + expr = expr.expr + expr = Expr.__new__(cls, expr, *unhandled) + + if (nderivs > 1) == True and kwargs.get('simplify', True): + from .exprtools import factor_terms + from sympy.simplify.simplify import signsimp + expr = factor_terms(signsimp(expr)) + return expr + + @property + def canonical(cls): + return cls.func(cls.expr, + *Derivative._sort_variable_count(cls.variable_count)) + + @classmethod + def _sort_variable_count(cls, vc): + """ + Sort (variable, count) pairs into canonical order while + retaining order of variables that do not commute during + differentiation: + + * symbols and functions commute with each other + * derivatives commute with each other + * a derivative does not commute with anything it contains + * any other object is not allowed to commute if it has + free symbols in common with another object + + Examples + ======== + + >>> from sympy import Derivative, Function, symbols + >>> vsort = Derivative._sort_variable_count + >>> x, y, z = symbols('x y z') + >>> f, g, h = symbols('f g h', cls=Function) + + Contiguous items are collapsed into one pair: + + >>> vsort([(x, 1), (x, 1)]) + [(x, 2)] + >>> vsort([(y, 1), (f(x), 1), (y, 1), (f(x), 1)]) + [(y, 2), (f(x), 2)] + + Ordering is canonical. + + >>> def vsort0(*v): + ... # docstring helper to + ... # change vi -> (vi, 0), sort, and return vi vals + ... return [i[0] for i in vsort([(i, 0) for i in v])] + + >>> vsort0(y, x) + [x, y] + >>> vsort0(g(y), g(x), f(y)) + [f(y), g(x), g(y)] + + Symbols are sorted as far to the left as possible but never + move to the left of a derivative having the same symbol in + its variables; the same applies to AppliedUndef which are + always sorted after Symbols: + + >>> dfx = f(x).diff(x) + >>> assert vsort0(dfx, y) == [y, dfx] + >>> assert vsort0(dfx, x) == [dfx, x] + """ + if not vc: + return [] + vc = list(vc) + if len(vc) == 1: + return [Tuple(*vc[0])] + V = list(range(len(vc))) + E = [] + v = lambda i: vc[i][0] + D = Dummy() + def _block(d, v, wrt=False): + # return True if v should not come before d else False + if d == v: + return wrt + if d.is_Symbol: + return False + if isinstance(d, Derivative): + # a derivative blocks if any of it's variables contain + # v; the wrt flag will return True for an exact match + # and will cause an AppliedUndef to block if v is in + # the arguments + if any(_block(k, v, wrt=True) + for k in d._wrt_variables): + return True + return False + if not wrt and isinstance(d, AppliedUndef): + return False + if v.is_Symbol: + return v in d.free_symbols + if isinstance(v, AppliedUndef): + return _block(d.xreplace({v: D}), D) + return d.free_symbols & v.free_symbols + for i in range(len(vc)): + for j in range(i): + if _block(v(j), v(i)): + E.append((j,i)) + # this is the default ordering to use in case of ties + O = dict(zip(ordered(uniq([i for i, c in vc])), range(len(vc)))) + ix = topological_sort((V, E), key=lambda i: O[v(i)]) + # merge counts of contiguously identical items + merged = [] + for v, c in [vc[i] for i in ix]: + if merged and merged[-1][0] == v: + merged[-1][1] += c + else: + merged.append([v, c]) + return [Tuple(*i) for i in merged] + + def _eval_is_commutative(self): + return self.expr.is_commutative + + def _eval_derivative(self, v): + # If v (the variable of differentiation) is not in + # self.variables, we might be able to take the derivative. + if v not in self._wrt_variables: + dedv = self.expr.diff(v) + if isinstance(dedv, Derivative): + return dedv.func(dedv.expr, *(self.variable_count + dedv.variable_count)) + # dedv (d(self.expr)/dv) could have simplified things such that the + # derivative wrt things in self.variables can now be done. Thus, + # we set evaluate=True to see if there are any other derivatives + # that can be done. The most common case is when dedv is a simple + # number so that the derivative wrt anything else will vanish. + return self.func(dedv, *self.variables, evaluate=True) + # In this case v was in self.variables so the derivative wrt v has + # already been attempted and was not computed, either because it + # couldn't be or evaluate=False originally. + variable_count = list(self.variable_count) + variable_count.append((v, 1)) + return self.func(self.expr, *variable_count, evaluate=False) + + def doit(self, **hints): + expr = self.expr + if hints.get('deep', True): + expr = expr.doit(**hints) + hints['evaluate'] = True + rv = self.func(expr, *self.variable_count, **hints) + if rv!= self and rv.has(Derivative): + rv = rv.doit(**hints) + return rv + + @_sympifyit('z0', NotImplementedError) + def doit_numerically(self, z0): + """ + Evaluate the derivative at z numerically. + + When we can represent derivatives at a point, this should be folded + into the normal evalf. For now, we need a special method. + """ + if len(self.free_symbols) != 1 or len(self.variables) != 1: + raise NotImplementedError('partials and higher order derivatives') + z = list(self.free_symbols)[0] + + def eval(x): + f0 = self.expr.subs(z, Expr._from_mpmath(x, prec=mpmath.mp.prec)) + f0 = f0.evalf(prec_to_dps(mpmath.mp.prec)) + return f0._to_mpmath(mpmath.mp.prec) + return Expr._from_mpmath(mpmath.diff(eval, + z0._to_mpmath(mpmath.mp.prec)), + mpmath.mp.prec) + + @property + def expr(self): + return self._args[0] + + @property + def _wrt_variables(self): + # return the variables of differentiation without + # respect to the type of count (int or symbolic) + return [i[0] for i in self.variable_count] + + @property + def variables(self): + # TODO: deprecate? YES, make this 'enumerated_variables' and + # name _wrt_variables as variables + # TODO: support for `d^n`? + rv = [] + for v, count in self.variable_count: + if not count.is_Integer: + raise TypeError(filldedent(''' + Cannot give expansion for symbolic count. If you just + want a list of all variables of differentiation, use + _wrt_variables.''')) + rv.extend([v]*count) + return tuple(rv) + + @property + def variable_count(self): + return self._args[1:] + + @property + def derivative_count(self): + return sum([count for _, count in self.variable_count], 0) + + @property + def free_symbols(self): + ret = self.expr.free_symbols + # Add symbolic counts to free_symbols + for _, count in self.variable_count: + ret.update(count.free_symbols) + return ret + + @property + def kind(self): + return self.args[0].kind + + def _eval_subs(self, old, new): + # The substitution (old, new) cannot be done inside + # Derivative(expr, vars) for a variety of reasons + # as handled below. + if old in self._wrt_variables: + # first handle the counts + expr = self.func(self.expr, *[(v, c.subs(old, new)) + for v, c in self.variable_count]) + if expr != self: + return expr._eval_subs(old, new) + # quick exit case + if not getattr(new, '_diff_wrt', False): + # case (0): new is not a valid variable of + # differentiation + if isinstance(old, Symbol): + # don't introduce a new symbol if the old will do + return Subs(self, old, new) + else: + xi = Dummy('xi') + return Subs(self.xreplace({old: xi}), xi, new) + + # If both are Derivatives with the same expr, check if old is + # equivalent to self or if old is a subderivative of self. + if old.is_Derivative and old.expr == self.expr: + if self.canonical == old.canonical: + return new + + # collections.Counter doesn't have __le__ + def _subset(a, b): + return all((a[i] <= b[i]) == True for i in a) + + old_vars = Counter(dict(reversed(old.variable_count))) + self_vars = Counter(dict(reversed(self.variable_count))) + if _subset(old_vars, self_vars): + return _derivative_dispatch(new, *(self_vars - old_vars).items()).canonical + + args = list(self.args) + newargs = [x._subs(old, new) for x in args] + if args[0] == old: + # complete replacement of self.expr + # we already checked that the new is valid so we know + # it won't be a problem should it appear in variables + return _derivative_dispatch(*newargs) + + if newargs[0] != args[0]: + # case (1) can't change expr by introducing something that is in + # the _wrt_variables if it was already in the expr + # e.g. + # for Derivative(f(x, g(y)), y), x cannot be replaced with + # anything that has y in it; for f(g(x), g(y)).diff(g(y)) + # g(x) cannot be replaced with anything that has g(y) + syms = {vi: Dummy() for vi in self._wrt_variables + if not vi.is_Symbol} + wrt = {syms.get(vi, vi) for vi in self._wrt_variables} + forbidden = args[0].xreplace(syms).free_symbols & wrt + nfree = new.xreplace(syms).free_symbols + ofree = old.xreplace(syms).free_symbols + if (nfree - ofree) & forbidden: + return Subs(self, old, new) + + viter = ((i, j) for ((i, _), (j, _)) in zip(newargs[1:], args[1:])) + if any(i != j for i, j in viter): # a wrt-variable change + # case (2) can't change vars by introducing a variable + # that is contained in expr, e.g. + # for Derivative(f(z, g(h(x), y)), y), y cannot be changed to + # x, h(x), or g(h(x), y) + for a in _atomic(self.expr, recursive=True): + for i in range(1, len(newargs)): + vi, _ = newargs[i] + if a == vi and vi != args[i][0]: + return Subs(self, old, new) + # more arg-wise checks + vc = newargs[1:] + oldv = self._wrt_variables + newe = self.expr + subs = [] + for i, (vi, ci) in enumerate(vc): + if not vi._diff_wrt: + # case (3) invalid differentiation expression so + # create a replacement dummy + xi = Dummy('xi_%i' % i) + # replace the old valid variable with the dummy + # in the expression + newe = newe.xreplace({oldv[i]: xi}) + # and replace the bad variable with the dummy + vc[i] = (xi, ci) + # and record the dummy with the new (invalid) + # differentiation expression + subs.append((xi, vi)) + + if subs: + # handle any residual substitution in the expression + newe = newe._subs(old, new) + # return the Subs-wrapped derivative + return Subs(Derivative(newe, *vc), *zip(*subs)) + + # everything was ok + return _derivative_dispatch(*newargs) + + def _eval_lseries(self, x, logx, cdir=0): + dx = self.variables + for term in self.expr.lseries(x, logx=logx, cdir=cdir): + yield self.func(term, *dx) + + def _eval_nseries(self, x, n, logx, cdir=0): + arg = self.expr.nseries(x, n=n, logx=logx) + o = arg.getO() + dx = self.variables + rv = [self.func(a, *dx) for a in Add.make_args(arg.removeO())] + if o: + rv.append(o/x) + return Add(*rv) + + def _eval_as_leading_term(self, x, logx, cdir): + series_gen = self.expr.lseries(x) + d = S.Zero + for leading_term in series_gen: + d = diff(leading_term, *self.variables) + if d != 0: + break + return d + + def as_finite_difference(self, points=1, x0=None, wrt=None): + """ Expresses a Derivative instance as a finite difference. + + Parameters + ========== + + points : sequence or coefficient, optional + If sequence: discrete values (length >= order+1) of the + independent variable used for generating the finite + difference weights. + If it is a coefficient, it will be used as the step-size + for generating an equidistant sequence of length order+1 + centered around ``x0``. Default: 1 (step-size 1) + + x0 : number or Symbol, optional + the value of the independent variable (``wrt``) at which the + derivative is to be approximated. Default: same as ``wrt``. + + wrt : Symbol, optional + "with respect to" the variable for which the (partial) + derivative is to be approximated for. If not provided it + is required that the derivative is ordinary. Default: ``None``. + + + Examples + ======== + + >>> from sympy import symbols, Function, exp, sqrt, Symbol + >>> x, h = symbols('x h') + >>> f = Function('f') + >>> f(x).diff(x).as_finite_difference() + -f(x - 1/2) + f(x + 1/2) + + The default step size and number of points are 1 and + ``order + 1`` respectively. We can change the step size by + passing a symbol as a parameter: + + >>> f(x).diff(x).as_finite_difference(h) + -f(-h/2 + x)/h + f(h/2 + x)/h + + We can also specify the discretized values to be used in a + sequence: + + >>> f(x).diff(x).as_finite_difference([x, x+h, x+2*h]) + -3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h) + + The algorithm is not restricted to use equidistant spacing, nor + do we need to make the approximation around ``x0``, but we can get + an expression estimating the derivative at an offset: + + >>> e, sq2 = exp(1), sqrt(2) + >>> xl = [x-h, x+h, x+e*h] + >>> f(x).diff(x, 1).as_finite_difference(xl, x+h*sq2) # doctest: +ELLIPSIS + 2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/... + + To approximate ``Derivative`` around ``x0`` using a non-equidistant + spacing step, the algorithm supports assignment of undefined + functions to ``points``: + + >>> dx = Function('dx') + >>> f(x).diff(x).as_finite_difference(points=dx(x), x0=x-h) + -f(-h + x - dx(-h + x)/2)/dx(-h + x) + f(-h + x + dx(-h + x)/2)/dx(-h + x) + + Partial derivatives are also supported: + + >>> y = Symbol('y') + >>> d2fdxdy=f(x,y).diff(x,y) + >>> d2fdxdy.as_finite_difference(wrt=x) + -Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y) + + We can apply ``as_finite_difference`` to ``Derivative`` instances in + compound expressions using ``replace``: + + >>> (1 + 42**f(x).diff(x)).replace(lambda arg: arg.is_Derivative, + ... lambda arg: arg.as_finite_difference()) + 42**(-f(x - 1/2) + f(x + 1/2)) + 1 + + + See also + ======== + + sympy.calculus.finite_diff.apply_finite_diff + sympy.calculus.finite_diff.differentiate_finite + sympy.calculus.finite_diff.finite_diff_weights + + """ + from sympy.calculus.finite_diff import _as_finite_diff + return _as_finite_diff(self, points, x0, wrt) + + @classmethod + def _get_zero_with_shape_like(cls, expr): + return S.Zero + + @classmethod + def _dispatch_eval_derivative_n_times(cls, expr, v, count): + # Evaluate the derivative `n` times. If + # `_eval_derivative_n_times` is not overridden by the current + # object, the default in `Basic` will call a loop over + # `_eval_derivative`: + return expr._eval_derivative_n_times(v, count) + + +def _derivative_dispatch(expr, *variables, **kwargs): + from sympy.matrices.matrixbase import MatrixBase + from sympy.matrices.expressions.matexpr import MatrixExpr + from sympy.tensor.array import NDimArray + array_types = (MatrixBase, MatrixExpr, NDimArray, list, tuple, Tuple) + if isinstance(expr, array_types) or any(isinstance(i[0], array_types) if isinstance(i, (tuple, list, Tuple)) else isinstance(i, array_types) for i in variables): + from sympy.tensor.array.array_derivatives import ArrayDerivative + return ArrayDerivative(expr, *variables, **kwargs) + return Derivative(expr, *variables, **kwargs) + + +class Lambda(Expr): + """ + Lambda(x, expr) represents a lambda function similar to Python's + 'lambda x: expr'. A function of several variables is written as + Lambda((x, y, ...), expr). + + Examples + ======== + + A simple example: + + >>> from sympy import Lambda + >>> from sympy.abc import x + >>> f = Lambda(x, x**2) + >>> f(4) + 16 + + For multivariate functions, use: + + >>> from sympy.abc import y, z, t + >>> f2 = Lambda((x, y, z, t), x + y**z + t**z) + >>> f2(1, 2, 3, 4) + 73 + + It is also possible to unpack tuple arguments: + + >>> f = Lambda(((x, y), z), x + y + z) + >>> f((1, 2), 3) + 6 + + A handy shortcut for lots of arguments: + + >>> p = x, y, z + >>> f = Lambda(p, x + y*z) + >>> f(*p) + x + y*z + + """ + is_Function = True + + def __new__(cls, signature, expr) -> Lambda: + if iterable(signature) and not isinstance(signature, (tuple, Tuple)): + sympy_deprecation_warning( + """ + Using a non-tuple iterable as the first argument to Lambda + is deprecated. Use Lambda(tuple(args), expr) instead. + """, + deprecated_since_version="1.5", + active_deprecations_target="deprecated-non-tuple-lambda", + ) + signature = tuple(signature) + _sig = signature if iterable(signature) else (signature,) + sig: Tuple = sympify(_sig) # type: ignore + cls._check_signature(sig) + + if len(sig) == 1 and sig[0] == expr: + return S.IdentityFunction + + return Expr.__new__(cls, sig, sympify(expr)) + + @classmethod + def _check_signature(cls, sig): + syms = set() + + def rcheck(args): + for a in args: + if a.is_symbol: + if a in syms: + raise BadSignatureError("Duplicate symbol %s" % a) + syms.add(a) + elif isinstance(a, Tuple): + rcheck(a) + else: + raise BadSignatureError("Lambda signature should be only tuples" + " and symbols, not %s" % a) + + if not isinstance(sig, Tuple): + raise BadSignatureError("Lambda signature should be a tuple not %s" % sig) + # Recurse through the signature: + rcheck(sig) + + @property + def signature(self): + """The expected form of the arguments to be unpacked into variables""" + return self._args[0] + + @property + def expr(self): + """The return value of the function""" + return self._args[1] + + @property + def variables(self): + """The variables used in the internal representation of the function""" + def _variables(args): + if isinstance(args, Tuple): + for arg in args: + yield from _variables(arg) + else: + yield args + return tuple(_variables(self.signature)) + + @property + def nargs(self): + from sympy.sets.sets import FiniteSet + return FiniteSet(len(self.signature)) + + bound_symbols = variables + + @property + def free_symbols(self): + return self.expr.free_symbols - set(self.variables) + + def __call__(self, *args): + n = len(args) + if n not in self.nargs: # Lambda only ever has 1 value in nargs + # XXX: exception message must be in exactly this format to + # make it work with NumPy's functions like vectorize(). See, + # for example, https://github.com/numpy/numpy/issues/1697. + # The ideal solution would be just to attach metadata to + # the exception and change NumPy to take advantage of this. + ## XXX does this apply to Lambda? If not, remove this comment. + temp = ('%(name)s takes exactly %(args)s ' + 'argument%(plural)s (%(given)s given)') + raise BadArgumentsError(temp % { + 'name': self, + 'args': list(self.nargs)[0], + 'plural': 's'*(list(self.nargs)[0] != 1), + 'given': n}) + + d = self._match_signature(self.signature, args) + + return self.expr.xreplace(d) + + def _match_signature(self, sig, args): + + symargmap = {} + + def rmatch(pars, args): + for par, arg in zip(pars, args): + if par.is_symbol: + symargmap[par] = arg + elif isinstance(par, Tuple): + if not isinstance(arg, (tuple, Tuple)) or len(args) != len(pars): + raise BadArgumentsError("Can't match %s and %s" % (args, pars)) + rmatch(par, arg) + + rmatch(sig, args) + + return symargmap + + @property + def is_identity(self): + """Return ``True`` if this ``Lambda`` is an identity function. """ + return self.signature == self.expr + + def _eval_evalf(self, prec): + return self.func(self.args[0], self.args[1].evalf(n=prec_to_dps(prec))) + + +class Subs(Expr): + """ + Represents unevaluated substitutions of an expression. + + ``Subs(expr, x, x0)`` represents the expression resulting + from substituting x with x0 in expr. + + Parameters + ========== + + expr : Expr + An expression. + + x : tuple, variable + A variable or list of distinct variables. + + x0 : tuple or list of tuples + A point or list of evaluation points + corresponding to those variables. + + Examples + ======== + + >>> from sympy import Subs, Function, sin, cos + >>> from sympy.abc import x, y, z + >>> f = Function('f') + + Subs are created when a particular substitution cannot be made. The + x in the derivative cannot be replaced with 0 because 0 is not a + valid variables of differentiation: + + >>> f(x).diff(x).subs(x, 0) + Subs(Derivative(f(x), x), x, 0) + + Once f is known, the derivative and evaluation at 0 can be done: + + >>> _.subs(f, sin).doit() == sin(x).diff(x).subs(x, 0) == cos(0) + True + + Subs can also be created directly with one or more variables: + + >>> Subs(f(x)*sin(y) + z, (x, y), (0, 1)) + Subs(z + f(x)*sin(y), (x, y), (0, 1)) + >>> _.doit() + z + f(0)*sin(1) + + Notes + ===== + + ``Subs`` objects are generally useful to represent unevaluated derivatives + calculated at a point. + + The variables may be expressions, but they are subjected to the limitations + of subs(), so it is usually a good practice to use only symbols for + variables, since in that case there can be no ambiguity. + + There's no automatic expansion - use the method .doit() to effect all + possible substitutions of the object and also of objects inside the + expression. + + When evaluating derivatives at a point that is not a symbol, a Subs object + is returned. One is also able to calculate derivatives of Subs objects - in + this case the expression is always expanded (for the unevaluated form, use + Derivative()). + + In order to allow expressions to combine before doit is done, a + representation of the Subs expression is used internally to make + expressions that are superficially different compare the same: + + >>> a, b = Subs(x, x, 0), Subs(y, y, 0) + >>> a + b + 2*Subs(x, x, 0) + + This can lead to unexpected consequences when using methods + like `has` that are cached: + + >>> s = Subs(x, x, 0) + >>> s.has(x), s.has(y) + (True, False) + >>> ss = s.subs(x, y) + >>> ss.has(x), ss.has(y) + (True, False) + >>> s, ss + (Subs(x, x, 0), Subs(y, y, 0)) + """ + def __new__(cls, expr, variables, point, **assumptions): + if not is_sequence(variables, Tuple): + variables = [variables] + variables = Tuple(*variables) + + if has_dups(variables): + repeated = [str(v) for v, i in Counter(variables).items() if i > 1] + __ = ', '.join(repeated) + raise ValueError(filldedent(''' + The following expressions appear more than once: %s + ''' % __)) + + point = Tuple(*(point if is_sequence(point, Tuple) else [point])) + + if len(point) != len(variables): + raise ValueError('Number of point values must be the same as ' + 'the number of variables.') + + if not point: + return sympify(expr) + + # denest + if isinstance(expr, Subs): + variables = expr.variables + variables + point = expr.point + point + expr = expr.expr + else: + expr = sympify(expr) + + # use symbols with names equal to the point value (with prepended _) + # to give a variable-independent expression + pre = "_" + pts = sorted(set(point), key=default_sort_key) + from sympy.printing.str import StrPrinter + class CustomStrPrinter(StrPrinter): + def _print_Dummy(self, expr): + return str(expr) + str(expr.dummy_index) + def mystr(expr, **settings): + p = CustomStrPrinter(settings) + return p.doprint(expr) + while 1: + s_pts = {p: Symbol(pre + mystr(p)) for p in pts} + reps = [(v, s_pts[p]) + for v, p in zip(variables, point)] + # if any underscore-prepended symbol is already a free symbol + # and is a variable with a different point value, then there + # is a clash, e.g. _0 clashes in Subs(_0 + _1, (_0, _1), (1, 0)) + # because the new symbol that would be created is _1 but _1 + # is already mapped to 0 so __0 and __1 are used for the new + # symbols + if any(r in expr.free_symbols and + r in variables and + Symbol(pre + mystr(point[variables.index(r)])) != r + for _, r in reps): + pre += "_" + continue + break + + obj = Expr.__new__(cls, expr, Tuple(*variables), point) + obj._expr = expr.xreplace(dict(reps)) + return obj + + def _eval_is_commutative(self): + return self.expr.is_commutative + + def doit(self, **hints): + e, v, p = self.args + + # remove self mappings + for i, (vi, pi) in enumerate(zip(v, p)): + if vi == pi: + v = v[:i] + v[i + 1:] + p = p[:i] + p[i + 1:] + if not v: + return self.expr + + if isinstance(e, Derivative): + # apply functions first, e.g. f -> cos + undone = [] + for i, vi in enumerate(v): + if isinstance(vi, FunctionClass): + e = e.subs(vi, p[i]) + else: + undone.append((vi, p[i])) + if not isinstance(e, Derivative): + e = e.doit() + if isinstance(e, Derivative): + # do Subs that aren't related to differentiation + undone2 = [] + D = Dummy() + arg = e.args[0] + for vi, pi in undone: + if D not in e.xreplace({vi: D}).free_symbols: + if arg.has(vi): + e = e.subs(vi, pi) + else: + undone2.append((vi, pi)) + undone = undone2 + # differentiate wrt variables that are present + wrt = [] + D = Dummy() + expr = e.expr + free = expr.free_symbols + for vi, ci in e.variable_count: + if isinstance(vi, Symbol) and vi in free: + expr = expr.diff((vi, ci)) + elif D in expr.subs(vi, D).free_symbols: + expr = expr.diff((vi, ci)) + else: + wrt.append((vi, ci)) + # inject remaining subs + rv = expr.subs(undone) + # do remaining differentiation *in order given* + for vc in wrt: + rv = rv.diff(vc) + else: + # inject remaining subs + rv = e.subs(undone) + else: + rv = e.doit(**hints).subs(list(zip(v, p))) + + if hints.get('deep', True) and rv != self: + rv = rv.doit(**hints) + return rv + + def evalf(self, prec=None, **options): + return self.doit().evalf(prec, **options) + + n = evalf # type:ignore + + @property + def variables(self): + """The variables to be evaluated""" + return self._args[1] + + bound_symbols = variables + + @property + def expr(self): + """The expression on which the substitution operates""" + return self._args[0] + + @property + def point(self): + """The values for which the variables are to be substituted""" + return self._args[2] + + @property + def free_symbols(self): + return (self.expr.free_symbols - set(self.variables) | + set(self.point.free_symbols)) + + @property + def expr_free_symbols(self): + sympy_deprecation_warning(""" + The expr_free_symbols property is deprecated. Use free_symbols to get + the free symbols of an expression. + """, + deprecated_since_version="1.9", + active_deprecations_target="deprecated-expr-free-symbols") + # Don't show the warning twice from the recursive call + with ignore_warnings(SymPyDeprecationWarning): + return (self.expr.expr_free_symbols - set(self.variables) | + set(self.point.expr_free_symbols)) + + def __eq__(self, other): + if not isinstance(other, Subs): + return False + return self._hashable_content() == other._hashable_content() + + def __ne__(self, other): + return not(self == other) + + def __hash__(self): + return super().__hash__() + + def _hashable_content(self): + return (self._expr.xreplace(self.canonical_variables), + ) + tuple(ordered([(v, p) for v, p in + zip(self.variables, self.point) if not self.expr.has(v)])) + + def _eval_subs(self, old, new): + # Subs doit will do the variables in order; the semantics + # of subs for Subs is have the following invariant for + # Subs object foo: + # foo.doit().subs(reps) == foo.subs(reps).doit() + pt = list(self.point) + if old in self.variables: + if _atomic(new) == {new} and not any( + i.has(new) for i in self.args): + # the substitution is neutral + return self.xreplace({old: new}) + # any occurrence of old before this point will get + # handled by replacements from here on + i = self.variables.index(old) + for j in range(i, len(self.variables)): + pt[j] = pt[j]._subs(old, new) + return self.func(self.expr, self.variables, pt) + v = [i._subs(old, new) for i in self.variables] + if v != list(self.variables): + return self.func(self.expr, self.variables + (old,), pt + [new]) + expr = self.expr._subs(old, new) + pt = [i._subs(old, new) for i in self.point] + return self.func(expr, v, pt) + + def _eval_derivative(self, s): + # Apply the chain rule of the derivative on the substitution variables: + f = self.expr + vp = V, P = self.variables, self.point + val = Add.fromiter(p.diff(s)*Subs(f.diff(v), *vp).doit() + for v, p in zip(V, P)) + + # these are all the free symbols in the expr + efree = f.free_symbols + # some symbols like IndexedBase include themselves and args + # as free symbols + compound = {i for i in efree if len(i.free_symbols) > 1} + # hide them and see what independent free symbols remain + dums = {Dummy() for i in compound} + masked = f.xreplace(dict(zip(compound, dums))) + ifree = masked.free_symbols - dums + # include the compound symbols + free = ifree | compound + # remove the variables already handled + free -= set(V) + # add back any free symbols of remaining compound symbols + free |= {i for j in free & compound for i in j.free_symbols} + # if symbols of s are in free then there is more to do + if free & s.free_symbols: + val += Subs(f.diff(s), self.variables, self.point).doit() + return val + + def _eval_nseries(self, x, n, logx, cdir=0): + if x in self.point: + # x is the variable being substituted into + apos = self.point.index(x) + other = self.variables[apos] + else: + other = x + arg = self.expr.nseries(other, n=n, logx=logx) + o = arg.getO() + terms = Add.make_args(arg.removeO()) + rv = Add(*[self.func(a, *self.args[1:]) for a in terms]) + if o: + rv += o.subs(other, x) + return rv + + def _eval_as_leading_term(self, x, logx, cdir): + if x in self.point: + ipos = self.point.index(x) + xvar = self.variables[ipos] + return self.expr.as_leading_term(xvar) + if x in self.variables: + # if `x` is a dummy variable, it means it won't exist after the + # substitution has been performed: + return self + # The variable is independent of the substitution: + return self.expr.as_leading_term(x) + + +def diff(f, *symbols, **kwargs): + """ + Differentiate f with respect to symbols. + + Explanation + =========== + + This is just a wrapper to unify .diff() and the Derivative class; its + interface is similar to that of integrate(). You can use the same + shortcuts for multiple variables as with Derivative. For example, + diff(f(x), x, x, x) and diff(f(x), x, 3) both return the third derivative + of f(x). + + You can pass evaluate=False to get an unevaluated Derivative class. Note + that if there are 0 symbols (such as diff(f(x), x, 0), then the result will + be the function (the zeroth derivative), even if evaluate=False. + + Examples + ======== + + >>> from sympy import sin, cos, Function, diff + >>> from sympy.abc import x, y + >>> f = Function('f') + + >>> diff(sin(x), x) + cos(x) + >>> diff(f(x), x, x, x) + Derivative(f(x), (x, 3)) + >>> diff(f(x), x, 3) + Derivative(f(x), (x, 3)) + >>> diff(sin(x)*cos(y), x, 2, y, 2) + sin(x)*cos(y) + + >>> type(diff(sin(x), x)) + cos + >>> type(diff(sin(x), x, evaluate=False)) + + >>> type(diff(sin(x), x, 0)) + sin + >>> type(diff(sin(x), x, 0, evaluate=False)) + sin + + >>> diff(sin(x)) + cos(x) + >>> diff(sin(x*y)) + Traceback (most recent call last): + ... + ValueError: specify differentiation variables to differentiate sin(x*y) + + Note that ``diff(sin(x))`` syntax is meant only for convenience + in interactive sessions and should be avoided in library code. + + References + ========== + + .. [1] https://reference.wolfram.com/legacy/v5_2/Built-inFunctions/AlgebraicComputation/Calculus/D.html + + See Also + ======== + + Derivative + idiff: computes the derivative implicitly + + """ + if hasattr(f, 'diff'): + return f.diff(*symbols, **kwargs) + kwargs.setdefault('evaluate', True) + return _derivative_dispatch(f, *symbols, **kwargs) + + +def expand(e, deep=True, modulus=None, power_base=True, power_exp=True, + mul=True, log=True, multinomial=True, basic=True, **hints): + r""" + Expand an expression using methods given as hints. + + Explanation + =========== + + Hints evaluated unless explicitly set to False are: ``basic``, ``log``, + ``multinomial``, ``mul``, ``power_base``, and ``power_exp`` The following + hints are supported but not applied unless set to True: ``complex``, + ``func``, and ``trig``. In addition, the following meta-hints are + supported by some or all of the other hints: ``frac``, ``numer``, + ``denom``, ``modulus``, and ``force``. ``deep`` is supported by all + hints. Additionally, subclasses of Expr may define their own hints or + meta-hints. + + The ``basic`` hint is used for any special rewriting of an object that + should be done automatically (along with the other hints like ``mul``) + when expand is called. This is a catch-all hint to handle any sort of + expansion that may not be described by the existing hint names. To use + this hint an object should override the ``_eval_expand_basic`` method. + Objects may also define their own expand methods, which are not run by + default. See the API section below. + + If ``deep`` is set to ``True`` (the default), things like arguments of + functions are recursively expanded. Use ``deep=False`` to only expand on + the top level. + + If the ``force`` hint is used, assumptions about variables will be ignored + in making the expansion. + + Hints + ===== + + These hints are run by default + + mul + --- + + Distributes multiplication over addition: + + >>> from sympy import cos, exp, sin + >>> from sympy.abc import x, y, z + >>> (y*(x + z)).expand(mul=True) + x*y + y*z + + multinomial + ----------- + + Expand (x + y + ...)**n where n is a positive integer. + + >>> ((x + y + z)**2).expand(multinomial=True) + x**2 + 2*x*y + 2*x*z + y**2 + 2*y*z + z**2 + + power_exp + --------- + + Expand addition in exponents into multiplied bases. + + >>> exp(x + y).expand(power_exp=True) + exp(x)*exp(y) + >>> (2**(x + y)).expand(power_exp=True) + 2**x*2**y + + power_base + ---------- + + Split powers of multiplied bases. + + This only happens by default if assumptions allow, or if the + ``force`` meta-hint is used: + + >>> ((x*y)**z).expand(power_base=True) + (x*y)**z + >>> ((x*y)**z).expand(power_base=True, force=True) + x**z*y**z + >>> ((2*y)**z).expand(power_base=True) + 2**z*y**z + + Note that in some cases where this expansion always holds, SymPy performs + it automatically: + + >>> (x*y)**2 + x**2*y**2 + + log + --- + + Pull out power of an argument as a coefficient and split logs products + into sums of logs. + + Note that these only work if the arguments of the log function have the + proper assumptions--the arguments must be positive and the exponents must + be real--or else the ``force`` hint must be True: + + >>> from sympy import log, symbols + >>> log(x**2*y).expand(log=True) + log(x**2*y) + >>> log(x**2*y).expand(log=True, force=True) + 2*log(x) + log(y) + >>> x, y = symbols('x,y', positive=True) + >>> log(x**2*y).expand(log=True) + 2*log(x) + log(y) + + basic + ----- + + This hint is intended primarily as a way for custom subclasses to enable + expansion by default. + + These hints are not run by default: + + complex + ------- + + Split an expression into real and imaginary parts. + + >>> x, y = symbols('x,y') + >>> (x + y).expand(complex=True) + re(x) + re(y) + I*im(x) + I*im(y) + >>> cos(x).expand(complex=True) + -I*sin(re(x))*sinh(im(x)) + cos(re(x))*cosh(im(x)) + + Note that this is just a wrapper around ``as_real_imag()``. Most objects + that wish to redefine ``_eval_expand_complex()`` should consider + redefining ``as_real_imag()`` instead. + + func + ---- + + Expand other functions. + + >>> from sympy import gamma + >>> gamma(x + 1).expand(func=True) + x*gamma(x) + + trig + ---- + + Do trigonometric expansions. + + >>> cos(x + y).expand(trig=True) + -sin(x)*sin(y) + cos(x)*cos(y) + >>> sin(2*x).expand(trig=True) + 2*sin(x)*cos(x) + + Note that the forms of ``sin(n*x)`` and ``cos(n*x)`` in terms of ``sin(x)`` + and ``cos(x)`` are not unique, due to the identity `\sin^2(x) + \cos^2(x) + = 1`. The current implementation uses the form obtained from Chebyshev + polynomials, but this may change. See `this MathWorld article + `_ for more + information. + + Notes + ===== + + - You can shut off unwanted methods:: + + >>> (exp(x + y)*(x + y)).expand() + x*exp(x)*exp(y) + y*exp(x)*exp(y) + >>> (exp(x + y)*(x + y)).expand(power_exp=False) + x*exp(x + y) + y*exp(x + y) + >>> (exp(x + y)*(x + y)).expand(mul=False) + (x + y)*exp(x)*exp(y) + + - Use deep=False to only expand on the top level:: + + >>> exp(x + exp(x + y)).expand() + exp(x)*exp(exp(x)*exp(y)) + >>> exp(x + exp(x + y)).expand(deep=False) + exp(x)*exp(exp(x + y)) + + - Hints are applied in an arbitrary, but consistent order (in the current + implementation, they are applied in alphabetical order, except + multinomial comes before mul, but this may change). Because of this, + some hints may prevent expansion by other hints if they are applied + first. For example, ``mul`` may distribute multiplications and prevent + ``log`` and ``power_base`` from expanding them. Also, if ``mul`` is + applied before ``multinomial`, the expression might not be fully + distributed. The solution is to use the various ``expand_hint`` helper + functions or to use ``hint=False`` to this function to finely control + which hints are applied. Here are some examples:: + + >>> from sympy import expand, expand_mul, expand_power_base + >>> x, y, z = symbols('x,y,z', positive=True) + + >>> expand(log(x*(y + z))) + log(x) + log(y + z) + + Here, we see that ``log`` was applied before ``mul``. To get the mul + expanded form, either of the following will work:: + + >>> expand_mul(log(x*(y + z))) + log(x*y + x*z) + >>> expand(log(x*(y + z)), log=False) + log(x*y + x*z) + + A similar thing can happen with the ``power_base`` hint:: + + >>> expand((x*(y + z))**x) + (x*y + x*z)**x + + To get the ``power_base`` expanded form, either of the following will + work:: + + >>> expand((x*(y + z))**x, mul=False) + x**x*(y + z)**x + >>> expand_power_base((x*(y + z))**x) + x**x*(y + z)**x + + >>> expand((x + y)*y/x) + y + y**2/x + + The parts of a rational expression can be targeted:: + + >>> expand((x + y)*y/x/(x + 1), frac=True) + (x*y + y**2)/(x**2 + x) + >>> expand((x + y)*y/x/(x + 1), numer=True) + (x*y + y**2)/(x*(x + 1)) + >>> expand((x + y)*y/x/(x + 1), denom=True) + y*(x + y)/(x**2 + x) + + - The ``modulus`` meta-hint can be used to reduce the coefficients of an + expression post-expansion:: + + >>> expand((3*x + 1)**2) + 9*x**2 + 6*x + 1 + >>> expand((3*x + 1)**2, modulus=5) + 4*x**2 + x + 1 + + - Either ``expand()`` the function or ``.expand()`` the method can be + used. Both are equivalent:: + + >>> expand((x + 1)**2) + x**2 + 2*x + 1 + >>> ((x + 1)**2).expand() + x**2 + 2*x + 1 + + API + === + + Objects can define their own expand hints by defining + ``_eval_expand_hint()``. The function should take the form:: + + def _eval_expand_hint(self, **hints): + # Only apply the method to the top-level expression + ... + + See also the example below. Objects should define ``_eval_expand_hint()`` + methods only if ``hint`` applies to that specific object. The generic + ``_eval_expand_hint()`` method defined in Expr will handle the no-op case. + + Each hint should be responsible for expanding that hint only. + Furthermore, the expansion should be applied to the top-level expression + only. ``expand()`` takes care of the recursion that happens when + ``deep=True``. + + You should only call ``_eval_expand_hint()`` methods directly if you are + 100% sure that the object has the method, as otherwise you are liable to + get unexpected ``AttributeError``s. Note, again, that you do not need to + recursively apply the hint to args of your object: this is handled + automatically by ``expand()``. ``_eval_expand_hint()`` should + generally not be used at all outside of an ``_eval_expand_hint()`` method. + If you want to apply a specific expansion from within another method, use + the public ``expand()`` function, method, or ``expand_hint()`` functions. + + In order for expand to work, objects must be rebuildable by their args, + i.e., ``obj.func(*obj.args) == obj`` must hold. + + Expand methods are passed ``**hints`` so that expand hints may use + 'metahints'--hints that control how different expand methods are applied. + For example, the ``force=True`` hint described above that causes + ``expand(log=True)`` to ignore assumptions is such a metahint. The + ``deep`` meta-hint is handled exclusively by ``expand()`` and is not + passed to ``_eval_expand_hint()`` methods. + + Note that expansion hints should generally be methods that perform some + kind of 'expansion'. For hints that simply rewrite an expression, use the + .rewrite() API. + + Examples + ======== + + >>> from sympy import Expr, sympify + >>> class MyClass(Expr): + ... def __new__(cls, *args): + ... args = sympify(args) + ... return Expr.__new__(cls, *args) + ... + ... def _eval_expand_double(self, *, force=False, **hints): + ... ''' + ... Doubles the args of MyClass. + ... + ... If there more than four args, doubling is not performed, + ... unless force=True is also used (False by default). + ... ''' + ... if not force and len(self.args) > 4: + ... return self + ... return self.func(*(self.args + self.args)) + ... + >>> a = MyClass(1, 2, MyClass(3, 4)) + >>> a + MyClass(1, 2, MyClass(3, 4)) + >>> a.expand(double=True) + MyClass(1, 2, MyClass(3, 4, 3, 4), 1, 2, MyClass(3, 4, 3, 4)) + >>> a.expand(double=True, deep=False) + MyClass(1, 2, MyClass(3, 4), 1, 2, MyClass(3, 4)) + + >>> b = MyClass(1, 2, 3, 4, 5) + >>> b.expand(double=True) + MyClass(1, 2, 3, 4, 5) + >>> b.expand(double=True, force=True) + MyClass(1, 2, 3, 4, 5, 1, 2, 3, 4, 5) + + See Also + ======== + + expand_log, expand_mul, expand_multinomial, expand_complex, expand_trig, + expand_power_base, expand_power_exp, expand_func, sympy.simplify.hyperexpand.hyperexpand + + """ + # don't modify this; modify the Expr.expand method + hints['power_base'] = power_base + hints['power_exp'] = power_exp + hints['mul'] = mul + hints['log'] = log + hints['multinomial'] = multinomial + hints['basic'] = basic + return sympify(e).expand(deep=deep, modulus=modulus, **hints) + +# This is a special application of two hints + +def _mexpand(expr, recursive=False): + # expand multinomials and then expand products; this may not always + # be sufficient to give a fully expanded expression (see + # test_issue_8247_8354 in test_arit) + if expr is None: + return + was = None + while was != expr: + was, expr = expr, expand_mul(expand_multinomial(expr)) + if not recursive: + break + return expr + + +# These are simple wrappers around single hints. + + +def expand_mul(expr, deep=True): + """ + Wrapper around expand that only uses the mul hint. See the expand + docstring for more information. + + Examples + ======== + + >>> from sympy import symbols, expand_mul, exp, log + >>> x, y = symbols('x,y', positive=True) + >>> expand_mul(exp(x+y)*(x+y)*log(x*y**2)) + x*exp(x + y)*log(x*y**2) + y*exp(x + y)*log(x*y**2) + + """ + return sympify(expr).expand(deep=deep, mul=True, power_exp=False, + power_base=False, basic=False, multinomial=False, log=False) + + +def expand_multinomial(expr, deep=True): + """ + Wrapper around expand that only uses the multinomial hint. See the expand + docstring for more information. + + Examples + ======== + + >>> from sympy import symbols, expand_multinomial, exp + >>> x, y = symbols('x y', positive=True) + >>> expand_multinomial((x + exp(x + 1))**2) + x**2 + 2*x*exp(x + 1) + exp(2*x + 2) + + """ + return sympify(expr).expand(deep=deep, mul=False, power_exp=False, + power_base=False, basic=False, multinomial=True, log=False) + + +def expand_log(expr, deep=True, force=False, factor=False): + """ + Wrapper around expand that only uses the log hint. See the expand + docstring for more information. + + Examples + ======== + + >>> from sympy import symbols, expand_log, exp, log + >>> x, y = symbols('x,y', positive=True) + >>> expand_log(exp(x+y)*(x+y)*log(x*y**2)) + (x + y)*(log(x) + 2*log(y))*exp(x + y) + + """ + from sympy.functions.elementary.exponential import log + from sympy.simplify.radsimp import fraction + if factor is False: + def _handleMul(x): + # look for the simple case of expanded log(b**a)/log(b) -> a in args + n, d = fraction(x) + n = [i for i in n.atoms(log) if i.args[0].is_Integer] + d = [i for i in d.atoms(log) if i.args[0].is_Integer] + if len(n) == 1 and len(d) == 1: + n = n[0] + d = d[0] + from sympy import multiplicity + m = multiplicity(d.args[0], n.args[0]) + if m: + r = m + log(n.args[0]//d.args[0]**m)/d + x = x.subs(n, d*r) + x1 = expand_mul(expand_log(x, deep=deep, force=force, factor=True)) + if x1.count(log) <= x.count(log): + return x1 + return x + + expr = expr.replace( + lambda x: x.is_Mul and all(any(isinstance(i, log) and i.args[0].is_Rational + for i in Mul.make_args(j)) for j in x.as_numer_denom()), + _handleMul) + + return sympify(expr).expand(deep=deep, log=True, mul=False, + power_exp=False, power_base=False, multinomial=False, + basic=False, force=force, factor=factor) + + +def expand_func(expr, deep=True): + """ + Wrapper around expand that only uses the func hint. See the expand + docstring for more information. + + Examples + ======== + + >>> from sympy import expand_func, gamma + >>> from sympy.abc import x + >>> expand_func(gamma(x + 2)) + x*(x + 1)*gamma(x) + + """ + return sympify(expr).expand(deep=deep, func=True, basic=False, + log=False, mul=False, power_exp=False, power_base=False, multinomial=False) + + +def expand_trig(expr, deep=True): + """ + Wrapper around expand that only uses the trig hint. See the expand + docstring for more information. + + Examples + ======== + + >>> from sympy import expand_trig, sin + >>> from sympy.abc import x, y + >>> expand_trig(sin(x+y)*(x+y)) + (x + y)*(sin(x)*cos(y) + sin(y)*cos(x)) + + """ + return sympify(expr).expand(deep=deep, trig=True, basic=False, + log=False, mul=False, power_exp=False, power_base=False, multinomial=False) + + +def expand_complex(expr, deep=True): + """ + Wrapper around expand that only uses the complex hint. See the expand + docstring for more information. + + Examples + ======== + + >>> from sympy import expand_complex, exp, sqrt, I + >>> from sympy.abc import z + >>> expand_complex(exp(z)) + I*exp(re(z))*sin(im(z)) + exp(re(z))*cos(im(z)) + >>> expand_complex(sqrt(I)) + sqrt(2)/2 + sqrt(2)*I/2 + + See Also + ======== + + sympy.core.expr.Expr.as_real_imag + """ + return sympify(expr).expand(deep=deep, complex=True, basic=False, + log=False, mul=False, power_exp=False, power_base=False, multinomial=False) + + +def expand_power_base(expr, deep=True, force=False): + """ + Wrapper around expand that only uses the power_base hint. + + A wrapper to expand(power_base=True) which separates a power with a base + that is a Mul into a product of powers, without performing any other + expansions, provided that assumptions about the power's base and exponent + allow. + + deep=False (default is True) will only apply to the top-level expression. + + force=True (default is False) will cause the expansion to ignore + assumptions about the base and exponent. When False, the expansion will + only happen if the base is non-negative or the exponent is an integer. + + >>> from sympy.abc import x, y, z + >>> from sympy import expand_power_base, sin, cos, exp, Symbol + + >>> (x*y)**2 + x**2*y**2 + + >>> (2*x)**y + (2*x)**y + >>> expand_power_base(_) + 2**y*x**y + + >>> expand_power_base((x*y)**z) + (x*y)**z + >>> expand_power_base((x*y)**z, force=True) + x**z*y**z + >>> expand_power_base(sin((x*y)**z), deep=False) + sin((x*y)**z) + >>> expand_power_base(sin((x*y)**z), force=True) + sin(x**z*y**z) + + >>> expand_power_base((2*sin(x))**y + (2*cos(x))**y) + 2**y*sin(x)**y + 2**y*cos(x)**y + + >>> expand_power_base((2*exp(y))**x) + 2**x*exp(y)**x + + >>> expand_power_base((2*cos(x))**y) + 2**y*cos(x)**y + + Notice that sums are left untouched. If this is not the desired behavior, + apply full ``expand()`` to the expression: + + >>> expand_power_base(((x+y)*z)**2) + z**2*(x + y)**2 + >>> (((x+y)*z)**2).expand() + x**2*z**2 + 2*x*y*z**2 + y**2*z**2 + + >>> expand_power_base((2*y)**(1+z)) + 2**(z + 1)*y**(z + 1) + >>> ((2*y)**(1+z)).expand() + 2*2**z*y**(z + 1) + + The power that is unexpanded can be expanded safely when + ``y != 0``, otherwise different values might be obtained for the expression: + + >>> prev = _ + + If we indicate that ``y`` is positive but then replace it with + a value of 0 after expansion, the expression becomes 0: + + >>> p = Symbol('p', positive=True) + >>> prev.subs(y, p).expand().subs(p, 0) + 0 + + But if ``z = -1`` the expression would not be zero: + + >>> prev.subs(y, 0).subs(z, -1) + 1 + + See Also + ======== + + expand + + """ + return sympify(expr).expand(deep=deep, log=False, mul=False, + power_exp=False, power_base=True, multinomial=False, + basic=False, force=force) + + +def expand_power_exp(expr, deep=True): + """ + Wrapper around expand that only uses the power_exp hint. + + See the expand docstring for more information. + + Examples + ======== + + >>> from sympy import expand_power_exp, Symbol + >>> from sympy.abc import x, y + >>> expand_power_exp(3**(y + 2)) + 9*3**y + >>> expand_power_exp(x**(y + 2)) + x**(y + 2) + + If ``x = 0`` the value of the expression depends on the + value of ``y``; if the expression were expanded the result + would be 0. So expansion is only done if ``x != 0``: + + >>> expand_power_exp(Symbol('x', zero=False)**(y + 2)) + x**2*x**y + """ + return sympify(expr).expand(deep=deep, complex=False, basic=False, + log=False, mul=False, power_exp=True, power_base=False, multinomial=False) + + +def count_ops(expr, visual=False): + """ + Return a representation (integer or expression) of the operations in expr. + + Parameters + ========== + + expr : Expr + If expr is an iterable, the sum of the op counts of the + items will be returned. + + visual : bool, optional + If ``False`` (default) then the sum of the coefficients of the + visual expression will be returned. + If ``True`` then the number of each type of operation is shown + with the core class types (or their virtual equivalent) multiplied by the + number of times they occur. + + Examples + ======== + + >>> from sympy.abc import a, b, x, y + >>> from sympy import sin, count_ops + + Although there is not a SUB object, minus signs are interpreted as + either negations or subtractions: + + >>> (x - y).count_ops(visual=True) + SUB + >>> (-x).count_ops(visual=True) + NEG + + Here, there are two Adds and a Pow: + + >>> (1 + a + b**2).count_ops(visual=True) + 2*ADD + POW + + In the following, an Add, Mul, Pow and two functions: + + >>> (sin(x)*x + sin(x)**2).count_ops(visual=True) + ADD + MUL + POW + 2*SIN + + for a total of 5: + + >>> (sin(x)*x + sin(x)**2).count_ops(visual=False) + 5 + + Note that "what you type" is not always what you get. The expression + 1/x/y is translated by sympy into 1/(x*y) so it gives a DIV and MUL rather + than two DIVs: + + >>> (1/x/y).count_ops(visual=True) + DIV + MUL + + The visual option can be used to demonstrate the difference in + operations for expressions in different forms. Here, the Horner + representation is compared with the expanded form of a polynomial: + + >>> eq=x*(1 + x*(2 + x*(3 + x))) + >>> count_ops(eq.expand(), visual=True) - count_ops(eq, visual=True) + -MUL + 3*POW + + The count_ops function also handles iterables: + + >>> count_ops([x, sin(x), None, True, x + 2], visual=False) + 2 + >>> count_ops([x, sin(x), None, True, x + 2], visual=True) + ADD + SIN + >>> count_ops({x: sin(x), x + 2: y + 1}, visual=True) + 2*ADD + SIN + + """ + from .relational import Relational + from sympy.concrete.summations import Sum + from sympy.integrals.integrals import Integral + from sympy.logic.boolalg import BooleanFunction + from sympy.simplify.radsimp import fraction + + expr = sympify(expr) + if isinstance(expr, Expr) and not expr.is_Relational: + + ops = [] + args = [expr] + NEG = Symbol('NEG') + DIV = Symbol('DIV') + SUB = Symbol('SUB') + ADD = Symbol('ADD') + EXP = Symbol('EXP') + while args: + a = args.pop() + + # if the following fails because the object is + # not Basic type, then the object should be fixed + # since it is the intention that all args of Basic + # should themselves be Basic + if a.is_Rational: + #-1/3 = NEG + DIV + if a is not S.One: + if a.p < 0: + ops.append(NEG) + if a.q != 1: + ops.append(DIV) + continue + elif a.is_Mul or a.is_MatMul: + if _coeff_isneg(a): + ops.append(NEG) + if a.args[0] is S.NegativeOne: + a = a.as_two_terms()[1] + else: + a = -a + n, d = fraction(a) + if n.is_Integer: + ops.append(DIV) + if n < 0: + ops.append(NEG) + args.append(d) + continue # won't be -Mul but could be Add + elif d is not S.One: + if not d.is_Integer: + args.append(d) + ops.append(DIV) + args.append(n) + continue # could be -Mul + elif a.is_Add or a.is_MatAdd: + aargs = list(a.args) + negs = 0 + for i, ai in enumerate(aargs): + if _coeff_isneg(ai): + negs += 1 + args.append(-ai) + if i > 0: + ops.append(SUB) + else: + args.append(ai) + if i > 0: + ops.append(ADD) + if negs == len(aargs): # -x - y = NEG + SUB + ops.append(NEG) + elif _coeff_isneg(aargs[0]): # -x + y = SUB, but already recorded ADD + ops.append(SUB - ADD) + continue + if a.is_Pow and a.exp is S.NegativeOne: + ops.append(DIV) + args.append(a.base) # won't be -Mul but could be Add + continue + if a == S.Exp1: + ops.append(EXP) + continue + if a.is_Pow and a.base == S.Exp1: + ops.append(EXP) + args.append(a.exp) + continue + if a.is_Mul or isinstance(a, LatticeOp): + o = Symbol(a.func.__name__.upper()) + # count the args + ops.append(o*(len(a.args) - 1)) + elif a.args and ( + a.is_Pow or a.is_Function or isinstance(a, (Derivative, Integral, Sum))): + # if it's not in the list above we don't + # consider a.func something to count, e.g. + # Tuple, MatrixSymbol, etc... + if isinstance(a.func, UndefinedFunction): + o = Symbol("FUNC_" + a.func.__name__.upper()) + else: + o = Symbol(a.func.__name__.upper()) + ops.append(o) + + if not a.is_Symbol: + args.extend(a.args) + + elif isinstance(expr, Dict): + ops = [count_ops(k, visual=visual) + + count_ops(v, visual=visual) for k, v in expr.items()] + elif iterable(expr): + ops = [count_ops(i, visual=visual) for i in expr] + elif isinstance(expr, (Relational, BooleanFunction)): + ops = [] + for arg in expr.args: + ops.append(count_ops(arg, visual=True)) + o = Symbol(func_name(expr, short=True).upper()) + ops.append(o) + elif not isinstance(expr, Basic): + ops = [] + else: # it's Basic not isinstance(expr, Expr): + if not isinstance(expr, Basic): + raise TypeError("Invalid type of expr") + else: + ops = [] + args = [expr] + while args: + a = args.pop() + + if a.args: + o = Symbol(type(a).__name__.upper()) + if a.is_Boolean: + ops.append(o*(len(a.args)-1)) + else: + ops.append(o) + args.extend(a.args) + + if not ops: + if visual: + return S.Zero + return 0 + + ops = Add(*ops) + + if visual: + return ops + + if ops.is_Number: + return int(ops) + + return sum(int((a.args or [1])[0]) for a in Add.make_args(ops)) + + +def nfloat(expr, n=15, exponent=False, dkeys=False): + """Make all Rationals in expr Floats except those in exponents + (unless the exponents flag is set to True) and those in undefined + functions. When processing dictionaries, do not modify the keys + unless ``dkeys=True``. + + Examples + ======== + + >>> from sympy import nfloat, cos, pi, sqrt + >>> from sympy.abc import x, y + >>> nfloat(x**4 + x/2 + cos(pi/3) + 1 + sqrt(y)) + x**4 + 0.5*x + sqrt(y) + 1.5 + >>> nfloat(x**4 + sqrt(y), exponent=True) + x**4.0 + y**0.5 + + Container types are not modified: + + >>> type(nfloat((1, 2))) is tuple + True + """ + from sympy.matrices.matrixbase import MatrixBase + + kw = {"n": n, "exponent": exponent, "dkeys": dkeys} + + if isinstance(expr, MatrixBase): + return expr.applyfunc(lambda e: nfloat(e, **kw)) + + # handling of iterable containers + if iterable(expr, exclude=str): + if isinstance(expr, (dict, Dict)): + if dkeys: + args = [tuple((nfloat(i, **kw) for i in a)) + for a in expr.items()] + else: + args = [(k, nfloat(v, **kw)) for k, v in expr.items()] + if isinstance(expr, dict): + return type(expr)(args) + else: + return expr.func(*args) + elif isinstance(expr, Basic): + return expr.func(*[nfloat(a, **kw) for a in expr.args]) + return type(expr)([nfloat(a, **kw) for a in expr]) + + rv = sympify(expr) + + if rv.is_Number: + return Float(rv, n) + elif rv.is_number: + # evalf doesn't always set the precision + rv = rv.n(n) + if rv.is_Number: + rv = Float(rv.n(n), n) + else: + pass # pure_complex(rv) is likely True + return rv + elif rv.is_Atom: + return rv + elif rv.is_Relational: + args_nfloat = (nfloat(arg, **kw) for arg in rv.args) + return rv.func(*args_nfloat) + + + # watch out for RootOf instances that don't like to have + # their exponents replaced with Dummies and also sometimes have + # problems with evaluating at low precision (issue 6393) + from sympy.polys.rootoftools import RootOf + rv = rv.xreplace({ro: ro.n(n) for ro in rv.atoms(RootOf)}) + + from .power import Pow + if not exponent: + reps = [(p, Pow(p.base, Dummy())) for p in rv.atoms(Pow)] + rv = rv.xreplace(dict(reps)) + rv = rv.n(n) + if not exponent: + rv = rv.xreplace({d.exp: p.exp for p, d in reps}) + else: + # Pow._eval_evalf special cases Integer exponents so if + # exponent is suppose to be handled we have to do so here + rv = rv.xreplace(Transform( + lambda x: Pow(x.base, Float(x.exp, n)), + lambda x: x.is_Pow and x.exp.is_Integer)) + + return rv.xreplace(Transform( + lambda x: x.func(*nfloat(x.args, n, exponent)), + lambda x: isinstance(x, Function) and not isinstance(x, AppliedUndef))) + + +from .symbol import Dummy, Symbol diff --git a/phivenv/Lib/site-packages/sympy/core/intfunc.py b/phivenv/Lib/site-packages/sympy/core/intfunc.py new file mode 100644 index 0000000000000000000000000000000000000000..50cb625dafcc1e795933311780e26423ddc6015a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/intfunc.py @@ -0,0 +1,530 @@ +""" +The routines here were removed from numbers.py, power.py, +digits.py and factor_.py so they could be imported into core +without raising circular import errors. + +Although the name 'intfunc' was chosen to represent functions that +work with integers, it can also be thought of as containing +internal/core functions that are needed by the classes of the core. +""" + +import math +import sys +from functools import lru_cache + +from .sympify import sympify +from .singleton import S +from sympy.external.gmpy import (gcd as number_gcd, lcm as number_lcm, sqrt, + iroot, bit_scan1, gcdext) +from sympy.utilities.misc import as_int, filldedent + + +def num_digits(n, base=10): + """Return the number of digits needed to express n in give base. + + Examples + ======== + + >>> from sympy.core.intfunc import num_digits + >>> num_digits(10) + 2 + >>> num_digits(10, 2) # 1010 -> 4 digits + 4 + >>> num_digits(-100, 16) # -64 -> 2 digits + 2 + + + Parameters + ========== + + n: integer + The number whose digits are counted. + + b: integer + The base in which digits are computed. + + See Also + ======== + sympy.ntheory.digits.digits, sympy.ntheory.digits.count_digits + """ + if base < 0: + raise ValueError('base must be int greater than 1') + if not n: + return 1 + e, t = integer_log(abs(n), base) + return 1 + e + + +def integer_log(n, b): + r""" + Returns ``(e, bool)`` where e is the largest nonnegative integer + such that :math:`|n| \geq |b^e|` and ``bool`` is True if $n = b^e$. + + Examples + ======== + + >>> from sympy import integer_log + >>> integer_log(125, 5) + (3, True) + >>> integer_log(17, 9) + (1, False) + + If the base is positive and the number negative the + return value will always be the same except for 2: + + >>> integer_log(-4, 2) + (2, False) + >>> integer_log(-16, 4) + (0, False) + + When the base is negative, the returned value + will only be True if the parity of the exponent is + correct for the sign of the base: + + >>> integer_log(4, -2) + (2, True) + >>> integer_log(8, -2) + (3, False) + >>> integer_log(-8, -2) + (3, True) + >>> integer_log(-4, -2) + (2, False) + + See Also + ======== + integer_nthroot + sympy.ntheory.primetest.is_square + sympy.ntheory.factor_.multiplicity + sympy.ntheory.factor_.perfect_power + """ + n = as_int(n) + b = as_int(b) + + if b < 0: + e, t = integer_log(abs(n), -b) + # (-2)**3 == -8 + # (-2)**2 = 4 + t = t and e % 2 == (n < 0) + return e, t + if b <= 1: + raise ValueError('base must be 2 or more') + if n < 0: + if b != 2: + return 0, False + e, t = integer_log(-n, b) + return e, False + if n == 0: + raise ValueError('n cannot be 0') + + if n < b: + return 0, n == 1 + if b == 2: + e = n.bit_length() - 1 + return e, trailing(n) == e + t = trailing(b) + if 2**t == b: + e = int(n.bit_length() - 1)//t + n_ = 1 << (t*e) + return e, n_ == n + + d = math.floor(math.log10(n) / math.log10(b)) + n_ = b ** d + while n_ <= n: # this will iterate 0, 1 or 2 times + d += 1 + n_ *= b + return d - (n_ > n), (n_ == n or n_//b == n) + + +def trailing(n): + """Count the number of trailing zero digits in the binary + representation of n, i.e. determine the largest power of 2 + that divides n. + + Examples + ======== + + >>> from sympy import trailing + >>> trailing(128) + 7 + >>> trailing(63) + 0 + + See Also + ======== + sympy.ntheory.factor_.multiplicity + + """ + if not n: + return 0 + return bit_scan1(int(n)) + + +@lru_cache(1024) +def igcd(*args): + """Computes nonnegative integer greatest common divisor. + + Explanation + =========== + + The algorithm is based on the well known Euclid's algorithm [1]_. To + improve speed, ``igcd()`` has its own caching mechanism. + If you do not need the cache mechanism, using ``sympy.external.gmpy.gcd``. + + Examples + ======== + + >>> from sympy import igcd + >>> igcd(2, 4) + 2 + >>> igcd(5, 10, 15) + 5 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Euclidean_algorithm + + """ + if len(args) < 2: + raise TypeError("igcd() takes at least 2 arguments (%s given)" % len(args)) + return int(number_gcd(*map(as_int, args))) + + +igcd2 = math.gcd + + +def igcd_lehmer(a, b): + r"""Computes greatest common divisor of two integers. + + Explanation + =========== + + Euclid's algorithm for the computation of the greatest + common divisor ``gcd(a, b)`` of two (positive) integers + $a$ and $b$ is based on the division identity + $$ a = q \times b + r$$, + where the quotient $q$ and the remainder $r$ are integers + and $0 \le r < b$. Then each common divisor of $a$ and $b$ + divides $r$, and it follows that ``gcd(a, b) == gcd(b, r)``. + The algorithm works by constructing the sequence + r0, r1, r2, ..., where r0 = a, r1 = b, and each rn + is the remainder from the division of the two preceding + elements. + + In Python, ``q = a // b`` and ``r = a % b`` are obtained by the + floor division and the remainder operations, respectively. + These are the most expensive arithmetic operations, especially + for large a and b. + + Lehmer's algorithm [1]_ is based on the observation that the quotients + ``qn = r(n-1) // rn`` are in general small integers even + when a and b are very large. Hence the quotients can be + usually determined from a relatively small number of most + significant bits. + + The efficiency of the algorithm is further enhanced by not + computing each long remainder in Euclid's sequence. The remainders + are linear combinations of a and b with integer coefficients + derived from the quotients. The coefficients can be computed + as far as the quotients can be determined from the chosen + most significant parts of a and b. Only then a new pair of + consecutive remainders is computed and the algorithm starts + anew with this pair. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lehmer%27s_GCD_algorithm + + """ + a, b = abs(as_int(a)), abs(as_int(b)) + if a < b: + a, b = b, a + + # The algorithm works by using one or two digit division + # whenever possible. The outer loop will replace the + # pair (a, b) with a pair of shorter consecutive elements + # of the Euclidean gcd sequence until a and b + # fit into two Python (long) int digits. + nbits = 2 * sys.int_info.bits_per_digit + + while a.bit_length() > nbits and b != 0: + # Quotients are mostly small integers that can + # be determined from most significant bits. + n = a.bit_length() - nbits + x, y = int(a >> n), int(b >> n) # most significant bits + + # Elements of the Euclidean gcd sequence are linear + # combinations of a and b with integer coefficients. + # Compute the coefficients of consecutive pairs + # a' = A*a + B*b, b' = C*a + D*b + # using small integer arithmetic as far as possible. + A, B, C, D = 1, 0, 0, 1 # initial values + + while True: + # The coefficients alternate in sign while looping. + # The inner loop combines two steps to keep track + # of the signs. + + # At this point we have + # A > 0, B <= 0, C <= 0, D > 0, + # x' = x + B <= x < x" = x + A, + # y' = y + C <= y < y" = y + D, + # and + # x'*N <= a' < x"*N, y'*N <= b' < y"*N, + # where N = 2**n. + + # Now, if y' > 0, and x"//y' and x'//y" agree, + # then their common value is equal to q = a'//b'. + # In addition, + # x'%y" = x' - q*y" < x" - q*y' = x"%y', + # and + # (x'%y")*N < a'%b' < (x"%y')*N. + + # On the other hand, we also have x//y == q, + # and therefore + # x'%y" = x + B - q*(y + D) = x%y + B', + # x"%y' = x + A - q*(y + C) = x%y + A', + # where + # B' = B - q*D < 0, A' = A - q*C > 0. + + if y + C <= 0: + break + q = (x + A) // (y + C) + + # Now x'//y" <= q, and equality holds if + # x' - q*y" = (x - q*y) + (B - q*D) >= 0. + # This is a minor optimization to avoid division. + x_qy, B_qD = x - q * y, B - q * D + if x_qy + B_qD < 0: + break + + # Next step in the Euclidean sequence. + x, y = y, x_qy + A, B, C, D = C, D, A - q * C, B_qD + + # At this point the signs of the coefficients + # change and their roles are interchanged. + # A <= 0, B > 0, C > 0, D < 0, + # x' = x + A <= x < x" = x + B, + # y' = y + D < y < y" = y + C. + + if y + D <= 0: + break + q = (x + B) // (y + D) + x_qy, A_qC = x - q * y, A - q * C + if x_qy + A_qC < 0: + break + + x, y = y, x_qy + A, B, C, D = C, D, A_qC, B - q * D + # Now the conditions on top of the loop + # are again satisfied. + # A > 0, B < 0, C < 0, D > 0. + + if B == 0: + # This can only happen when y == 0 in the beginning + # and the inner loop does nothing. + # Long division is forced. + a, b = b, a % b + continue + + # Compute new long arguments using the coefficients. + a, b = A * a + B * b, C * a + D * b + + # Small divisors. Finish with the standard algorithm. + while b: + a, b = b, a % b + + return a + + +def ilcm(*args): + """Computes integer least common multiple. + + Examples + ======== + + >>> from sympy import ilcm + >>> ilcm(5, 10) + 10 + >>> ilcm(7, 3) + 21 + >>> ilcm(5, 10, 15) + 30 + + """ + if len(args) < 2: + raise TypeError("ilcm() takes at least 2 arguments (%s given)" % len(args)) + return int(number_lcm(*map(as_int, args))) + + +def igcdex(a, b): + """Returns x, y, g such that g = x*a + y*b = gcd(a, b). + + Examples + ======== + + >>> from sympy.core.intfunc import igcdex + >>> igcdex(2, 3) + (-1, 1, 1) + >>> igcdex(10, 12) + (-1, 1, 2) + + >>> x, y, g = igcdex(100, 2004) + >>> x, y, g + (-20, 1, 4) + >>> x*100 + y*2004 + 4 + + """ + g, x, y = gcdext(int(a), int(b)) + return x, y, g + + +def mod_inverse(a, m): + r""" + Return the number $c$ such that, $a \times c = 1 \pmod{m}$ + where $c$ has the same sign as $m$. If no such value exists, + a ValueError is raised. + + Examples + ======== + + >>> from sympy import mod_inverse, S + + Suppose we wish to find multiplicative inverse $x$ of + 3 modulo 11. This is the same as finding $x$ such + that $3x = 1 \pmod{11}$. One value of x that satisfies + this congruence is 4. Because $3 \times 4 = 12$ and $12 = 1 \pmod{11}$. + This is the value returned by ``mod_inverse``: + + >>> mod_inverse(3, 11) + 4 + >>> mod_inverse(-3, 11) + 7 + + When there is a common factor between the numerators of + `a` and `m` the inverse does not exist: + + >>> mod_inverse(2, 4) + Traceback (most recent call last): + ... + ValueError: inverse of 2 mod 4 does not exist + + >>> mod_inverse(S(2)/7, S(5)/2) + 7/2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Modular_multiplicative_inverse + .. [2] https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm + """ + c = None + try: + a, m = as_int(a), as_int(m) + if m != 1 and m != -1: + x, _, g = igcdex(a, m) + if g == 1: + c = x % m + except ValueError: + a, m = sympify(a), sympify(m) + if not (a.is_number and m.is_number): + raise TypeError( + filldedent( + """ + Expected numbers for arguments; symbolic `mod_inverse` + is not implemented + but symbolic expressions can be handled with the + similar function, + sympy.polys.polytools.invert""" + ) + ) + big = m > 1 + if big not in (S.true, S.false): + raise ValueError("m > 1 did not evaluate; try to simplify %s" % m) + elif big: + c = 1 / a + if c is None: + raise ValueError("inverse of %s (mod %s) does not exist" % (a, m)) + return c + + +def isqrt(n): + r""" Return the largest integer less than or equal to `\sqrt{n}`. + + Parameters + ========== + + n : non-negative integer + + Returns + ======= + + int : `\left\lfloor\sqrt{n}\right\rfloor` + + Raises + ====== + + ValueError + If n is negative. + TypeError + If n is of a type that cannot be compared to ``int``. + Therefore, a TypeError is raised for ``str``, but not for ``float``. + + Examples + ======== + + >>> from sympy.core.intfunc import isqrt + >>> isqrt(0) + 0 + >>> isqrt(9) + 3 + >>> isqrt(10) + 3 + >>> isqrt("30") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + >>> from sympy.core.numbers import Rational + >>> isqrt(Rational(-1, 2)) + Traceback (most recent call last): + ... + ValueError: n must be nonnegative + + """ + if n < 0: + raise ValueError("n must be nonnegative") + return int(sqrt(int(n))) + + +def integer_nthroot(y, n): + """ + Return a tuple containing x = floor(y**(1/n)) + and a boolean indicating whether the result is exact (that is, + whether x**n == y). + + Examples + ======== + + >>> from sympy import integer_nthroot + >>> integer_nthroot(16, 2) + (4, True) + >>> integer_nthroot(26, 2) + (5, False) + + To simply determine if a number is a perfect square, the is_square + function should be used: + + >>> from sympy.ntheory.primetest import is_square + >>> is_square(26) + False + + See Also + ======== + sympy.ntheory.primetest.is_square + integer_log + """ + x, b = iroot(as_int(y), as_int(n)) + return int(x), b diff --git a/phivenv/Lib/site-packages/sympy/core/kind.py b/phivenv/Lib/site-packages/sympy/core/kind.py new file mode 100644 index 0000000000000000000000000000000000000000..83c5929eda14114659f2a5a72eb2d8b91a560f0e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/kind.py @@ -0,0 +1,388 @@ +""" +Module to efficiently partition SymPy objects. + +This system is introduced because class of SymPy object does not always +represent the mathematical classification of the entity. For example, +``Integral(1, x)`` and ``Integral(Matrix([1,2]), x)`` are both instance +of ``Integral`` class. However the former is number and the latter is +matrix. + +One way to resolve this is defining subclass for each mathematical type, +such as ``MatAdd`` for the addition between matrices. Basic algebraic +operation such as addition or multiplication take this approach, but +defining every class for every mathematical object is not scalable. + +Therefore, we define the "kind" of the object and let the expression +infer the kind of itself from its arguments. Function and class can +filter the arguments by their kind, and behave differently according to +the type of itself. + +This module defines basic kinds for core objects. Other kinds such as +``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules. + +.. notes:: + This approach is experimental, and can be replaced or deleted in the future. + See https://github.com/sympy/sympy/pull/20549. +""" + +from collections import defaultdict + +from .cache import cacheit +from sympy.multipledispatch.dispatcher import (Dispatcher, + ambiguity_warn, ambiguity_register_error_ignore_dup, + str_signature, RaiseNotImplementedError) + + +class KindMeta(type): + """ + Metaclass for ``Kind``. + + Assigns empty ``dict`` as class attribute ``_inst`` for every class, + in order to endow singleton-like behavior. + """ + def __new__(cls, clsname, bases, dct): + dct['_inst'] = {} + return super().__new__(cls, clsname, bases, dct) + + +class Kind(object, metaclass=KindMeta): + """ + Base class for kinds. + + Kind of the object represents the mathematical classification that + the entity falls into. It is expected that functions and classes + recognize and filter the argument by its kind. + + Kind of every object must be carefully selected so that it shows the + intention of design. Expressions may have different kind according + to the kind of its arguments. For example, arguments of ``Add`` + must have common kind since addition is group operator, and the + resulting ``Add()`` has the same kind. + + For the performance, each kind is as broad as possible and is not + based on set theory. For example, ``NumberKind`` includes not only + complex number but expression containing ``S.Infinity`` or ``S.NaN`` + which are not strictly number. + + Kind may have arguments as parameter. For example, ``MatrixKind()`` + may be constructed with one element which represents the kind of its + elements. + + ``Kind`` behaves in singleton-like fashion. Same signature will + return the same object. + + """ + def __new__(cls, *args): + if args in cls._inst: + inst = cls._inst[args] + else: + inst = super().__new__(cls) + cls._inst[args] = inst + return inst + + +class _UndefinedKind(Kind): + """ + Default kind for all SymPy object. If the kind is not defined for + the object, or if the object cannot infer the kind from its + arguments, this will be returned. + + Examples + ======== + + >>> from sympy import Expr + >>> Expr().kind + UndefinedKind + """ + def __new__(cls): + return super().__new__(cls) + + def __repr__(self): + return "UndefinedKind" + +UndefinedKind = _UndefinedKind() + + +class _NumberKind(Kind): + """ + Kind for all numeric object. + + This kind represents every number, including complex numbers, + infinity and ``S.NaN``. Other objects such as quaternions do not + have this kind. + + Most ``Expr`` are initially designed to represent the number, so + this will be the most common kind in SymPy core. For example + ``Symbol()``, which represents a scalar, has this kind as long as it + is commutative. + + Numbers form a field. Any operation between number-kind objects will + result this kind as well. + + Examples + ======== + + >>> from sympy import S, oo, Symbol + >>> S.One.kind + NumberKind + >>> (-oo).kind + NumberKind + >>> S.NaN.kind + NumberKind + + Commutative symbol are treated as number. + + >>> x = Symbol('x') + >>> x.kind + NumberKind + >>> Symbol('y', commutative=False).kind + UndefinedKind + + Operation between numbers results number. + + >>> (x+1).kind + NumberKind + + See Also + ======== + + sympy.core.expr.Expr.is_Number : check if the object is strictly + subclass of ``Number`` class. + + sympy.core.expr.Expr.is_number : check if the object is number + without any free symbol. + + """ + def __new__(cls): + return super().__new__(cls) + + def __repr__(self): + return "NumberKind" + +NumberKind = _NumberKind() + + +class _BooleanKind(Kind): + """ + Kind for boolean objects. + + SymPy's ``S.true``, ``S.false``, and built-in ``True`` and ``False`` + have this kind. Boolean number ``1`` and ``0`` are not relevant. + + Examples + ======== + + >>> from sympy import S, Q + >>> S.true.kind + BooleanKind + >>> Q.even(3).kind + BooleanKind + """ + def __new__(cls): + return super().__new__(cls) + + def __repr__(self): + return "BooleanKind" + +BooleanKind = _BooleanKind() + + +class KindDispatcher: + """ + Dispatcher to select a kind from multiple kinds by binary dispatching. + + .. notes:: + This approach is experimental, and can be replaced or deleted in + the future. + + Explanation + =========== + + SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the + algebraic structure where the object belongs to. Therefore, with + given operation, we can always find a dominating kind among the + different kinds. This class selects the kind by recursive binary + dispatching. If the result cannot be determined, ``UndefinedKind`` + is returned. + + Examples + ======== + + Multiplication between numbers return number. + + >>> from sympy import NumberKind, Mul + >>> Mul._kind_dispatcher(NumberKind, NumberKind) + NumberKind + + Multiplication between number and unknown-kind object returns unknown kind. + + >>> from sympy import UndefinedKind + >>> Mul._kind_dispatcher(NumberKind, UndefinedKind) + UndefinedKind + + Any number and order of kinds is allowed. + + >>> Mul._kind_dispatcher(UndefinedKind, NumberKind) + UndefinedKind + >>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind) + UndefinedKind + + Since matrix forms a vector space over scalar field, multiplication + between matrix with numeric element and number returns matrix with + numeric element. + + >>> from sympy.matrices import MatrixKind + >>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind) + MatrixKind(NumberKind) + + If a matrix with number element and another matrix with unknown-kind + element are multiplied, we know that the result is matrix but the + kind of its elements is unknown. + + >>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind)) + MatrixKind(UndefinedKind) + + Parameters + ========== + + name : str + + commutative : bool, optional + If True, binary dispatch will be automatically registered in + reversed order as well. + + doc : str, optional + + """ + def __init__(self, name, commutative=False, doc=None): + self.name = name + self.doc = doc + self.commutative = commutative + self._dispatcher = Dispatcher(name) + + def __repr__(self): + return "" % self.name + + def register(self, *types, **kwargs): + """ + Register the binary dispatcher for two kind classes. + + If *self.commutative* is ``True``, signature in reversed order is + automatically registered as well. + """ + on_ambiguity = kwargs.pop("on_ambiguity", None) + if not on_ambiguity: + if self.commutative: + on_ambiguity = ambiguity_register_error_ignore_dup + else: + on_ambiguity = ambiguity_warn + kwargs.update(on_ambiguity=on_ambiguity) + + if not len(types) == 2: + raise RuntimeError( + "Only binary dispatch is supported, but got %s types: <%s>." % ( + len(types), str_signature(types) + )) + + def _(func): + self._dispatcher.add(types, func, **kwargs) + if self.commutative: + self._dispatcher.add(tuple(reversed(types)), func, **kwargs) + return _ + + def __call__(self, *args, **kwargs): + if self.commutative: + kinds = frozenset(args) + else: + kinds = [] + prev = None + for a in args: + if prev is not a: + kinds.append(a) + prev = a + return self.dispatch_kinds(kinds, **kwargs) + + @cacheit + def dispatch_kinds(self, kinds, **kwargs): + # Quick exit for the case where all kinds are same + if len(kinds) == 1: + result, = kinds + if not isinstance(result, Kind): + raise RuntimeError("%s is not a kind." % result) + return result + + for i,kind in enumerate(kinds): + if not isinstance(kind, Kind): + raise RuntimeError("%s is not a kind." % kind) + + if i == 0: + result = kind + else: + prev_kind = result + + t1, t2 = type(prev_kind), type(kind) + k1, k2 = prev_kind, kind + func = self._dispatcher.dispatch(t1, t2) + if func is None and self.commutative: + # try reversed order + func = self._dispatcher.dispatch(t2, t1) + k1, k2 = k2, k1 + if func is None: + # unregistered kind relation + result = UndefinedKind + else: + result = func(k1, k2) + if not isinstance(result, Kind): + raise RuntimeError( + "Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format( + prev_kind, kind, result + )) + + return result + + @property + def __doc__(self): + docs = [ + "Kind dispatcher : %s" % self.name, + "Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details" + ] + + if self.doc: + docs.append(self.doc) + + s = "Registered kind classes\n" + s += '=' * len(s) + docs.append(s) + + amb_sigs = [] + + typ_sigs = defaultdict(list) + for sigs in self._dispatcher.ordering[::-1]: + key = self._dispatcher.funcs[sigs] + typ_sigs[key].append(sigs) + + for func, sigs in typ_sigs.items(): + + sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs) + + if isinstance(func, RaiseNotImplementedError): + amb_sigs.append(sigs_str) + continue + + s = 'Inputs: %s\n' % sigs_str + s += '-' * len(s) + '\n' + if func.__doc__: + s += func.__doc__.strip() + else: + s += func.__name__ + docs.append(s) + + if amb_sigs: + s = "Ambiguous kind classes\n" + s += '=' * len(s) + docs.append(s) + + s = '\n'.join(amb_sigs) + docs.append(s) + + return '\n\n'.join(docs) diff --git a/phivenv/Lib/site-packages/sympy/core/logic.py b/phivenv/Lib/site-packages/sympy/core/logic.py new file mode 100644 index 0000000000000000000000000000000000000000..1c318063049a4657952c8ca84e0f0fdeef62a207 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/logic.py @@ -0,0 +1,425 @@ +"""Logic expressions handling + +NOTE +---- + +at present this is mainly needed for facts.py, feel free however to improve +this stuff for general purpose. +""" + +from __future__ import annotations +from typing import Optional + +# Type of a fuzzy bool +FuzzyBool = Optional[bool] + + +def _torf(args): + """Return True if all args are True, False if they + are all False, else None. + + >>> from sympy.core.logic import _torf + >>> _torf((True, True)) + True + >>> _torf((False, False)) + False + >>> _torf((True, False)) + """ + sawT = sawF = False + for a in args: + if a is True: + if sawF: + return + sawT = True + elif a is False: + if sawT: + return + sawF = True + else: + return + return sawT + + +def _fuzzy_group(args, quick_exit=False): + """Return True if all args are True, None if there is any None else False + unless ``quick_exit`` is True (then return None as soon as a second False + is seen. + + ``_fuzzy_group`` is like ``fuzzy_and`` except that it is more + conservative in returning a False, waiting to make sure that all + arguments are True or False and returning None if any arguments are + None. It also has the capability of permiting only a single False and + returning None if more than one is seen. For example, the presence of a + single transcendental amongst rationals would indicate that the group is + no longer rational; but a second transcendental in the group would make the + determination impossible. + + + Examples + ======== + + >>> from sympy.core.logic import _fuzzy_group + + By default, multiple Falses mean the group is broken: + + >>> _fuzzy_group([False, False, True]) + False + + If multiple Falses mean the group status is unknown then set + `quick_exit` to True so None can be returned when the 2nd False is seen: + + >>> _fuzzy_group([False, False, True], quick_exit=True) + + But if only a single False is seen then the group is known to + be broken: + + >>> _fuzzy_group([False, True, True], quick_exit=True) + False + + """ + saw_other = False + for a in args: + if a is True: + continue + if a is None: + return + if quick_exit and saw_other: + return + saw_other = True + return not saw_other + + +def fuzzy_bool(x): + """Return True, False or None according to x. + + Whereas bool(x) returns True or False, fuzzy_bool allows + for the None value and non-false values (which become None), too. + + Examples + ======== + + >>> from sympy.core.logic import fuzzy_bool + >>> from sympy.abc import x + >>> fuzzy_bool(x), fuzzy_bool(None) + (None, None) + >>> bool(x), bool(None) + (True, False) + + """ + if x is None: + return None + if x in (True, False): + return bool(x) + + +def fuzzy_and(args): + """Return True (all True), False (any False) or None. + + Examples + ======== + + >>> from sympy.core.logic import fuzzy_and + >>> from sympy import Dummy + + If you had a list of objects to test the commutivity of + and you want the fuzzy_and logic applied, passing an + iterator will allow the commutativity to only be computed + as many times as necessary. With this list, False can be + returned after analyzing the first symbol: + + >>> syms = [Dummy(commutative=False), Dummy()] + >>> fuzzy_and(s.is_commutative for s in syms) + False + + That False would require less work than if a list of pre-computed + items was sent: + + >>> fuzzy_and([s.is_commutative for s in syms]) + False + """ + + rv = True + for ai in args: + ai = fuzzy_bool(ai) + if ai is False: + return False + if rv: # this will stop updating if a None is ever trapped + rv = ai + return rv + + +def fuzzy_not(v): + """ + Not in fuzzy logic + + Return None if `v` is None else `not v`. + + Examples + ======== + + >>> from sympy.core.logic import fuzzy_not + >>> fuzzy_not(True) + False + >>> fuzzy_not(None) + >>> fuzzy_not(False) + True + + """ + if v is None: + return v + else: + return not v + + +def fuzzy_or(args): + """ + Or in fuzzy logic. Returns True (any True), False (all False), or None + + See the docstrings of fuzzy_and and fuzzy_not for more info. fuzzy_or is + related to the two by the standard De Morgan's law. + + >>> from sympy.core.logic import fuzzy_or + >>> fuzzy_or([True, False]) + True + >>> fuzzy_or([True, None]) + True + >>> fuzzy_or([False, False]) + False + >>> print(fuzzy_or([False, None])) + None + + """ + rv = False + for ai in args: + ai = fuzzy_bool(ai) + if ai is True: + return True + if rv is False: # this will stop updating if a None is ever trapped + rv = ai + return rv + + +def fuzzy_xor(args): + """Return None if any element of args is not True or False, else + True (if there are an odd number of True elements), else False.""" + t = 0 + for a in args: + ai = fuzzy_bool(a) + if ai: + t += 1 + elif ai is None: + return + return t % 2 == 1 + + +def fuzzy_nand(args): + """Return False if all args are True, True if they are all False, + else None.""" + return fuzzy_not(fuzzy_and(args)) + + +class Logic: + """Logical expression""" + # {} 'op' -> LogicClass + op_2class: dict[str, type[Logic]] = {} + + def __new__(cls, *args): + obj = object.__new__(cls) + obj.args = args + return obj + + def __getnewargs__(self): + return self.args + + def __hash__(self): + return hash((type(self).__name__,) + tuple(self.args)) + + def __eq__(a, b): + if not isinstance(b, type(a)): + return False + else: + return a.args == b.args + + def __ne__(a, b): + if not isinstance(b, type(a)): + return True + else: + return a.args != b.args + + def __lt__(self, other): + if self.__cmp__(other) == -1: + return True + return False + + def __cmp__(self, other): + if type(self) is not type(other): + a = str(type(self)) + b = str(type(other)) + else: + a = self.args + b = other.args + return (a > b) - (a < b) + + def __str__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(str(a) for a in self.args)) + + __repr__ = __str__ + + @staticmethod + def fromstring(text): + """Logic from string with space around & and | but none after !. + + e.g. + + !a & b | c + """ + lexpr = None # current logical expression + schedop = None # scheduled operation + for term in text.split(): + # operation symbol + if term in '&|': + if schedop is not None: + raise ValueError( + 'double op forbidden: "%s %s"' % (term, schedop)) + if lexpr is None: + raise ValueError( + '%s cannot be in the beginning of expression' % term) + schedop = term + continue + if '&' in term or '|' in term: + raise ValueError('& and | must have space around them') + if term[0] == '!': + if len(term) == 1: + raise ValueError('do not include space after "!"') + term = Not(term[1:]) + + # already scheduled operation, e.g. '&' + if schedop: + lexpr = Logic.op_2class[schedop](lexpr, term) + schedop = None + continue + + # this should be atom + if lexpr is not None: + raise ValueError( + 'missing op between "%s" and "%s"' % (lexpr, term)) + + lexpr = term + + # let's check that we ended up in correct state + if schedop is not None: + raise ValueError('premature end-of-expression in "%s"' % text) + if lexpr is None: + raise ValueError('"%s" is empty' % text) + + # everything looks good now + return lexpr + + +class AndOr_Base(Logic): + + def __new__(cls, *args): + bargs = [] + for a in args: + if a == cls.op_x_notx: + return a + elif a == (not cls.op_x_notx): + continue # skip this argument + bargs.append(a) + + args = sorted(set(cls.flatten(bargs)), key=hash) + + for a in args: + if Not(a) in args: + return cls.op_x_notx + + if len(args) == 1: + return args.pop() + elif len(args) == 0: + return not cls.op_x_notx + + return Logic.__new__(cls, *args) + + @classmethod + def flatten(cls, args): + # quick-n-dirty flattening for And and Or + args_queue = list(args) + res = [] + + while True: + try: + arg = args_queue.pop(0) + except IndexError: + break + if isinstance(arg, Logic): + if isinstance(arg, cls): + args_queue.extend(arg.args) + continue + res.append(arg) + + args = tuple(res) + return args + + +class And(AndOr_Base): + op_x_notx = False + + def _eval_propagate_not(self): + # !(a&b&c ...) == !a | !b | !c ... + return Or(*[Not(a) for a in self.args]) + + # (a|b|...) & c == (a&c) | (b&c) | ... + def expand(self): + + # first locate Or + for i, arg in enumerate(self.args): + if isinstance(arg, Or): + arest = self.args[:i] + self.args[i + 1:] + + orterms = [And(*(arest + (a,))) for a in arg.args] + for j in range(len(orterms)): + if isinstance(orterms[j], Logic): + orterms[j] = orterms[j].expand() + + res = Or(*orterms) + return res + + return self + + +class Or(AndOr_Base): + op_x_notx = True + + def _eval_propagate_not(self): + # !(a|b|c ...) == !a & !b & !c ... + return And(*[Not(a) for a in self.args]) + + +class Not(Logic): + + def __new__(cls, arg): + if isinstance(arg, str): + return Logic.__new__(cls, arg) + + elif isinstance(arg, bool): + return not arg + elif isinstance(arg, Not): + return arg.args[0] + + elif isinstance(arg, Logic): + # XXX this is a hack to expand right from the beginning + arg = arg._eval_propagate_not() + return arg + + else: + raise ValueError('Not: unknown argument %r' % (arg,)) + + @property + def arg(self): + return self.args[0] + + +Logic.op_2class['&'] = And +Logic.op_2class['|'] = Or +Logic.op_2class['!'] = Not diff --git a/phivenv/Lib/site-packages/sympy/core/mod.py b/phivenv/Lib/site-packages/sympy/core/mod.py new file mode 100644 index 0000000000000000000000000000000000000000..8be0c56e497eb5ed0041801488044b50f907962c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/mod.py @@ -0,0 +1,260 @@ +from .add import Add +from .exprtools import gcd_terms +from .function import DefinedFunction +from .kind import NumberKind +from .logic import fuzzy_and, fuzzy_not +from .mul import Mul +from .numbers import equal_valued +from .relational import is_le, is_lt, is_ge, is_gt +from .singleton import S + + +class Mod(DefinedFunction): + """Represents a modulo operation on symbolic expressions. + + Parameters + ========== + + p : Expr + Dividend. + + q : Expr + Divisor. + + Notes + ===== + + The convention used is the same as Python's: the remainder always has the + same sign as the divisor. + + Many objects can be evaluated modulo ``n`` much faster than they can be + evaluated directly (or at all). For this, ``evaluate=False`` is + necessary to prevent eager evaluation: + + >>> from sympy import binomial, factorial, Mod, Pow + >>> Mod(Pow(2, 10**16, evaluate=False), 97) + 61 + >>> Mod(factorial(10**9, evaluate=False), 10**9 + 9) + 712524808 + >>> Mod(binomial(10**18, 10**12, evaluate=False), (10**5 + 3)**2) + 3744312326 + + Examples + ======== + + >>> from sympy.abc import x, y + >>> x**2 % y + Mod(x**2, y) + >>> _.subs({x: 5, y: 6}) + 1 + + """ + + kind = NumberKind + + @classmethod + def eval(cls, p, q): + def number_eval(p, q): + """Try to return p % q if both are numbers or +/-p is known + to be less than or equal q. + """ + + if q.is_zero: + raise ZeroDivisionError("Modulo by zero") + if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False: + return S.NaN + if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1): + return S.Zero + + if q.is_Number: + if p.is_Number: + return p%q + if q == 2: + if p.is_even: + return S.Zero + elif p.is_odd: + return S.One + + if hasattr(p, '_eval_Mod'): + rv = getattr(p, '_eval_Mod')(q) + if rv is not None: + return rv + + # by ratio + r = p/q + if r.is_integer: + return S.Zero + try: + d = int(r) + except TypeError: + pass + else: + if isinstance(d, int): + rv = p - d*q + if (rv*q < 0) == True: + rv += q + return rv + + # by difference + # -2|q| < p < 2|q| + if q.is_positive: + comp1, comp2 = is_le, is_lt + elif q.is_negative: + comp1, comp2 = is_ge, is_gt + else: + return + ls = -2*q + r = p - q + for _ in range(4): + if not comp1(ls, p): + return + if comp2(r, ls): + return p - ls + ls += q + + rv = number_eval(p, q) + if rv is not None: + return rv + + # denest + if isinstance(p, cls): + qinner = p.args[1] + if qinner % q == 0: + return cls(p.args[0], q) + elif (qinner*(q - qinner)).is_nonnegative: + # |qinner| < |q| and have same sign + return p + elif isinstance(-p, cls): + qinner = (-p).args[1] + if qinner % q == 0: + return cls(-(-p).args[0], q) + elif (qinner*(q + qinner)).is_nonpositive: + # |qinner| < |q| and have different sign + return p + elif isinstance(p, Add): + # separating into modulus and non modulus + both_l = non_mod_l, mod_l = [], [] + for arg in p.args: + both_l[isinstance(arg, cls)].append(arg) + # if q same for all + if mod_l and all(inner.args[1] == q for inner in mod_l): + net = Add(*non_mod_l) + Add(*[i.args[0] for i in mod_l]) + return cls(net, q) + + elif isinstance(p, Mul): + # separating into modulus and non modulus + both_l = non_mod_l, mod_l = [], [] + for arg in p.args: + both_l[isinstance(arg, cls)].append(arg) + + if mod_l and all(inner.args[1] == q for inner in mod_l) and all(t.is_integer for t in p.args) and q.is_integer: + # finding distributive term + non_mod_l = [cls(x, q) for x in non_mod_l] + mod = [] + non_mod = [] + for j in non_mod_l: + if isinstance(j, cls): + mod.append(j.args[0]) + else: + non_mod.append(j) + prod_mod = Mul(*mod) + prod_non_mod = Mul(*non_mod) + prod_mod1 = Mul(*[i.args[0] for i in mod_l]) + net = prod_mod1*prod_mod + return prod_non_mod*cls(net, q) + + if q.is_Integer and q is not S.One: + if all(t.is_integer for t in p.args): + non_mod_l = [i % q if i.is_Integer else i for i in p.args] + if any(iq is S.Zero for iq in non_mod_l): + return S.Zero + + p = Mul(*(non_mod_l + mod_l)) + + # XXX other possibilities? + + from sympy.polys.polyerrors import PolynomialError + from sympy.polys.polytools import gcd + + # extract gcd; any further simplification should be done by the user + try: + G = gcd(p, q) + if not equal_valued(G, 1): + p, q = [gcd_terms(i/G, clear=False, fraction=False) + for i in (p, q)] + except PolynomialError: # issue 21373 + G = S.One + pwas, qwas = p, q + + # simplify terms + # (x + y + 2) % x -> Mod(y + 2, x) + if p.is_Add: + args = [] + for i in p.args: + a = cls(i, q) + if a.count(cls) > i.count(cls): + args.append(i) + else: + args.append(a) + if args != list(p.args): + p = Add(*args) + + else: + # handle coefficients if they are not Rational + # since those are not handled by factor_terms + # e.g. Mod(.6*x, .3*y) -> 0.3*Mod(2*x, y) + cp, p = p.as_coeff_Mul() + cq, q = q.as_coeff_Mul() + ok = False + if not cp.is_Rational or not cq.is_Rational: + r = cp % cq + if equal_valued(r, 0): + G *= cq + p *= int(cp/cq) + ok = True + if not ok: + p = cp*p + q = cq*q + + # simple -1 extraction + if p.could_extract_minus_sign() and q.could_extract_minus_sign(): + G, p, q = [-i for i in (G, p, q)] + + # check again to see if p and q can now be handled as numbers + rv = number_eval(p, q) + if rv is not None: + return rv*G + + # put 1.0 from G on inside + if G.is_Float and equal_valued(G, 1): + p *= G + return cls(p, q, evaluate=False) + elif G.is_Mul and G.args[0].is_Float and equal_valued(G.args[0], 1): + p = G.args[0]*p + G = Mul._from_args(G.args[1:]) + return G*cls(p, q, evaluate=(p, q) != (pwas, qwas)) + + def _eval_is_integer(self): + p, q = self.args + if fuzzy_and([p.is_integer, q.is_integer, fuzzy_not(q.is_zero)]): + return True + + def _eval_is_nonnegative(self): + if self.args[1].is_positive: + return True + + def _eval_is_nonpositive(self): + if self.args[1].is_negative: + return True + + def _eval_rewrite_as_floor(self, a, b, **kwargs): + from sympy.functions.elementary.integers import floor + return a - b*floor(a/b) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.functions.elementary.integers import floor + return self.rewrite(floor)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.functions.elementary.integers import floor + return self.rewrite(floor)._eval_nseries(x, n, logx=logx, cdir=cdir) diff --git a/phivenv/Lib/site-packages/sympy/core/mul.py b/phivenv/Lib/site-packages/sympy/core/mul.py new file mode 100644 index 0000000000000000000000000000000000000000..fd83c8610a76db4e7bc7a2a71b98e437bd00a28e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/mul.py @@ -0,0 +1,2214 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, ClassVar + +from collections import defaultdict +from functools import reduce +from itertools import product +import operator + +from .sympify import sympify +from .basic import Basic, _args_sortkey +from .singleton import S +from .operations import AssocOp, AssocOpDispatcher +from .cache import cacheit +from .intfunc import integer_nthroot, trailing +from .logic import fuzzy_not, _fuzzy_group +from .expr import Expr +from .parameters import global_parameters +from .kind import KindDispatcher +from .traversal import bottom_up +from sympy.utilities.iterables import sift + + +# internal marker to indicate: +# "there are still non-commutative objects -- don't forget to process them" +class NC_Marker: + is_Order = False + is_Mul = False + is_Number = False + is_Poly = False + + is_commutative = False + + +def _mulsort(args): + # in-place sorting of args + args.sort(key=_args_sortkey) + + +def _unevaluated_Mul(*args): + """Return a well-formed unevaluated Mul: Numbers are collected and + put in slot 0, any arguments that are Muls will be flattened, and args + are sorted. Use this when args have changed but you still want to return + an unevaluated Mul. + + Examples + ======== + + >>> from sympy.core.mul import _unevaluated_Mul as uMul + >>> from sympy import S, sqrt, Mul + >>> from sympy.abc import x + >>> a = uMul(*[S(3.0), x, S(2)]) + >>> a.args[0] + 6.00000000000000 + >>> a.args[1] + x + + Two unevaluated Muls with the same arguments will + always compare as equal during testing: + + >>> m = uMul(sqrt(2), sqrt(3)) + >>> m == uMul(sqrt(3), sqrt(2)) + True + >>> u = Mul(sqrt(3), sqrt(2), evaluate=False) + >>> m == uMul(u) + True + >>> m == Mul(*m.args) + False + + """ + cargs = [] + ncargs = [] + args = list(args) + co = S.One + for a in args: + if a.is_Mul: + a_c, a_nc = a.args_cnc() + args.extend(a_c) # grow args + ncargs.extend(a_nc) + elif a.is_Number: + co *= a + elif a.is_commutative: + cargs.append(a) + else: + ncargs.append(a) + _mulsort(cargs) + if co is not S.One: + cargs.insert(0, co) + return Mul._from_args(cargs+ncargs) + + +class Mul(Expr, AssocOp): + """ + Expression representing multiplication operation for algebraic field. + + .. deprecated:: 1.7 + + Using arguments that aren't subclasses of :class:`~.Expr` in core + operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is + deprecated. See :ref:`non-expr-args-deprecated` for details. + + Every argument of ``Mul()`` must be ``Expr``. Infix operator ``*`` + on most scalar objects in SymPy calls this class. + + Another use of ``Mul()`` is to represent the structure of abstract + multiplication so that its arguments can be substituted to return + different class. Refer to examples section for this. + + ``Mul()`` evaluates the argument unless ``evaluate=False`` is passed. + The evaluation logic includes: + + 1. Flattening + ``Mul(x, Mul(y, z))`` -> ``Mul(x, y, z)`` + + 2. Identity removing + ``Mul(x, 1, y)`` -> ``Mul(x, y)`` + + 3. Exponent collecting by ``.as_base_exp()`` + ``Mul(x, x**2)`` -> ``Pow(x, 3)`` + + 4. Term sorting + ``Mul(y, x, 2)`` -> ``Mul(2, x, y)`` + + Since multiplication can be vector space operation, arguments may + have the different :obj:`sympy.core.kind.Kind()`. Kind of the + resulting object is automatically inferred. + + Examples + ======== + + >>> from sympy import Mul + >>> from sympy.abc import x, y + >>> Mul(x, 1) + x + >>> Mul(x, x) + x**2 + + If ``evaluate=False`` is passed, result is not evaluated. + + >>> Mul(1, 2, evaluate=False) + 1*2 + >>> Mul(x, x, evaluate=False) + x*x + + ``Mul()`` also represents the general structure of multiplication + operation. + + >>> from sympy import MatrixSymbol + >>> A = MatrixSymbol('A', 2,2) + >>> expr = Mul(x,y).subs({y:A}) + >>> expr + x*A + >>> type(expr) + + + See Also + ======== + + MatMul + + """ + __slots__ = () + + is_Mul = True + + _args_type = Expr + _kind_dispatcher = KindDispatcher("Mul_kind_dispatcher", commutative=True) + + identity: ClassVar[Expr] + + @property + def kind(self): + arg_kinds = (a.kind for a in self.args) + return self._kind_dispatcher(*arg_kinds) + + if TYPE_CHECKING: + + def __new__(cls, *args: Expr | complex, evaluate: bool=True) -> Expr: # type: ignore + ... + + @property + def args(self) -> tuple[Expr, ...]: + ... + + def could_extract_minus_sign(self): + if self == (-self): + return False # e.g. zoo*x == -zoo*x + c = self.args[0] + return c.is_Number and c.is_extended_negative + + def __neg__(self): + c, args = self.as_coeff_mul() + if args[0] is not S.ComplexInfinity: + c = -c + if c is not S.One: + if args[0].is_Number: + args = list(args) + if c is S.NegativeOne: + args[0] = -args[0] + else: + args[0] *= c + else: + args = (c,) + args + return self._from_args(args, self.is_commutative) + + @classmethod + def flatten(cls, seq): + """Return commutative, noncommutative and order arguments by + combining related terms. + + Notes + ===== + * In an expression like ``a*b*c``, Python process this through SymPy + as ``Mul(Mul(a, b), c)``. This can have undesirable consequences. + + - Sometimes terms are not combined as one would like: + {c.f. https://github.com/sympy/sympy/issues/4596} + + >>> from sympy import Mul, sqrt + >>> from sympy.abc import x, y, z + >>> 2*(x + 1) # this is the 2-arg Mul behavior + 2*x + 2 + >>> y*(x + 1)*2 + 2*y*(x + 1) + >>> 2*(x + 1)*y # 2-arg result will be obtained first + y*(2*x + 2) + >>> Mul(2, x + 1, y) # all 3 args simultaneously processed + 2*y*(x + 1) + >>> 2*((x + 1)*y) # parentheses can control this behavior + 2*y*(x + 1) + + Powers with compound bases may not find a single base to + combine with unless all arguments are processed at once. + Post-processing may be necessary in such cases. + {c.f. https://github.com/sympy/sympy/issues/5728} + + >>> a = sqrt(x*sqrt(y)) + >>> a**3 + (x*sqrt(y))**(3/2) + >>> Mul(a,a,a) + (x*sqrt(y))**(3/2) + >>> a*a*a + x*sqrt(y)*sqrt(x*sqrt(y)) + >>> _.subs(a.base, z).subs(z, a.base) + (x*sqrt(y))**(3/2) + + - If more than two terms are being multiplied then all the + previous terms will be re-processed for each new argument. + So if each of ``a``, ``b`` and ``c`` were :class:`Mul` + expression, then ``a*b*c`` (or building up the product + with ``*=``) will process all the arguments of ``a`` and + ``b`` twice: once when ``a*b`` is computed and again when + ``c`` is multiplied. + + Using ``Mul(a, b, c)`` will process all arguments once. + + * The results of Mul are cached according to arguments, so flatten + will only be called once for ``Mul(a, b, c)``. If you can + structure a calculation so the arguments are most likely to be + repeats then this can save time in computing the answer. For + example, say you had a Mul, M, that you wished to divide by ``d[i]`` + and multiply by ``n[i]`` and you suspect there are many repeats + in ``n``. It would be better to compute ``M*n[i]/d[i]`` rather + than ``M/d[i]*n[i]`` since every time n[i] is a repeat, the + product, ``M*n[i]`` will be returned without flattening -- the + cached value will be returned. If you divide by the ``d[i]`` + first (and those are more unique than the ``n[i]``) then that will + create a new Mul, ``M/d[i]`` the args of which will be traversed + again when it is multiplied by ``n[i]``. + + {c.f. https://github.com/sympy/sympy/issues/5706} + + This consideration is moot if the cache is turned off. + + NB + -- + The validity of the above notes depends on the implementation + details of Mul and flatten which may change at any time. Therefore, + you should only consider them when your code is highly performance + sensitive. + + Removal of 1 from the sequence is already handled by AssocOp.__new__. + """ + + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.matrices.expressions import MatrixExpr + rv = None + if len(seq) == 2: + a, b = seq + if b.is_Rational: + a, b = b, a + seq = [a, b] + assert a is not S.One + if a.is_Rational and not a.is_zero: + r, b = b.as_coeff_Mul() + if b.is_Add: + if r is not S.One: # 2-arg hack + # leave the Mul as a Mul? + ar = a*r + if ar is S.One: + arb = b + else: + arb = cls(a*r, b, evaluate=False) + rv = [arb], [], None + elif global_parameters.distribute and b.is_commutative: + newb = Add(*[_keep_coeff(a, bi) for bi in b.args]) + rv = [newb], [], None + if rv: + return rv + + # apply associativity, separate commutative part of seq + c_part = [] # out: commutative factors + nc_part = [] # out: non-commutative factors + + nc_seq = [] + + coeff = S.One # standalone term + # e.g. 3 * ... + + c_powers = [] # (base,exp) n + # e.g. (x,n) for x + + num_exp = [] # (num-base, exp) y + # e.g. (3, y) for ... * 3 * ... + + neg1e = S.Zero # exponent on -1 extracted from Number-based Pow and I + + pnum_rat = {} # (num-base, Rat-exp) 1/2 + # e.g. (3, 1/2) for ... * 3 * ... + + order_symbols = None + + # --- PART 1 --- + # + # "collect powers and coeff": + # + # o coeff + # o c_powers + # o num_exp + # o neg1e + # o pnum_rat + # + # NOTE: this is optimized for all-objects-are-commutative case + for o in seq: + # O(x) + if o.is_Order: + o, order_symbols = o.as_expr_variables(order_symbols) + + # Mul([...]) + if o.is_Mul: + if o.is_commutative: + seq.extend(o.args) # XXX zerocopy? + + else: + # NCMul can have commutative parts as well + for q in o.args: + if q.is_commutative: + seq.append(q) + else: + nc_seq.append(q) + + # append non-commutative marker, so we don't forget to + # process scheduled non-commutative objects + seq.append(NC_Marker) + + continue + + # 3 + elif o.is_Number: + if o is S.NaN or coeff is S.ComplexInfinity and o.is_zero: + # we know for sure the result will be nan + return [S.NaN], [], None + elif coeff.is_Number or isinstance(coeff, AccumBounds): # it could be zoo + coeff *= o + if coeff is S.NaN: + # we know for sure the result will be nan + return [S.NaN], [], None + continue + + elif isinstance(o, AccumBounds): + coeff = o.__mul__(coeff) + continue + + elif o is S.ComplexInfinity: + if not coeff: + # 0 * zoo = NaN + return [S.NaN], [], None + coeff = S.ComplexInfinity + continue + + elif not coeff and isinstance(o, Add) and any( + _ in (S.NegativeInfinity, S.ComplexInfinity, S.Infinity) + for __ in o.args for _ in Mul.make_args(__)): + # e.g 0 * (x + oo) = NaN but not + # 0 * (1 + Integral(x, (x, 0, oo))) which is + # treated like 0 * x -> 0 + return [S.NaN], [], None + + elif o is S.ImaginaryUnit: + neg1e += S.Half + continue + + elif o.is_commutative: + # e + # o = b + b, e = o.as_base_exp() + + # y + # 3 + if o.is_Pow: + if b.is_Number: + + # get all the factors with numeric base so they can be + # combined below, but don't combine negatives unless + # the exponent is an integer + if e.is_Rational: + if e.is_Integer: + coeff *= Pow(b, e) # it is an unevaluated power + continue + elif e.is_negative: # also a sign of an unevaluated power + seq.append(Pow(b, e)) + continue + elif b.is_negative: + neg1e += e + b = -b + if b is not S.One: + pnum_rat.setdefault(b, []).append(e) + continue + elif b.is_positive or e.is_integer: + num_exp.append((b, e)) + continue + + c_powers.append((b, e)) + + # NON-COMMUTATIVE + # TODO: Make non-commutative exponents not combine automatically + else: + if o is not NC_Marker: + nc_seq.append(o) + + # process nc_seq (if any) + while nc_seq: + o = nc_seq.pop(0) + if not nc_part: + nc_part.append(o) + continue + + # b c b+c + # try to combine last terms: a * a -> a + o1 = nc_part.pop() + b1, e1 = o1.as_base_exp() + b2, e2 = o.as_base_exp() + new_exp = e1 + e2 + # Only allow powers to combine if the new exponent is + # not an Add. This allow things like a**2*b**3 == a**5 + # if a.is_commutative == False, but prohibits + # a**x*a**y and x**a*x**b from combining (x,y commute). + if b1 == b2 and (not new_exp.is_Add): + o12 = b1 ** new_exp + + # now o12 could be a commutative object + if o12.is_commutative: + seq.append(o12) + continue + else: + nc_seq.insert(0, o12) + + else: + nc_part.extend([o1, o]) + + # We do want a combined exponent if it would not be an Add, such as + # y 2y 3y + # x * x -> x + # We determine if two exponents have the same term by using + # as_coeff_Mul. + # + # Unfortunately, this isn't smart enough to consider combining into + # exponents that might already be adds, so things like: + # z - y y + # x * x will be left alone. This is because checking every possible + # combination can slow things down. + + # gather exponents of common bases... + def _gather(c_powers): + common_b = {} # b:e + for b, e in c_powers: + co = e.as_coeff_Mul() + common_b.setdefault(b, {}).setdefault( + co[1], []).append(co[0]) + for b, d in common_b.items(): + for di, li in d.items(): + d[di] = Add(*li) + new_c_powers = [] + for b, e in common_b.items(): + new_c_powers.extend([(b, c*t) for t, c in e.items()]) + return new_c_powers + + # in c_powers + c_powers = _gather(c_powers) + + # and in num_exp + num_exp = _gather(num_exp) + + # --- PART 2 --- + # + # o process collected powers (x**0 -> 1; x**1 -> x; otherwise Pow) + # o combine collected powers (2**x * 3**x -> 6**x) + # with numeric base + + # ................................ + # now we have: + # - coeff: + # - c_powers: (b, e) + # - num_exp: (2, e) + # - pnum_rat: {(1/3, [1/3, 2/3, 1/4])} + + # 0 1 + # x -> 1 x -> x + + # this should only need to run twice; if it fails because + # it needs to be run more times, perhaps this should be + # changed to a "while True" loop -- the only reason it + # isn't such now is to allow a less-than-perfect result to + # be obtained rather than raising an error or entering an + # infinite loop + for i in range(2): + new_c_powers = [] + changed = False + for b, e in c_powers: + if e.is_zero: + # canceling out infinities yields NaN + if (b.is_Add or b.is_Mul) and any(infty in b.args + for infty in (S.ComplexInfinity, S.Infinity, + S.NegativeInfinity)): + return [S.NaN], [], None + continue + if e is S.One: + if b.is_Number: + coeff *= b + continue + p = b + if e is not S.One: + p = Pow(b, e) + # check to make sure that the base doesn't change + # after exponentiation; to allow for unevaluated + # Pow, we only do so if b is not already a Pow + if p.is_Pow and not b.is_Pow: + bi = b + b, e = p.as_base_exp() + if b != bi: + changed = True + c_part.append(p) + new_c_powers.append((b, e)) + # there might have been a change, but unless the base + # matches some other base, there is nothing to do + if changed and len({ + b for b, e in new_c_powers}) != len(new_c_powers): + # start over again + c_part = [] + c_powers = _gather(new_c_powers) + else: + break + + # x x x + # 2 * 3 -> 6 + inv_exp_dict = {} # exp:Mul(num-bases) x x + # e.g. x:6 for ... * 2 * 3 * ... + for b, e in num_exp: + inv_exp_dict.setdefault(e, []).append(b) + for e, b in inv_exp_dict.items(): + inv_exp_dict[e] = cls(*b) + c_part.extend([Pow(b, e) for e, b in inv_exp_dict.items() if e]) + + # b, e -> e' = sum(e), b + # {(1/5, [1/3]), (1/2, [1/12, 1/4]} -> {(1/3, [1/5, 1/2])} + comb_e = {} + for b, e in pnum_rat.items(): + comb_e.setdefault(Add(*e), []).append(b) + del pnum_rat + # process them, reducing exponents to values less than 1 + # and updating coeff if necessary else adding them to + # num_rat for further processing + num_rat = [] + for e, b in comb_e.items(): + b = cls(*b) + if e.q == 1: + coeff *= Pow(b, e) + continue + if e.p > e.q: + e_i, ep = divmod(e.p, e.q) + coeff *= Pow(b, e_i) + e = Rational(ep, e.q) + num_rat.append((b, e)) + del comb_e + + # extract gcd of bases in num_rat + # 2**(1/3)*6**(1/4) -> 2**(1/3+1/4)*3**(1/4) + pnew = defaultdict(list) + i = 0 # steps through num_rat which may grow + while i < len(num_rat): + bi, ei = num_rat[i] + if bi == 1: + i += 1 + continue + grow = [] + for j in range(i + 1, len(num_rat)): + bj, ej = num_rat[j] + g = bi.gcd(bj) + if g is not S.One: + # 4**r1*6**r2 -> 2**(r1+r2) * 2**r1 * 3**r2 + # this might have a gcd with something else + e = ei + ej + if e.q == 1: + coeff *= Pow(g, e) + else: + if e.p > e.q: + e_i, ep = divmod(e.p, e.q) # change e in place + coeff *= Pow(g, e_i) + e = Rational(ep, e.q) + grow.append((g, e)) + # update the jth item + num_rat[j] = (bj/g, ej) + # update bi that we are checking with + bi = bi/g + if bi is S.One: + break + if bi is not S.One: + obj = Pow(bi, ei) + if obj.is_Number: + coeff *= obj + else: + # changes like sqrt(12) -> 2*sqrt(3) + for obj in Mul.make_args(obj): + if obj.is_Number: + coeff *= obj + else: + assert obj.is_Pow + bi, ei = obj.args + pnew[ei].append(bi) + + num_rat.extend(grow) + i += 1 + + # combine bases of the new powers + for e, b in pnew.items(): + pnew[e] = cls(*b) + + # handle -1 and I + if neg1e: + # treat I as (-1)**(1/2) and compute -1's total exponent + p, q = neg1e.as_numer_denom() + # if the integer part is odd, extract -1 + n, p = divmod(p, q) + if n % 2: + coeff = -coeff + # if it's a multiple of 1/2 extract I + if q == 2: + c_part.append(S.ImaginaryUnit) + elif p: + # see if there is any positive base this power of + # -1 can join + neg1e = Rational(p, q) + for e, b in pnew.items(): + if e == neg1e and b.is_positive: + pnew[e] = -b + break + else: + # keep it separate; we've already evaluated it as + # much as possible so evaluate=False + c_part.append(Pow(S.NegativeOne, neg1e, evaluate=False)) + + # add all the pnew powers + c_part.extend([Pow(b, e) for e, b in pnew.items()]) + + # oo, -oo + if coeff in (S.Infinity, S.NegativeInfinity): + def _handle_for_oo(c_part, coeff_sign): + new_c_part = [] + for t in c_part: + if t.is_extended_positive: + continue + if t.is_extended_negative: + coeff_sign *= -1 + continue + new_c_part.append(t) + return new_c_part, coeff_sign + c_part, coeff_sign = _handle_for_oo(c_part, 1) + nc_part, coeff_sign = _handle_for_oo(nc_part, coeff_sign) + coeff *= coeff_sign + + # zoo + if coeff is S.ComplexInfinity: + # zoo might be + # infinite_real + bounded_im + # bounded_real + infinite_im + # infinite_real + infinite_im + # and non-zero real or imaginary will not change that status. + c_part = [c for c in c_part if not (fuzzy_not(c.is_zero) and + c.is_extended_real is not None)] + nc_part = [c for c in nc_part if not (fuzzy_not(c.is_zero) and + c.is_extended_real is not None)] + + # 0 + elif coeff.is_zero: + # we know for sure the result will be 0 except the multiplicand + # is infinity or a matrix + if any(isinstance(c, MatrixExpr) for c in nc_part): + return [coeff], nc_part, order_symbols + if any(c.is_finite == False for c in c_part): + return [S.NaN], [], order_symbols + return [coeff], [], order_symbols + + # check for straggling Numbers that were produced + _new = [] + for i in c_part: + if i.is_Number: + coeff *= i + else: + _new.append(i) + c_part = _new + + # order commutative part canonically + _mulsort(c_part) + + # current code expects coeff to be always in slot-0 + if coeff is not S.One: + c_part.insert(0, coeff) + + # we are done + if (global_parameters.distribute and not nc_part and len(c_part) == 2 and + c_part[0].is_Number and c_part[0].is_finite and c_part[1].is_Add): + # 2*(1+a) -> 2 + 2 * a + coeff = c_part[0] + c_part = [Add(*[coeff*f for f in c_part[1].args])] + + return c_part, nc_part, order_symbols + + def _eval_power(self, expt): + + # don't break up NC terms: (A*B)**3 != A**3*B**3, it is A*B*A*B*A*B + cargs, nc = self.args_cnc(split_1=False) + + if expt.is_Integer: + return Mul(*[Pow(b, expt, evaluate=False) for b in cargs]) * \ + Pow(Mul._from_args(nc), expt, evaluate=False) + if expt.is_Rational and expt.q == 2: + if self.is_imaginary: + a = self.as_real_imag()[1] + if a.is_Rational: + n, d = abs(a/2).as_numer_denom() + n, t = integer_nthroot(n, 2) + if t: + d, t = integer_nthroot(d, 2) + if t: + from sympy.functions.elementary.complexes import sign + r = sympify(n)/d + return _unevaluated_Mul(r**expt.p, (1 + sign(a)*S.ImaginaryUnit)**expt.p) + + p = Pow(self, expt, evaluate=False) + + if expt.is_Rational or expt.is_Float: + return p._eval_expand_power_base() + + return p + + @classmethod + def class_key(cls): + return 3, 0, cls.__name__ + + def _eval_evalf(self, prec): + c, m = self.as_coeff_Mul() + if c is S.NegativeOne: + if m.is_Mul: + rv = -AssocOp._eval_evalf(m, prec) + else: + mnew = m._eval_evalf(prec) + if mnew is not None: + m = mnew + rv = -m + else: + rv = AssocOp._eval_evalf(self, prec) + if rv.is_number: + return rv.expand() + return rv + + @property + def _mpc_(self): + """ + Convert self to an mpmath mpc if possible + """ + from .numbers import Float + im_part, imag_unit = self.as_coeff_Mul() + if imag_unit is not S.ImaginaryUnit: + # ValueError may seem more reasonable but since it's a @property, + # we need to use AttributeError to keep from confusing things like + # hasattr. + raise AttributeError("Cannot convert Mul to mpc. Must be of the form Number*I") + + return (Float(0)._mpf_, Float(im_part)._mpf_) + + @cacheit + def as_two_terms(self): + """Return head and tail of self. + + This is the most efficient way to get the head and tail of an + expression. + + - if you want only the head, use self.args[0]; + - if you want to process the arguments of the tail then use + self.as_coef_mul() which gives the head and a tuple containing + the arguments of the tail when treated as a Mul. + - if you want the coefficient when self is treated as an Add + then use self.as_coeff_add()[0] + + Examples + ======== + + >>> from sympy.abc import x, y + >>> (3*x*y).as_two_terms() + (3, x*y) + """ + args = self.args + + if len(args) == 1: + return S.One, self + elif len(args) == 2: + return args + + else: + return args[0], self._new_rawargs(*args[1:]) + + @cacheit + def as_coeff_mul(self, *deps, rational=True, **kwargs): + if deps: + l1, l2 = sift(self.args, lambda x: x.has(*deps), binary=True) + return self._new_rawargs(*l2), tuple(l1) + args = self.args + if args[0].is_Number: + if not rational or args[0].is_Rational: + return args[0], args[1:] + elif args[0].is_extended_negative: + return S.NegativeOne, (-args[0],) + args[1:] + return S.One, args + + def as_coeff_Mul(self, rational=False): + """ + Efficiently extract the coefficient of a product. + """ + coeff, args = self.args[0], self.args[1:] + + if coeff.is_Number: + if not rational or coeff.is_Rational: + if len(args) == 1: + return coeff, args[0] + else: + return coeff, self._new_rawargs(*args) + elif coeff.is_extended_negative: + return S.NegativeOne, self._new_rawargs(*((-coeff,) + args)) + return S.One, self + + def as_real_imag(self, deep=True, **hints): + from sympy.functions.elementary.complexes import Abs, im, re + other = [] + coeffr = [] + coeffi = [] + addterms = S.One + for a in self.args: + r, i = a.as_real_imag() + if i.is_zero: + coeffr.append(r) + elif r.is_zero: + coeffi.append(i*S.ImaginaryUnit) + elif a.is_commutative: + aconj = a.conjugate() if other else None + # search for complex conjugate pairs: + for i, x in enumerate(other): + if x == aconj: + coeffr.append(Abs(x)**2) + del other[i] + break + else: + if a.is_Add: + addterms *= a + else: + other.append(a) + else: + other.append(a) + m = self.func(*other) + if hints.get('ignore') == m: + return + if len(coeffi) % 2: + imco = im(coeffi.pop(0)) + # all other pairs make a real factor; they will be + # put into reco below + else: + imco = S.Zero + reco = self.func(*(coeffr + coeffi)) + r, i = (reco*re(m), reco*im(m)) + if addterms == 1: + if m == 1: + if imco.is_zero: + return (reco, S.Zero) + else: + return (S.Zero, reco*imco) + if imco is S.Zero: + return (r, i) + return (-imco*i, imco*r) + from .function import expand_mul + addre, addim = expand_mul(addterms, deep=False).as_real_imag() + if imco is S.Zero: + return (r*addre - i*addim, i*addre + r*addim) + else: + r, i = -imco*i, imco*r + return (r*addre - i*addim, r*addim + i*addre) + + @staticmethod + def _expandsums(sums): + """ + Helper function for _eval_expand_mul. + + sums must be a list of instances of Basic. + """ + + L = len(sums) + if L == 1: + return sums[0].args + terms = [] + left = Mul._expandsums(sums[:L//2]) + right = Mul._expandsums(sums[L//2:]) + + terms = [Mul(a, b) for a in left for b in right] + added = Add(*terms) + return Add.make_args(added) # it may have collapsed down to one term + + def _eval_expand_mul(self, **hints): + from sympy.simplify.radsimp import fraction + + # Handle things like 1/(x*(x + 1)), which are automatically converted + # to 1/x*1/(x + 1) + expr = self + # default matches fraction's default + n, d = fraction(expr, hints.get('exact', False)) + if d.is_Mul: + n, d = [i._eval_expand_mul(**hints) if i.is_Mul else i + for i in (n, d)] + expr = n/d + if not expr.is_Mul: + return expr + + plain, sums, rewrite = [], [], False + for factor in expr.args: + if factor.is_Add: + sums.append(factor) + rewrite = True + else: + if factor.is_commutative: + plain.append(factor) + else: + sums.append(Basic(factor)) # Wrapper + + if not rewrite: + return expr + else: + plain = self.func(*plain) + if sums: + deep = hints.get("deep", False) + terms = self.func._expandsums(sums) + args = [] + for term in terms: + t = self.func(plain, term) + if t.is_Mul and any(a.is_Add for a in t.args) and deep: + t = t._eval_expand_mul() + args.append(t) + return Add(*args) + else: + return plain + + @cacheit + def _eval_derivative(self, s): + args = list(self.args) + terms = [] + for i in range(len(args)): + d = args[i].diff(s) + if d: + # Note: reduce is used in step of Mul as Mul is unable to + # handle subtypes and operation priority: + terms.append(reduce(lambda x, y: x*y, (args[:i] + [d] + args[i + 1:]), S.One)) + return Add.fromiter(terms) + + @cacheit + def _eval_derivative_n_times(self, s, n): + from .function import AppliedUndef + from .symbol import Symbol, symbols, Dummy + if not isinstance(s, (AppliedUndef, Symbol)): + # other types of s may not be well behaved, e.g. + # (cos(x)*sin(y)).diff([[x, y, z]]) + return super()._eval_derivative_n_times(s, n) + from .numbers import Integer + args = self.args + m = len(args) + if isinstance(n, (int, Integer)): + # https://en.wikipedia.org/wiki/General_Leibniz_rule#More_than_two_factors + terms = [] + from sympy.ntheory.multinomial import multinomial_coefficients_iterator + for kvals, c in multinomial_coefficients_iterator(m, n): + p = Mul(*[arg.diff((s, k)) for k, arg in zip(kvals, args)]) + terms.append(c * p) + return Add(*terms) + from sympy.concrete.summations import Sum + from sympy.functions.combinatorial.factorials import factorial + from sympy.functions.elementary.miscellaneous import Max + kvals = symbols("k1:%i" % m, cls=Dummy) + klast = n - sum(kvals) + nfact = factorial(n) + e, l = (# better to use the multinomial? + nfact/prod(map(factorial, kvals))/factorial(klast)*\ + Mul(*[args[t].diff((s, kvals[t])) for t in range(m-1)])*\ + args[-1].diff((s, Max(0, klast))), + [(k, 0, n) for k in kvals]) + return Sum(e, *l) + + def _eval_difference_delta(self, n, step): + from sympy.series.limitseq import difference_delta as dd + arg0 = self.args[0] + rest = Mul(*self.args[1:]) + return (arg0.subs(n, n + step) * dd(rest, n, step) + dd(arg0, n, step) * + rest) + + def _matches_simple(self, expr, repl_dict): + # handle (w*3).matches('x*5') -> {w: x*5/3} + coeff, terms = self.as_coeff_Mul() + terms = Mul.make_args(terms) + if len(terms) == 1: + newexpr = self.__class__._combine_inverse(expr, coeff) + return terms[0].matches(newexpr, repl_dict) + return + + def matches(self, expr, repl_dict=None, old=False): + expr = sympify(expr) + if self.is_commutative and expr.is_commutative: + return self._matches_commutative(expr, repl_dict, old) + elif self.is_commutative is not expr.is_commutative: + return None + + # Proceed only if both both expressions are non-commutative + c1, nc1 = self.args_cnc() + c2, nc2 = expr.args_cnc() + c1, c2 = [c or [1] for c in [c1, c2]] + + # TODO: Should these be self.func? + comm_mul_self = Mul(*c1) + comm_mul_expr = Mul(*c2) + + repl_dict = comm_mul_self.matches(comm_mul_expr, repl_dict, old) + + # If the commutative arguments didn't match and aren't equal, then + # then the expression as a whole doesn't match + if not repl_dict and c1 != c2: + return None + + # Now match the non-commutative arguments, expanding powers to + # multiplications + nc1 = Mul._matches_expand_pows(nc1) + nc2 = Mul._matches_expand_pows(nc2) + + repl_dict = Mul._matches_noncomm(nc1, nc2, repl_dict) + + return repl_dict or None + + @staticmethod + def _matches_expand_pows(arg_list): + new_args = [] + for arg in arg_list: + if arg.is_Pow and arg.exp > 0: + new_args.extend([arg.base] * arg.exp) + else: + new_args.append(arg) + return new_args + + @staticmethod + def _matches_noncomm(nodes, targets, repl_dict=None): + """Non-commutative multiplication matcher. + + `nodes` is a list of symbols within the matcher multiplication + expression, while `targets` is a list of arguments in the + multiplication expression being matched against. + """ + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + + # List of possible future states to be considered + agenda = [] + # The current matching state, storing index in nodes and targets + state = (0, 0) + node_ind, target_ind = state + # Mapping between wildcard indices and the index ranges they match + wildcard_dict = {} + + while target_ind < len(targets) and node_ind < len(nodes): + node = nodes[node_ind] + + if node.is_Wild: + Mul._matches_add_wildcard(wildcard_dict, state) + + states_matches = Mul._matches_new_states(wildcard_dict, state, + nodes, targets) + if states_matches: + new_states, new_matches = states_matches + agenda.extend(new_states) + if new_matches: + for match in new_matches: + repl_dict[match] = new_matches[match] + if not agenda: + return None + else: + state = agenda.pop() + node_ind, target_ind = state + + return repl_dict + + @staticmethod + def _matches_add_wildcard(dictionary, state): + node_ind, target_ind = state + if node_ind in dictionary: + begin, end = dictionary[node_ind] + dictionary[node_ind] = (begin, target_ind) + else: + dictionary[node_ind] = (target_ind, target_ind) + + @staticmethod + def _matches_new_states(dictionary, state, nodes, targets): + node_ind, target_ind = state + node = nodes[node_ind] + target = targets[target_ind] + + # Don't advance at all if we've exhausted the targets but not the nodes + if target_ind >= len(targets) - 1 and node_ind < len(nodes) - 1: + return None + + if node.is_Wild: + match_attempt = Mul._matches_match_wilds(dictionary, node_ind, + nodes, targets) + if match_attempt: + # If the same node has been matched before, don't return + # anything if the current match is diverging from the previous + # match + other_node_inds = Mul._matches_get_other_nodes(dictionary, + nodes, node_ind) + for ind in other_node_inds: + other_begin, other_end = dictionary[ind] + curr_begin, curr_end = dictionary[node_ind] + + other_targets = targets[other_begin:other_end + 1] + current_targets = targets[curr_begin:curr_end + 1] + + for curr, other in zip(current_targets, other_targets): + if curr != other: + return None + + # A wildcard node can match more than one target, so only the + # target index is advanced + new_state = [(node_ind, target_ind + 1)] + # Only move on to the next node if there is one + if node_ind < len(nodes) - 1: + new_state.append((node_ind + 1, target_ind + 1)) + return new_state, match_attempt + else: + # If we're not at a wildcard, then make sure we haven't exhausted + # nodes but not targets, since in this case one node can only match + # one target + if node_ind >= len(nodes) - 1 and target_ind < len(targets) - 1: + return None + + match_attempt = node.matches(target) + + if match_attempt: + return [(node_ind + 1, target_ind + 1)], match_attempt + elif node == target: + return [(node_ind + 1, target_ind + 1)], None + else: + return None + + @staticmethod + def _matches_match_wilds(dictionary, wildcard_ind, nodes, targets): + """Determine matches of a wildcard with sub-expression in `target`.""" + wildcard = nodes[wildcard_ind] + begin, end = dictionary[wildcard_ind] + terms = targets[begin:end + 1] + # TODO: Should this be self.func? + mult = Mul(*terms) if len(terms) > 1 else terms[0] + return wildcard.matches(mult) + + @staticmethod + def _matches_get_other_nodes(dictionary, nodes, node_ind): + """Find other wildcards that may have already been matched.""" + ind_node = nodes[node_ind] + return [ind for ind in dictionary if nodes[ind] == ind_node] + + @staticmethod + def _combine_inverse(lhs, rhs): + """ + Returns lhs/rhs, but treats arguments like symbols, so things + like oo/oo return 1 (instead of a nan) and ``I`` behaves like + a symbol instead of sqrt(-1). + """ + from sympy.simplify.simplify import signsimp + from .symbol import Dummy + if lhs == rhs: + return S.One + + def check(l, r): + if l.is_Float and r.is_comparable: + # if both objects are added to 0 they will share the same "normalization" + # and are more likely to compare the same. Since Add(foo, 0) will not allow + # the 0 to pass, we use __add__ directly. + return l.__add__(0) == r.evalf().__add__(0) + return False + if check(lhs, rhs) or check(rhs, lhs): + return S.One + if any(i.is_Pow or i.is_Mul for i in (lhs, rhs)): + # gruntz and limit wants a literal I to not combine + # with a power of -1 + d = Dummy('I') + _i = {S.ImaginaryUnit: d} + i_ = {d: S.ImaginaryUnit} + a = lhs.xreplace(_i).as_powers_dict() + b = rhs.xreplace(_i).as_powers_dict() + blen = len(b) + for bi in tuple(b.keys()): + if bi in a: + a[bi] -= b.pop(bi) + if not a[bi]: + a.pop(bi) + if len(b) != blen: + lhs = Mul(*[k**v for k, v in a.items()]).xreplace(i_) + rhs = Mul(*[k**v for k, v in b.items()]).xreplace(i_) + rv = lhs/rhs + srv = signsimp(rv) + return srv if srv.is_Number else rv + + def as_powers_dict(self): + d = defaultdict(int) + for term in self.args: + for b, e in term.as_powers_dict().items(): + d[b] += e + return d + + def as_numer_denom(self): + # don't use _from_args to rebuild the numerators and denominators + # as the order is not guaranteed to be the same once they have + # been separated from each other + numers, denoms = list(zip(*[f.as_numer_denom() for f in self.args])) + return self.func(*numers), self.func(*denoms) + + def as_base_exp(self): + e1 = None + bases = [] + nc = 0 + for m in self.args: + b, e = m.as_base_exp() + if not b.is_commutative: + nc += 1 + if e1 is None: + e1 = e + elif e != e1 or nc > 1 or not e.is_Integer: + return self, S.One + bases.append(b) + return self.func(*bases), e1 + + def _eval_is_polynomial(self, syms): + return all(term._eval_is_polynomial(syms) for term in self.args) + + def _eval_is_rational_function(self, syms): + return all(term._eval_is_rational_function(syms) for term in self.args) + + def _eval_is_meromorphic(self, x, a): + return _fuzzy_group((arg.is_meromorphic(x, a) for arg in self.args), + quick_exit=True) + + def _eval_is_algebraic_expr(self, syms): + return all(term._eval_is_algebraic_expr(syms) for term in self.args) + + _eval_is_commutative = lambda self: _fuzzy_group( + a.is_commutative for a in self.args) + + def _eval_is_complex(self): + comp = _fuzzy_group(a.is_complex for a in self.args) + if comp is False: + if any(a.is_infinite for a in self.args): + if any(a.is_zero is not False for a in self.args): + return None + return False + return comp + + def _eval_is_zero_infinite_helper(self): + # + # Helper used by _eval_is_zero and _eval_is_infinite. + # + # Three-valued logic is tricky so let us reason this carefully. It + # would be nice to say that we just check is_zero/is_infinite in all + # args but we need to be careful about the case that one arg is zero + # and another is infinite like Mul(0, oo) or more importantly a case + # where it is not known if the arguments are zero or infinite like + # Mul(y, 1/x). If either y or x could be zero then there is a + # *possibility* that we have Mul(0, oo) which should give None for both + # is_zero and is_infinite. + # + # We keep track of whether we have seen a zero or infinity but we also + # need to keep track of whether we have *possibly* seen one which + # would be indicated by None. + # + # For each argument there is the possibility that is_zero might give + # True, False or None and likewise that is_infinite might give True, + # False or None, giving 9 combinations. The True cases for is_zero and + # is_infinite are mutually exclusive though so there are 3 main cases: + # + # - is_zero = True + # - is_infinite = True + # - is_zero and is_infinite are both either False or None + # + # At the end seen_zero and seen_infinite can be any of 9 combinations + # of True/False/None. Unless one is False though we cannot return + # anything except None: + # + # - is_zero=True needs seen_zero=True and seen_infinite=False + # - is_zero=False needs seen_zero=False + # - is_infinite=True needs seen_infinite=True and seen_zero=False + # - is_infinite=False needs seen_infinite=False + # - anything else gives both is_zero=None and is_infinite=None + # + # The loop only sets the flags to True or None and never back to False. + # Hence as soon as neither flag is False we exit early returning None. + # In particular as soon as we encounter a single arg that has + # is_zero=is_infinite=None we exit. This is a common case since it is + # the default assumptions for a Symbol and also the case for most + # expressions containing such a symbol. The early exit gives a big + # speedup for something like Mul(*symbols('x:1000')).is_zero. + # + seen_zero = seen_infinite = False + + for a in self.args: + if a.is_zero: + if seen_infinite is not False: + return None, None + seen_zero = True + elif a.is_infinite: + if seen_zero is not False: + return None, None + seen_infinite = True + else: + if seen_zero is False and a.is_zero is None: + if seen_infinite is not False: + return None, None + seen_zero = None + if seen_infinite is False and a.is_infinite is None: + if seen_zero is not False: + return None, None + seen_infinite = None + + return seen_zero, seen_infinite + + def _eval_is_zero(self): + # True iff any arg is zero and no arg is infinite but need to handle + # three valued logic carefully. + seen_zero, seen_infinite = self._eval_is_zero_infinite_helper() + + if seen_zero is False: + return False + elif seen_zero is True and seen_infinite is False: + return True + else: + return None + + def _eval_is_infinite(self): + # True iff any arg is infinite and no arg is zero but need to handle + # three valued logic carefully. + seen_zero, seen_infinite = self._eval_is_zero_infinite_helper() + + if seen_infinite is True and seen_zero is False: + return True + elif seen_infinite is False: + return False + else: + return None + + # We do not need to implement _eval_is_finite because the assumptions + # system can infer it from finite = not infinite. + + def _eval_is_rational(self): + r = _fuzzy_group((a.is_rational for a in self.args), quick_exit=True) + if r: + return r + elif r is False: + # All args except one are rational + if all(a.is_zero is False for a in self.args): + return False + + def _eval_is_algebraic(self): + r = _fuzzy_group((a.is_algebraic for a in self.args), quick_exit=True) + if r: + return r + elif r is False: + # All args except one are algebraic + if all(a.is_zero is False for a in self.args): + return False + + # without involving odd/even checks this code would suffice: + #_eval_is_integer = lambda self: _fuzzy_group( + # (a.is_integer for a in self.args), quick_exit=True) + def _eval_is_integer(self): + is_rational = self._eval_is_rational() + if is_rational is False: + return False + + numerators = [] + denominators = [] + unknown = False + for a in self.args: + hit = False + if a.is_integer: + if abs(a) is not S.One: + numerators.append(a) + elif a.is_Rational: + n, d = a.as_numer_denom() + if abs(n) is not S.One: + numerators.append(n) + if d is not S.One: + denominators.append(d) + elif a.is_Pow: + b, e = a.as_base_exp() + if not b.is_integer or not e.is_integer: + hit = unknown = True + if e.is_negative: + denominators.append(2 if a is S.Half else + Pow(a, S.NegativeOne)) + elif not hit: + # int b and pos int e: a = b**e is integer + assert not e.is_positive + # for rational self and e equal to zero: a = b**e is 1 + assert not e.is_zero + return # sign of e unknown -> self.is_integer unknown + else: + # x**2, 2**x, or x**y with x and y int-unknown -> unknown + return + else: + return + + if not denominators and not unknown: + return True + + allodd = lambda x: all(i.is_odd for i in x) + alleven = lambda x: all(i.is_even for i in x) + anyeven = lambda x: any(i.is_even for i in x) + + from .relational import is_gt + if not numerators and denominators and all( + is_gt(_, S.One) for _ in denominators): + return False + elif unknown: + return + elif allodd(numerators) and anyeven(denominators): + return False + elif anyeven(numerators) and denominators == [2]: + return True + elif alleven(numerators) and allodd(denominators + ) and (Mul(*denominators, evaluate=False) - 1 + ).is_positive: + return False + if len(denominators) == 1: + d = denominators[0] + if d.is_Integer and d.is_even: + # if minimal power of 2 in num vs den is not + # negative then we have an integer + if (Add(*[i.as_base_exp()[1] for i in + numerators if i.is_even]) - trailing(d.p) + ).is_nonnegative: + return True + if len(numerators) == 1: + n = numerators[0] + if n.is_Integer and n.is_even: + # if minimal power of 2 in den vs num is positive + # then we have have a non-integer + if (Add(*[i.as_base_exp()[1] for i in + denominators if i.is_even]) - trailing(n.p) + ).is_positive: + return False + + def _eval_is_polar(self): + has_polar = any(arg.is_polar for arg in self.args) + return has_polar and \ + all(arg.is_polar or arg.is_positive for arg in self.args) + + def _eval_is_extended_real(self): + return self._eval_real_imag(True) + + def _eval_real_imag(self, real): + zero = False + t_not_re_im = None + + for t in self.args: + if (t.is_complex or t.is_infinite) is False and t.is_extended_real is False: + return False + elif t.is_imaginary: # I + real = not real + elif t.is_extended_real: # 2 + if not zero: + z = t.is_zero + if not z and zero is False: + zero = z + elif z: + if all(a.is_finite for a in self.args): + return True + return + elif t.is_extended_real is False: + # symbolic or literal like `2 + I` or symbolic imaginary + if t_not_re_im: + return # complex terms might cancel + t_not_re_im = t + elif t.is_imaginary is False: # symbolic like `2` or `2 + I` + if t_not_re_im: + return # complex terms might cancel + t_not_re_im = t + else: + return + + if t_not_re_im: + if t_not_re_im.is_extended_real is False: + if real: # like 3 + return zero # 3*(smthng like 2 + I or i) is not real + if t_not_re_im.is_imaginary is False: # symbolic 2 or 2 + I + if not real: # like I + return zero # I*(smthng like 2 or 2 + I) is not real + elif zero is False: + return real # can't be trumped by 0 + elif real: + return real # doesn't matter what zero is + + def _eval_is_imaginary(self): + if all(a.is_zero is False and a.is_finite for a in self.args): + return self._eval_real_imag(False) + + def _eval_is_hermitian(self): + return self._eval_herm_antiherm(True) + + def _eval_is_antihermitian(self): + return self._eval_herm_antiherm(False) + + def _eval_herm_antiherm(self, herm): + for t in self.args: + if t.is_hermitian is None or t.is_antihermitian is None: + return + if t.is_hermitian: + continue + elif t.is_antihermitian: + herm = not herm + else: + return + + if herm is not False: + return herm + + is_zero = self._eval_is_zero() + if is_zero: + return True + elif is_zero is False: + return herm + + def _eval_is_irrational(self): + for t in self.args: + a = t.is_irrational + if a: + others = list(self.args) + others.remove(t) + if all((x.is_rational and fuzzy_not(x.is_zero)) is True for x in others): + return True + return + if a is None: + return + if all(x.is_real for x in self.args): + return False + + def _eval_is_extended_positive(self): + """Return True if self is positive, False if not, and None if it + cannot be determined. + + Explanation + =========== + + This algorithm is non-recursive and works by keeping track of the + sign which changes when a negative or nonpositive is encountered. + Whether a nonpositive or nonnegative is seen is also tracked since + the presence of these makes it impossible to return True, but + possible to return False if the end result is nonpositive. e.g. + + pos * neg * nonpositive -> pos or zero -> None is returned + pos * neg * nonnegative -> neg or zero -> False is returned + """ + return self._eval_pos_neg(1) + + def _eval_pos_neg(self, sign): + saw_NON = saw_NOT = False + for t in self.args: + if t.is_extended_positive: + continue + elif t.is_extended_negative: + sign = -sign + elif t.is_zero: + if all(a.is_finite for a in self.args): + return False + return + elif t.is_extended_nonpositive: + sign = -sign + saw_NON = True + elif t.is_extended_nonnegative: + saw_NON = True + # FIXME: is_positive/is_negative is False doesn't take account of + # Symbol('x', infinite=True, extended_real=True) which has + # e.g. is_positive is False but has uncertain sign. + elif t.is_positive is False: + sign = -sign + if saw_NOT: + return + saw_NOT = True + elif t.is_negative is False: + if saw_NOT: + return + saw_NOT = True + else: + return + if sign == 1 and saw_NON is False and saw_NOT is False: + return True + if sign < 0: + return False + + def _eval_is_extended_negative(self): + return self._eval_pos_neg(-1) + + def _eval_is_odd(self): + is_integer = self._eval_is_integer() + if is_integer is not True: + return is_integer + + from sympy.simplify.radsimp import fraction + n, d = fraction(self) + if d.is_Integer and d.is_even: + # if minimal power of 2 in num vs den is + # positive then we have an even number + if (Add(*[i.as_base_exp()[1] for i in + Mul.make_args(n) if i.is_even]) - trailing(d.p) + ).is_positive: + return False + return + r, acc = True, 1 + for t in self.args: + if abs(t) is S.One: + continue + if t.is_even: + return False + if r is False: + pass + elif acc != 1 and (acc + t).is_odd: + r = False + elif t.is_even is None: + r = None + acc = t + return r + + def _eval_is_even(self): + from sympy.simplify.radsimp import fraction + n, d = fraction(self) + if n.is_Integer and n.is_even: + # if minimal power of 2 in den vs num is not + # negative then this is not an integer and + # can't be even + if (Add(*[i.as_base_exp()[1] for i in + Mul.make_args(d) if i.is_even]) - trailing(n.p) + ).is_nonnegative: + return False + + def _eval_is_composite(self): + """ + Here we count the number of arguments that have a minimum value + greater than two. + If there are more than one of such a symbol then the result is composite. + Else, the result cannot be determined. + """ + number_of_args = 0 # count of symbols with minimum value greater than one + for arg in self.args: + if not (arg.is_integer and arg.is_positive): + return None + if (arg-1).is_positive: + number_of_args += 1 + + if number_of_args > 1: + return True + + def _eval_subs(self, old, new): + from sympy.functions.elementary.complexes import sign + from sympy.ntheory.factor_ import multiplicity + from sympy.simplify.powsimp import powdenest + from sympy.simplify.radsimp import fraction + + if not old.is_Mul: + return None + + # try keep replacement literal so -2*x doesn't replace 4*x + if old.args[0].is_Number and old.args[0] < 0: + if self.args[0].is_Number: + if self.args[0] < 0: + return self._subs(-old, -new) + return None + + def base_exp(a): + # if I and -1 are in a Mul, they get both end up with + # a -1 base (see issue 6421); all we want here are the + # true Pow or exp separated into base and exponent + from sympy.functions.elementary.exponential import exp + if a.is_Pow or isinstance(a, exp): + return a.as_base_exp() + return a, S.One + + def breakup(eq): + """break up powers of eq when treated as a Mul: + b**(Rational*e) -> b**e, Rational + commutatives come back as a dictionary {b**e: Rational} + noncommutatives come back as a list [(b**e, Rational)] + """ + + (c, nc) = (defaultdict(int), []) + for a in Mul.make_args(eq): + a = powdenest(a) + (b, e) = base_exp(a) + if e is not S.One: + (co, _) = e.as_coeff_mul() + b = Pow(b, e/co) + e = co + if a.is_commutative: + c[b] += e + else: + nc.append([b, e]) + return (c, nc) + + def rejoin(b, co): + """ + Put rational back with exponent; in general this is not ok, but + since we took it from the exponent for analysis, it's ok to put + it back. + """ + + (b, e) = base_exp(b) + return Pow(b, e*co) + + def ndiv(a, b): + """if b divides a in an extractive way (like 1/4 divides 1/2 + but not vice versa, and 2/5 does not divide 1/3) then return + the integer number of times it divides, else return 0. + """ + if not b.q % a.q or not a.q % b.q: + return int(a/b) + return 0 + + # give Muls in the denominator a chance to be changed (see issue 5651) + # rv will be the default return value + rv = None + n, d = fraction(self) + self2 = self + if d is not S.One: + self2 = n._subs(old, new)/d._subs(old, new) + if not self2.is_Mul: + return self2._subs(old, new) + if self2 != self: + rv = self2 + + # Now continue with regular substitution. + + # handle the leading coefficient and use it to decide if anything + # should even be started; we always know where to find the Rational + # so it's a quick test + + co_self = self2.args[0] + co_old = old.args[0] + co_xmul = None + if co_old.is_Rational and co_self.is_Rational: + # if coeffs are the same there will be no updating to do + # below after breakup() step; so skip (and keep co_xmul=None) + if co_old != co_self: + co_xmul = co_self.extract_multiplicatively(co_old) + elif co_old.is_Rational: + return rv + + # break self and old into factors + + (c, nc) = breakup(self2) + (old_c, old_nc) = breakup(old) + + # update the coefficients if we had an extraction + # e.g. if co_self were 2*(3/35*x)**2 and co_old = 3/5 + # then co_self in c is replaced by (3/5)**2 and co_residual + # is 2*(1/7)**2 + + if co_xmul and co_xmul.is_Rational and abs(co_old) != 1: + mult = S(multiplicity(abs(co_old), co_self)) + c.pop(co_self) + if co_old in c: + c[co_old] += mult + else: + c[co_old] = mult + co_residual = co_self/co_old**mult + else: + co_residual = 1 + + # do quick tests to see if we can't succeed + + ok = True + if len(old_nc) > len(nc): + # more non-commutative terms + ok = False + elif len(old_c) > len(c): + # more commutative terms + ok = False + elif {i[0] for i in old_nc}.difference({i[0] for i in nc}): + # unmatched non-commutative bases + ok = False + elif set(old_c).difference(set(c)): + # unmatched commutative terms + ok = False + elif any(sign(c[b]) != sign(old_c[b]) for b in old_c): + # differences in sign + ok = False + if not ok: + return rv + + if not old_c: + cdid = None + else: + rat = [] + for (b, old_e) in old_c.items(): + c_e = c[b] + rat.append(ndiv(c_e, old_e)) + if not rat[-1]: + return rv + cdid = min(rat) + + if not old_nc: + ncdid = None + for i in range(len(nc)): + nc[i] = rejoin(*nc[i]) + else: + ncdid = 0 # number of nc replacements we did + take = len(old_nc) # how much to look at each time + limit = cdid or S.Infinity # max number that we can take + failed = [] # failed terms will need subs if other terms pass + i = 0 + while limit and i + take <= len(nc): + hit = False + + # the bases must be equivalent in succession, and + # the powers must be extractively compatible on the + # first and last factor but equal in between. + + rat = [] + for j in range(take): + if nc[i + j][0] != old_nc[j][0]: + break + elif j == 0: + rat.append(ndiv(nc[i + j][1], old_nc[j][1])) + elif j == take - 1: + rat.append(ndiv(nc[i + j][1], old_nc[j][1])) + elif nc[i + j][1] != old_nc[j][1]: + break + else: + rat.append(1) + j += 1 + else: + ndo = min(rat) + if ndo: + if take == 1: + if cdid: + ndo = min(cdid, ndo) + nc[i] = Pow(new, ndo)*rejoin(nc[i][0], + nc[i][1] - ndo*old_nc[0][1]) + else: + ndo = 1 + + # the left residual + + l = rejoin(nc[i][0], nc[i][1] - ndo* + old_nc[0][1]) + + # eliminate all middle terms + + mid = new + + # the right residual (which may be the same as the middle if take == 2) + + ir = i + take - 1 + r = (nc[ir][0], nc[ir][1] - ndo* + old_nc[-1][1]) + if r[1]: + if i + take < len(nc): + nc[i:i + take] = [l*mid, r] + else: + r = rejoin(*r) + nc[i:i + take] = [l*mid*r] + else: + + # there was nothing left on the right + + nc[i:i + take] = [l*mid] + + limit -= ndo + ncdid += ndo + hit = True + if not hit: + + # do the subs on this failing factor + + failed.append(i) + i += 1 + else: + + if not ncdid: + return rv + + # although we didn't fail, certain nc terms may have + # failed so we rebuild them after attempting a partial + # subs on them + + failed.extend(range(i, len(nc))) + for i in failed: + nc[i] = rejoin(*nc[i]).subs(old, new) + + # rebuild the expression + + if cdid is None: + do = ncdid + elif ncdid is None: + do = cdid + else: + do = min(ncdid, cdid) + + margs = [] + for b in c: + if b in old_c: + + # calculate the new exponent + + e = c[b] - old_c[b]*do + margs.append(rejoin(b, e)) + else: + margs.append(rejoin(b.subs(old, new), c[b])) + if cdid and not ncdid: + + # in case we are replacing commutative with non-commutative, + # we want the new term to come at the front just like the + # rest of this routine + + margs = [Pow(new, cdid)] + margs + return co_residual*self2.func(*margs)*self2.func(*nc) + + def _eval_nseries(self, x, n, logx, cdir=0): + from .function import PoleError + from sympy.functions.elementary.integers import ceiling + from sympy.series.order import Order + + def coeff_exp(term, x): + lt = term.as_coeff_exponent(x) + if lt[0].has(x): + try: + lt = term.leadterm(x) + except ValueError: + return term, S.Zero + return lt + + ords = [] + + try: + for t in self.args: + coeff, exp = t.leadterm(x) + if not coeff.has(x): + ords.append((t, exp)) + else: + raise ValueError + + n0 = sum(t[1] for t in ords if t[1].is_number) + facs = [] + for t, m in ords: + n1 = ceiling(n - n0 + (m if m.is_number else 0)) + s = t.nseries(x, n=n1, logx=logx, cdir=cdir) + ns = s.getn() + if ns is not None: + if ns < n1: # less than expected + n -= n1 - ns # reduce n + facs.append(s) + + except (ValueError, NotImplementedError, TypeError, PoleError): + # XXX: Catching so many generic exceptions around a large block of + # code will mask bugs. Whatever purpose catching these exceptions + # serves should be handled in a different way. + n0 = sympify(sum(t[1] for t in ords if t[1].is_number)) + if n0.is_nonnegative: + n0 = S.Zero + facs = [t.nseries(x, n=ceiling(n-n0), logx=logx, cdir=cdir) for t in self.args] + from sympy.simplify.powsimp import powsimp + res = powsimp(self.func(*facs).expand(), combine='exp', deep=True) + if res.has(Order): + res += Order(x**n, x) + return res + + res = S.Zero + ords2 = [Add.make_args(factor) for factor in facs] + + for fac in product(*ords2): + ords3 = [coeff_exp(term, x) for term in fac] + coeffs, powers = zip(*ords3) + power = sum(powers) + if (power - n).is_negative: + res += Mul(*coeffs)*(x**power) + + def max_degree(e, x): + if e is x: + return S.One + if e.is_Atom: + return S.Zero + if e.is_Add: + return max(max_degree(a, x) for a in e.args) + if e.is_Mul: + return Add(*[max_degree(a, x) for a in e.args]) + if e.is_Pow: + return max_degree(e.base, x)*e.exp + return S.Zero + + if self.is_polynomial(x): + from sympy.polys.polyerrors import PolynomialError + from sympy.polys.polytools import degree + try: + if max_degree(self, x) >= n or degree(self, x) != degree(res, x): + res += Order(x**n, x) + except PolynomialError: + pass + else: + return res + + if res != self: + if (self - res).subs(x, 0) == S.Zero and n > 0: + lt = self._eval_as_leading_term(x, logx=logx, cdir=cdir) + if lt == S.Zero: + return res + res += Order(x**n, x) + return res + + def _eval_as_leading_term(self, x, logx, cdir): + return self.func(*[t.as_leading_term(x, logx=logx, cdir=cdir) for t in self.args]) + + def _eval_conjugate(self): + return self.func(*[t.conjugate() for t in self.args]) + + def _eval_transpose(self): + return self.func(*[t.transpose() for t in self.args[::-1]]) + + def _eval_adjoint(self): + return self.func(*[t.adjoint() for t in self.args[::-1]]) + + def as_content_primitive(self, radical=False, clear=True): + """Return the tuple (R, self/R) where R is the positive Rational + extracted from self. + + Examples + ======== + + >>> from sympy import sqrt + >>> (-3*sqrt(2)*(2 - 2*sqrt(2))).as_content_primitive() + (6, -sqrt(2)*(1 - sqrt(2))) + + See docstring of Expr.as_content_primitive for more examples. + """ + + coef = S.One + args = [] + for a in self.args: + c, p = a.as_content_primitive(radical=radical, clear=clear) + coef *= c + if p is not S.One: + args.append(p) + # don't use self._from_args here to reconstruct args + # since there may be identical args now that should be combined + # e.g. (2+2*x)*(3+3*x) should be (6, (1 + x)**2) not (6, (1+x)*(1+x)) + return coef, self.func(*args) + + def as_ordered_factors(self, order=None): + """Transform an expression into an ordered list of factors. + + Examples + ======== + + >>> from sympy import sin, cos + >>> from sympy.abc import x, y + + >>> (2*x*y*sin(x)*cos(x)).as_ordered_factors() + [2, x, y, sin(x), cos(x)] + + """ + cpart, ncpart = self.args_cnc() + cpart.sort(key=lambda expr: expr.sort_key(order=order)) + return cpart + ncpart + + @property + def _sorted_args(self): + return tuple(self.as_ordered_factors()) + +mul = AssocOpDispatcher('mul') + + +def prod(a, start=1): + """Return product of elements of a. Start with int 1 so if only + ints are included then an int result is returned. + + Examples + ======== + + >>> from sympy import prod, S + >>> prod(range(3)) + 0 + >>> type(_) is int + True + >>> prod([S(2), 3]) + 6 + >>> _.is_Integer + True + + You can start the product at something other than 1: + + >>> prod([1, 2], 3) + 6 + + """ + return reduce(operator.mul, a, start) + + +def _keep_coeff(coeff, factors, clear=True, sign=False): + """Return ``coeff*factors`` unevaluated if necessary. + + If ``clear`` is False, do not keep the coefficient as a factor + if it can be distributed on a single factor such that one or + more terms will still have integer coefficients. + + If ``sign`` is True, allow a coefficient of -1 to remain factored out. + + Examples + ======== + + >>> from sympy.core.mul import _keep_coeff + >>> from sympy.abc import x, y + >>> from sympy import S + + >>> _keep_coeff(S.Half, x + 2) + (x + 2)/2 + >>> _keep_coeff(S.Half, x + 2, clear=False) + x/2 + 1 + >>> _keep_coeff(S.Half, (x + 2)*y, clear=False) + y*(x + 2)/2 + >>> _keep_coeff(S(-1), x + y) + -x - y + >>> _keep_coeff(S(-1), x + y, sign=True) + -(x + y) + """ + if not coeff.is_Number: + if factors.is_Number: + factors, coeff = coeff, factors + else: + return coeff*factors + if factors is S.One: + return coeff + if coeff is S.One: + return factors + elif coeff is S.NegativeOne and not sign: + return -factors + elif factors.is_Add: + if not clear and coeff.is_Rational and coeff.q != 1: + args = [i.as_coeff_Mul() for i in factors.args] + args = [(_keep_coeff(c, coeff), m) for c, m in args] + if any(c.is_Integer for c, _ in args): + return Add._from_args([Mul._from_args( + i[1:] if i[0] == 1 else i) for i in args]) + return Mul(coeff, factors, evaluate=False) + elif factors.is_Mul: + margs = list(factors.args) + if margs[0].is_Number: + margs[0] *= coeff + if margs[0] == 1: + margs.pop(0) + else: + margs.insert(0, coeff) + return Mul._from_args(margs) + else: + m = coeff*factors + if m.is_Number and not factors.is_Number: + m = Mul._from_args((coeff, factors)) + return m + +def expand_2arg(e): + def do(e): + if e.is_Mul: + c, r = e.as_coeff_Mul() + if c.is_Number and r.is_Add: + return _unevaluated_Add(*[c*ri for ri in r.args]) + return e + return bottom_up(e, do) + + +from .numbers import Rational +from .power import Pow +from .add import Add, _unevaluated_Add diff --git a/phivenv/Lib/site-packages/sympy/core/multidimensional.py b/phivenv/Lib/site-packages/sympy/core/multidimensional.py new file mode 100644 index 0000000000000000000000000000000000000000..133e0ab6cba6a87c627feb6f6034a6daed1128c5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/multidimensional.py @@ -0,0 +1,131 @@ +""" +Provides functionality for multidimensional usage of scalar-functions. + +Read the vectorize docstring for more details. +""" + +from functools import wraps + + +def apply_on_element(f, args, kwargs, n): + """ + Returns a structure with the same dimension as the specified argument, + where each basic element is replaced by the function f applied on it. All + other arguments stay the same. + """ + # Get the specified argument. + if isinstance(n, int): + structure = args[n] + is_arg = True + elif isinstance(n, str): + structure = kwargs[n] + is_arg = False + + # Define reduced function that is only dependent on the specified argument. + def f_reduced(x): + if hasattr(x, "__iter__"): + return list(map(f_reduced, x)) + else: + if is_arg: + args[n] = x + else: + kwargs[n] = x + return f(*args, **kwargs) + + # f_reduced will call itself recursively so that in the end f is applied to + # all basic elements. + return list(map(f_reduced, structure)) + + +def iter_copy(structure): + """ + Returns a copy of an iterable object (also copying all embedded iterables). + """ + return [iter_copy(i) if hasattr(i, "__iter__") else i for i in structure] + + +def structure_copy(structure): + """ + Returns a copy of the given structure (numpy-array, list, iterable, ..). + """ + if hasattr(structure, "copy"): + return structure.copy() + return iter_copy(structure) + + +class vectorize: + """ + Generalizes a function taking scalars to accept multidimensional arguments. + + Examples + ======== + + >>> from sympy import vectorize, diff, sin, symbols, Function + >>> x, y, z = symbols('x y z') + >>> f, g, h = list(map(Function, 'fgh')) + + >>> @vectorize(0) + ... def vsin(x): + ... return sin(x) + + >>> vsin([1, x, y]) + [sin(1), sin(x), sin(y)] + + >>> @vectorize(0, 1) + ... def vdiff(f, y): + ... return diff(f, y) + + >>> vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z]) + [[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y), Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x), Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)], [Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]] + """ + def __init__(self, *mdargs): + """ + The given numbers and strings characterize the arguments that will be + treated as data structures, where the decorated function will be applied + to every single element. + If no argument is given, everything is treated multidimensional. + """ + for a in mdargs: + if not isinstance(a, (int, str)): + raise TypeError("a is of invalid type") + self.mdargs = mdargs + + def __call__(self, f): + """ + Returns a wrapper for the one-dimensional function that can handle + multidimensional arguments. + """ + @wraps(f) + def wrapper(*args, **kwargs): + # Get arguments that should be treated multidimensional + if self.mdargs: + mdargs = self.mdargs + else: + mdargs = range(len(args)) + kwargs.keys() + + arglength = len(args) + + for n in mdargs: + if isinstance(n, int): + if n >= arglength: + continue + entry = args[n] + is_arg = True + elif isinstance(n, str): + try: + entry = kwargs[n] + except KeyError: + continue + is_arg = False + if hasattr(entry, "__iter__"): + # Create now a copy of the given array and manipulate then + # the entries directly. + if is_arg: + args = list(args) + args[n] = structure_copy(entry) + else: + kwargs[n] = structure_copy(entry) + result = apply_on_element(wrapper, args, kwargs, n) + return result + return f(*args, **kwargs) + return wrapper diff --git a/phivenv/Lib/site-packages/sympy/core/numbers.py b/phivenv/Lib/site-packages/sympy/core/numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..9fa13fbb96aa25a8e60e048c0147a5e660804ccc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/numbers.py @@ -0,0 +1,4482 @@ +from __future__ import annotations + +from typing import overload + +import numbers +import decimal +import fractions +import math + +from .containers import Tuple +from .sympify import (SympifyError, _sympy_converter, sympify, _convert_numpy_types, + _sympify, _is_numpy_instance) +from .singleton import S, Singleton +from .basic import Basic +from .expr import Expr, AtomicExpr +from .evalf import pure_complex +from .cache import cacheit, clear_cache +from .decorators import _sympifyit +from .intfunc import num_digits, igcd, ilcm, mod_inverse, integer_nthroot +from .logic import fuzzy_not +from .kind import NumberKind +from .sorting import ordered +from sympy.external.gmpy import SYMPY_INTS, gmpy, flint +from sympy.multipledispatch import dispatch +import mpmath +import mpmath.libmp as mlib +from mpmath.libmp import bitcount, round_nearest as rnd +from mpmath.libmp.backend import MPZ +from mpmath.libmp import mpf_pow, mpf_pi, mpf_e, phi_fixed +from mpmath.ctx_mp_python import mpnumeric +from mpmath.libmp.libmpf import ( + finf as _mpf_inf, fninf as _mpf_ninf, + fnan as _mpf_nan, fzero, _normalize as mpf_normalize, + prec_to_dps, dps_to_prec) +from sympy.utilities.misc import debug +from sympy.utilities.exceptions import sympy_deprecation_warning +from .parameters import global_parameters + +_LOG2 = math.log(2) + + +def comp(z1, z2, tol=None): + r"""Return a bool indicating whether the error between z1 and z2 + is $\le$ ``tol``. + + Examples + ======== + + If ``tol`` is ``None`` then ``True`` will be returned if + :math:`|z1 - z2|\times 10^p \le 5` where $p$ is minimum value of the + decimal precision of each value. + + >>> from sympy import comp, pi + >>> pi4 = pi.n(4); pi4 + 3.142 + >>> comp(_, 3.142) + True + >>> comp(pi4, 3.141) + False + >>> comp(pi4, 3.143) + False + + A comparison of strings will be made + if ``z1`` is a Number and ``z2`` is a string or ``tol`` is ''. + + >>> comp(pi4, 3.1415) + True + >>> comp(pi4, 3.1415, '') + False + + When ``tol`` is provided and $z2$ is non-zero and + :math:`|z1| > 1` the error is normalized by :math:`|z1|`: + + >>> abs(pi4 - 3.14)/pi4 + 0.000509791731426756 + >>> comp(pi4, 3.14, .001) # difference less than 0.1% + True + >>> comp(pi4, 3.14, .0005) # difference less than 0.1% + False + + When :math:`|z1| \le 1` the absolute error is used: + + >>> 1/pi4 + 0.3183 + >>> abs(1/pi4 - 0.3183)/(1/pi4) + 3.07371499106316e-5 + >>> abs(1/pi4 - 0.3183) + 9.78393554684764e-6 + >>> comp(1/pi4, 0.3183, 1e-5) + True + + To see if the absolute error between ``z1`` and ``z2`` is less + than or equal to ``tol``, call this as ``comp(z1 - z2, 0, tol)`` + or ``comp(z1 - z2, tol=tol)``: + + >>> abs(pi4 - 3.14) + 0.00160156249999988 + >>> comp(pi4 - 3.14, 0, .002) + True + >>> comp(pi4 - 3.14, 0, .001) + False + """ + if isinstance(z2, str): + if not pure_complex(z1, or_real=True): + raise ValueError('when z2 is a str z1 must be a Number') + return str(z1) == z2 + if not z1: + z1, z2 = z2, z1 + if not z1: + return True + if not tol: + a, b = z1, z2 + if tol == '': + return str(a) == str(b) + if tol is None: + a, b = sympify(a), sympify(b) + if not all(i.is_number for i in (a, b)): + raise ValueError('expecting 2 numbers') + fa = a.atoms(Float) + fb = b.atoms(Float) + if not fa and not fb: + # no floats -- compare exactly + return a == b + # get a to be pure_complex + for _ in range(2): + ca = pure_complex(a, or_real=True) + if not ca: + if fa: + a = a.n(prec_to_dps(min(i._prec for i in fa))) + ca = pure_complex(a, or_real=True) + break + else: + fa, fb = fb, fa + a, b = b, a + cb = pure_complex(b) + if not cb and fb: + b = b.n(prec_to_dps(min(i._prec for i in fb))) + cb = pure_complex(b, or_real=True) + if ca and cb and (ca[1] or cb[1]): + return all(comp(i, j) for i, j in zip(ca, cb)) + tol = 10**prec_to_dps(min(a._prec, getattr(b, '_prec', a._prec))) + return int(abs(a - b)*tol) <= 5 + diff = abs(z1 - z2) + az1 = abs(z1) + if z2 and az1 > 1: + return diff/az1 <= tol + else: + return diff <= tol + + +def mpf_norm(mpf, prec): + """Return the mpf tuple normalized appropriately for the indicated + precision after doing a check to see if zero should be returned or + not when the mantissa is 0. ``mpf_normlize`` always assumes that this + is zero, but it may not be since the mantissa for mpf's values "+inf", + "-inf" and "nan" have a mantissa of zero, too. + + Note: this is not intended to validate a given mpf tuple, so sending + mpf tuples that were not created by mpmath may produce bad results. This + is only a wrapper to ``mpf_normalize`` which provides the check for non- + zero mpfs that have a 0 for the mantissa. + """ + sign, man, expt, bc = mpf + if not man: + # hack for mpf_normalize which does not do this; + # it assumes that if man is zero the result is 0 + # (see issue 6639) + if not bc: + return fzero + else: + # don't change anything; this should already + # be a well formed mpf tuple + return mpf + + # Necessary if mpmath is using the gmpy backend + from mpmath.libmp.backend import MPZ + rv = mpf_normalize(sign, MPZ(man), expt, bc, prec, rnd) + return rv + +# TODO: we should use the warnings module +_errdict = {"divide": False} + + +def seterr(divide=False): + """ + Should SymPy raise an exception on 0/0 or return a nan? + + divide == True .... raise an exception + divide == False ... return nan + """ + if _errdict["divide"] != divide: + clear_cache() + _errdict["divide"] = divide + + +def _as_integer_ratio(p): + neg_pow, man, expt, _ = getattr(p, '_mpf_', mpmath.mpf(p)._mpf_) + p = [1, -1][neg_pow % 2]*man + if expt < 0: + q = 2**-expt + else: + q = 1 + p *= 2**expt + return int(p), int(q) + + +def _decimal_to_Rational_prec(dec): + """Convert an ordinary decimal instance to a Rational.""" + if not dec.is_finite(): + raise TypeError("dec must be finite, got %s." % dec) + s, d, e = dec.as_tuple() + prec = len(d) + if e >= 0: # it's an integer + rv = Integer(int(dec)) + else: + s = (-1)**s + d = sum(di*10**i for i, di in enumerate(reversed(d))) + rv = Rational(s*d, 10**-e) + return rv, prec + +_dig = str.maketrans(dict.fromkeys('1234567890')) + +def _literal_float(s): + """return True if s is space-trimmed number literal else False + + Python allows underscore as digit separators: there must be a + digit on each side. So neither a leading underscore nor a + double underscore are valid as part of a number. A number does + not have to precede the decimal point, but there must be a + digit before the optional "e" or "E" that begins the signs + exponent of the number which must be an integer, perhaps with + underscore separators. + + SymPy allows space as a separator; if the calling routine replaces + them with underscores then the same semantics will be enforced + for them as for underscores: there can only be 1 *between* digits. + + We don't check for error from float(s) because we don't know + whether s is malicious or not. A regex for this could maybe + be written but will it be understood by most who read it? + """ + # mantissa and exponent + parts = s.split('e') + if len(parts) > 2: + return False + if len(parts) == 2: + m, e = parts + if e.startswith(tuple('+-')): + e = e[1:] + if not e: + return False + else: + m, e = s, '1' + # integer and fraction of mantissa + parts = m.split('.') + if len(parts) > 2: + return False + elif len(parts) == 2: + i, f = parts + else: + i, f = m, '1' + if not i and not f: + return False + if i and i[0] in '+-': + i = i[1:] + if not i: # -.3e4 -> -0.3e4 + i = '1' + f = f or '1' + # check that all groups contain only digits and are not null + for n in (i, f, e): + for g in n.split('_'): + if not g or g.translate(_dig): + return False + return True + +# (a,b) -> gcd(a,b) + +# TODO caching with decorator, but not to degrade performance + + +class Number(AtomicExpr): + """Represents atomic numbers in SymPy. + + Explanation + =========== + + Floating point numbers are represented by the Float class. + Rational numbers (of any size) are represented by the Rational class. + Integer numbers (of any size) are represented by the Integer class. + Float and Rational are subclasses of Number; Integer is a subclass + of Rational. + + For example, ``2/3`` is represented as ``Rational(2, 3)`` which is + a different object from the floating point number obtained with + Python division ``2/3``. Even for numbers that are exactly + represented in binary, there is a difference between how two forms, + such as ``Rational(1, 2)`` and ``Float(0.5)``, are used in SymPy. + The rational form is to be preferred in symbolic computations. + + Other kinds of numbers, such as algebraic numbers ``sqrt(2)`` or + complex numbers ``3 + 4*I``, are not instances of Number class as + they are not atomic. + + See Also + ======== + + Float, Integer, Rational + """ + is_commutative = True + is_number = True + is_Number = True + + __slots__ = () + + # Used to make max(x._prec, y._prec) return x._prec when only x is a float + _prec = -1 + + kind = NumberKind + + def __new__(cls, *obj): + if len(obj) == 1: + obj = obj[0] + + if isinstance(obj, Number): + return obj + if isinstance(obj, SYMPY_INTS): + return Integer(obj) + if isinstance(obj, tuple) and len(obj) == 2: + return Rational(*obj) + if isinstance(obj, (float, mpmath.mpf, decimal.Decimal)): + return Float(obj) + if isinstance(obj, str): + _obj = obj.lower() # float('INF') == float('inf') + if _obj == 'nan': + return S.NaN + elif _obj == 'inf': + return S.Infinity + elif _obj == '+inf': + return S.Infinity + elif _obj == '-inf': + return S.NegativeInfinity + val = sympify(obj) + if isinstance(val, Number): + return val + else: + raise ValueError('String "%s" does not denote a Number' % obj) + msg = "expected str|int|long|float|Decimal|Number object but got %r" + raise TypeError(msg % type(obj).__name__) + + def could_extract_minus_sign(self): + return bool(self.is_extended_negative) + + def invert(self, other, *gens, **args): + from sympy.polys.polytools import invert + if getattr(other, 'is_number', True): + return mod_inverse(self, other) + return invert(self, other, *gens, **args) + + def __divmod__(self, other): + from sympy.functions.elementary.complexes import sign + + try: + other = Number(other) + if self.is_infinite or S.NaN in (self, other): + return (S.NaN, S.NaN) + except TypeError: + return NotImplemented + if not other: + raise ZeroDivisionError('modulo by zero') + if self.is_Integer and other.is_Integer: + return Tuple(*divmod(self.p, other.p)) + elif isinstance(other, Float): + rat = self/Rational(other) + else: + rat = self/other + if other.is_finite: + w = int(rat) if rat >= 0 else int(rat) - 1 + r = self - other*w + if r == Float(other): + w += 1 + r = 0 + if isinstance(self, Float) or isinstance(other, Float): + r = Float(r) # in case w or r is 0 + else: + w = 0 if not self or (sign(self) == sign(other)) else -1 + r = other if w else self + return Tuple(w, r) + + def __rdivmod__(self, other): + try: + other = Number(other) + except TypeError: + return NotImplemented + return divmod(other, self) + + def _as_mpf_val(self, prec): + """Evaluation of mpf tuple accurate to at least prec bits.""" + raise NotImplementedError('%s needs ._as_mpf_val() method' % + (self.__class__.__name__)) + + def _eval_evalf(self, prec): + return Float._new(self._as_mpf_val(prec), prec) + + def _as_mpf_op(self, prec): + prec = max(prec, self._prec) + return self._as_mpf_val(prec), prec + + def __float__(self): + return mlib.to_float(self._as_mpf_val(53)) + + def floor(self): + raise NotImplementedError('%s needs .floor() method' % + (self.__class__.__name__)) + + def ceiling(self): + raise NotImplementedError('%s needs .ceiling() method' % + (self.__class__.__name__)) + + def __floor__(self): + return self.floor() + + def __ceil__(self): + return self.ceiling() + + def _eval_conjugate(self): + return self + + def _eval_order(self, *symbols): + from sympy.series.order import Order + # Order(5, x, y) -> Order(1,x,y) + return Order(S.One, *symbols) + + def _eval_subs(self, old, new): + if old == -self: + return -new + return self # there is no other possibility + + @classmethod + def class_key(cls): + return 1, 0, 'Number' + + @cacheit + def sort_key(self, order=None): + return self.class_key(), (0, ()), (), self + + def __neg__(self) -> Number: + raise NotImplementedError + + @overload + def __add__(self, other: Number | int | float) -> Number: ... + @overload + def __add__(self, other: Expr) -> Expr: ... + + @_sympifyit('other', NotImplemented) + def __add__(self, other) -> Expr: + if isinstance(other, Number) and global_parameters.evaluate: + if other is S.NaN: + return S.NaN + elif other is S.Infinity: + return S.Infinity + elif other is S.NegativeInfinity: + return S.NegativeInfinity + return AtomicExpr.__add__(self, other) + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other is S.NaN: + return S.NaN + elif other is S.Infinity: + return S.NegativeInfinity + elif other is S.NegativeInfinity: + return S.Infinity + return AtomicExpr.__sub__(self, other) + + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other is S.NaN: + return S.NaN + elif other is S.Infinity: + if self.is_zero: + return S.NaN + elif self.is_positive: + return S.Infinity + else: + return S.NegativeInfinity + elif other is S.NegativeInfinity: + if self.is_zero: + return S.NaN + elif self.is_positive: + return S.NegativeInfinity + else: + return S.Infinity + elif isinstance(other, Tuple): + return NotImplemented + return AtomicExpr.__mul__(self, other) + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other is S.NaN: + return S.NaN + elif other in (S.Infinity, S.NegativeInfinity): + return S.Zero + return AtomicExpr.__truediv__(self, other) + + def __eq__(self, other): + raise NotImplementedError('%s needs .__eq__() method' % + (self.__class__.__name__)) + + def __ne__(self, other): + raise NotImplementedError('%s needs .__ne__() method' % + (self.__class__.__name__)) + + def __lt__(self, other): + try: + other = _sympify(other) + except SympifyError: + raise TypeError("Invalid comparison %s < %s" % (self, other)) + raise NotImplementedError('%s needs .__lt__() method' % + (self.__class__.__name__)) + + def __le__(self, other): + try: + other = _sympify(other) + except SympifyError: + raise TypeError("Invalid comparison %s <= %s" % (self, other)) + raise NotImplementedError('%s needs .__le__() method' % + (self.__class__.__name__)) + + def __gt__(self, other): + try: + other = _sympify(other) + except SympifyError: + raise TypeError("Invalid comparison %s > %s" % (self, other)) + return _sympify(other).__lt__(self) + + def __ge__(self, other): + try: + other = _sympify(other) + except SympifyError: + raise TypeError("Invalid comparison %s >= %s" % (self, other)) + return _sympify(other).__le__(self) + + def __hash__(self): + return super().__hash__() + + def is_constant(self, *wrt, **flags): + return True + + def as_coeff_mul(self, *deps, rational=True, **kwargs): + # a -> c*t + if self.is_Rational or not rational: + return self, () + elif self.is_negative: + return S.NegativeOne, (-self,) + return S.One, (self,) + + def as_coeff_add(self, *deps): + # a -> c + t + if self.is_Rational: + return self, () + return S.Zero, (self,) + + def as_coeff_Mul(self, rational=False): + """Efficiently extract the coefficient of a product.""" + if not rational: + return self, S.One + return S.One, self + + def as_coeff_Add(self, rational=False): + """Efficiently extract the coefficient of a summation.""" + if not rational: + return self, S.Zero + return S.Zero, self + + def gcd(self, other): + """Compute GCD of `self` and `other`. """ + from sympy.polys.polytools import gcd + return gcd(self, other) + + def lcm(self, other): + """Compute LCM of `self` and `other`. """ + from sympy.polys.polytools import lcm + return lcm(self, other) + + def cofactors(self, other): + """Compute GCD and cofactors of `self` and `other`. """ + from sympy.polys.polytools import cofactors + return cofactors(self, other) + + +class Float(Number): + """Represent a floating-point number of arbitrary precision. + + Examples + ======== + + >>> from sympy import Float + >>> Float(3.5) + 3.50000000000000 + >>> Float(3) + 3.00000000000000 + + Creating Floats from strings (and Python ``int`` and ``long`` + types) will give a minimum precision of 15 digits, but the + precision will automatically increase to capture all digits + entered. + + >>> Float(1) + 1.00000000000000 + >>> Float(10**20) + 100000000000000000000. + >>> Float('1e20') + 100000000000000000000. + + However, *floating-point* numbers (Python ``float`` types) retain + only 15 digits of precision: + + >>> Float(1e20) + 1.00000000000000e+20 + >>> Float(1.23456789123456789) + 1.23456789123457 + + It may be preferable to enter high-precision decimal numbers + as strings: + + >>> Float('1.23456789123456789') + 1.23456789123456789 + + The desired number of digits can also be specified: + + >>> Float('1e-3', 3) + 0.00100 + >>> Float(100, 4) + 100.0 + + Float can automatically count significant figures if a null string + is sent for the precision; spaces or underscores are also allowed. (Auto- + counting is only allowed for strings, ints and longs). + + >>> Float('123 456 789.123_456', '') + 123456789.123456 + >>> Float('12e-3', '') + 0.012 + >>> Float(3, '') + 3. + + If a number is written in scientific notation, only the digits before the + exponent are considered significant if a decimal appears, otherwise the + "e" signifies only how to move the decimal: + + >>> Float('60.e2', '') # 2 digits significant + 6.0e+3 + >>> Float('60e2', '') # 4 digits significant + 6000. + >>> Float('600e-2', '') # 3 digits significant + 6.00 + + Notes + ===== + + Floats are inexact by their nature unless their value is a binary-exact + value. + + >>> approx, exact = Float(.1, 1), Float(.125, 1) + + For calculation purposes, evalf needs to be able to change the precision + but this will not increase the accuracy of the inexact value. The + following is the most accurate 5-digit approximation of a value of 0.1 + that had only 1 digit of precision: + + >>> approx.evalf(5) + 0.099609 + + By contrast, 0.125 is exact in binary (as it is in base 10) and so it + can be passed to Float or evalf to obtain an arbitrary precision with + matching accuracy: + + >>> Float(exact, 5) + 0.12500 + >>> exact.evalf(20) + 0.12500000000000000000 + + Trying to make a high-precision Float from a float is not disallowed, + but one must keep in mind that the *underlying float* (not the apparent + decimal value) is being obtained with high precision. For example, 0.3 + does not have a finite binary representation. The closest rational is + the fraction 5404319552844595/2**54. So if you try to obtain a Float of + 0.3 to 20 digits of precision you will not see the same thing as 0.3 + followed by 19 zeros: + + >>> Float(0.3, 20) + 0.29999999999999998890 + + If you want a 20-digit value of the decimal 0.3 (not the floating point + approximation of 0.3) you should send the 0.3 as a string. The underlying + representation is still binary but a higher precision than Python's float + is used: + + >>> Float('0.3', 20) + 0.30000000000000000000 + + Although you can increase the precision of an existing Float using Float + it will not increase the accuracy -- the underlying value is not changed: + + >>> def show(f): # binary rep of Float + ... from sympy import Mul, Pow + ... s, m, e, b = f._mpf_ + ... v = Mul(int(m), Pow(2, int(e), evaluate=False), evaluate=False) + ... print('%s at prec=%s' % (v, f._prec)) + ... + >>> t = Float('0.3', 3) + >>> show(t) + 4915/2**14 at prec=13 + >>> show(Float(t, 20)) # higher prec, not higher accuracy + 4915/2**14 at prec=70 + >>> show(Float(t, 2)) # lower prec + 307/2**10 at prec=10 + + The same thing happens when evalf is used on a Float: + + >>> show(t.evalf(20)) + 4915/2**14 at prec=70 + >>> show(t.evalf(2)) + 307/2**10 at prec=10 + + Finally, Floats can be instantiated with an mpf tuple (n, c, p) to + produce the number (-1)**n*c*2**p: + + >>> n, c, p = 1, 5, 0 + >>> (-1)**n*c*2**p + -5 + >>> Float((1, 5, 0)) + -5.00000000000000 + + An actual mpf tuple also contains the number of bits in c as the last + element of the tuple: + + >>> _._mpf_ + (1, 5, 0, 3) + + This is not needed for instantiation and is not the same thing as the + precision. The mpf tuple and the precision are two separate quantities + that Float tracks. + + In SymPy, a Float is a number that can be computed with arbitrary + precision. Although floating point 'inf' and 'nan' are not such + numbers, Float can create these numbers: + + >>> Float('-inf') + -oo + >>> _.is_Float + False + + Zero in Float only has a single value. Values are not separate for + positive and negative zeroes. + """ + __slots__ = ('_mpf_', '_prec') + + _mpf_: tuple[int, int, int, int] + + # A Float, though rational in form, does not behave like + # a rational in all Python expressions so we deal with + # exceptions (where we want to deal with the rational + # form of the Float as a rational) at the source rather + # than assigning a mathematically loaded category of 'rational' + is_rational = None + is_irrational = None + is_number = True + + is_real = True + is_extended_real = True + + is_Float = True + + _remove_non_digits = str.maketrans(dict.fromkeys("-+_.")) + + def __new__(cls, num, dps=None, precision=None): + if dps is not None and precision is not None: + raise ValueError('Both decimal and binary precision supplied. ' + 'Supply only one. ') + + if isinstance(num, str): + _num = num = num.strip() # Python ignores leading and trailing space + num = num.replace(' ', '_').lower() # Float treats spaces as digit sep; E -> e + if num.startswith('.') and len(num) > 1: + num = '0' + num + elif num.startswith('-.') and len(num) > 2: + num = '-0.' + num[2:] + elif num in ('inf', '+inf'): + return S.Infinity + elif num == '-inf': + return S.NegativeInfinity + elif num == 'nan': + return S.NaN + elif not _literal_float(num): + raise ValueError('string-float not recognized: %s' % _num) + elif isinstance(num, float) and num == 0: + num = '0' + elif isinstance(num, float) and num == float('inf'): + return S.Infinity + elif isinstance(num, float) and num == float('-inf'): + return S.NegativeInfinity + elif isinstance(num, float) and math.isnan(num): + return S.NaN + elif isinstance(num, (SYMPY_INTS, Integer)): + num = str(num) + elif num is S.Infinity: + return num + elif num is S.NegativeInfinity: + return num + elif num is S.NaN: + return num + elif _is_numpy_instance(num): # support for numpy datatypes + num = _convert_numpy_types(num) + elif isinstance(num, mpmath.mpf): + if precision is None: + if dps is None: + precision = num.context.prec + num = num._mpf_ + + if dps is None and precision is None: + dps = 15 + if isinstance(num, Float): + return num + if isinstance(num, str): + try: + Num = decimal.Decimal(num) + except decimal.InvalidOperation: + pass + else: + isint = '.' not in num + num, dps = _decimal_to_Rational_prec(Num) + if num.is_Integer and isint: + # 12e3 is shorthand for int, not float; + # 12.e3 would be the float version + dps = max(dps, num_digits(num)) + dps = max(15, dps) + precision = dps_to_prec(dps) + elif precision == '' and dps is None or precision is None and dps == '': + if not isinstance(num, str): + raise ValueError('The null string can only be used when ' + 'the number to Float is passed as a string or an integer.') + try: + Num = decimal.Decimal(num) + except decimal.InvalidOperation: + raise ValueError('string-float not recognized by Decimal: %s' % num) + else: + isint = '.' not in num + num, dps = _decimal_to_Rational_prec(Num) + if num.is_Integer and isint: + # without dec, e-notation is short for int + dps = max(dps, num_digits(num)) + precision = dps_to_prec(dps) + + # decimal precision(dps) is set and maybe binary precision(precision) + # as well.From here on binary precision is used to compute the Float. + # Hence, if supplied use binary precision else translate from decimal + # precision. + + if precision is None or precision == '': + precision = dps_to_prec(dps) + + precision = int(precision) + + if isinstance(num, float): + _mpf_ = mlib.from_float(num, precision, rnd) + elif isinstance(num, str): + _mpf_ = mlib.from_str(num, precision, rnd) + elif isinstance(num, decimal.Decimal): + if num.is_finite(): + _mpf_ = mlib.from_str(str(num), precision, rnd) + elif num.is_nan(): + return S.NaN + elif num.is_infinite(): + if num > 0: + return S.Infinity + return S.NegativeInfinity + else: + raise ValueError("unexpected decimal value %s" % str(num)) + elif isinstance(num, tuple) and len(num) in (3, 4): + if isinstance(num[1], str): + # it's a hexadecimal (coming from a pickled object) + num = list(num) + # If we're loading an object pickled in Python 2 into + # Python 3, we may need to strip a tailing 'L' because + # of a shim for int on Python 3, see issue #13470. + # Strip leading '0x' - gmpy2 only documents such inputs + # with base prefix as valid when the 2nd argument (base) is 0. + # When mpmath uses Sage as the backend, however, it + # ends up including '0x' when preparing the picklable tuple. + # See issue #19690. + num[1] = num[1].removeprefix('0x').removesuffix('L') + # Now we can assume that it is in standard form + num[1] = MPZ(num[1], 16) + _mpf_ = tuple(num) + else: + if len(num) == 4: + # handle normalization hack + return Float._new(num, precision) + else: + if not all(( + num[0] in (0, 1), + num[1] >= 0, + all(type(i) in (int, int) for i in num) + )): + raise ValueError('malformed mpf: %s' % (num,)) + # don't compute number or else it may + # over/underflow + return Float._new( + (num[0], num[1], num[2], bitcount(num[1])), + precision) + elif isinstance(num, (Number, NumberSymbol)): + _mpf_ = num._as_mpf_val(precision) + else: + _mpf_ = mpmath.mpf(num, prec=precision)._mpf_ + + return cls._new(_mpf_, precision, zero=False) + + @classmethod + def _new(cls, _mpf_, _prec, zero=True): + # special cases + if zero and _mpf_ == fzero: + return S.Zero # Float(0) -> 0.0; Float._new((0,0,0,0)) -> 0 + elif _mpf_ == _mpf_nan: + return S.NaN + elif _mpf_ == _mpf_inf: + return S.Infinity + elif _mpf_ == _mpf_ninf: + return S.NegativeInfinity + + obj = Expr.__new__(cls) + obj._mpf_ = mpf_norm(_mpf_, _prec) + obj._prec = _prec + return obj + + def __getnewargs_ex__(self): + sign, man, exp, bc = self._mpf_ + arg = (sign, hex(man)[2:], exp, bc) + kwargs = {'precision': self._prec} + return ((arg,), kwargs) + + def _hashable_content(self): + return (self._mpf_, self._prec) + + def floor(self): + return Integer(int(mlib.to_int( + mlib.mpf_floor(self._mpf_, self._prec)))) + + def ceiling(self): + return Integer(int(mlib.to_int( + mlib.mpf_ceil(self._mpf_, self._prec)))) + + def __floor__(self): + return self.floor() + + def __ceil__(self): + return self.ceiling() + + @property + def num(self): + return mpmath.mpf(self._mpf_) + + def _as_mpf_val(self, prec): + rv = mpf_norm(self._mpf_, prec) + if rv != self._mpf_ and self._prec == prec: + debug(self._mpf_, rv) + return rv + + def _as_mpf_op(self, prec): + return self._mpf_, max(prec, self._prec) + + def _eval_is_finite(self): + if self._mpf_ in (_mpf_inf, _mpf_ninf): + return False + return True + + def _eval_is_infinite(self): + if self._mpf_ in (_mpf_inf, _mpf_ninf): + return True + return False + + def _eval_is_integer(self): + if self._mpf_ == fzero: + return True + if not int_valued(self): + return False + + def _eval_is_negative(self): + if self._mpf_ in (_mpf_ninf, _mpf_inf): + return False + return self.num < 0 + + def _eval_is_positive(self): + if self._mpf_ in (_mpf_ninf, _mpf_inf): + return False + return self.num > 0 + + def _eval_is_extended_negative(self): + if self._mpf_ == _mpf_ninf: + return True + if self._mpf_ == _mpf_inf: + return False + return self.num < 0 + + def _eval_is_extended_positive(self): + if self._mpf_ == _mpf_inf: + return True + if self._mpf_ == _mpf_ninf: + return False + return self.num > 0 + + def _eval_is_zero(self): + return self._mpf_ == fzero + + def __bool__(self): + return self._mpf_ != fzero + + def __neg__(self): + if not self: + return self + return Float._new(mlib.mpf_neg(self._mpf_), self._prec) + + @_sympifyit('other', NotImplemented) + def __add__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + rhs, prec = other._as_mpf_op(self._prec) + return Float._new(mlib.mpf_add(self._mpf_, rhs, prec, rnd), prec) + return Number.__add__(self, other) + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + rhs, prec = other._as_mpf_op(self._prec) + return Float._new(mlib.mpf_sub(self._mpf_, rhs, prec, rnd), prec) + return Number.__sub__(self, other) + + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + rhs, prec = other._as_mpf_op(self._prec) + return Float._new(mlib.mpf_mul(self._mpf_, rhs, prec, rnd), prec) + return Number.__mul__(self, other) + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + if isinstance(other, Number) and other != 0 and global_parameters.evaluate: + rhs, prec = other._as_mpf_op(self._prec) + return Float._new(mlib.mpf_div(self._mpf_, rhs, prec, rnd), prec) + return Number.__truediv__(self, other) + + @_sympifyit('other', NotImplemented) + def __mod__(self, other): + if isinstance(other, Rational) and other.q != 1 and global_parameters.evaluate: + # calculate mod with Rationals, *then* round the result + return Float(Rational.__mod__(Rational(self), other), + precision=self._prec) + if isinstance(other, Float) and global_parameters.evaluate: + r = self/other + if int_valued(r): + return Float(0, precision=max(self._prec, other._prec)) + if isinstance(other, Number) and global_parameters.evaluate: + rhs, prec = other._as_mpf_op(self._prec) + return Float._new(mlib.mpf_mod(self._mpf_, rhs, prec, rnd), prec) + return Number.__mod__(self, other) + + @_sympifyit('other', NotImplemented) + def __rmod__(self, other): + if isinstance(other, Float) and global_parameters.evaluate: + return other.__mod__(self) + if isinstance(other, Number) and global_parameters.evaluate: + rhs, prec = other._as_mpf_op(self._prec) + return Float._new(mlib.mpf_mod(rhs, self._mpf_, prec, rnd), prec) + return Number.__rmod__(self, other) + + def _eval_power(self, expt): + """ + expt is symbolic object but not equal to 0, 1 + + (-p)**r -> exp(r*log(-p)) -> exp(r*(log(p) + I*Pi)) -> + -> p**r*(sin(Pi*r) + cos(Pi*r)*I) + """ + if equal_valued(self, 0): + if expt.is_extended_positive: + return self + if expt.is_extended_negative: + return S.ComplexInfinity + if isinstance(expt, Number): + if isinstance(expt, Integer): + prec = self._prec + return Float._new( + mlib.mpf_pow_int(self._mpf_, expt.p, prec, rnd), prec) + elif isinstance(expt, Rational) and \ + expt.p == 1 and expt.q % 2 and self.is_negative: + return Pow(S.NegativeOne, expt, evaluate=False)*( + -self)._eval_power(expt) + expt, prec = expt._as_mpf_op(self._prec) + mpfself = self._mpf_ + try: + y = mpf_pow(mpfself, expt, prec, rnd) + return Float._new(y, prec) + except mlib.ComplexResult: + re, im = mlib.mpc_pow( + (mpfself, fzero), (expt, fzero), prec, rnd) + return Float._new(re, prec) + \ + Float._new(im, prec)*S.ImaginaryUnit + + def __abs__(self): + return Float._new(mlib.mpf_abs(self._mpf_), self._prec) + + def __int__(self): + if self._mpf_ == fzero: + return 0 + return int(mlib.to_int(self._mpf_)) # uses round_fast = round_down + + def __eq__(self, other): + if isinstance(other, float): + other = Float(other) + return Basic.__eq__(self, other) + + def __ne__(self, other): + eq = self.__eq__(other) + if eq is NotImplemented: + return eq + else: + return not eq + + def __hash__(self): + float_val = float(self) + if not math.isinf(float_val): + return hash(float_val) + return Basic.__hash__(self) + + def _Frel(self, other, op): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if other.is_Rational: + # test self*other.q other.p without losing precision + ''' + >>> f = Float(.1,2) + >>> i = 1234567890 + >>> (f*i)._mpf_ + (0, 471, 18, 9) + >>> mlib.mpf_mul(f._mpf_, mlib.from_int(i)) + (0, 505555550955, -12, 39) + ''' + smpf = mlib.mpf_mul(self._mpf_, mlib.from_int(other.q)) + ompf = mlib.from_int(other.p) + return _sympify(bool(op(smpf, ompf))) + elif other.is_Float: + return _sympify(bool( + op(self._mpf_, other._mpf_))) + elif other.is_comparable and other not in ( + S.Infinity, S.NegativeInfinity): + other = other.evalf(prec_to_dps(self._prec)) + if other._prec > 1: + if other.is_Number: + return _sympify(bool( + op(self._mpf_, other._as_mpf_val(self._prec)))) + + def __gt__(self, other): + if isinstance(other, NumberSymbol): + return other.__lt__(self) + rv = self._Frel(other, mlib.mpf_gt) + if rv is None: + return Expr.__gt__(self, other) + return rv + + def __ge__(self, other): + if isinstance(other, NumberSymbol): + return other.__le__(self) + rv = self._Frel(other, mlib.mpf_ge) + if rv is None: + return Expr.__ge__(self, other) + return rv + + def __lt__(self, other): + if isinstance(other, NumberSymbol): + return other.__gt__(self) + rv = self._Frel(other, mlib.mpf_lt) + if rv is None: + return Expr.__lt__(self, other) + return rv + + def __le__(self, other): + if isinstance(other, NumberSymbol): + return other.__ge__(self) + rv = self._Frel(other, mlib.mpf_le) + if rv is None: + return Expr.__le__(self, other) + return rv + + def epsilon_eq(self, other, epsilon="1e-15"): + return abs(self - other) < Float(epsilon) + + def __format__(self, format_spec): + return format(decimal.Decimal(str(self)), format_spec) + + +# Add sympify converters +_sympy_converter[float] = _sympy_converter[decimal.Decimal] = Float + +# this is here to work nicely in Sage +RealNumber = Float + + +class Rational(Number): + """Represents rational numbers (p/q) of any size. + + Examples + ======== + + >>> from sympy import Rational, nsimplify, S, pi + >>> Rational(1, 2) + 1/2 + + Rational is unprejudiced in accepting input. If a float is passed, the + underlying value of the binary representation will be returned: + + >>> Rational(.5) + 1/2 + >>> Rational(.2) + 3602879701896397/18014398509481984 + + If the simpler representation of the float is desired then consider + limiting the denominator to the desired value or convert the float to + a string (which is roughly equivalent to limiting the denominator to + 10**12): + + >>> Rational(str(.2)) + 1/5 + >>> Rational(.2).limit_denominator(10**12) + 1/5 + + An arbitrarily precise Rational is obtained when a string literal is + passed: + + >>> Rational("1.23") + 123/100 + >>> Rational('1e-2') + 1/100 + >>> Rational(".1") + 1/10 + >>> Rational('1e-2/3.2') + 1/320 + + The conversion of other types of strings can be handled by + the sympify() function, and conversion of floats to expressions + or simple fractions can be handled with nsimplify: + + >>> S('.[3]') # repeating digits in brackets + 1/3 + >>> S('3**2/10') # general expressions + 9/10 + >>> nsimplify(.3) # numbers that have a simple form + 3/10 + + But if the input does not reduce to a literal Rational, an error will + be raised: + + >>> Rational(pi) + Traceback (most recent call last): + ... + TypeError: invalid input: pi + + + Low-level + --------- + + Access numerator and denominator as .p and .q: + + >>> r = Rational(3, 4) + >>> r + 3/4 + >>> r.p + 3 + >>> r.q + 4 + + Note that p and q return integers (not SymPy Integers) so some care + is needed when using them in expressions: + + >>> r.p/r.q + 0.75 + + See Also + ======== + sympy.core.sympify.sympify, sympy.simplify.simplify.nsimplify + """ + is_real = True + is_integer = False + is_rational = True + is_number = True + + __slots__ = ('p', 'q') + + p: int + q: int + + is_Rational = True + + @cacheit + def __new__(cls, p, q=None, gcd=None): + if q is None: + if isinstance(p, Rational): + return p + + if isinstance(p, SYMPY_INTS): + pass + else: + if isinstance(p, (float, Float)): + return Rational(*_as_integer_ratio(p)) + + if not isinstance(p, str): + try: + p = sympify(p) + except (SympifyError, SyntaxError): + pass # error will raise below + else: + if p.count('/') > 1: + raise TypeError('invalid input: %s' % p) + p = p.replace(' ', '') + pq = p.rsplit('/', 1) + if len(pq) == 2: + p, q = pq + fp = fractions.Fraction(p) + fq = fractions.Fraction(q) + p = fp/fq + try: + p = fractions.Fraction(p) + except ValueError: + pass # error will raise below + else: + return cls._new(p.numerator, p.denominator, 1) + + if not isinstance(p, Rational): + raise TypeError('invalid input: %s' % p) + + q = 1 + + Q = 1 + + if not isinstance(p, SYMPY_INTS): + p = Rational(p) + Q *= p.q + p = p.p + else: + p = int(p) + + if not isinstance(q, SYMPY_INTS): + q = Rational(q) + p *= q.q + Q *= q.p + else: + Q *= int(q) + q = Q + + if gcd is not None: + sympy_deprecation_warning( + "gcd is deprecated in Rational, use nsimplify instead", + deprecated_since_version="1.11", + active_deprecations_target="deprecated-rational-gcd", + stacklevel=4, + ) + return cls._new(p, q, gcd) + + # p and q are now ints + return cls._new(p, q) + + @classmethod + def _new(cls, p, q, gcd=None): + if q == 0: + if p == 0: + if _errdict["divide"]: + raise ValueError("Indeterminate 0/0") + else: + return S.NaN + return S.ComplexInfinity + + if q < 0: + q = -q + p = -p + + if gcd is None: + gcd = igcd(abs(p), q) + + if gcd > 1: + p //= gcd + q //= gcd + + return cls.from_coprime_ints(p, q) + + @classmethod + def from_coprime_ints(cls, p: int, q: int) -> Rational: + """Create a Rational from a pair of coprime integers. + + Both ``p`` and ``q`` should be strictly of type ``int``. + + The caller should ensure that ``gcd(p,q) == 1`` and ``q > 0``. + + This may be more efficient than ``Rational(p, q)``. The validity of the + arguments may or may not be checked so it should not be relied upon to + pass unvalidated or invalid arguments to this function. + """ + if q == 1: + return Integer(p) + if p == 1 and q == 2: + return S.Half + + obj = Expr.__new__(cls) + obj.p = p + obj.q = q + return obj + + def limit_denominator(self, max_denominator=1000000): + """Closest Rational to self with denominator at most max_denominator. + + Examples + ======== + + >>> from sympy import Rational + >>> Rational('3.141592653589793').limit_denominator(10) + 22/7 + >>> Rational('3.141592653589793').limit_denominator(100) + 311/99 + + """ + f = fractions.Fraction(self.p, self.q) + return Rational(f.limit_denominator(fractions.Fraction(int(max_denominator)))) + + def __getnewargs__(self): + return (self.p, self.q) + + def _hashable_content(self): + return (self.p, self.q) + + def _eval_is_positive(self): + return self.p > 0 + + def _eval_is_zero(self): + return self.p == 0 + + def __neg__(self): + return Rational(-self.p, self.q) + + @_sympifyit('other', NotImplemented) + def __add__(self, other): + if global_parameters.evaluate: + if isinstance(other, Integer): + return Rational._new(self.p + self.q*other.p, self.q, 1) + elif isinstance(other, Rational): + #TODO: this can probably be optimized more + return Rational(self.p*other.q + self.q*other.p, self.q*other.q) + elif isinstance(other, Float): + return other + self + else: + return Number.__add__(self, other) + return Number.__add__(self, other) + __radd__ = __add__ + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + if global_parameters.evaluate: + if isinstance(other, Integer): + return Rational._new(self.p - self.q*other.p, self.q, 1) + elif isinstance(other, Rational): + return Rational(self.p*other.q - self.q*other.p, self.q*other.q) + elif isinstance(other, Float): + return -other + self + else: + return Number.__sub__(self, other) + return Number.__sub__(self, other) + @_sympifyit('other', NotImplemented) + def __rsub__(self, other): + if global_parameters.evaluate: + if isinstance(other, Integer): + return Rational._new(self.q*other.p - self.p, self.q, 1) + elif isinstance(other, Rational): + return Rational(self.q*other.p - self.p*other.q, self.q*other.q) + elif isinstance(other, Float): + return -self + other + else: + return Number.__rsub__(self, other) + return Number.__rsub__(self, other) + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + if global_parameters.evaluate: + if isinstance(other, Integer): + return Rational._new(self.p*other.p, self.q, igcd(other.p, self.q)) + elif isinstance(other, Rational): + return Rational._new(self.p*other.p, self.q*other.q, igcd(self.p, other.q)*igcd(self.q, other.p)) + elif isinstance(other, Float): + return other*self + else: + return Number.__mul__(self, other) + return Number.__mul__(self, other) + __rmul__ = __mul__ + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + if global_parameters.evaluate: + if isinstance(other, Integer): + if self.p and other.p == S.Zero: + return S.ComplexInfinity + else: + return Rational._new(self.p, self.q*other.p, igcd(self.p, other.p)) + elif isinstance(other, Rational): + return Rational._new(self.p*other.q, self.q*other.p, igcd(self.p, other.p)*igcd(self.q, other.q)) + elif isinstance(other, Float): + return self*(1/other) + else: + return Number.__truediv__(self, other) + return Number.__truediv__(self, other) + @_sympifyit('other', NotImplemented) + def __rtruediv__(self, other): + if global_parameters.evaluate: + if isinstance(other, Integer): + return Rational._new(other.p*self.q, self.p, igcd(self.p, other.p)) + elif isinstance(other, Rational): + return Rational._new(other.p*self.q, other.q*self.p, igcd(self.p, other.p)*igcd(self.q, other.q)) + elif isinstance(other, Float): + return other*(1/self) + else: + return Number.__rtruediv__(self, other) + return Number.__rtruediv__(self, other) + + @_sympifyit('other', NotImplemented) + def __mod__(self, other): + if global_parameters.evaluate: + if isinstance(other, Rational): + n = (self.p*other.q) // (other.p*self.q) + return Rational(self.p*other.q - n*other.p*self.q, self.q*other.q) + if isinstance(other, Float): + # calculate mod with Rationals, *then* round the answer + return Float(self.__mod__(Rational(other)), + precision=other._prec) + return Number.__mod__(self, other) + return Number.__mod__(self, other) + + @_sympifyit('other', NotImplemented) + def __rmod__(self, other): + if isinstance(other, Rational): + return Rational.__mod__(other, self) + return Number.__rmod__(self, other) + + def _eval_power(self, expt): + if isinstance(expt, Number): + if isinstance(expt, Float): + return self._eval_evalf(expt._prec)**expt + if expt.is_extended_negative: + # (3/4)**-2 -> (4/3)**2 + ne = -expt + if (ne is S.One): + return Rational(self.q, self.p) + if self.is_negative: + return S.NegativeOne**expt*Rational(self.q, -self.p)**ne + else: + return Rational(self.q, self.p)**ne + if expt is S.Infinity: # -oo already caught by test for negative + if self.p > self.q: + # (3/2)**oo -> oo + return S.Infinity + if self.p < -self.q: + # (-3/2)**oo -> oo + I*oo + return S.Infinity + S.Infinity*S.ImaginaryUnit + return S.Zero + if isinstance(expt, Integer): + # (4/3)**2 -> 4**2 / 3**2 + return Rational._new(self.p**expt.p, self.q**expt.p, 1) + if isinstance(expt, Rational): + intpart = expt.p // expt.q + if intpart: + intpart += 1 + remfracpart = intpart*expt.q - expt.p + ratfracpart = Rational(remfracpart, expt.q) + if self.p != 1: + return Integer(self.p)**expt*Integer(self.q)**ratfracpart*Rational._new(1, self.q**intpart, 1) + return Integer(self.q)**ratfracpart*Rational._new(1, self.q**intpart, 1) + else: + remfracpart = expt.q - expt.p + ratfracpart = Rational(remfracpart, expt.q) + if self.p != 1: + return Integer(self.p)**expt*Integer(self.q)**ratfracpart*Rational._new(1, self.q, 1) + return Integer(self.q)**ratfracpart*Rational._new(1, self.q, 1) + + if self.is_extended_negative and expt.is_even: + return (-self)**expt + + return + + def _as_mpf_val(self, prec): + return mlib.from_rational(self.p, self.q, prec, rnd) + + def _mpmath_(self, prec, rnd): + return mpmath.make_mpf(mlib.from_rational(self.p, self.q, prec, rnd)) + + def __abs__(self): + return Rational(abs(self.p), self.q) + + def __int__(self): + p, q = self.p, self.q + if p < 0: + return -int(-p//q) + return int(p//q) + + def floor(self): + return Integer(self.p // self.q) + + def ceiling(self): + return -Integer(-self.p // self.q) + + def __floor__(self): + return self.floor() + + def __ceil__(self): + return self.ceiling() + + def __eq__(self, other): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if not isinstance(other, Number): + # S(0) == S.false is False + # S(0) == False is True + return False + if other.is_NumberSymbol: + if other.is_irrational: + return False + return other.__eq__(self) + if other.is_Rational: + # a Rational is always in reduced form so will never be 2/4 + # so we can just check equivalence of args + return self.p == other.p and self.q == other.q + return False + + def __ne__(self, other): + return not self == other + + def _Rrel(self, other, attr): + # if you want self < other, pass self, other, __gt__ + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if other.is_Number: + op = None + s, o = self, other + if other.is_NumberSymbol: + op = getattr(o, attr) + elif other.is_Float: + op = getattr(o, attr) + elif other.is_Rational: + s, o = Integer(s.p*o.q), Integer(s.q*o.p) + op = getattr(o, attr) + if op: + return op(s) + if o.is_number and o.is_extended_real: + return Integer(s.p), s.q*o + + def __gt__(self, other): + rv = self._Rrel(other, '__lt__') + if rv is None: + rv = self, other + elif not isinstance(rv, tuple): + return rv + return Expr.__gt__(*rv) + + def __ge__(self, other): + rv = self._Rrel(other, '__le__') + if rv is None: + rv = self, other + elif not isinstance(rv, tuple): + return rv + return Expr.__ge__(*rv) + + def __lt__(self, other): + rv = self._Rrel(other, '__gt__') + if rv is None: + rv = self, other + elif not isinstance(rv, tuple): + return rv + return Expr.__lt__(*rv) + + def __le__(self, other): + rv = self._Rrel(other, '__ge__') + if rv is None: + rv = self, other + elif not isinstance(rv, tuple): + return rv + return Expr.__le__(*rv) + + def __hash__(self): + return super().__hash__() + + def factors(self, limit=None, use_trial=True, use_rho=False, + use_pm1=False, verbose=False, visual=False): + """A wrapper to factorint which return factors of self that are + smaller than limit (or cheap to compute). Special methods of + factoring are disabled by default so that only trial division is used. + """ + from sympy.ntheory.factor_ import factorrat + + return factorrat(self, limit=limit, use_trial=use_trial, + use_rho=use_rho, use_pm1=use_pm1, + verbose=verbose).copy() + + @property + def numerator(self): + return self.p + + @property + def denominator(self): + return self.q + + @_sympifyit('other', NotImplemented) + def gcd(self, other): + if isinstance(other, Rational): + if other == S.Zero: + return other + return Rational( + igcd(self.p, other.p), + ilcm(self.q, other.q)) + return Number.gcd(self, other) + + @_sympifyit('other', NotImplemented) + def lcm(self, other): + if isinstance(other, Rational): + return Rational( + self.p // igcd(self.p, other.p) * other.p, + igcd(self.q, other.q)) + return Number.lcm(self, other) + + def as_numer_denom(self): + return Integer(self.p), Integer(self.q) + + def as_content_primitive(self, radical=False, clear=True): + """Return the tuple (R, self/R) where R is the positive Rational + extracted from self. + + Examples + ======== + + >>> from sympy import S + >>> (S(-3)/2).as_content_primitive() + (3/2, -1) + + See docstring of Expr.as_content_primitive for more examples. + """ + + if self: + if self.is_positive: + return self, S.One + return -self, S.NegativeOne + return S.One, self + + def as_coeff_Mul(self, rational=False): + """Efficiently extract the coefficient of a product.""" + return self, S.One + + def as_coeff_Add(self, rational=False): + """Efficiently extract the coefficient of a summation.""" + return self, S.Zero + + +class Integer(Rational): + """Represents integer numbers of any size. + + Examples + ======== + + >>> from sympy import Integer + >>> Integer(3) + 3 + + If a float or a rational is passed to Integer, the fractional part + will be discarded; the effect is of rounding toward zero. + + >>> Integer(3.8) + 3 + >>> Integer(-3.8) + -3 + + A string is acceptable input if it can be parsed as an integer: + + >>> Integer("9" * 20) + 99999999999999999999 + + It is rarely needed to explicitly instantiate an Integer, because + Python integers are automatically converted to Integer when they + are used in SymPy expressions. + """ + q = 1 + is_integer = True + is_number = True + + is_Integer = True + + __slots__ = () + + def _as_mpf_val(self, prec): + return mlib.from_int(self.p, prec, rnd) + + def _mpmath_(self, prec, rnd): + return mpmath.make_mpf(self._as_mpf_val(prec)) + + @cacheit + def __new__(cls, i): + if isinstance(i, str): + i = i.replace(' ', '') + # whereas we cannot, in general, make a Rational from an + # arbitrary expression, we can make an Integer unambiguously + # (except when a non-integer expression happens to round to + # an integer). So we proceed by taking int() of the input and + # let the int routines determine whether the expression can + # be made into an int or whether an error should be raised. + try: + ival = int(i) + except TypeError: + raise TypeError( + "Argument of Integer should be of numeric type, got %s." % i) + # We only work with well-behaved integer types. This converts, for + # example, numpy.int32 instances. + if ival == 1: + return S.One + if ival == -1: + return S.NegativeOne + if ival == 0: + return S.Zero + obj = Expr.__new__(cls) + obj.p = ival + return obj + + def __getnewargs__(self): + return (self.p,) + + # Arithmetic operations are here for efficiency + def __int__(self): + return self.p + + def floor(self): + return Integer(self.p) + + def ceiling(self): + return Integer(self.p) + + def __floor__(self): + return self.floor() + + def __ceil__(self): + return self.ceiling() + + def __neg__(self): + return Integer(-self.p) + + def __abs__(self): + if self.p >= 0: + return self + else: + return Integer(-self.p) + + def __divmod__(self, other): + if isinstance(other, Integer) and global_parameters.evaluate: + return Tuple(*(divmod(self.p, other.p))) + else: + return Number.__divmod__(self, other) + + def __rdivmod__(self, other): + if isinstance(other, int) and global_parameters.evaluate: + return Tuple(*(divmod(other, self.p))) + else: + try: + other = Number(other) + except TypeError: + msg = "unsupported operand type(s) for divmod(): '%s' and '%s'" + oname = type(other).__name__ + sname = type(self).__name__ + raise TypeError(msg % (oname, sname)) + return Number.__divmod__(other, self) + + # TODO make it decorator + bytecodehacks? + def __add__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(self.p + other) + elif isinstance(other, Integer): + return Integer(self.p + other.p) + elif isinstance(other, Rational): + return Rational._new(self.p*other.q + other.p, other.q, 1) + return Rational.__add__(self, other) + else: + return Add(self, other) + + def __radd__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(other + self.p) + elif isinstance(other, Rational): + return Rational._new(other.p + self.p*other.q, other.q, 1) + return Rational.__radd__(self, other) + return Rational.__radd__(self, other) + + def __sub__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(self.p - other) + elif isinstance(other, Integer): + return Integer(self.p - other.p) + elif isinstance(other, Rational): + return Rational._new(self.p*other.q - other.p, other.q, 1) + return Rational.__sub__(self, other) + return Rational.__sub__(self, other) + + def __rsub__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(other - self.p) + elif isinstance(other, Rational): + return Rational._new(other.p - self.p*other.q, other.q, 1) + return Rational.__rsub__(self, other) + return Rational.__rsub__(self, other) + + def __mul__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(self.p*other) + elif isinstance(other, Integer): + return Integer(self.p*other.p) + elif isinstance(other, Rational): + return Rational._new(self.p*other.p, other.q, igcd(self.p, other.q)) + return Rational.__mul__(self, other) + return Rational.__mul__(self, other) + + def __rmul__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(other*self.p) + elif isinstance(other, Rational): + return Rational._new(other.p*self.p, other.q, igcd(self.p, other.q)) + return Rational.__rmul__(self, other) + return Rational.__rmul__(self, other) + + def __mod__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(self.p % other) + elif isinstance(other, Integer): + return Integer(self.p % other.p) + return Rational.__mod__(self, other) + return Rational.__mod__(self, other) + + def __rmod__(self, other): + if global_parameters.evaluate: + if isinstance(other, int): + return Integer(other % self.p) + elif isinstance(other, Integer): + return Integer(other.p % self.p) + return Rational.__rmod__(self, other) + return Rational.__rmod__(self, other) + + def __eq__(self, other): + if isinstance(other, int): + return (self.p == other) + elif isinstance(other, Integer): + return (self.p == other.p) + return Rational.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if other.is_Integer: + return _sympify(self.p > other.p) + return Rational.__gt__(self, other) + + def __lt__(self, other): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if other.is_Integer: + return _sympify(self.p < other.p) + return Rational.__lt__(self, other) + + def __ge__(self, other): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if other.is_Integer: + return _sympify(self.p >= other.p) + return Rational.__ge__(self, other) + + def __le__(self, other): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if other.is_Integer: + return _sympify(self.p <= other.p) + return Rational.__le__(self, other) + + def __hash__(self): + return hash(self.p) + + def __index__(self): + return self.p + + ######################################## + + def _eval_is_odd(self): + return bool(self.p % 2) + + def _eval_power(self, expt): + """ + Tries to do some simplifications on self**expt + + Returns None if no further simplifications can be done. + + Explanation + =========== + + When exponent is a fraction (so we have for example a square root), + we try to find a simpler representation by factoring the argument + up to factors of 2**15, e.g. + + - sqrt(4) becomes 2 + - sqrt(-4) becomes 2*I + - (2**(3+7)*3**(6+7))**Rational(1,7) becomes 6*18**(3/7) + + Further simplification would require a special call to factorint on + the argument which is not done here for sake of speed. + + """ + from sympy.ntheory.factor_ import perfect_power + + if expt is S.Infinity: + if self.p > S.One: + return S.Infinity + # cases -1, 0, 1 are done in their respective classes + return S.Infinity + S.ImaginaryUnit*S.Infinity + if expt is S.NegativeInfinity: + return Rational._new(1, self, 1)**S.Infinity + if not isinstance(expt, Number): + # simplify when expt is even + # (-2)**k --> 2**k + if self.is_negative and expt.is_even: + return (-self)**expt + if isinstance(expt, Float): + # Rational knows how to exponentiate by a Float + return super()._eval_power(expt) + if not isinstance(expt, Rational): + return + if expt is S.Half and self.is_negative: + # we extract I for this special case since everyone is doing so + return S.ImaginaryUnit*Pow(-self, expt) + if expt.is_negative: + # invert base and change sign on exponent + ne = -expt + if self.is_negative: + return S.NegativeOne**expt*Rational._new(1, -self.p, 1)**ne + else: + return Rational._new(1, self.p, 1)**ne + # see if base is a perfect root, sqrt(4) --> 2 + x, xexact = integer_nthroot(abs(self.p), expt.q) + if xexact: + # if it's a perfect root we've finished + result = Integer(x**abs(expt.p)) + if self.is_negative: + result *= S.NegativeOne**expt + return result + + # The following is an algorithm where we collect perfect roots + # from the factors of base. + + # if it's not an nth root, it still might be a perfect power + b_pos = int(abs(self.p)) + p = perfect_power(b_pos) + if p is not False: + # XXX: Convert to int because perfect_power may return fmpz + # Ideally that should be fixed in perfect_power though... + dict = {int(p[0]): int(p[1])} + else: + dict = Integer(b_pos).factors(limit=2**15) + + # now process the dict of factors + out_int = 1 # integer part + out_rad = 1 # extracted radicals + sqr_int = 1 + sqr_gcd = 0 + sqr_dict = {} + for prime, exponent in dict.items(): + exponent *= expt.p + # remove multiples of expt.q: (2**12)**(1/10) -> 2*(2**2)**(1/10) + div_e, div_m = divmod(exponent, expt.q) + if div_e > 0: + out_int *= prime**div_e + if div_m > 0: + # see if the reduced exponent shares a gcd with e.q + # (2**2)**(1/10) -> 2**(1/5) + g = igcd(div_m, expt.q) + if g != 1: + out_rad *= Pow(prime, Rational._new(div_m//g, expt.q//g, 1)) + else: + sqr_dict[prime] = div_m + # identify gcd of remaining powers + for p, ex in sqr_dict.items(): + if sqr_gcd == 0: + sqr_gcd = ex + else: + sqr_gcd = igcd(sqr_gcd, ex) + if sqr_gcd == 1: + break + for k, v in sqr_dict.items(): + sqr_int *= k**(v//sqr_gcd) + if sqr_int == b_pos and out_int == 1 and out_rad == 1: + result = None + else: + result = out_int*out_rad*Pow(sqr_int, Rational(sqr_gcd, expt.q)) + if self.is_negative: + result *= Pow(S.NegativeOne, expt) + return result + + def _eval_is_prime(self): + from sympy.ntheory.primetest import isprime + + return isprime(self) + + def _eval_is_composite(self): + if self > 1: + return fuzzy_not(self.is_prime) + else: + return False + + def as_numer_denom(self): + return self, S.One + + @_sympifyit('other', NotImplemented) + def __floordiv__(self, other): + if not isinstance(other, Expr): + return NotImplemented + if isinstance(other, Integer): + return Integer(self.p // other) + return divmod(self, other)[0] + + def __rfloordiv__(self, other): + return Integer(Integer(other).p // self.p) + + # These bitwise operations (__lshift__, __rlshift__, ..., __invert__) are defined + # for Integer only and not for general SymPy expressions. This is to achieve + # compatibility with the numbers.Integral ABC which only defines these operations + # among instances of numbers.Integral. Therefore, these methods check explicitly for + # integer types rather than using sympify because they should not accept arbitrary + # symbolic expressions and there is no symbolic analogue of numbers.Integral's + # bitwise operations. + def __lshift__(self, other): + if isinstance(other, (int, Integer, numbers.Integral)): + return Integer(self.p << int(other)) + else: + return NotImplemented + + def __rlshift__(self, other): + if isinstance(other, (int, numbers.Integral)): + return Integer(int(other) << self.p) + else: + return NotImplemented + + def __rshift__(self, other): + if isinstance(other, (int, Integer, numbers.Integral)): + return Integer(self.p >> int(other)) + else: + return NotImplemented + + def __rrshift__(self, other): + if isinstance(other, (int, numbers.Integral)): + return Integer(int(other) >> self.p) + else: + return NotImplemented + + def __and__(self, other): + if isinstance(other, (int, Integer, numbers.Integral)): + return Integer(self.p & int(other)) + else: + return NotImplemented + + def __rand__(self, other): + if isinstance(other, (int, numbers.Integral)): + return Integer(int(other) & self.p) + else: + return NotImplemented + + def __xor__(self, other): + if isinstance(other, (int, Integer, numbers.Integral)): + return Integer(self.p ^ int(other)) + else: + return NotImplemented + + def __rxor__(self, other): + if isinstance(other, (int, numbers.Integral)): + return Integer(int(other) ^ self.p) + else: + return NotImplemented + + def __or__(self, other): + if isinstance(other, (int, Integer, numbers.Integral)): + return Integer(self.p | int(other)) + else: + return NotImplemented + + def __ror__(self, other): + if isinstance(other, (int, numbers.Integral)): + return Integer(int(other) | self.p) + else: + return NotImplemented + + def __invert__(self): + return Integer(~self.p) + +# Add sympify converters +_sympy_converter[int] = Integer + + +class AlgebraicNumber(Expr): + r""" + Class for representing algebraic numbers in SymPy. + + Symbolically, an instance of this class represents an element + $\alpha \in \mathbb{Q}(\theta) \hookrightarrow \mathbb{C}$. That is, the + algebraic number $\alpha$ is represented as an element of a particular + number field $\mathbb{Q}(\theta)$, with a particular embedding of this + field into the complex numbers. + + Formally, the primitive element $\theta$ is given by two data points: (1) + its minimal polynomial (which defines $\mathbb{Q}(\theta)$), and (2) a + particular complex number that is a root of this polynomial (which defines + the embedding $\mathbb{Q}(\theta) \hookrightarrow \mathbb{C}$). Finally, + the algebraic number $\alpha$ which we represent is then given by the + coefficients of a polynomial in $\theta$. + """ + + __slots__ = ('rep', 'root', 'alias', 'minpoly', '_own_minpoly') + + is_AlgebraicNumber = True + is_algebraic = True + is_number = True + + + kind = NumberKind + + # Optional alias symbol is not free. + # Actually, alias should be a Str, but some methods + # expect that it be an instance of Expr. + free_symbols: set[Basic] = set() + + def __new__(cls, expr, coeffs=None, alias=None, **args): + r""" + Construct a new algebraic number $\alpha$ belonging to a number field + $k = \mathbb{Q}(\theta)$. + + There are four instance attributes to be determined: + + =========== ============================================================================ + Attribute Type/Meaning + =========== ============================================================================ + ``root`` :py:class:`~.Expr` for $\theta$ as a complex number + ``minpoly`` :py:class:`~.Poly`, the minimal polynomial of $\theta$ + ``rep`` :py:class:`~sympy.polys.polyclasses.DMP` giving $\alpha$ as poly in $\theta$ + ``alias`` :py:class:`~.Symbol` for $\theta$, or ``None`` + =========== ============================================================================ + + See Parameters section for how they are determined. + + Parameters + ========== + + expr : :py:class:`~.Expr`, or pair $(m, r)$ + There are three distinct modes of construction, depending on what + is passed as *expr*. + + **(1)** *expr* is an :py:class:`~.AlgebraicNumber`: + In this case we begin by copying all four instance attributes from + *expr*. If *coeffs* were also given, we compose the two coeff + polynomials (see below). If an *alias* was given, it overrides. + + **(2)** *expr* is any other type of :py:class:`~.Expr`: + Then ``root`` will equal *expr*. Therefore it + must express an algebraic quantity, and we will compute its + ``minpoly``. + + **(3)** *expr* is an ordered pair $(m, r)$ giving the + ``minpoly`` $m$, and a ``root`` $r$ thereof, which together + define $\theta$. In this case $m$ may be either a univariate + :py:class:`~.Poly` or any :py:class:`~.Expr` which represents the + same, while $r$ must be some :py:class:`~.Expr` representing a + complex number that is a root of $m$, including both explicit + expressions in radicals, and instances of + :py:class:`~.ComplexRootOf` or :py:class:`~.AlgebraicNumber`. + + coeffs : list, :py:class:`~.ANP`, None, optional (default=None) + This defines ``rep``, giving the algebraic number $\alpha$ as a + polynomial in $\theta$. + + If a list, the elements should be integers or rational numbers. + If an :py:class:`~.ANP`, we take its coefficients (using its + :py:meth:`~.ANP.to_list()` method). If ``None``, then the list of + coefficients defaults to ``[1, 0]``, meaning that $\alpha = \theta$ + is the primitive element of the field. + + If *expr* was an :py:class:`~.AlgebraicNumber`, let $g(x)$ be its + ``rep`` polynomial, and let $f(x)$ be the polynomial defined by + *coeffs*. Then ``self.rep`` will represent the composition + $(f \circ g)(x)$. + + alias : str, :py:class:`~.Symbol`, None, optional (default=None) + This is a way to provide a name for the primitive element. We + described several ways in which the *expr* argument can define the + value of the primitive element, but none of these methods gave it + a name. Here, for example, *alias* could be set as + ``Symbol('theta')``, in order to make this symbol appear when + $\alpha$ is printed, or rendered as a polynomial, using the + :py:meth:`~.as_poly()` method. + + Examples + ======== + + Recall that we are constructing an algebraic number as a field element + $\alpha \in \mathbb{Q}(\theta)$. + + >>> from sympy import AlgebraicNumber, sqrt, CRootOf, S + >>> from sympy.abc import x + + Example (1): $\alpha = \theta = \sqrt{2}$ + + >>> a1 = AlgebraicNumber(sqrt(2)) + >>> a1.minpoly_of_element().as_expr(x) + x**2 - 2 + >>> a1.evalf(10) + 1.414213562 + + Example (2): $\alpha = 3 \sqrt{2} - 5$, $\theta = \sqrt{2}$. We can + either build on the last example: + + >>> a2 = AlgebraicNumber(a1, [3, -5]) + >>> a2.as_expr() + -5 + 3*sqrt(2) + + or start from scratch: + + >>> a2 = AlgebraicNumber(sqrt(2), [3, -5]) + >>> a2.as_expr() + -5 + 3*sqrt(2) + + Example (3): $\alpha = 6 \sqrt{2} - 11$, $\theta = \sqrt{2}$. Again we + can build on the previous example, and we see that the coeff polys are + composed: + + >>> a3 = AlgebraicNumber(a2, [2, -1]) + >>> a3.as_expr() + -11 + 6*sqrt(2) + + reflecting the fact that $(2x - 1) \circ (3x - 5) = 6x - 11$. + + Example (4): $\alpha = \sqrt{2}$, $\theta = \sqrt{2} + \sqrt{3}$. The + easiest way is to use the :py:func:`~.to_number_field()` function: + + >>> from sympy import to_number_field + >>> a4 = to_number_field(sqrt(2), sqrt(2) + sqrt(3)) + >>> a4.minpoly_of_element().as_expr(x) + x**2 - 2 + >>> a4.to_root() + sqrt(2) + >>> a4.primitive_element() + sqrt(2) + sqrt(3) + >>> a4.coeffs() + [1/2, 0, -9/2, 0] + + but if you already knew the right coefficients, you could construct it + directly: + + >>> a4 = AlgebraicNumber(sqrt(2) + sqrt(3), [S(1)/2, 0, S(-9)/2, 0]) + >>> a4.to_root() + sqrt(2) + >>> a4.primitive_element() + sqrt(2) + sqrt(3) + + Example (5): Construct the Golden Ratio as an element of the 5th + cyclotomic field, supposing we already know its coefficients. This time + we introduce the alias $\zeta$ for the primitive element of the field: + + >>> from sympy import cyclotomic_poly + >>> from sympy.abc import zeta + >>> a5 = AlgebraicNumber(CRootOf(cyclotomic_poly(5), -1), + ... [-1, -1, 0, 0], alias=zeta) + >>> a5.as_poly().as_expr() + -zeta**3 - zeta**2 + >>> a5.evalf() + 1.61803398874989 + + (The index ``-1`` to ``CRootOf`` selects the complex root with the + largest real and imaginary parts, which in this case is + $\mathrm{e}^{2i\pi/5}$. See :py:class:`~.ComplexRootOf`.) + + Example (6): Building on the last example, construct the number + $2 \phi \in \mathbb{Q}(\phi)$, where $\phi$ is the Golden Ratio: + + >>> from sympy.abc import phi + >>> a6 = AlgebraicNumber(a5.to_root(), coeffs=[2, 0], alias=phi) + >>> a6.as_poly().as_expr() + 2*phi + >>> a6.primitive_element().evalf() + 1.61803398874989 + + Note that we needed to use ``a5.to_root()``, since passing ``a5`` as + the first argument would have constructed the number $2 \phi$ as an + element of the field $\mathbb{Q}(\zeta)$: + + >>> a6_wrong = AlgebraicNumber(a5, coeffs=[2, 0]) + >>> a6_wrong.as_poly().as_expr() + -2*zeta**3 - 2*zeta**2 + >>> a6_wrong.primitive_element().evalf() + 0.309016994374947 + 0.951056516295154*I + + """ + from sympy.polys.polyclasses import ANP, DMP + from sympy.polys.numberfields import minimal_polynomial + + expr = sympify(expr) + rep0 = None + alias0 = None + + if isinstance(expr, (tuple, Tuple)): + minpoly, root = expr + + if not minpoly.is_Poly: + from sympy.polys.polytools import Poly + minpoly = Poly(minpoly) + elif expr.is_AlgebraicNumber: + minpoly, root, rep0, alias0 = (expr.minpoly, expr.root, + expr.rep, expr.alias) + else: + minpoly, root = minimal_polynomial( + expr, args.get('gen'), polys=True), expr + + dom = minpoly.get_domain() + + if coeffs is not None: + if not isinstance(coeffs, ANP): + rep = DMP.from_sympy_list(sympify(coeffs), 0, dom) + scoeffs = Tuple(*coeffs) + else: + rep = DMP.from_list(coeffs.to_list(), 0, dom) + scoeffs = Tuple(*coeffs.to_list()) + + else: + rep = DMP.from_list([1, 0], 0, dom) + scoeffs = Tuple(1, 0) + + if rep0 is not None: + from sympy.polys.densetools import dup_compose + c = dup_compose(rep.to_list(), rep0.to_list(), dom) + rep = DMP.from_list(c, 0, dom) + scoeffs = Tuple(*c) + + if rep.degree() >= minpoly.degree(): + rep = rep.rem(minpoly.rep) + + sargs = (root, scoeffs) + + alias = alias or alias0 + if alias is not None: + from .symbol import Symbol + if not isinstance(alias, Symbol): + alias = Symbol(alias) + sargs = sargs + (alias,) + + obj = Expr.__new__(cls, *sargs) + + obj.rep = rep + obj.root = root + obj.alias = alias + obj.minpoly = minpoly + + obj._own_minpoly = None + + return obj + + def __hash__(self): + return super().__hash__() + + def _eval_evalf(self, prec): + return self.as_expr()._evalf(prec) + + @property + def is_aliased(self): + """Returns ``True`` if ``alias`` was set. """ + return self.alias is not None + + def as_poly(self, x=None): + """Create a Poly instance from ``self``. """ + from sympy.polys.polytools import Poly, PurePoly + if x is not None: + return Poly.new(self.rep, x) + else: + if self.alias is not None: + return Poly.new(self.rep, self.alias) + else: + from .symbol import Dummy + return PurePoly.new(self.rep, Dummy('x')) + + def as_expr(self, x=None): + """Create a Basic expression from ``self``. """ + return self.as_poly(x or self.root).as_expr().expand() + + def coeffs(self): + """Returns all SymPy coefficients of an algebraic number. """ + return [ self.rep.dom.to_sympy(c) for c in self.rep.all_coeffs() ] + + def native_coeffs(self): + """Returns all native coefficients of an algebraic number. """ + return self.rep.all_coeffs() + + def to_algebraic_integer(self): + """Convert ``self`` to an algebraic integer. """ + from sympy.polys.polytools import Poly + + f = self.minpoly + + if f.LC() == 1: + return self + + coeff = f.LC()**(f.degree() - 1) + poly = f.compose(Poly(f.gen/f.LC())) + + minpoly = poly*coeff + root = f.LC()*self.root + + return AlgebraicNumber((minpoly, root), self.coeffs()) + + def _eval_simplify(self, **kwargs): + from sympy.polys.rootoftools import CRootOf + from sympy.polys import minpoly + measure, ratio = kwargs['measure'], kwargs['ratio'] + for r in [r for r in self.minpoly.all_roots() if r.func != CRootOf]: + if minpoly(self.root - r).is_Symbol: + # use the matching root if it's simpler + if measure(r) < ratio*measure(self.root): + return AlgebraicNumber(r) + return self + + def field_element(self, coeffs): + r""" + Form another element of the same number field. + + Explanation + =========== + + If we represent $\alpha \in \mathbb{Q}(\theta)$, form another element + $\beta \in \mathbb{Q}(\theta)$ of the same number field. + + Parameters + ========== + + coeffs : list, :py:class:`~.ANP` + Like the *coeffs* arg to the class + :py:meth:`constructor<.AlgebraicNumber.__new__>`, defines the + new element as a polynomial in the primitive element. + + If a list, the elements should be integers or rational numbers. + If an :py:class:`~.ANP`, we take its coefficients (using its + :py:meth:`~.ANP.to_list()` method). + + Examples + ======== + + >>> from sympy import AlgebraicNumber, sqrt + >>> a = AlgebraicNumber(sqrt(5), [-1, 1]) + >>> b = a.field_element([3, 2]) + >>> print(a) + 1 - sqrt(5) + >>> print(b) + 2 + 3*sqrt(5) + >>> print(b.primitive_element() == a.primitive_element()) + True + + See Also + ======== + + AlgebraicNumber + """ + return AlgebraicNumber( + (self.minpoly, self.root), coeffs=coeffs, alias=self.alias) + + @property + def is_primitive_element(self): + r""" + Say whether this algebraic number $\alpha \in \mathbb{Q}(\theta)$ is + equal to the primitive element $\theta$ for its field. + """ + c = self.coeffs() + # Second case occurs if self.minpoly is linear: + return c == [1, 0] or c == [self.root] + + def primitive_element(self): + r""" + Get the primitive element $\theta$ for the number field + $\mathbb{Q}(\theta)$ to which this algebraic number $\alpha$ belongs. + + Returns + ======= + + AlgebraicNumber + + """ + if self.is_primitive_element: + return self + return self.field_element([1, 0]) + + def to_primitive_element(self, radicals=True): + r""" + Convert ``self`` to an :py:class:`~.AlgebraicNumber` instance that is + equal to its own primitive element. + + Explanation + =========== + + If we represent $\alpha \in \mathbb{Q}(\theta)$, $\alpha \neq \theta$, + construct a new :py:class:`~.AlgebraicNumber` that represents + $\alpha \in \mathbb{Q}(\alpha)$. + + Examples + ======== + + >>> from sympy import sqrt, to_number_field + >>> from sympy.abc import x + >>> a = to_number_field(sqrt(2), sqrt(2) + sqrt(3)) + + The :py:class:`~.AlgebraicNumber` ``a`` represents the number + $\sqrt{2}$ in the field $\mathbb{Q}(\sqrt{2} + \sqrt{3})$. Rendering + ``a`` as a polynomial, + + >>> a.as_poly().as_expr(x) + x**3/2 - 9*x/2 + + reflects the fact that $\sqrt{2} = \theta^3/2 - 9 \theta/2$, where + $\theta = \sqrt{2} + \sqrt{3}$. + + ``a`` is not equal to its own primitive element. Its minpoly + + >>> a.minpoly.as_poly().as_expr(x) + x**4 - 10*x**2 + 1 + + is that of $\theta$. + + Converting to a primitive element, + + >>> a_prim = a.to_primitive_element() + >>> a_prim.minpoly.as_poly().as_expr(x) + x**2 - 2 + + we obtain an :py:class:`~.AlgebraicNumber` whose ``minpoly`` is that of + the number itself. + + Parameters + ========== + + radicals : boolean, optional (default=True) + If ``True``, then we will try to return an + :py:class:`~.AlgebraicNumber` whose ``root`` is an expression + in radicals. If that is not possible (or if *radicals* is + ``False``), ``root`` will be a :py:class:`~.ComplexRootOf`. + + Returns + ======= + + AlgebraicNumber + + See Also + ======== + + is_primitive_element + + """ + if self.is_primitive_element: + return self + m = self.minpoly_of_element() + r = self.to_root(radicals=radicals) + return AlgebraicNumber((m, r)) + + def minpoly_of_element(self): + r""" + Compute the minimal polynomial for this algebraic number. + + Explanation + =========== + + Recall that we represent an element $\alpha \in \mathbb{Q}(\theta)$. + Our instance attribute ``self.minpoly`` is the minimal polynomial for + our primitive element $\theta$. This method computes the minimal + polynomial for $\alpha$. + + """ + if self._own_minpoly is None: + if self.is_primitive_element: + self._own_minpoly = self.minpoly + else: + from sympy.polys.numberfields.minpoly import minpoly + theta = self.primitive_element() + self._own_minpoly = minpoly(self.as_expr(theta), polys=True) + return self._own_minpoly + + def to_root(self, radicals=True, minpoly=None): + """ + Convert to an :py:class:`~.Expr` that is not an + :py:class:`~.AlgebraicNumber`, specifically, either a + :py:class:`~.ComplexRootOf`, or, optionally and where possible, an + expression in radicals. + + Parameters + ========== + + radicals : boolean, optional (default=True) + If ``True``, then we will try to return the root as an expression + in radicals. If that is not possible, we will return a + :py:class:`~.ComplexRootOf`. + + minpoly : :py:class:`~.Poly` + If the minimal polynomial for `self` has been pre-computed, it can + be passed in order to save time. + + """ + if self.is_primitive_element and not isinstance(self.root, AlgebraicNumber): + return self.root + m = minpoly or self.minpoly_of_element() + roots = m.all_roots(radicals=radicals) + if len(roots) == 1: + return roots[0] + ex = self.as_expr() + for b in roots: + if m.same_root(b, ex): + return b + + +class RationalConstant(Rational): + """ + Abstract base class for rationals with specific behaviors + + Derived classes must define class attributes p and q and should probably all + be singletons. + """ + __slots__ = () + + def __new__(cls): + return AtomicExpr.__new__(cls) + + +class IntegerConstant(Integer): + __slots__ = () + + def __new__(cls): + return AtomicExpr.__new__(cls) + + +class Zero(IntegerConstant, metaclass=Singleton): + """The number zero. + + Zero is a singleton, and can be accessed by ``S.Zero`` + + Examples + ======== + + >>> from sympy import S, Integer + >>> Integer(0) is S.Zero + True + >>> 1/S.Zero + zoo + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Zero + """ + + p = 0 + q = 1 + is_positive = False + is_negative = False + is_zero = True + is_number = True + is_comparable = True + + __slots__ = () + + def __getnewargs__(self): + return () + + @staticmethod + def __abs__(): + return S.Zero + + @staticmethod + def __neg__(): + return S.Zero + + def _eval_power(self, expt): + if expt.is_extended_positive: + return self + if expt.is_extended_negative: + return S.ComplexInfinity + if expt.is_extended_real is False: + return S.NaN + if expt.is_zero: + return S.One + + # infinities are already handled with pos and neg + # tests above; now throw away leading numbers on Mul + # exponent since 0**-x = zoo**x even when x == 0 + coeff, terms = expt.as_coeff_Mul() + if coeff.is_negative: + return S.ComplexInfinity**terms + if coeff is not S.One: # there is a Number to discard + return self**terms + + def _eval_order(self, *symbols): + # Order(0,x) -> 0 + return self + + def __bool__(self): + return False + + +class One(IntegerConstant, metaclass=Singleton): + """The number one. + + One is a singleton, and can be accessed by ``S.One``. + + Examples + ======== + + >>> from sympy import S, Integer + >>> Integer(1) is S.One + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/1_%28number%29 + """ + is_number = True + is_positive = True + + p = 1 + q = 1 + + __slots__ = () + + def __getnewargs__(self): + return () + + @staticmethod + def __abs__(): + return S.One + + @staticmethod + def __neg__(): + return S.NegativeOne + + def _eval_power(self, expt): + return self + + def _eval_order(self, *symbols): + return + + @staticmethod + def factors(limit=None, use_trial=True, use_rho=False, use_pm1=False, + verbose=False, visual=False): + if visual: + return S.One + else: + return {} + + +class NegativeOne(IntegerConstant, metaclass=Singleton): + """The number negative one. + + NegativeOne is a singleton, and can be accessed by ``S.NegativeOne``. + + Examples + ======== + + >>> from sympy import S, Integer + >>> Integer(-1) is S.NegativeOne + True + + See Also + ======== + + One + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/%E2%88%921_%28number%29 + + """ + is_number = True + + p = -1 + q = 1 + + __slots__ = () + + def __getnewargs__(self): + return () + + @staticmethod + def __abs__(): + return S.One + + @staticmethod + def __neg__(): + return S.One + + def _eval_power(self, expt): + if expt.is_odd: + return S.NegativeOne + if expt.is_even: + return S.One + if isinstance(expt, Number): + if isinstance(expt, Float): + return Float(-1.0)**expt + if expt is S.NaN: + return S.NaN + if expt in (S.Infinity, S.NegativeInfinity): + return S.NaN + if expt is S.Half: + return S.ImaginaryUnit + if isinstance(expt, Rational): + if expt.q == 2: + return S.ImaginaryUnit**Integer(expt.p) + i, r = divmod(expt.p, expt.q) + if i: + return self**i*self**Rational(r, expt.q) + return + + +class Half(RationalConstant, metaclass=Singleton): + """The rational number 1/2. + + Half is a singleton, and can be accessed by ``S.Half``. + + Examples + ======== + + >>> from sympy import S, Rational + >>> Rational(1, 2) is S.Half + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/One_half + """ + is_number = True + + p = 1 + q = 2 + + __slots__ = () + + def __getnewargs__(self): + return () + + @staticmethod + def __abs__(): + return S.Half + + +class Infinity(Number, metaclass=Singleton): + r"""Positive infinite quantity. + + Explanation + =========== + + In real analysis the symbol `\infty` denotes an unbounded + limit: `x\to\infty` means that `x` grows without bound. + + Infinity is often used not only to define a limit but as a value + in the affinely extended real number system. Points labeled `+\infty` + and `-\infty` can be added to the topological space of the real numbers, + producing the two-point compactification of the real numbers. Adding + algebraic properties to this gives us the extended real numbers. + + Infinity is a singleton, and can be accessed by ``S.Infinity``, + or can be imported as ``oo``. + + Examples + ======== + + >>> from sympy import oo, exp, limit, Symbol + >>> 1 + oo + oo + >>> 42/oo + 0 + >>> x = Symbol('x') + >>> limit(exp(x), x, oo) + oo + + See Also + ======== + + NegativeInfinity, NaN + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Infinity + """ + + is_commutative = True + is_number = True + is_complex = False + is_extended_real = True + is_infinite = True + is_comparable = True + is_extended_positive = True + is_prime = False + + __slots__ = () + + def __new__(cls): + return AtomicExpr.__new__(cls) + + def _latex(self, printer): + return r"\infty" + + def _eval_subs(self, old, new): + if self == old: + return new + + def _eval_evalf(self, prec=None): + return Float('inf') + + def evalf(self, prec=None, **options): + return self._eval_evalf(prec) + + @_sympifyit('other', NotImplemented) + def __add__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other in (S.NegativeInfinity, S.NaN): + return S.NaN + return self + return Number.__add__(self, other) + __radd__ = __add__ + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other in (S.Infinity, S.NaN): + return S.NaN + return self + return Number.__sub__(self, other) + + @_sympifyit('other', NotImplemented) + def __rsub__(self, other): + return (-self).__add__(other) + + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other.is_zero or other is S.NaN: + return S.NaN + if other.is_extended_positive: + return self + return S.NegativeInfinity + return Number.__mul__(self, other) + __rmul__ = __mul__ + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other is S.Infinity or \ + other is S.NegativeInfinity or \ + other is S.NaN: + return S.NaN + if other.is_extended_nonnegative: + return self + return S.NegativeInfinity + return Number.__truediv__(self, other) + + def __abs__(self): + return S.Infinity + + def __neg__(self): + return S.NegativeInfinity + + def _eval_power(self, expt): + """ + ``expt`` is symbolic object but not equal to 0 or 1. + + ================ ======= ============================== + Expression Result Notes + ================ ======= ============================== + ``oo ** nan`` ``nan`` + ``oo ** -p`` ``0`` ``p`` is number, ``oo`` + ================ ======= ============================== + + See Also + ======== + Pow + NaN + NegativeInfinity + + """ + if expt.is_extended_positive: + return S.Infinity + if expt.is_extended_negative: + return S.Zero + if expt is S.NaN: + return S.NaN + if expt is S.ComplexInfinity: + return S.NaN + if expt.is_extended_real is False and expt.is_number: + from sympy.functions.elementary.complexes import re + expt_real = re(expt) + if expt_real.is_positive: + return S.ComplexInfinity + if expt_real.is_negative: + return S.Zero + if expt_real.is_zero: + return S.NaN + + return self**expt.evalf() + + def _as_mpf_val(self, prec): + return mlib.finf + + def __hash__(self): + return super().__hash__() + + def __eq__(self, other): + return other is S.Infinity or other == float('inf') + + def __ne__(self, other): + return other is not S.Infinity and other != float('inf') + + __gt__ = Expr.__gt__ + __ge__ = Expr.__ge__ + __lt__ = Expr.__lt__ + __le__ = Expr.__le__ + + @_sympifyit('other', NotImplemented) + def __mod__(self, other): + if not isinstance(other, Expr): + return NotImplemented + return S.NaN + + __rmod__ = __mod__ + + def floor(self): + return self + + def ceiling(self): + return self + +oo = S.Infinity + + +class NegativeInfinity(Number, metaclass=Singleton): + """Negative infinite quantity. + + NegativeInfinity is a singleton, and can be accessed + by ``S.NegativeInfinity``. + + See Also + ======== + + Infinity + """ + + is_extended_real = True + is_complex = False + is_commutative = True + is_infinite = True + is_comparable = True + is_extended_negative = True + is_number = True + is_prime = False + + __slots__ = () + + def __new__(cls): + return AtomicExpr.__new__(cls) + + def _latex(self, printer): + return r"-\infty" + + def _eval_subs(self, old, new): + if self == old: + return new + + def _eval_evalf(self, prec=None): + return Float('-inf') + + def evalf(self, prec=None, **options): + return self._eval_evalf(prec) + + @_sympifyit('other', NotImplemented) + def __add__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other in (S.Infinity, S.NaN): + return S.NaN + return self + return Number.__add__(self, other) + __radd__ = __add__ + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other in (S.NegativeInfinity, S.NaN): + return S.NaN + return self + return Number.__sub__(self, other) + + @_sympifyit('other', NotImplemented) + def __rsub__(self, other): + return (-self).__add__(other) + + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other.is_zero or other is S.NaN: + return S.NaN + if other.is_extended_positive: + return self + return S.Infinity + return Number.__mul__(self, other) + __rmul__ = __mul__ + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + if isinstance(other, Number) and global_parameters.evaluate: + if other is S.Infinity or \ + other is S.NegativeInfinity or \ + other is S.NaN: + return S.NaN + if other.is_extended_nonnegative: + return self + return S.Infinity + return Number.__truediv__(self, other) + + def __abs__(self): + return S.Infinity + + def __neg__(self): + return S.Infinity + + def _eval_power(self, expt): + """ + ``expt`` is symbolic object but not equal to 0 or 1. + + ================ ======= ============================== + Expression Result Notes + ================ ======= ============================== + ``(-oo) ** nan`` ``nan`` + ``(-oo) ** oo`` ``nan`` + ``(-oo) ** -oo`` ``nan`` + ``(-oo) ** e`` ``oo`` ``e`` is positive even integer + ``(-oo) ** o`` ``-oo`` ``o`` is positive odd integer + ================ ======= ============================== + + See Also + ======== + + Infinity + Pow + NaN + + """ + if expt.is_number: + if expt is S.NaN or \ + expt is S.Infinity or \ + expt is S.NegativeInfinity: + return S.NaN + + if isinstance(expt, Integer) and expt.is_extended_positive: + if expt.is_odd: + return S.NegativeInfinity + else: + return S.Infinity + + inf_part = S.Infinity**expt + s_part = S.NegativeOne**expt + if inf_part == 0 and s_part.is_finite: + return inf_part + if (inf_part is S.ComplexInfinity and + s_part.is_finite and not s_part.is_zero): + return S.ComplexInfinity + return s_part*inf_part + + def _as_mpf_val(self, prec): + return mlib.fninf + + def __hash__(self): + return super().__hash__() + + def __eq__(self, other): + return other is S.NegativeInfinity or other == float('-inf') + + def __ne__(self, other): + return other is not S.NegativeInfinity and other != float('-inf') + + __gt__ = Expr.__gt__ + __ge__ = Expr.__ge__ + __lt__ = Expr.__lt__ + __le__ = Expr.__le__ + + @_sympifyit('other', NotImplemented) + def __mod__(self, other): + if not isinstance(other, Expr): + return NotImplemented + return S.NaN + + __rmod__ = __mod__ + + def floor(self): + return self + + def ceiling(self): + return self + + def as_powers_dict(self): + return {S.NegativeOne: 1, S.Infinity: 1} + + +class NaN(Number, metaclass=Singleton): + """ + Not a Number. + + Explanation + =========== + + This serves as a place holder for numeric values that are indeterminate. + Most operations on NaN, produce another NaN. Most indeterminate forms, + such as ``0/0`` or ``oo - oo` produce NaN. Two exceptions are ``0**0`` + and ``oo**0``, which all produce ``1`` (this is consistent with Python's + float). + + NaN is loosely related to floating point nan, which is defined in the + IEEE 754 floating point standard, and corresponds to the Python + ``float('nan')``. Differences are noted below. + + NaN is mathematically not equal to anything else, even NaN itself. This + explains the initially counter-intuitive results with ``Eq`` and ``==`` in + the examples below. + + NaN is not comparable so inequalities raise a TypeError. This is in + contrast with floating point nan where all inequalities are false. + + NaN is a singleton, and can be accessed by ``S.NaN``, or can be imported + as ``nan``. + + Examples + ======== + + >>> from sympy import nan, S, oo, Eq + >>> nan is S.NaN + True + >>> oo - oo + nan + >>> nan + 1 + nan + >>> Eq(nan, nan) # mathematical equality + False + >>> nan == nan # structural equality + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/NaN + + """ + is_commutative = True + is_extended_real = None + is_real = None + is_rational = None + is_algebraic = None + is_transcendental = None + is_integer = None + is_comparable = False + is_finite = None + is_zero = None + is_prime = None + is_positive = None + is_negative = None + is_number = True + + __slots__ = () + + def __new__(cls): + return AtomicExpr.__new__(cls) + + def _latex(self, printer): + return r"\text{NaN}" + + def __neg__(self): + return self + + @_sympifyit('other', NotImplemented) + def __add__(self, other): + return self + + @_sympifyit('other', NotImplemented) + def __sub__(self, other): + return self + + @_sympifyit('other', NotImplemented) + def __mul__(self, other): + return self + + @_sympifyit('other', NotImplemented) + def __truediv__(self, other): + return self + + def floor(self): + return self + + def ceiling(self): + return self + + def _as_mpf_val(self, prec): + return _mpf_nan + + def __hash__(self): + return super().__hash__() + + def __eq__(self, other): + # NaN is structurally equal to another NaN + return other is S.NaN + + def __ne__(self, other): + return other is not S.NaN + + # Expr will _sympify and raise TypeError + __gt__ = Expr.__gt__ + __ge__ = Expr.__ge__ + __lt__ = Expr.__lt__ + __le__ = Expr.__le__ + +nan = S.NaN + +@dispatch(NaN, Expr) # type:ignore +def _eval_is_eq(a, b): # noqa:F811 + return False + + +class ComplexInfinity(AtomicExpr, metaclass=Singleton): + r"""Complex infinity. + + Explanation + =========== + + In complex analysis the symbol `\tilde\infty`, called "complex + infinity", represents a quantity with infinite magnitude, but + undetermined complex phase. + + ComplexInfinity is a singleton, and can be accessed by + ``S.ComplexInfinity``, or can be imported as ``zoo``. + + Examples + ======== + + >>> from sympy import zoo + >>> zoo + 42 + zoo + >>> 42/zoo + 0 + >>> zoo + zoo + nan + >>> zoo*zoo + zoo + + See Also + ======== + + Infinity + """ + + is_commutative = True + is_infinite = True + is_number = True + is_prime = False + is_complex = False + is_extended_real = False + + kind = NumberKind + + __slots__ = () + + def __new__(cls): + return AtomicExpr.__new__(cls) + + def _latex(self, printer): + return r"\tilde{\infty}" + + @staticmethod + def __abs__(): + return S.Infinity + + def floor(self): + return self + + def ceiling(self): + return self + + @staticmethod + def __neg__(): + return S.ComplexInfinity + + def _eval_power(self, expt): + if expt is S.ComplexInfinity: + return S.NaN + + if isinstance(expt, Number): + if expt.is_zero: + return S.NaN + else: + if expt.is_positive: + return S.ComplexInfinity + else: + return S.Zero + + +zoo = S.ComplexInfinity + + +class NumberSymbol(AtomicExpr): + + is_commutative = True + is_finite = True + is_number = True + + __slots__ = () + + is_NumberSymbol = True + + kind = NumberKind + + def __new__(cls): + return AtomicExpr.__new__(cls) + + def approximation(self, number_cls): + """ Return an interval with number_cls endpoints + that contains the value of NumberSymbol. + If not implemented, then return None. + """ + + def _eval_evalf(self, prec): + return Float._new(self._as_mpf_val(prec), prec) + + def __eq__(self, other): + try: + other = _sympify(other) + except SympifyError: + return NotImplemented + if self is other: + return True + if other.is_Number and self.is_irrational: + return False + + return False # NumberSymbol != non-(Number|self) + + def __ne__(self, other): + return not self == other + + def __le__(self, other): + if self is other: + return S.true + return Expr.__le__(self, other) + + def __ge__(self, other): + if self is other: + return S.true + return Expr.__ge__(self, other) + + def __int__(self): + # subclass with appropriate return value + raise NotImplementedError + + def __hash__(self): + return super().__hash__() + + +class Exp1(NumberSymbol, metaclass=Singleton): + r"""The `e` constant. + + Explanation + =========== + + The transcendental number `e = 2.718281828\ldots` is the base of the + natural logarithm and of the exponential function, `e = \exp(1)`. + Sometimes called Euler's number or Napier's constant. + + Exp1 is a singleton, and can be accessed by ``S.Exp1``, + or can be imported as ``E``. + + Examples + ======== + + >>> from sympy import exp, log, E + >>> E is exp(1) + True + >>> log(E) + 1 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/E_%28mathematical_constant%29 + """ + + is_real = True + is_positive = True + is_negative = False # XXX Forces is_negative/is_nonnegative + is_irrational = True + is_number = True + is_algebraic = False + is_transcendental = True + + __slots__ = () + + def _latex(self, printer): + return r"e" + + @staticmethod + def __abs__(): + return S.Exp1 + + def __int__(self): + return 2 + + def _as_mpf_val(self, prec): + return mpf_e(prec) + + def approximation_interval(self, number_cls): + if issubclass(number_cls, Integer): + return (Integer(2), Integer(3)) + elif issubclass(number_cls, Rational): + pass + + def _eval_power(self, expt): + if global_parameters.exp_is_pow: + return self._eval_power_exp_is_pow(expt) + else: + from sympy.functions.elementary.exponential import exp + return exp(expt) + + def _eval_power_exp_is_pow(self, arg): + if arg.is_Number: + if arg is oo: + return oo + elif arg == -oo: + return S.Zero + from sympy.functions.elementary.exponential import log + if isinstance(arg, log): + return arg.args[0] + + # don't autoexpand Pow or Mul (see the issue 3351): + elif not arg.is_Add: + Ioo = I*oo + if arg in [Ioo, -Ioo]: + return nan + + coeff = arg.coeff(pi*I) + if coeff: + if (2*coeff).is_integer: + if coeff.is_even: + return S.One + elif coeff.is_odd: + return S.NegativeOne + elif (coeff + S.Half).is_even: + return -I + elif (coeff + S.Half).is_odd: + return I + elif coeff.is_Rational: + ncoeff = coeff % 2 # restrict to [0, 2pi) + if ncoeff > 1: # restrict to (-pi, pi] + ncoeff -= 2 + if ncoeff != coeff: + return S.Exp1**(ncoeff*S.Pi*S.ImaginaryUnit) + + # Warning: code in risch.py will be very sensitive to changes + # in this (see DifferentialExtension). + + # look for a single log factor + + coeff, terms = arg.as_coeff_Mul() + + # but it can't be multiplied by oo + if coeff in (oo, -oo): + return + + coeffs, log_term = [coeff], None + for term in Mul.make_args(terms): + if isinstance(term, log): + if log_term is None: + log_term = term.args[0] + else: + return + elif term.is_comparable: + coeffs.append(term) + else: + return + + return log_term**Mul(*coeffs) if log_term else None + elif arg.is_Add: + out = [] + add = [] + argchanged = False + for a in arg.args: + if a is S.One: + add.append(a) + continue + newa = self**a + if isinstance(newa, Pow) and newa.base is self: + if newa.exp != a: + add.append(newa.exp) + argchanged = True + else: + add.append(a) + else: + out.append(newa) + if out or argchanged: + return Mul(*out)*Pow(self, Add(*add), evaluate=False) + elif arg.is_Matrix: + return arg.exp() + + def _eval_rewrite_as_sin(self, **kwargs): + from sympy.functions.elementary.trigonometric import sin + return sin(I + S.Pi/2) - I*sin(I) + + def _eval_rewrite_as_cos(self, **kwargs): + from sympy.functions.elementary.trigonometric import cos + return cos(I) + I*cos(I + S.Pi/2) + +E = S.Exp1 + + +class Pi(NumberSymbol, metaclass=Singleton): + r"""The `\pi` constant. + + Explanation + =========== + + The transcendental number `\pi = 3.141592654\ldots` represents the ratio + of a circle's circumference to its diameter, the area of the unit circle, + the half-period of trigonometric functions, and many other things + in mathematics. + + Pi is a singleton, and can be accessed by ``S.Pi``, or can + be imported as ``pi``. + + Examples + ======== + + >>> from sympy import S, pi, oo, sin, exp, integrate, Symbol + >>> S.Pi + pi + >>> pi > 3 + True + >>> pi.is_irrational + True + >>> x = Symbol('x') + >>> sin(x + 2*pi) + sin(x) + >>> integrate(exp(-x**2), (x, -oo, oo)) + sqrt(pi) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Pi + """ + + is_real = True + is_positive = True + is_negative = False + is_irrational = True + is_number = True + is_algebraic = False + is_transcendental = True + + __slots__ = () + + def _latex(self, printer): + return r"\pi" + + @staticmethod + def __abs__(): + return S.Pi + + def __int__(self): + return 3 + + def _as_mpf_val(self, prec): + return mpf_pi(prec) + + def approximation_interval(self, number_cls): + if issubclass(number_cls, Integer): + return (Integer(3), Integer(4)) + elif issubclass(number_cls, Rational): + return (Rational(223, 71, 1), Rational(22, 7, 1)) + +pi = S.Pi + + +class GoldenRatio(NumberSymbol, metaclass=Singleton): + r"""The golden ratio, `\phi`. + + Explanation + =========== + + `\phi = \frac{1 + \sqrt{5}}{2}` is an algebraic number. Two quantities + are in the golden ratio if their ratio is the same as the ratio of + their sum to the larger of the two quantities, i.e. their maximum. + + GoldenRatio is a singleton, and can be accessed by ``S.GoldenRatio``. + + Examples + ======== + + >>> from sympy import S + >>> S.GoldenRatio > 1 + True + >>> S.GoldenRatio.expand(func=True) + 1/2 + sqrt(5)/2 + >>> S.GoldenRatio.is_irrational + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Golden_ratio + """ + + is_real = True + is_positive = True + is_negative = False + is_irrational = True + is_number = True + is_algebraic = True + is_transcendental = False + + __slots__ = () + + def _latex(self, printer): + return r"\phi" + + def __int__(self): + return 1 + + def _as_mpf_val(self, prec): + # XXX track down why this has to be increased + rv = mlib.from_man_exp(phi_fixed(prec + 10), -prec - 10) + return mpf_norm(rv, prec) + + def _eval_expand_func(self, **hints): + from sympy.functions.elementary.miscellaneous import sqrt + return S.Half + S.Half*sqrt(5) + + def approximation_interval(self, number_cls): + if issubclass(number_cls, Integer): + return (S.One, Rational(2)) + elif issubclass(number_cls, Rational): + pass + + _eval_rewrite_as_sqrt = _eval_expand_func + + +class TribonacciConstant(NumberSymbol, metaclass=Singleton): + r"""The tribonacci constant. + + Explanation + =========== + + The tribonacci numbers are like the Fibonacci numbers, but instead + of starting with two predetermined terms, the sequence starts with + three predetermined terms and each term afterwards is the sum of the + preceding three terms. + + The tribonacci constant is the ratio toward which adjacent tribonacci + numbers tend. It is a root of the polynomial `x^3 - x^2 - x - 1 = 0`, + and also satisfies the equation `x + x^{-3} = 2`. + + TribonacciConstant is a singleton, and can be accessed + by ``S.TribonacciConstant``. + + Examples + ======== + + >>> from sympy import S + >>> S.TribonacciConstant > 1 + True + >>> S.TribonacciConstant.expand(func=True) + 1/3 + (19 - 3*sqrt(33))**(1/3)/3 + (3*sqrt(33) + 19)**(1/3)/3 + >>> S.TribonacciConstant.is_irrational + True + >>> S.TribonacciConstant.n(20) + 1.8392867552141611326 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Tribonacci_numbers + """ + + is_real = True + is_positive = True + is_negative = False + is_irrational = True + is_number = True + is_algebraic = True + is_transcendental = False + + __slots__ = () + + def _latex(self, printer): + return r"\text{TribonacciConstant}" + + def __int__(self): + return 1 + + def _as_mpf_val(self, prec): + return self._eval_evalf(prec)._mpf_ + + def _eval_evalf(self, prec): + rv = self._eval_expand_func(function=True)._eval_evalf(prec + 4) + return Float(rv, precision=prec) + + def _eval_expand_func(self, **hints): + from sympy.functions.elementary.miscellaneous import cbrt, sqrt + return (1 + cbrt(19 - 3*sqrt(33)) + cbrt(19 + 3*sqrt(33))) / 3 + + def approximation_interval(self, number_cls): + if issubclass(number_cls, Integer): + return (S.One, Rational(2)) + elif issubclass(number_cls, Rational): + pass + + _eval_rewrite_as_sqrt = _eval_expand_func + + +class EulerGamma(NumberSymbol, metaclass=Singleton): + r"""The Euler-Mascheroni constant. + + Explanation + =========== + + `\gamma = 0.5772157\ldots` (also called Euler's constant) is a mathematical + constant recurring in analysis and number theory. It is defined as the + limiting difference between the harmonic series and the + natural logarithm: + + .. math:: \gamma = \lim\limits_{n\to\infty} + \left(\sum\limits_{k=1}^n\frac{1}{k} - \ln n\right) + + EulerGamma is a singleton, and can be accessed by ``S.EulerGamma``. + + Examples + ======== + + >>> from sympy import S + >>> S.EulerGamma.is_irrational + >>> S.EulerGamma > 0 + True + >>> S.EulerGamma > 1 + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Euler%E2%80%93Mascheroni_constant + """ + + is_real = True + is_positive = True + is_negative = False + is_irrational = None + is_number = True + + __slots__ = () + + def _latex(self, printer): + return r"\gamma" + + def __int__(self): + return 0 + + def _as_mpf_val(self, prec): + # XXX track down why this has to be increased + v = mlib.libhyper.euler_fixed(prec + 10) + rv = mlib.from_man_exp(v, -prec - 10) + return mpf_norm(rv, prec) + + def approximation_interval(self, number_cls): + if issubclass(number_cls, Integer): + return (S.Zero, S.One) + elif issubclass(number_cls, Rational): + return (S.Half, Rational(3, 5, 1)) + + +class Catalan(NumberSymbol, metaclass=Singleton): + r"""Catalan's constant. + + Explanation + =========== + + $G = 0.91596559\ldots$ is given by the infinite series + + .. math:: G = \sum_{k=0}^{\infty} \frac{(-1)^k}{(2k+1)^2} + + Catalan is a singleton, and can be accessed by ``S.Catalan``. + + Examples + ======== + + >>> from sympy import S + >>> S.Catalan.is_irrational + >>> S.Catalan > 0 + True + >>> S.Catalan > 1 + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Catalan%27s_constant + """ + + is_real = True + is_positive = True + is_negative = False + is_irrational = None + is_number = True + + __slots__ = () + + def __int__(self): + return 0 + + def _as_mpf_val(self, prec): + # XXX track down why this has to be increased + v = mlib.catalan_fixed(prec + 10) + rv = mlib.from_man_exp(v, -prec - 10) + return mpf_norm(rv, prec) + + def approximation_interval(self, number_cls): + if issubclass(number_cls, Integer): + return (S.Zero, S.One) + elif issubclass(number_cls, Rational): + return (Rational(9, 10, 1), S.One) + + def _eval_rewrite_as_Sum(self, k_sym=None, symbols=None, **hints): + if (k_sym is not None) or (symbols is not None): + return self + from .symbol import Dummy + from sympy.concrete.summations import Sum + k = Dummy('k', integer=True, nonnegative=True) + return Sum(S.NegativeOne**k / (2*k+1)**2, (k, 0, S.Infinity)) + + def _latex(self, printer): + return "G" + + +class ImaginaryUnit(AtomicExpr, metaclass=Singleton): + r"""The imaginary unit, `i = \sqrt{-1}`. + + I is a singleton, and can be accessed by ``S.I``, or can be + imported as ``I``. + + Examples + ======== + + >>> from sympy import I, sqrt + >>> sqrt(-1) + I + >>> I*I + -1 + >>> 1/I + -I + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Imaginary_unit + """ + + is_commutative = True + is_imaginary = True + is_finite = True + is_number = True + is_algebraic = True + is_transcendental = False + + kind = NumberKind + + __slots__ = () + + def _latex(self, printer): + return printer._settings['imaginary_unit_latex'] + + @staticmethod + def __abs__(): + return S.One + + def _eval_evalf(self, prec): + return self + + def _eval_conjugate(self): + return -S.ImaginaryUnit + + def _eval_power(self, expt): + """ + b is I = sqrt(-1) + e is symbolic object but not equal to 0, 1 + + I**r -> (-1)**(r/2) -> exp(r/2*Pi*I) -> sin(Pi*r/2) + cos(Pi*r/2)*I, r is decimal + I**0 mod 4 -> 1 + I**1 mod 4 -> I + I**2 mod 4 -> -1 + I**3 mod 4 -> -I + """ + + if isinstance(expt, Integer): + expt = expt % 4 + if expt == 0: + return S.One + elif expt == 1: + return S.ImaginaryUnit + elif expt == 2: + return S.NegativeOne + elif expt == 3: + return -S.ImaginaryUnit + if isinstance(expt, Rational): + i, r = divmod(expt, 2) + rv = Pow(S.ImaginaryUnit, r, evaluate=False) + if i % 2: + return Mul(S.NegativeOne, rv, evaluate=False) + return rv + + def as_base_exp(self): + return S.NegativeOne, S.Half + + @property + def _mpc_(self): + return (Float(0)._mpf_, Float(1)._mpf_) + + +I = S.ImaginaryUnit + + +def int_valued(x): + """return True only for a literal Number whose internal + representation as a fraction has a denominator of 1, + else False, i.e. integer, with no fractional part. + """ + if isinstance(x, (SYMPY_INTS, int)): + return True + if type(x) is float: + return x.is_integer() + if isinstance(x, Integer): + return True + if isinstance(x, Float): + # x = s*m*2**p; _mpf_ = s,m,e,p + return x._mpf_[2] >= 0 + return False # or add new types to recognize + + +def equal_valued(x, y): + """Compare expressions treating plain floats as rationals. + + Examples + ======== + + >>> from sympy import S, symbols, Rational, Float + >>> from sympy.core.numbers import equal_valued + >>> equal_valued(1, 2) + False + >>> equal_valued(1, 1) + True + + In SymPy expressions with Floats compare unequal to corresponding + expressions with rationals: + + >>> x = symbols('x') + >>> x**2 == x**2.0 + False + + However an individual Float compares equal to a Rational: + + >>> Rational(1, 2) == Float(0.5) + False + + In a future version of SymPy this might change so that Rational and Float + compare unequal. This function provides the behavior currently expected of + ``==`` so that it could still be used if the behavior of ``==`` were to + change in future. + + >>> equal_valued(1, 1.0) # Float vs Rational + True + >>> equal_valued(S(1).n(3), S(1).n(5)) # Floats of different precision + True + + Explanation + =========== + + In future SymPy versions Float and Rational might compare unequal and floats + with different precisions might compare unequal. In that context a function + is needed that can check if a number is equal to 1 or 0 etc. The idea is + that instead of testing ``if x == 1:`` if we want to accept floats like + ``1.0`` as well then the test can be written as ``if equal_valued(x, 1):`` + or ``if equal_valued(x, 2):``. Since this function is intended to be used + in situations where one or both operands are expected to be concrete + numbers like 1 or 0 the function does not recurse through the args of any + compound expression to compare any nested floats. + + References + ========== + + .. [1] https://github.com/sympy/sympy/pull/20033 + """ + x = _sympify(x) + y = _sympify(y) + + # Handle everything except Float/Rational first + if not x.is_Float and not y.is_Float: + return x == y + elif x.is_Float and y.is_Float: + # Compare values without regard for precision + return x._mpf_ == y._mpf_ + elif x.is_Float: + x, y = y, x + if not x.is_Rational: + return False + + # Now y is Float and x is Rational. A simple approach at this point would + # just be x == Rational(y) but if y has a large exponent creating a + # Rational could be prohibitively expensive. + + sign, man, exp, _ = y._mpf_ + p, q = x.p, x.q + + if sign: + man = -man + + if exp == 0: + # y odd integer + return q == 1 and man == p + elif exp > 0: + # y even integer + if q != 1: + return False + if p.bit_length() != man.bit_length() + exp: + return False + return man << exp == p + else: + # y non-integer. Need p == man and q == 2**-exp + if p != man: + return False + neg_exp = -exp + if q.bit_length() - 1 != neg_exp: + return False + return (1 << neg_exp) == q + + +def all_close(expr1, expr2, rtol=1e-5, atol=1e-8): + """Return True if expr1 and expr2 are numerically close. + + The expressions must have the same structure, but any Rational, Integer, or + Float numbers they contain are compared approximately using rtol and atol. + Any other parts of expressions are compared exactly. However, allowance is + made to allow for the additive and multiplicative identities. + + Relative tolerance is measured with respect to expr2 so when used in + testing expr2 should be the expected correct answer. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.abc import x, y + >>> from sympy.core.numbers import all_close + >>> expr1 = 0.1*exp(x - y) + >>> expr2 = exp(x - y)/10 + >>> expr1 + 0.1*exp(x - y) + >>> expr2 + exp(x - y)/10 + >>> expr1 == expr2 + False + >>> all_close(expr1, expr2) + True + + Identities are automatically supplied: + + >>> all_close(x, x + 1e-10) + True + >>> all_close(x, 1.0*x) + True + >>> all_close(x, 1.0*x + 1e-10) + True + + """ + NUM_TYPES = (Rational, Float) + + def _all_close(obj1, obj2): + if type(obj1) == type(obj2) and isinstance(obj1, (list, tuple)): + if len(obj1) != len(obj2): + return False + return all(_all_close(e1, e2) for e1, e2 in zip(obj1, obj2)) + else: + return _all_close_expr(_sympify(obj1), _sympify(obj2)) + + def _all_close_expr(expr1, expr2): + num1 = isinstance(expr1, NUM_TYPES) + num2 = isinstance(expr2, NUM_TYPES) + if num1 != num2: + return False + elif num1: + return _close_num(expr1, expr2) + if expr1.is_Add or expr1.is_Mul or expr2.is_Add or expr2.is_Mul: + return _all_close_ac(expr1, expr2) + if expr1.func != expr2.func or len(expr1.args) != len(expr2.args): + return False + args = zip(expr1.args, expr2.args) + return all(_all_close_expr(a1, a2) for a1, a2 in args) + + def _close_num(num1, num2): + return bool(abs(num1 - num2) <= atol + rtol*abs(num2)) + + def _all_close_ac(expr1, expr2): + # compare expressions with associative commutative operators for + # approximate equality by seeing that all terms have equivalent + # coefficients (which are always Rational or Float) + if expr1.is_Mul or expr2.is_Mul: + # as_coeff_mul automatically will supply coeff of 1 + c1, e1 = expr1.as_coeff_mul(rational=False) + c2, e2 = expr2.as_coeff_mul(rational=False) + if not _close_num(c1, c2): + return False + s1 = set(e1) + s2 = set(e2) + common = s1 & s2 + s1 -= common + s2 -= common + if not s1: + return True + if not any(i.has(Float) for j in (s1, s2) for i in j): + return False + # factors might not be matching, e.g. + # x != x**1.0, exp(x) != exp(1.0*x), etc... + s1 = [i.as_base_exp() for i in ordered(s1)] + s2 = [i.as_base_exp() for i in ordered(s2)] + unmatched = list(range(len(s1))) + for be1 in s1: + for i in unmatched: + be2 = s2[i] + if _all_close(be1, be2): + unmatched.remove(i) + break + else: + return False + return not(unmatched) + assert expr1.is_Add or expr2.is_Add + cd1 = expr1.as_coefficients_dict() + cd2 = expr2.as_coefficients_dict() + # this test will assure that the key of 1 is in + # each dict and that they have equal values + if not _close_num(cd1[1], cd2[1]): + return False + if len(cd1) != len(cd2): + return False + for k in list(cd1): + if k in cd2: + if not _close_num(cd1.pop(k), cd2.pop(k)): + return False + # k (or a close version in cd2) might have + # Floats in a factor of the term which will + # be handled below + else: + if not cd1: + return True + for k1 in cd1: + for k2 in cd2: + if _all_close_expr(k1, k2): + # found a matching key + # XXX there could be a corner case where + # more than 1 might match and the numbers are + # such that one is better than the other + # that is not being considered here + if not _close_num(cd1[k1], cd2[k2]): + return False + break + else: + # no key matched + return False + return True + + return _all_close(expr1, expr2) + + +@dispatch(Tuple, Number) # type:ignore +def _eval_is_eq(self, other): # noqa: F811 + return False + + +def sympify_fractions(f): + return Rational._new(f.numerator, f.denominator, 1) + +_sympy_converter[fractions.Fraction] = sympify_fractions + + +if gmpy is not None: + + def sympify_mpz(x): + return Integer(int(x)) + + def sympify_mpq(x): + return Rational(int(x.numerator), int(x.denominator)) + + _sympy_converter[type(gmpy.mpz(1))] = sympify_mpz + _sympy_converter[type(gmpy.mpq(1, 2))] = sympify_mpq + + +if flint is not None: + + def sympify_fmpz(x): + return Integer(int(x)) + + def sympify_fmpq(x): + return Rational(int(x.numerator), int(x.denominator)) + + _sympy_converter[type(flint.fmpz(1))] = sympify_fmpz + _sympy_converter[type(flint.fmpq(1, 2))] = sympify_fmpq + + +def sympify_mpmath(x): + return Expr._from_mpmath(x, x.context.prec) + +_sympy_converter[mpnumeric] = sympify_mpmath + + +def sympify_complex(a): + real, imag = list(map(sympify, (a.real, a.imag))) + return real + S.ImaginaryUnit*imag + +_sympy_converter[complex] = sympify_complex + +from .power import Pow +from .mul import Mul +Mul.identity = One() +from .add import Add +Add.identity = Zero() + + +def _register_classes(): + numbers.Number.register(Number) + numbers.Real.register(Float) + numbers.Rational.register(Rational) + numbers.Integral.register(Integer) + +_register_classes() + +_illegal = (S.NaN, S.Infinity, S.NegativeInfinity, S.ComplexInfinity) diff --git a/phivenv/Lib/site-packages/sympy/core/operations.py b/phivenv/Lib/site-packages/sympy/core/operations.py new file mode 100644 index 0000000000000000000000000000000000000000..70d22127eb4d3f69fc5e304ab38f5cce9c4bb551 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/operations.py @@ -0,0 +1,741 @@ +from __future__ import annotations + +from typing import overload, TYPE_CHECKING + +from operator import attrgetter +from collections import defaultdict + +from sympy.utilities.exceptions import sympy_deprecation_warning + +from .sympify import _sympify as _sympify_, sympify +from .basic import Basic +from .cache import cacheit +from .sorting import ordered +from .logic import fuzzy_and +from .parameters import global_parameters +from sympy.utilities.iterables import sift +from sympy.multipledispatch.dispatcher import (Dispatcher, + ambiguity_register_error_ignore_dup, + str_signature, RaiseNotImplementedError) + + +if TYPE_CHECKING: + from sympy.core.expr import Expr + from sympy.core.add import Add + from sympy.core.mul import Mul + from sympy.logic.boolalg import Boolean, And, Or + + +class AssocOp(Basic): + """ Associative operations, can separate noncommutative and + commutative parts. + + (a op b) op c == a op (b op c) == a op b op c. + + Base class for Add and Mul. + + This is an abstract base class, concrete derived classes must define + the attribute `identity`. + + .. deprecated:: 1.7 + + Using arguments that aren't subclasses of :class:`~.Expr` in core + operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is + deprecated. See :ref:`non-expr-args-deprecated` for details. + + Parameters + ========== + + *args : + Arguments which are operated + + evaluate : bool, optional + Evaluate the operation. If not passed, refer to ``global_parameters.evaluate``. + """ + + # for performance reason, we don't let is_commutative go to assumptions, + # and keep it right here + __slots__: tuple[str, ...] = ('is_commutative',) + + _args_type: type[Basic] | None = None + + @cacheit + def __new__(cls, *args, evaluate=None, _sympify=True): + # Allow faster processing by passing ``_sympify=False``, if all arguments + # are already sympified. + if _sympify: + args = list(map(_sympify_, args)) + + # Disallow non-Expr args in Add/Mul + typ = cls._args_type + if typ is not None: + from .relational import Relational + if any(isinstance(arg, Relational) for arg in args): + raise TypeError("Relational cannot be used in %s" % cls.__name__) + + # This should raise TypeError once deprecation period is over: + for arg in args: + if not isinstance(arg, typ): + sympy_deprecation_warning( + f""" + +Using non-Expr arguments in {cls.__name__} is deprecated (in this case, one of +the arguments has type {type(arg).__name__!r}). + +If you really did intend to use a multiplication or addition operation with +this object, use the * or + operator instead. + + """, + deprecated_since_version="1.7", + active_deprecations_target="non-expr-args-deprecated", + stacklevel=4, + ) + + if evaluate is None: + evaluate = global_parameters.evaluate + if not evaluate: + obj = cls._from_args(args) + obj = cls._exec_constructor_postprocessors(obj) + return obj + + args = [a for a in args if a is not cls.identity] + + if len(args) == 0: + return cls.identity + if len(args) == 1: + return args[0] + + c_part, nc_part, order_symbols = cls.flatten(args) + is_commutative = not nc_part + obj = cls._from_args(c_part + nc_part, is_commutative) + obj = cls._exec_constructor_postprocessors(obj) + + if order_symbols is not None: + from sympy.series.order import Order + return Order(obj, *order_symbols) + return obj + + @classmethod + def _from_args(cls, args, is_commutative=None): + """Create new instance with already-processed args. + If the args are not in canonical order, then a non-canonical + result will be returned, so use with caution. The order of + args may change if the sign of the args is changed.""" + if len(args) == 0: + return cls.identity + elif len(args) == 1: + return args[0] + + obj = super().__new__(cls, *args) + if is_commutative is None: + is_commutative = fuzzy_and(a.is_commutative for a in args) + obj.is_commutative = is_commutative + return obj + + def _new_rawargs(self, *args, reeval=True, **kwargs): + """Create new instance of own class with args exactly as provided by + caller but returning the self class identity if args is empty. + + Examples + ======== + + This is handy when we want to optimize things, e.g. + + >>> from sympy import Mul, S + >>> from sympy.abc import x, y + >>> e = Mul(3, x, y) + >>> e.args + (3, x, y) + >>> Mul(*e.args[1:]) + x*y + >>> e._new_rawargs(*e.args[1:]) # the same as above, but faster + x*y + + Note: use this with caution. There is no checking of arguments at + all. This is best used when you are rebuilding an Add or Mul after + simply removing one or more args. If, for example, modifications, + result in extra 1s being inserted they will show up in the result: + + >>> m = (x*y)._new_rawargs(S.One, x); m + 1*x + >>> m == x + False + >>> m.is_Mul + True + + Another issue to be aware of is that the commutativity of the result + is based on the commutativity of self. If you are rebuilding the + terms that came from a commutative object then there will be no + problem, but if self was non-commutative then what you are + rebuilding may now be commutative. + + Although this routine tries to do as little as possible with the + input, getting the commutativity right is important, so this level + of safety is enforced: commutativity will always be recomputed if + self is non-commutative and kwarg `reeval=False` has not been + passed. + """ + if reeval and self.is_commutative is False: + is_commutative = None + else: + is_commutative = self.is_commutative + return self._from_args(args, is_commutative) + + @classmethod + def flatten(cls, seq): + """Return seq so that none of the elements are of type `cls`. This is + the vanilla routine that will be used if a class derived from AssocOp + does not define its own flatten routine.""" + # apply associativity, no commutativity property is used + new_seq = [] + while seq: + o = seq.pop() + if o.__class__ is cls: # classes must match exactly + seq.extend(o.args) + else: + new_seq.append(o) + new_seq.reverse() + + # c_part, nc_part, order_symbols + return [], new_seq, None + + def _matches_commutative(self, expr, repl_dict=None, old=False): + """ + Matches Add/Mul "pattern" to an expression "expr". + + repl_dict ... a dictionary of (wild: expression) pairs, that get + returned with the results + + This function is the main workhorse for Add/Mul. + + Examples + ======== + + >>> from sympy import symbols, Wild, sin + >>> a = Wild("a") + >>> b = Wild("b") + >>> c = Wild("c") + >>> x, y, z = symbols("x y z") + >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z) + {a_: x, b_: y, c_: z} + + In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is + the expression. + + The repl_dict contains parts that were already matched. For example + here: + + >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x}) + {a_: x, b_: y, c_: z} + + the only function of the repl_dict is to return it in the + result, e.g. if you omit it: + + >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z) + {b_: y, c_: z} + + the "a: x" is not returned in the result, but otherwise it is + equivalent. + + """ + from .function import _coeff_isneg + # make sure expr is Expr if pattern is Expr + from .expr import Expr + if isinstance(self, Expr) and not isinstance(expr, Expr): + return None + + if repl_dict is None: + repl_dict = {} + + # handle simple patterns + if self == expr: + return repl_dict + + d = self._matches_simple(expr, repl_dict) + if d is not None: + return d + + # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2) + from .function import WildFunction + from .symbol import Wild + wild_part, exact_part = sift(self.args, lambda p: + p.has(Wild, WildFunction) and not expr.has(p), + binary=True) + if not exact_part: + wild_part = list(ordered(wild_part)) + if self.is_Add: + # in addition to normal ordered keys, impose + # sorting on Muls with leading Number to put + # them in order + wild_part = sorted(wild_part, key=lambda x: + x.args[0] if x.is_Mul and x.args[0].is_Number else + 0) + else: + exact = self._new_rawargs(*exact_part) + free = expr.free_symbols + if free and (exact.free_symbols - free): + # there are symbols in the exact part that are not + # in the expr; but if there are no free symbols, let + # the matching continue + return None + newexpr = self._combine_inverse(expr, exact) + if not old and (expr.is_Add or expr.is_Mul): + check = newexpr + if _coeff_isneg(check): + check = -check + if check.count_ops() > expr.count_ops(): + return None + newpattern = self._new_rawargs(*wild_part) + return newpattern.matches(newexpr, repl_dict) + + # now to real work ;) + i = 0 + saw = set() + while expr not in saw: + saw.add(expr) + args = tuple(ordered(self.make_args(expr))) + if self.is_Add and expr.is_Add: + # in addition to normal ordered keys, impose + # sorting on Muls with leading Number to put + # them in order + args = tuple(sorted(args, key=lambda x: + x.args[0] if x.is_Mul and x.args[0].is_Number else + 0)) + expr_list = (self.identity,) + args + for last_op in reversed(expr_list): + for w in reversed(wild_part): + d1 = w.matches(last_op, repl_dict) + if d1 is not None: + d2 = self.xreplace(d1).matches(expr, d1) + if d2 is not None: + return d2 + + if i == 0: + if self.is_Mul: + # make e**i look like Mul + if expr.is_Pow and expr.exp.is_Integer: + from .mul import Mul + if expr.exp > 0: + expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False) + else: + expr = Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False) + i += 1 + continue + + elif self.is_Add: + # make i*e look like Add + c, e = expr.as_coeff_Mul() + if abs(c) > 1: + from .add import Add + if c > 0: + expr = Add(*[e, (c - 1)*e], evaluate=False) + else: + expr = Add(*[-e, (c + 1)*e], evaluate=False) + i += 1 + continue + + # try collection on non-Wild symbols + from sympy.simplify.radsimp import collect + was = expr + did = set() + for w in reversed(wild_part): + c, w = w.as_coeff_mul(Wild) + free = c.free_symbols - did + if free: + did.update(free) + expr = collect(expr, free) + if expr != was: + i += 0 + continue + + break # if we didn't continue, there is nothing more to do + + return + + def _has_matcher(self): + """Helper for .has() that checks for containment of + subexpressions within an expr by using sets of args + of similar nodes, e.g. x + 1 in x + y + 1 checks + to see that {x, 1} & {x, y, 1} == {x, 1} + """ + def _ncsplit(expr): + # this is not the same as args_cnc because here + # we don't assume expr is a Mul -- hence deal with args -- + # and always return a set. + cpart, ncpart = sift(expr.args, + lambda arg: arg.is_commutative is True, binary=True) + return set(cpart), ncpart + + c, nc = _ncsplit(self) + cls = self.__class__ + + def is_in(expr): + if isinstance(expr, cls): + if expr == self: + return True + _c, _nc = _ncsplit(expr) + if (c & _c) == c: + if not nc: + return True + elif len(nc) <= len(_nc): + for i in range(len(_nc) - len(nc) + 1): + if _nc[i:i + len(nc)] == nc: + return True + return False + return is_in + + def _eval_evalf(self, prec): + """ + Evaluate the parts of self that are numbers; if the whole thing + was a number with no functions it would have been evaluated, but + it wasn't so we must judiciously extract the numbers and reconstruct + the object. This is *not* simply replacing numbers with evaluated + numbers. Numbers should be handled in the largest pure-number + expression as possible. So the code below separates ``self`` into + number and non-number parts and evaluates the number parts and + walks the args of the non-number part recursively (doing the same + thing). + """ + from .add import Add + from .mul import Mul + from .symbol import Symbol + from .function import AppliedUndef + if isinstance(self, (Mul, Add)): + x, tail = self.as_independent(Symbol, AppliedUndef) + # if x is an AssocOp Function then the _evalf below will + # call _eval_evalf (here) so we must break the recursion + if not (tail is self.identity or + isinstance(x, AssocOp) and x.is_Function or + x is self.identity and isinstance(tail, AssocOp)): + # here, we have a number so we just call to _evalf with prec; + # prec is not the same as n, it is the binary precision so + # that's why we don't call to evalf. + x = x._evalf(prec) if x is not self.identity else self.identity + args = [] + tail_args = tuple(self.func.make_args(tail)) + for a in tail_args: + # here we call to _eval_evalf since we don't know what we + # are dealing with and all other _eval_evalf routines should + # be doing the same thing (i.e. taking binary prec and + # finding the evalf-able args) + newa = a._eval_evalf(prec) + if newa is None: + args.append(a) + else: + args.append(newa) + return self.func(x, *args) + + # this is the same as above, but there were no pure-number args to + # deal with + args = [] + for a in self.args: + newa = a._eval_evalf(prec) + if newa is None: + args.append(a) + else: + args.append(newa) + return self.func(*args) + + @overload + @classmethod + def make_args(cls: type[Add], expr: Expr) -> tuple[Expr, ...]: ... # type: ignore + @overload + @classmethod + def make_args(cls: type[Mul], expr: Expr) -> tuple[Expr, ...]: ... # type: ignore + @overload + @classmethod + def make_args(cls: type[And], expr: Boolean) -> tuple[Boolean, ...]: ... # type: ignore + @overload + @classmethod + def make_args(cls: type[Or], expr: Boolean) -> tuple[Boolean, ...]: ... # type: ignore + + @classmethod + def make_args(cls: type[Basic], expr: Basic) -> tuple[Basic, ...]: + """ + Return a sequence of elements `args` such that cls(*args) == expr + + Examples + ======== + + >>> from sympy import Symbol, Mul, Add + >>> x, y = map(Symbol, 'xy') + + >>> Mul.make_args(x*y) + (x, y) + >>> Add.make_args(x*y) + (x*y,) + >>> set(Add.make_args(x*y + y)) == set([y, x*y]) + True + + """ + if isinstance(expr, cls): + return expr.args + else: + return (sympify(expr),) + + def doit(self, **hints): + if hints.get('deep', True): + terms = [term.doit(**hints) for term in self.args] + else: + terms = self.args + return self.func(*terms, evaluate=True) + +class ShortCircuit(Exception): + pass + + +class LatticeOp(AssocOp): + """ + Join/meet operations of an algebraic lattice[1]. + + Explanation + =========== + + These binary operations are associative (op(op(a, b), c) = op(a, op(b, c))), + commutative (op(a, b) = op(b, a)) and idempotent (op(a, a) = op(a) = a). + Common examples are AND, OR, Union, Intersection, max or min. They have an + identity element (op(identity, a) = a) and an absorbing element + conventionally called zero (op(zero, a) = zero). + + This is an abstract base class, concrete derived classes must declare + attributes zero and identity. All defining properties are then respected. + + Examples + ======== + + >>> from sympy import Integer + >>> from sympy.core.operations import LatticeOp + >>> class my_join(LatticeOp): + ... zero = Integer(0) + ... identity = Integer(1) + >>> my_join(2, 3) == my_join(3, 2) + True + >>> my_join(2, my_join(3, 4)) == my_join(2, 3, 4) + True + >>> my_join(0, 1, 4, 2, 3, 4) + 0 + >>> my_join(1, 2) + 2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lattice_%28order%29 + """ + + is_commutative = True + + def __new__(cls, *args, **options): + args = (_sympify_(arg) for arg in args) + + try: + # /!\ args is a generator and _new_args_filter + # must be careful to handle as such; this + # is done so short-circuiting can be done + # without having to sympify all values + _args = frozenset(cls._new_args_filter(args)) + except ShortCircuit: + return sympify(cls.zero) + if not _args: + return sympify(cls.identity) + elif len(_args) == 1: + return set(_args).pop() + else: + # XXX in almost every other case for __new__, *_args is + # passed along, but the expectation here is for _args + obj = super(AssocOp, cls).__new__(cls, *ordered(_args)) + obj._argset = _args + return obj + + @classmethod + def _new_args_filter(cls, arg_sequence, call_cls=None): + """Generator filtering args""" + ncls = call_cls or cls + for arg in arg_sequence: + if arg == ncls.zero: + raise ShortCircuit(arg) + elif arg == ncls.identity: + continue + elif arg.func == ncls: + yield from arg.args + else: + yield arg + + @classmethod + def make_args(cls, expr): + """ + Return a set of args such that cls(*arg_set) == expr. + """ + if isinstance(expr, cls): + return expr._argset + else: + return frozenset([sympify(expr)]) + + +class AssocOpDispatcher: + """ + Handler dispatcher for associative operators + + .. notes:: + This approach is experimental, and can be replaced or deleted in the future. + See https://github.com/sympy/sympy/pull/19463. + + Explanation + =========== + + If arguments of different types are passed, the classes which handle the operation for each type + are collected. Then, a class which performs the operation is selected by recursive binary dispatching. + Dispatching relation can be registered by ``register_handlerclass`` method. + + Priority registration is unordered. You cannot make ``A*B`` and ``B*A`` refer to + different handler classes. All logic dealing with the order of arguments must be implemented + in the handler class. + + Examples + ======== + + >>> from sympy import Add, Expr, Symbol + >>> from sympy.core.add import add + + >>> class NewExpr(Expr): + ... @property + ... def _add_handler(self): + ... return NewAdd + >>> class NewAdd(NewExpr, Add): + ... pass + >>> add.register_handlerclass((Add, NewAdd), NewAdd) + + >>> a, b = Symbol('a'), NewExpr() + >>> add(a, b) == NewAdd(a, b) + True + + """ + def __init__(self, name, doc=None): + self.name = name + self.doc = doc + self.handlerattr = "_%s_handler" % name + self._handlergetter = attrgetter(self.handlerattr) + self._dispatcher = Dispatcher(name) + + def __repr__(self): + return "" % self.name + + def register_handlerclass(self, classes, typ, on_ambiguity=ambiguity_register_error_ignore_dup): + """ + Register the handler class for two classes, in both straight and reversed order. + + Paramteters + =========== + + classes : tuple of two types + Classes who are compared with each other. + + typ: + Class which is registered to represent *cls1* and *cls2*. + Handler method of *self* must be implemented in this class. + """ + if not len(classes) == 2: + raise RuntimeError( + "Only binary dispatch is supported, but got %s types: <%s>." % ( + len(classes), str_signature(classes) + )) + if len(set(classes)) == 1: + raise RuntimeError( + "Duplicate types <%s> cannot be dispatched." % str_signature(classes) + ) + self._dispatcher.add(tuple(classes), typ, on_ambiguity=on_ambiguity) + self._dispatcher.add(tuple(reversed(classes)), typ, on_ambiguity=on_ambiguity) + + @cacheit + def __call__(self, *args, _sympify=True, **kwargs): + """ + Parameters + ========== + + *args : + Arguments which are operated + """ + if _sympify: + args = tuple(map(_sympify_, args)) + handlers = frozenset(map(self._handlergetter, args)) + + # no need to sympify again + return self.dispatch(handlers)(*args, _sympify=False, **kwargs) + + @cacheit + def dispatch(self, handlers): + """ + Select the handler class, and return its handler method. + """ + + # Quick exit for the case where all handlers are same + if len(handlers) == 1: + h, = handlers + if not isinstance(h, type): + raise RuntimeError("Handler {!r} is not a type.".format(h)) + return h + + # Recursively select with registered binary priority + for i, typ in enumerate(handlers): + + if not isinstance(typ, type): + raise RuntimeError("Handler {!r} is not a type.".format(typ)) + + if i == 0: + handler = typ + else: + prev_handler = handler + handler = self._dispatcher.dispatch(prev_handler, typ) + + if not isinstance(handler, type): + raise RuntimeError( + "Dispatcher for {!r} and {!r} must return a type, but got {!r}".format( + prev_handler, typ, handler + )) + + # return handler class + return handler + + @property + def __doc__(self): + docs = [ + "Multiply dispatched associative operator: %s" % self.name, + "Note that support for this is experimental, see the docs for :class:`AssocOpDispatcher` for details" + ] + + if self.doc: + docs.append(self.doc) + + s = "Registered handler classes\n" + s += '=' * len(s) + docs.append(s) + + amb_sigs = [] + + typ_sigs = defaultdict(list) + for sigs in self._dispatcher.ordering[::-1]: + key = self._dispatcher.funcs[sigs] + typ_sigs[key].append(sigs) + + for typ, sigs in typ_sigs.items(): + + sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs) + + if isinstance(typ, RaiseNotImplementedError): + amb_sigs.append(sigs_str) + continue + + s = 'Inputs: %s\n' % sigs_str + s += '-' * len(s) + '\n' + s += typ.__name__ + docs.append(s) + + if amb_sigs: + s = "Ambiguous handler classes\n" + s += '=' * len(s) + docs.append(s) + + s = '\n'.join(amb_sigs) + docs.append(s) + + return '\n\n'.join(docs) diff --git a/phivenv/Lib/site-packages/sympy/core/parameters.py b/phivenv/Lib/site-packages/sympy/core/parameters.py new file mode 100644 index 0000000000000000000000000000000000000000..d911a3652bf02fa5b24c43b138035a57be687228 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/parameters.py @@ -0,0 +1,161 @@ +"""Thread-safe global parameters""" + +from .cache import clear_cache +from contextlib import contextmanager +from threading import local + +class _global_parameters(local): + """ + Thread-local global parameters. + + Explanation + =========== + + This class generates thread-local container for SymPy's global parameters. + Every global parameters must be passed as keyword argument when generating + its instance. + A variable, `global_parameters` is provided as default instance for this class. + + WARNING! Although the global parameters are thread-local, SymPy's cache is not + by now. + This may lead to undesired result in multi-threading operations. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.core.cache import clear_cache + >>> from sympy.core.parameters import global_parameters as gp + + >>> gp.evaluate + True + >>> x+x + 2*x + + >>> log = [] + >>> def f(): + ... clear_cache() + ... gp.evaluate = False + ... log.append(x+x) + ... clear_cache() + >>> import threading + >>> thread = threading.Thread(target=f) + >>> thread.start() + >>> thread.join() + + >>> print(log) + [x + x] + + >>> gp.evaluate + True + >>> x+x + 2*x + + References + ========== + + .. [1] https://docs.python.org/3/library/threading.html + + """ + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __setattr__(self, name, value): + if getattr(self, name) != value: + clear_cache() + return super().__setattr__(name, value) + +global_parameters = _global_parameters(evaluate=True, distribute=True, exp_is_pow=False) + +class evaluate: + """ Control automatic evaluation + + Explanation + =========== + + This context manager controls whether or not all SymPy functions evaluate + by default. + + Note that much of SymPy expects evaluated expressions. This functionality + is experimental and is unlikely to function as intended on large + expressions. + + Examples + ======== + + >>> from sympy import evaluate + >>> from sympy.abc import x + >>> print(x + x) + 2*x + >>> with evaluate(False): + ... print(x + x) + x + x + """ + def __init__(self, x): + self.x = x + self.old = [] + + def __enter__(self): + self.old.append(global_parameters.evaluate) + global_parameters.evaluate = self.x + + def __exit__(self, exc_type, exc_val, exc_tb): + global_parameters.evaluate = self.old.pop() + +@contextmanager +def distribute(x): + """ Control automatic distribution of Number over Add + + Explanation + =========== + + This context manager controls whether or not Mul distribute Number over + Add. Plan is to avoid distributing Number over Add in all of sympy. Once + that is done, this contextmanager will be removed. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy.core.parameters import distribute + >>> print(2*(x + 1)) + 2*x + 2 + >>> with distribute(False): + ... print(2*(x + 1)) + 2*(x + 1) + """ + + old = global_parameters.distribute + + try: + global_parameters.distribute = x + yield + finally: + global_parameters.distribute = old + + +@contextmanager +def _exp_is_pow(x): + """ + Control whether `e^x` should be represented as ``exp(x)`` or a ``Pow(E, x)``. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.abc import x + >>> from sympy.core.parameters import _exp_is_pow + >>> with _exp_is_pow(True): print(type(exp(x))) + + >>> with _exp_is_pow(False): print(type(exp(x))) + exp + """ + old = global_parameters.exp_is_pow + + clear_cache() + try: + global_parameters.exp_is_pow = x + yield + finally: + clear_cache() + global_parameters.exp_is_pow = old diff --git a/phivenv/Lib/site-packages/sympy/core/power.py b/phivenv/Lib/site-packages/sympy/core/power.py new file mode 100644 index 0000000000000000000000000000000000000000..0f257d030553ecc7b887ca9d1199ccc19b9a642f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/power.py @@ -0,0 +1,1847 @@ +from __future__ import annotations +from typing import Callable, TYPE_CHECKING +from itertools import product + +from .sympify import _sympify +from .cache import cacheit +from .singleton import S +from .expr import Expr +from .evalf import PrecisionExhausted +from .function import (expand_complex, expand_multinomial, + expand_mul, _mexpand, PoleError) +from .logic import fuzzy_bool, fuzzy_not, fuzzy_and, fuzzy_or +from .parameters import global_parameters +from .relational import is_gt, is_lt +from .kind import NumberKind, UndefinedKind +from sympy.utilities.iterables import sift +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.misc import as_int +from sympy.multipledispatch import Dispatcher + + +class Pow(Expr): + """ + Defines the expression x**y as "x raised to a power y" + + .. deprecated:: 1.7 + + Using arguments that aren't subclasses of :class:`~.Expr` in core + operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is + deprecated. See :ref:`non-expr-args-deprecated` for details. + + Singleton definitions involving (0, 1, -1, oo, -oo, I, -I): + + +--------------+---------+-----------------------------------------------+ + | expr | value | reason | + +==============+=========+===============================================+ + | z**0 | 1 | Although arguments over 0**0 exist, see [2]. | + +--------------+---------+-----------------------------------------------+ + | z**1 | z | | + +--------------+---------+-----------------------------------------------+ + | (-oo)**(-1) | 0 | | + +--------------+---------+-----------------------------------------------+ + | (-1)**-1 | -1 | | + +--------------+---------+-----------------------------------------------+ + | S.Zero**-1 | zoo | This is not strictly true, as 0**-1 may be | + | | | undefined, but is convenient in some contexts | + | | | where the base is assumed to be positive. | + +--------------+---------+-----------------------------------------------+ + | 1**-1 | 1 | | + +--------------+---------+-----------------------------------------------+ + | oo**-1 | 0 | | + +--------------+---------+-----------------------------------------------+ + | 0**oo | 0 | Because for all complex numbers z near | + | | | 0, z**oo -> 0. | + +--------------+---------+-----------------------------------------------+ + | 0**-oo | zoo | This is not strictly true, as 0**oo may be | + | | | oscillating between positive and negative | + | | | values or rotating in the complex plane. | + | | | It is convenient, however, when the base | + | | | is positive. | + +--------------+---------+-----------------------------------------------+ + | 1**oo | nan | Because there are various cases where | + | 1**-oo | | lim(x(t),t)=1, lim(y(t),t)=oo (or -oo), | + | | | but lim( x(t)**y(t), t) != 1. See [3]. | + +--------------+---------+-----------------------------------------------+ + | b**zoo | nan | Because b**z has no limit as z -> zoo | + +--------------+---------+-----------------------------------------------+ + | (-1)**oo | nan | Because of oscillations in the limit. | + | (-1)**(-oo) | | | + +--------------+---------+-----------------------------------------------+ + | oo**oo | oo | | + +--------------+---------+-----------------------------------------------+ + | oo**-oo | 0 | | + +--------------+---------+-----------------------------------------------+ + | (-oo)**oo | nan | | + | (-oo)**-oo | | | + +--------------+---------+-----------------------------------------------+ + | oo**I | nan | oo**e could probably be best thought of as | + | (-oo)**I | | the limit of x**e for real x as x tends to | + | | | oo. If e is I, then the limit does not exist | + | | | and nan is used to indicate that. | + +--------------+---------+-----------------------------------------------+ + | oo**(1+I) | zoo | If the real part of e is positive, then the | + | (-oo)**(1+I) | | limit of abs(x**e) is oo. So the limit value | + | | | is zoo. | + +--------------+---------+-----------------------------------------------+ + | oo**(-1+I) | 0 | If the real part of e is negative, then the | + | -oo**(-1+I) | | limit is 0. | + +--------------+---------+-----------------------------------------------+ + + Because symbolic computations are more flexible than floating point + calculations and we prefer to never return an incorrect answer, + we choose not to conform to all IEEE 754 conventions. This helps + us avoid extra test-case code in the calculation of limits. + + See Also + ======== + + sympy.core.numbers.Infinity + sympy.core.numbers.NegativeInfinity + sympy.core.numbers.NaN + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Exponentiation + .. [2] https://en.wikipedia.org/wiki/Zero_to_the_power_of_zero + .. [3] https://en.wikipedia.org/wiki/Indeterminate_forms + + """ + is_Pow = True + + __slots__ = ('is_commutative',) + + if TYPE_CHECKING: + + @property + def args(self) -> tuple[Expr, Expr]: + ... + + @property + def base(self) -> Expr: + return self.args[0] + + @property + def exp(self) -> Expr: + return self.args[1] + + @property + def kind(self): + if self.exp.kind is NumberKind: + return self.base.kind + else: + return UndefinedKind + + @cacheit + def __new__(cls, b: Expr | complex, e: Expr | complex, evaluate=None) -> Expr: # type: ignore + if evaluate is None: + evaluate = global_parameters.evaluate + + base = _sympify(b) + exp = _sympify(e) + + # XXX: This can be removed when non-Expr args are disallowed rather + # than deprecated. + from .relational import Relational + if isinstance(base, Relational) or isinstance(exp, Relational): + raise TypeError('Relational cannot be used in Pow') + + # XXX: This should raise TypeError once deprecation period is over: + for arg in [base, exp]: + if not isinstance(arg, Expr): + sympy_deprecation_warning( + f""" + Using non-Expr arguments in Pow is deprecated (in this case, one of the + arguments is of type {type(arg).__name__!r}). + + If you really did intend to construct a power with this base, use the ** + operator instead.""", + deprecated_since_version="1.7", + active_deprecations_target="non-expr-args-deprecated", + stacklevel=4, + ) + + if evaluate: + if exp is S.ComplexInfinity: + return S.NaN + if exp is S.Infinity: + if is_gt(base, S.One): + return S.Infinity + if is_gt(base, S.NegativeOne) and is_lt(base, S.One): + return S.Zero + if is_lt(base, S.NegativeOne): + if base.is_finite: + return S.ComplexInfinity + if base.is_finite is False: + return S.NaN + if exp is S.Zero: + return S.One + elif exp is S.One: + return base + elif exp == -1 and not base: + return S.ComplexInfinity + elif exp.__class__.__name__ == "AccumulationBounds": + if base == S.Exp1: + from sympy.calculus.accumulationbounds import AccumBounds + return AccumBounds(Pow(base, exp.min), Pow(base, exp.max)) + # autosimplification if base is a number and exp odd/even + # if base is Number then the base will end up positive; we + # do not do this with arbitrary expressions since symbolic + # cancellation might occur as in (x - 1)/(1 - x) -> -1. If + # we returned Piecewise((-1, Ne(x, 1))) for such cases then + # we could do this...but we don't + elif (exp.is_Symbol and exp.is_integer or exp.is_Integer + ) and (base.is_number and base.is_Mul or base.is_Number + ) and base.could_extract_minus_sign(): + if exp.is_even: + base = -base + elif exp.is_odd: + return -Pow(-base, exp) + if S.NaN in (base, exp): # XXX S.NaN**x -> S.NaN under assumption that x != 0 + return S.NaN + elif base is S.One: + if abs(exp).is_infinite: + return S.NaN + return S.One + else: + # recognize base as E + from sympy.functions.elementary.exponential import exp_polar + if not exp.is_Atom and base is not S.Exp1 and not isinstance(base, exp_polar): + from .exprtools import factor_terms + from sympy.functions.elementary.exponential import log + from sympy.simplify.radsimp import fraction + c, ex = factor_terms(exp, sign=False).as_coeff_Mul() + num, den = fraction(ex) + if isinstance(den, log) and den.args[0] == base: + return S.Exp1**(c*num) + elif den.is_Add: + from sympy.functions.elementary.complexes import sign, im + s = sign(im(base)) + if s.is_Number and s and den == \ + log(-factor_terms(base, sign=False)) + s*S.ImaginaryUnit*S.Pi: + return S.Exp1**(c*num) + + obj = base._eval_power(exp) + if obj is not None: + return obj + obj = Expr.__new__(cls, base, exp) + obj = cls._exec_constructor_postprocessors(obj) + if not isinstance(obj, Pow): + return obj + obj.is_commutative = (base.is_commutative and exp.is_commutative) + return obj + + def inverse(self, argindex=1): + if self.base == S.Exp1: + from sympy.functions.elementary.exponential import log + return log + return None + + @classmethod + def class_key(cls): + return 3, 2, cls.__name__ + + def _eval_refine(self, assumptions): + from sympy.assumptions.ask import ask, Q + b, e = self.as_base_exp() + if ask(Q.integer(e), assumptions) and b.could_extract_minus_sign(): + if ask(Q.even(e), assumptions): + return Pow(-b, e) + elif ask(Q.odd(e), assumptions): + return -Pow(-b, e) + + def _eval_power(self, expt): + b, e = self.as_base_exp() + if b is S.NaN: + return (b**e)**expt # let __new__ handle it + + s = None + if expt.is_integer: + s = 1 + elif b.is_polar: # e.g. exp_polar, besselj, var('p', polar=True)... + s = 1 + elif e.is_extended_real is not None: + from sympy.functions.elementary.complexes import arg, im, re, sign + from sympy.functions.elementary.exponential import exp, log + from sympy.functions.elementary.integers import floor + # helper functions =========================== + def _half(e): + """Return True if the exponent has a literal 2 as the + denominator, else None.""" + if getattr(e, 'q', None) == 2: + return True + n, d = e.as_numer_denom() + if n.is_integer and d == 2: + return True + def _n2(e): + """Return ``e`` evaluated to a Number with 2 significant + digits, else None.""" + try: + rv = e.evalf(2, strict=True) + if rv.is_Number: + return rv + except PrecisionExhausted: + pass + # =================================================== + if e.is_extended_real: + # we need _half(expt) with constant floor or + # floor(S.Half - e*arg(b)/2/pi) == 0 + + + # handle -1 as special case + if e == -1: + # floor arg. is 1/2 + arg(b)/2/pi + if _half(expt): + if b.is_negative is True: + return S.NegativeOne**expt*Pow(-b, e*expt) + elif b.is_negative is False: # XXX ok if im(b) != 0? + return Pow(b, -expt) + elif e.is_even: + if b.is_extended_real: + b = abs(b) + if b.is_imaginary: + b = abs(im(b))*S.ImaginaryUnit + + if (abs(e) < 1) == True or e == 1: + s = 1 # floor = 0 + elif b.is_extended_nonnegative: + s = 1 # floor = 0 + elif re(b).is_extended_nonnegative and (abs(e) < 2) == True: + s = 1 # floor = 0 + elif _half(expt): + s = exp(2*S.Pi*S.ImaginaryUnit*expt*floor( + S.Half - e*arg(b)/(2*S.Pi))) + if s.is_extended_real and _n2(sign(s) - s) == 0: + s = sign(s) + else: + s = None + else: + # e.is_extended_real is False requires: + # _half(expt) with constant floor or + # floor(S.Half - im(e*log(b))/2/pi) == 0 + try: + s = exp(2*S.ImaginaryUnit*S.Pi*expt* + floor(S.Half - im(e*log(b))/2/S.Pi)) + # be careful to test that s is -1 or 1 b/c sign(I) == I: + # so check that s is real + if s.is_extended_real and _n2(sign(s) - s) == 0: + s = sign(s) + else: + s = None + except PrecisionExhausted: + s = None + + if s is not None: + return s*Pow(b, e*expt) + + def _eval_Mod(self, q): + r"""A dispatched function to compute `b^e \bmod q`, dispatched + by ``Mod``. + + Notes + ===== + + Algorithms: + + 1. For unevaluated integer power, use built-in ``pow`` function + with 3 arguments, if powers are not too large wrt base. + + 2. For very large powers, use totient reduction if $e \ge \log(m)$. + Bound on m, is for safe factorization memory wise i.e. $m^{1/4}$. + For pollard-rho to be faster than built-in pow $\log(e) > m^{1/4}$ + check is added. + + 3. For any unevaluated power found in `b` or `e`, the step 2 + will be recursed down to the base and the exponent + such that the $b \bmod q$ becomes the new base and + $\phi(q) + e \bmod \phi(q)$ becomes the new exponent, and then + the computation for the reduced expression can be done. + """ + + base, exp = self.base, self.exp + + if exp.is_integer and exp.is_positive: + if q.is_integer and base % q == 0: + return S.Zero + + from sympy.functions.combinatorial.numbers import totient + + if base.is_Integer and exp.is_Integer and q.is_Integer: + b, e, m = int(base), int(exp), int(q) + mb = m.bit_length() + if mb <= 80 and e >= mb and e.bit_length()**4 >= m: + phi = int(totient(m)) + return Integer(pow(b, phi + e%phi, m)) + return Integer(pow(b, e, m)) + + from .mod import Mod + + if isinstance(base, Pow) and base.is_integer and base.is_number: + base = Mod(base, q) + return Mod(Pow(base, exp, evaluate=False), q) + + if isinstance(exp, Pow) and exp.is_integer and exp.is_number: + bit_length = int(q).bit_length() + # XXX Mod-Pow actually attempts to do a hanging evaluation + # if this dispatched function returns None. + # May need some fixes in the dispatcher itself. + if bit_length <= 80: + phi = totient(q) + exp = phi + Mod(exp, phi) + return Mod(Pow(base, exp, evaluate=False), q) + + def _eval_is_even(self): + if self.exp.is_integer and self.exp.is_positive: + return self.base.is_even + + def _eval_is_negative(self): + ext_neg = Pow._eval_is_extended_negative(self) + if ext_neg is True: + return self.is_finite + return ext_neg + + def _eval_is_extended_positive(self): + if self.base == self.exp: + if self.base.is_extended_nonnegative: + return True + elif self.base.is_positive: + if self.exp.is_real: + return True + elif self.base.is_extended_negative: + if self.exp.is_even: + return True + if self.exp.is_odd: + return False + elif self.base.is_zero: + if self.exp.is_extended_real: + return self.exp.is_zero + elif self.base.is_extended_nonpositive: + if self.exp.is_odd: + return False + elif self.base.is_imaginary: + if self.exp.is_integer: + m = self.exp % 4 + if m.is_zero: + return True + if m.is_integer and m.is_zero is False: + return False + if self.exp.is_imaginary: + from sympy.functions.elementary.exponential import log + return log(self.base).is_imaginary + + def _eval_is_extended_negative(self): + if self.exp is S.Half: + if self.base.is_complex or self.base.is_extended_real: + return False + if self.base.is_extended_negative: + if self.exp.is_odd and self.base.is_finite: + return True + if self.exp.is_even: + return False + elif self.base.is_extended_positive: + if self.exp.is_extended_real: + return False + elif self.base.is_zero: + if self.exp.is_extended_real: + return False + elif self.base.is_extended_nonnegative: + if self.exp.is_extended_nonnegative: + return False + elif self.base.is_extended_nonpositive: + if self.exp.is_even: + return False + elif self.base.is_extended_real: + if self.exp.is_even: + return False + + def _eval_is_zero(self): + if self.base.is_zero: + if self.exp.is_extended_positive: + return True + elif self.exp.is_extended_nonpositive: + return False + elif self.base == S.Exp1: + return self.exp is S.NegativeInfinity + elif self.base.is_zero is False: + if self.base.is_finite and self.exp.is_finite: + return False + elif self.exp.is_negative: + return self.base.is_infinite + elif self.exp.is_nonnegative: + return False + elif self.exp.is_infinite and self.exp.is_extended_real: + if (1 - abs(self.base)).is_extended_positive: + return self.exp.is_extended_positive + elif (1 - abs(self.base)).is_extended_negative: + return self.exp.is_extended_negative + elif self.base.is_finite and self.exp.is_negative: + # when self.base.is_zero is None + return False + + def _eval_is_integer(self): + b, e = self.args + if b.is_rational: + if b.is_integer is False and e.is_positive: + return False # rat**nonneg + if b.is_integer and e.is_integer: + if b is S.NegativeOne: + return True + if e.is_nonnegative or e.is_positive: + return True + if b.is_integer and e.is_negative and (e.is_finite or e.is_integer): + if fuzzy_not((b - 1).is_zero) and fuzzy_not((b + 1).is_zero): + return False + if b.is_Number and e.is_Number: + check = self.func(*self.args) + return check.is_Integer + if e.is_negative and b.is_positive and (b - 1).is_positive: + return False + if e.is_negative and b.is_negative and (b + 1).is_negative: + return False + + def _eval_is_extended_real(self): + if self.base is S.Exp1: + if self.exp.is_extended_real: + return True + elif self.exp.is_imaginary: + return (2*S.ImaginaryUnit*self.exp/S.Pi).is_even + + from sympy.functions.elementary.exponential import log, exp + real_b = self.base.is_extended_real + if real_b is None: + if self.base.func == exp and self.base.exp.is_imaginary: + return self.exp.is_imaginary + if self.base.func == Pow and self.base.base is S.Exp1 and self.base.exp.is_imaginary: + return self.exp.is_imaginary + return + real_e = self.exp.is_extended_real + if real_e is None: + return + if real_b and real_e: + if self.base.is_extended_positive: + return True + elif self.base.is_extended_nonnegative and self.exp.is_extended_nonnegative: + return True + elif self.exp.is_integer and self.base.is_extended_nonzero: + return True + elif self.exp.is_integer and self.exp.is_nonnegative: + return True + elif self.base.is_extended_negative: + if self.exp.is_Rational: + return False + if real_e and self.exp.is_extended_negative and self.base.is_zero is False: + return Pow(self.base, -self.exp).is_extended_real + im_b = self.base.is_imaginary + im_e = self.exp.is_imaginary + if im_b: + if self.exp.is_integer: + if self.exp.is_even: + return True + elif self.exp.is_odd: + return False + elif im_e and log(self.base).is_imaginary: + return True + elif self.exp.is_Add: + c, a = self.exp.as_coeff_Add() + if c and c.is_Integer: + return Mul( + self.base**c, self.base**a, evaluate=False).is_extended_real + elif self.base in (-S.ImaginaryUnit, S.ImaginaryUnit): + if (self.exp/2).is_integer is False: + return False + if real_b and im_e: + if self.base is S.NegativeOne: + return True + c = self.exp.coeff(S.ImaginaryUnit) + if c: + if self.base.is_rational and c.is_rational: + if self.base.is_nonzero and (self.base - 1).is_nonzero and c.is_nonzero: + return False + ok = (c*log(self.base)/S.Pi).is_integer + if ok is not None: + return ok + + if real_b is False and real_e: # we already know it's not imag + if isinstance(self.exp, Rational) and self.exp.p == 1: + return False + from sympy.functions.elementary.complexes import arg + i = arg(self.base)*self.exp/S.Pi + if i.is_complex: # finite + return i.is_integer + + def _eval_is_complex(self): + + if self.base == S.Exp1: + return fuzzy_or([self.exp.is_complex, self.exp.is_extended_negative]) + + if all(a.is_complex for a in self.args) and self._eval_is_finite(): + return True + + def _eval_is_imaginary(self): + if self.base.is_commutative is False: + return False + + if self.base.is_imaginary: + if self.exp.is_integer: + odd = self.exp.is_odd + if odd is not None: + return odd + return + + if self.base == S.Exp1: + f = 2 * self.exp / (S.Pi*S.ImaginaryUnit) + # exp(pi*integer) = 1 or -1, so not imaginary + if f.is_even: + return False + # exp(pi*integer + pi/2) = I or -I, so it is imaginary + if f.is_odd: + return True + return None + + if self.exp.is_imaginary: + from sympy.functions.elementary.exponential import log + imlog = log(self.base).is_imaginary + if imlog is not None: + return False # I**i -> real; (2*I)**i -> complex ==> not imaginary + + if self.base.is_extended_real and self.exp.is_extended_real: + if self.base.is_positive: + return False + else: + rat = self.exp.is_rational + if not rat: + return rat + if self.exp.is_integer: + return False + else: + half = (2*self.exp).is_integer + if half: + return self.base.is_negative + return half + + if self.base.is_extended_real is False: # we already know it's not imag + from sympy.functions.elementary.complexes import arg + i = arg(self.base)*self.exp/S.Pi + isodd = (2*i).is_odd + if isodd is not None: + return isodd + + def _eval_is_odd(self): + if self.exp.is_integer: + if self.exp.is_positive: + return self.base.is_odd + elif self.exp.is_nonnegative and self.base.is_odd: + return True + elif self.base is S.NegativeOne: + return True + + def _eval_is_finite(self): + if self.exp.is_negative: + if self.base.is_zero: + return False + if self.base.is_infinite or self.base.is_nonzero: + return True + c1 = self.base.is_finite + if c1 is None: + return + c2 = self.exp.is_finite + if c2 is None: + return + if c1 and c2: + if self.exp.is_nonnegative or fuzzy_not(self.base.is_zero): + return True + + def _eval_is_prime(self): + ''' + An integer raised to the n(>=2)-th power cannot be a prime. + ''' + if self.base.is_integer and self.exp.is_integer and (self.exp - 1).is_positive: + return False + + def _eval_is_composite(self): + """ + A power is composite if both base and exponent are greater than 1 + """ + if (self.base.is_integer and self.exp.is_integer and + ((self.base - 1).is_positive and (self.exp - 1).is_positive or + (self.base + 1).is_negative and self.exp.is_positive and self.exp.is_even)): + return True + + def _eval_is_polar(self): + return self.base.is_polar + + def _eval_subs(self, old, new): + from sympy.calculus.accumulationbounds import AccumBounds + + if isinstance(self.exp, AccumBounds): + b = self.base.subs(old, new) + e = self.exp.subs(old, new) + if isinstance(e, AccumBounds): + return e.__rpow__(b) + return self.func(b, e) + + from sympy.functions.elementary.exponential import exp, log + + def _check(ct1, ct2, old): + """Return (bool, pow, remainder_pow) where, if bool is True, then the + exponent of Pow `old` will combine with `pow` so the substitution + is valid, otherwise bool will be False. + + For noncommutative objects, `pow` will be an integer, and a factor + `Pow(old.base, remainder_pow)` needs to be included. If there is + no such factor, None is returned. For commutative objects, + remainder_pow is always None. + + cti are the coefficient and terms of an exponent of self or old + In this _eval_subs routine a change like (b**(2*x)).subs(b**x, y) + will give y**2 since (b**x)**2 == b**(2*x); if that equality does + not hold then the substitution should not occur so `bool` will be + False. + + """ + coeff1, terms1 = ct1 + coeff2, terms2 = ct2 + if terms1 == terms2: + if old.is_commutative: + # Allow fractional powers for commutative objects + pow = coeff1/coeff2 + try: + as_int(pow, strict=False) + combines = True + except ValueError: + b, e = old.as_base_exp() + # These conditions ensure that (b**e)**f == b**(e*f) for any f + combines = b.is_positive and e.is_real or b.is_nonnegative and e.is_nonnegative + + return combines, pow, None + else: + # With noncommutative symbols, substitute only integer powers + if not isinstance(terms1, tuple): + terms1 = (terms1,) + if not all(term.is_integer for term in terms1): + return False, None, None + + try: + # Round pow toward zero + pow, remainder = divmod(as_int(coeff1), as_int(coeff2)) + if pow < 0 and remainder != 0: + pow += 1 + remainder -= as_int(coeff2) + + if remainder == 0: + remainder_pow = None + else: + remainder_pow = Mul(remainder, *terms1) + + return True, pow, remainder_pow + except ValueError: + # Can't substitute + pass + + return False, None, None + + if old == self.base or (old == exp and self.base == S.Exp1): + if new.is_Function and isinstance(new, Callable): + return new(self.exp._subs(old, new)) + else: + return new**self.exp._subs(old, new) + + # issue 10829: (4**x - 3*y + 2).subs(2**x, y) -> y**2 - 3*y + 2 + if isinstance(old, self.func) and self.exp == old.exp: + l = log(self.base, old.base) + if l.is_Number: + return Pow(new, l) + + if isinstance(old, self.func) and self.base == old.base: + if self.exp.is_Add is False: + ct1 = self.exp.as_independent(Symbol, as_Add=False) + ct2 = old.exp.as_independent(Symbol, as_Add=False) + ok, pow, remainder_pow = _check(ct1, ct2, old) + if ok: + # issue 5180: (x**(6*y)).subs(x**(3*y),z)->z**2 + result = self.func(new, pow) + if remainder_pow is not None: + result = Mul(result, Pow(old.base, remainder_pow)) + return result + else: # b**(6*x + a).subs(b**(3*x), y) -> y**2 * b**a + # exp(exp(x) + exp(x**2)).subs(exp(exp(x)), w) -> w * exp(exp(x**2)) + oarg = old.exp + new_l = [] + o_al = [] + ct2 = oarg.as_coeff_mul() + for a in self.exp.args: + newa = a._subs(old, new) + ct1 = newa.as_coeff_mul() + ok, pow, remainder_pow = _check(ct1, ct2, old) + if ok: + new_l.append(new**pow) + if remainder_pow is not None: + o_al.append(remainder_pow) + continue + elif not old.is_commutative and not newa.is_integer: + # If any term in the exponent is non-integer, + # we do not do any substitutions in the noncommutative case + return + o_al.append(newa) + if new_l: + expo = Add(*o_al) + new_l.append(Pow(self.base, expo, evaluate=False) if expo != 1 else self.base) + return Mul(*new_l) + + if (isinstance(old, exp) or (old.is_Pow and old.base is S.Exp1)) and self.exp.is_extended_real and self.base.is_positive: + ct1 = old.exp.as_independent(Symbol, as_Add=False) + ct2 = (self.exp*log(self.base)).as_independent( + Symbol, as_Add=False) + ok, pow, remainder_pow = _check(ct1, ct2, old) + if ok: + result = self.func(new, pow) # (2**x).subs(exp(x*log(2)), z) -> z + if remainder_pow is not None: + result = Mul(result, Pow(old.base, remainder_pow)) + return result + + def as_base_exp(self): + """Return base and exp of self. + + Explanation + =========== + + If base a Rational less than 1, then return 1/Rational, -exp. + If this extra processing is not needed, the base and exp + properties will give the raw arguments. + + Examples + ======== + + >>> from sympy import Pow, S + >>> p = Pow(S.Half, 2, evaluate=False) + >>> p.as_base_exp() + (2, -2) + >>> p.args + (1/2, 2) + >>> p.base, p.exp + (1/2, 2) + + """ + b, e = self.args + if b.is_Rational and b.p == 1 and b.q != 1: + return Integer(b.q), -e + return b, e + + def _eval_adjoint(self): + from sympy.functions.elementary.complexes import adjoint + i, p = self.exp.is_integer, self.base.is_positive + if i: + return adjoint(self.base)**self.exp + if p: + return self.base**adjoint(self.exp) + if i is False and p is False: + expanded = expand_complex(self) + if expanded != self: + return adjoint(expanded) + + def _eval_conjugate(self): + from sympy.functions.elementary.complexes import conjugate as c + i, p = self.exp.is_integer, self.base.is_positive + if i: + return c(self.base)**self.exp + if p: + return self.base**c(self.exp) + if i is False and p is False: + expanded = expand_complex(self) + if expanded != self: + return c(expanded) + if self.is_extended_real: + return self + + def _eval_transpose(self): + from sympy.functions.elementary.complexes import transpose + if self.base == S.Exp1: + return self.func(S.Exp1, self.exp.transpose()) + i, p = self.exp.is_integer, (self.base.is_complex or self.base.is_infinite) + if p: + return self.base**self.exp + if i: + return transpose(self.base)**self.exp + if i is False and p is False: + expanded = expand_complex(self) + if expanded != self: + return transpose(expanded) + + def _eval_expand_power_exp(self, **hints): + """a**(n + m) -> a**n*a**m""" + b = self.base + e = self.exp + if b == S.Exp1: + from sympy.concrete.summations import Sum + if isinstance(e, Sum) and e.is_commutative: + from sympy.concrete.products import Product + return Product(self.func(b, e.function), *e.limits) + if e.is_Add and (hints.get('force', False) or + b.is_zero is False or e._all_nonneg_or_nonppos()): + if e.is_commutative: + return Mul(*[self.func(b, x) for x in e.args]) + if b.is_commutative: + c, nc = sift(e.args, lambda x: x.is_commutative, binary=True) + if c: + return Mul(*[self.func(b, x) for x in c] + )*b**Add._from_args(nc) + return self + + def _eval_expand_power_base(self, **hints): + """(a*b)**n -> a**n * b**n""" + force = hints.get('force', False) + + b = self.base + e = self.exp + if not b.is_Mul: + return self + + cargs, nc = b.args_cnc(split_1=False) + + # expand each term - this is top-level-only + # expansion but we have to watch out for things + # that don't have an _eval_expand method + if nc: + nc = [i._eval_expand_power_base(**hints) + if hasattr(i, '_eval_expand_power_base') else i + for i in nc] + + if e.is_Integer: + if e.is_positive: + rv = Mul(*nc*e) + else: + rv = Mul(*[i**-1 for i in nc[::-1]]*-e) + if cargs: + rv *= Mul(*cargs)**e + return rv + + if not cargs: + return self.func(Mul(*nc), e, evaluate=False) + + nc = [Mul(*nc)] + + # sift the commutative bases + other, maybe_real = sift(cargs, lambda x: x.is_extended_real is False, + binary=True) + def pred(x): + if x is S.ImaginaryUnit: + return S.ImaginaryUnit + polar = x.is_polar + if polar: + return True + if polar is None: + return fuzzy_bool(x.is_extended_nonnegative) + sifted = sift(maybe_real, pred) + nonneg = sifted[True] + other += sifted[None] + neg = sifted[False] + imag = sifted[S.ImaginaryUnit] + if imag: + I = S.ImaginaryUnit + i = len(imag) % 4 + if i == 0: + pass + elif i == 1: + other.append(I) + elif i == 2: + if neg: + nonn = -neg.pop() + if nonn is not S.One: + nonneg.append(nonn) + else: + neg.append(S.NegativeOne) + else: + if neg: + nonn = -neg.pop() + if nonn is not S.One: + nonneg.append(nonn) + else: + neg.append(S.NegativeOne) + other.append(I) + del imag + + # bring out the bases that can be separated from the base + + if force or e.is_integer: + # treat all commutatives the same and put nc in other + cargs = nonneg + neg + other + other = nc + else: + # this is just like what is happening automatically, except + # that now we are doing it for an arbitrary exponent for which + # no automatic expansion is done + + assert not e.is_Integer + + # handle negatives by making them all positive and putting + # the residual -1 in other + if len(neg) > 1: + o = S.One + if not other and neg[0].is_Number: + o *= neg.pop(0) + if len(neg) % 2: + o = -o + for n in neg: + nonneg.append(-n) + if o is not S.One: + other.append(o) + elif neg and other: + if neg[0].is_Number and neg[0] is not S.NegativeOne: + other.append(S.NegativeOne) + nonneg.append(-neg[0]) + else: + other.extend(neg) + else: + other.extend(neg) + del neg + + cargs = nonneg + other += nc + + rv = S.One + if cargs: + if e.is_Rational: + npow, cargs = sift(cargs, lambda x: x.is_Pow and + x.exp.is_Rational and x.base.is_number, + binary=True) + rv = Mul(*[self.func(b.func(*b.args), e) for b in npow]) + rv *= Mul(*[self.func(b, e, evaluate=False) for b in cargs]) + if other: + rv *= self.func(Mul(*other), e, evaluate=False) + return rv + + def _eval_expand_multinomial(self, **hints): + """(a + b + ..)**n -> a**n + n*a**(n-1)*b + .., n is nonzero integer""" + + base, exp = self.args + result = self + + if exp.is_Rational and exp.p > 0 and base.is_Add: + if not exp.is_Integer: + n = Integer(exp.p // exp.q) + + if not n: + return result + else: + radical, result = self.func(base, exp - n), [] + + expanded_base_n = self.func(base, n) + if expanded_base_n.is_Pow: + expanded_base_n = \ + expanded_base_n._eval_expand_multinomial() + for term in Add.make_args(expanded_base_n): + result.append(term*radical) + + return Add(*result) + + n = int(exp) + + if base.is_commutative: + order_terms, other_terms = [], [] + + for b in base.args: + if b.is_Order: + order_terms.append(b) + else: + other_terms.append(b) + + if order_terms: + # (f(x) + O(x^n))^m -> f(x)^m + m*f(x)^{m-1} *O(x^n) + f = Add(*other_terms) + o = Add(*order_terms) + + if n == 2: + return expand_multinomial(f**n, deep=False) + n*f*o + else: + g = expand_multinomial(f**(n - 1), deep=False) + return expand_mul(f*g, deep=False) + n*g*o + + if base.is_number: + # Efficiently expand expressions of the form (a + b*I)**n + # where 'a' and 'b' are real numbers and 'n' is integer. + a, b = base.as_real_imag() + + if a.is_Rational and b.is_Rational: + if not a.is_Integer: + if not b.is_Integer: + k = self.func(a.q * b.q, n) + a, b = a.p*b.q, a.q*b.p + else: + k = self.func(a.q, n) + a, b = a.p, a.q*b + elif not b.is_Integer: + k = self.func(b.q, n) + a, b = a*b.q, b.p + else: + k = 1 + + a, b, c, d = int(a), int(b), 1, 0 + + while n: + if n & 1: + c, d = a*c - b*d, b*c + a*d + n -= 1 + a, b = a*a - b*b, 2*a*b + n //= 2 + + I = S.ImaginaryUnit + + if k == 1: + return c + I*d + else: + return Integer(c)/k + I*d/k + + p = other_terms + # (x + y)**3 -> x**3 + 3*x**2*y + 3*x*y**2 + y**3 + # in this particular example: + # p = [x,y]; n = 3 + # so now it's easy to get the correct result -- we get the + # coefficients first: + from sympy.ntheory.multinomial import multinomial_coefficients + from sympy.polys.polyutils import basic_from_dict + expansion_dict = multinomial_coefficients(len(p), n) + # in our example: {(3, 0): 1, (1, 2): 3, (0, 3): 1, (2, 1): 3} + # and now construct the expression. + return basic_from_dict(expansion_dict, *p) + else: + if n == 2: + return Add(*[f*g for f in base.args for g in base.args]) + else: + multi = (base**(n - 1))._eval_expand_multinomial() + if multi.is_Add: + return Add(*[f*g for f in base.args + for g in multi.args]) + else: + # XXX can this ever happen if base was an Add? + return Add(*[f*multi for f in base.args]) + elif (exp.is_Rational and exp.p < 0 and base.is_Add and + abs(exp.p) > exp.q): + return 1 / self.func(base, -exp)._eval_expand_multinomial() + elif exp.is_Add and base.is_Number and (hints.get('force', False) or + base.is_zero is False or exp._all_nonneg_or_nonppos()): + # a + b a b + # n --> n n, where n, a, b are Numbers + # XXX should be in expand_power_exp? + coeff, tail = [], [] + for term in exp.args: + if term.is_Number: + coeff.append(self.func(base, term)) + else: + tail.append(term) + return Mul(*(coeff + [self.func(base, Add._from_args(tail))])) + else: + return result + + def as_real_imag(self, deep=True, **hints): + if self.exp.is_Integer: + from sympy.polys.polytools import poly + + exp = self.exp + re_e, im_e = self.base.as_real_imag(deep=deep) + if not im_e: + return self, S.Zero + a, b = symbols('a b', cls=Dummy) + if exp >= 0: + if re_e.is_Number and im_e.is_Number: + # We can be more efficient in this case + expr = expand_multinomial(self.base**exp) + if expr != self: + return expr.as_real_imag() + + expr = poly( + (a + b)**exp) # a = re, b = im; expr = (a + b*I)**exp + else: + mag = re_e**2 + im_e**2 + re_e, im_e = re_e/mag, -im_e/mag + if re_e.is_Number and im_e.is_Number: + # We can be more efficient in this case + expr = expand_multinomial((re_e + im_e*S.ImaginaryUnit)**-exp) + if expr != self: + return expr.as_real_imag() + + expr = poly((a + b)**-exp) + + # Terms with even b powers will be real + r = [i for i in expr.terms() if not i[0][1] % 2] + re_part = Add(*[cc*a**aa*b**bb for (aa, bb), cc in r]) + # Terms with odd b powers will be imaginary + r = [i for i in expr.terms() if i[0][1] % 4 == 1] + im_part1 = Add(*[cc*a**aa*b**bb for (aa, bb), cc in r]) + r = [i for i in expr.terms() if i[0][1] % 4 == 3] + im_part3 = Add(*[cc*a**aa*b**bb for (aa, bb), cc in r]) + + return (re_part.subs({a: re_e, b: S.ImaginaryUnit*im_e}), + im_part1.subs({a: re_e, b: im_e}) + im_part3.subs({a: re_e, b: -im_e})) + + from sympy.functions.elementary.trigonometric import atan2, cos, sin + + if self.exp.is_Rational: + re_e, im_e = self.base.as_real_imag(deep=deep) + + if im_e.is_zero and self.exp is S.Half: + if re_e.is_extended_nonnegative: + return self, S.Zero + if re_e.is_extended_nonpositive: + return S.Zero, (-self.base)**self.exp + + # XXX: This is not totally correct since for x**(p/q) with + # x being imaginary there are actually q roots, but + # only a single one is returned from here. + r = self.func(self.func(re_e, 2) + self.func(im_e, 2), S.Half) + + t = atan2(im_e, re_e) + + rp, tp = self.func(r, self.exp), t*self.exp + + return rp*cos(tp), rp*sin(tp) + elif self.base is S.Exp1: + from sympy.functions.elementary.exponential import exp + re_e, im_e = self.exp.as_real_imag() + if deep: + re_e = re_e.expand(deep, **hints) + im_e = im_e.expand(deep, **hints) + c, s = cos(im_e), sin(im_e) + return exp(re_e)*c, exp(re_e)*s + else: + from sympy.functions.elementary.complexes import im, re + if deep: + hints['complex'] = False + + expanded = self.expand(deep, **hints) + if hints.get('ignore') == expanded: + return None + else: + return (re(expanded), im(expanded)) + else: + return re(self), im(self) + + def _eval_derivative(self, s): + from sympy.functions.elementary.exponential import log + dbase = self.base.diff(s) + dexp = self.exp.diff(s) + return self * (dexp * log(self.base) + dbase * self.exp/self.base) + + def _eval_evalf(self, prec): + base, exp = self.as_base_exp() + if base == S.Exp1: + # Use mpmath function associated to class "exp": + from sympy.functions.elementary.exponential import exp as exp_function + return exp_function(self.exp, evaluate=False)._eval_evalf(prec) + base = base._evalf(prec) + if not exp.is_Integer: + exp = exp._evalf(prec) + if exp.is_negative and base.is_number and base.is_extended_real is False: + base = base.conjugate() / (base * base.conjugate())._evalf(prec) + exp = -exp + return self.func(base, exp).expand() + return self.func(base, exp) + + def _eval_is_polynomial(self, syms): + if self.exp.has(*syms): + return False + + if self.base.has(*syms): + return bool(self.base._eval_is_polynomial(syms) and + self.exp.is_Integer and (self.exp >= 0)) + else: + return True + + def _eval_is_rational(self): + # The evaluation of self.func below can be very expensive in the case + # of integer**integer if the exponent is large. We should try to exit + # before that if possible: + if (self.exp.is_integer and self.base.is_rational + and fuzzy_not(fuzzy_and([self.exp.is_negative, self.base.is_zero]))): + return True + p = self.func(*self.as_base_exp()) # in case it's unevaluated + if not p.is_Pow: + return p.is_rational + b, e = p.as_base_exp() + if e.is_Rational and b.is_Rational: + # we didn't check that e is not an Integer + # because Rational**Integer autosimplifies + return False + if e.is_integer: + if b.is_rational: + if fuzzy_not(b.is_zero) or e.is_nonnegative: + return True + if b == e: # always rational, even for 0**0 + return True + elif b.is_irrational: + return e.is_zero + if b is S.Exp1: + if e.is_rational and e.is_nonzero: + return False + + def _eval_is_algebraic(self): + def _is_one(expr): + try: + return (expr - 1).is_zero + except ValueError: + # when the operation is not allowed + return False + + if self.base.is_zero or _is_one(self.base): + return True + elif self.base is S.Exp1: + s = self.func(*self.args) + if s.func == self.func: + if self.exp.is_nonzero: + if self.exp.is_algebraic: + return False + elif (self.exp/S.Pi).is_rational: + return False + elif (self.exp/(S.ImaginaryUnit*S.Pi)).is_rational: + return True + else: + return s.is_algebraic + elif self.exp.is_rational: + if self.base.is_algebraic is False: + return self.exp.is_zero + if self.base.is_zero is False: + if self.exp.is_nonzero: + return self.base.is_algebraic + elif self.base.is_algebraic: + return True + if self.exp.is_positive: + return self.base.is_algebraic + elif self.base.is_algebraic and self.exp.is_algebraic: + if ((fuzzy_not(self.base.is_zero) + and fuzzy_not(_is_one(self.base))) + or self.base.is_integer is False + or self.base.is_irrational): + return self.exp.is_rational + + def _eval_is_rational_function(self, syms): + if self.exp.has(*syms): + return False + + if self.base.has(*syms): + return self.base._eval_is_rational_function(syms) and \ + self.exp.is_Integer + else: + return True + + def _eval_is_meromorphic(self, x, a): + # f**g is meromorphic if g is an integer and f is meromorphic. + # E**(log(f)*g) is meromorphic if log(f)*g is meromorphic + # and finite. + base_merom = self.base._eval_is_meromorphic(x, a) + exp_integer = self.exp.is_Integer + if exp_integer: + return base_merom + + exp_merom = self.exp._eval_is_meromorphic(x, a) + if base_merom is False: + # f**g = E**(log(f)*g) may be meromorphic if the + # singularities of log(f) and g cancel each other, + # for example, if g = 1/log(f). Hence, + return False if exp_merom else None + elif base_merom is None: + return None + + b = self.base.subs(x, a) + # b is extended complex as base is meromorphic. + # log(base) is finite and meromorphic when b != 0, zoo. + b_zero = b.is_zero + if b_zero: + log_defined = False + else: + log_defined = fuzzy_and((b.is_finite, fuzzy_not(b_zero))) + + if log_defined is False: # zero or pole of base + return exp_integer # False or None + elif log_defined is None: + return None + + if not exp_merom: + return exp_merom # False or None + + return self.exp.subs(x, a).is_finite + + def _eval_is_algebraic_expr(self, syms): + if self.exp.has(*syms): + return False + + if self.base.has(*syms): + return self.base._eval_is_algebraic_expr(syms) and \ + self.exp.is_Rational + else: + return True + + def _eval_rewrite_as_exp(self, base, expo, **kwargs): + from sympy.functions.elementary.exponential import exp, log + + if base.is_zero or base.has(exp) or expo.has(exp): + return base**expo + + evaluate = expo.has(Symbol) + + if base.has(Symbol): + # delay evaluation if expo is non symbolic + # (as exp(x*log(5)) automatically reduces to x**5) + if global_parameters.exp_is_pow: + return Pow(S.Exp1, log(base)*expo, evaluate=evaluate) + else: + return exp(log(base)*expo, evaluate=evaluate) + + else: + from sympy.functions.elementary.complexes import arg, Abs + return exp((log(Abs(base)) + S.ImaginaryUnit*arg(base))*expo) + + def as_numer_denom(self): + if not self.is_commutative: + return self, S.One + base, exp = self.as_base_exp() + n, d = base.as_numer_denom() + # this should be the same as ExpBase.as_numer_denom wrt + # exponent handling + neg_exp = exp.is_negative + if exp.is_Mul and not neg_exp and not exp.is_positive: + neg_exp = exp.could_extract_minus_sign() + int_exp = exp.is_integer + # the denominator cannot be separated from the numerator if + # its sign is unknown unless the exponent is an integer, e.g. + # sqrt(a/b) != sqrt(a)/sqrt(b) when a=1 and b=-1. But if the + # denominator is negative the numerator and denominator can + # be negated and the denominator (now positive) separated. + if not (d.is_extended_real or int_exp): + n = base + d = S.One + dnonpos = d.is_nonpositive + if dnonpos: + n, d = -n, -d + elif dnonpos is None and not int_exp: + n = base + d = S.One + if neg_exp: + n, d = d, n + exp = -exp + if exp.is_infinite: + if n is S.One and d is not S.One: + return n, self.func(d, exp) + if n is not S.One and d is S.One: + return self.func(n, exp), d + return self.func(n, exp), self.func(d, exp) + + def matches(self, expr, repl_dict=None, old=False): + expr = _sympify(expr) + if repl_dict is None: + repl_dict = {} + + # special case, pattern = 1 and expr.exp can match to 0 + if expr is S.One: + d = self.exp.matches(S.Zero, repl_dict) + if d is not None: + return d + + # make sure the expression to be matched is an Expr + if not isinstance(expr, Expr): + return None + + b, e = expr.as_base_exp() + + # special case number + sb, se = self.as_base_exp() + if sb.is_Symbol and se.is_Integer and expr: + if e.is_rational: + return sb.matches(b**(e/se), repl_dict) + return sb.matches(expr**(1/se), repl_dict) + + d = repl_dict.copy() + d = self.base.matches(b, d) + if d is None: + return None + + d = self.exp.xreplace(d).matches(e, d) + if d is None: + return Expr.matches(self, expr, repl_dict) + return d + + def _eval_nseries(self, x, n, logx, cdir=0): + # NOTE! This function is an important part of the gruntz algorithm + # for computing limits. It has to return a generalized power + # series with coefficients in C(log, log(x)). In more detail: + # It has to return an expression + # c_0*x**e_0 + c_1*x**e_1 + ... (finitely many terms) + # where e_i are numbers (not necessarily integers) and c_i are + # expressions involving only numbers, the log function, and log(x). + # The series expansion of b**e is computed as follows: + # 1) We express b as f*(1 + g) where f is the leading term of b. + # g has order O(x**d) where d is strictly positive. + # 2) Then b**e = (f**e)*((1 + g)**e). + # (1 + g)**e is computed using binomial series. + from sympy.functions.elementary.exponential import exp, log + from sympy.series.limits import limit + from sympy.series.order import Order + from sympy.core.sympify import sympify + if self.base is S.Exp1: + e_series = self.exp.nseries(x, n=n, logx=logx) + if e_series.is_Order: + return 1 + e_series + e0 = limit(e_series.removeO(), x, 0) + if e0 is S.NegativeInfinity: + return Order(x**n, x) + if e0 is S.Infinity: + return self + t = e_series - e0 + exp_series = term = exp(e0) + # series of exp(e0 + t) in t + for i in range(1, n): + term *= t/i + term = term.nseries(x, n=n, logx=logx) + exp_series += term + exp_series += Order(t**n, x) + from sympy.simplify.powsimp import powsimp + return powsimp(exp_series, deep=True, combine='exp') + from sympy.simplify.powsimp import powdenest + from .numbers import _illegal + self = powdenest(self, force=True).trigsimp() + b, e = self.as_base_exp() + + if e.has(*_illegal): + raise PoleError() + + if e.has(x): + return exp(e*log(b))._eval_nseries(x, n=n, logx=logx, cdir=cdir) + + if logx is not None and b.has(log): + from .symbol import Wild + c, ex = symbols('c, ex', cls=Wild, exclude=[x]) + b = b.replace(log(c*x**ex), log(c) + ex*logx) + self = b**e + + b = b.removeO() + try: + from sympy.functions.special.gamma_functions import polygamma + if b.has(polygamma, S.EulerGamma) and logx is not None: + raise ValueError() + _, m = b.leadterm(x) + except (ValueError, NotImplementedError, PoleError): + b = b._eval_nseries(x, n=max(2, n), logx=logx, cdir=cdir).removeO() + if b.has(S.NaN, S.ComplexInfinity): + raise NotImplementedError() + _, m = b.leadterm(x) + + if e.has(log): + from sympy.simplify.simplify import logcombine + e = logcombine(e).cancel() + + if not (m.is_zero or e.is_number and e.is_real): + if self == self._eval_as_leading_term(x, logx=logx, cdir=cdir): + res = exp(e*log(b))._eval_nseries(x, n=n, logx=logx, cdir=cdir) + if res == exp(e*log(b)): + return self + return res + + f = b.as_leading_term(x, logx=logx) + g = (_mexpand(b) - f).cancel() + g = g/f + if not m.is_number: + raise NotImplementedError() + maxpow = n - m*e + if maxpow.has(Symbol): + maxpow = sympify(n) + + if maxpow.is_negative: + return Order(x**(m*e), x) + + if g.is_zero: + r = f**e + if r != self: + r += Order(x**n, x) + return r + + def coeff_exp(term, x): + coeff, exp = S.One, S.Zero + for factor in Mul.make_args(term): + if factor.has(x): + base, exp = factor.as_base_exp() + if base != x: + try: + return term.leadterm(x) + except ValueError: + return term, S.Zero + else: + coeff *= factor + return coeff, exp + + def mul(d1, d2): + res = {} + for e1, e2 in product(d1, d2): + ex = e1 + e2 + if ex < maxpow: + res[ex] = res.get(ex, S.Zero) + d1[e1]*d2[e2] + return res + + try: + c, d = g.leadterm(x, logx=logx) + except (ValueError, NotImplementedError): + if limit(g/x**maxpow, x, 0) == 0: + # g has higher order zero + return f**e + e*f**e*g # first term of binomial series + else: + raise NotImplementedError() + if c.is_Float and d == S.Zero: + # Convert floats like 0.5 to exact SymPy numbers like S.Half, to + # prevent rounding errors which can induce wrong values of d leading + # to a NotImplementedError being returned from the block below. + g = g.replace(lambda x: x.is_Float, lambda x: Rational(x)) + _, d = g.leadterm(x, logx=logx) + if not d.is_positive: + g = g.simplify() + if g.is_zero: + return f**e + _, d = g.leadterm(x, logx=logx) + if not d.is_positive: + g = ((b - f)/f).expand() + _, d = g.leadterm(x, logx=logx) + if not d.is_positive: + raise NotImplementedError() + + from sympy.functions.elementary.integers import ceiling + gpoly = g._eval_nseries(x, n=ceiling(maxpow), logx=logx, cdir=cdir).removeO() + gterms = {} + + for term in Add.make_args(gpoly): + co1, e1 = coeff_exp(term, x) + gterms[e1] = gterms.get(e1, S.Zero) + co1 + + k = S.One + terms = {S.Zero: S.One} + tk = gterms + + from sympy.functions.combinatorial.factorials import factorial, ff + + while (k*d - maxpow).is_negative: + coeff = ff(e, k)/factorial(k) + for ex in tk: + terms[ex] = terms.get(ex, S.Zero) + coeff*tk[ex] + tk = mul(tk, gterms) + k += S.One + + from sympy.functions.elementary.complexes import im + + if not e.is_integer and m.is_zero and f.is_negative: + ndir = (b - f).dir(x, cdir) + if im(ndir).is_negative: + inco, inex = coeff_exp(f**e*(-1)**(-2*e), x) + elif im(ndir).is_zero: + inco, inex = coeff_exp(exp(e*log(b)).as_leading_term(x, logx=logx, cdir=cdir), x) + else: + inco, inex = coeff_exp(f**e, x) + else: + inco, inex = coeff_exp(f**e, x) + res = S.Zero + + for e1 in terms: + ex = e1 + inex + res += terms[e1]*inco*x**(ex) + + if not (e.is_integer and e.is_positive and (e*d - n).is_nonpositive and + res == _mexpand(self)): + try: + res += Order(x**n, x) + except NotImplementedError: + return exp(e*log(b))._eval_nseries(x, n=n, logx=logx, cdir=cdir) + return res + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.functions.elementary.exponential import exp, log + e = self.exp + b = self.base + if self.base is S.Exp1: + arg = e.as_leading_term(x, logx=logx) + arg0 = arg.subs(x, 0) + if arg0 is S.NaN: + arg0 = arg.limit(x, 0) + if arg0.is_infinite is False: + return S.Exp1**arg0 + raise PoleError("Cannot expand %s around 0" % (self)) + elif e.has(x): + lt = exp(e * log(b)) + return lt.as_leading_term(x, logx=logx, cdir=cdir) + else: + from sympy.functions.elementary.complexes import im + try: + f = b.as_leading_term(x, logx=logx, cdir=cdir) + except PoleError: + return self + if not e.is_integer and f.is_negative and not f.has(x): + ndir = (b - f).dir(x, cdir) + if im(ndir).is_negative: + # Normally, f**e would evaluate to exp(e*log(f)) but on branch cuts + # an other value is expected through the following computation + # exp(e*(log(f) - 2*pi*I)) == f**e*exp(-2*e*pi*I) == f**e*(-1)**(-2*e). + return self.func(f, e) * (-1)**(-2*e) + elif im(ndir).is_zero: + log_leadterm = log(b)._eval_as_leading_term(x, logx=logx, cdir=cdir) + if log_leadterm.is_infinite is False: + return exp(e*log_leadterm) + return self.func(f, e) + + @cacheit + def _taylor_term(self, n, x, *previous_terms): # of (1 + x)**e + from sympy.functions.combinatorial.factorials import binomial + return binomial(self.exp, n) * self.func(x, n) + + def taylor_term(self, n, x, *previous_terms): + if self.base is not S.Exp1: + return super().taylor_term(n, x, *previous_terms) + if n < 0: + return S.Zero + if n == 0: + return S.One + from .sympify import sympify + x = sympify(x) + if previous_terms: + p = previous_terms[-1] + if p is not None: + return p * x / n + from sympy.functions.combinatorial.factorials import factorial + return x**n/factorial(n) + + def _eval_rewrite_as_sin(self, base, exp, **hints): + if self.base is S.Exp1: + from sympy.functions.elementary.trigonometric import sin + return sin(S.ImaginaryUnit*self.exp + S.Pi/2) - S.ImaginaryUnit*sin(S.ImaginaryUnit*self.exp) + + def _eval_rewrite_as_cos(self, base, exp, **hints): + if self.base is S.Exp1: + from sympy.functions.elementary.trigonometric import cos + return cos(S.ImaginaryUnit*self.exp) + S.ImaginaryUnit*cos(S.ImaginaryUnit*self.exp + S.Pi/2) + + def _eval_rewrite_as_tanh(self, base, exp, **hints): + if self.base is S.Exp1: + from sympy.functions.elementary.hyperbolic import tanh + return (1 + tanh(self.exp/2))/(1 - tanh(self.exp/2)) + + def _eval_rewrite_as_sqrt(self, base, exp, **kwargs): + from sympy.functions.elementary.trigonometric import sin, cos + if base is not S.Exp1: + return None + if exp.is_Mul: + coeff = exp.coeff(S.Pi * S.ImaginaryUnit) + if coeff and coeff.is_number: + cosine, sine = cos(S.Pi*coeff), sin(S.Pi*coeff) + if not isinstance(cosine, cos) and not isinstance (sine, sin): + return cosine + S.ImaginaryUnit*sine + + def as_content_primitive(self, radical=False, clear=True): + """Return the tuple (R, self/R) where R is the positive Rational + extracted from self. + + Examples + ======== + + >>> from sympy import sqrt + >>> sqrt(4 + 4*sqrt(2)).as_content_primitive() + (2, sqrt(1 + sqrt(2))) + >>> sqrt(3 + 3*sqrt(2)).as_content_primitive() + (1, sqrt(3)*sqrt(1 + sqrt(2))) + + >>> from sympy import expand_power_base, powsimp, Mul + >>> from sympy.abc import x, y + + >>> ((2*x + 2)**2).as_content_primitive() + (4, (x + 1)**2) + >>> (4**((1 + y)/2)).as_content_primitive() + (2, 4**(y/2)) + >>> (3**((1 + y)/2)).as_content_primitive() + (1, 3**((y + 1)/2)) + >>> (3**((5 + y)/2)).as_content_primitive() + (9, 3**((y + 1)/2)) + >>> eq = 3**(2 + 2*x) + >>> powsimp(eq) == eq + True + >>> eq.as_content_primitive() + (9, 3**(2*x)) + >>> powsimp(Mul(*_)) + 3**(2*x + 2) + + >>> eq = (2 + 2*x)**y + >>> s = expand_power_base(eq); s.is_Mul, s + (False, (2*x + 2)**y) + >>> eq.as_content_primitive() + (1, (2*(x + 1))**y) + >>> s = expand_power_base(_[1]); s.is_Mul, s + (True, 2**y*(x + 1)**y) + + See docstring of Expr.as_content_primitive for more examples. + """ + + b, e = self.as_base_exp() + b = _keep_coeff(*b.as_content_primitive(radical=radical, clear=clear)) + ce, pe = e.as_content_primitive(radical=radical, clear=clear) + if b.is_Rational: + #e + #= ce*pe + #= ce*(h + t) + #= ce*h + ce*t + #=> self + #= b**(ce*h)*b**(ce*t) + #= b**(cehp/cehq)*b**(ce*t) + #= b**(iceh + r/cehq)*b**(ce*t) + #= b**(iceh)*b**(r/cehq)*b**(ce*t) + #= b**(iceh)*b**(ce*t + r/cehq) + h, t = pe.as_coeff_Add() + if h.is_Rational and b != S.Zero: + ceh = ce*h + c = self.func(b, ceh) + r = S.Zero + if not c.is_Rational: + iceh, r = divmod(ceh.p, ceh.q) + c = self.func(b, iceh) + return c, self.func(b, _keep_coeff(ce, t + r/ce/ceh.q)) + e = _keep_coeff(ce, pe) + # b**e = (h*t)**e = h**e*t**e = c*m*t**e + if e.is_Rational and b.is_Mul: + h, t = b.as_content_primitive(radical=radical, clear=clear) # h is positive + c, m = self.func(h, e).as_coeff_Mul() # so c is positive + m, me = m.as_base_exp() + if m is S.One or me == e: # probably always true + # return the following, not return c, m*Pow(t, e) + # which would change Pow into Mul; we let SymPy + # decide what to do by using the unevaluated Mul, e.g + # should it stay as sqrt(2 + 2*sqrt(5)) or become + # sqrt(2)*sqrt(1 + sqrt(5)) + return c, self.func(_keep_coeff(m, t), e) + return S.One, self.func(b, e) + + def is_constant(self, *wrt, **flags): + expr = self + if flags.get('simplify', True): + expr = expr.simplify() + b, e = expr.as_base_exp() + bz = b.equals(0) + if bz: # recalculate with assumptions in case it's unevaluated + new = b**e + if new != expr: + return new.is_constant() + econ = e.is_constant(*wrt) + bcon = b.is_constant(*wrt) + if bcon: + if econ: + return True + bz = b.equals(0) + if bz is False: + return False + elif bcon is None: + return None + + return e.equals(0) + + def _eval_difference_delta(self, n, step): + b, e = self.args + if e.has(n) and not b.has(n): + new_e = e.subs(n, n + step) + return (b**(new_e - e) - 1) * self + +power = Dispatcher('power') +power.add((object, object), Pow) + +from .add import Add +from .numbers import Integer, Rational +from .mul import Mul, _keep_coeff +from .symbol import Symbol, Dummy, symbols diff --git a/phivenv/Lib/site-packages/sympy/core/random.py b/phivenv/Lib/site-packages/sympy/core/random.py new file mode 100644 index 0000000000000000000000000000000000000000..c02986283523b39462a1e2c0b97e3fb230cff100 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/random.py @@ -0,0 +1,227 @@ +""" +When you need to use random numbers in SymPy library code, import from here +so there is only one generator working for SymPy. Imports from here should +behave the same as if they were being imported from Python's random module. +But only the routines currently used in SymPy are included here. To use others +import ``rng`` and access the method directly. For example, to capture the +current state of the generator use ``rng.getstate()``. + +There is intentionally no Random to import from here. If you want +to control the state of the generator, import ``seed`` and call it +with or without an argument to set the state. + +Examples +======== + +>>> from sympy.core.random import random, seed +>>> assert random() < 1 +>>> seed(1); a = random() +>>> b = random() +>>> seed(1); c = random() +>>> assert a == c +>>> assert a != b # remote possibility this will fail + +""" +from sympy.utilities.iterables import is_sequence +from sympy.utilities.misc import as_int + +import random as _random +rng = _random.Random() + +choice = rng.choice +random = rng.random +randint = rng.randint +randrange = rng.randrange +sample = rng.sample +# seed = rng.seed +shuffle = rng.shuffle +uniform = rng.uniform + +_assumptions_rng = _random.Random() +_assumptions_shuffle = _assumptions_rng.shuffle + + +def seed(a=None, version=2): + rng.seed(a=a, version=version) + _assumptions_rng.seed(a=a, version=version) + + +def random_complex_number(a=2, b=-1, c=3, d=1, rational=False, tolerance=None): + """ + Return a random complex number. + + To reduce chance of hitting branch cuts or anything, we guarantee + b <= Im z <= d, a <= Re z <= c + + When rational is True, a rational approximation to a random number + is obtained within specified tolerance, if any. + """ + from sympy.core.numbers import I + from sympy.simplify.simplify import nsimplify + A, B = uniform(a, c), uniform(b, d) + if not rational: + return A + I*B + return (nsimplify(A, rational=True, tolerance=tolerance) + + I*nsimplify(B, rational=True, tolerance=tolerance)) + + +def verify_numerically(f, g, z=None, tol=1.0e-6, a=2, b=-1, c=3, d=1): + """ + Test numerically that f and g agree when evaluated in the argument z. + + If z is None, all symbols will be tested. This routine does not test + whether there are Floats present with precision higher than 15 digits + so if there are, your results may not be what you expect due to round- + off errors. + + Examples + ======== + + >>> from sympy import sin, cos + >>> from sympy.abc import x + >>> from sympy.core.random import verify_numerically as tn + >>> tn(sin(x)**2 + cos(x)**2, 1, x) + True + """ + from sympy.core.symbol import Symbol + from sympy.core.sympify import sympify + from sympy.core.numbers import comp + f, g = (sympify(i) for i in (f, g)) + if z is None: + z = f.free_symbols | g.free_symbols + elif isinstance(z, Symbol): + z = [z] + reps = list(zip(z, [random_complex_number(a, b, c, d) for _ in z])) + z1 = f.subs(reps).n() + z2 = g.subs(reps).n() + return comp(z1, z2, tol) + + +def test_derivative_numerically(f, z, tol=1.0e-6, a=2, b=-1, c=3, d=1): + """ + Test numerically that the symbolically computed derivative of f + with respect to z is correct. + + This routine does not test whether there are Floats present with + precision higher than 15 digits so if there are, your results may + not be what you expect due to round-off errors. + + Examples + ======== + + >>> from sympy import sin + >>> from sympy.abc import x + >>> from sympy.core.random import test_derivative_numerically as td + >>> td(sin(x), x) + True + """ + from sympy.core.numbers import comp + from sympy.core.function import Derivative + z0 = random_complex_number(a, b, c, d) + f1 = f.diff(z).subs(z, z0) + f2 = Derivative(f, z).doit_numerically(z0) + return comp(f1.n(), f2.n(), tol) + + +def _randrange(seed=None): + """Return a randrange generator. + + ``seed`` can be + + * None - return randomly seeded generator + * int - return a generator seeded with the int + * list - the values to be returned will be taken from the list + in the order given; the provided list is not modified. + + Examples + ======== + + >>> from sympy.core.random import _randrange + >>> rr = _randrange() + >>> rr(1000) # doctest: +SKIP + 999 + >>> rr = _randrange(3) + >>> rr(1000) # doctest: +SKIP + 238 + >>> rr = _randrange([0, 5, 1, 3, 4]) + >>> rr(3), rr(3) + (0, 1) + """ + if seed is None: + return randrange + elif isinstance(seed, int): + rng.seed(seed) + return randrange + elif is_sequence(seed): + seed = list(seed) # make a copy + seed.reverse() + + def give(a, b=None, seq=seed): + if b is None: + a, b = 0, a + a, b = as_int(a), as_int(b) + w = b - a + if w < 1: + raise ValueError('_randrange got empty range') + try: + x = seq.pop() + except IndexError: + raise ValueError('_randrange sequence was too short') + if a <= x < b: + return x + else: + return give(a, b, seq) + return give + else: + raise ValueError('_randrange got an unexpected seed') + + +def _randint(seed=None): + """Return a randint generator. + + ``seed`` can be + + * None - return randomly seeded generator + * int - return a generator seeded with the int + * list - the values to be returned will be taken from the list + in the order given; the provided list is not modified. + + Examples + ======== + + >>> from sympy.core.random import _randint + >>> ri = _randint() + >>> ri(1, 1000) # doctest: +SKIP + 999 + >>> ri = _randint(3) + >>> ri(1, 1000) # doctest: +SKIP + 238 + >>> ri = _randint([0, 5, 1, 2, 4]) + >>> ri(1, 3), ri(1, 3) + (1, 2) + """ + if seed is None: + return randint + elif isinstance(seed, int): + rng.seed(seed) + return randint + elif is_sequence(seed): + seed = list(seed) # make a copy + seed.reverse() + + def give(a, b, seq=seed): + a, b = as_int(a), as_int(b) + w = b - a + if w < 0: + raise ValueError('_randint got empty range') + try: + x = seq.pop() + except IndexError: + raise ValueError('_randint sequence was too short') + if a <= x <= b: + return x + else: + return give(a, b, seq) + return give + else: + raise ValueError('_randint got an unexpected seed') diff --git a/phivenv/Lib/site-packages/sympy/core/relational.py b/phivenv/Lib/site-packages/sympy/core/relational.py new file mode 100644 index 0000000000000000000000000000000000000000..28bf039c9be67a6f5cd6f11df1968961c0760373 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/relational.py @@ -0,0 +1,1622 @@ +from __future__ import annotations + +from .basic import Atom, Basic +from .coreerrors import LazyExceptionMessage +from .sorting import ordered +from .evalf import EvalfMixin +from .function import AppliedUndef +from .numbers import int_valued +from .singleton import S +from .sympify import _sympify, SympifyError +from .parameters import global_parameters +from .logic import fuzzy_bool, fuzzy_xor, fuzzy_and, fuzzy_not +from sympy.logic.boolalg import Boolean, BooleanAtom +from sympy.utilities.iterables import sift +from sympy.utilities.misc import filldedent +from sympy.utilities.exceptions import sympy_deprecation_warning + + +__all__ = ( + 'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', + 'Relational', 'Equality', 'Unequality', 'StrictLessThan', 'LessThan', + 'StrictGreaterThan', 'GreaterThan', +) + +from .expr import Expr +from sympy.multipledispatch import dispatch +from .containers import Tuple +from .symbol import Symbol + + +def _nontrivBool(side): + return isinstance(side, Boolean) and \ + not isinstance(side, Atom) + + +# Note, see issue 4986. Ideally, we wouldn't want to subclass both Boolean +# and Expr. +# from .. import Expr + + +def _canonical(cond): + # return a condition in which all relationals are canonical + reps = {r: r.canonical for r in cond.atoms(Relational)} + return cond.xreplace(reps) + # XXX: AttributeError was being caught here but it wasn't triggered by any of + # the tests so I've removed it... + + +def _canonical_coeff(rel): + # return -2*x + 1 < 0 as x > 1/2 + # XXX make this part of Relational.canonical? + rel = rel.canonical + if not rel.is_Relational or rel.rhs.is_Boolean: + return rel # Eq(x, True) + if not isinstance(rel.lhs, Expr): + return rel.reversed # e.g.: Eq(True, x) -> Eq(x, True) + b, l = rel.lhs.as_coeff_Add(rational=True) + m, lhs = l.as_coeff_Mul(rational=True) + rhs = (rel.rhs - b)/m + if m < 0: + return rel.reversed.func(lhs, rhs) + return rel.func(lhs, rhs) + + +class Relational(Boolean, EvalfMixin): + """Base class for all relation types. + + Explanation + =========== + + Subclasses of Relational should generally be instantiated directly, but + Relational can be instantiated with a valid ``rop`` value to dispatch to + the appropriate subclass. + + Parameters + ========== + + rop : str or None + Indicates what subclass to instantiate. Valid values can be found + in the keys of Relational.ValidRelationOperator. + + Examples + ======== + + >>> from sympy import Rel + >>> from sympy.abc import x, y + >>> Rel(y, x + x**2, '==') + Eq(y, x**2 + x) + + A relation's type can be defined upon creation using ``rop``. + The relation type of an existing expression can be obtained + using its ``rel_op`` property. + Here is a table of all the relation types, along with their + ``rop`` and ``rel_op`` values: + + +---------------------+----------------------------+------------+ + |Relation |``rop`` |``rel_op`` | + +=====================+============================+============+ + |``Equality`` |``==`` or ``eq`` or ``None``|``==`` | + +---------------------+----------------------------+------------+ + |``Unequality`` |``!=`` or ``ne`` |``!=`` | + +---------------------+----------------------------+------------+ + |``GreaterThan`` |``>=`` or ``ge`` |``>=`` | + +---------------------+----------------------------+------------+ + |``LessThan`` |``<=`` or ``le`` |``<=`` | + +---------------------+----------------------------+------------+ + |``StrictGreaterThan``|``>`` or ``gt`` |``>`` | + +---------------------+----------------------------+------------+ + |``StrictLessThan`` |``<`` or ``lt`` |``<`` | + +---------------------+----------------------------+------------+ + + For example, setting ``rop`` to ``==`` produces an + ``Equality`` relation, ``Eq()``. + So does setting ``rop`` to ``eq``, or leaving ``rop`` unspecified. + That is, the first three ``Rel()`` below all produce the same result. + Using a ``rop`` from a different row in the table produces a + different relation type. + For example, the fourth ``Rel()`` below using ``lt`` for ``rop`` + produces a ``StrictLessThan`` inequality: + + >>> from sympy import Rel + >>> from sympy.abc import x, y + >>> Rel(y, x + x**2, '==') + Eq(y, x**2 + x) + >>> Rel(y, x + x**2, 'eq') + Eq(y, x**2 + x) + >>> Rel(y, x + x**2) + Eq(y, x**2 + x) + >>> Rel(y, x + x**2, 'lt') + y < x**2 + x + + To obtain the relation type of an existing expression, + get its ``rel_op`` property. + For example, ``rel_op`` is ``==`` for the ``Equality`` relation above, + and ``<`` for the strict less than inequality above: + + >>> from sympy import Rel + >>> from sympy.abc import x, y + >>> my_equality = Rel(y, x + x**2, '==') + >>> my_equality.rel_op + '==' + >>> my_inequality = Rel(y, x + x**2, 'lt') + >>> my_inequality.rel_op + '<' + + """ + __slots__ = () + + ValidRelationOperator: dict[str | None, type[Relational]] = {} + + is_Relational = True + + # ValidRelationOperator - Defined below, because the necessary classes + # have not yet been defined + + def __new__(cls, lhs, rhs, rop=None, **assumptions): + # If called by a subclass, do nothing special and pass on to Basic. + if cls is not Relational: + return Basic.__new__(cls, lhs, rhs, **assumptions) + + # XXX: Why do this? There should be a separate function to make a + # particular subclass of Relational from a string. + # + # If called directly with an operator, look up the subclass + # corresponding to that operator and delegate to it + cls = cls.ValidRelationOperator.get(rop, None) + if cls is None: + raise ValueError("Invalid relational operator symbol: %r" % rop) + + if not issubclass(cls, (Eq, Ne)): + # validate that Booleans are not being used in a relational + # other than Eq/Ne; + # Note: Symbol is a subclass of Boolean but is considered + # acceptable here. + if any(map(_nontrivBool, (lhs, rhs))): + raise TypeError(filldedent(''' + A Boolean argument can only be used in + Eq and Ne; all other relationals expect + real expressions. + ''')) + + return cls(lhs, rhs, **assumptions) + + @property + def lhs(self): + """The left-hand side of the relation.""" + return self._args[0] + + @property + def rhs(self): + """The right-hand side of the relation.""" + return self._args[1] + + @property + def reversed(self): + """Return the relationship with sides reversed. + + Examples + ======== + + >>> from sympy import Eq + >>> from sympy.abc import x + >>> Eq(x, 1) + Eq(x, 1) + >>> _.reversed + Eq(1, x) + >>> x < 1 + x < 1 + >>> _.reversed + 1 > x + """ + ops = {Eq: Eq, Gt: Lt, Ge: Le, Lt: Gt, Le: Ge, Ne: Ne} + a, b = self.args + return Relational.__new__(ops.get(self.func, self.func), b, a) + + @property + def reversedsign(self): + """Return the relationship with signs reversed. + + Examples + ======== + + >>> from sympy import Eq + >>> from sympy.abc import x + >>> Eq(x, 1) + Eq(x, 1) + >>> _.reversedsign + Eq(-x, -1) + >>> x < 1 + x < 1 + >>> _.reversedsign + -x > -1 + """ + a, b = self.args + if not (isinstance(a, BooleanAtom) or isinstance(b, BooleanAtom)): + ops = {Eq: Eq, Gt: Lt, Ge: Le, Lt: Gt, Le: Ge, Ne: Ne} + return Relational.__new__(ops.get(self.func, self.func), -a, -b) + else: + return self + + @property + def negated(self): + """Return the negated relationship. + + Examples + ======== + + >>> from sympy import Eq + >>> from sympy.abc import x + >>> Eq(x, 1) + Eq(x, 1) + >>> _.negated + Ne(x, 1) + >>> x < 1 + x < 1 + >>> _.negated + x >= 1 + + Notes + ===== + + This works more or less identical to ``~``/``Not``. The difference is + that ``negated`` returns the relationship even if ``evaluate=False``. + Hence, this is useful in code when checking for e.g. negated relations + to existing ones as it will not be affected by the `evaluate` flag. + + """ + ops = {Eq: Ne, Ge: Lt, Gt: Le, Le: Gt, Lt: Ge, Ne: Eq} + # If there ever will be new Relational subclasses, the following line + # will work until it is properly sorted out + # return ops.get(self.func, lambda a, b, evaluate=False: ~(self.func(a, + # b, evaluate=evaluate)))(*self.args, evaluate=False) + return Relational.__new__(ops.get(self.func), *self.args) + + @property + def weak(self): + """return the non-strict version of the inequality or self + + EXAMPLES + ======== + + >>> from sympy.abc import x + >>> (x < 1).weak + x <= 1 + >>> _.weak + x <= 1 + """ + return self + + @property + def strict(self): + """return the strict version of the inequality or self + + EXAMPLES + ======== + + >>> from sympy.abc import x + >>> (x <= 1).strict + x < 1 + >>> _.strict + x < 1 + """ + return self + + def _eval_evalf(self, prec): + return self.func(*[s._evalf(prec) for s in self.args]) + + @property + def canonical(self): + """Return a canonical form of the relational by putting a + number on the rhs, canonically removing a sign or else + ordering the args canonically. No other simplification is + attempted. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> x < 2 + x < 2 + >>> _.reversed.canonical + x < 2 + >>> (-y < x).canonical + x > -y + >>> (-y > x).canonical + x < -y + >>> (-y < -x).canonical + x < y + + The canonicalization is recursively applied: + + >>> from sympy import Eq + >>> Eq(x < y, y > x).canonical + True + """ + args = tuple([i.canonical if isinstance(i, Relational) else i for i in self.args]) + if args != self.args: + r = self.func(*args) + if not isinstance(r, Relational): + return r + else: + r = self + if r.rhs.is_number: + if r.rhs.is_Number and r.lhs.is_Number and r.lhs > r.rhs: + r = r.reversed + elif r.lhs.is_number: + r = r.reversed + elif tuple(ordered(args)) != args: + r = r.reversed + + LHS_CEMS = getattr(r.lhs, 'could_extract_minus_sign', None) + RHS_CEMS = getattr(r.rhs, 'could_extract_minus_sign', None) + + if isinstance(r.lhs, BooleanAtom) or isinstance(r.rhs, BooleanAtom): + return r + + # Check if first value has negative sign + if LHS_CEMS and LHS_CEMS(): + return r.reversedsign + elif not r.rhs.is_number and RHS_CEMS and RHS_CEMS(): + # Right hand side has a minus, but not lhs. + # How does the expression with reversed signs behave? + # This is so that expressions of the type + # Eq(x, -y) and Eq(-x, y) + # have the same canonical representation + expr1, _ = ordered([r.lhs, -r.rhs]) + if expr1 != r.lhs: + return r.reversed.reversedsign + + return r + + def equals(self, other, failing_expression=False): + """Return True if the sides of the relationship are mathematically + identical and the type of relationship is the same. + If failing_expression is True, return the expression whose truth value + was unknown.""" + if isinstance(other, Relational): + if other in (self, self.reversed): + return True + a, b = self, other + if a.func in (Eq, Ne) or b.func in (Eq, Ne): + if a.func != b.func: + return False + left, right = [i.equals(j, + failing_expression=failing_expression) + for i, j in zip(a.args, b.args)] + if left is True: + return right + if right is True: + return left + lr, rl = [i.equals(j, failing_expression=failing_expression) + for i, j in zip(a.args, b.reversed.args)] + if lr is True: + return rl + if rl is True: + return lr + e = (left, right, lr, rl) + if all(i is False for i in e): + return False + for i in e: + if i not in (True, False): + return i + else: + if b.func != a.func: + b = b.reversed + if a.func != b.func: + return False + left = a.lhs.equals(b.lhs, + failing_expression=failing_expression) + if left is False: + return False + right = a.rhs.equals(b.rhs, + failing_expression=failing_expression) + if right is False: + return False + if left is True: + return right + return left + + def _eval_simplify(self, **kwargs): + from .add import Add + from .expr import Expr + r = self + r = r.func(*[i.simplify(**kwargs) for i in r.args]) + if r.is_Relational: + if not isinstance(r.lhs, Expr) or not isinstance(r.rhs, Expr): + return r + dif = r.lhs - r.rhs + # replace dif with a valid Number that will + # allow a definitive comparison with 0 + v = None + if dif.is_comparable: + v = dif.n(2) + if any(i._prec == 1 for i in v.as_real_imag()): + rv, iv = [i.n(2) for i in dif.as_real_imag()] + v = rv + S.ImaginaryUnit*iv + elif dif.equals(0): # XXX this is expensive + v = S.Zero + if v is not None: + r = r.func._eval_relation(v, S.Zero) + r = r.canonical + # If there is only one symbol in the expression, + # try to write it on a simplified form + free = list(filter(lambda x: x.is_real is not False, r.free_symbols)) + if len(free) == 1: + try: + from sympy.solvers.solveset import linear_coeffs + x = free.pop() + dif = r.lhs - r.rhs + m, b = linear_coeffs(dif, x) + if m.is_zero is False: + if m.is_negative: + # Dividing with a negative number, so change order of arguments + # canonical will put the symbol back on the lhs later + r = r.func(-b / m, x) + else: + r = r.func(x, -b / m) + else: + r = r.func(b, S.Zero) + except ValueError: + # maybe not a linear function, try polynomial + from sympy.polys.polyerrors import PolynomialError + from sympy.polys.polytools import gcd, Poly, poly + try: + p = poly(dif, x) + c = p.all_coeffs() + constant = c[-1] + c[-1] = 0 + scale = gcd(c) + c = [ctmp / scale for ctmp in c] + r = r.func(Poly.from_list(c, x).as_expr(), -constant / scale) + except PolynomialError: + pass + elif len(free) >= 2: + try: + from sympy.solvers.solveset import linear_coeffs + from sympy.polys.polytools import gcd + free = list(ordered(free)) + dif = r.lhs - r.rhs + m = linear_coeffs(dif, *free) + constant = m[-1] + del m[-1] + scale = gcd(m) + m = [mtmp / scale for mtmp in m] + nzm = list(filter(lambda f: f[0] != 0, list(zip(m, free)))) + if scale.is_zero is False: + if constant != 0: + # lhs: expression, rhs: constant + newexpr = Add(*[i * j for i, j in nzm]) + r = r.func(newexpr, -constant / scale) + else: + # keep first term on lhs + lhsterm = nzm[0][0] * nzm[0][1] + del nzm[0] + newexpr = Add(*[i * j for i, j in nzm]) + r = r.func(lhsterm, -newexpr) + + else: + r = r.func(constant, S.Zero) + except ValueError: + pass + # Did we get a simplified result? + r = r.canonical + measure = kwargs['measure'] + if measure(r) < kwargs['ratio'] * measure(self): + return r + else: + return self + + def _eval_trigsimp(self, **opts): + from sympy.simplify.trigsimp import trigsimp + return self.func(trigsimp(self.lhs, **opts), trigsimp(self.rhs, **opts)) + + def expand(self, **kwargs): + args = (arg.expand(**kwargs) for arg in self.args) + return self.func(*args) + + def __bool__(self) -> bool: + raise TypeError( + LazyExceptionMessage( + lambda: f"cannot determine truth value of Relational: {self}" + ) + ) + + def _eval_as_set(self): + # self is univariate and periodicity(self, x) in (0, None) + from sympy.solvers.inequalities import solve_univariate_inequality + from sympy.sets.conditionset import ConditionSet + syms = self.free_symbols + assert len(syms) == 1 + x = syms.pop() + try: + xset = solve_univariate_inequality(self, x, relational=False) + except NotImplementedError: + # solve_univariate_inequality raises NotImplementedError for + # unsolvable equations/inequalities. + xset = ConditionSet(x, self, S.Reals) + return xset + + @property + def binary_symbols(self): + # override where necessary + return set() + + +Rel = Relational + + +class Equality(Relational): + """ + An equal relation between two objects. + + Explanation + =========== + + Represents that two objects are equal. If they can be easily shown + to be definitively equal (or unequal), this will reduce to True (or + False). Otherwise, the relation is maintained as an unevaluated + Equality object. Use the ``simplify`` function on this object for + more nontrivial evaluation of the equality relation. + + As usual, the keyword argument ``evaluate=False`` can be used to + prevent any evaluation. + + Examples + ======== + + >>> from sympy import Eq, simplify, exp, cos + >>> from sympy.abc import x, y + >>> Eq(y, x + x**2) + Eq(y, x**2 + x) + >>> Eq(2, 5) + False + >>> Eq(2, 5, evaluate=False) + Eq(2, 5) + >>> _.doit() + False + >>> Eq(exp(x), exp(x).rewrite(cos)) + Eq(exp(x), sinh(x) + cosh(x)) + >>> simplify(_) + True + + See Also + ======== + + sympy.logic.boolalg.Equivalent : for representing equality between two + boolean expressions + + Notes + ===== + + Python treats 1 and True (and 0 and False) as being equal; SymPy + does not. And integer will always compare as unequal to a Boolean: + + >>> Eq(True, 1), True == 1 + (False, True) + + This class is not the same as the == operator. The == operator tests + for exact structural equality between two expressions; this class + compares expressions mathematically. + + If either object defines an ``_eval_Eq`` method, it can be used in place of + the default algorithm. If ``lhs._eval_Eq(rhs)`` or ``rhs._eval_Eq(lhs)`` + returns anything other than None, that return value will be substituted for + the Equality. If None is returned by ``_eval_Eq``, an Equality object will + be created as usual. + + Since this object is already an expression, it does not respond to + the method ``as_expr`` if one tries to create `x - y` from ``Eq(x, y)``. + If ``eq = Eq(x, y)`` then write `eq.lhs - eq.rhs` to get ``x - y``. + + .. deprecated:: 1.5 + + ``Eq(expr)`` with a single argument is a shorthand for ``Eq(expr, 0)``, + but this behavior is deprecated and will be removed in a future version + of SymPy. + + """ + rel_op = '==' + + __slots__ = () + + is_Equality = True + + def __new__(cls, lhs, rhs, **options): + evaluate = options.pop('evaluate', global_parameters.evaluate) + lhs = _sympify(lhs) + rhs = _sympify(rhs) + if evaluate: + val = is_eq(lhs, rhs) + if val is None: + return cls(lhs, rhs, evaluate=False) + else: + return _sympify(val) + + return Relational.__new__(cls, lhs, rhs) + + @classmethod + def _eval_relation(cls, lhs, rhs): + return _sympify(lhs == rhs) + + def _eval_rewrite_as_Add(self, L, R, evaluate=True, **kwargs): + """ + return Eq(L, R) as L - R. To control the evaluation of + the result set pass `evaluate=True` to give L - R; + if `evaluate=None` then terms in L and R will not cancel + but they will be listed in canonical order; otherwise + non-canonical args will be returned. If one side is 0, the + non-zero side will be returned. + + .. deprecated:: 1.13 + + The method ``Eq.rewrite(Add)`` is deprecated. + See :ref:`eq-rewrite-Add` for details. + + Examples + ======== + + >>> from sympy import Eq, Add + >>> from sympy.abc import b, x + >>> eq = Eq(x + b, x - b) + >>> eq.rewrite(Add) #doctest: +SKIP + 2*b + >>> eq.rewrite(Add, evaluate=None).args #doctest: +SKIP + (b, b, x, -x) + >>> eq.rewrite(Add, evaluate=False).args #doctest: +SKIP + (b, x, b, -x) + """ + sympy_deprecation_warning(""" + Eq.rewrite(Add) is deprecated. + + For ``eq = Eq(a, b)`` use ``eq.lhs - eq.rhs`` to obtain + ``a - b``. + """, + deprecated_since_version="1.13", + active_deprecations_target="eq-rewrite-Add", + stacklevel=5, + ) + from .add import _unevaluated_Add, Add + if L == 0: + return R + if R == 0: + return L + if evaluate: + # allow cancellation of args + return L - R + args = Add.make_args(L) + Add.make_args(-R) + if evaluate is None: + # no cancellation, but canonical + return _unevaluated_Add(*args) + # no cancellation, not canonical + return Add._from_args(args) + + @property + def binary_symbols(self): + if S.true in self.args or S.false in self.args: + if self.lhs.is_Symbol: + return {self.lhs} + elif self.rhs.is_Symbol: + return {self.rhs} + return set() + + def _eval_simplify(self, **kwargs): + # standard simplify + e = super()._eval_simplify(**kwargs) + if not isinstance(e, Equality): + return e + from .expr import Expr + if not isinstance(e.lhs, Expr) or not isinstance(e.rhs, Expr): + return e + free = self.free_symbols + if len(free) == 1: + try: + from .add import Add + from sympy.solvers.solveset import linear_coeffs + x = free.pop() + m, b = linear_coeffs( + Add(e.lhs, -e.rhs, evaluate=False), x) + if m.is_zero is False: + enew = e.func(x, -b / m) + else: + enew = e.func(m * x, -b) + measure = kwargs['measure'] + if measure(enew) <= kwargs['ratio'] * measure(e): + e = enew + except ValueError: + pass + return e.canonical + + def integrate(self, *args, **kwargs): + """See the integrate function in sympy.integrals""" + from sympy.integrals.integrals import integrate + return integrate(self, *args, **kwargs) + + def as_poly(self, *gens, **kwargs): + '''Returns lhs-rhs as a Poly + + Examples + ======== + + >>> from sympy import Eq + >>> from sympy.abc import x + >>> Eq(x**2, 1).as_poly(x) + Poly(x**2 - 1, x, domain='ZZ') + ''' + return (self.lhs - self.rhs).as_poly(*gens, **kwargs) + + +Eq = Equality + + +class Unequality(Relational): + """An unequal relation between two objects. + + Explanation + =========== + + Represents that two objects are not equal. If they can be shown to be + definitively equal, this will reduce to False; if definitively unequal, + this will reduce to True. Otherwise, the relation is maintained as an + Unequality object. + + Examples + ======== + + >>> from sympy import Ne + >>> from sympy.abc import x, y + >>> Ne(y, x+x**2) + Ne(y, x**2 + x) + + See Also + ======== + Equality + + Notes + ===== + This class is not the same as the != operator. The != operator tests + for exact structural equality between two expressions; this class + compares expressions mathematically. + + This class is effectively the inverse of Equality. As such, it uses the + same algorithms, including any available `_eval_Eq` methods. + + """ + rel_op = '!=' + + __slots__ = () + + def __new__(cls, lhs, rhs, **options): + lhs = _sympify(lhs) + rhs = _sympify(rhs) + evaluate = options.pop('evaluate', global_parameters.evaluate) + if evaluate: + val = is_neq(lhs, rhs) + if val is None: + return cls(lhs, rhs, evaluate=False) + else: + return _sympify(val) + + return Relational.__new__(cls, lhs, rhs, **options) + + @classmethod + def _eval_relation(cls, lhs, rhs): + return _sympify(lhs != rhs) + + @property + def binary_symbols(self): + if S.true in self.args or S.false in self.args: + if self.lhs.is_Symbol: + return {self.lhs} + elif self.rhs.is_Symbol: + return {self.rhs} + return set() + + def _eval_simplify(self, **kwargs): + # simplify as an equality + eq = Equality(*self.args)._eval_simplify(**kwargs) + if isinstance(eq, Equality): + # send back Ne with the new args + return self.func(*eq.args) + return eq.negated # result of Ne is the negated Eq + + +Ne = Unequality + + +class _Inequality(Relational): + """Internal base class for all *Than types. + + Each subclass must implement _eval_relation to provide the method for + comparing two real numbers. + + """ + __slots__ = () + + def __new__(cls, lhs, rhs, **options): + + try: + lhs = _sympify(lhs) + rhs = _sympify(rhs) + except SympifyError: + return NotImplemented + + evaluate = options.pop('evaluate', global_parameters.evaluate) + if evaluate: + for me in (lhs, rhs): + if me.is_extended_real is False: + raise TypeError("Invalid comparison of non-real %s" % me) + if me is S.NaN: + raise TypeError("Invalid NaN comparison") + # First we invoke the appropriate inequality method of `lhs` + # (e.g., `lhs.__lt__`). That method will try to reduce to + # boolean or raise an exception. It may keep calling + # superclasses until it reaches `Expr` (e.g., `Expr.__lt__`). + # In some cases, `Expr` will just invoke us again (if neither it + # nor a subclass was able to reduce to boolean or raise an + # exception). In that case, it must call us with + # `evaluate=False` to prevent infinite recursion. + return cls._eval_relation(lhs, rhs, **options) + + # make a "non-evaluated" Expr for the inequality + return Relational.__new__(cls, lhs, rhs, **options) + + @classmethod + def _eval_relation(cls, lhs, rhs, **options): + val = cls._eval_fuzzy_relation(lhs, rhs) + if val is None: + return cls(lhs, rhs, evaluate=False) + else: + return _sympify(val) + + +class _Greater(_Inequality): + """Not intended for general use + + _Greater is only used so that GreaterThan and StrictGreaterThan may + subclass it for the .gts and .lts properties. + + """ + __slots__ = () + + @property + def gts(self): + return self._args[0] + + @property + def lts(self): + return self._args[1] + + +class _Less(_Inequality): + """Not intended for general use. + + _Less is only used so that LessThan and StrictLessThan may subclass it for + the .gts and .lts properties. + + """ + __slots__ = () + + @property + def gts(self): + return self._args[1] + + @property + def lts(self): + return self._args[0] + + +class GreaterThan(_Greater): + r"""Class representations of inequalities. + + Explanation + =========== + + The ``*Than`` classes represent inequal relationships, where the left-hand + side is generally bigger or smaller than the right-hand side. For example, + the GreaterThan class represents an inequal relationship where the + left-hand side is at least as big as the right side, if not bigger. In + mathematical notation: + + lhs $\ge$ rhs + + In total, there are four ``*Than`` classes, to represent the four + inequalities: + + +-----------------+--------+ + |Class Name | Symbol | + +=================+========+ + |GreaterThan | ``>=`` | + +-----------------+--------+ + |LessThan | ``<=`` | + +-----------------+--------+ + |StrictGreaterThan| ``>`` | + +-----------------+--------+ + |StrictLessThan | ``<`` | + +-----------------+--------+ + + All classes take two arguments, lhs and rhs. + + +----------------------------+-----------------+ + |Signature Example | Math Equivalent | + +============================+=================+ + |GreaterThan(lhs, rhs) | lhs $\ge$ rhs | + +----------------------------+-----------------+ + |LessThan(lhs, rhs) | lhs $\le$ rhs | + +----------------------------+-----------------+ + |StrictGreaterThan(lhs, rhs) | lhs $>$ rhs | + +----------------------------+-----------------+ + |StrictLessThan(lhs, rhs) | lhs $<$ rhs | + +----------------------------+-----------------+ + + In addition to the normal .lhs and .rhs of Relations, ``*Than`` inequality + objects also have the .lts and .gts properties, which represent the "less + than side" and "greater than side" of the operator. Use of .lts and .gts + in an algorithm rather than .lhs and .rhs as an assumption of inequality + direction will make more explicit the intent of a certain section of code, + and will make it similarly more robust to client code changes: + + >>> from sympy import GreaterThan, StrictGreaterThan + >>> from sympy import LessThan, StrictLessThan + >>> from sympy import And, Ge, Gt, Le, Lt, Rel, S + >>> from sympy.abc import x, y, z + >>> from sympy.core.relational import Relational + + >>> e = GreaterThan(x, 1) + >>> e + x >= 1 + >>> '%s >= %s is the same as %s <= %s' % (e.gts, e.lts, e.lts, e.gts) + 'x >= 1 is the same as 1 <= x' + + Examples + ======== + + One generally does not instantiate these classes directly, but uses various + convenience methods: + + >>> for f in [Ge, Gt, Le, Lt]: # convenience wrappers + ... print(f(x, 2)) + x >= 2 + x > 2 + x <= 2 + x < 2 + + Another option is to use the Python inequality operators (``>=``, ``>``, + ``<=``, ``<``) directly. Their main advantage over the ``Ge``, ``Gt``, + ``Le``, and ``Lt`` counterparts, is that one can write a more + "mathematical looking" statement rather than littering the math with + oddball function calls. However there are certain (minor) caveats of + which to be aware (search for 'gotcha', below). + + >>> x >= 2 + x >= 2 + >>> _ == Ge(x, 2) + True + + However, it is also perfectly valid to instantiate a ``*Than`` class less + succinctly and less conveniently: + + >>> Rel(x, 1, ">") + x > 1 + >>> Relational(x, 1, ">") + x > 1 + + >>> StrictGreaterThan(x, 1) + x > 1 + >>> GreaterThan(x, 1) + x >= 1 + >>> LessThan(x, 1) + x <= 1 + >>> StrictLessThan(x, 1) + x < 1 + + Notes + ===== + + There are a couple of "gotchas" to be aware of when using Python's + operators. + + The first is that what your write is not always what you get: + + >>> 1 < x + x > 1 + + Due to the order that Python parses a statement, it may + not immediately find two objects comparable. When ``1 < x`` + is evaluated, Python recognizes that the number 1 is a native + number and that x is *not*. Because a native Python number does + not know how to compare itself with a SymPy object + Python will try the reflective operation, ``x > 1`` and that is the + form that gets evaluated, hence returned. + + If the order of the statement is important (for visual output to + the console, perhaps), one can work around this annoyance in a + couple ways: + + (1) "sympify" the literal before comparison + + >>> S(1) < x + 1 < x + + (2) use one of the wrappers or less succinct methods described + above + + >>> Lt(1, x) + 1 < x + >>> Relational(1, x, "<") + 1 < x + + The second gotcha involves writing equality tests between relationals + when one or both sides of the test involve a literal relational: + + >>> e = x < 1; e + x < 1 + >>> e == e # neither side is a literal + True + >>> e == x < 1 # expecting True, too + False + >>> e != x < 1 # expecting False + x < 1 + >>> x < 1 != x < 1 # expecting False or the same thing as before + Traceback (most recent call last): + ... + TypeError: cannot determine truth value of Relational + + The solution for this case is to wrap literal relationals in + parentheses: + + >>> e == (x < 1) + True + >>> e != (x < 1) + False + >>> (x < 1) != (x < 1) + False + + The third gotcha involves chained inequalities not involving + ``==`` or ``!=``. Occasionally, one may be tempted to write: + + >>> e = x < y < z + Traceback (most recent call last): + ... + TypeError: symbolic boolean expression has no truth value. + + Due to an implementation detail or decision of Python [1]_, + there is no way for SymPy to create a chained inequality with + that syntax so one must use And: + + >>> e = And(x < y, y < z) + >>> type( e ) + And + >>> e + (x < y) & (y < z) + + Although this can also be done with the '&' operator, it cannot + be done with the 'and' operarator: + + >>> (x < y) & (y < z) + (x < y) & (y < z) + >>> (x < y) and (y < z) + Traceback (most recent call last): + ... + TypeError: cannot determine truth value of Relational + + .. [1] This implementation detail is that Python provides no reliable + method to determine that a chained inequality is being built. + Chained comparison operators are evaluated pairwise, using "and" + logic (see + https://docs.python.org/3/reference/expressions.html#not-in). This + is done in an efficient way, so that each object being compared + is only evaluated once and the comparison can short-circuit. For + example, ``1 > 2 > 3`` is evaluated by Python as ``(1 > 2) and (2 + > 3)``. The ``and`` operator coerces each side into a bool, + returning the object itself when it short-circuits. The bool of + the --Than operators will raise TypeError on purpose, because + SymPy cannot determine the mathematical ordering of symbolic + expressions. Thus, if we were to compute ``x > y > z``, with + ``x``, ``y``, and ``z`` being Symbols, Python converts the + statement (roughly) into these steps: + + (1) x > y > z + (2) (x > y) and (y > z) + (3) (GreaterThanObject) and (y > z) + (4) (GreaterThanObject.__bool__()) and (y > z) + (5) TypeError + + Because of the ``and`` added at step 2, the statement gets turned into a + weak ternary statement, and the first object's ``__bool__`` method will + raise TypeError. Thus, creating a chained inequality is not possible. + + In Python, there is no way to override the ``and`` operator, or to + control how it short circuits, so it is impossible to make something + like ``x > y > z`` work. There was a PEP to change this, + :pep:`335`, but it was officially closed in March, 2012. + + """ + __slots__ = () + + rel_op = '>=' + + @classmethod + def _eval_fuzzy_relation(cls, lhs, rhs): + return is_ge(lhs, rhs) + + @property + def strict(self): + return Gt(*self.args) + +Ge = GreaterThan + + +class LessThan(_Less): + __doc__ = GreaterThan.__doc__ + __slots__ = () + + rel_op = '<=' + + @classmethod + def _eval_fuzzy_relation(cls, lhs, rhs): + return is_le(lhs, rhs) + + @property + def strict(self): + return Lt(*self.args) + +Le = LessThan + + +class StrictGreaterThan(_Greater): + __doc__ = GreaterThan.__doc__ + __slots__ = () + + rel_op = '>' + + @classmethod + def _eval_fuzzy_relation(cls, lhs, rhs): + return is_gt(lhs, rhs) + + @property + def weak(self): + return Ge(*self.args) + + +Gt = StrictGreaterThan + + +class StrictLessThan(_Less): + __doc__ = GreaterThan.__doc__ + __slots__ = () + + rel_op = '<' + + @classmethod + def _eval_fuzzy_relation(cls, lhs, rhs): + return is_lt(lhs, rhs) + + @property + def weak(self): + return Le(*self.args) + +Lt = StrictLessThan + +# A class-specific (not object-specific) data item used for a minor speedup. +# It is defined here, rather than directly in the class, because the classes +# that it references have not been defined until now (e.g. StrictLessThan). +Relational.ValidRelationOperator = { + None: Equality, + '==': Equality, + 'eq': Equality, + '!=': Unequality, + '<>': Unequality, + 'ne': Unequality, + '>=': GreaterThan, + 'ge': GreaterThan, + '<=': LessThan, + 'le': LessThan, + '>': StrictGreaterThan, + 'gt': StrictGreaterThan, + '<': StrictLessThan, + 'lt': StrictLessThan, +} + + +def _n2(a, b): + """Return (a - b).evalf(2) if a and b are comparable, else None. + This should only be used when a and b are already sympified. + """ + # /!\ it is very important (see issue 8245) not to + # use a re-evaluated number in the calculation of dif + if a.is_comparable and b.is_comparable: + dif = (a - b).evalf(2) + if dif.is_comparable: + return dif + + +@dispatch(Expr, Expr) +def _eval_is_ge(lhs, rhs): + return None + + +@dispatch(Basic, Basic) +def _eval_is_eq(lhs, rhs): + return None + + +@dispatch(Tuple, Expr) # type: ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + return False + + +@dispatch(Tuple, AppliedUndef) # type: ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + return None + + +@dispatch(Tuple, Symbol) # type: ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + return None + + +@dispatch(Tuple, Tuple) # type: ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + if len(lhs) != len(rhs): + return False + + return fuzzy_and(fuzzy_bool(is_eq(s, o)) for s, o in zip(lhs, rhs)) + + +def is_lt(lhs, rhs, assumptions=None): + """Fuzzy bool for lhs is strictly less than rhs. + + See the docstring for :func:`~.is_ge` for more. + """ + return fuzzy_not(is_ge(lhs, rhs, assumptions)) + + +def is_gt(lhs, rhs, assumptions=None): + """Fuzzy bool for lhs is strictly greater than rhs. + + See the docstring for :func:`~.is_ge` for more. + """ + return fuzzy_not(is_le(lhs, rhs, assumptions)) + + +def is_le(lhs, rhs, assumptions=None): + """Fuzzy bool for lhs is less than or equal to rhs. + + See the docstring for :func:`~.is_ge` for more. + """ + return is_ge(rhs, lhs, assumptions) + + +def is_ge(lhs, rhs, assumptions=None): + """ + Fuzzy bool for *lhs* is greater than or equal to *rhs*. + + Parameters + ========== + + lhs : Expr + The left-hand side of the expression, must be sympified, + and an instance of expression. Throws an exception if + lhs is not an instance of expression. + + rhs : Expr + The right-hand side of the expression, must be sympified + and an instance of expression. Throws an exception if + lhs is not an instance of expression. + + assumptions: Boolean, optional + Assumptions taken to evaluate the inequality. + + Returns + ======= + + ``True`` if *lhs* is greater than or equal to *rhs*, ``False`` if *lhs* + is less than *rhs*, and ``None`` if the comparison between *lhs* and + *rhs* is indeterminate. + + Explanation + =========== + + This function is intended to give a relatively fast determination and + deliberately does not attempt slow calculations that might help in + obtaining a determination of True or False in more difficult cases. + + The four comparison functions ``is_le``, ``is_lt``, ``is_ge``, and ``is_gt`` are + each implemented in terms of ``is_ge`` in the following way: + + is_ge(x, y) := is_ge(x, y) + is_le(x, y) := is_ge(y, x) + is_lt(x, y) := fuzzy_not(is_ge(x, y)) + is_gt(x, y) := fuzzy_not(is_ge(y, x)) + + Therefore, supporting new type with this function will ensure behavior for + other three functions as well. + + To maintain these equivalences in fuzzy logic it is important that in cases where + either x or y is non-real all comparisons will give None. + + Examples + ======== + + >>> from sympy import S, Q + >>> from sympy.core.relational import is_ge, is_le, is_gt, is_lt + >>> from sympy.abc import x + >>> is_ge(S(2), S(0)) + True + >>> is_ge(S(0), S(2)) + False + >>> is_le(S(0), S(2)) + True + >>> is_gt(S(0), S(2)) + False + >>> is_lt(S(2), S(0)) + False + + Assumptions can be passed to evaluate the quality which is otherwise + indeterminate. + + >>> print(is_ge(x, S(0))) + None + >>> is_ge(x, S(0), assumptions=Q.positive(x)) + True + + New types can be supported by dispatching to ``_eval_is_ge``. + + >>> from sympy import Expr, sympify + >>> from sympy.multipledispatch import dispatch + >>> class MyExpr(Expr): + ... def __new__(cls, arg): + ... return super().__new__(cls, sympify(arg)) + ... @property + ... def value(self): + ... return self.args[0] + >>> @dispatch(MyExpr, MyExpr) + ... def _eval_is_ge(a, b): + ... return is_ge(a.value, b.value) + >>> a = MyExpr(1) + >>> b = MyExpr(2) + >>> is_ge(b, a) + True + >>> is_le(a, b) + True + """ + from sympy.assumptions.wrapper import AssumptionsWrapper, is_extended_nonnegative + + if not (isinstance(lhs, Expr) and isinstance(rhs, Expr)): + raise TypeError("Can only compare inequalities with Expr") + + retval = _eval_is_ge(lhs, rhs) + + if retval is not None: + return retval + else: + n2 = _n2(lhs, rhs) + if n2 is not None: + # use float comparison for infinity. + # otherwise get stuck in infinite recursion + if n2 in (S.Infinity, S.NegativeInfinity): + n2 = float(n2) + return n2 >= 0 + + _lhs = AssumptionsWrapper(lhs, assumptions) + _rhs = AssumptionsWrapper(rhs, assumptions) + if _lhs.is_extended_real and _rhs.is_extended_real: + if (_lhs.is_infinite and _lhs.is_extended_positive) or (_rhs.is_infinite and _rhs.is_extended_negative): + return True + diff = lhs - rhs + if diff is not S.NaN: + rv = is_extended_nonnegative(diff, assumptions) + if rv is not None: + return rv + + +def is_neq(lhs, rhs, assumptions=None): + """Fuzzy bool for lhs does not equal rhs. + + See the docstring for :func:`~.is_eq` for more. + """ + return fuzzy_not(is_eq(lhs, rhs, assumptions)) + + +def is_eq(lhs, rhs, assumptions=None): + """ + Fuzzy bool representing mathematical equality between *lhs* and *rhs*. + + Parameters + ========== + + lhs : Expr + The left-hand side of the expression, must be sympified. + + rhs : Expr + The right-hand side of the expression, must be sympified. + + assumptions: Boolean, optional + Assumptions taken to evaluate the equality. + + Returns + ======= + + ``True`` if *lhs* is equal to *rhs*, ``False`` is *lhs* is not equal to *rhs*, + and ``None`` if the comparison between *lhs* and *rhs* is indeterminate. + + Explanation + =========== + + This function is intended to give a relatively fast determination and + deliberately does not attempt slow calculations that might help in + obtaining a determination of True or False in more difficult cases. + + :func:`~.is_neq` calls this function to return its value, so supporting + new type with this function will ensure correct behavior for ``is_neq`` + as well. + + Examples + ======== + + >>> from sympy import Q, S + >>> from sympy.core.relational import is_eq, is_neq + >>> from sympy.abc import x + >>> is_eq(S(0), S(0)) + True + >>> is_neq(S(0), S(0)) + False + >>> is_eq(S(0), S(2)) + False + >>> is_neq(S(0), S(2)) + True + + Assumptions can be passed to evaluate the equality which is otherwise + indeterminate. + + >>> print(is_eq(x, S(0))) + None + >>> is_eq(x, S(0), assumptions=Q.zero(x)) + True + + New types can be supported by dispatching to ``_eval_is_eq``. + + >>> from sympy import Basic, sympify + >>> from sympy.multipledispatch import dispatch + >>> class MyBasic(Basic): + ... def __new__(cls, arg): + ... return Basic.__new__(cls, sympify(arg)) + ... @property + ... def value(self): + ... return self.args[0] + ... + >>> @dispatch(MyBasic, MyBasic) + ... def _eval_is_eq(a, b): + ... return is_eq(a.value, b.value) + ... + >>> a = MyBasic(1) + >>> b = MyBasic(1) + >>> is_eq(a, b) + True + >>> is_neq(a, b) + False + + """ + # here, _eval_Eq is only called for backwards compatibility + # new code should use is_eq with multiple dispatch as + # outlined in the docstring + for side1, side2 in (lhs, rhs), (rhs, lhs): + eval_func = getattr(side1, '_eval_Eq', None) + if eval_func is not None: + retval = eval_func(side2) + if retval is not None: + return retval + + retval = _eval_is_eq(lhs, rhs) + if retval is not None: + return retval + + if dispatch(type(lhs), type(rhs)) != dispatch(type(rhs), type(lhs)): + retval = _eval_is_eq(rhs, lhs) + if retval is not None: + return retval + + # retval is still None, so go through the equality logic + # If expressions have the same structure, they must be equal. + if lhs == rhs: + return True # e.g. True == True + elif all(isinstance(i, BooleanAtom) for i in (rhs, lhs)): + return False # True != False + elif not (lhs.is_Symbol or rhs.is_Symbol) and ( + isinstance(lhs, Boolean) != + isinstance(rhs, Boolean)): + return False # only Booleans can equal Booleans + + from sympy.assumptions.wrapper import (AssumptionsWrapper, + is_infinite, is_extended_real) + from .add import Add + + _lhs = AssumptionsWrapper(lhs, assumptions) + _rhs = AssumptionsWrapper(rhs, assumptions) + + if _lhs.is_infinite or _rhs.is_infinite: + if fuzzy_xor([_lhs.is_infinite, _rhs.is_infinite]): + return False + if fuzzy_xor([_lhs.is_extended_real, _rhs.is_extended_real]): + return False + if fuzzy_and([_lhs.is_extended_real, _rhs.is_extended_real]): + return fuzzy_xor([_lhs.is_extended_positive, fuzzy_not(_rhs.is_extended_positive)]) + + # Try to split real/imaginary parts and equate them + I = S.ImaginaryUnit + + def split_real_imag(expr): + real_imag = lambda t: ( + 'real' if is_extended_real(t, assumptions) else + 'imag' if is_extended_real(I*t, assumptions) else None) + return sift(Add.make_args(expr), real_imag) + + lhs_ri = split_real_imag(lhs) + if not lhs_ri[None]: + rhs_ri = split_real_imag(rhs) + if not rhs_ri[None]: + eq_real = is_eq(Add(*lhs_ri['real']), Add(*rhs_ri['real']), assumptions) + eq_imag = is_eq(I * Add(*lhs_ri['imag']), I * Add(*rhs_ri['imag']), assumptions) + return fuzzy_and(map(fuzzy_bool, [eq_real, eq_imag])) + + from sympy.functions.elementary.complexes import arg + # Compare e.g. zoo with 1+I*oo by comparing args + arglhs = arg(lhs) + argrhs = arg(rhs) + # Guard against Eq(nan, nan) -> False + if not (arglhs == S.NaN and argrhs == S.NaN): + return fuzzy_bool(is_eq(arglhs, argrhs, assumptions)) + + if all(isinstance(i, Expr) for i in (lhs, rhs)): + # see if the difference evaluates + dif = lhs - rhs + _dif = AssumptionsWrapper(dif, assumptions) + z = _dif.is_zero + if z is not None: + if z is False and _dif.is_commutative: # issue 10728 + return False + if z: + return True + + # is_zero cannot help decide integer/rational with Float + c, t = dif.as_coeff_Add() + if c.is_Float: + if int_valued(c): + if t.is_integer is False: + return False + elif t.is_rational is False: + return False + + n2 = _n2(lhs, rhs) + if n2 is not None: + return _sympify(n2 == 0) + + # see if the ratio evaluates + n, d = dif.as_numer_denom() + rv = None + _n = AssumptionsWrapper(n, assumptions) + _d = AssumptionsWrapper(d, assumptions) + if _n.is_zero: + rv = _d.is_nonzero + elif _n.is_finite: + if _d.is_infinite: + rv = True + elif _n.is_zero is False: + rv = _d.is_infinite + if rv is None: + # if the condition that makes the denominator + # infinite does not make the original expression + # True then False can be returned + from sympy.simplify.simplify import clear_coefficients + l, r = clear_coefficients(d, S.Infinity) + args = [_.subs(l, r) for _ in (lhs, rhs)] + if args != [lhs, rhs]: + rv = fuzzy_bool(is_eq(*args, assumptions)) + if rv is True: + rv = None + elif any(is_infinite(a, assumptions) for a in Add.make_args(n)): + # (inf or nan)/x != 0 + rv = False + if rv is not None: + return rv diff --git a/phivenv/Lib/site-packages/sympy/core/rules.py b/phivenv/Lib/site-packages/sympy/core/rules.py new file mode 100644 index 0000000000000000000000000000000000000000..5ae331f71b21c8a6ef35f499c5c5c89239349e9c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/rules.py @@ -0,0 +1,66 @@ +""" +Replacement rules. +""" + +class Transform: + """ + Immutable mapping that can be used as a generic transformation rule. + + Parameters + ========== + + transform : callable + Computes the value corresponding to any key. + + filter : callable, optional + If supplied, specifies which objects are in the mapping. + + Examples + ======== + + >>> from sympy.core.rules import Transform + >>> from sympy.abc import x + + This Transform will return, as a value, one more than the key: + + >>> add1 = Transform(lambda x: x + 1) + >>> add1[1] + 2 + >>> add1[x] + x + 1 + + By default, all values are considered to be in the dictionary. If a filter + is supplied, only the objects for which it returns True are considered as + being in the dictionary: + + >>> add1_odd = Transform(lambda x: x + 1, lambda x: x%2 == 1) + >>> 2 in add1_odd + False + >>> add1_odd.get(2, 0) + 0 + >>> 3 in add1_odd + True + >>> add1_odd[3] + 4 + >>> add1_odd.get(3, 0) + 4 + """ + + def __init__(self, transform, filter=lambda x: True): + self._transform = transform + self._filter = filter + + def __contains__(self, item): + return self._filter(item) + + def __getitem__(self, key): + if self._filter(key): + return self._transform(key) + else: + raise KeyError(key) + + def get(self, item, default=None): + if item in self: + return self[item] + else: + return default diff --git a/phivenv/Lib/site-packages/sympy/core/singleton.py b/phivenv/Lib/site-packages/sympy/core/singleton.py new file mode 100644 index 0000000000000000000000000000000000000000..e8b9df959393270140bc3ef11b3d9a4e948c5e80 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/singleton.py @@ -0,0 +1,199 @@ +"""Singleton mechanism""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .core import Registry +from .sympify import sympify + + +if TYPE_CHECKING: + from sympy.core.numbers import ( + Zero as _Zero, + One as _One, + NegativeOne as _NegativeOne, + Half as _Half, + Infinity as _Infinity, + NegativeInfinity as _NegativeInfinity, + ComplexInfinity as _ComplexInfinity, + NaN as _NaN, + ) + + +class SingletonRegistry(Registry): + """ + The registry for the singleton classes (accessible as ``S``). + + Explanation + =========== + + This class serves as two separate things. + + The first thing it is is the ``SingletonRegistry``. Several classes in + SymPy appear so often that they are singletonized, that is, using some + metaprogramming they are made so that they can only be instantiated once + (see the :class:`sympy.core.singleton.Singleton` class for details). For + instance, every time you create ``Integer(0)``, this will return the same + instance, :class:`sympy.core.numbers.Zero`. All singleton instances are + attributes of the ``S`` object, so ``Integer(0)`` can also be accessed as + ``S.Zero``. + + Singletonization offers two advantages: it saves memory, and it allows + fast comparison. It saves memory because no matter how many times the + singletonized objects appear in expressions in memory, they all point to + the same single instance in memory. The fast comparison comes from the + fact that you can use ``is`` to compare exact instances in Python + (usually, you need to use ``==`` to compare things). ``is`` compares + objects by memory address, and is very fast. + + Examples + ======== + + >>> from sympy import S, Integer + >>> a = Integer(0) + >>> a is S.Zero + True + + For the most part, the fact that certain objects are singletonized is an + implementation detail that users should not need to worry about. In SymPy + library code, ``is`` comparison is often used for performance purposes + The primary advantage of ``S`` for end users is the convenient access to + certain instances that are otherwise difficult to type, like ``S.Half`` + (instead of ``Rational(1, 2)``). + + When using ``is`` comparison, make sure the argument is sympified. For + instance, + + >>> x = 0 + >>> x is S.Zero + False + + This problem is not an issue when using ``==``, which is recommended for + most use-cases: + + >>> 0 == S.Zero + True + + The second thing ``S`` is is a shortcut for + :func:`sympy.core.sympify.sympify`. :func:`sympy.core.sympify.sympify` is + the function that converts Python objects such as ``int(1)`` into SymPy + objects such as ``Integer(1)``. It also converts the string form of an + expression into a SymPy expression, like ``sympify("x**2")`` -> + ``Symbol("x")**2``. ``S(1)`` is the same thing as ``sympify(1)`` + (basically, ``S.__call__`` has been defined to call ``sympify``). + + This is for convenience, since ``S`` is a single letter. It's mostly + useful for defining rational numbers. Consider an expression like ``x + + 1/2``. If you enter this directly in Python, it will evaluate the ``1/2`` + and give ``0.5``, because both arguments are ints (see also + :ref:`tutorial-gotchas-final-notes`). However, in SymPy, you usually want + the quotient of two integers to give an exact rational number. The way + Python's evaluation works, at least one side of an operator needs to be a + SymPy object for the SymPy evaluation to take over. You could write this + as ``x + Rational(1, 2)``, but this is a lot more typing. A shorter + version is ``x + S(1)/2``. Since ``S(1)`` returns ``Integer(1)``, the + division will return a ``Rational`` type, since it will call + ``Integer.__truediv__``, which knows how to return a ``Rational``. + + """ + __slots__ = () + + Zero: _Zero + One: _One + NegativeOne: _NegativeOne + Half: _Half + Infinity: _Infinity + NegativeInfinity: _NegativeInfinity + ComplexInfinity: _ComplexInfinity + NaN: _NaN + + # Also allow things like S(5) + __call__ = staticmethod(sympify) + + def __init__(self): + self._classes_to_install = {} + # Dict of classes that have been registered, but that have not have been + # installed as an attribute of this SingletonRegistry. + # Installation automatically happens at the first attempt to access the + # attribute. + # The purpose of this is to allow registration during class + # initialization during import, but not trigger object creation until + # actual use (which should not happen until after all imports are + # finished). + + def register(self, cls): + # Make sure a duplicate class overwrites the old one + if hasattr(self, cls.__name__): + delattr(self, cls.__name__) + self._classes_to_install[cls.__name__] = cls + + def __getattr__(self, name): + """Python calls __getattr__ if no attribute of that name was installed + yet. + + Explanation + =========== + + This __getattr__ checks whether a class with the requested name was + already registered but not installed; if no, raises an AttributeError. + Otherwise, retrieves the class, calculates its singleton value, installs + it as an attribute of the given name, and unregisters the class.""" + if name not in self._classes_to_install: + raise AttributeError( + "Attribute '%s' was not installed on SymPy registry %s" % ( + name, self)) + class_to_install = self._classes_to_install[name] + value_to_install = class_to_install() + self.__setattr__(name, value_to_install) + del self._classes_to_install[name] + return value_to_install + + def __repr__(self): + return "S" + +S = SingletonRegistry() + + +class Singleton(type): + """ + Metaclass for singleton classes. + + Explanation + =========== + + A singleton class has only one instance which is returned every time the + class is instantiated. Additionally, this instance can be accessed through + the global registry object ``S`` as ``S.``. + + Examples + ======== + + >>> from sympy import S, Basic + >>> from sympy.core.singleton import Singleton + >>> class MySingleton(Basic, metaclass=Singleton): + ... pass + >>> Basic() is Basic() + False + >>> MySingleton() is MySingleton() + True + >>> S.MySingleton is MySingleton() + True + + Notes + ===== + + Instance creation is delayed until the first time the value is accessed. + (SymPy versions before 1.0 would create the instance during class + creation time, which would be prone to import cycles.) + """ + def __init__(cls, *args, **kwargs): + cls._instance = obj = Basic.__new__(cls) + cls.__new__ = lambda cls: obj + cls.__getnewargs__ = lambda obj: () + cls.__getstate__ = lambda obj: None + S.register(cls) + + +# Delayed to avoid cyclic import +from .basic import Basic diff --git a/phivenv/Lib/site-packages/sympy/core/sorting.py b/phivenv/Lib/site-packages/sympy/core/sorting.py new file mode 100644 index 0000000000000000000000000000000000000000..399a7efa1f6cbe1ebdf6307c14b411df36fc7de0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/sorting.py @@ -0,0 +1,312 @@ +from collections import defaultdict + +from .sympify import sympify, SympifyError +from sympy.utilities.iterables import iterable, uniq + + +__all__ = ['default_sort_key', 'ordered'] + + +def default_sort_key(item, order=None): + """Return a key that can be used for sorting. + + The key has the structure: + + (class_key, (len(args), args), exponent.sort_key(), coefficient) + + This key is supplied by the sort_key routine of Basic objects when + ``item`` is a Basic object or an object (other than a string) that + sympifies to a Basic object. Otherwise, this function produces the + key. + + The ``order`` argument is passed along to the sort_key routine and is + used to determine how the terms *within* an expression are ordered. + (See examples below) ``order`` options are: 'lex', 'grlex', 'grevlex', + and reversed values of the same (e.g. 'rev-lex'). The default order + value is None (which translates to 'lex'). + + Examples + ======== + + >>> from sympy import S, I, default_sort_key, sin, cos, sqrt + >>> from sympy.core.function import UndefinedFunction + >>> from sympy.abc import x + + The following are equivalent ways of getting the key for an object: + + >>> x.sort_key() == default_sort_key(x) + True + + Here are some examples of the key that is produced: + + >>> default_sort_key(UndefinedFunction('f')) + ((0, 0, 'UndefinedFunction'), (1, ('f',)), ((1, 0, 'Number'), + (0, ()), (), 1), 1) + >>> default_sort_key('1') + ((0, 0, 'str'), (1, ('1',)), ((1, 0, 'Number'), (0, ()), (), 1), 1) + >>> default_sort_key(S.One) + ((1, 0, 'Number'), (0, ()), (), 1) + >>> default_sort_key(2) + ((1, 0, 'Number'), (0, ()), (), 2) + + While sort_key is a method only defined for SymPy objects, + default_sort_key will accept anything as an argument so it is + more robust as a sorting key. For the following, using key= + lambda i: i.sort_key() would fail because 2 does not have a sort_key + method; that's why default_sort_key is used. Note, that it also + handles sympification of non-string items likes ints: + + >>> a = [2, I, -I] + >>> sorted(a, key=default_sort_key) + [2, -I, I] + + The returned key can be used anywhere that a key can be specified for + a function, e.g. sort, min, max, etc...: + + >>> a.sort(key=default_sort_key); a[0] + 2 + >>> min(a, key=default_sort_key) + 2 + + Notes + ===== + + The key returned is useful for getting items into a canonical order + that will be the same across platforms. It is not directly useful for + sorting lists of expressions: + + >>> a, b = x, 1/x + + Since ``a`` has only 1 term, its value of sort_key is unaffected by + ``order``: + + >>> a.sort_key() == a.sort_key('rev-lex') + True + + If ``a`` and ``b`` are combined then the key will differ because there + are terms that can be ordered: + + >>> eq = a + b + >>> eq.sort_key() == eq.sort_key('rev-lex') + False + >>> eq.as_ordered_terms() + [x, 1/x] + >>> eq.as_ordered_terms('rev-lex') + [1/x, x] + + But since the keys for each of these terms are independent of ``order``'s + value, they do not sort differently when they appear separately in a list: + + >>> sorted(eq.args, key=default_sort_key) + [1/x, x] + >>> sorted(eq.args, key=lambda i: default_sort_key(i, order='rev-lex')) + [1/x, x] + + The order of terms obtained when using these keys is the order that would + be obtained if those terms were *factors* in a product. + + Although it is useful for quickly putting expressions in canonical order, + it does not sort expressions based on their complexity defined by the + number of operations, power of variables and others: + + >>> sorted([sin(x)*cos(x), sin(x)], key=default_sort_key) + [sin(x)*cos(x), sin(x)] + >>> sorted([x, x**2, sqrt(x), x**3], key=default_sort_key) + [sqrt(x), x, x**2, x**3] + + See Also + ======== + + ordered, sympy.core.expr.Expr.as_ordered_factors, sympy.core.expr.Expr.as_ordered_terms + + """ + from .basic import Basic + from .singleton import S + + if isinstance(item, Basic): + return item.sort_key(order=order) + + if iterable(item, exclude=str): + if isinstance(item, dict): + args = item.items() + unordered = True + elif isinstance(item, set): + args = item + unordered = True + else: + # e.g. tuple, list + args = list(item) + unordered = False + + args = [default_sort_key(arg, order=order) for arg in args] + + if unordered: + # e.g. dict, set + args = sorted(args) + + cls_index, args = 10, (len(args), tuple(args)) + else: + if not isinstance(item, str): + try: + item = sympify(item, strict=True) + except SympifyError: + # e.g. lambda x: x + pass + else: + if isinstance(item, Basic): + # e.g int -> Integer + return default_sort_key(item) + # e.g. UndefinedFunction + + # e.g. str + cls_index, args = 0, (1, (str(item),)) + + return (cls_index, 0, item.__class__.__name__ + ), args, S.One.sort_key(), S.One + + +def _node_count(e): + # this not only counts nodes, it affirms that the + # args are Basic (i.e. have an args property). If + # some object has a non-Basic arg, it needs to be + # fixed since it is intended that all Basic args + # are of Basic type (though this is not easy to enforce). + if e.is_Float: + return 0.5 + return 1 + sum(map(_node_count, e.args)) + + +def _nodes(e): + """ + A helper for ordered() which returns the node count of ``e`` which + for Basic objects is the number of Basic nodes in the expression tree + but for other objects is 1 (unless the object is an iterable or dict + for which the sum of nodes is returned). + """ + from .basic import Basic + from .function import Derivative + + if isinstance(e, Basic): + if isinstance(e, Derivative): + return _nodes(e.expr) + sum(i[1] if i[1].is_Number else + _nodes(i[1]) for i in e.variable_count) + return _node_count(e) + elif iterable(e): + return 1 + sum(_nodes(ei) for ei in e) + elif isinstance(e, dict): + return 1 + sum(_nodes(k) + _nodes(v) for k, v in e.items()) + else: + return 1 + + +def ordered(seq, keys=None, default=True, warn=False): + """Return an iterator of the seq where keys are used to break ties + in a conservative fashion: if, after applying a key, there are no + ties then no other keys will be computed. + + Two default keys will be applied if 1) keys are not provided or + 2) the given keys do not resolve all ties (but only if ``default`` + is True). The two keys are ``_nodes`` (which places smaller + expressions before large) and ``default_sort_key`` which (if the + ``sort_key`` for an object is defined properly) should resolve + any ties. This strategy is similar to sorting done by + ``Basic.compare``, but differs in that ``ordered`` never makes a + decision based on an objects name. + + If ``warn`` is True then an error will be raised if there were no + keys remaining to break ties. This can be used if it was expected that + there should be no ties between items that are not identical. + + Examples + ======== + + >>> from sympy import ordered, count_ops + >>> from sympy.abc import x, y + + The count_ops is not sufficient to break ties in this list and the first + two items appear in their original order (i.e. the sorting is stable): + + >>> list(ordered([y + 2, x + 2, x**2 + y + 3], + ... count_ops, default=False, warn=False)) + ... + [y + 2, x + 2, x**2 + y + 3] + + The default_sort_key allows the tie to be broken: + + >>> list(ordered([y + 2, x + 2, x**2 + y + 3])) + ... + [x + 2, y + 2, x**2 + y + 3] + + Here, sequences are sorted by length, then sum: + + >>> seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], [ + ... lambda x: len(x), + ... lambda x: sum(x)]] + ... + >>> list(ordered(seq, keys, default=False, warn=False)) + [[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]] + + If ``warn`` is True, an error will be raised if there were not + enough keys to break ties: + + >>> list(ordered(seq, keys, default=False, warn=True)) + Traceback (most recent call last): + ... + ValueError: not enough keys to break ties + + + Notes + ===== + + The decorated sort is one of the fastest ways to sort a sequence for + which special item comparison is desired: the sequence is decorated, + sorted on the basis of the decoration (e.g. making all letters lower + case) and then undecorated. If one wants to break ties for items that + have the same decorated value, a second key can be used. But if the + second key is expensive to compute then it is inefficient to decorate + all items with both keys: only those items having identical first key + values need to be decorated. This function applies keys successively + only when needed to break ties. By yielding an iterator, use of the + tie-breaker is delayed as long as possible. + + This function is best used in cases when use of the first key is + expected to be a good hashing function; if there are no unique hashes + from application of a key, then that key should not have been used. The + exception, however, is that even if there are many collisions, if the + first group is small and one does not need to process all items in the + list then time will not be wasted sorting what one was not interested + in. For example, if one were looking for the minimum in a list and + there were several criteria used to define the sort order, then this + function would be good at returning that quickly if the first group + of candidates is small relative to the number of items being processed. + + """ + + d = defaultdict(list) + if keys: + if isinstance(keys, (list, tuple)): + keys = list(keys) + f = keys.pop(0) + else: + f = keys + keys = [] + for a in seq: + d[f(a)].append(a) + else: + if not default: + raise ValueError('if default=False then keys must be provided') + d[None].extend(seq) + + for k, value in sorted(d.items()): + if len(value) > 1: + if keys: + value = ordered(value, keys, default, warn) + elif default: + value = ordered(value, (_nodes, default_sort_key,), + default=False, warn=warn) + elif warn: + u = list(uniq(value)) + if len(u) > 1: + raise ValueError( + 'not enough keys to break ties: %s' % u) + yield from value diff --git a/phivenv/Lib/site-packages/sympy/core/symbol.py b/phivenv/Lib/site-packages/sympy/core/symbol.py new file mode 100644 index 0000000000000000000000000000000000000000..2e03ff0c84c1668b70ec5b3d7f8bc854a2e5e4ac --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/symbol.py @@ -0,0 +1,993 @@ +from __future__ import annotations + + +from .assumptions import StdFactKB, _assume_defined +from .basic import Basic, Atom +from .cache import cacheit +from .containers import Tuple +from .expr import Expr, AtomicExpr +from .function import AppliedUndef, FunctionClass +from .kind import NumberKind, UndefinedKind +from .logic import fuzzy_bool +from .singleton import S +from .sorting import ordered +from .sympify import sympify +from sympy.logic.boolalg import Boolean +from sympy.utilities.iterables import sift, is_sequence +from sympy.utilities.misc import filldedent + +import string +import re as _re +import random +from itertools import product +from typing import Any + + +class Str(Atom): + """ + Represents string in SymPy. + + Explanation + =========== + + Previously, ``Symbol`` was used where string is needed in ``args`` of SymPy + objects, e.g. denoting the name of the instance. However, since ``Symbol`` + represents mathematical scalar, this class should be used instead. + + """ + __slots__ = ('name',) + + def __new__(cls, name, **kwargs): + if not isinstance(name, str): + raise TypeError("name should be a string, not %s" % repr(type(name))) + obj = Expr.__new__(cls, **kwargs) + obj.name = name + return obj + + def __getnewargs__(self): + return (self.name,) + + def _hashable_content(self): + return (self.name,) + + +def _filter_assumptions(kwargs): + """Split the given dict into assumptions and non-assumptions. + Keys are taken as assumptions if they correspond to an + entry in ``_assume_defined``. + """ + assumptions, nonassumptions = map(dict, sift(kwargs.items(), + lambda i: i[0] in _assume_defined, + binary=True)) + Symbol._sanitize(assumptions) + return assumptions, nonassumptions + +def _symbol(s, matching_symbol=None, **assumptions): + """Return s if s is a Symbol, else if s is a string, return either + the matching_symbol if the names are the same or else a new symbol + with the same assumptions as the matching symbol (or the + assumptions as provided). + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy.core.symbol import _symbol + >>> _symbol('y') + y + >>> _.is_real is None + True + >>> _symbol('y', real=True).is_real + True + + >>> x = Symbol('x') + >>> _symbol(x, real=True) + x + >>> _.is_real is None # ignore attribute if s is a Symbol + True + + Below, the variable sym has the name 'foo': + + >>> sym = Symbol('foo', real=True) + + Since 'x' is not the same as sym's name, a new symbol is created: + + >>> _symbol('x', sym).name + 'x' + + It will acquire any assumptions give: + + >>> _symbol('x', sym, real=False).is_real + False + + Since 'foo' is the same as sym's name, sym is returned + + >>> _symbol('foo', sym) + foo + + Any assumptions given are ignored: + + >>> _symbol('foo', sym, real=False).is_real + True + + NB: the symbol here may not be the same as a symbol with the same + name defined elsewhere as a result of different assumptions. + + See Also + ======== + + sympy.core.symbol.Symbol + + """ + if isinstance(s, str): + if matching_symbol and matching_symbol.name == s: + return matching_symbol + return Symbol(s, **assumptions) + elif isinstance(s, Symbol): + return s + else: + raise ValueError('symbol must be string for symbol name or Symbol') + +def uniquely_named_symbol(xname, exprs=(), compare=str, modify=None, **assumptions): + """ + Return a symbol whose name is derivated from *xname* but is unique + from any other symbols in *exprs*. + + *xname* and symbol names in *exprs* are passed to *compare* to be + converted to comparable forms. If ``compare(xname)`` is not unique, + it is recursively passed to *modify* until unique name is acquired. + + Parameters + ========== + + xname : str or Symbol + Base name for the new symbol. + + exprs : Expr or iterable of Expr + Expressions whose symbols are compared to *xname*. + + compare : function + Unary function which transforms *xname* and symbol names from + *exprs* to comparable form. + + modify : function + Unary function which modifies the string. Default is appending + the number, or increasing the number if exists. + + Examples + ======== + + By default, a number is appended to *xname* to generate unique name. + If the number already exists, it is recursively increased. + + >>> from sympy.core.symbol import uniquely_named_symbol, Symbol + >>> uniquely_named_symbol('x', Symbol('x')) + x0 + >>> uniquely_named_symbol('x', (Symbol('x'), Symbol('x0'))) + x1 + >>> uniquely_named_symbol('x0', (Symbol('x1'), Symbol('x0'))) + x2 + + Name generation can be controlled by passing *modify* parameter. + + >>> from sympy.abc import x + >>> uniquely_named_symbol('x', x, modify=lambda s: 2*s) + xx + + """ + def numbered_string_incr(s, start=0): + if not s: + return str(start) + i = len(s) - 1 + while i != -1: + if not s[i].isdigit(): + break + i -= 1 + n = str(int(s[i + 1:] or start - 1) + 1) + return s[:i + 1] + n + + default = None + if is_sequence(xname): + xname, default = xname + x = compare(xname) + if not exprs: + return _symbol(x, default, **assumptions) + if not is_sequence(exprs): + exprs = [exprs] + names = set().union( + [i.name for e in exprs for i in e.atoms(Symbol)] + + [i.func.name for e in exprs for i in e.atoms(AppliedUndef)]) + if modify is None: + modify = numbered_string_incr + while any(x == compare(s) for s in names): + x = modify(x) + return _symbol(x, default, **assumptions) +_uniquely_named_symbol = uniquely_named_symbol + + +# XXX: We need type: ignore below because Expr and Boolean are incompatible as +# superclasses. Really Symbol should not be a subclass of Boolean. + + +class Symbol(AtomicExpr, Boolean): # type: ignore + """ + Symbol class is used to create symbolic variables. + + Explanation + =========== + + Symbolic variables are placeholders for mathematical symbols that can represent numbers, constants, or any other mathematical entities and can be used in mathematical expressions and to perform symbolic computations. + + Assumptions: + + commutative = True + positive = True + real = True + imaginary = True + complex = True + complete list of more assumptions- :ref:`predicates` + + You can override the default assumptions in the constructor. + + Examples + ======== + + >>> from sympy import Symbol + >>> x = Symbol("x", positive=True) + >>> x.is_positive + True + >>> x.is_negative + False + + passing in greek letters: + + >>> from sympy import Symbol + >>> alpha = Symbol('alpha') + >>> alpha #doctest: +SKIP + α + + Trailing digits are automatically treated like subscripts of what precedes them in the name. + General format to add subscript to a symbol : + `` = Symbol('_')`` + + >>> from sympy import Symbol + >>> alpha_i = Symbol('alpha_i') + >>> alpha_i #doctest: +SKIP + αᵢ + + Parameters + ========== + + AtomicExpr: variable name + Boolean: Assumption with a boolean value(True or False) + """ + + is_comparable = False + + __slots__ = ('name', '_assumptions_orig', '_assumptions0') + + name: str + + is_Symbol = True + is_symbol = True + + @property + def kind(self): + if self.is_commutative: + return NumberKind + return UndefinedKind + + @property + def _diff_wrt(self): + """Allow derivatives wrt Symbols. + + Examples + ======== + + >>> from sympy import Symbol + >>> x = Symbol('x') + >>> x._diff_wrt + True + """ + return True + + @staticmethod + def _sanitize(assumptions, obj=None): + """Remove None, convert values to bool, check commutativity *in place*. + """ + + # be strict about commutativity: cannot be None + is_commutative = fuzzy_bool(assumptions.get('commutative', True)) + if is_commutative is None: + whose = '%s ' % obj.__name__ if obj else '' + raise ValueError( + '%scommutativity must be True or False.' % whose) + + # sanitize other assumptions so 1 -> True and 0 -> False + for key in list(assumptions.keys()): + v = assumptions[key] + if v is None: + assumptions.pop(key) + continue + assumptions[key] = bool(v) + + def _merge(self, assumptions): + base = self.assumptions0 + for k in set(assumptions) & set(base): + if assumptions[k] != base[k]: + raise ValueError(filldedent(''' + non-matching assumptions for %s: existing value + is %s and new value is %s''' % ( + k, base[k], assumptions[k]))) + base.update(assumptions) + return base + + def __new__(cls, name, **assumptions): + """Symbols are identified by name and assumptions:: + + >>> from sympy import Symbol + >>> Symbol("x") == Symbol("x") + True + >>> Symbol("x", real=True) == Symbol("x", real=False) + False + + """ + cls._sanitize(assumptions, cls) + return Symbol.__xnew_cached_(cls, name, **assumptions) + + + @staticmethod + @cacheit + def _canonical_assumptions(**assumptions): + # This is retained purely so that srepr can include commutative=True if + # that was explicitly specified but not if it was not. Ideally srepr + # should not distinguish these cases because the symbols otherwise + # compare equal and are considered equivalent. + # + # See https://github.com/sympy/sympy/issues/8873 + # + assumptions_orig = assumptions.copy() + + # The only assumption that is assumed by default is commutative=True: + assumptions.setdefault('commutative', True) + + assumptions_kb = StdFactKB(assumptions) + assumptions0 = dict(assumptions_kb) + + return assumptions_kb, assumptions_orig, assumptions0 + + @staticmethod + def __xnew__(cls, name, **assumptions): # never cached (e.g. dummy) + if not isinstance(name, str): + raise TypeError("name should be a string, not %s" % repr(type(name))) + + + obj = Expr.__new__(cls) + obj.name = name + + assumptions_kb, assumptions_orig, assumptions0 = Symbol._canonical_assumptions(**assumptions) + + obj._assumptions = assumptions_kb + obj._assumptions_orig = assumptions_orig + obj._assumptions0 = tuple(sorted(assumptions0.items())) + + # The three assumptions dicts are all a little different: + # + # >>> from sympy import Symbol + # >>> x = Symbol('x', finite=True) + # >>> x.is_positive # query an assumption + # >>> x._assumptions + # {'finite': True, 'infinite': False, 'commutative': True, 'positive': None} + # >>> x._assumptions0 + # {'finite': True, 'infinite': False, 'commutative': True} + # >>> x._assumptions_orig + # {'finite': True} + # + # Two symbols with the same name are equal if their _assumptions0 are + # the same. Arguably it should be _assumptions_orig that is being + # compared because that is more transparent to the user (it is + # what was passed to the constructor modulo changes made by _sanitize). + + return obj + + @staticmethod + @cacheit + def __xnew_cached_(cls, name, **assumptions): # symbols are always cached + return Symbol.__xnew__(cls, name, **assumptions) + + def __getnewargs_ex__(self): + return ((self.name,), self._assumptions_orig) + + # NOTE: __setstate__ is not needed for pickles created by __getnewargs_ex__ + # but was used before Symbol was changed to use __getnewargs_ex__ in v1.9. + # Pickles created in previous SymPy versions will still need __setstate__ + # so that they can be unpickled in SymPy > v1.9. + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + def _hashable_content(self): + return (self.name,) + self._assumptions0 + + def _eval_subs(self, old, new): + if old.is_Pow: + from sympy.core.power import Pow + return Pow(self, S.One, evaluate=False)._eval_subs(old, new) + + def _eval_refine(self, assumptions): + return self + + @property + def assumptions0(self): + return dict(self._assumptions0) + + @cacheit + def sort_key(self, order=None): + return self.class_key(), (1, (self.name,)), S.One.sort_key(), S.One + + def as_dummy(self): + # only put commutativity in explicitly if it is False + return Dummy(self.name) if self.is_commutative is not False \ + else Dummy(self.name, commutative=self.is_commutative) + + def as_real_imag(self, deep=True, **hints): + if hints.get('ignore') == self: + return None + else: + from sympy.functions.elementary.complexes import im, re + return (re(self), im(self)) + + def is_constant(self, *wrt, **flags): + if not wrt: + return False + return self not in wrt + + @property + def free_symbols(self): + return {self} + + binary_symbols = free_symbols # in this case, not always + + def as_set(self): + return S.UniversalSet + + +class Dummy(Symbol): + """Dummy symbols are each unique, even if they have the same name: + + Examples + ======== + + >>> from sympy import Dummy + >>> Dummy("x") == Dummy("x") + False + + If a name is not supplied then a string value of an internal count will be + used. This is useful when a temporary variable is needed and the name + of the variable used in the expression is not important. + + >>> Dummy() #doctest: +SKIP + _Dummy_10 + + """ + + # In the rare event that a Dummy object needs to be recreated, both the + # `name` and `dummy_index` should be passed. This is used by `srepr` for + # example: + # >>> d1 = Dummy() + # >>> d2 = eval(srepr(d1)) + # >>> d2 == d1 + # True + # + # If a new session is started between `srepr` and `eval`, there is a very + # small chance that `d2` will be equal to a previously-created Dummy. + + _count = 0 + _prng = random.Random() + _base_dummy_index = _prng.randint(10**6, 9*10**6) + + __slots__ = ('dummy_index',) + + is_Dummy = True + + def __new__(cls, name=None, dummy_index=None, **assumptions): + if dummy_index is not None: + assert name is not None, "If you specify a dummy_index, you must also provide a name" + + if name is None: + name = "Dummy_" + str(Dummy._count) + + if dummy_index is None: + dummy_index = Dummy._base_dummy_index + Dummy._count + Dummy._count += 1 + + cls._sanitize(assumptions, cls) + obj = Symbol.__xnew__(cls, name, **assumptions) + + obj.dummy_index = dummy_index + + return obj + + def __getnewargs_ex__(self): + return ((self.name, self.dummy_index), self._assumptions_orig) + + @cacheit + def sort_key(self, order=None): + return self.class_key(), ( + 2, (self.name, self.dummy_index)), S.One.sort_key(), S.One + + def _hashable_content(self): + return Symbol._hashable_content(self) + (self.dummy_index,) + + +class Wild(Symbol): + """ + A Wild symbol matches anything, or anything + without whatever is explicitly excluded. + + Parameters + ========== + + name : str + Name of the Wild instance. + + exclude : iterable, optional + Instances in ``exclude`` will not be matched. + + properties : iterable of functions, optional + Functions, each taking an expressions as input + and returns a ``bool``. All functions in ``properties`` + need to return ``True`` in order for the Wild instance + to match the expression. + + Examples + ======== + + >>> from sympy import Wild, WildFunction, cos, pi + >>> from sympy.abc import x, y, z + >>> a = Wild('a') + >>> x.match(a) + {a_: x} + >>> pi.match(a) + {a_: pi} + >>> (3*x**2).match(a*x) + {a_: 3*x} + >>> cos(x).match(a) + {a_: cos(x)} + >>> b = Wild('b', exclude=[x]) + >>> (3*x**2).match(b*x) + >>> b.match(a) + {a_: b_} + >>> A = WildFunction('A') + >>> A.match(a) + {a_: A_} + + Tips + ==== + + When using Wild, be sure to use the exclude + keyword to make the pattern more precise. + Without the exclude pattern, you may get matches + that are technically correct, but not what you + wanted. For example, using the above without + exclude: + + >>> from sympy import symbols + >>> a, b = symbols('a b', cls=Wild) + >>> (2 + 3*y).match(a*x + b*y) + {a_: 2/x, b_: 3} + + This is technically correct, because + (2/x)*x + 3*y == 2 + 3*y, but you probably + wanted it to not match at all. The issue is that + you really did not want a and b to include x and y, + and the exclude parameter lets you specify exactly + this. With the exclude parameter, the pattern will + not match. + + >>> a = Wild('a', exclude=[x, y]) + >>> b = Wild('b', exclude=[x, y]) + >>> (2 + 3*y).match(a*x + b*y) + + Exclude also helps remove ambiguity from matches. + + >>> E = 2*x**3*y*z + >>> a, b = symbols('a b', cls=Wild) + >>> E.match(a*b) + {a_: 2*y*z, b_: x**3} + >>> a = Wild('a', exclude=[x, y]) + >>> E.match(a*b) + {a_: z, b_: 2*x**3*y} + >>> a = Wild('a', exclude=[x, y, z]) + >>> E.match(a*b) + {a_: 2, b_: x**3*y*z} + + Wild also accepts a ``properties`` parameter: + + >>> a = Wild('a', properties=[lambda k: k.is_Integer]) + >>> E.match(a*b) + {a_: 2, b_: x**3*y*z} + + """ + is_Wild = True + + __slots__ = ('exclude', 'properties') + + def __new__(cls, name, exclude=(), properties=(), **assumptions): + exclude = tuple([sympify(x) for x in exclude]) + properties = tuple(properties) + cls._sanitize(assumptions, cls) + return Wild.__xnew__(cls, name, exclude, properties, **assumptions) + + def __getnewargs__(self): + return (self.name, self.exclude, self.properties) + + @staticmethod + @cacheit + def __xnew__(cls, name, exclude, properties, **assumptions): + obj = Symbol.__xnew__(cls, name, **assumptions) + obj.exclude = exclude + obj.properties = properties + return obj + + def _hashable_content(self): + return super()._hashable_content() + (self.exclude, self.properties) + + # TODO add check against another Wild + def matches(self, expr, repl_dict=None, old=False): + if any(expr.has(x) for x in self.exclude): + return None + if not all(f(expr) for f in self.properties): + return None + if repl_dict is None: + repl_dict = {} + else: + repl_dict = repl_dict.copy() + repl_dict[self] = expr + return repl_dict + + +_range = _re.compile('([0-9]*:[0-9]+|[a-zA-Z]?:[a-zA-Z])') + + +def symbols(names, *, cls=Symbol, **args) -> Any: + r""" + Transform strings into instances of :class:`Symbol` class. + + :func:`symbols` function returns a sequence of symbols with names taken + from ``names`` argument, which can be a comma or whitespace delimited + string, or a sequence of strings:: + + >>> from sympy import symbols, Function + + >>> x, y, z = symbols('x,y,z') + >>> a, b, c = symbols('a b c') + + The type of output is dependent on the properties of input arguments:: + + >>> symbols('x') + x + >>> symbols('x,') + (x,) + >>> symbols('x,y') + (x, y) + >>> symbols(('a', 'b', 'c')) + (a, b, c) + >>> symbols(['a', 'b', 'c']) + [a, b, c] + >>> symbols({'a', 'b', 'c'}) + {a, b, c} + + If an iterable container is needed for a single symbol, set the ``seq`` + argument to ``True`` or terminate the symbol name with a comma:: + + >>> symbols('x', seq=True) + (x,) + + To reduce typing, range syntax is supported to create indexed symbols. + Ranges are indicated by a colon and the type of range is determined by + the character to the right of the colon. If the character is a digit + then all contiguous digits to the left are taken as the nonnegative + starting value (or 0 if there is no digit left of the colon) and all + contiguous digits to the right are taken as 1 greater than the ending + value:: + + >>> symbols('x:10') + (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) + + >>> symbols('x5:10') + (x5, x6, x7, x8, x9) + >>> symbols('x5(:2)') + (x50, x51) + + >>> symbols('x5:10,y:5') + (x5, x6, x7, x8, x9, y0, y1, y2, y3, y4) + + >>> symbols(('x5:10', 'y:5')) + ((x5, x6, x7, x8, x9), (y0, y1, y2, y3, y4)) + + If the character to the right of the colon is a letter, then the single + letter to the left (or 'a' if there is none) is taken as the start + and all characters in the lexicographic range *through* the letter to + the right are used as the range:: + + >>> symbols('x:z') + (x, y, z) + >>> symbols('x:c') # null range + () + >>> symbols('x(:c)') + (xa, xb, xc) + + >>> symbols(':c') + (a, b, c) + + >>> symbols('a:d, x:z') + (a, b, c, d, x, y, z) + + >>> symbols(('a:d', 'x:z')) + ((a, b, c, d), (x, y, z)) + + Multiple ranges are supported; contiguous numerical ranges should be + separated by parentheses to disambiguate the ending number of one + range from the starting number of the next:: + + >>> symbols('x:2(1:3)') + (x01, x02, x11, x12) + >>> symbols(':3:2') # parsing is from left to right + (00, 01, 10, 11, 20, 21) + + Only one pair of parentheses surrounding ranges are removed, so to + include parentheses around ranges, double them. And to include spaces, + commas, or colons, escape them with a backslash:: + + >>> symbols('x((a:b))') + (x(a), x(b)) + >>> symbols(r'x(:1\,:2)') # or r'x((:1)\,(:2))' + (x(0,0), x(0,1)) + + All newly created symbols have assumptions set according to ``args``:: + + >>> a = symbols('a', integer=True) + >>> a.is_integer + True + + >>> x, y, z = symbols('x,y,z', real=True) + >>> x.is_real and y.is_real and z.is_real + True + + Despite its name, :func:`symbols` can create symbol-like objects like + instances of Function or Wild classes. To achieve this, set ``cls`` + keyword argument to the desired type:: + + >>> symbols('f,g,h', cls=Function) + (f, g, h) + + >>> type(_[0]) + + + """ + result = [] + + if isinstance(names, str): + marker = 0 + splitters = r'\,', r'\:', r'\ ' + literals: list[tuple[str, str]] = [] + for splitter in splitters: + if splitter in names: + while chr(marker) in names: + marker += 1 + lit_char = chr(marker) + marker += 1 + names = names.replace(splitter, lit_char) + literals.append((lit_char, splitter[1:])) + def literal(s): + if literals: + for c, l in literals: + s = s.replace(c, l) + return s + + names = names.strip() + as_seq = names.endswith(',') + if as_seq: + names = names[:-1].rstrip() + if not names: + raise ValueError('no symbols given') + + # split on commas + names = [n.strip() for n in names.split(',')] + if not all(n for n in names): + raise ValueError('missing symbol between commas') + # split on spaces + for i in range(len(names) - 1, -1, -1): + names[i: i + 1] = names[i].split() + + seq = args.pop('seq', as_seq) + + for name in names: + if not name: + raise ValueError('missing symbol') + + if ':' not in name: + symbol = cls(literal(name), **args) + result.append(symbol) + continue + + split: list[str] = _range.split(name) + split_list: list[list[str]] = [] + # remove 1 layer of bounding parentheses around ranges + for i in range(len(split) - 1): + if i and ':' in split[i] and split[i] != ':' and \ + split[i - 1].endswith('(') and \ + split[i + 1].startswith(')'): + split[i - 1] = split[i - 1][:-1] + split[i + 1] = split[i + 1][1:] + for s in split: + if ':' in s: + if s.endswith(':'): + raise ValueError('missing end range') + a, b = s.split(':') + if b[-1] in string.digits: + a_i = 0 if not a else int(a) + b_i = int(b) + split_list.append([str(c) for c in range(a_i, b_i)]) + else: + a = a or 'a' + split_list.append([string.ascii_letters[c] for c in range( + string.ascii_letters.index(a), + string.ascii_letters.index(b) + 1)]) # inclusive + if not split_list[-1]: + break + else: + split_list.append([s]) + else: + seq = True + if len(split_list) == 1: + names = split_list[0] + else: + names = [''.join(s) for s in product(*split_list)] + if literals: + result.extend([cls(literal(s), **args) for s in names]) + else: + result.extend([cls(s, **args) for s in names]) + + if not seq and len(result) <= 1: + if not result: + return () + return result[0] + + return tuple(result) + else: + for name in names: + result.append(symbols(name, cls=cls, **args)) + + return type(names)(result) + + +def var(names, **args): + """ + Create symbols and inject them into the global namespace. + + Explanation + =========== + + This calls :func:`symbols` with the same arguments and puts the results + into the *global* namespace. It's recommended not to use :func:`var` in + library code, where :func:`symbols` has to be used:: + + Examples + ======== + + >>> from sympy import var + + >>> var('x') + x + >>> x # noqa: F821 + x + + >>> var('a,ab,abc') + (a, ab, abc) + >>> abc # noqa: F821 + abc + + >>> var('x,y', real=True) + (x, y) + >>> x.is_real and y.is_real # noqa: F821 + True + + See :func:`symbols` documentation for more details on what kinds of + arguments can be passed to :func:`var`. + + """ + def traverse(symbols, frame): + """Recursively inject symbols to the global namespace. """ + for symbol in symbols: + if isinstance(symbol, Basic): + frame.f_globals[symbol.name] = symbol + elif isinstance(symbol, FunctionClass): + frame.f_globals[symbol.__name__] = symbol + else: + traverse(symbol, frame) + + from inspect import currentframe + frame = currentframe().f_back + + try: + syms = symbols(names, **args) + + if syms is not None: + if isinstance(syms, Basic): + frame.f_globals[syms.name] = syms + elif isinstance(syms, FunctionClass): + frame.f_globals[syms.__name__] = syms + else: + traverse(syms, frame) + finally: + del frame # break cyclic dependencies as stated in inspect docs + + return syms + +def disambiguate(*iter): + """ + Return a Tuple containing the passed expressions with symbols + that appear the same when printed replaced with numerically + subscripted symbols, and all Dummy symbols replaced with Symbols. + + Parameters + ========== + + iter: list of symbols or expressions. + + Examples + ======== + + >>> from sympy.core.symbol import disambiguate + >>> from sympy import Dummy, Symbol, Tuple + >>> from sympy.abc import y + + >>> tup = Symbol('_x'), Dummy('x'), Dummy('x') + >>> disambiguate(*tup) + (x_2, x, x_1) + + >>> eqs = Tuple(Symbol('x')/y, Dummy('x')/y) + >>> disambiguate(*eqs) + (x_1/y, x/y) + + >>> ix = Symbol('x', integer=True) + >>> vx = Symbol('x') + >>> disambiguate(vx + ix) + (x + x_1,) + + To make your own mapping of symbols to use, pass only the free symbols + of the expressions and create a dictionary: + + >>> free = eqs.free_symbols + >>> mapping = dict(zip(free, disambiguate(*free))) + >>> eqs.xreplace(mapping) + (x_1/y, x/y) + + """ + new_iter = Tuple(*iter) + key = lambda x:tuple(sorted(x.assumptions0.items())) + syms = ordered(new_iter.free_symbols, keys=key) + mapping = {} + for s in syms: + mapping.setdefault(str(s).lstrip('_'), []).append(s) + reps = {} + for k in mapping: + # the first or only symbol doesn't get subscripted but make + # sure that it's a Symbol, not a Dummy + mapk0 = Symbol("%s" % (k), **mapping[k][0].assumptions0) + if mapping[k][0] != mapk0: + reps[mapping[k][0]] = mapk0 + # the others get subscripts (and are made into Symbols) + skip = 0 + for i in range(1, len(mapping[k])): + while True: + name = "%s_%i" % (k, i + skip) + if name not in mapping: + break + skip += 1 + ki = mapping[k][i] + reps[ki] = Symbol(name, **ki.assumptions0) + return new_iter.xreplace(reps) diff --git a/phivenv/Lib/site-packages/sympy/core/sympify.py b/phivenv/Lib/site-packages/sympy/core/sympify.py new file mode 100644 index 0000000000000000000000000000000000000000..df30fbb85d5f160540312de4eef1d0e6702fc974 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/sympify.py @@ -0,0 +1,646 @@ +"""sympify -- convert objects SymPy internal format""" + +from __future__ import annotations + +from typing import Any, Callable, overload, TYPE_CHECKING, TypeVar + +import mpmath.libmp as mlib + +from inspect import getmro +import string +from sympy.core.random import choice + +from .parameters import global_parameters + +from sympy.utilities.iterables import iterable + + +if TYPE_CHECKING: + + from sympy.core.basic import Basic + from sympy.core.expr import Expr + from sympy.core.numbers import Integer, Float + + Tbasic = TypeVar('Tbasic', bound=Basic) + + +class SympifyError(ValueError): + def __init__(self, expr, base_exc=None): + self.expr = expr + self.base_exc = base_exc + + def __str__(self): + if self.base_exc is None: + return "SympifyError: %r" % (self.expr,) + + return ("Sympify of expression '%s' failed, because of exception being " + "raised:\n%s: %s" % (self.expr, self.base_exc.__class__.__name__, + str(self.base_exc))) + + +converter: dict[type[Any], Callable[[Any], Basic]] = {} + +#holds the conversions defined in SymPy itself, i.e. non-user defined conversions +_sympy_converter: dict[type[Any], Callable[[Any], Basic]] = {} + +#alias for clearer use in the library +_external_converter = converter + +class CantSympify: + """ + Mix in this trait to a class to disallow sympification of its instances. + + Examples + ======== + + >>> from sympy import sympify + >>> from sympy.core.sympify import CantSympify + + >>> class Something(dict): + ... pass + ... + >>> sympify(Something()) + {} + + >>> class Something(dict, CantSympify): + ... pass + ... + >>> sympify(Something()) + Traceback (most recent call last): + ... + SympifyError: SympifyError: {} + + """ + + __slots__ = () + + +def _is_numpy_instance(a): + """ + Checks if an object is an instance of a type from the numpy module. + """ + # This check avoids unnecessarily importing NumPy. We check the whole + # __mro__ in case any base type is a numpy type. + return any(type_.__module__ == 'numpy' + for type_ in type(a).__mro__) + + +def _convert_numpy_types(a, **sympify_args): + """ + Converts a numpy datatype input to an appropriate SymPy type. + """ + import numpy as np + if not isinstance(a, np.floating): + if np.iscomplex(a): + return _sympy_converter[complex](a.item()) + else: + return sympify(a.item(), **sympify_args) + else: + from .numbers import Float + prec = np.finfo(a).nmant + 1 + # E.g. double precision means prec=53 but nmant=52 + # Leading bit of mantissa is always 1, so is not stored + if np.isposinf(a): + return Float('inf') + elif np.isneginf(a): + return Float('-inf') + else: + p, q = a.as_integer_ratio() + a = mlib.from_rational(p, q, prec) + return Float(a, precision=prec) + + +@overload +def sympify(a: int, *, strict: bool = False) -> Integer: ... # type: ignore +@overload +def sympify(a: float, *, strict: bool = False) -> Float: ... +@overload +def sympify(a: Expr | complex, *, strict: bool = False) -> Expr: ... +@overload +def sympify(a: Tbasic, *, strict: bool = False) -> Tbasic: ... +@overload +def sympify(a: Any, *, strict: bool = False) -> Basic: ... + +def sympify(a, locals=None, convert_xor=True, strict=False, rational=False, + evaluate=None): + """ + Converts an arbitrary expression to a type that can be used inside SymPy. + + Explanation + =========== + + It will convert Python ints into instances of :class:`~.Integer`, floats + into instances of :class:`~.Float`, etc. It is also able to coerce + symbolic expressions which inherit from :class:`~.Basic`. This can be + useful in cooperation with SAGE. + + .. warning:: + Note that this function uses ``eval``, and thus shouldn't be used on + unsanitized input. + + If the argument is already a type that SymPy understands, it will do + nothing but return that value. This can be used at the beginning of a + function to ensure you are working with the correct type. + + Examples + ======== + + >>> from sympy import sympify + + >>> sympify(2).is_integer + True + >>> sympify(2).is_real + True + + >>> sympify(2.0).is_real + True + >>> sympify("2.0").is_real + True + >>> sympify("2e-45").is_real + True + + If the expression could not be converted, a SympifyError is raised. + + >>> sympify("x***2") + Traceback (most recent call last): + ... + SympifyError: SympifyError: "could not parse 'x***2'" + + When attempting to parse non-Python syntax using ``sympify``, it raises a + ``SympifyError``: + + >>> sympify("2x+1") + Traceback (most recent call last): + ... + SympifyError: Sympify of expression 'could not parse '2x+1'' failed + + To parse non-Python syntax, use ``parse_expr`` from ``sympy.parsing.sympy_parser``. + + >>> from sympy.parsing.sympy_parser import parse_expr + >>> parse_expr("2x+1", transformations="all") + 2*x + 1 + + For more details about ``transformations``: see :func:`~sympy.parsing.sympy_parser.parse_expr` + + Locals + ------ + + The sympification happens with access to everything that is loaded + by ``from sympy import *``; anything used in a string that is not + defined by that import will be converted to a symbol. In the following, + the ``bitcount`` function is treated as a symbol and the ``O`` is + interpreted as the :class:`~.Order` object (used with series) and it raises + an error when used improperly: + + >>> s = 'bitcount(42)' + >>> sympify(s) + bitcount(42) + >>> sympify("O(x)") + O(x) + >>> sympify("O + 1") + Traceback (most recent call last): + ... + TypeError: unbound method... + + In order to have ``bitcount`` be recognized it can be imported into a + namespace dictionary and passed as locals: + + >>> ns = {} + >>> exec('from sympy.core.evalf import bitcount', ns) + >>> sympify(s, locals=ns) + 6 + + In order to have the ``O`` interpreted as a Symbol, identify it as such + in the namespace dictionary. This can be done in a variety of ways; all + three of the following are possibilities: + + >>> from sympy import Symbol + >>> ns["O"] = Symbol("O") # method 1 + >>> exec('from sympy.abc import O', ns) # method 2 + >>> ns.update(dict(O=Symbol("O"))) # method 3 + >>> sympify("O + 1", locals=ns) + O + 1 + + If you want *all* single-letter and Greek-letter variables to be symbols + then you can use the clashing-symbols dictionaries that have been defined + there as private variables: ``_clash1`` (single-letter variables), + ``_clash2`` (the multi-letter Greek names) or ``_clash`` (both single and + multi-letter names that are defined in ``abc``). + + >>> from sympy.abc import _clash1 + >>> set(_clash1) # if this fails, see issue #23903 + {'E', 'I', 'N', 'O', 'Q', 'S'} + >>> sympify('I & Q', _clash1) + I & Q + + Strict + ------ + + If the option ``strict`` is set to ``True``, only the types for which an + explicit conversion has been defined are converted. In the other + cases, a SympifyError is raised. + + >>> print(sympify(None)) + None + >>> sympify(None, strict=True) + Traceback (most recent call last): + ... + SympifyError: SympifyError: None + + .. deprecated:: 1.6 + + ``sympify(obj)`` automatically falls back to ``str(obj)`` when all + other conversion methods fail, but this is deprecated. ``strict=True`` + will disable this deprecated behavior. See + :ref:`deprecated-sympify-string-fallback`. + + Evaluation + ---------- + + If the option ``evaluate`` is set to ``False``, then arithmetic and + operators will be converted into their SymPy equivalents and the + ``evaluate=False`` option will be added. Nested ``Add`` or ``Mul`` will + be denested first. This is done via an AST transformation that replaces + operators with their SymPy equivalents, so if an operand redefines any + of those operations, the redefined operators will not be used. If + argument a is not a string, the mathematical expression is evaluated + before being passed to sympify, so adding ``evaluate=False`` will still + return the evaluated result of expression. + + >>> sympify('2**2 / 3 + 5') + 19/3 + >>> sympify('2**2 / 3 + 5', evaluate=False) + 2**2/3 + 5 + >>> sympify('4/2+7', evaluate=True) + 9 + >>> sympify('4/2+7', evaluate=False) + 4/2 + 7 + >>> sympify(4/2+7, evaluate=False) + 9.00000000000000 + + Extending + --------- + + To extend ``sympify`` to convert custom objects (not derived from ``Basic``), + just define a ``_sympy_`` method to your class. You can do that even to + classes that you do not own by subclassing or adding the method at runtime. + + >>> from sympy import Matrix + >>> class MyList1(object): + ... def __iter__(self): + ... yield 1 + ... yield 2 + ... return + ... def __getitem__(self, i): return list(self)[i] + ... def _sympy_(self): return Matrix(self) + >>> sympify(MyList1()) + Matrix([ + [1], + [2]]) + + If you do not have control over the class definition you could also use the + ``converter`` global dictionary. The key is the class and the value is a + function that takes a single argument and returns the desired SymPy + object, e.g. ``converter[MyList] = lambda x: Matrix(x)``. + + >>> class MyList2(object): # XXX Do not do this if you control the class! + ... def __iter__(self): # Use _sympy_! + ... yield 1 + ... yield 2 + ... return + ... def __getitem__(self, i): return list(self)[i] + >>> from sympy.core.sympify import converter + >>> converter[MyList2] = lambda x: Matrix(x) + >>> sympify(MyList2()) + Matrix([ + [1], + [2]]) + + Notes + ===== + + The keywords ``rational`` and ``convert_xor`` are only used + when the input is a string. + + convert_xor + ----------- + + >>> sympify('x^y',convert_xor=True) + x**y + >>> sympify('x^y',convert_xor=False) + x ^ y + + rational + -------- + + >>> sympify('0.1',rational=False) + 0.1 + >>> sympify('0.1',rational=True) + 1/10 + + Sometimes autosimplification during sympification results in expressions + that are very different in structure than what was entered. Until such + autosimplification is no longer done, the ``kernS`` function might be of + some use. In the example below you can see how an expression reduces to + $-1$ by autosimplification, but does not do so when ``kernS`` is used. + + >>> from sympy.core.sympify import kernS + >>> from sympy.abc import x + >>> -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1 + -1 + >>> s = '-2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1' + >>> sympify(s) + -1 + >>> kernS(s) + -2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) - 1 + + Parameters + ========== + + a : + - any object defined in SymPy + - standard numeric Python types: ``int``, ``long``, ``float``, ``Decimal`` + - strings (like ``"0.09"``, ``"2e-19"`` or ``'sin(x)'``) + - booleans, including ``None`` (will leave ``None`` unchanged) + - dicts, lists, sets or tuples containing any of the above + + convert_xor : bool, optional + If true, treats ``^`` as exponentiation. + If False, treats ``^`` as XOR itself. + Used only when input is a string. + + locals : any object defined in SymPy, optional + In order to have strings be recognized it can be imported + into a namespace dictionary and passed as locals. + + strict : bool, optional + If the option strict is set to ``True``, only the types for which + an explicit conversion has been defined are converted. In the + other cases, a SympifyError is raised. + + rational : bool, optional + If ``True``, converts floats into :class:`~.Rational`. + If ``False``, it lets floats remain as it is. + Used only when input is a string. + + evaluate : bool, optional + If False, then arithmetic and operators will be converted into + their SymPy equivalents. If True the expression will be evaluated + and the result will be returned. + + """ + # XXX: If a is a Basic subclass rather than instance (e.g. sin rather than + # sin(x)) then a.__sympy__ will be the property. Only on the instance will + # a.__sympy__ give the *value* of the property (True). Since sympify(sin) + # was used for a long time we allow it to pass. However if strict=True as + # is the case in internal calls to _sympify then we only allow + # is_sympy=True. + # + # https://github.com/sympy/sympy/issues/20124 + is_sympy = getattr(a, '__sympy__', None) + if is_sympy is True: + return a + elif is_sympy is not None: + if not strict: + return a + else: + raise SympifyError(a) + + if isinstance(a, CantSympify): + raise SympifyError(a) + + cls = getattr(a, "__class__", None) + + #Check if there exists a converter for any of the types in the mro + for superclass in getmro(cls): + #First check for user defined converters + conv = _external_converter.get(superclass) + if conv is None: + #if none exists, check for SymPy defined converters + conv = _sympy_converter.get(superclass) + if conv is not None: + return conv(a) + + if cls is type(None): + if strict: + raise SympifyError(a) + else: + return a + + if evaluate is None: + evaluate = global_parameters.evaluate + + # Support for basic numpy datatypes + if _is_numpy_instance(a): + import numpy as np + if np.isscalar(a): + return _convert_numpy_types(a, locals=locals, + convert_xor=convert_xor, strict=strict, rational=rational, + evaluate=evaluate) + + _sympy_ = getattr(a, "_sympy_", None) + if _sympy_ is not None: + return a._sympy_() + + if not strict: + # Put numpy array conversion _before_ float/int, see + # . + flat = getattr(a, "flat", None) + if flat is not None: + shape = getattr(a, "shape", None) + if shape is not None: + from sympy.tensor.array import Array + return Array(a.flat, a.shape) # works with e.g. NumPy arrays + + if not isinstance(a, str): + if _is_numpy_instance(a): + import numpy as np + assert not isinstance(a, np.number) + if isinstance(a, np.ndarray): + # Scalar arrays (those with zero dimensions) have sympify + # called on the scalar element. + if a.ndim == 0: + try: + return sympify(a.item(), + locals=locals, + convert_xor=convert_xor, + strict=strict, + rational=rational, + evaluate=evaluate) + except SympifyError: + pass + elif hasattr(a, '__float__'): + # float and int can coerce size-one numpy arrays to their lone + # element. See issue https://github.com/numpy/numpy/issues/10404. + return sympify(float(a)) + elif hasattr(a, '__int__'): + return sympify(int(a)) + + if strict: + raise SympifyError(a) + + if iterable(a): + try: + return type(a)([sympify(x, locals=locals, convert_xor=convert_xor, + rational=rational, evaluate=evaluate) for x in a]) + except TypeError: + # Not all iterables are rebuildable with their type. + pass + + if not isinstance(a, str): + raise SympifyError('cannot sympify object of type %r' % type(a)) + + from sympy.parsing.sympy_parser import (parse_expr, TokenError, + standard_transformations) + from sympy.parsing.sympy_parser import convert_xor as t_convert_xor + from sympy.parsing.sympy_parser import rationalize as t_rationalize + + transformations = standard_transformations + + if rational: + transformations += (t_rationalize,) + if convert_xor: + transformations += (t_convert_xor,) + + try: + a = a.replace('\n', '') + expr = parse_expr(a, local_dict=locals, transformations=transformations, evaluate=evaluate) + except (TokenError, SyntaxError) as exc: + raise SympifyError('could not parse %r' % a, exc) + + return expr + + +def _sympify(a): + """ + Short version of :func:`~.sympify` for internal usage for ``__add__`` and + ``__eq__`` methods where it is ok to allow some things (like Python + integers and floats) in the expression. This excludes things (like strings) + that are unwise to allow into such an expression. + + >>> from sympy import Integer + >>> Integer(1) == 1 + True + + >>> Integer(1) == '1' + False + + >>> from sympy.abc import x + >>> x + 1 + x + 1 + + >>> x + '1' + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for +: 'Symbol' and 'str' + + see: sympify + + """ + return sympify(a, strict=True) + + +def kernS(s): + """Use a hack to try keep autosimplification from distributing a + a number into an Add; this modification does not + prevent the 2-arg Mul from becoming an Add, however. + + Examples + ======== + + >>> from sympy.core.sympify import kernS + >>> from sympy.abc import x, y + + The 2-arg Mul distributes a number (or minus sign) across the terms + of an expression, but kernS will prevent that: + + >>> 2*(x + y), -(x + 1) + (2*x + 2*y, -x - 1) + >>> kernS('2*(x + y)') + 2*(x + y) + >>> kernS('-(x + 1)') + -(x + 1) + + If use of the hack fails, the un-hacked string will be passed to sympify... + and you get what you get. + + XXX This hack should not be necessary once issue 4596 has been resolved. + """ + hit = False + quoted = '"' in s or "'" in s + if '(' in s and not quoted: + if s.count('(') != s.count(")"): + raise SympifyError('unmatched left parenthesis') + + # strip all space from s + s = ''.join(s.split()) + olds = s + # now use space to represent a symbol that + # will + # step 1. turn potential 2-arg Muls into 3-arg versions + # 1a. *( -> * *( + s = s.replace('*(', '* *(') + # 1b. close up exponentials + s = s.replace('** *', '**') + # 2. handle the implied multiplication of a negated + # parenthesized expression in two steps + # 2a: -(...) --> -( *(...) + target = '-( *(' + s = s.replace('-(', target) + # 2b: double the matching closing parenthesis + # -( *(...) --> -( *(...)) + i = nest = 0 + assert target.endswith('(') # assumption below + while True: + j = s.find(target, i) + if j == -1: + break + j += len(target) - 1 + for j in range(j, len(s)): + if s[j] == "(": + nest += 1 + elif s[j] == ")": + nest -= 1 + if nest == 0: + break + s = s[:j] + ")" + s[j:] + i = j + 2 # the first char after 2nd ) + if ' ' in s: + # get a unique kern + kern = '_' + while kern in s: + kern += choice(string.ascii_letters + string.digits) + s = s.replace(' ', kern) + hit = kern in s + else: + hit = False + + for i in range(2): + try: + expr = sympify(s) + break + except TypeError: # the kern might cause unknown errors... + if hit: + s = olds # maybe it didn't like the kern; use un-kerned s + hit = False + continue + expr = sympify(s) # let original error raise + + if not hit: + return expr + + from .symbol import Symbol + rep = {Symbol(kern): 1} + def _clear(expr): + if isinstance(expr, (list, tuple, set)): + return type(expr)([_clear(e) for e in expr]) + if hasattr(expr, 'subs'): + return expr.subs(rep, hack2=True) + return expr + expr = _clear(expr) + # hope that kern is not there anymore + return expr + + +# Avoid circular import +from .basic import Basic diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__init__.py b/phivenv/Lib/site-packages/sympy/core/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b1aa6d870f2eec70b00413e661de6f80a829b5e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_arit.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_arit.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9dd404bbfd204c7578959a397556ff76ba4864f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_arit.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_assumptions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_assumptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33ab373dc27f91127e57294f79d8dad9d98cf5ed Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_assumptions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_basic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_basic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce63826fdd91f1d3a0457925ad70f2b6df53df41 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_basic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_cache.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_cache.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b78f2e37fc3da04daea17619f0e25e985625a67b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_cache.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_compatibility.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_compatibility.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec0ecbff9546551407b51fa3a75f2084eb14bfff Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_compatibility.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_complex.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_complex.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90a7a3315e35996fbe82ffd4807f07f3d080092a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_complex.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_constructor_postprocessor.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_constructor_postprocessor.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b08334bc279dc25cee3511458315ebd81995529e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_constructor_postprocessor.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_containers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_containers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c957156be4330ef7f9ed86fa9b2f9fcfa0ae568 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_containers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_count_ops.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_count_ops.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c18495528db6ef1f0256e8be4f09c0f88ff9393 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_count_ops.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_diff.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_diff.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a3133e86a88612f4cb1e016f60fb30722adff6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_diff.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_equal.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_equal.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df4ea0e6f3c97b286454c515d69715037abbe0b5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_equal.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_eval.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_eval.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17bef7a2750003f1ee8fe19a5eaeaf3e967a8a6b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_eval.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_evalf.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_evalf.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b227a8618205c1ae7c8eec2a4b2ce1b32e52e35f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_evalf.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_expand.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_expand.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c9f298ef280ab13fbe31851cfb4a80f793d0cbb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_expand.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_expr.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_expr.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9152e7f0c7b76f10e8925ba1e75e9849a6f5c32 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_expr.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_exprtools.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_exprtools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7365f284d33fb79b7cd08c6700bd06761c42d787 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_exprtools.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_facts.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_facts.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81709414143679def61af6bee5746233a8b2e489 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_facts.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_function.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_function.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3428a61c0a90aea162f0e4b4aefaa2b7bc192b5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_function.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_kind.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_kind.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75464ca953e97c1b3c046c350a1b955dbbf5f26d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_kind.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_logic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_logic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7f4ddfa6f988f7a2bca46198596f1e0f7c6dcf6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_logic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_match.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_match.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15c530b8d15bb814116079f55cbfbd209119c127 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_match.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_multidimensional.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_multidimensional.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38dd0e685efcf67f522630f2521660f1e50f535b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_multidimensional.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_noncommutative.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_noncommutative.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c777592318cdb658d6b2a53d47835c812d65eeb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_noncommutative.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_numbers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_numbers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b62139b1cffd25e0372ff83f29bf23fcae983b82 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_numbers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_operations.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_operations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f508580115e474751ef23efd1e5aa91c6b8517f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_operations.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_parameters.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_parameters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21bf05f88124d68e1c85e9da2d3f63ba1087a8ba Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_parameters.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_power.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_power.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d368d31900837147f07d26d6b79a069b97cf18a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_power.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_priority.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_priority.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9382f4e9b47f1538edeb5e6a8c91db77f06734a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_priority.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_random.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_random.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0778c9f0079349dbe680f759fc5e4a6170a5b01a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_random.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_relational.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_relational.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7376413871d761778a58934d2a7462f6bd2d13f6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_relational.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_rules.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_rules.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb2f56076422124c5d31fd1ea1dd5f9acaf8f04f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_rules.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_singleton.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_singleton.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92c73297d6b00633df64c2586c6d81ff51a8a869 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_singleton.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_sorting.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_sorting.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5e8c6cb9a88dd1b84c8091ca05de67d4f2062e8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_sorting.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_subs.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_subs.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8d806642ca61d922dad3716ee389d9a298ede45 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_subs.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_symbol.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_symbol.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ba3ffb40391e228647b5542cff73f3ab62ef732 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_symbol.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_sympify.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_sympify.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59dc18baefce5123d6869f6aa1ee340698ca8074 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_sympify.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_traversal.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_traversal.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e733908d2e6de538b065e39d8c0b59b7d992920f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_traversal.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_truediv.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_truediv.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1308e374e805e630d259586750d2c4a0b7b536d1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_truediv.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_var.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_var.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7dc1340fe4a0a3e0d0f23723a5d57109e755817 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/core/tests/__pycache__/test_var.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_args.py b/phivenv/Lib/site-packages/sympy/core/tests/test_args.py new file mode 100644 index 0000000000000000000000000000000000000000..75b326146e1cc645a27e26ba13d44c92d56d5efb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_args.py @@ -0,0 +1,5517 @@ +"""Test whether all elements of cls.args are instances of Basic. """ + +# NOTE: keep tests sorted by (module, class name) key. If a class can't +# be instantiated, add it here anyway with @SKIP("abstract class) (see +# e.g. Function). + +import os +import re +from pathlib import Path + +from sympy.assumptions.ask import Q +from sympy.core.basic import Basic +from sympy.core.function import (Function, Lambda) +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin + +from sympy.testing.pytest import SKIP, warns_deprecated_sympy + +a, b, c, x, y, z, s = symbols('a,b,c,x,y,z,s') + + +whitelist = [ + "sympy.assumptions.predicates", # tested by test_predicates() + "sympy.assumptions.relation.equality", # tested by test_predicates() +] + +def test_all_classes_are_tested(): + this = os.path.split(__file__)[0] + path = os.path.join(this, os.pardir, os.pardir) + sympy_path = os.path.abspath(path) + prefix = os.path.split(sympy_path)[0] + os.sep + + re_cls = re.compile(r"^class ([A-Za-z][A-Za-z0-9_]*)\s*\(", re.MULTILINE) + + modules = {} + + for root, dirs, files in os.walk(sympy_path): + module = root.replace(prefix, "").replace(os.sep, ".") + + for file in files: + if file.startswith(("_", "test_", "bench_")): + continue + if not file.endswith(".py"): + continue + + text = Path(os.path.join(root, file)).read_text(encoding='utf-8') + + submodule = module + '.' + file[:-3] + + if any(submodule.startswith(wpath) for wpath in whitelist): + continue + + names = re_cls.findall(text) + + if not names: + continue + + try: + mod = __import__(submodule, fromlist=names) + except ImportError: + continue + + def is_Basic(name): + cls = getattr(mod, name) + if hasattr(cls, '_sympy_deprecated_func'): + cls = cls._sympy_deprecated_func + if not isinstance(cls, type): + # check instance of singleton class with same name + cls = type(cls) + return issubclass(cls, Basic) + + names = list(filter(is_Basic, names)) + + if names: + modules[submodule] = names + + ns = globals() + failed = [] + + for module, names in modules.items(): + mod = module.replace('.', '__') + + for name in names: + test = 'test_' + mod + '__' + name + + if test not in ns: + failed.append(module + '.' + name) + + assert not failed, "Missing classes: %s. Please add tests for these to sympy/core/tests/test_args.py." % ", ".join(failed) + + +def _test_args(obj): + all_basic = all(isinstance(arg, Basic) for arg in obj.args) + # Ideally obj.func(*obj.args) would always recreate the object, but for + # now, we only require it for objects with non-empty .args + recreatable = not obj.args or obj.func(*obj.args) == obj + return all_basic and recreatable + + +def test_sympy__algebras__quaternion__Quaternion(): + from sympy.algebras.quaternion import Quaternion + assert _test_args(Quaternion(x, 1, 2, 3)) + + +def test_sympy__assumptions__assume__AppliedPredicate(): + from sympy.assumptions.assume import AppliedPredicate, Predicate + assert _test_args(AppliedPredicate(Predicate("test"), 2)) + assert _test_args(Q.is_true(True)) + +@SKIP("abstract class") +def test_sympy__assumptions__assume__Predicate(): + pass + +def test_predicates(): + predicates = [ + getattr(Q, attr) + for attr in Q.__class__.__dict__ + if not attr.startswith('__')] + for p in predicates: + assert _test_args(p) + +def test_sympy__assumptions__assume__UndefinedPredicate(): + from sympy.assumptions.assume import Predicate + assert _test_args(Predicate("test")) + +@SKIP('abstract class') +def test_sympy__assumptions__relation__binrel__BinaryRelation(): + pass + +def test_sympy__assumptions__relation__binrel__AppliedBinaryRelation(): + assert _test_args(Q.eq(1, 2)) + +def test_sympy__assumptions__wrapper__AssumptionsWrapper(): + from sympy.assumptions.wrapper import AssumptionsWrapper + assert _test_args(AssumptionsWrapper(x, Q.positive(x))) + +@SKIP("abstract Class") +def test_sympy__codegen__ast__CodegenAST(): + from sympy.codegen.ast import CodegenAST + assert _test_args(CodegenAST()) + +@SKIP("abstract Class") +def test_sympy__codegen__ast__AssignmentBase(): + from sympy.codegen.ast import AssignmentBase + assert _test_args(AssignmentBase(x, 1)) + +@SKIP("abstract Class") +def test_sympy__codegen__ast__AugmentedAssignment(): + from sympy.codegen.ast import AugmentedAssignment + assert _test_args(AugmentedAssignment(x, 1)) + +def test_sympy__codegen__ast__AddAugmentedAssignment(): + from sympy.codegen.ast import AddAugmentedAssignment + assert _test_args(AddAugmentedAssignment(x, 1)) + +def test_sympy__codegen__ast__SubAugmentedAssignment(): + from sympy.codegen.ast import SubAugmentedAssignment + assert _test_args(SubAugmentedAssignment(x, 1)) + +def test_sympy__codegen__ast__MulAugmentedAssignment(): + from sympy.codegen.ast import MulAugmentedAssignment + assert _test_args(MulAugmentedAssignment(x, 1)) + +def test_sympy__codegen__ast__DivAugmentedAssignment(): + from sympy.codegen.ast import DivAugmentedAssignment + assert _test_args(DivAugmentedAssignment(x, 1)) + +def test_sympy__codegen__ast__ModAugmentedAssignment(): + from sympy.codegen.ast import ModAugmentedAssignment + assert _test_args(ModAugmentedAssignment(x, 1)) + +def test_sympy__codegen__ast__CodeBlock(): + from sympy.codegen.ast import CodeBlock, Assignment + assert _test_args(CodeBlock(Assignment(x, 1), Assignment(y, 2))) + +def test_sympy__codegen__ast__For(): + from sympy.codegen.ast import For, CodeBlock, AddAugmentedAssignment + from sympy.sets import Range + assert _test_args(For(x, Range(10), CodeBlock(AddAugmentedAssignment(y, 1)))) + + +def test_sympy__codegen__ast__Token(): + from sympy.codegen.ast import Token + assert _test_args(Token()) + + +def test_sympy__codegen__ast__ContinueToken(): + from sympy.codegen.ast import ContinueToken + assert _test_args(ContinueToken()) + +def test_sympy__codegen__ast__BreakToken(): + from sympy.codegen.ast import BreakToken + assert _test_args(BreakToken()) + +def test_sympy__codegen__ast__NoneToken(): + from sympy.codegen.ast import NoneToken + assert _test_args(NoneToken()) + +def test_sympy__codegen__ast__String(): + from sympy.codegen.ast import String + assert _test_args(String('foobar')) + +def test_sympy__codegen__ast__QuotedString(): + from sympy.codegen.ast import QuotedString + assert _test_args(QuotedString('foobar')) + +def test_sympy__codegen__ast__Comment(): + from sympy.codegen.ast import Comment + assert _test_args(Comment('this is a comment')) + +def test_sympy__codegen__ast__Node(): + from sympy.codegen.ast import Node + assert _test_args(Node()) + assert _test_args(Node(attrs={1, 2, 3})) + + +def test_sympy__codegen__ast__Type(): + from sympy.codegen.ast import Type + assert _test_args(Type('float128')) + + +def test_sympy__codegen__ast__IntBaseType(): + from sympy.codegen.ast import IntBaseType + assert _test_args(IntBaseType('bigint')) + + +def test_sympy__codegen__ast___SizedIntType(): + from sympy.codegen.ast import _SizedIntType + assert _test_args(_SizedIntType('int128', 128)) + + +def test_sympy__codegen__ast__SignedIntType(): + from sympy.codegen.ast import SignedIntType + assert _test_args(SignedIntType('int128_with_sign', 128)) + + +def test_sympy__codegen__ast__UnsignedIntType(): + from sympy.codegen.ast import UnsignedIntType + assert _test_args(UnsignedIntType('unt128', 128)) + + +def test_sympy__codegen__ast__FloatBaseType(): + from sympy.codegen.ast import FloatBaseType + assert _test_args(FloatBaseType('positive_real')) + + +def test_sympy__codegen__ast__FloatType(): + from sympy.codegen.ast import FloatType + assert _test_args(FloatType('float242', 242, nmant=142, nexp=99)) + + +def test_sympy__codegen__ast__ComplexBaseType(): + from sympy.codegen.ast import ComplexBaseType + assert _test_args(ComplexBaseType('positive_cmplx')) + +def test_sympy__codegen__ast__ComplexType(): + from sympy.codegen.ast import ComplexType + assert _test_args(ComplexType('complex42', 42, nmant=15, nexp=5)) + + +def test_sympy__codegen__ast__Attribute(): + from sympy.codegen.ast import Attribute + assert _test_args(Attribute('noexcept')) + + +def test_sympy__codegen__ast__Variable(): + from sympy.codegen.ast import Variable, Type, value_const + assert _test_args(Variable(x)) + assert _test_args(Variable(y, Type('float32'), {value_const})) + assert _test_args(Variable(z, type=Type('float64'))) + + +def test_sympy__codegen__ast__Pointer(): + from sympy.codegen.ast import Pointer, Type, pointer_const + assert _test_args(Pointer(x)) + assert _test_args(Pointer(y, type=Type('float32'))) + assert _test_args(Pointer(z, Type('float64'), {pointer_const})) + + +def test_sympy__codegen__ast__Declaration(): + from sympy.codegen.ast import Declaration, Variable, Type + vx = Variable(x, type=Type('float')) + assert _test_args(Declaration(vx)) + + +def test_sympy__codegen__ast__While(): + from sympy.codegen.ast import While, AddAugmentedAssignment + assert _test_args(While(abs(x) < 1, [AddAugmentedAssignment(x, -1)])) + + +def test_sympy__codegen__ast__Scope(): + from sympy.codegen.ast import Scope, AddAugmentedAssignment + assert _test_args(Scope([AddAugmentedAssignment(x, -1)])) + + +def test_sympy__codegen__ast__Stream(): + from sympy.codegen.ast import Stream + assert _test_args(Stream('stdin')) + +def test_sympy__codegen__ast__Print(): + from sympy.codegen.ast import Print + assert _test_args(Print([x, y])) + assert _test_args(Print([x, y], "%d %d")) + + +def test_sympy__codegen__ast__FunctionPrototype(): + from sympy.codegen.ast import FunctionPrototype, real, Declaration, Variable + inp_x = Declaration(Variable(x, type=real)) + assert _test_args(FunctionPrototype(real, 'pwer', [inp_x])) + + +def test_sympy__codegen__ast__FunctionDefinition(): + from sympy.codegen.ast import FunctionDefinition, real, Declaration, Variable, Assignment + inp_x = Declaration(Variable(x, type=real)) + assert _test_args(FunctionDefinition(real, 'pwer', [inp_x], [Assignment(x, x**2)])) + + +def test_sympy__codegen__ast__Raise(): + from sympy.codegen.ast import Raise + assert _test_args(Raise(x)) + + +def test_sympy__codegen__ast__Return(): + from sympy.codegen.ast import Return + assert _test_args(Return(x)) + + +def test_sympy__codegen__ast__RuntimeError_(): + from sympy.codegen.ast import RuntimeError_ + assert _test_args(RuntimeError_('"message"')) + + +def test_sympy__codegen__ast__FunctionCall(): + from sympy.codegen.ast import FunctionCall + assert _test_args(FunctionCall('pwer', [x])) + + +def test_sympy__codegen__ast__Element(): + from sympy.codegen.ast import Element + assert _test_args(Element('x', range(3))) + + +def test_sympy__codegen__cnodes__CommaOperator(): + from sympy.codegen.cnodes import CommaOperator + assert _test_args(CommaOperator(1, 2)) + + +def test_sympy__codegen__cnodes__goto(): + from sympy.codegen.cnodes import goto + assert _test_args(goto('early_exit')) + + +def test_sympy__codegen__cnodes__Label(): + from sympy.codegen.cnodes import Label + assert _test_args(Label('early_exit')) + + +def test_sympy__codegen__cnodes__PreDecrement(): + from sympy.codegen.cnodes import PreDecrement + assert _test_args(PreDecrement(x)) + + +def test_sympy__codegen__cnodes__PostDecrement(): + from sympy.codegen.cnodes import PostDecrement + assert _test_args(PostDecrement(x)) + + +def test_sympy__codegen__cnodes__PreIncrement(): + from sympy.codegen.cnodes import PreIncrement + assert _test_args(PreIncrement(x)) + + +def test_sympy__codegen__cnodes__PostIncrement(): + from sympy.codegen.cnodes import PostIncrement + assert _test_args(PostIncrement(x)) + + +def test_sympy__codegen__cnodes__struct(): + from sympy.codegen.ast import real, Variable + from sympy.codegen.cnodes import struct + assert _test_args(struct(declarations=[ + Variable(x, type=real), + Variable(y, type=real) + ])) + + +def test_sympy__codegen__cnodes__union(): + from sympy.codegen.ast import float32, int32, Variable + from sympy.codegen.cnodes import union + assert _test_args(union(declarations=[ + Variable(x, type=float32), + Variable(y, type=int32) + ])) + + +def test_sympy__codegen__cxxnodes__using(): + from sympy.codegen.cxxnodes import using + assert _test_args(using('std::vector')) + assert _test_args(using('std::vector', 'vec')) + + +def test_sympy__codegen__fnodes__Program(): + from sympy.codegen.fnodes import Program + assert _test_args(Program('foobar', [])) + +def test_sympy__codegen__fnodes__Module(): + from sympy.codegen.fnodes import Module + assert _test_args(Module('foobar', [], [])) + + +def test_sympy__codegen__fnodes__Subroutine(): + from sympy.codegen.fnodes import Subroutine + x = symbols('x', real=True) + assert _test_args(Subroutine('foo', [x], [])) + + +def test_sympy__codegen__fnodes__GoTo(): + from sympy.codegen.fnodes import GoTo + assert _test_args(GoTo([10])) + assert _test_args(GoTo([10, 20], x > 1)) + + +def test_sympy__codegen__fnodes__FortranReturn(): + from sympy.codegen.fnodes import FortranReturn + assert _test_args(FortranReturn(10)) + + +def test_sympy__codegen__fnodes__Extent(): + from sympy.codegen.fnodes import Extent + assert _test_args(Extent()) + assert _test_args(Extent(None)) + assert _test_args(Extent(':')) + assert _test_args(Extent(-3, 4)) + assert _test_args(Extent(x, y)) + + +def test_sympy__codegen__fnodes__use_rename(): + from sympy.codegen.fnodes import use_rename + assert _test_args(use_rename('loc', 'glob')) + + +def test_sympy__codegen__fnodes__use(): + from sympy.codegen.fnodes import use + assert _test_args(use('modfoo', only='bar')) + + +def test_sympy__codegen__fnodes__SubroutineCall(): + from sympy.codegen.fnodes import SubroutineCall + assert _test_args(SubroutineCall('foo', ['bar', 'baz'])) + + +def test_sympy__codegen__fnodes__Do(): + from sympy.codegen.fnodes import Do + assert _test_args(Do([], 'i', 1, 42)) + + +def test_sympy__codegen__fnodes__ImpliedDoLoop(): + from sympy.codegen.fnodes import ImpliedDoLoop + assert _test_args(ImpliedDoLoop('i', 'i', 1, 42)) + + +def test_sympy__codegen__fnodes__ArrayConstructor(): + from sympy.codegen.fnodes import ArrayConstructor + assert _test_args(ArrayConstructor([1, 2, 3])) + from sympy.codegen.fnodes import ImpliedDoLoop + idl = ImpliedDoLoop('i', 'i', 1, 42) + assert _test_args(ArrayConstructor([1, idl, 3])) + + +def test_sympy__codegen__fnodes__sum_(): + from sympy.codegen.fnodes import sum_ + assert _test_args(sum_('arr')) + + +def test_sympy__codegen__fnodes__product_(): + from sympy.codegen.fnodes import product_ + assert _test_args(product_('arr')) + + +def test_sympy__codegen__numpy_nodes__logaddexp(): + from sympy.codegen.numpy_nodes import logaddexp + assert _test_args(logaddexp(x, y)) + + +def test_sympy__codegen__numpy_nodes__logaddexp2(): + from sympy.codegen.numpy_nodes import logaddexp2 + assert _test_args(logaddexp2(x, y)) + + +def test_sympy__codegen__numpy_nodes__amin(): + from sympy.codegen.numpy_nodes import amin + assert _test_args(amin(x)) + + +def test_sympy__codegen__numpy_nodes__amax(): + from sympy.codegen.numpy_nodes import amax + assert _test_args(amax(x)) + + +def test_sympy__codegen__numpy_nodes__minimum(): + from sympy.codegen.numpy_nodes import minimum + assert _test_args(minimum(x, y, z)) + + +def test_sympy__codegen__numpy_nodes__maximum(): + from sympy.codegen.numpy_nodes import maximum + assert _test_args(maximum(x, y, z)) + + +def test_sympy__codegen__pynodes__List(): + from sympy.codegen.pynodes import List + assert _test_args(List(1, 2, 3)) + + +def test_sympy__codegen__pynodes__NumExprEvaluate(): + from sympy.codegen.pynodes import NumExprEvaluate + assert _test_args(NumExprEvaluate(x)) + + +def test_sympy__codegen__scipy_nodes__cosm1(): + from sympy.codegen.scipy_nodes import cosm1 + assert _test_args(cosm1(x)) + + +def test_sympy__codegen__scipy_nodes__powm1(): + from sympy.codegen.scipy_nodes import powm1 + assert _test_args(powm1(x, y)) + + +def test_sympy__codegen__abstract_nodes__List(): + from sympy.codegen.abstract_nodes import List + assert _test_args(List(1, 2, 3)) + +def test_sympy__combinatorics__graycode__GrayCode(): + from sympy.combinatorics.graycode import GrayCode + # an integer is given and returned from GrayCode as the arg + assert _test_args(GrayCode(3, start='100')) + assert _test_args(GrayCode(3, rank=1)) + + +def test_sympy__combinatorics__permutations__Permutation(): + from sympy.combinatorics.permutations import Permutation + assert _test_args(Permutation([0, 1, 2, 3])) + +def test_sympy__combinatorics__permutations__AppliedPermutation(): + from sympy.combinatorics.permutations import Permutation + from sympy.combinatorics.permutations import AppliedPermutation + p = Permutation([0, 1, 2, 3]) + assert _test_args(AppliedPermutation(p, x)) + +def test_sympy__combinatorics__perm_groups__PermutationGroup(): + from sympy.combinatorics.permutations import Permutation + from sympy.combinatorics.perm_groups import PermutationGroup + assert _test_args(PermutationGroup([Permutation([0, 1])])) + + +def test_sympy__combinatorics__polyhedron__Polyhedron(): + from sympy.combinatorics.permutations import Permutation + from sympy.combinatorics.polyhedron import Polyhedron + from sympy.abc import w, x, y, z + pgroup = [Permutation([[0, 1, 2], [3]]), + Permutation([[0, 1, 3], [2]]), + Permutation([[0, 2, 3], [1]]), + Permutation([[1, 2, 3], [0]]), + Permutation([[0, 1], [2, 3]]), + Permutation([[0, 2], [1, 3]]), + Permutation([[0, 3], [1, 2]]), + Permutation([[0, 1, 2, 3]])] + corners = [w, x, y, z] + faces = [(w, x, y), (w, y, z), (w, z, x), (x, y, z)] + assert _test_args(Polyhedron(corners, faces, pgroup)) + + +def test_sympy__combinatorics__prufer__Prufer(): + from sympy.combinatorics.prufer import Prufer + assert _test_args(Prufer([[0, 1], [0, 2], [0, 3]], 4)) + + +def test_sympy__combinatorics__partitions__Partition(): + from sympy.combinatorics.partitions import Partition + assert _test_args(Partition([1])) + + +def test_sympy__combinatorics__partitions__IntegerPartition(): + from sympy.combinatorics.partitions import IntegerPartition + assert _test_args(IntegerPartition([1])) + + +def test_sympy__concrete__products__Product(): + from sympy.concrete.products import Product + assert _test_args(Product(x, (x, 0, 10))) + assert _test_args(Product(x, (x, 0, y), (y, 0, 10))) + + +@SKIP("abstract Class") +def test_sympy__concrete__expr_with_limits__ExprWithLimits(): + from sympy.concrete.expr_with_limits import ExprWithLimits + assert _test_args(ExprWithLimits(x, (x, 0, 10))) + assert _test_args(ExprWithLimits(x*y, (x, 0, 10.),(y,1.,3))) + + +@SKIP("abstract Class") +def test_sympy__concrete__expr_with_limits__AddWithLimits(): + from sympy.concrete.expr_with_limits import AddWithLimits + assert _test_args(AddWithLimits(x, (x, 0, 10))) + assert _test_args(AddWithLimits(x*y, (x, 0, 10),(y,1,3))) + + +@SKIP("abstract Class") +def test_sympy__concrete__expr_with_intlimits__ExprWithIntLimits(): + from sympy.concrete.expr_with_intlimits import ExprWithIntLimits + assert _test_args(ExprWithIntLimits(x, (x, 0, 10))) + assert _test_args(ExprWithIntLimits(x*y, (x, 0, 10),(y,1,3))) + + +def test_sympy__concrete__summations__Sum(): + from sympy.concrete.summations import Sum + assert _test_args(Sum(x, (x, 0, 10))) + assert _test_args(Sum(x, (x, 0, y), (y, 0, 10))) + + +def test_sympy__core__add__Add(): + from sympy.core.add import Add + assert _test_args(Add(x, y, z, 2)) + + +def test_sympy__core__basic__Atom(): + from sympy.core.basic import Atom + assert _test_args(Atom()) + + +def test_sympy__core__basic__Basic(): + from sympy.core.basic import Basic + assert _test_args(Basic()) + + +def test_sympy__core__containers__Dict(): + from sympy.core.containers import Dict + assert _test_args(Dict({x: y, y: z})) + + +def test_sympy__core__containers__Tuple(): + from sympy.core.containers import Tuple + assert _test_args(Tuple(x, y, z, 2)) + + +def test_sympy__core__expr__AtomicExpr(): + from sympy.core.expr import AtomicExpr + assert _test_args(AtomicExpr()) + + +def test_sympy__core__expr__Expr(): + from sympy.core.expr import Expr + assert _test_args(Expr()) + + +def test_sympy__core__expr__UnevaluatedExpr(): + from sympy.core.expr import UnevaluatedExpr + from sympy.abc import x + assert _test_args(UnevaluatedExpr(x)) + + +def test_sympy__core__function__Application(): + from sympy.core.function import Application + assert _test_args(Application(1, 2, 3)) + + +def test_sympy__core__function__AppliedUndef(): + from sympy.core.function import AppliedUndef + assert _test_args(AppliedUndef(1, 2, 3)) + + +def test_sympy__core__function__DefinedFunction(): + from sympy.core.function import DefinedFunction + assert _test_args(DefinedFunction(1, 2, 3)) + + +def test_sympy__core__function__Derivative(): + from sympy.core.function import Derivative + assert _test_args(Derivative(2, x, y, 3)) + + +@SKIP("abstract class") +def test_sympy__core__function__Function(): + pass + + +def test_sympy__core__function__Lambda(): + assert _test_args(Lambda((x, y), x + y + z)) + + +def test_sympy__core__function__Subs(): + from sympy.core.function import Subs + assert _test_args(Subs(x + y, x, 2)) + + +def test_sympy__core__function__WildFunction(): + from sympy.core.function import WildFunction + assert _test_args(WildFunction('f')) + + +def test_sympy__core__mod__Mod(): + from sympy.core.mod import Mod + assert _test_args(Mod(x, 2)) + + +def test_sympy__core__mul__Mul(): + from sympy.core.mul import Mul + assert _test_args(Mul(2, x, y, z)) + + +def test_sympy__core__numbers__Catalan(): + from sympy.core.numbers import Catalan + assert _test_args(Catalan()) + + +def test_sympy__core__numbers__ComplexInfinity(): + from sympy.core.numbers import ComplexInfinity + assert _test_args(ComplexInfinity()) + + +def test_sympy__core__numbers__EulerGamma(): + from sympy.core.numbers import EulerGamma + assert _test_args(EulerGamma()) + + +def test_sympy__core__numbers__Exp1(): + from sympy.core.numbers import Exp1 + assert _test_args(Exp1()) + + +def test_sympy__core__numbers__Float(): + from sympy.core.numbers import Float + assert _test_args(Float(1.23)) + + +def test_sympy__core__numbers__GoldenRatio(): + from sympy.core.numbers import GoldenRatio + assert _test_args(GoldenRatio()) + + +def test_sympy__core__numbers__TribonacciConstant(): + from sympy.core.numbers import TribonacciConstant + assert _test_args(TribonacciConstant()) + + +def test_sympy__core__numbers__Half(): + from sympy.core.numbers import Half + assert _test_args(Half()) + + +def test_sympy__core__numbers__ImaginaryUnit(): + from sympy.core.numbers import ImaginaryUnit + assert _test_args(ImaginaryUnit()) + + +def test_sympy__core__numbers__Infinity(): + from sympy.core.numbers import Infinity + assert _test_args(Infinity()) + + +def test_sympy__core__numbers__Integer(): + from sympy.core.numbers import Integer + assert _test_args(Integer(7)) + + +@SKIP("abstract class") +def test_sympy__core__numbers__IntegerConstant(): + pass + + +def test_sympy__core__numbers__NaN(): + from sympy.core.numbers import NaN + assert _test_args(NaN()) + + +def test_sympy__core__numbers__NegativeInfinity(): + from sympy.core.numbers import NegativeInfinity + assert _test_args(NegativeInfinity()) + + +def test_sympy__core__numbers__NegativeOne(): + from sympy.core.numbers import NegativeOne + assert _test_args(NegativeOne()) + + +def test_sympy__core__numbers__Number(): + from sympy.core.numbers import Number + assert _test_args(Number(1, 7)) + + +def test_sympy__core__numbers__NumberSymbol(): + from sympy.core.numbers import NumberSymbol + assert _test_args(NumberSymbol()) + + +def test_sympy__core__numbers__One(): + from sympy.core.numbers import One + assert _test_args(One()) + + +def test_sympy__core__numbers__Pi(): + from sympy.core.numbers import Pi + assert _test_args(Pi()) + + +def test_sympy__core__numbers__Rational(): + from sympy.core.numbers import Rational + assert _test_args(Rational(1, 7)) + + +@SKIP("abstract class") +def test_sympy__core__numbers__RationalConstant(): + pass + + +def test_sympy__core__numbers__Zero(): + from sympy.core.numbers import Zero + assert _test_args(Zero()) + + +@SKIP("abstract class") +def test_sympy__core__operations__AssocOp(): + pass + + +@SKIP("abstract class") +def test_sympy__core__operations__LatticeOp(): + pass + + +def test_sympy__core__power__Pow(): + from sympy.core.power import Pow + assert _test_args(Pow(x, 2)) + + +def test_sympy__core__relational__Equality(): + from sympy.core.relational import Equality + assert _test_args(Equality(x, 2)) + + +def test_sympy__core__relational__GreaterThan(): + from sympy.core.relational import GreaterThan + assert _test_args(GreaterThan(x, 2)) + + +def test_sympy__core__relational__LessThan(): + from sympy.core.relational import LessThan + assert _test_args(LessThan(x, 2)) + + +@SKIP("abstract class") +def test_sympy__core__relational__Relational(): + pass + + +def test_sympy__core__relational__StrictGreaterThan(): + from sympy.core.relational import StrictGreaterThan + assert _test_args(StrictGreaterThan(x, 2)) + + +def test_sympy__core__relational__StrictLessThan(): + from sympy.core.relational import StrictLessThan + assert _test_args(StrictLessThan(x, 2)) + + +def test_sympy__core__relational__Unequality(): + from sympy.core.relational import Unequality + assert _test_args(Unequality(x, 2)) + + +def test_sympy__sandbox__indexed_integrals__IndexedIntegral(): + from sympy.tensor import IndexedBase, Idx + from sympy.sandbox.indexed_integrals import IndexedIntegral + A = IndexedBase('A') + i, j = symbols('i j', integer=True) + a1, a2 = symbols('a1:3', cls=Idx) + assert _test_args(IndexedIntegral(A[a1], A[a2])) + assert _test_args(IndexedIntegral(A[i], A[j])) + + +def test_sympy__calculus__accumulationbounds__AccumulationBounds(): + from sympy.calculus.accumulationbounds import AccumulationBounds + assert _test_args(AccumulationBounds(0, 1)) + + +def test_sympy__sets__ordinals__OmegaPower(): + from sympy.sets.ordinals import OmegaPower + assert _test_args(OmegaPower(1, 1)) + +def test_sympy__sets__ordinals__Ordinal(): + from sympy.sets.ordinals import Ordinal, OmegaPower + assert _test_args(Ordinal(OmegaPower(2, 1))) + +def test_sympy__sets__ordinals__OrdinalOmega(): + from sympy.sets.ordinals import OrdinalOmega + assert _test_args(OrdinalOmega()) + +def test_sympy__sets__ordinals__OrdinalZero(): + from sympy.sets.ordinals import OrdinalZero + assert _test_args(OrdinalZero()) + + +def test_sympy__sets__powerset__PowerSet(): + from sympy.sets.powerset import PowerSet + from sympy.core.singleton import S + assert _test_args(PowerSet(S.EmptySet)) + + +def test_sympy__sets__sets__EmptySet(): + from sympy.sets.sets import EmptySet + assert _test_args(EmptySet()) + + +def test_sympy__sets__sets__UniversalSet(): + from sympy.sets.sets import UniversalSet + assert _test_args(UniversalSet()) + + +def test_sympy__sets__sets__FiniteSet(): + from sympy.sets.sets import FiniteSet + assert _test_args(FiniteSet(x, y, z)) + + +def test_sympy__sets__sets__Interval(): + from sympy.sets.sets import Interval + assert _test_args(Interval(0, 1)) + + +def test_sympy__sets__sets__ProductSet(): + from sympy.sets.sets import ProductSet, Interval + assert _test_args(ProductSet(Interval(0, 1), Interval(0, 1))) + + +@SKIP("does it make sense to test this?") +def test_sympy__sets__sets__Set(): + from sympy.sets.sets import Set + assert _test_args(Set()) + + +def test_sympy__sets__sets__Intersection(): + from sympy.sets.sets import Intersection, Interval + from sympy.core.symbol import Symbol + x = Symbol('x') + y = Symbol('y') + S = Intersection(Interval(0, x), Interval(y, 1)) + assert isinstance(S, Intersection) + assert _test_args(S) + + +def test_sympy__sets__sets__Union(): + from sympy.sets.sets import Union, Interval + assert _test_args(Union(Interval(0, 1), Interval(2, 3))) + + +def test_sympy__sets__sets__Complement(): + from sympy.sets.sets import Complement, Interval + assert _test_args(Complement(Interval(0, 2), Interval(0, 1))) + + +def test_sympy__sets__sets__SymmetricDifference(): + from sympy.sets.sets import FiniteSet, SymmetricDifference + assert _test_args(SymmetricDifference(FiniteSet(1, 2, 3), \ + FiniteSet(2, 3, 4))) + +def test_sympy__sets__sets__DisjointUnion(): + from sympy.sets.sets import FiniteSet, DisjointUnion + assert _test_args(DisjointUnion(FiniteSet(1, 2, 3), \ + FiniteSet(2, 3, 4))) + + +def test_sympy__physics__quantum__trace__Tr(): + from sympy.physics.quantum.trace import Tr + a, b = symbols('a b', commutative=False) + assert _test_args(Tr(a + b)) + + +def test_sympy__sets__setexpr__SetExpr(): + from sympy.sets.setexpr import SetExpr + from sympy.sets.sets import Interval + assert _test_args(SetExpr(Interval(0, 1))) + + +def test_sympy__sets__fancysets__Rationals(): + from sympy.sets.fancysets import Rationals + assert _test_args(Rationals()) + + +def test_sympy__sets__fancysets__Naturals(): + from sympy.sets.fancysets import Naturals + assert _test_args(Naturals()) + + +def test_sympy__sets__fancysets__Naturals0(): + from sympy.sets.fancysets import Naturals0 + assert _test_args(Naturals0()) + + +def test_sympy__sets__fancysets__Integers(): + from sympy.sets.fancysets import Integers + assert _test_args(Integers()) + + +def test_sympy__sets__fancysets__Reals(): + from sympy.sets.fancysets import Reals + assert _test_args(Reals()) + + +def test_sympy__sets__fancysets__Complexes(): + from sympy.sets.fancysets import Complexes + assert _test_args(Complexes()) + + +def test_sympy__sets__fancysets__ComplexRegion(): + from sympy.sets.fancysets import ComplexRegion + from sympy.core.singleton import S + from sympy.sets import Interval + a = Interval(0, 1) + b = Interval(2, 3) + theta = Interval(0, 2*S.Pi) + assert _test_args(ComplexRegion(a*b)) + assert _test_args(ComplexRegion(a*theta, polar=True)) + + +def test_sympy__sets__fancysets__CartesianComplexRegion(): + from sympy.sets.fancysets import CartesianComplexRegion + from sympy.sets import Interval + a = Interval(0, 1) + b = Interval(2, 3) + assert _test_args(CartesianComplexRegion(a*b)) + + +def test_sympy__sets__fancysets__PolarComplexRegion(): + from sympy.sets.fancysets import PolarComplexRegion + from sympy.core.singleton import S + from sympy.sets import Interval + a = Interval(0, 1) + theta = Interval(0, 2*S.Pi) + assert _test_args(PolarComplexRegion(a*theta)) + + +def test_sympy__sets__fancysets__ImageSet(): + from sympy.sets.fancysets import ImageSet + from sympy.core.singleton import S + from sympy.core.symbol import Symbol + x = Symbol('x') + assert _test_args(ImageSet(Lambda(x, x**2), S.Naturals)) + + +def test_sympy__sets__fancysets__Range(): + from sympy.sets.fancysets import Range + assert _test_args(Range(1, 5, 1)) + + +def test_sympy__sets__conditionset__ConditionSet(): + from sympy.sets.conditionset import ConditionSet + from sympy.core.singleton import S + from sympy.core.symbol import Symbol + x = Symbol('x') + assert _test_args(ConditionSet(x, Eq(x**2, 1), S.Reals)) + + +def test_sympy__sets__contains__Contains(): + from sympy.sets.fancysets import Range + from sympy.sets.contains import Contains + assert _test_args(Contains(x, Range(0, 10, 2))) + + +# STATS + + +from sympy.stats.crv_types import NormalDistribution +nd = NormalDistribution(0, 1) +from sympy.stats.frv_types import DieDistribution +die = DieDistribution(6) + + +def test_sympy__stats__crv__ContinuousDomain(): + from sympy.sets.sets import Interval + from sympy.stats.crv import ContinuousDomain + assert _test_args(ContinuousDomain({x}, Interval(-oo, oo))) + + +def test_sympy__stats__crv__SingleContinuousDomain(): + from sympy.sets.sets import Interval + from sympy.stats.crv import SingleContinuousDomain + assert _test_args(SingleContinuousDomain(x, Interval(-oo, oo))) + + +def test_sympy__stats__crv__ProductContinuousDomain(): + from sympy.sets.sets import Interval + from sympy.stats.crv import SingleContinuousDomain, ProductContinuousDomain + D = SingleContinuousDomain(x, Interval(-oo, oo)) + E = SingleContinuousDomain(y, Interval(0, oo)) + assert _test_args(ProductContinuousDomain(D, E)) + + +def test_sympy__stats__crv__ConditionalContinuousDomain(): + from sympy.sets.sets import Interval + from sympy.stats.crv import (SingleContinuousDomain, + ConditionalContinuousDomain) + D = SingleContinuousDomain(x, Interval(-oo, oo)) + assert _test_args(ConditionalContinuousDomain(D, x > 0)) + + +def test_sympy__stats__crv__ContinuousPSpace(): + from sympy.sets.sets import Interval + from sympy.stats.crv import ContinuousPSpace, SingleContinuousDomain + D = SingleContinuousDomain(x, Interval(-oo, oo)) + assert _test_args(ContinuousPSpace(D, nd)) + + +def test_sympy__stats__crv__SingleContinuousPSpace(): + from sympy.stats.crv import SingleContinuousPSpace + assert _test_args(SingleContinuousPSpace(x, nd)) + +@SKIP("abstract class") +def test_sympy__stats__rv__Distribution(): + pass + +@SKIP("abstract class") +def test_sympy__stats__crv__SingleContinuousDistribution(): + pass + + +def test_sympy__stats__drv__SingleDiscreteDomain(): + from sympy.stats.drv import SingleDiscreteDomain + assert _test_args(SingleDiscreteDomain(x, S.Naturals)) + + +def test_sympy__stats__drv__ProductDiscreteDomain(): + from sympy.stats.drv import SingleDiscreteDomain, ProductDiscreteDomain + X = SingleDiscreteDomain(x, S.Naturals) + Y = SingleDiscreteDomain(y, S.Integers) + assert _test_args(ProductDiscreteDomain(X, Y)) + + +def test_sympy__stats__drv__SingleDiscretePSpace(): + from sympy.stats.drv import SingleDiscretePSpace + from sympy.stats.drv_types import PoissonDistribution + assert _test_args(SingleDiscretePSpace(x, PoissonDistribution(1))) + +def test_sympy__stats__drv__DiscretePSpace(): + from sympy.stats.drv import DiscretePSpace, SingleDiscreteDomain + density = Lambda(x, 2**(-x)) + domain = SingleDiscreteDomain(x, S.Naturals) + assert _test_args(DiscretePSpace(domain, density)) + +def test_sympy__stats__drv__ConditionalDiscreteDomain(): + from sympy.stats.drv import ConditionalDiscreteDomain, SingleDiscreteDomain + X = SingleDiscreteDomain(x, S.Naturals0) + assert _test_args(ConditionalDiscreteDomain(X, x > 2)) + +def test_sympy__stats__joint_rv__JointPSpace(): + from sympy.stats.joint_rv import JointPSpace, JointDistribution + assert _test_args(JointPSpace('X', JointDistribution(1))) + +def test_sympy__stats__joint_rv__JointRandomSymbol(): + from sympy.stats.joint_rv import JointRandomSymbol + assert _test_args(JointRandomSymbol(x)) + +def test_sympy__stats__joint_rv_types__JointDistributionHandmade(): + from sympy.tensor.indexed import Indexed + from sympy.stats.joint_rv_types import JointDistributionHandmade + x1, x2 = (Indexed('x', i) for i in (1, 2)) + assert _test_args(JointDistributionHandmade(x1 + x2, S.Reals**2)) + + +def test_sympy__stats__joint_rv__MarginalDistribution(): + from sympy.stats.rv import RandomSymbol + from sympy.stats.joint_rv import MarginalDistribution + r = RandomSymbol(S('r')) + assert _test_args(MarginalDistribution(r, (r,))) + + +def test_sympy__stats__compound_rv__CompoundDistribution(): + from sympy.stats.compound_rv import CompoundDistribution + from sympy.stats.drv_types import PoissonDistribution, Poisson + r = Poisson('r', 10) + assert _test_args(CompoundDistribution(PoissonDistribution(r))) + + +def test_sympy__stats__compound_rv__CompoundPSpace(): + from sympy.stats.compound_rv import CompoundPSpace, CompoundDistribution + from sympy.stats.drv_types import PoissonDistribution, Poisson + r = Poisson('r', 5) + C = CompoundDistribution(PoissonDistribution(r)) + assert _test_args(CompoundPSpace('C', C)) + + +@SKIP("abstract class") +def test_sympy__stats__drv__SingleDiscreteDistribution(): + pass + +@SKIP("abstract class") +def test_sympy__stats__drv__DiscreteDistribution(): + pass + +@SKIP("abstract class") +def test_sympy__stats__drv__DiscreteDomain(): + pass + + +def test_sympy__stats__rv__RandomDomain(): + from sympy.stats.rv import RandomDomain + from sympy.sets.sets import FiniteSet + assert _test_args(RandomDomain(FiniteSet(x), FiniteSet(1, 2, 3))) + + +def test_sympy__stats__rv__SingleDomain(): + from sympy.stats.rv import SingleDomain + from sympy.sets.sets import FiniteSet + assert _test_args(SingleDomain(x, FiniteSet(1, 2, 3))) + + +def test_sympy__stats__rv__ConditionalDomain(): + from sympy.stats.rv import ConditionalDomain, RandomDomain + from sympy.sets.sets import FiniteSet + D = RandomDomain(FiniteSet(x), FiniteSet(1, 2)) + assert _test_args(ConditionalDomain(D, x > 1)) + +def test_sympy__stats__rv__MatrixDomain(): + from sympy.stats.rv import MatrixDomain + from sympy.matrices import MatrixSet + from sympy.core.singleton import S + assert _test_args(MatrixDomain(x, MatrixSet(2, 2, S.Reals))) + +def test_sympy__stats__rv__PSpace(): + from sympy.stats.rv import PSpace, RandomDomain + from sympy.sets.sets import FiniteSet + D = RandomDomain(FiniteSet(x), FiniteSet(1, 2, 3, 4, 5, 6)) + assert _test_args(PSpace(D, die)) + + +@SKIP("abstract Class") +def test_sympy__stats__rv__SinglePSpace(): + pass + + +def test_sympy__stats__rv__RandomSymbol(): + from sympy.stats.rv import RandomSymbol + from sympy.stats.crv import SingleContinuousPSpace + A = SingleContinuousPSpace(x, nd) + assert _test_args(RandomSymbol(x, A)) + + +@SKIP("abstract Class") +def test_sympy__stats__rv__ProductPSpace(): + pass + + +def test_sympy__stats__rv__IndependentProductPSpace(): + from sympy.stats.rv import IndependentProductPSpace + from sympy.stats.crv import SingleContinuousPSpace + A = SingleContinuousPSpace(x, nd) + B = SingleContinuousPSpace(y, nd) + assert _test_args(IndependentProductPSpace(A, B)) + + +def test_sympy__stats__rv__ProductDomain(): + from sympy.sets.sets import Interval + from sympy.stats.rv import ProductDomain, SingleDomain + D = SingleDomain(x, Interval(-oo, oo)) + E = SingleDomain(y, Interval(0, oo)) + assert _test_args(ProductDomain(D, E)) + + +def test_sympy__stats__symbolic_probability__Probability(): + from sympy.stats.symbolic_probability import Probability + from sympy.stats import Normal + X = Normal('X', 0, 1) + assert _test_args(Probability(X > 0)) + + +def test_sympy__stats__symbolic_probability__Expectation(): + from sympy.stats.symbolic_probability import Expectation + from sympy.stats import Normal + X = Normal('X', 0, 1) + assert _test_args(Expectation(X > 0)) + + +def test_sympy__stats__symbolic_probability__Covariance(): + from sympy.stats.symbolic_probability import Covariance + from sympy.stats import Normal + X = Normal('X', 0, 1) + Y = Normal('Y', 0, 3) + assert _test_args(Covariance(X, Y)) + + +def test_sympy__stats__symbolic_probability__Variance(): + from sympy.stats.symbolic_probability import Variance + from sympy.stats import Normal + X = Normal('X', 0, 1) + assert _test_args(Variance(X)) + + +def test_sympy__stats__symbolic_probability__Moment(): + from sympy.stats.symbolic_probability import Moment + from sympy.stats import Normal + X = Normal('X', 0, 1) + assert _test_args(Moment(X, 3, 2, X > 3)) + + +def test_sympy__stats__symbolic_probability__CentralMoment(): + from sympy.stats.symbolic_probability import CentralMoment + from sympy.stats import Normal + X = Normal('X', 0, 1) + assert _test_args(CentralMoment(X, 2, X > 1)) + + +def test_sympy__stats__frv_types__DiscreteUniformDistribution(): + from sympy.stats.frv_types import DiscreteUniformDistribution + from sympy.core.containers import Tuple + assert _test_args(DiscreteUniformDistribution(Tuple(*list(range(6))))) + + +def test_sympy__stats__frv_types__DieDistribution(): + assert _test_args(die) + + +def test_sympy__stats__frv_types__BernoulliDistribution(): + from sympy.stats.frv_types import BernoulliDistribution + assert _test_args(BernoulliDistribution(S.Half, 0, 1)) + + +def test_sympy__stats__frv_types__BinomialDistribution(): + from sympy.stats.frv_types import BinomialDistribution + assert _test_args(BinomialDistribution(5, S.Half, 1, 0)) + +def test_sympy__stats__frv_types__BetaBinomialDistribution(): + from sympy.stats.frv_types import BetaBinomialDistribution + assert _test_args(BetaBinomialDistribution(5, 1, 1)) + + +def test_sympy__stats__frv_types__HypergeometricDistribution(): + from sympy.stats.frv_types import HypergeometricDistribution + assert _test_args(HypergeometricDistribution(10, 5, 3)) + + +def test_sympy__stats__frv_types__RademacherDistribution(): + from sympy.stats.frv_types import RademacherDistribution + assert _test_args(RademacherDistribution()) + +def test_sympy__stats__frv_types__IdealSolitonDistribution(): + from sympy.stats.frv_types import IdealSolitonDistribution + assert _test_args(IdealSolitonDistribution(10)) + +def test_sympy__stats__frv_types__RobustSolitonDistribution(): + from sympy.stats.frv_types import RobustSolitonDistribution + assert _test_args(RobustSolitonDistribution(1000, 0.5, 0.1)) + +def test_sympy__stats__frv__FiniteDomain(): + from sympy.stats.frv import FiniteDomain + assert _test_args(FiniteDomain({(x, 1), (x, 2)})) # x can be 1 or 2 + + +def test_sympy__stats__frv__SingleFiniteDomain(): + from sympy.stats.frv import SingleFiniteDomain + assert _test_args(SingleFiniteDomain(x, {1, 2})) # x can be 1 or 2 + + +def test_sympy__stats__frv__ProductFiniteDomain(): + from sympy.stats.frv import SingleFiniteDomain, ProductFiniteDomain + xd = SingleFiniteDomain(x, {1, 2}) + yd = SingleFiniteDomain(y, {1, 2}) + assert _test_args(ProductFiniteDomain(xd, yd)) + + +def test_sympy__stats__frv__ConditionalFiniteDomain(): + from sympy.stats.frv import SingleFiniteDomain, ConditionalFiniteDomain + xd = SingleFiniteDomain(x, {1, 2}) + assert _test_args(ConditionalFiniteDomain(xd, x > 1)) + + +def test_sympy__stats__frv__FinitePSpace(): + from sympy.stats.frv import FinitePSpace, SingleFiniteDomain + xd = SingleFiniteDomain(x, {1, 2, 3, 4, 5, 6}) + assert _test_args(FinitePSpace(xd, {(x, 1): S.Half, (x, 2): S.Half})) + + xd = SingleFiniteDomain(x, {1, 2}) + assert _test_args(FinitePSpace(xd, {(x, 1): S.Half, (x, 2): S.Half})) + + +def test_sympy__stats__frv__SingleFinitePSpace(): + from sympy.stats.frv import SingleFinitePSpace + from sympy.core.symbol import Symbol + + assert _test_args(SingleFinitePSpace(Symbol('x'), die)) + + +def test_sympy__stats__frv__ProductFinitePSpace(): + from sympy.stats.frv import SingleFinitePSpace, ProductFinitePSpace + from sympy.core.symbol import Symbol + xp = SingleFinitePSpace(Symbol('x'), die) + yp = SingleFinitePSpace(Symbol('y'), die) + assert _test_args(ProductFinitePSpace(xp, yp)) + +@SKIP("abstract class") +def test_sympy__stats__frv__SingleFiniteDistribution(): + pass + +@SKIP("abstract class") +def test_sympy__stats__crv__ContinuousDistribution(): + pass + + +def test_sympy__stats__frv_types__FiniteDistributionHandmade(): + from sympy.stats.frv_types import FiniteDistributionHandmade + from sympy.core.containers import Dict + assert _test_args(FiniteDistributionHandmade(Dict({1: 1}))) + + +def test_sympy__stats__crv_types__ContinuousDistributionHandmade(): + from sympy.stats.crv_types import ContinuousDistributionHandmade + from sympy.core.function import Lambda + from sympy.sets.sets import Interval + from sympy.abc import x + assert _test_args(ContinuousDistributionHandmade(Lambda(x, 2*x), + Interval(0, 1))) + + +def test_sympy__stats__drv_types__DiscreteDistributionHandmade(): + from sympy.stats.drv_types import DiscreteDistributionHandmade + from sympy.core.function import Lambda + from sympy.sets.sets import FiniteSet + from sympy.abc import x + assert _test_args(DiscreteDistributionHandmade(Lambda(x, Rational(1, 10)), + FiniteSet(*range(10)))) + + +def test_sympy__stats__rv__Density(): + from sympy.stats.rv import Density + from sympy.stats.crv_types import Normal + assert _test_args(Density(Normal('x', 0, 1))) + + +def test_sympy__stats__crv_types__ArcsinDistribution(): + from sympy.stats.crv_types import ArcsinDistribution + assert _test_args(ArcsinDistribution(0, 1)) + + +def test_sympy__stats__crv_types__BeniniDistribution(): + from sympy.stats.crv_types import BeniniDistribution + assert _test_args(BeniniDistribution(1, 1, 1)) + + +def test_sympy__stats__crv_types__BetaDistribution(): + from sympy.stats.crv_types import BetaDistribution + assert _test_args(BetaDistribution(1, 1)) + +def test_sympy__stats__crv_types__BetaNoncentralDistribution(): + from sympy.stats.crv_types import BetaNoncentralDistribution + assert _test_args(BetaNoncentralDistribution(1, 1, 1)) + + +def test_sympy__stats__crv_types__BetaPrimeDistribution(): + from sympy.stats.crv_types import BetaPrimeDistribution + assert _test_args(BetaPrimeDistribution(1, 1)) + +def test_sympy__stats__crv_types__BoundedParetoDistribution(): + from sympy.stats.crv_types import BoundedParetoDistribution + assert _test_args(BoundedParetoDistribution(1, 1, 2)) + +def test_sympy__stats__crv_types__CauchyDistribution(): + from sympy.stats.crv_types import CauchyDistribution + assert _test_args(CauchyDistribution(0, 1)) + + +def test_sympy__stats__crv_types__ChiDistribution(): + from sympy.stats.crv_types import ChiDistribution + assert _test_args(ChiDistribution(1)) + + +def test_sympy__stats__crv_types__ChiNoncentralDistribution(): + from sympy.stats.crv_types import ChiNoncentralDistribution + assert _test_args(ChiNoncentralDistribution(1,1)) + + +def test_sympy__stats__crv_types__ChiSquaredDistribution(): + from sympy.stats.crv_types import ChiSquaredDistribution + assert _test_args(ChiSquaredDistribution(1)) + + +def test_sympy__stats__crv_types__DagumDistribution(): + from sympy.stats.crv_types import DagumDistribution + assert _test_args(DagumDistribution(1, 1, 1)) + + +def test_sympy__stats__crv_types__DavisDistribution(): + from sympy.stats.crv_types import DavisDistribution + assert _test_args(DavisDistribution(1, 1, 1)) + + +def test_sympy__stats__crv_types__ExGaussianDistribution(): + from sympy.stats.crv_types import ExGaussianDistribution + assert _test_args(ExGaussianDistribution(1, 1, 1)) + + +def test_sympy__stats__crv_types__ExponentialDistribution(): + from sympy.stats.crv_types import ExponentialDistribution + assert _test_args(ExponentialDistribution(1)) + + +def test_sympy__stats__crv_types__ExponentialPowerDistribution(): + from sympy.stats.crv_types import ExponentialPowerDistribution + assert _test_args(ExponentialPowerDistribution(0, 1, 1)) + + +def test_sympy__stats__crv_types__FDistributionDistribution(): + from sympy.stats.crv_types import FDistributionDistribution + assert _test_args(FDistributionDistribution(1, 1)) + + +def test_sympy__stats__crv_types__FisherZDistribution(): + from sympy.stats.crv_types import FisherZDistribution + assert _test_args(FisherZDistribution(1, 1)) + + +def test_sympy__stats__crv_types__FrechetDistribution(): + from sympy.stats.crv_types import FrechetDistribution + assert _test_args(FrechetDistribution(1, 1, 1)) + + +def test_sympy__stats__crv_types__GammaInverseDistribution(): + from sympy.stats.crv_types import GammaInverseDistribution + assert _test_args(GammaInverseDistribution(1, 1)) + + +def test_sympy__stats__crv_types__GammaDistribution(): + from sympy.stats.crv_types import GammaDistribution + assert _test_args(GammaDistribution(1, 1)) + +def test_sympy__stats__crv_types__GumbelDistribution(): + from sympy.stats.crv_types import GumbelDistribution + assert _test_args(GumbelDistribution(1, 1, False)) + +def test_sympy__stats__crv_types__GompertzDistribution(): + from sympy.stats.crv_types import GompertzDistribution + assert _test_args(GompertzDistribution(1, 1)) + +def test_sympy__stats__crv_types__KumaraswamyDistribution(): + from sympy.stats.crv_types import KumaraswamyDistribution + assert _test_args(KumaraswamyDistribution(1, 1)) + +def test_sympy__stats__crv_types__LaplaceDistribution(): + from sympy.stats.crv_types import LaplaceDistribution + assert _test_args(LaplaceDistribution(0, 1)) + +def test_sympy__stats__crv_types__LevyDistribution(): + from sympy.stats.crv_types import LevyDistribution + assert _test_args(LevyDistribution(0, 1)) + +def test_sympy__stats__crv_types__LogCauchyDistribution(): + from sympy.stats.crv_types import LogCauchyDistribution + assert _test_args(LogCauchyDistribution(0, 1)) + +def test_sympy__stats__crv_types__LogisticDistribution(): + from sympy.stats.crv_types import LogisticDistribution + assert _test_args(LogisticDistribution(0, 1)) + +def test_sympy__stats__crv_types__LogLogisticDistribution(): + from sympy.stats.crv_types import LogLogisticDistribution + assert _test_args(LogLogisticDistribution(1, 1)) + +def test_sympy__stats__crv_types__LogitNormalDistribution(): + from sympy.stats.crv_types import LogitNormalDistribution + assert _test_args(LogitNormalDistribution(0, 1)) + +def test_sympy__stats__crv_types__LogNormalDistribution(): + from sympy.stats.crv_types import LogNormalDistribution + assert _test_args(LogNormalDistribution(0, 1)) + +def test_sympy__stats__crv_types__LomaxDistribution(): + from sympy.stats.crv_types import LomaxDistribution + assert _test_args(LomaxDistribution(1, 2)) + +def test_sympy__stats__crv_types__MaxwellDistribution(): + from sympy.stats.crv_types import MaxwellDistribution + assert _test_args(MaxwellDistribution(1)) + +def test_sympy__stats__crv_types__MoyalDistribution(): + from sympy.stats.crv_types import MoyalDistribution + assert _test_args(MoyalDistribution(1,2)) + +def test_sympy__stats__crv_types__NakagamiDistribution(): + from sympy.stats.crv_types import NakagamiDistribution + assert _test_args(NakagamiDistribution(1, 1)) + + +def test_sympy__stats__crv_types__NormalDistribution(): + from sympy.stats.crv_types import NormalDistribution + assert _test_args(NormalDistribution(0, 1)) + +def test_sympy__stats__crv_types__GaussianInverseDistribution(): + from sympy.stats.crv_types import GaussianInverseDistribution + assert _test_args(GaussianInverseDistribution(1, 1)) + + +def test_sympy__stats__crv_types__ParetoDistribution(): + from sympy.stats.crv_types import ParetoDistribution + assert _test_args(ParetoDistribution(1, 1)) + +def test_sympy__stats__crv_types__PowerFunctionDistribution(): + from sympy.stats.crv_types import PowerFunctionDistribution + assert _test_args(PowerFunctionDistribution(2,0,1)) + +def test_sympy__stats__crv_types__QuadraticUDistribution(): + from sympy.stats.crv_types import QuadraticUDistribution + assert _test_args(QuadraticUDistribution(1, 2)) + +def test_sympy__stats__crv_types__RaisedCosineDistribution(): + from sympy.stats.crv_types import RaisedCosineDistribution + assert _test_args(RaisedCosineDistribution(1, 1)) + +def test_sympy__stats__crv_types__RayleighDistribution(): + from sympy.stats.crv_types import RayleighDistribution + assert _test_args(RayleighDistribution(1)) + +def test_sympy__stats__crv_types__ReciprocalDistribution(): + from sympy.stats.crv_types import ReciprocalDistribution + assert _test_args(ReciprocalDistribution(5, 30)) + +def test_sympy__stats__crv_types__ShiftedGompertzDistribution(): + from sympy.stats.crv_types import ShiftedGompertzDistribution + assert _test_args(ShiftedGompertzDistribution(1, 1)) + +def test_sympy__stats__crv_types__StudentTDistribution(): + from sympy.stats.crv_types import StudentTDistribution + assert _test_args(StudentTDistribution(1)) + +def test_sympy__stats__crv_types__TrapezoidalDistribution(): + from sympy.stats.crv_types import TrapezoidalDistribution + assert _test_args(TrapezoidalDistribution(1, 2, 3, 4)) + +def test_sympy__stats__crv_types__TriangularDistribution(): + from sympy.stats.crv_types import TriangularDistribution + assert _test_args(TriangularDistribution(-1, 0, 1)) + + +def test_sympy__stats__crv_types__UniformDistribution(): + from sympy.stats.crv_types import UniformDistribution + assert _test_args(UniformDistribution(0, 1)) + + +def test_sympy__stats__crv_types__UniformSumDistribution(): + from sympy.stats.crv_types import UniformSumDistribution + assert _test_args(UniformSumDistribution(1)) + + +def test_sympy__stats__crv_types__VonMisesDistribution(): + from sympy.stats.crv_types import VonMisesDistribution + assert _test_args(VonMisesDistribution(1, 1)) + + +def test_sympy__stats__crv_types__WeibullDistribution(): + from sympy.stats.crv_types import WeibullDistribution + assert _test_args(WeibullDistribution(1, 1)) + + +def test_sympy__stats__crv_types__WignerSemicircleDistribution(): + from sympy.stats.crv_types import WignerSemicircleDistribution + assert _test_args(WignerSemicircleDistribution(1)) + + +def test_sympy__stats__drv_types__GeometricDistribution(): + from sympy.stats.drv_types import GeometricDistribution + assert _test_args(GeometricDistribution(.5)) + +def test_sympy__stats__drv_types__HermiteDistribution(): + from sympy.stats.drv_types import HermiteDistribution + assert _test_args(HermiteDistribution(1, 2)) + +def test_sympy__stats__drv_types__LogarithmicDistribution(): + from sympy.stats.drv_types import LogarithmicDistribution + assert _test_args(LogarithmicDistribution(.5)) + + +def test_sympy__stats__drv_types__NegativeBinomialDistribution(): + from sympy.stats.drv_types import NegativeBinomialDistribution + assert _test_args(NegativeBinomialDistribution(.5, .5)) + +def test_sympy__stats__drv_types__FlorySchulzDistribution(): + from sympy.stats.drv_types import FlorySchulzDistribution + assert _test_args(FlorySchulzDistribution(.5)) + +def test_sympy__stats__drv_types__PoissonDistribution(): + from sympy.stats.drv_types import PoissonDistribution + assert _test_args(PoissonDistribution(1)) + + +def test_sympy__stats__drv_types__SkellamDistribution(): + from sympy.stats.drv_types import SkellamDistribution + assert _test_args(SkellamDistribution(1, 1)) + + +def test_sympy__stats__drv_types__YuleSimonDistribution(): + from sympy.stats.drv_types import YuleSimonDistribution + assert _test_args(YuleSimonDistribution(.5)) + + +def test_sympy__stats__drv_types__ZetaDistribution(): + from sympy.stats.drv_types import ZetaDistribution + assert _test_args(ZetaDistribution(1.5)) + + +def test_sympy__stats__joint_rv__JointDistribution(): + from sympy.stats.joint_rv import JointDistribution + assert _test_args(JointDistribution(1, 2, 3, 4)) + + +def test_sympy__stats__joint_rv_types__MultivariateNormalDistribution(): + from sympy.stats.joint_rv_types import MultivariateNormalDistribution + assert _test_args( + MultivariateNormalDistribution([0, 1], [[1, 0],[0, 1]])) + +def test_sympy__stats__joint_rv_types__MultivariateLaplaceDistribution(): + from sympy.stats.joint_rv_types import MultivariateLaplaceDistribution + assert _test_args(MultivariateLaplaceDistribution([0, 1], [[1, 0],[0, 1]])) + + +def test_sympy__stats__joint_rv_types__MultivariateTDistribution(): + from sympy.stats.joint_rv_types import MultivariateTDistribution + assert _test_args(MultivariateTDistribution([0, 1], [[1, 0],[0, 1]], 1)) + + +def test_sympy__stats__joint_rv_types__NormalGammaDistribution(): + from sympy.stats.joint_rv_types import NormalGammaDistribution + assert _test_args(NormalGammaDistribution(1, 2, 3, 4)) + +def test_sympy__stats__joint_rv_types__GeneralizedMultivariateLogGammaDistribution(): + from sympy.stats.joint_rv_types import GeneralizedMultivariateLogGammaDistribution + v, l, mu = (4, [1, 2, 3, 4], [1, 2, 3, 4]) + assert _test_args(GeneralizedMultivariateLogGammaDistribution(S.Half, v, l, mu)) + +def test_sympy__stats__joint_rv_types__MultivariateBetaDistribution(): + from sympy.stats.joint_rv_types import MultivariateBetaDistribution + assert _test_args(MultivariateBetaDistribution([1, 2, 3])) + +def test_sympy__stats__joint_rv_types__MultivariateEwensDistribution(): + from sympy.stats.joint_rv_types import MultivariateEwensDistribution + assert _test_args(MultivariateEwensDistribution(5, 1)) + +def test_sympy__stats__joint_rv_types__MultinomialDistribution(): + from sympy.stats.joint_rv_types import MultinomialDistribution + assert _test_args(MultinomialDistribution(5, [0.5, 0.1, 0.3])) + +def test_sympy__stats__joint_rv_types__NegativeMultinomialDistribution(): + from sympy.stats.joint_rv_types import NegativeMultinomialDistribution + assert _test_args(NegativeMultinomialDistribution(5, [0.5, 0.1, 0.3])) + +def test_sympy__stats__rv__RandomIndexedSymbol(): + from sympy.stats.rv import RandomIndexedSymbol, pspace + from sympy.stats.stochastic_process_types import DiscreteMarkovChain + X = DiscreteMarkovChain("X") + assert _test_args(RandomIndexedSymbol(X[0].symbol, pspace(X[0]))) + +def test_sympy__stats__rv__RandomMatrixSymbol(): + from sympy.stats.rv import RandomMatrixSymbol + from sympy.stats.random_matrix import RandomMatrixPSpace + pspace = RandomMatrixPSpace('P') + assert _test_args(RandomMatrixSymbol('M', 3, 3, pspace)) + +def test_sympy__stats__stochastic_process__StochasticPSpace(): + from sympy.stats.stochastic_process import StochasticPSpace + from sympy.stats.stochastic_process_types import StochasticProcess + from sympy.stats.frv_types import BernoulliDistribution + assert _test_args(StochasticPSpace("Y", StochasticProcess("Y", [1, 2, 3]), BernoulliDistribution(S.Half, 1, 0))) + +def test_sympy__stats__stochastic_process_types__StochasticProcess(): + from sympy.stats.stochastic_process_types import StochasticProcess + assert _test_args(StochasticProcess("Y", [1, 2, 3])) + +def test_sympy__stats__stochastic_process_types__MarkovProcess(): + from sympy.stats.stochastic_process_types import MarkovProcess + assert _test_args(MarkovProcess("Y", [1, 2, 3])) + +def test_sympy__stats__stochastic_process_types__DiscreteTimeStochasticProcess(): + from sympy.stats.stochastic_process_types import DiscreteTimeStochasticProcess + assert _test_args(DiscreteTimeStochasticProcess("Y", [1, 2, 3])) + +def test_sympy__stats__stochastic_process_types__ContinuousTimeStochasticProcess(): + from sympy.stats.stochastic_process_types import ContinuousTimeStochasticProcess + assert _test_args(ContinuousTimeStochasticProcess("Y", [1, 2, 3])) + +def test_sympy__stats__stochastic_process_types__TransitionMatrixOf(): + from sympy.stats.stochastic_process_types import TransitionMatrixOf, DiscreteMarkovChain + from sympy.matrices.expressions.matexpr import MatrixSymbol + DMC = DiscreteMarkovChain("Y") + assert _test_args(TransitionMatrixOf(DMC, MatrixSymbol('T', 3, 3))) + +def test_sympy__stats__stochastic_process_types__GeneratorMatrixOf(): + from sympy.stats.stochastic_process_types import GeneratorMatrixOf, ContinuousMarkovChain + from sympy.matrices.expressions.matexpr import MatrixSymbol + DMC = ContinuousMarkovChain("Y") + assert _test_args(GeneratorMatrixOf(DMC, MatrixSymbol('T', 3, 3))) + +def test_sympy__stats__stochastic_process_types__StochasticStateSpaceOf(): + from sympy.stats.stochastic_process_types import StochasticStateSpaceOf, DiscreteMarkovChain + DMC = DiscreteMarkovChain("Y") + assert _test_args(StochasticStateSpaceOf(DMC, [0, 1, 2])) + +def test_sympy__stats__stochastic_process_types__DiscreteMarkovChain(): + from sympy.stats.stochastic_process_types import DiscreteMarkovChain + from sympy.matrices.expressions.matexpr import MatrixSymbol + assert _test_args(DiscreteMarkovChain("Y", [0, 1, 2], MatrixSymbol('T', 3, 3))) + +def test_sympy__stats__stochastic_process_types__ContinuousMarkovChain(): + from sympy.stats.stochastic_process_types import ContinuousMarkovChain + from sympy.matrices.expressions.matexpr import MatrixSymbol + assert _test_args(ContinuousMarkovChain("Y", [0, 1, 2], MatrixSymbol('T', 3, 3))) + +def test_sympy__stats__stochastic_process_types__BernoulliProcess(): + from sympy.stats.stochastic_process_types import BernoulliProcess + assert _test_args(BernoulliProcess("B", 0.5, 1, 0)) + +def test_sympy__stats__stochastic_process_types__CountingProcess(): + from sympy.stats.stochastic_process_types import CountingProcess + assert _test_args(CountingProcess("C")) + +def test_sympy__stats__stochastic_process_types__PoissonProcess(): + from sympy.stats.stochastic_process_types import PoissonProcess + assert _test_args(PoissonProcess("X", 2)) + +def test_sympy__stats__stochastic_process_types__WienerProcess(): + from sympy.stats.stochastic_process_types import WienerProcess + assert _test_args(WienerProcess("X")) + +def test_sympy__stats__stochastic_process_types__GammaProcess(): + from sympy.stats.stochastic_process_types import GammaProcess + assert _test_args(GammaProcess("X", 1, 2)) + +def test_sympy__stats__random_matrix__RandomMatrixPSpace(): + from sympy.stats.random_matrix import RandomMatrixPSpace + from sympy.stats.random_matrix_models import RandomMatrixEnsembleModel + model = RandomMatrixEnsembleModel('R', 3) + assert _test_args(RandomMatrixPSpace('P', model=model)) + +def test_sympy__stats__random_matrix_models__RandomMatrixEnsembleModel(): + from sympy.stats.random_matrix_models import RandomMatrixEnsembleModel + assert _test_args(RandomMatrixEnsembleModel('R', 3)) + +def test_sympy__stats__random_matrix_models__GaussianEnsembleModel(): + from sympy.stats.random_matrix_models import GaussianEnsembleModel + assert _test_args(GaussianEnsembleModel('G', 3)) + +def test_sympy__stats__random_matrix_models__GaussianUnitaryEnsembleModel(): + from sympy.stats.random_matrix_models import GaussianUnitaryEnsembleModel + assert _test_args(GaussianUnitaryEnsembleModel('U', 3)) + +def test_sympy__stats__random_matrix_models__GaussianOrthogonalEnsembleModel(): + from sympy.stats.random_matrix_models import GaussianOrthogonalEnsembleModel + assert _test_args(GaussianOrthogonalEnsembleModel('U', 3)) + +def test_sympy__stats__random_matrix_models__GaussianSymplecticEnsembleModel(): + from sympy.stats.random_matrix_models import GaussianSymplecticEnsembleModel + assert _test_args(GaussianSymplecticEnsembleModel('U', 3)) + +def test_sympy__stats__random_matrix_models__CircularEnsembleModel(): + from sympy.stats.random_matrix_models import CircularEnsembleModel + assert _test_args(CircularEnsembleModel('C', 3)) + +def test_sympy__stats__random_matrix_models__CircularUnitaryEnsembleModel(): + from sympy.stats.random_matrix_models import CircularUnitaryEnsembleModel + assert _test_args(CircularUnitaryEnsembleModel('U', 3)) + +def test_sympy__stats__random_matrix_models__CircularOrthogonalEnsembleModel(): + from sympy.stats.random_matrix_models import CircularOrthogonalEnsembleModel + assert _test_args(CircularOrthogonalEnsembleModel('O', 3)) + +def test_sympy__stats__random_matrix_models__CircularSymplecticEnsembleModel(): + from sympy.stats.random_matrix_models import CircularSymplecticEnsembleModel + assert _test_args(CircularSymplecticEnsembleModel('S', 3)) + +def test_sympy__stats__symbolic_multivariate_probability__ExpectationMatrix(): + from sympy.stats import ExpectationMatrix + from sympy.stats.rv import RandomMatrixSymbol + assert _test_args(ExpectationMatrix(RandomMatrixSymbol('R', 2, 1))) + +def test_sympy__stats__symbolic_multivariate_probability__VarianceMatrix(): + from sympy.stats import VarianceMatrix + from sympy.stats.rv import RandomMatrixSymbol + assert _test_args(VarianceMatrix(RandomMatrixSymbol('R', 3, 1))) + +def test_sympy__stats__symbolic_multivariate_probability__CrossCovarianceMatrix(): + from sympy.stats import CrossCovarianceMatrix + from sympy.stats.rv import RandomMatrixSymbol + assert _test_args(CrossCovarianceMatrix(RandomMatrixSymbol('R', 3, 1), + RandomMatrixSymbol('X', 3, 1))) + +def test_sympy__stats__matrix_distributions__MatrixPSpace(): + from sympy.stats.matrix_distributions import MatrixDistribution, MatrixPSpace + from sympy.matrices.dense import Matrix + M = MatrixDistribution(1, Matrix([[1, 0], [0, 1]])) + assert _test_args(MatrixPSpace('M', M, 2, 2)) + +def test_sympy__stats__matrix_distributions__MatrixDistribution(): + from sympy.stats.matrix_distributions import MatrixDistribution + from sympy.matrices.dense import Matrix + assert _test_args(MatrixDistribution(1, Matrix([[1, 0], [0, 1]]))) + +def test_sympy__stats__matrix_distributions__MatrixGammaDistribution(): + from sympy.stats.matrix_distributions import MatrixGammaDistribution + from sympy.matrices.dense import Matrix + assert _test_args(MatrixGammaDistribution(3, 4, Matrix([[1, 0], [0, 1]]))) + +def test_sympy__stats__matrix_distributions__WishartDistribution(): + from sympy.stats.matrix_distributions import WishartDistribution + from sympy.matrices.dense import Matrix + assert _test_args(WishartDistribution(3, Matrix([[1, 0], [0, 1]]))) + +def test_sympy__stats__matrix_distributions__MatrixNormalDistribution(): + from sympy.stats.matrix_distributions import MatrixNormalDistribution + from sympy.matrices.expressions.matexpr import MatrixSymbol + L = MatrixSymbol('L', 1, 2) + S1 = MatrixSymbol('S1', 1, 1) + S2 = MatrixSymbol('S2', 2, 2) + assert _test_args(MatrixNormalDistribution(L, S1, S2)) + +def test_sympy__stats__matrix_distributions__MatrixStudentTDistribution(): + from sympy.stats.matrix_distributions import MatrixStudentTDistribution + from sympy.matrices.expressions.matexpr import MatrixSymbol + v = symbols('v', positive=True) + Omega = MatrixSymbol('Omega', 3, 3) + Sigma = MatrixSymbol('Sigma', 1, 1) + Location = MatrixSymbol('Location', 1, 3) + assert _test_args(MatrixStudentTDistribution(v, Location, Omega, Sigma)) + +def test_sympy__utilities__matchpy_connector__WildDot(): + from sympy.utilities.matchpy_connector import WildDot + assert _test_args(WildDot("w_")) + + +def test_sympy__utilities__matchpy_connector__WildPlus(): + from sympy.utilities.matchpy_connector import WildPlus + assert _test_args(WildPlus("w__")) + + +def test_sympy__utilities__matchpy_connector__WildStar(): + from sympy.utilities.matchpy_connector import WildStar + assert _test_args(WildStar("w___")) + + +def test_sympy__core__symbol__Str(): + from sympy.core.symbol import Str + assert _test_args(Str('t')) + +def test_sympy__core__symbol__Dummy(): + from sympy.core.symbol import Dummy + assert _test_args(Dummy('t')) + + +def test_sympy__core__symbol__Symbol(): + from sympy.core.symbol import Symbol + assert _test_args(Symbol('t')) + + +def test_sympy__core__symbol__Wild(): + from sympy.core.symbol import Wild + assert _test_args(Wild('x', exclude=[x])) + + +@SKIP("abstract class") +def test_sympy__functions__combinatorial__factorials__CombinatorialFunction(): + pass + + +def test_sympy__functions__combinatorial__factorials__FallingFactorial(): + from sympy.functions.combinatorial.factorials import FallingFactorial + assert _test_args(FallingFactorial(2, x)) + + +def test_sympy__functions__combinatorial__factorials__MultiFactorial(): + from sympy.functions.combinatorial.factorials import MultiFactorial + assert _test_args(MultiFactorial(x)) + + +def test_sympy__functions__combinatorial__factorials__RisingFactorial(): + from sympy.functions.combinatorial.factorials import RisingFactorial + assert _test_args(RisingFactorial(2, x)) + + +def test_sympy__functions__combinatorial__factorials__binomial(): + from sympy.functions.combinatorial.factorials import binomial + assert _test_args(binomial(2, x)) + + +def test_sympy__functions__combinatorial__factorials__subfactorial(): + from sympy.functions.combinatorial.factorials import subfactorial + assert _test_args(subfactorial(x)) + + +def test_sympy__functions__combinatorial__factorials__factorial(): + from sympy.functions.combinatorial.factorials import factorial + assert _test_args(factorial(x)) + + +def test_sympy__functions__combinatorial__factorials__factorial2(): + from sympy.functions.combinatorial.factorials import factorial2 + assert _test_args(factorial2(x)) + + +def test_sympy__functions__combinatorial__numbers__bell(): + from sympy.functions.combinatorial.numbers import bell + assert _test_args(bell(x, y)) + + +def test_sympy__functions__combinatorial__numbers__bernoulli(): + from sympy.functions.combinatorial.numbers import bernoulli + assert _test_args(bernoulli(x)) + + +def test_sympy__functions__combinatorial__numbers__catalan(): + from sympy.functions.combinatorial.numbers import catalan + assert _test_args(catalan(x)) + + +def test_sympy__functions__combinatorial__numbers__genocchi(): + from sympy.functions.combinatorial.numbers import genocchi + assert _test_args(genocchi(x)) + + +def test_sympy__functions__combinatorial__numbers__euler(): + from sympy.functions.combinatorial.numbers import euler + assert _test_args(euler(x)) + + +def test_sympy__functions__combinatorial__numbers__andre(): + from sympy.functions.combinatorial.numbers import andre + assert _test_args(andre(x)) + + +def test_sympy__functions__combinatorial__numbers__carmichael(): + from sympy.functions.combinatorial.numbers import carmichael + assert _test_args(carmichael(x)) + + +def test_sympy__functions__combinatorial__numbers__divisor_sigma(): + from sympy.functions.combinatorial.numbers import divisor_sigma + k = symbols('k', integer=True) + n = symbols('n', integer=True) + t = divisor_sigma(n, k) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__fibonacci(): + from sympy.functions.combinatorial.numbers import fibonacci + assert _test_args(fibonacci(x)) + + +def test_sympy__functions__combinatorial__numbers__jacobi_symbol(): + from sympy.functions.combinatorial.numbers import jacobi_symbol + assert _test_args(jacobi_symbol(2, 3)) + + +def test_sympy__functions__combinatorial__numbers__kronecker_symbol(): + from sympy.functions.combinatorial.numbers import kronecker_symbol + assert _test_args(kronecker_symbol(2, 3)) + + +def test_sympy__functions__combinatorial__numbers__legendre_symbol(): + from sympy.functions.combinatorial.numbers import legendre_symbol + assert _test_args(legendre_symbol(2, 3)) + + +def test_sympy__functions__combinatorial__numbers__mobius(): + from sympy.functions.combinatorial.numbers import mobius + assert _test_args(mobius(2)) + + +def test_sympy__functions__combinatorial__numbers__motzkin(): + from sympy.functions.combinatorial.numbers import motzkin + assert _test_args(motzkin(5)) + + +def test_sympy__functions__combinatorial__numbers__partition(): + from sympy.core.symbol import Symbol + from sympy.functions.combinatorial.numbers import partition + assert _test_args(partition(Symbol('a', integer=True))) + + +def test_sympy__functions__combinatorial__numbers__primenu(): + from sympy.functions.combinatorial.numbers import primenu + n = symbols('n', integer=True) + t = primenu(n) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__primeomega(): + from sympy.functions.combinatorial.numbers import primeomega + n = symbols('n', integer=True) + t = primeomega(n) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__primepi(): + from sympy.functions.combinatorial.numbers import primepi + n = symbols('n') + t = primepi(n) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__reduced_totient(): + from sympy.functions.combinatorial.numbers import reduced_totient + k = symbols('k', integer=True) + t = reduced_totient(k) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__totient(): + from sympy.functions.combinatorial.numbers import totient + k = symbols('k', integer=True) + t = totient(k) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__tribonacci(): + from sympy.functions.combinatorial.numbers import tribonacci + assert _test_args(tribonacci(x)) + + +def test_sympy__functions__combinatorial__numbers__udivisor_sigma(): + from sympy.functions.combinatorial.numbers import udivisor_sigma + k = symbols('k', integer=True) + n = symbols('n', integer=True) + t = udivisor_sigma(n, k) + assert _test_args(t) + + +def test_sympy__functions__combinatorial__numbers__harmonic(): + from sympy.functions.combinatorial.numbers import harmonic + assert _test_args(harmonic(x, 2)) + + +def test_sympy__functions__combinatorial__numbers__lucas(): + from sympy.functions.combinatorial.numbers import lucas + assert _test_args(lucas(x)) + + +def test_sympy__functions__elementary__complexes__Abs(): + from sympy.functions.elementary.complexes import Abs + assert _test_args(Abs(x)) + + +def test_sympy__functions__elementary__complexes__adjoint(): + from sympy.functions.elementary.complexes import adjoint + assert _test_args(adjoint(x)) + + +def test_sympy__functions__elementary__complexes__arg(): + from sympy.functions.elementary.complexes import arg + assert _test_args(arg(x)) + + +def test_sympy__functions__elementary__complexes__conjugate(): + from sympy.functions.elementary.complexes import conjugate + assert _test_args(conjugate(x)) + + +def test_sympy__functions__elementary__complexes__im(): + from sympy.functions.elementary.complexes import im + assert _test_args(im(x)) + + +def test_sympy__functions__elementary__complexes__re(): + from sympy.functions.elementary.complexes import re + assert _test_args(re(x)) + + +def test_sympy__functions__elementary__complexes__sign(): + from sympy.functions.elementary.complexes import sign + assert _test_args(sign(x)) + + +def test_sympy__functions__elementary__complexes__polar_lift(): + from sympy.functions.elementary.complexes import polar_lift + assert _test_args(polar_lift(x)) + + +def test_sympy__functions__elementary__complexes__periodic_argument(): + from sympy.functions.elementary.complexes import periodic_argument + assert _test_args(periodic_argument(x, y)) + + +def test_sympy__functions__elementary__complexes__principal_branch(): + from sympy.functions.elementary.complexes import principal_branch + assert _test_args(principal_branch(x, y)) + + +def test_sympy__functions__elementary__complexes__transpose(): + from sympy.functions.elementary.complexes import transpose + assert _test_args(transpose(x)) + + +def test_sympy__functions__elementary__exponential__LambertW(): + from sympy.functions.elementary.exponential import LambertW + assert _test_args(LambertW(2)) + + +@SKIP("abstract class") +def test_sympy__functions__elementary__exponential__ExpBase(): + pass + + +def test_sympy__functions__elementary__exponential__exp(): + from sympy.functions.elementary.exponential import exp + assert _test_args(exp(2)) + + +def test_sympy__functions__elementary__exponential__exp_polar(): + from sympy.functions.elementary.exponential import exp_polar + assert _test_args(exp_polar(2)) + + +def test_sympy__functions__elementary__exponential__log(): + from sympy.functions.elementary.exponential import log + assert _test_args(log(2)) + + +@SKIP("abstract class") +def test_sympy__functions__elementary__hyperbolic__HyperbolicFunction(): + pass + + +@SKIP("abstract class") +def test_sympy__functions__elementary__hyperbolic__ReciprocalHyperbolicFunction(): + pass + + +@SKIP("abstract class") +def test_sympy__functions__elementary__hyperbolic__InverseHyperbolicFunction(): + pass + + +def test_sympy__functions__elementary__hyperbolic__acosh(): + from sympy.functions.elementary.hyperbolic import acosh + assert _test_args(acosh(2)) + + +def test_sympy__functions__elementary__hyperbolic__acoth(): + from sympy.functions.elementary.hyperbolic import acoth + assert _test_args(acoth(2)) + + +def test_sympy__functions__elementary__hyperbolic__asinh(): + from sympy.functions.elementary.hyperbolic import asinh + assert _test_args(asinh(2)) + + +def test_sympy__functions__elementary__hyperbolic__atanh(): + from sympy.functions.elementary.hyperbolic import atanh + assert _test_args(atanh(2)) + + +def test_sympy__functions__elementary__hyperbolic__asech(): + from sympy.functions.elementary.hyperbolic import asech + assert _test_args(asech(x)) + +def test_sympy__functions__elementary__hyperbolic__acsch(): + from sympy.functions.elementary.hyperbolic import acsch + assert _test_args(acsch(x)) + +def test_sympy__functions__elementary__hyperbolic__cosh(): + from sympy.functions.elementary.hyperbolic import cosh + assert _test_args(cosh(2)) + + +def test_sympy__functions__elementary__hyperbolic__coth(): + from sympy.functions.elementary.hyperbolic import coth + assert _test_args(coth(2)) + + +def test_sympy__functions__elementary__hyperbolic__csch(): + from sympy.functions.elementary.hyperbolic import csch + assert _test_args(csch(2)) + + +def test_sympy__functions__elementary__hyperbolic__sech(): + from sympy.functions.elementary.hyperbolic import sech + assert _test_args(sech(2)) + + +def test_sympy__functions__elementary__hyperbolic__sinh(): + from sympy.functions.elementary.hyperbolic import sinh + assert _test_args(sinh(2)) + + +def test_sympy__functions__elementary__hyperbolic__tanh(): + from sympy.functions.elementary.hyperbolic import tanh + assert _test_args(tanh(2)) + + +@SKIP("abstract class") +def test_sympy__functions__elementary__integers__RoundFunction(): + pass + + +def test_sympy__functions__elementary__integers__ceiling(): + from sympy.functions.elementary.integers import ceiling + assert _test_args(ceiling(x)) + + +def test_sympy__functions__elementary__integers__floor(): + from sympy.functions.elementary.integers import floor + assert _test_args(floor(x)) + + +def test_sympy__functions__elementary__integers__frac(): + from sympy.functions.elementary.integers import frac + assert _test_args(frac(x)) + + +def test_sympy__functions__elementary__miscellaneous__IdentityFunction(): + from sympy.functions.elementary.miscellaneous import IdentityFunction + assert _test_args(IdentityFunction()) + + +def test_sympy__functions__elementary__miscellaneous__Max(): + from sympy.functions.elementary.miscellaneous import Max + assert _test_args(Max(x, 2)) + + +def test_sympy__functions__elementary__miscellaneous__Min(): + from sympy.functions.elementary.miscellaneous import Min + assert _test_args(Min(x, 2)) + + +@SKIP("abstract class") +def test_sympy__functions__elementary__miscellaneous__MinMaxBase(): + pass + + +def test_sympy__functions__elementary__miscellaneous__Rem(): + from sympy.functions.elementary.miscellaneous import Rem + assert _test_args(Rem(x, 2)) + + +def test_sympy__functions__elementary__piecewise__ExprCondPair(): + from sympy.functions.elementary.piecewise import ExprCondPair + assert _test_args(ExprCondPair(1, True)) + + +def test_sympy__functions__elementary__piecewise__Piecewise(): + from sympy.functions.elementary.piecewise import Piecewise + assert _test_args(Piecewise((1, x >= 0), (0, True))) + + +@SKIP("abstract class") +def test_sympy__functions__elementary__trigonometric__TrigonometricFunction(): + pass + +@SKIP("abstract class") +def test_sympy__functions__elementary__trigonometric__ReciprocalTrigonometricFunction(): + pass + +@SKIP("abstract class") +def test_sympy__functions__elementary__trigonometric__InverseTrigonometricFunction(): + pass + +def test_sympy__functions__elementary__trigonometric__acos(): + from sympy.functions.elementary.trigonometric import acos + assert _test_args(acos(2)) + + +def test_sympy__functions__elementary__trigonometric__acot(): + from sympy.functions.elementary.trigonometric import acot + assert _test_args(acot(2)) + + +def test_sympy__functions__elementary__trigonometric__asin(): + from sympy.functions.elementary.trigonometric import asin + assert _test_args(asin(2)) + + +def test_sympy__functions__elementary__trigonometric__asec(): + from sympy.functions.elementary.trigonometric import asec + assert _test_args(asec(x)) + + +def test_sympy__functions__elementary__trigonometric__acsc(): + from sympy.functions.elementary.trigonometric import acsc + assert _test_args(acsc(x)) + + +def test_sympy__functions__elementary__trigonometric__atan(): + from sympy.functions.elementary.trigonometric import atan + assert _test_args(atan(2)) + + +def test_sympy__functions__elementary__trigonometric__atan2(): + from sympy.functions.elementary.trigonometric import atan2 + assert _test_args(atan2(2, 3)) + + +def test_sympy__functions__elementary__trigonometric__cos(): + from sympy.functions.elementary.trigonometric import cos + assert _test_args(cos(2)) + + +def test_sympy__functions__elementary__trigonometric__csc(): + from sympy.functions.elementary.trigonometric import csc + assert _test_args(csc(2)) + + +def test_sympy__functions__elementary__trigonometric__cot(): + from sympy.functions.elementary.trigonometric import cot + assert _test_args(cot(2)) + + +def test_sympy__functions__elementary__trigonometric__sin(): + assert _test_args(sin(2)) + + +def test_sympy__functions__elementary__trigonometric__sinc(): + from sympy.functions.elementary.trigonometric import sinc + assert _test_args(sinc(2)) + + +def test_sympy__functions__elementary__trigonometric__sec(): + from sympy.functions.elementary.trigonometric import sec + assert _test_args(sec(2)) + + +def test_sympy__functions__elementary__trigonometric__tan(): + from sympy.functions.elementary.trigonometric import tan + assert _test_args(tan(2)) + + +@SKIP("abstract class") +def test_sympy__functions__special__bessel__BesselBase(): + pass + + +@SKIP("abstract class") +def test_sympy__functions__special__bessel__SphericalBesselBase(): + pass + + +@SKIP("abstract class") +def test_sympy__functions__special__bessel__SphericalHankelBase(): + pass + + +def test_sympy__functions__special__bessel__besseli(): + from sympy.functions.special.bessel import besseli + assert _test_args(besseli(x, 1)) + + +def test_sympy__functions__special__bessel__besselj(): + from sympy.functions.special.bessel import besselj + assert _test_args(besselj(x, 1)) + + +def test_sympy__functions__special__bessel__besselk(): + from sympy.functions.special.bessel import besselk + assert _test_args(besselk(x, 1)) + + +def test_sympy__functions__special__bessel__bessely(): + from sympy.functions.special.bessel import bessely + assert _test_args(bessely(x, 1)) + + +def test_sympy__functions__special__bessel__hankel1(): + from sympy.functions.special.bessel import hankel1 + assert _test_args(hankel1(x, 1)) + + +def test_sympy__functions__special__bessel__hankel2(): + from sympy.functions.special.bessel import hankel2 + assert _test_args(hankel2(x, 1)) + + +def test_sympy__functions__special__bessel__jn(): + from sympy.functions.special.bessel import jn + assert _test_args(jn(0, x)) + + +def test_sympy__functions__special__bessel__yn(): + from sympy.functions.special.bessel import yn + assert _test_args(yn(0, x)) + + +def test_sympy__functions__special__bessel__hn1(): + from sympy.functions.special.bessel import hn1 + assert _test_args(hn1(0, x)) + + +def test_sympy__functions__special__bessel__hn2(): + from sympy.functions.special.bessel import hn2 + assert _test_args(hn2(0, x)) + + +def test_sympy__functions__special__bessel__AiryBase(): + pass + + +def test_sympy__functions__special__bessel__airyai(): + from sympy.functions.special.bessel import airyai + assert _test_args(airyai(2)) + + +def test_sympy__functions__special__bessel__airybi(): + from sympy.functions.special.bessel import airybi + assert _test_args(airybi(2)) + + +def test_sympy__functions__special__bessel__airyaiprime(): + from sympy.functions.special.bessel import airyaiprime + assert _test_args(airyaiprime(2)) + + +def test_sympy__functions__special__bessel__airybiprime(): + from sympy.functions.special.bessel import airybiprime + assert _test_args(airybiprime(2)) + + +def test_sympy__functions__special__bessel__marcumq(): + from sympy.functions.special.bessel import marcumq + assert _test_args(marcumq(x, y, z)) + + +def test_sympy__functions__special__elliptic_integrals__elliptic_k(): + from sympy.functions.special.elliptic_integrals import elliptic_k as K + assert _test_args(K(x)) + + +def test_sympy__functions__special__elliptic_integrals__elliptic_f(): + from sympy.functions.special.elliptic_integrals import elliptic_f as F + assert _test_args(F(x, y)) + + +def test_sympy__functions__special__elliptic_integrals__elliptic_e(): + from sympy.functions.special.elliptic_integrals import elliptic_e as E + assert _test_args(E(x)) + assert _test_args(E(x, y)) + + +def test_sympy__functions__special__elliptic_integrals__elliptic_pi(): + from sympy.functions.special.elliptic_integrals import elliptic_pi as P + assert _test_args(P(x, y)) + assert _test_args(P(x, y, z)) + + +def test_sympy__functions__special__delta_functions__DiracDelta(): + from sympy.functions.special.delta_functions import DiracDelta + assert _test_args(DiracDelta(x, 1)) + + +def test_sympy__functions__special__singularity_functions__SingularityFunction(): + from sympy.functions.special.singularity_functions import SingularityFunction + assert _test_args(SingularityFunction(x, y, z)) + + +def test_sympy__functions__special__delta_functions__Heaviside(): + from sympy.functions.special.delta_functions import Heaviside + assert _test_args(Heaviside(x)) + + +def test_sympy__functions__special__error_functions__erf(): + from sympy.functions.special.error_functions import erf + assert _test_args(erf(2)) + +def test_sympy__functions__special__error_functions__erfc(): + from sympy.functions.special.error_functions import erfc + assert _test_args(erfc(2)) + +def test_sympy__functions__special__error_functions__erfi(): + from sympy.functions.special.error_functions import erfi + assert _test_args(erfi(2)) + +def test_sympy__functions__special__error_functions__erf2(): + from sympy.functions.special.error_functions import erf2 + assert _test_args(erf2(2, 3)) + +def test_sympy__functions__special__error_functions__erfinv(): + from sympy.functions.special.error_functions import erfinv + assert _test_args(erfinv(2)) + +def test_sympy__functions__special__error_functions__erfcinv(): + from sympy.functions.special.error_functions import erfcinv + assert _test_args(erfcinv(2)) + +def test_sympy__functions__special__error_functions__erf2inv(): + from sympy.functions.special.error_functions import erf2inv + assert _test_args(erf2inv(2, 3)) + +@SKIP("abstract class") +def test_sympy__functions__special__error_functions__FresnelIntegral(): + pass + + +def test_sympy__functions__special__error_functions__fresnels(): + from sympy.functions.special.error_functions import fresnels + assert _test_args(fresnels(2)) + + +def test_sympy__functions__special__error_functions__fresnelc(): + from sympy.functions.special.error_functions import fresnelc + assert _test_args(fresnelc(2)) + + +def test_sympy__functions__special__error_functions__erfs(): + from sympy.functions.special.error_functions import _erfs + assert _test_args(_erfs(2)) + + +def test_sympy__functions__special__error_functions__Ei(): + from sympy.functions.special.error_functions import Ei + assert _test_args(Ei(2)) + + +def test_sympy__functions__special__error_functions__li(): + from sympy.functions.special.error_functions import li + assert _test_args(li(2)) + + +def test_sympy__functions__special__error_functions__Li(): + from sympy.functions.special.error_functions import Li + assert _test_args(Li(5)) + + +@SKIP("abstract class") +def test_sympy__functions__special__error_functions__TrigonometricIntegral(): + pass + + +def test_sympy__functions__special__error_functions__Si(): + from sympy.functions.special.error_functions import Si + assert _test_args(Si(2)) + + +def test_sympy__functions__special__error_functions__Ci(): + from sympy.functions.special.error_functions import Ci + assert _test_args(Ci(2)) + + +def test_sympy__functions__special__error_functions__Shi(): + from sympy.functions.special.error_functions import Shi + assert _test_args(Shi(2)) + + +def test_sympy__functions__special__error_functions__Chi(): + from sympy.functions.special.error_functions import Chi + assert _test_args(Chi(2)) + + +def test_sympy__functions__special__error_functions__expint(): + from sympy.functions.special.error_functions import expint + assert _test_args(expint(y, x)) + + +def test_sympy__functions__special__gamma_functions__gamma(): + from sympy.functions.special.gamma_functions import gamma + assert _test_args(gamma(x)) + + +def test_sympy__functions__special__gamma_functions__loggamma(): + from sympy.functions.special.gamma_functions import loggamma + assert _test_args(loggamma(x)) + + +def test_sympy__functions__special__gamma_functions__lowergamma(): + from sympy.functions.special.gamma_functions import lowergamma + assert _test_args(lowergamma(x, 2)) + + +def test_sympy__functions__special__gamma_functions__polygamma(): + from sympy.functions.special.gamma_functions import polygamma + assert _test_args(polygamma(x, 2)) + +def test_sympy__functions__special__gamma_functions__digamma(): + from sympy.functions.special.gamma_functions import digamma + assert _test_args(digamma(x)) + +def test_sympy__functions__special__gamma_functions__trigamma(): + from sympy.functions.special.gamma_functions import trigamma + assert _test_args(trigamma(x)) + +def test_sympy__functions__special__gamma_functions__uppergamma(): + from sympy.functions.special.gamma_functions import uppergamma + assert _test_args(uppergamma(x, 2)) + +def test_sympy__functions__special__gamma_functions__multigamma(): + from sympy.functions.special.gamma_functions import multigamma + assert _test_args(multigamma(x, 1)) + + +def test_sympy__functions__special__beta_functions__beta(): + from sympy.functions.special.beta_functions import beta + assert _test_args(beta(x)) + assert _test_args(beta(x, x)) + +def test_sympy__functions__special__beta_functions__betainc(): + from sympy.functions.special.beta_functions import betainc + assert _test_args(betainc(a, b, x, y)) + +def test_sympy__functions__special__beta_functions__betainc_regularized(): + from sympy.functions.special.beta_functions import betainc_regularized + assert _test_args(betainc_regularized(a, b, x, y)) + + +def test_sympy__functions__special__mathieu_functions__MathieuBase(): + pass + + +def test_sympy__functions__special__mathieu_functions__mathieus(): + from sympy.functions.special.mathieu_functions import mathieus + assert _test_args(mathieus(1, 1, 1)) + + +def test_sympy__functions__special__mathieu_functions__mathieuc(): + from sympy.functions.special.mathieu_functions import mathieuc + assert _test_args(mathieuc(1, 1, 1)) + + +def test_sympy__functions__special__mathieu_functions__mathieusprime(): + from sympy.functions.special.mathieu_functions import mathieusprime + assert _test_args(mathieusprime(1, 1, 1)) + + +def test_sympy__functions__special__mathieu_functions__mathieucprime(): + from sympy.functions.special.mathieu_functions import mathieucprime + assert _test_args(mathieucprime(1, 1, 1)) + + +@SKIP("abstract class") +def test_sympy__functions__special__hyper__TupleParametersBase(): + pass + + +@SKIP("abstract class") +def test_sympy__functions__special__hyper__TupleArg(): + pass + + +def test_sympy__functions__special__hyper__hyper(): + from sympy.functions.special.hyper import hyper + assert _test_args(hyper([1, 2, 3], [4, 5], x)) + + +def test_sympy__functions__special__hyper__meijerg(): + from sympy.functions.special.hyper import meijerg + assert _test_args(meijerg([1, 2, 3], [4, 5], [6], [], x)) + + +@SKIP("abstract class") +def test_sympy__functions__special__hyper__HyperRep(): + pass + + +def test_sympy__functions__special__hyper__HyperRep_power1(): + from sympy.functions.special.hyper import HyperRep_power1 + assert _test_args(HyperRep_power1(x, y)) + + +def test_sympy__functions__special__hyper__HyperRep_power2(): + from sympy.functions.special.hyper import HyperRep_power2 + assert _test_args(HyperRep_power2(x, y)) + + +def test_sympy__functions__special__hyper__HyperRep_log1(): + from sympy.functions.special.hyper import HyperRep_log1 + assert _test_args(HyperRep_log1(x)) + + +def test_sympy__functions__special__hyper__HyperRep_atanh(): + from sympy.functions.special.hyper import HyperRep_atanh + assert _test_args(HyperRep_atanh(x)) + + +def test_sympy__functions__special__hyper__HyperRep_asin1(): + from sympy.functions.special.hyper import HyperRep_asin1 + assert _test_args(HyperRep_asin1(x)) + + +def test_sympy__functions__special__hyper__HyperRep_asin2(): + from sympy.functions.special.hyper import HyperRep_asin2 + assert _test_args(HyperRep_asin2(x)) + + +def test_sympy__functions__special__hyper__HyperRep_sqrts1(): + from sympy.functions.special.hyper import HyperRep_sqrts1 + assert _test_args(HyperRep_sqrts1(x, y)) + + +def test_sympy__functions__special__hyper__HyperRep_sqrts2(): + from sympy.functions.special.hyper import HyperRep_sqrts2 + assert _test_args(HyperRep_sqrts2(x, y)) + + +def test_sympy__functions__special__hyper__HyperRep_log2(): + from sympy.functions.special.hyper import HyperRep_log2 + assert _test_args(HyperRep_log2(x)) + + +def test_sympy__functions__special__hyper__HyperRep_cosasin(): + from sympy.functions.special.hyper import HyperRep_cosasin + assert _test_args(HyperRep_cosasin(x, y)) + + +def test_sympy__functions__special__hyper__HyperRep_sinasin(): + from sympy.functions.special.hyper import HyperRep_sinasin + assert _test_args(HyperRep_sinasin(x, y)) + +def test_sympy__functions__special__hyper__appellf1(): + from sympy.functions.special.hyper import appellf1 + a, b1, b2, c, x, y = symbols('a b1 b2 c x y') + assert _test_args(appellf1(a, b1, b2, c, x, y)) + +@SKIP("abstract class") +def test_sympy__functions__special__polynomials__OrthogonalPolynomial(): + pass + + +def test_sympy__functions__special__polynomials__jacobi(): + from sympy.functions.special.polynomials import jacobi + assert _test_args(jacobi(x, y, 2, 2)) + + +def test_sympy__functions__special__polynomials__gegenbauer(): + from sympy.functions.special.polynomials import gegenbauer + assert _test_args(gegenbauer(x, 2, 2)) + + +def test_sympy__functions__special__polynomials__chebyshevt(): + from sympy.functions.special.polynomials import chebyshevt + assert _test_args(chebyshevt(x, 2)) + + +def test_sympy__functions__special__polynomials__chebyshevt_root(): + from sympy.functions.special.polynomials import chebyshevt_root + assert _test_args(chebyshevt_root(3, 2)) + + +def test_sympy__functions__special__polynomials__chebyshevu(): + from sympy.functions.special.polynomials import chebyshevu + assert _test_args(chebyshevu(x, 2)) + + +def test_sympy__functions__special__polynomials__chebyshevu_root(): + from sympy.functions.special.polynomials import chebyshevu_root + assert _test_args(chebyshevu_root(3, 2)) + + +def test_sympy__functions__special__polynomials__hermite(): + from sympy.functions.special.polynomials import hermite + assert _test_args(hermite(x, 2)) + + +def test_sympy__functions__special__polynomials__hermite_prob(): + from sympy.functions.special.polynomials import hermite_prob + assert _test_args(hermite_prob(x, 2)) + + +def test_sympy__functions__special__polynomials__legendre(): + from sympy.functions.special.polynomials import legendre + assert _test_args(legendre(x, 2)) + + +def test_sympy__functions__special__polynomials__assoc_legendre(): + from sympy.functions.special.polynomials import assoc_legendre + assert _test_args(assoc_legendre(x, 0, y)) + + +def test_sympy__functions__special__polynomials__laguerre(): + from sympy.functions.special.polynomials import laguerre + assert _test_args(laguerre(x, 2)) + + +def test_sympy__functions__special__polynomials__assoc_laguerre(): + from sympy.functions.special.polynomials import assoc_laguerre + assert _test_args(assoc_laguerre(x, 0, y)) + + +def test_sympy__functions__special__spherical_harmonics__Ynm(): + from sympy.functions.special.spherical_harmonics import Ynm + assert _test_args(Ynm(1, 1, x, y)) + + +def test_sympy__functions__special__spherical_harmonics__Znm(): + from sympy.functions.special.spherical_harmonics import Znm + assert _test_args(Znm(x, y, 1, 1)) + + +def test_sympy__functions__special__tensor_functions__LeviCivita(): + from sympy.functions.special.tensor_functions import LeviCivita + assert _test_args(LeviCivita(x, y, 2)) + + +def test_sympy__functions__special__tensor_functions__KroneckerDelta(): + from sympy.functions.special.tensor_functions import KroneckerDelta + assert _test_args(KroneckerDelta(x, y)) + + +def test_sympy__functions__special__zeta_functions__dirichlet_eta(): + from sympy.functions.special.zeta_functions import dirichlet_eta + assert _test_args(dirichlet_eta(x)) + + +def test_sympy__functions__special__zeta_functions__riemann_xi(): + from sympy.functions.special.zeta_functions import riemann_xi + assert _test_args(riemann_xi(x)) + + +def test_sympy__functions__special__zeta_functions__zeta(): + from sympy.functions.special.zeta_functions import zeta + assert _test_args(zeta(101)) + + +def test_sympy__functions__special__zeta_functions__lerchphi(): + from sympy.functions.special.zeta_functions import lerchphi + assert _test_args(lerchphi(x, y, z)) + + +def test_sympy__functions__special__zeta_functions__polylog(): + from sympy.functions.special.zeta_functions import polylog + assert _test_args(polylog(x, y)) + + +def test_sympy__functions__special__zeta_functions__stieltjes(): + from sympy.functions.special.zeta_functions import stieltjes + assert _test_args(stieltjes(x, y)) + + +def test_sympy__integrals__integrals__Integral(): + from sympy.integrals.integrals import Integral + assert _test_args(Integral(2, (x, 0, 1))) + + +def test_sympy__integrals__risch__NonElementaryIntegral(): + from sympy.integrals.risch import NonElementaryIntegral + assert _test_args(NonElementaryIntegral(exp(-x**2), x)) + + +@SKIP("abstract class") +def test_sympy__integrals__transforms__IntegralTransform(): + pass + + +def test_sympy__integrals__transforms__MellinTransform(): + from sympy.integrals.transforms import MellinTransform + assert _test_args(MellinTransform(2, x, y)) + + +def test_sympy__integrals__transforms__InverseMellinTransform(): + from sympy.integrals.transforms import InverseMellinTransform + assert _test_args(InverseMellinTransform(2, x, y, 0, 1)) + + +def test_sympy__integrals__laplace__LaplaceTransform(): + from sympy.integrals.laplace import LaplaceTransform + assert _test_args(LaplaceTransform(2, x, y)) + + +def test_sympy__integrals__laplace__InverseLaplaceTransform(): + from sympy.integrals.laplace import InverseLaplaceTransform + assert _test_args(InverseLaplaceTransform(2, x, y, 0)) + + +@SKIP("abstract class") +def test_sympy__integrals__transforms__FourierTypeTransform(): + pass + + +def test_sympy__integrals__transforms__InverseFourierTransform(): + from sympy.integrals.transforms import InverseFourierTransform + assert _test_args(InverseFourierTransform(2, x, y)) + + +def test_sympy__integrals__transforms__FourierTransform(): + from sympy.integrals.transforms import FourierTransform + assert _test_args(FourierTransform(2, x, y)) + + +@SKIP("abstract class") +def test_sympy__integrals__transforms__SineCosineTypeTransform(): + pass + + +def test_sympy__integrals__transforms__InverseSineTransform(): + from sympy.integrals.transforms import InverseSineTransform + assert _test_args(InverseSineTransform(2, x, y)) + + +def test_sympy__integrals__transforms__SineTransform(): + from sympy.integrals.transforms import SineTransform + assert _test_args(SineTransform(2, x, y)) + + +def test_sympy__integrals__transforms__InverseCosineTransform(): + from sympy.integrals.transforms import InverseCosineTransform + assert _test_args(InverseCosineTransform(2, x, y)) + + +def test_sympy__integrals__transforms__CosineTransform(): + from sympy.integrals.transforms import CosineTransform + assert _test_args(CosineTransform(2, x, y)) + + +@SKIP("abstract class") +def test_sympy__integrals__transforms__HankelTypeTransform(): + pass + + +def test_sympy__integrals__transforms__InverseHankelTransform(): + from sympy.integrals.transforms import InverseHankelTransform + assert _test_args(InverseHankelTransform(2, x, y, 0)) + + +def test_sympy__integrals__transforms__HankelTransform(): + from sympy.integrals.transforms import HankelTransform + assert _test_args(HankelTransform(2, x, y, 0)) + + +def test_sympy__liealgebras__cartan_type__Standard_Cartan(): + from sympy.liealgebras.cartan_type import Standard_Cartan + assert _test_args(Standard_Cartan("A", 2)) + +def test_sympy__liealgebras__weyl_group__WeylGroup(): + from sympy.liealgebras.weyl_group import WeylGroup + assert _test_args(WeylGroup("B4")) + +def test_sympy__liealgebras__root_system__RootSystem(): + from sympy.liealgebras.root_system import RootSystem + assert _test_args(RootSystem("A2")) + +def test_sympy__liealgebras__type_a__TypeA(): + from sympy.liealgebras.type_a import TypeA + assert _test_args(TypeA(2)) + +def test_sympy__liealgebras__type_b__TypeB(): + from sympy.liealgebras.type_b import TypeB + assert _test_args(TypeB(4)) + +def test_sympy__liealgebras__type_c__TypeC(): + from sympy.liealgebras.type_c import TypeC + assert _test_args(TypeC(4)) + +def test_sympy__liealgebras__type_d__TypeD(): + from sympy.liealgebras.type_d import TypeD + assert _test_args(TypeD(4)) + +def test_sympy__liealgebras__type_e__TypeE(): + from sympy.liealgebras.type_e import TypeE + assert _test_args(TypeE(6)) + +def test_sympy__liealgebras__type_f__TypeF(): + from sympy.liealgebras.type_f import TypeF + assert _test_args(TypeF(4)) + +def test_sympy__liealgebras__type_g__TypeG(): + from sympy.liealgebras.type_g import TypeG + assert _test_args(TypeG(2)) + + +def test_sympy__logic__boolalg__And(): + from sympy.logic.boolalg import And + assert _test_args(And(x, y, 1)) + + +@SKIP("abstract class") +def test_sympy__logic__boolalg__Boolean(): + pass + + +def test_sympy__logic__boolalg__BooleanFunction(): + from sympy.logic.boolalg import BooleanFunction + assert _test_args(BooleanFunction(1, 2, 3)) + +@SKIP("abstract class") +def test_sympy__logic__boolalg__BooleanAtom(): + pass + +def test_sympy__logic__boolalg__BooleanTrue(): + from sympy.logic.boolalg import true + assert _test_args(true) + +def test_sympy__logic__boolalg__BooleanFalse(): + from sympy.logic.boolalg import false + assert _test_args(false) + +def test_sympy__logic__boolalg__Equivalent(): + from sympy.logic.boolalg import Equivalent + assert _test_args(Equivalent(x, 2)) + + +def test_sympy__logic__boolalg__ITE(): + from sympy.logic.boolalg import ITE + assert _test_args(ITE(x, y, 1)) + + +def test_sympy__logic__boolalg__Implies(): + from sympy.logic.boolalg import Implies + assert _test_args(Implies(x, y)) + + +def test_sympy__logic__boolalg__Nand(): + from sympy.logic.boolalg import Nand + assert _test_args(Nand(x, y, 1)) + + +def test_sympy__logic__boolalg__Nor(): + from sympy.logic.boolalg import Nor + assert _test_args(Nor(x, y)) + + +def test_sympy__logic__boolalg__Not(): + from sympy.logic.boolalg import Not + assert _test_args(Not(x)) + + +def test_sympy__logic__boolalg__Or(): + from sympy.logic.boolalg import Or + assert _test_args(Or(x, y)) + + +def test_sympy__logic__boolalg__Xor(): + from sympy.logic.boolalg import Xor + assert _test_args(Xor(x, y, 2)) + +def test_sympy__logic__boolalg__Xnor(): + from sympy.logic.boolalg import Xnor + assert _test_args(Xnor(x, y, 2)) + +def test_sympy__logic__boolalg__Exclusive(): + from sympy.logic.boolalg import Exclusive + assert _test_args(Exclusive(x, y, z)) + + +def test_sympy__matrices__matrixbase__DeferredVector(): + from sympy.matrices.matrixbase import DeferredVector + assert _test_args(DeferredVector("X")) + + +@SKIP("abstract class") +def test_sympy__matrices__expressions__matexpr__MatrixBase(): + pass + + +@SKIP("abstract class") +def test_sympy__matrices__immutable__ImmutableRepMatrix(): + pass + + +def test_sympy__matrices__immutable__ImmutableDenseMatrix(): + from sympy.matrices.immutable import ImmutableDenseMatrix + m = ImmutableDenseMatrix([[1, 2], [3, 4]]) + assert _test_args(m) + assert _test_args(Basic(*list(m))) + m = ImmutableDenseMatrix(1, 1, [1]) + assert _test_args(m) + assert _test_args(Basic(*list(m))) + m = ImmutableDenseMatrix(2, 2, lambda i, j: 1) + assert m[0, 0] is S.One + m = ImmutableDenseMatrix(2, 2, lambda i, j: 1/(1 + i) + 1/(1 + j)) + assert m[1, 1] is S.One # true div. will give 1.0 if i,j not sympified + assert _test_args(m) + assert _test_args(Basic(*list(m))) + + +def test_sympy__matrices__immutable__ImmutableSparseMatrix(): + from sympy.matrices.immutable import ImmutableSparseMatrix + m = ImmutableSparseMatrix([[1, 2], [3, 4]]) + assert _test_args(m) + assert _test_args(Basic(*list(m))) + m = ImmutableSparseMatrix(1, 1, {(0, 0): 1}) + assert _test_args(m) + assert _test_args(Basic(*list(m))) + m = ImmutableSparseMatrix(1, 1, [1]) + assert _test_args(m) + assert _test_args(Basic(*list(m))) + m = ImmutableSparseMatrix(2, 2, lambda i, j: 1) + assert m[0, 0] is S.One + m = ImmutableSparseMatrix(2, 2, lambda i, j: 1/(1 + i) + 1/(1 + j)) + assert m[1, 1] is S.One # true div. will give 1.0 if i,j not sympified + assert _test_args(m) + assert _test_args(Basic(*list(m))) + + +def test_sympy__matrices__expressions__slice__MatrixSlice(): + from sympy.matrices.expressions.slice import MatrixSlice + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', 4, 4) + assert _test_args(MatrixSlice(X, (0, 2), (0, 2))) + + +def test_sympy__matrices__expressions__applyfunc__ElementwiseApplyFunction(): + from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol("X", x, x) + func = Lambda(x, x**2) + assert _test_args(ElementwiseApplyFunction(func, X)) + + +def test_sympy__matrices__expressions__blockmatrix__BlockDiagMatrix(): + from sympy.matrices.expressions.blockmatrix import BlockDiagMatrix + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, x) + Y = MatrixSymbol('Y', y, y) + assert _test_args(BlockDiagMatrix(X, Y)) + + +def test_sympy__matrices__expressions__blockmatrix__BlockMatrix(): + from sympy.matrices.expressions.blockmatrix import BlockMatrix + from sympy.matrices.expressions import MatrixSymbol, ZeroMatrix + X = MatrixSymbol('X', x, x) + Y = MatrixSymbol('Y', y, y) + Z = MatrixSymbol('Z', x, y) + O = ZeroMatrix(y, x) + assert _test_args(BlockMatrix([[X, Z], [O, Y]])) + + +def test_sympy__matrices__expressions__inverse__Inverse(): + from sympy.matrices.expressions.inverse import Inverse + from sympy.matrices.expressions import MatrixSymbol + assert _test_args(Inverse(MatrixSymbol('A', 3, 3))) + + +def test_sympy__matrices__expressions__matadd__MatAdd(): + from sympy.matrices.expressions.matadd import MatAdd + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, y) + Y = MatrixSymbol('Y', x, y) + assert _test_args(MatAdd(X, Y)) + + +@SKIP("abstract class") +def test_sympy__matrices__expressions__matexpr__MatrixExpr(): + pass + +def test_sympy__matrices__expressions__matexpr__MatrixElement(): + from sympy.matrices.expressions.matexpr import MatrixSymbol, MatrixElement + from sympy.core.singleton import S + assert _test_args(MatrixElement(MatrixSymbol('A', 3, 5), S(2), S(3))) + +def test_sympy__matrices__expressions__matexpr__MatrixSymbol(): + from sympy.matrices.expressions.matexpr import MatrixSymbol + assert _test_args(MatrixSymbol('A', 3, 5)) + + +def test_sympy__matrices__expressions__special__OneMatrix(): + from sympy.matrices.expressions.special import OneMatrix + assert _test_args(OneMatrix(3, 5)) + + +def test_sympy__matrices__expressions__special__ZeroMatrix(): + from sympy.matrices.expressions.special import ZeroMatrix + assert _test_args(ZeroMatrix(3, 5)) + + +def test_sympy__matrices__expressions__special__GenericZeroMatrix(): + from sympy.matrices.expressions.special import GenericZeroMatrix + assert _test_args(GenericZeroMatrix()) + + +def test_sympy__matrices__expressions__special__Identity(): + from sympy.matrices.expressions.special import Identity + assert _test_args(Identity(3)) + + +def test_sympy__matrices__expressions__special__GenericIdentity(): + from sympy.matrices.expressions.special import GenericIdentity + assert _test_args(GenericIdentity()) + + +def test_sympy__matrices__expressions__sets__MatrixSet(): + from sympy.matrices.expressions.sets import MatrixSet + from sympy.core.singleton import S + assert _test_args(MatrixSet(2, 2, S.Reals)) + +def test_sympy__matrices__expressions__matmul__MatMul(): + from sympy.matrices.expressions.matmul import MatMul + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, y) + Y = MatrixSymbol('Y', y, x) + assert _test_args(MatMul(X, Y)) + + +def test_sympy__matrices__expressions__dotproduct__DotProduct(): + from sympy.matrices.expressions.dotproduct import DotProduct + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, 1) + Y = MatrixSymbol('Y', x, 1) + assert _test_args(DotProduct(X, Y)) + +def test_sympy__matrices__expressions__diagonal__DiagonalMatrix(): + from sympy.matrices.expressions.diagonal import DiagonalMatrix + from sympy.matrices.expressions import MatrixSymbol + x = MatrixSymbol('x', 10, 1) + assert _test_args(DiagonalMatrix(x)) + +def test_sympy__matrices__expressions__diagonal__DiagonalOf(): + from sympy.matrices.expressions.diagonal import DiagonalOf + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('x', 10, 10) + assert _test_args(DiagonalOf(X)) + +def test_sympy__matrices__expressions__diagonal__DiagMatrix(): + from sympy.matrices.expressions.diagonal import DiagMatrix + from sympy.matrices.expressions import MatrixSymbol + x = MatrixSymbol('x', 10, 1) + assert _test_args(DiagMatrix(x)) + +def test_sympy__matrices__expressions__hadamard__HadamardProduct(): + from sympy.matrices.expressions.hadamard import HadamardProduct + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, y) + Y = MatrixSymbol('Y', x, y) + assert _test_args(HadamardProduct(X, Y)) + +def test_sympy__matrices__expressions__hadamard__HadamardPower(): + from sympy.matrices.expressions.hadamard import HadamardPower + from sympy.matrices.expressions import MatrixSymbol + from sympy.core.symbol import Symbol + X = MatrixSymbol('X', x, y) + n = Symbol("n") + assert _test_args(HadamardPower(X, n)) + +def test_sympy__matrices__expressions__kronecker__KroneckerProduct(): + from sympy.matrices.expressions.kronecker import KroneckerProduct + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, y) + Y = MatrixSymbol('Y', x, y) + assert _test_args(KroneckerProduct(X, Y)) + + +def test_sympy__matrices__expressions__matpow__MatPow(): + from sympy.matrices.expressions.matpow import MatPow + from sympy.matrices.expressions import MatrixSymbol + X = MatrixSymbol('X', x, x) + assert _test_args(MatPow(X, 2)) + + +def test_sympy__matrices__expressions__transpose__Transpose(): + from sympy.matrices.expressions.transpose import Transpose + from sympy.matrices.expressions import MatrixSymbol + assert _test_args(Transpose(MatrixSymbol('A', 3, 5))) + + +def test_sympy__matrices__expressions__adjoint__Adjoint(): + from sympy.matrices.expressions.adjoint import Adjoint + from sympy.matrices.expressions import MatrixSymbol + assert _test_args(Adjoint(MatrixSymbol('A', 3, 5))) + + +def test_sympy__matrices__expressions__trace__Trace(): + from sympy.matrices.expressions.trace import Trace + from sympy.matrices.expressions import MatrixSymbol + assert _test_args(Trace(MatrixSymbol('A', 3, 3))) + +def test_sympy__matrices__expressions__determinant__Determinant(): + from sympy.matrices.expressions.determinant import Determinant + from sympy.matrices.expressions import MatrixSymbol + assert _test_args(Determinant(MatrixSymbol('A', 3, 3))) + +def test_sympy__matrices__expressions__determinant__Permanent(): + from sympy.matrices.expressions.determinant import Permanent + from sympy.matrices.expressions import MatrixSymbol + assert _test_args(Permanent(MatrixSymbol('A', 3, 4))) + +def test_sympy__matrices__expressions__funcmatrix__FunctionMatrix(): + from sympy.matrices.expressions.funcmatrix import FunctionMatrix + from sympy.core.symbol import symbols + i, j = symbols('i,j') + assert _test_args(FunctionMatrix(3, 3, Lambda((i, j), i - j) )) + +def test_sympy__matrices__expressions__fourier__DFT(): + from sympy.matrices.expressions.fourier import DFT + from sympy.core.singleton import S + assert _test_args(DFT(S(2))) + +def test_sympy__matrices__expressions__fourier__IDFT(): + from sympy.matrices.expressions.fourier import IDFT + from sympy.core.singleton import S + assert _test_args(IDFT(S(2))) + +from sympy.matrices.expressions import MatrixSymbol +X = MatrixSymbol('X', 10, 10) + +def test_sympy__matrices__expressions__factorizations__LofLU(): + from sympy.matrices.expressions.factorizations import LofLU + assert _test_args(LofLU(X)) + +def test_sympy__matrices__expressions__factorizations__UofLU(): + from sympy.matrices.expressions.factorizations import UofLU + assert _test_args(UofLU(X)) + +def test_sympy__matrices__expressions__factorizations__QofQR(): + from sympy.matrices.expressions.factorizations import QofQR + assert _test_args(QofQR(X)) + +def test_sympy__matrices__expressions__factorizations__RofQR(): + from sympy.matrices.expressions.factorizations import RofQR + assert _test_args(RofQR(X)) + +def test_sympy__matrices__expressions__factorizations__LofCholesky(): + from sympy.matrices.expressions.factorizations import LofCholesky + assert _test_args(LofCholesky(X)) + +def test_sympy__matrices__expressions__factorizations__UofCholesky(): + from sympy.matrices.expressions.factorizations import UofCholesky + assert _test_args(UofCholesky(X)) + +def test_sympy__matrices__expressions__factorizations__EigenVectors(): + from sympy.matrices.expressions.factorizations import EigenVectors + assert _test_args(EigenVectors(X)) + +def test_sympy__matrices__expressions__factorizations__EigenValues(): + from sympy.matrices.expressions.factorizations import EigenValues + assert _test_args(EigenValues(X)) + +def test_sympy__matrices__expressions__factorizations__UofSVD(): + from sympy.matrices.expressions.factorizations import UofSVD + assert _test_args(UofSVD(X)) + +def test_sympy__matrices__expressions__factorizations__VofSVD(): + from sympy.matrices.expressions.factorizations import VofSVD + assert _test_args(VofSVD(X)) + +def test_sympy__matrices__expressions__factorizations__SofSVD(): + from sympy.matrices.expressions.factorizations import SofSVD + assert _test_args(SofSVD(X)) + +@SKIP("abstract class") +def test_sympy__matrices__expressions__factorizations__Factorization(): + pass + +def test_sympy__matrices__expressions__permutation__PermutationMatrix(): + from sympy.combinatorics import Permutation + from sympy.matrices.expressions.permutation import PermutationMatrix + assert _test_args(PermutationMatrix(Permutation([2, 0, 1]))) + +def test_sympy__matrices__expressions__permutation__MatrixPermute(): + from sympy.combinatorics import Permutation + from sympy.matrices.expressions.matexpr import MatrixSymbol + from sympy.matrices.expressions.permutation import MatrixPermute + A = MatrixSymbol('A', 3, 3) + assert _test_args(MatrixPermute(A, Permutation([2, 0, 1]))) + +def test_sympy__matrices__expressions__companion__CompanionMatrix(): + from sympy.core.symbol import Symbol + from sympy.matrices.expressions.companion import CompanionMatrix + from sympy.polys.polytools import Poly + + x = Symbol('x') + p = Poly([1, 2, 3], x) + assert _test_args(CompanionMatrix(p)) + +def test_sympy__physics__vector__frame__CoordinateSym(): + from sympy.physics.vector import CoordinateSym + from sympy.physics.vector import ReferenceFrame + assert _test_args(CoordinateSym('R_x', ReferenceFrame('R'), 0)) + + +@SKIP("abstract class") +def test_sympy__physics__biomechanics__curve__CharacteristicCurveFunction(): + pass + + +def test_sympy__physics__biomechanics__curve__TendonForceLengthDeGroote2016(): + from sympy.physics.biomechanics import TendonForceLengthDeGroote2016 + l_T_tilde, c0, c1, c2, c3 = symbols('l_T_tilde, c0, c1, c2, c3') + assert _test_args(TendonForceLengthDeGroote2016(l_T_tilde, c0, c1, c2, c3)) + + +def test_sympy__physics__biomechanics__curve__TendonForceLengthInverseDeGroote2016(): + from sympy.physics.biomechanics import TendonForceLengthInverseDeGroote2016 + fl_T, c0, c1, c2, c3 = symbols('fl_T, c0, c1, c2, c3') + assert _test_args(TendonForceLengthInverseDeGroote2016(fl_T, c0, c1, c2, c3)) + + +def test_sympy__physics__biomechanics__curve__FiberForceLengthPassiveDeGroote2016(): + from sympy.physics.biomechanics import FiberForceLengthPassiveDeGroote2016 + l_M_tilde, c0, c1 = symbols('l_M_tilde, c0, c1') + assert _test_args(FiberForceLengthPassiveDeGroote2016(l_M_tilde, c0, c1)) + + +def test_sympy__physics__biomechanics__curve__FiberForceLengthPassiveInverseDeGroote2016(): + from sympy.physics.biomechanics import FiberForceLengthPassiveInverseDeGroote2016 + fl_M_pas, c0, c1 = symbols('fl_M_pas, c0, c1') + assert _test_args(FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, c0, c1)) + + +def test_sympy__physics__biomechanics__curve__FiberForceLengthActiveDeGroote2016(): + from sympy.physics.biomechanics import FiberForceLengthActiveDeGroote2016 + l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = symbols('l_M_tilde, c0:12') + assert _test_args(FiberForceLengthActiveDeGroote2016(l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)) + + +def test_sympy__physics__biomechanics__curve__FiberForceVelocityDeGroote2016(): + from sympy.physics.biomechanics import FiberForceVelocityDeGroote2016 + v_M_tilde, c0, c1, c2, c3 = symbols('v_M_tilde, c0, c1, c2, c3') + assert _test_args(FiberForceVelocityDeGroote2016(v_M_tilde, c0, c1, c2, c3)) + + +def test_sympy__physics__biomechanics__curve__FiberForceVelocityInverseDeGroote2016(): + from sympy.physics.biomechanics import FiberForceVelocityInverseDeGroote2016 + fv_M, c0, c1, c2, c3 = symbols('fv_M, c0, c1, c2, c3') + assert _test_args(FiberForceVelocityInverseDeGroote2016(fv_M, c0, c1, c2, c3)) + + +def test_sympy__physics__paulialgebra__Pauli(): + from sympy.physics.paulialgebra import Pauli + assert _test_args(Pauli(1)) + + +def test_sympy__physics__quantum__anticommutator__AntiCommutator(): + from sympy.physics.quantum.anticommutator import AntiCommutator + assert _test_args(AntiCommutator(x, y)) + + +def test_sympy__physics__quantum__cartesian__PositionBra3D(): + from sympy.physics.quantum.cartesian import PositionBra3D + assert _test_args(PositionBra3D(x, y, z)) + + +def test_sympy__physics__quantum__cartesian__PositionKet3D(): + from sympy.physics.quantum.cartesian import PositionKet3D + assert _test_args(PositionKet3D(x, y, z)) + + +def test_sympy__physics__quantum__cartesian__PositionState3D(): + from sympy.physics.quantum.cartesian import PositionState3D + assert _test_args(PositionState3D(x, y, z)) + + +def test_sympy__physics__quantum__cartesian__PxBra(): + from sympy.physics.quantum.cartesian import PxBra + assert _test_args(PxBra(x, y, z)) + + +def test_sympy__physics__quantum__cartesian__PxKet(): + from sympy.physics.quantum.cartesian import PxKet + assert _test_args(PxKet(x, y, z)) + + +def test_sympy__physics__quantum__cartesian__PxOp(): + from sympy.physics.quantum.cartesian import PxOp + assert _test_args(PxOp(x, y, z)) + + +def test_sympy__physics__quantum__cartesian__XBra(): + from sympy.physics.quantum.cartesian import XBra + assert _test_args(XBra(x)) + + +def test_sympy__physics__quantum__cartesian__XKet(): + from sympy.physics.quantum.cartesian import XKet + assert _test_args(XKet(x)) + + +def test_sympy__physics__quantum__cartesian__XOp(): + from sympy.physics.quantum.cartesian import XOp + assert _test_args(XOp(x)) + + +def test_sympy__physics__quantum__cartesian__YOp(): + from sympy.physics.quantum.cartesian import YOp + assert _test_args(YOp(x)) + + +def test_sympy__physics__quantum__cartesian__ZOp(): + from sympy.physics.quantum.cartesian import ZOp + assert _test_args(ZOp(x)) + + +def test_sympy__physics__quantum__cg__CG(): + from sympy.physics.quantum.cg import CG + from sympy.core.singleton import S + assert _test_args(CG(Rational(3, 2), Rational(3, 2), S.Half, Rational(-1, 2), 1, 1)) + + +def test_sympy__physics__quantum__cg__Wigner3j(): + from sympy.physics.quantum.cg import Wigner3j + assert _test_args(Wigner3j(6, 0, 4, 0, 2, 0)) + + +def test_sympy__physics__quantum__cg__Wigner6j(): + from sympy.physics.quantum.cg import Wigner6j + assert _test_args(Wigner6j(1, 2, 3, 2, 1, 2)) + + +def test_sympy__physics__quantum__cg__Wigner9j(): + from sympy.physics.quantum.cg import Wigner9j + assert _test_args(Wigner9j(2, 1, 1, Rational(3, 2), S.Half, 1, S.Half, S.Half, 0)) + +def test_sympy__physics__quantum__circuitplot__Mz(): + from sympy.physics.quantum.circuitplot import Mz + assert _test_args(Mz(0)) + +def test_sympy__physics__quantum__circuitplot__Mx(): + from sympy.physics.quantum.circuitplot import Mx + assert _test_args(Mx(0)) + +def test_sympy__physics__quantum__commutator__Commutator(): + from sympy.physics.quantum.commutator import Commutator + A, B = symbols('A,B', commutative=False) + assert _test_args(Commutator(A, B)) + + +def test_sympy__physics__quantum__constants__HBar(): + from sympy.physics.quantum.constants import HBar + assert _test_args(HBar()) + + +def test_sympy__physics__quantum__dagger__Dagger(): + from sympy.physics.quantum.dagger import Dagger + from sympy.physics.quantum.state import Ket + assert _test_args(Dagger(Dagger(Ket('psi')))) + + +def test_sympy__physics__quantum__gate__CGate(): + from sympy.physics.quantum.gate import CGate, Gate + assert _test_args(CGate((0, 1), Gate(2))) + + +def test_sympy__physics__quantum__gate__CGateS(): + from sympy.physics.quantum.gate import CGateS, Gate + assert _test_args(CGateS((0, 1), Gate(2))) + + +def test_sympy__physics__quantum__gate__CNotGate(): + from sympy.physics.quantum.gate import CNotGate + assert _test_args(CNotGate(0, 1)) + + +def test_sympy__physics__quantum__gate__Gate(): + from sympy.physics.quantum.gate import Gate + assert _test_args(Gate(0)) + + +def test_sympy__physics__quantum__gate__HadamardGate(): + from sympy.physics.quantum.gate import HadamardGate + assert _test_args(HadamardGate(0)) + + +def test_sympy__physics__quantum__gate__IdentityGate(): + from sympy.physics.quantum.gate import IdentityGate + assert _test_args(IdentityGate(0)) + + +def test_sympy__physics__quantum__gate__OneQubitGate(): + from sympy.physics.quantum.gate import OneQubitGate + assert _test_args(OneQubitGate(0)) + + +def test_sympy__physics__quantum__gate__PhaseGate(): + from sympy.physics.quantum.gate import PhaseGate + assert _test_args(PhaseGate(0)) + + +def test_sympy__physics__quantum__gate__SwapGate(): + from sympy.physics.quantum.gate import SwapGate + assert _test_args(SwapGate(0, 1)) + + +def test_sympy__physics__quantum__gate__TGate(): + from sympy.physics.quantum.gate import TGate + assert _test_args(TGate(0)) + + +def test_sympy__physics__quantum__gate__TwoQubitGate(): + from sympy.physics.quantum.gate import TwoQubitGate + assert _test_args(TwoQubitGate(0)) + + +def test_sympy__physics__quantum__gate__UGate(): + from sympy.physics.quantum.gate import UGate + from sympy.matrices.immutable import ImmutableDenseMatrix + from sympy.core.containers import Tuple + from sympy.core.numbers import Integer + assert _test_args( + UGate(Tuple(Integer(1)), ImmutableDenseMatrix([[1, 0], [0, 2]]))) + + +def test_sympy__physics__quantum__gate__XGate(): + from sympy.physics.quantum.gate import XGate + assert _test_args(XGate(0)) + + +def test_sympy__physics__quantum__gate__YGate(): + from sympy.physics.quantum.gate import YGate + assert _test_args(YGate(0)) + + +def test_sympy__physics__quantum__gate__ZGate(): + from sympy.physics.quantum.gate import ZGate + assert _test_args(ZGate(0)) + + +def test_sympy__physics__quantum__grover__OracleGateFunction(): + from sympy.physics.quantum.grover import OracleGateFunction + @OracleGateFunction + def f(qubit): + return + assert _test_args(f) + +def test_sympy__physics__quantum__grover__OracleGate(): + from sympy.physics.quantum.grover import OracleGate + def f(qubit): + return + assert _test_args(OracleGate(1,f)) + + +def test_sympy__physics__quantum__grover__WGate(): + from sympy.physics.quantum.grover import WGate + assert _test_args(WGate(1)) + + +def test_sympy__physics__quantum__hilbert__ComplexSpace(): + from sympy.physics.quantum.hilbert import ComplexSpace + assert _test_args(ComplexSpace(x)) + + +def test_sympy__physics__quantum__hilbert__DirectSumHilbertSpace(): + from sympy.physics.quantum.hilbert import DirectSumHilbertSpace, ComplexSpace, FockSpace + c = ComplexSpace(2) + f = FockSpace() + assert _test_args(DirectSumHilbertSpace(c, f)) + + +def test_sympy__physics__quantum__hilbert__FockSpace(): + from sympy.physics.quantum.hilbert import FockSpace + assert _test_args(FockSpace()) + + +def test_sympy__physics__quantum__hilbert__HilbertSpace(): + from sympy.physics.quantum.hilbert import HilbertSpace + assert _test_args(HilbertSpace()) + + +def test_sympy__physics__quantum__hilbert__L2(): + from sympy.physics.quantum.hilbert import L2 + from sympy.core.numbers import oo + from sympy.sets.sets import Interval + assert _test_args(L2(Interval(0, oo))) + + +def test_sympy__physics__quantum__hilbert__TensorPowerHilbertSpace(): + from sympy.physics.quantum.hilbert import TensorPowerHilbertSpace, FockSpace + f = FockSpace() + assert _test_args(TensorPowerHilbertSpace(f, 2)) + + +def test_sympy__physics__quantum__hilbert__TensorProductHilbertSpace(): + from sympy.physics.quantum.hilbert import TensorProductHilbertSpace, FockSpace, ComplexSpace + c = ComplexSpace(2) + f = FockSpace() + assert _test_args(TensorProductHilbertSpace(f, c)) + + +def test_sympy__physics__quantum__innerproduct__InnerProduct(): + from sympy.physics.quantum import Bra, Ket, InnerProduct + b = Bra('b') + k = Ket('k') + assert _test_args(InnerProduct(b, k)) + + +def test_sympy__physics__quantum__operator__DifferentialOperator(): + from sympy.physics.quantum.operator import DifferentialOperator + from sympy.core.function import (Derivative, Function) + f = Function('f') + assert _test_args(DifferentialOperator(1/x*Derivative(f(x), x), f(x))) + + +def test_sympy__physics__quantum__operator__HermitianOperator(): + from sympy.physics.quantum.operator import HermitianOperator + assert _test_args(HermitianOperator('H')) + + +def test_sympy__physics__quantum__operator__IdentityOperator(): + with warns_deprecated_sympy(): + from sympy.physics.quantum.operator import IdentityOperator + assert _test_args(IdentityOperator(5)) + + +def test_sympy__physics__quantum__operator__Operator(): + from sympy.physics.quantum.operator import Operator + assert _test_args(Operator('A')) + + +def test_sympy__physics__quantum__operator__OuterProduct(): + from sympy.physics.quantum.operator import OuterProduct + from sympy.physics.quantum import Ket, Bra + b = Bra('b') + k = Ket('k') + assert _test_args(OuterProduct(k, b)) + + +def test_sympy__physics__quantum__operator__UnitaryOperator(): + from sympy.physics.quantum.operator import UnitaryOperator + assert _test_args(UnitaryOperator('U')) + + +def test_sympy__physics__quantum__piab__PIABBra(): + from sympy.physics.quantum.piab import PIABBra + assert _test_args(PIABBra('B')) + + +def test_sympy__physics__quantum__boson__BosonOp(): + from sympy.physics.quantum.boson import BosonOp + assert _test_args(BosonOp('a')) + assert _test_args(BosonOp('a', False)) + + +def test_sympy__physics__quantum__boson__BosonFockKet(): + from sympy.physics.quantum.boson import BosonFockKet + assert _test_args(BosonFockKet(1)) + + +def test_sympy__physics__quantum__boson__BosonFockBra(): + from sympy.physics.quantum.boson import BosonFockBra + assert _test_args(BosonFockBra(1)) + + +def test_sympy__physics__quantum__boson__BosonCoherentKet(): + from sympy.physics.quantum.boson import BosonCoherentKet + assert _test_args(BosonCoherentKet(1)) + + +def test_sympy__physics__quantum__boson__BosonCoherentBra(): + from sympy.physics.quantum.boson import BosonCoherentBra + assert _test_args(BosonCoherentBra(1)) + + +def test_sympy__physics__quantum__fermion__FermionOp(): + from sympy.physics.quantum.fermion import FermionOp + assert _test_args(FermionOp('c')) + assert _test_args(FermionOp('c', False)) + + +def test_sympy__physics__quantum__fermion__FermionFockKet(): + from sympy.physics.quantum.fermion import FermionFockKet + assert _test_args(FermionFockKet(1)) + + +def test_sympy__physics__quantum__fermion__FermionFockBra(): + from sympy.physics.quantum.fermion import FermionFockBra + assert _test_args(FermionFockBra(1)) + + +def test_sympy__physics__quantum__pauli__SigmaOpBase(): + from sympy.physics.quantum.pauli import SigmaOpBase + assert _test_args(SigmaOpBase()) + + +def test_sympy__physics__quantum__pauli__SigmaX(): + from sympy.physics.quantum.pauli import SigmaX + assert _test_args(SigmaX()) + + +def test_sympy__physics__quantum__pauli__SigmaY(): + from sympy.physics.quantum.pauli import SigmaY + assert _test_args(SigmaY()) + + +def test_sympy__physics__quantum__pauli__SigmaZ(): + from sympy.physics.quantum.pauli import SigmaZ + assert _test_args(SigmaZ()) + + +def test_sympy__physics__quantum__pauli__SigmaMinus(): + from sympy.physics.quantum.pauli import SigmaMinus + assert _test_args(SigmaMinus()) + + +def test_sympy__physics__quantum__pauli__SigmaPlus(): + from sympy.physics.quantum.pauli import SigmaPlus + assert _test_args(SigmaPlus()) + + +def test_sympy__physics__quantum__pauli__SigmaZKet(): + from sympy.physics.quantum.pauli import SigmaZKet + assert _test_args(SigmaZKet(0)) + + +def test_sympy__physics__quantum__pauli__SigmaZBra(): + from sympy.physics.quantum.pauli import SigmaZBra + assert _test_args(SigmaZBra(0)) + + +def test_sympy__physics__quantum__piab__PIABHamiltonian(): + from sympy.physics.quantum.piab import PIABHamiltonian + assert _test_args(PIABHamiltonian('P')) + + +def test_sympy__physics__quantum__piab__PIABKet(): + from sympy.physics.quantum.piab import PIABKet + assert _test_args(PIABKet('K')) + + +def test_sympy__physics__quantum__qexpr__QExpr(): + from sympy.physics.quantum.qexpr import QExpr + assert _test_args(QExpr(0)) + + +def test_sympy__physics__quantum__qft__Fourier(): + from sympy.physics.quantum.qft import Fourier + assert _test_args(Fourier(0, 1)) + + +def test_sympy__physics__quantum__qft__IQFT(): + from sympy.physics.quantum.qft import IQFT + assert _test_args(IQFT(0, 1)) + + +def test_sympy__physics__quantum__qft__QFT(): + from sympy.physics.quantum.qft import QFT + assert _test_args(QFT(0, 1)) + + +def test_sympy__physics__quantum__qft__RkGate(): + from sympy.physics.quantum.qft import RkGate + assert _test_args(RkGate(0, 1)) + + +def test_sympy__physics__quantum__qubit__IntQubit(): + from sympy.physics.quantum.qubit import IntQubit + assert _test_args(IntQubit(0)) + + +def test_sympy__physics__quantum__qubit__IntQubitBra(): + from sympy.physics.quantum.qubit import IntQubitBra + assert _test_args(IntQubitBra(0)) + + +def test_sympy__physics__quantum__qubit__IntQubitState(): + from sympy.physics.quantum.qubit import IntQubitState, QubitState + assert _test_args(IntQubitState(QubitState(0, 1))) + + +def test_sympy__physics__quantum__qubit__Qubit(): + from sympy.physics.quantum.qubit import Qubit + assert _test_args(Qubit(0, 0, 0)) + + +def test_sympy__physics__quantum__qubit__QubitBra(): + from sympy.physics.quantum.qubit import QubitBra + assert _test_args(QubitBra('1', 0)) + + +def test_sympy__physics__quantum__qubit__QubitState(): + from sympy.physics.quantum.qubit import QubitState + assert _test_args(QubitState(0, 1)) + + +def test_sympy__physics__quantum__density__Density(): + from sympy.physics.quantum.density import Density + from sympy.physics.quantum.state import Ket + assert _test_args(Density([Ket(0), 0.5], [Ket(1), 0.5])) + + +@SKIP("TODO: sympy.physics.quantum.shor: Cmod Not Implemented") +def test_sympy__physics__quantum__shor__CMod(): + from sympy.physics.quantum.shor import CMod + assert _test_args(CMod()) + + +def test_sympy__physics__quantum__spin__CoupledSpinState(): + from sympy.physics.quantum.spin import CoupledSpinState + assert _test_args(CoupledSpinState(1, 0, (1, 1))) + assert _test_args(CoupledSpinState(1, 0, (1, S.Half, S.Half))) + assert _test_args(CoupledSpinState( + 1, 0, (1, S.Half, S.Half), ((2, 3, S.Half), (1, 2, 1)) )) + j, m, j1, j2, j3, j12, x = symbols('j m j1:4 j12 x') + assert CoupledSpinState( + j, m, (j1, j2, j3)).subs(j2, x) == CoupledSpinState(j, m, (j1, x, j3)) + assert CoupledSpinState(j, m, (j1, j2, j3), ((1, 3, j12), (1, 2, j)) ).subs(j12, x) == \ + CoupledSpinState(j, m, (j1, j2, j3), ((1, 3, x), (1, 2, j)) ) + + +def test_sympy__physics__quantum__spin__J2Op(): + from sympy.physics.quantum.spin import J2Op + assert _test_args(J2Op('J')) + + +def test_sympy__physics__quantum__spin__JminusOp(): + from sympy.physics.quantum.spin import JminusOp + assert _test_args(JminusOp('J')) + + +def test_sympy__physics__quantum__spin__JplusOp(): + from sympy.physics.quantum.spin import JplusOp + assert _test_args(JplusOp('J')) + + +def test_sympy__physics__quantum__spin__JxBra(): + from sympy.physics.quantum.spin import JxBra + assert _test_args(JxBra(1, 0)) + + +def test_sympy__physics__quantum__spin__JxBraCoupled(): + from sympy.physics.quantum.spin import JxBraCoupled + assert _test_args(JxBraCoupled(1, 0, (1, 1))) + + +def test_sympy__physics__quantum__spin__JxKet(): + from sympy.physics.quantum.spin import JxKet + assert _test_args(JxKet(1, 0)) + + +def test_sympy__physics__quantum__spin__JxKetCoupled(): + from sympy.physics.quantum.spin import JxKetCoupled + assert _test_args(JxKetCoupled(1, 0, (1, 1))) + + +def test_sympy__physics__quantum__spin__JxOp(): + from sympy.physics.quantum.spin import JxOp + assert _test_args(JxOp('J')) + + +def test_sympy__physics__quantum__spin__JyBra(): + from sympy.physics.quantum.spin import JyBra + assert _test_args(JyBra(1, 0)) + + +def test_sympy__physics__quantum__spin__JyBraCoupled(): + from sympy.physics.quantum.spin import JyBraCoupled + assert _test_args(JyBraCoupled(1, 0, (1, 1))) + + +def test_sympy__physics__quantum__spin__JyKet(): + from sympy.physics.quantum.spin import JyKet + assert _test_args(JyKet(1, 0)) + + +def test_sympy__physics__quantum__spin__JyKetCoupled(): + from sympy.physics.quantum.spin import JyKetCoupled + assert _test_args(JyKetCoupled(1, 0, (1, 1))) + + +def test_sympy__physics__quantum__spin__JyOp(): + from sympy.physics.quantum.spin import JyOp + assert _test_args(JyOp('J')) + + +def test_sympy__physics__quantum__spin__JzBra(): + from sympy.physics.quantum.spin import JzBra + assert _test_args(JzBra(1, 0)) + + +def test_sympy__physics__quantum__spin__JzBraCoupled(): + from sympy.physics.quantum.spin import JzBraCoupled + assert _test_args(JzBraCoupled(1, 0, (1, 1))) + + +def test_sympy__physics__quantum__spin__JzKet(): + from sympy.physics.quantum.spin import JzKet + assert _test_args(JzKet(1, 0)) + + +def test_sympy__physics__quantum__spin__JzKetCoupled(): + from sympy.physics.quantum.spin import JzKetCoupled + assert _test_args(JzKetCoupled(1, 0, (1, 1))) + + +def test_sympy__physics__quantum__spin__JzOp(): + from sympy.physics.quantum.spin import JzOp + assert _test_args(JzOp('J')) + + +def test_sympy__physics__quantum__spin__Rotation(): + from sympy.physics.quantum.spin import Rotation + assert _test_args(Rotation(pi, 0, pi/2)) + + +def test_sympy__physics__quantum__spin__SpinState(): + from sympy.physics.quantum.spin import SpinState + assert _test_args(SpinState(1, 0)) + + +def test_sympy__physics__quantum__spin__WignerD(): + from sympy.physics.quantum.spin import WignerD + assert _test_args(WignerD(0, 1, 2, 3, 4, 5)) + + +def test_sympy__physics__quantum__state__Bra(): + from sympy.physics.quantum.state import Bra + assert _test_args(Bra(0)) + + +def test_sympy__physics__quantum__state__BraBase(): + from sympy.physics.quantum.state import BraBase + assert _test_args(BraBase(0)) + + +def test_sympy__physics__quantum__state__Ket(): + from sympy.physics.quantum.state import Ket + assert _test_args(Ket(0)) + + +def test_sympy__physics__quantum__state__KetBase(): + from sympy.physics.quantum.state import KetBase + assert _test_args(KetBase(0)) + + +def test_sympy__physics__quantum__state__State(): + from sympy.physics.quantum.state import State + assert _test_args(State(0)) + + +def test_sympy__physics__quantum__state__StateBase(): + from sympy.physics.quantum.state import StateBase + assert _test_args(StateBase(0)) + + +def test_sympy__physics__quantum__state__OrthogonalBra(): + from sympy.physics.quantum.state import OrthogonalBra + assert _test_args(OrthogonalBra(0)) + + +def test_sympy__physics__quantum__state__OrthogonalKet(): + from sympy.physics.quantum.state import OrthogonalKet + assert _test_args(OrthogonalKet(0)) + + +def test_sympy__physics__quantum__state__OrthogonalState(): + from sympy.physics.quantum.state import OrthogonalState + assert _test_args(OrthogonalState(0)) + + +def test_sympy__physics__quantum__state__TimeDepBra(): + from sympy.physics.quantum.state import TimeDepBra + assert _test_args(TimeDepBra('psi', 't')) + + +def test_sympy__physics__quantum__state__TimeDepKet(): + from sympy.physics.quantum.state import TimeDepKet + assert _test_args(TimeDepKet('psi', 't')) + + +def test_sympy__physics__quantum__state__TimeDepState(): + from sympy.physics.quantum.state import TimeDepState + assert _test_args(TimeDepState('psi', 't')) + + +def test_sympy__physics__quantum__state__Wavefunction(): + from sympy.physics.quantum.state import Wavefunction + from sympy.functions import sin + from sympy.functions.elementary.piecewise import Piecewise + n = 1 + L = 1 + g = Piecewise((0, x < 0), (0, x > L), (sqrt(2//L)*sin(n*pi*x/L), True)) + assert _test_args(Wavefunction(g, x)) + + +def test_sympy__physics__quantum__tensorproduct__TensorProduct(): + from sympy.physics.quantum.tensorproduct import TensorProduct + x, y = symbols("x y", commutative=False) + assert _test_args(TensorProduct(x, y)) + + +def test_sympy__physics__quantum__identitysearch__GateIdentity(): + from sympy.physics.quantum.gate import X + from sympy.physics.quantum.identitysearch import GateIdentity + assert _test_args(GateIdentity(X(0), X(0))) + + +def test_sympy__physics__quantum__sho1d__SHOOp(): + from sympy.physics.quantum.sho1d import SHOOp + assert _test_args(SHOOp('a')) + + +def test_sympy__physics__quantum__sho1d__RaisingOp(): + from sympy.physics.quantum.sho1d import RaisingOp + assert _test_args(RaisingOp('a')) + + +def test_sympy__physics__quantum__sho1d__LoweringOp(): + from sympy.physics.quantum.sho1d import LoweringOp + assert _test_args(LoweringOp('a')) + + +def test_sympy__physics__quantum__sho1d__NumberOp(): + from sympy.physics.quantum.sho1d import NumberOp + assert _test_args(NumberOp('N')) + + +def test_sympy__physics__quantum__sho1d__Hamiltonian(): + from sympy.physics.quantum.sho1d import Hamiltonian + assert _test_args(Hamiltonian('H')) + + +def test_sympy__physics__quantum__sho1d__SHOState(): + from sympy.physics.quantum.sho1d import SHOState + assert _test_args(SHOState(0)) + + +def test_sympy__physics__quantum__sho1d__SHOKet(): + from sympy.physics.quantum.sho1d import SHOKet + assert _test_args(SHOKet(0)) + + +def test_sympy__physics__quantum__sho1d__SHOBra(): + from sympy.physics.quantum.sho1d import SHOBra + assert _test_args(SHOBra(0)) + + +def test_sympy__physics__secondquant__AnnihilateBoson(): + from sympy.physics.secondquant import AnnihilateBoson + assert _test_args(AnnihilateBoson(0)) + + +def test_sympy__physics__secondquant__AnnihilateFermion(): + from sympy.physics.secondquant import AnnihilateFermion + assert _test_args(AnnihilateFermion(0)) + + +@SKIP("abstract class") +def test_sympy__physics__secondquant__Annihilator(): + pass + + +def test_sympy__physics__secondquant__AntiSymmetricTensor(): + from sympy.physics.secondquant import AntiSymmetricTensor + i, j = symbols('i j', below_fermi=True) + a, b = symbols('a b', above_fermi=True) + assert _test_args(AntiSymmetricTensor('v', (a, i), (b, j))) + + +def test_sympy__physics__secondquant__BosonState(): + from sympy.physics.secondquant import BosonState + assert _test_args(BosonState((0, 1))) + + +@SKIP("abstract class") +def test_sympy__physics__secondquant__BosonicOperator(): + pass + + +def test_sympy__physics__secondquant__Commutator(): + from sympy.physics.secondquant import Commutator + x, y = symbols('x y', commutative=False) + assert _test_args(Commutator(x, y)) + + +def test_sympy__physics__secondquant__CreateBoson(): + from sympy.physics.secondquant import CreateBoson + assert _test_args(CreateBoson(0)) + + +def test_sympy__physics__secondquant__CreateFermion(): + from sympy.physics.secondquant import CreateFermion + assert _test_args(CreateFermion(0)) + + +@SKIP("abstract class") +def test_sympy__physics__secondquant__Creator(): + pass + + +def test_sympy__physics__secondquant__Dagger(): + from sympy.physics.secondquant import Dagger + assert _test_args(Dagger(x)) + + +def test_sympy__physics__secondquant__FermionState(): + from sympy.physics.secondquant import FermionState + assert _test_args(FermionState((0, 1))) + + +def test_sympy__physics__secondquant__FermionicOperator(): + from sympy.physics.secondquant import FermionicOperator + assert _test_args(FermionicOperator(0)) + + +def test_sympy__physics__secondquant__FockState(): + from sympy.physics.secondquant import FockState + assert _test_args(FockState((0, 1))) + + +def test_sympy__physics__secondquant__FockStateBosonBra(): + from sympy.physics.secondquant import FockStateBosonBra + assert _test_args(FockStateBosonBra((0, 1))) + + +def test_sympy__physics__secondquant__FockStateBosonKet(): + from sympy.physics.secondquant import FockStateBosonKet + assert _test_args(FockStateBosonKet((0, 1))) + + +def test_sympy__physics__secondquant__FockStateBra(): + from sympy.physics.secondquant import FockStateBra + assert _test_args(FockStateBra((0, 1))) + + +def test_sympy__physics__secondquant__FockStateFermionBra(): + from sympy.physics.secondquant import FockStateFermionBra + assert _test_args(FockStateFermionBra((0, 1))) + + +def test_sympy__physics__secondquant__FockStateFermionKet(): + from sympy.physics.secondquant import FockStateFermionKet + assert _test_args(FockStateFermionKet((0, 1))) + + +def test_sympy__physics__secondquant__FockStateKet(): + from sympy.physics.secondquant import FockStateKet + assert _test_args(FockStateKet((0, 1))) + + +def test_sympy__physics__secondquant__InnerProduct(): + from sympy.physics.secondquant import InnerProduct + from sympy.physics.secondquant import FockStateKet, FockStateBra + assert _test_args(InnerProduct(FockStateBra((0, 1)), FockStateKet((0, 1)))) + + +def test_sympy__physics__secondquant__NO(): + from sympy.physics.secondquant import NO, F, Fd + assert _test_args(NO(Fd(x)*F(y))) + + +def test_sympy__physics__secondquant__PermutationOperator(): + from sympy.physics.secondquant import PermutationOperator + assert _test_args(PermutationOperator(0, 1)) + + +def test_sympy__physics__secondquant__SqOperator(): + from sympy.physics.secondquant import SqOperator + assert _test_args(SqOperator(0)) + + +def test_sympy__physics__secondquant__TensorSymbol(): + from sympy.physics.secondquant import TensorSymbol + assert _test_args(TensorSymbol(x)) + + +def test_sympy__physics__control__lti__LinearTimeInvariant(): + # Direct instances of LinearTimeInvariant class are not allowed. + # func(*args) tests for its derived classes (TransferFunction, + # Series, Parallel and TransferFunctionMatrix) should pass. + pass + + +def test_sympy__physics__control__lti__SISOLinearTimeInvariant(): + # Direct instances of SISOLinearTimeInvariant class are not allowed. + pass + + +def test_sympy__physics__control__lti__MIMOLinearTimeInvariant(): + # Direct instances of MIMOLinearTimeInvariant class are not allowed. + pass + + +def test_sympy__physics__control__lti__TransferFunction(): + from sympy.physics.control.lti import TransferFunction + assert _test_args(TransferFunction(2, 3, x)) + + +def _test_args_PIDController(obj): + from sympy.physics.control.lti import PIDController + if isinstance(obj, PIDController): + kp, ki, kd, tf = obj.kp, obj.ki, obj.kd, obj.tf + recreated_pid = PIDController(kp, ki, kd, tf, s) + return recreated_pid == obj + return False + + +def test_sympy__physics__control__lti__PIDController(): + from sympy.physics.control.lti import PIDController + kp, ki, kd, tf = 1, 0.1, 0.01, 0 + assert _test_args_PIDController(PIDController(kp, ki, kd, tf, s)) + + +def test_sympy__physics__control__lti__Series(): + from sympy.physics.control import Series, TransferFunction + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + assert _test_args(Series(tf1, tf2)) + + +def test_sympy__physics__control__lti__MIMOSeries(): + from sympy.physics.control import MIMOSeries, TransferFunction, TransferFunctionMatrix + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + tfm_1 = TransferFunctionMatrix([[tf2, tf1]]) + tfm_2 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + tfm_3 = TransferFunctionMatrix([[tf1], [tf2]]) + assert _test_args(MIMOSeries(tfm_3, tfm_2, tfm_1)) + + +def test_sympy__physics__control__lti__Parallel(): + from sympy.physics.control import Parallel, TransferFunction + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + assert _test_args(Parallel(tf1, tf2)) + + +def test_sympy__physics__control__lti__MIMOParallel(): + from sympy.physics.control import MIMOParallel, TransferFunction, TransferFunctionMatrix + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + tfm_2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]]) + assert _test_args(MIMOParallel(tfm_1, tfm_2)) + + +def test_sympy__physics__control__lti__Feedback(): + from sympy.physics.control import TransferFunction, Feedback + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + assert _test_args(Feedback(tf1, tf2)) + assert _test_args(Feedback(tf1, tf2, 1)) + + +def test_sympy__physics__control__lti__MIMOFeedback(): + from sympy.physics.control import TransferFunction, MIMOFeedback, TransferFunctionMatrix + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + tfm_1 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]]) + tfm_2 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]]) + assert _test_args(MIMOFeedback(tfm_1, tfm_2)) + assert _test_args(MIMOFeedback(tfm_1, tfm_2, 1)) + + +def test_sympy__physics__control__lti__TransferFunctionMatrix(): + from sympy.physics.control import TransferFunction, TransferFunctionMatrix + tf1 = TransferFunction(x**2 - y**3, y - z, x) + tf2 = TransferFunction(y - x, z + y, x) + assert _test_args(TransferFunctionMatrix([[tf1, tf2]])) + + +def test_sympy__physics__control__lti__StateSpace(): + from sympy.matrices.dense import Matrix + from sympy.physics.control import StateSpace + A = Matrix([[-5, -1], [3, -1]]) + B = Matrix([2, 5]) + C = Matrix([[1, 2]]) + D = Matrix([0]) + assert _test_args(StateSpace(A, B, C, D)) + + +def test_sympy__physics__units__dimensions__Dimension(): + from sympy.physics.units.dimensions import Dimension + assert _test_args(Dimension("length", "L")) + + +def test_sympy__physics__units__dimensions__DimensionSystem(): + from sympy.physics.units.dimensions import DimensionSystem + from sympy.physics.units.definitions.dimension_definitions import length, time, velocity + assert _test_args(DimensionSystem((length, time), (velocity,))) + + +def test_sympy__physics__units__quantities__Quantity(): + from sympy.physics.units.quantities import Quantity + assert _test_args(Quantity("dam")) + + +def test_sympy__physics__units__quantities__PhysicalConstant(): + from sympy.physics.units.quantities import PhysicalConstant + assert _test_args(PhysicalConstant("foo")) + + +def test_sympy__physics__units__prefixes__Prefix(): + from sympy.physics.units.prefixes import Prefix + assert _test_args(Prefix('kilo', 'k', 3)) + + +def test_sympy__core__numbers__AlgebraicNumber(): + from sympy.core.numbers import AlgebraicNumber + assert _test_args(AlgebraicNumber(sqrt(2), [1, 2, 3])) + + +def test_sympy__polys__polytools__GroebnerBasis(): + from sympy.polys.polytools import GroebnerBasis + assert _test_args(GroebnerBasis([x, y, z], x, y, z)) + + +def test_sympy__polys__polytools__Poly(): + from sympy.polys.polytools import Poly + assert _test_args(Poly(2, x, y)) + + +def test_sympy__polys__polytools__PurePoly(): + from sympy.polys.polytools import PurePoly + assert _test_args(PurePoly(2, x, y)) + + +@SKIP('abstract class') +def test_sympy__polys__rootoftools__RootOf(): + pass + + +def test_sympy__polys__rootoftools__ComplexRootOf(): + from sympy.polys.rootoftools import ComplexRootOf + assert _test_args(ComplexRootOf(x**3 + x + 1, 0)) + + +def test_sympy__polys__rootoftools__RootSum(): + from sympy.polys.rootoftools import RootSum + assert _test_args(RootSum(x**3 + x + 1, sin)) + + +def test_sympy__series__limits__Limit(): + from sympy.series.limits import Limit + assert _test_args(Limit(x, x, 0, dir='-')) + + +def test_sympy__series__order__Order(): + from sympy.series.order import Order + assert _test_args(Order(1, x, y)) + + +@SKIP('Abstract Class') +def test_sympy__series__sequences__SeqBase(): + pass + + +def test_sympy__series__sequences__EmptySequence(): + # Need to import the instance from series not the class from + # series.sequence + from sympy.series import EmptySequence + assert _test_args(EmptySequence) + + +@SKIP('Abstract Class') +def test_sympy__series__sequences__SeqExpr(): + pass + + +def test_sympy__series__sequences__SeqPer(): + from sympy.series.sequences import SeqPer + assert _test_args(SeqPer((1, 2, 3), (0, 10))) + + +def test_sympy__series__sequences__SeqFormula(): + from sympy.series.sequences import SeqFormula + assert _test_args(SeqFormula(x**2, (0, 10))) + + +def test_sympy__series__sequences__RecursiveSeq(): + from sympy.series.sequences import RecursiveSeq + y = Function("y") + n = symbols("n") + assert _test_args(RecursiveSeq(y(n - 1) + y(n - 2), y(n), n, (0, 1))) + assert _test_args(RecursiveSeq(y(n - 1) + y(n - 2), y(n), n)) + + +def test_sympy__series__sequences__SeqExprOp(): + from sympy.series.sequences import SeqExprOp, sequence + s1 = sequence((1, 2, 3)) + s2 = sequence(x**2) + assert _test_args(SeqExprOp(s1, s2)) + + +def test_sympy__series__sequences__SeqAdd(): + from sympy.series.sequences import SeqAdd, sequence + s1 = sequence((1, 2, 3)) + s2 = sequence(x**2) + assert _test_args(SeqAdd(s1, s2)) + + +def test_sympy__series__sequences__SeqMul(): + from sympy.series.sequences import SeqMul, sequence + s1 = sequence((1, 2, 3)) + s2 = sequence(x**2) + assert _test_args(SeqMul(s1, s2)) + + +@SKIP('Abstract Class') +def test_sympy__series__series_class__SeriesBase(): + pass + + +def test_sympy__series__fourier__FourierSeries(): + from sympy.series.fourier import fourier_series + assert _test_args(fourier_series(x, (x, -pi, pi))) + +def test_sympy__series__fourier__FiniteFourierSeries(): + from sympy.series.fourier import fourier_series + assert _test_args(fourier_series(sin(pi*x), (x, -1, 1))) + + +def test_sympy__series__formal__FormalPowerSeries(): + from sympy.series.formal import fps + assert _test_args(fps(log(1 + x), x)) + + +def test_sympy__series__formal__Coeff(): + from sympy.series.formal import fps + assert _test_args(fps(x**2 + x + 1, x)) + + +@SKIP('Abstract Class') +def test_sympy__series__formal__FiniteFormalPowerSeries(): + pass + + +def test_sympy__series__formal__FormalPowerSeriesProduct(): + from sympy.series.formal import fps + f1, f2 = fps(sin(x)), fps(exp(x)) + assert _test_args(f1.product(f2, x)) + + +def test_sympy__series__formal__FormalPowerSeriesCompose(): + from sympy.series.formal import fps + f1, f2 = fps(exp(x)), fps(sin(x)) + assert _test_args(f1.compose(f2, x)) + + +def test_sympy__series__formal__FormalPowerSeriesInverse(): + from sympy.series.formal import fps + f1 = fps(exp(x)) + assert _test_args(f1.inverse(x)) + + +def test_sympy__simplify__hyperexpand__Hyper_Function(): + from sympy.simplify.hyperexpand import Hyper_Function + assert _test_args(Hyper_Function([2], [1])) + + +def test_sympy__simplify__hyperexpand__G_Function(): + from sympy.simplify.hyperexpand import G_Function + assert _test_args(G_Function([2], [1], [], [])) + + +@SKIP("abstract class") +def test_sympy__tensor__array__ndim_array__ImmutableNDimArray(): + pass + + +def test_sympy__tensor__array__dense_ndim_array__ImmutableDenseNDimArray(): + from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray + densarr = ImmutableDenseNDimArray(range(10, 34), (2, 3, 4)) + assert _test_args(densarr) + + +def test_sympy__tensor__array__sparse_ndim_array__ImmutableSparseNDimArray(): + from sympy.tensor.array.sparse_ndim_array import ImmutableSparseNDimArray + sparr = ImmutableSparseNDimArray(range(10, 34), (2, 3, 4)) + assert _test_args(sparr) + + +def test_sympy__tensor__array__array_comprehension__ArrayComprehension(): + from sympy.tensor.array.array_comprehension import ArrayComprehension + arrcom = ArrayComprehension(x, (x, 1, 5)) + assert _test_args(arrcom) + +def test_sympy__tensor__array__array_comprehension__ArrayComprehensionMap(): + from sympy.tensor.array.array_comprehension import ArrayComprehensionMap + arrcomma = ArrayComprehensionMap(lambda: 0, (x, 1, 5)) + assert _test_args(arrcomma) + + +def test_sympy__tensor__array__array_derivatives__ArrayDerivative(): + from sympy.tensor.array.array_derivatives import ArrayDerivative + A = MatrixSymbol("A", 2, 2) + arrder = ArrayDerivative(A, A, evaluate=False) + assert _test_args(arrder) + +def test_sympy__tensor__array__expressions__array_expressions__ArraySymbol(): + from sympy.tensor.array.expressions.array_expressions import ArraySymbol + m, n, k = symbols("m n k") + array = ArraySymbol("A", (m, n, k, 2)) + assert _test_args(array) + +def test_sympy__tensor__array__expressions__array_expressions__ArrayElement(): + from sympy.tensor.array.expressions.array_expressions import ArrayElement + m, n, k = symbols("m n k") + ae = ArrayElement("A", (m, n, k, 2)) + assert _test_args(ae) + +def test_sympy__tensor__array__expressions__array_expressions__ZeroArray(): + from sympy.tensor.array.expressions.array_expressions import ZeroArray + m, n, k = symbols("m n k") + za = ZeroArray(m, n, k, 2) + assert _test_args(za) + +def test_sympy__tensor__array__expressions__array_expressions__OneArray(): + from sympy.tensor.array.expressions.array_expressions import OneArray + m, n, k = symbols("m n k") + za = OneArray(m, n, k, 2) + assert _test_args(za) + +def test_sympy__tensor__functions__TensorProduct(): + from sympy.tensor.functions import TensorProduct + A = MatrixSymbol('A', 3, 3) + B = MatrixSymbol('B', 3, 3) + tp = TensorProduct(A, B) + assert _test_args(tp) + + +def test_sympy__tensor__indexed__Idx(): + from sympy.tensor.indexed import Idx + assert _test_args(Idx('test')) + assert _test_args(Idx('test', (0, 10))) + assert _test_args(Idx('test', 2)) + assert _test_args(Idx('test', x)) + + +def test_sympy__tensor__indexed__Indexed(): + from sympy.tensor.indexed import Indexed, Idx + assert _test_args(Indexed('A', Idx('i'), Idx('j'))) + + +def test_sympy__tensor__indexed__IndexedBase(): + from sympy.tensor.indexed import IndexedBase + assert _test_args(IndexedBase('A', shape=(x, y))) + assert _test_args(IndexedBase('A', 1)) + assert _test_args(IndexedBase('A')[0, 1]) + + +def test_sympy__tensor__tensor__TensorIndexType(): + from sympy.tensor.tensor import TensorIndexType + assert _test_args(TensorIndexType('Lorentz')) + + +@SKIP("deprecated class") +def test_sympy__tensor__tensor__TensorType(): + pass + + +def test_sympy__tensor__tensor__TensorSymmetry(): + from sympy.tensor.tensor import TensorSymmetry, get_symmetric_group_sgs + assert _test_args(TensorSymmetry(get_symmetric_group_sgs(2))) + + +def test_sympy__tensor__tensor__TensorHead(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, TensorHead + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + assert _test_args(TensorHead('p', [Lorentz], sym, 0)) + + +def test_sympy__tensor__tensor__TensorIndex(): + from sympy.tensor.tensor import TensorIndexType, TensorIndex + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + assert _test_args(TensorIndex('i', Lorentz)) + +@SKIP("abstract class") +def test_sympy__tensor__tensor__TensExpr(): + pass + +def test_sympy__tensor__tensor__TensAdd(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, tensor_indices, TensAdd, tensor_heads + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, b = tensor_indices('a,b', Lorentz) + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + p, q = tensor_heads('p,q', [Lorentz], sym) + t1 = p(a) + t2 = q(a) + assert _test_args(TensAdd(t1, t2)) + + +def test_sympy__tensor__tensor__Tensor(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, tensor_indices, TensorHead + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, b = tensor_indices('a,b', Lorentz) + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + p = TensorHead('p', [Lorentz], sym) + assert _test_args(p(a)) + + +def test_sympy__tensor__tensor__TensMul(): + from sympy.tensor.tensor import TensorIndexType, TensorSymmetry, get_symmetric_group_sgs, tensor_indices, tensor_heads + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, b = tensor_indices('a,b', Lorentz) + sym = TensorSymmetry(get_symmetric_group_sgs(1)) + p, q = tensor_heads('p, q', [Lorentz], sym) + assert _test_args(3*p(a)*q(b)) + + +def test_sympy__tensor__tensor__TensorElement(): + from sympy.tensor.tensor import TensorIndexType, TensorHead, TensorElement + L = TensorIndexType("L") + A = TensorHead("A", [L, L]) + telem = TensorElement(A(x, y), {x: 1}) + assert _test_args(telem) + +def test_sympy__tensor__tensor__WildTensor(): + from sympy.tensor.tensor import TensorIndexType, WildTensorHead, TensorIndex + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a = TensorIndex('a', Lorentz) + p = WildTensorHead('p') + assert _test_args(p(a)) + +def test_sympy__tensor__tensor__WildTensorHead(): + from sympy.tensor.tensor import WildTensorHead + assert _test_args(WildTensorHead('p')) + +def test_sympy__tensor__tensor__WildTensorIndex(): + from sympy.tensor.tensor import TensorIndexType, WildTensorIndex + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + assert _test_args(WildTensorIndex('i', Lorentz)) + +def test_sympy__tensor__toperators__PartialDerivative(): + from sympy.tensor.tensor import TensorIndexType, tensor_indices, TensorHead + from sympy.tensor.toperators import PartialDerivative + Lorentz = TensorIndexType('Lorentz', dummy_name='L') + a, b = tensor_indices('a,b', Lorentz) + A = TensorHead("A", [Lorentz]) + assert _test_args(PartialDerivative(A(a), A(b))) + + +def test_as_coeff_add(): + assert (7, (3*x, 4*x**2)) == (7 + 3*x + 4*x**2).as_coeff_add() + + +def test_sympy__geometry__curve__Curve(): + from sympy.geometry.curve import Curve + assert _test_args(Curve((x, 1), (x, 0, 1))) + + +def test_sympy__geometry__point__Point(): + from sympy.geometry.point import Point + assert _test_args(Point(0, 1)) + + +def test_sympy__geometry__point__Point2D(): + from sympy.geometry.point import Point2D + assert _test_args(Point2D(0, 1)) + + +def test_sympy__geometry__point__Point3D(): + from sympy.geometry.point import Point3D + assert _test_args(Point3D(0, 1, 2)) + + +def test_sympy__geometry__ellipse__Ellipse(): + from sympy.geometry.ellipse import Ellipse + assert _test_args(Ellipse((0, 1), 2, 3)) + + +def test_sympy__geometry__ellipse__Circle(): + from sympy.geometry.ellipse import Circle + assert _test_args(Circle((0, 1), 2)) + + +def test_sympy__geometry__parabola__Parabola(): + from sympy.geometry.parabola import Parabola + from sympy.geometry.line import Line + assert _test_args(Parabola((0, 0), Line((2, 3), (4, 3)))) + + +@SKIP("abstract class") +def test_sympy__geometry__line__LinearEntity(): + pass + + +def test_sympy__geometry__line__Line(): + from sympy.geometry.line import Line + assert _test_args(Line((0, 1), (2, 3))) + + +def test_sympy__geometry__line__Ray(): + from sympy.geometry.line import Ray + assert _test_args(Ray((0, 1), (2, 3))) + + +def test_sympy__geometry__line__Segment(): + from sympy.geometry.line import Segment + assert _test_args(Segment((0, 1), (2, 3))) + +@SKIP("abstract class") +def test_sympy__geometry__line__LinearEntity2D(): + pass + + +def test_sympy__geometry__line__Line2D(): + from sympy.geometry.line import Line2D + assert _test_args(Line2D((0, 1), (2, 3))) + + +def test_sympy__geometry__line__Ray2D(): + from sympy.geometry.line import Ray2D + assert _test_args(Ray2D((0, 1), (2, 3))) + + +def test_sympy__geometry__line__Segment2D(): + from sympy.geometry.line import Segment2D + assert _test_args(Segment2D((0, 1), (2, 3))) + + +@SKIP("abstract class") +def test_sympy__geometry__line__LinearEntity3D(): + pass + + +def test_sympy__geometry__line__Line3D(): + from sympy.geometry.line import Line3D + assert _test_args(Line3D((0, 1, 1), (2, 3, 4))) + + +def test_sympy__geometry__line__Segment3D(): + from sympy.geometry.line import Segment3D + assert _test_args(Segment3D((0, 1, 1), (2, 3, 4))) + + +def test_sympy__geometry__line__Ray3D(): + from sympy.geometry.line import Ray3D + assert _test_args(Ray3D((0, 1, 1), (2, 3, 4))) + + +def test_sympy__geometry__plane__Plane(): + from sympy.geometry.plane import Plane + assert _test_args(Plane((1, 1, 1), (-3, 4, -2), (1, 2, 3))) + + +def test_sympy__geometry__polygon__Polygon(): + from sympy.geometry.polygon import Polygon + assert _test_args(Polygon((0, 1), (2, 3), (4, 5), (6, 7))) + + +def test_sympy__geometry__polygon__RegularPolygon(): + from sympy.geometry.polygon import RegularPolygon + assert _test_args(RegularPolygon((0, 1), 2, 3, 4)) + + +def test_sympy__geometry__polygon__Triangle(): + from sympy.geometry.polygon import Triangle + assert _test_args(Triangle((0, 1), (2, 3), (4, 5))) + + +def test_sympy__geometry__entity__GeometryEntity(): + from sympy.geometry.entity import GeometryEntity + from sympy.geometry.point import Point + assert _test_args(GeometryEntity(Point(1, 0), 1, [1, 2])) + +@SKIP("abstract class") +def test_sympy__geometry__entity__GeometrySet(): + pass + +def test_sympy__diffgeom__diffgeom__Manifold(): + from sympy.diffgeom import Manifold + assert _test_args(Manifold('name', 3)) + + +def test_sympy__diffgeom__diffgeom__Patch(): + from sympy.diffgeom import Manifold, Patch + assert _test_args(Patch('name', Manifold('name', 3))) + + +def test_sympy__diffgeom__diffgeom__CoordSystem(): + from sympy.diffgeom import Manifold, Patch, CoordSystem + assert _test_args(CoordSystem('name', Patch('name', Manifold('name', 3)))) + assert _test_args(CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c])) + + +def test_sympy__diffgeom__diffgeom__CoordinateSymbol(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, CoordinateSymbol + assert _test_args(CoordinateSymbol(CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]), 0)) + + +def test_sympy__diffgeom__diffgeom__Point(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, Point + assert _test_args(Point( + CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]), [x, y])) + + +def test_sympy__diffgeom__diffgeom__BaseScalarField(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + assert _test_args(BaseScalarField(cs, 0)) + + +def test_sympy__diffgeom__diffgeom__BaseVectorField(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseVectorField + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + assert _test_args(BaseVectorField(cs, 0)) + + +def test_sympy__diffgeom__diffgeom__Differential(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField, Differential + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + assert _test_args(Differential(BaseScalarField(cs, 0))) + + +def test_sympy__diffgeom__diffgeom__Commutator(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseVectorField, Commutator + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + cs1 = CoordSystem('name1', Patch('name', Manifold('name', 3)), [a, b, c]) + v = BaseVectorField(cs, 0) + v1 = BaseVectorField(cs1, 0) + assert _test_args(Commutator(v, v1)) + + +def test_sympy__diffgeom__diffgeom__TensorProduct(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField, Differential, TensorProduct + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + d = Differential(BaseScalarField(cs, 0)) + assert _test_args(TensorProduct(d, d)) + + +def test_sympy__diffgeom__diffgeom__WedgeProduct(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField, Differential, WedgeProduct + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + d = Differential(BaseScalarField(cs, 0)) + d1 = Differential(BaseScalarField(cs, 1)) + assert _test_args(WedgeProduct(d, d1)) + + +def test_sympy__diffgeom__diffgeom__LieDerivative(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseScalarField, Differential, BaseVectorField, LieDerivative + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + d = Differential(BaseScalarField(cs, 0)) + v = BaseVectorField(cs, 0) + assert _test_args(LieDerivative(v, d)) + + +def test_sympy__diffgeom__diffgeom__BaseCovarDerivativeOp(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseCovarDerivativeOp + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + assert _test_args(BaseCovarDerivativeOp(cs, 0, [[[0, ]*3, ]*3, ]*3)) + + +def test_sympy__diffgeom__diffgeom__CovarDerivativeOp(): + from sympy.diffgeom import Manifold, Patch, CoordSystem, BaseVectorField, CovarDerivativeOp + cs = CoordSystem('name', Patch('name', Manifold('name', 3)), [a, b, c]) + v = BaseVectorField(cs, 0) + _test_args(CovarDerivativeOp(v, [[[0, ]*3, ]*3, ]*3)) + + +def test_sympy__categories__baseclasses__Class(): + from sympy.categories.baseclasses import Class + assert _test_args(Class()) + + +def test_sympy__categories__baseclasses__Object(): + from sympy.categories import Object + assert _test_args(Object("A")) + + +@SKIP("abstract class") +def test_sympy__categories__baseclasses__Morphism(): + pass + + +def test_sympy__categories__baseclasses__IdentityMorphism(): + from sympy.categories import Object, IdentityMorphism + assert _test_args(IdentityMorphism(Object("A"))) + + +def test_sympy__categories__baseclasses__NamedMorphism(): + from sympy.categories import Object, NamedMorphism + assert _test_args(NamedMorphism(Object("A"), Object("B"), "f")) + + +def test_sympy__categories__baseclasses__CompositeMorphism(): + from sympy.categories import Object, NamedMorphism, CompositeMorphism + A = Object("A") + B = Object("B") + C = Object("C") + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + assert _test_args(CompositeMorphism(f, g)) + + +def test_sympy__categories__baseclasses__Diagram(): + from sympy.categories import Object, NamedMorphism, Diagram + A = Object("A") + B = Object("B") + f = NamedMorphism(A, B, "f") + d = Diagram([f]) + assert _test_args(d) + + +def test_sympy__categories__baseclasses__Category(): + from sympy.categories import Object, NamedMorphism, Diagram, Category + A = Object("A") + B = Object("B") + C = Object("C") + f = NamedMorphism(A, B, "f") + g = NamedMorphism(B, C, "g") + d1 = Diagram([f, g]) + d2 = Diagram([f]) + K = Category("K", commutative_diagrams=[d1, d2]) + assert _test_args(K) + + +def test_sympy__physics__optics__waves__TWave(): + from sympy.physics.optics import TWave + A, f, phi = symbols('A, f, phi') + assert _test_args(TWave(A, f, phi)) + + +def test_sympy__physics__optics__gaussopt__BeamParameter(): + from sympy.physics.optics import BeamParameter + assert _test_args(BeamParameter(530e-9, 1, w=1e-3, n=1)) + + +def test_sympy__physics__optics__medium__Medium(): + from sympy.physics.optics import Medium + assert _test_args(Medium('m')) + + +def test_sympy__physics__optics__medium__MediumN(): + from sympy.physics.optics.medium import Medium + assert _test_args(Medium('m', n=2)) + + +def test_sympy__physics__optics__medium__MediumPP(): + from sympy.physics.optics.medium import Medium + assert _test_args(Medium('m', permittivity=2, permeability=2)) + + +def test_sympy__tensor__array__expressions__array_expressions__ArrayContraction(): + from sympy.tensor.array.expressions.array_expressions import ArrayContraction + from sympy.tensor.indexed import IndexedBase + A = symbols("A", cls=IndexedBase) + assert _test_args(ArrayContraction(A, (0, 1))) + + +def test_sympy__tensor__array__expressions__array_expressions__ArrayDiagonal(): + from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal + from sympy.tensor.indexed import IndexedBase + A = symbols("A", cls=IndexedBase) + assert _test_args(ArrayDiagonal(A, (0, 1))) + + +def test_sympy__tensor__array__expressions__array_expressions__ArrayTensorProduct(): + from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct + from sympy.tensor.indexed import IndexedBase + A, B = symbols("A B", cls=IndexedBase) + assert _test_args(ArrayTensorProduct(A, B)) + + +def test_sympy__tensor__array__expressions__array_expressions__ArrayAdd(): + from sympy.tensor.array.expressions.array_expressions import ArrayAdd + from sympy.tensor.indexed import IndexedBase + A, B = symbols("A B", cls=IndexedBase) + assert _test_args(ArrayAdd(A, B)) + + +def test_sympy__tensor__array__expressions__array_expressions__PermuteDims(): + from sympy.tensor.array.expressions.array_expressions import PermuteDims + A = MatrixSymbol("A", 4, 4) + assert _test_args(PermuteDims(A, (1, 0))) + + +def test_sympy__tensor__array__expressions__array_expressions__ArrayElementwiseApplyFunc(): + from sympy.tensor.array.expressions.array_expressions import ArraySymbol, ArrayElementwiseApplyFunc + A = ArraySymbol("A", (4,)) + assert _test_args(ArrayElementwiseApplyFunc(exp, A)) + + +def test_sympy__tensor__array__expressions__array_expressions__Reshape(): + from sympy.tensor.array.expressions.array_expressions import ArraySymbol, Reshape + A = ArraySymbol("A", (4,)) + assert _test_args(Reshape(A, (2, 2))) + + +def test_sympy__codegen__ast__Assignment(): + from sympy.codegen.ast import Assignment + assert _test_args(Assignment(x, y)) + + +def test_sympy__codegen__cfunctions__expm1(): + from sympy.codegen.cfunctions import expm1 + assert _test_args(expm1(x)) + + +def test_sympy__codegen__cfunctions__log1p(): + from sympy.codegen.cfunctions import log1p + assert _test_args(log1p(x)) + + +def test_sympy__codegen__cfunctions__exp2(): + from sympy.codegen.cfunctions import exp2 + assert _test_args(exp2(x)) + + +def test_sympy__codegen__cfunctions__log2(): + from sympy.codegen.cfunctions import log2 + assert _test_args(log2(x)) + + +def test_sympy__codegen__cfunctions__fma(): + from sympy.codegen.cfunctions import fma + assert _test_args(fma(x, y, z)) + + +def test_sympy__codegen__cfunctions__log10(): + from sympy.codegen.cfunctions import log10 + assert _test_args(log10(x)) + + +def test_sympy__codegen__cfunctions__Sqrt(): + from sympy.codegen.cfunctions import Sqrt + assert _test_args(Sqrt(x)) + +def test_sympy__codegen__cfunctions__Cbrt(): + from sympy.codegen.cfunctions import Cbrt + assert _test_args(Cbrt(x)) + +def test_sympy__codegen__cfunctions__hypot(): + from sympy.codegen.cfunctions import hypot + assert _test_args(hypot(x, y)) + + +def test_sympy__codegen__cfunctions__isnan(): + from sympy.codegen.cfunctions import isnan + assert _test_args(isnan(x)) + + +def test_sympy__codegen__cfunctions__isinf(): + from sympy.codegen.cfunctions import isinf + assert _test_args(isinf(x)) + + +def test_sympy__codegen__fnodes__FFunction(): + from sympy.codegen.fnodes import FFunction + assert _test_args(FFunction('f')) + + +def test_sympy__codegen__fnodes__F95Function(): + from sympy.codegen.fnodes import F95Function + assert _test_args(F95Function('f')) + + +def test_sympy__codegen__fnodes__isign(): + from sympy.codegen.fnodes import isign + assert _test_args(isign(1, x)) + + +def test_sympy__codegen__fnodes__dsign(): + from sympy.codegen.fnodes import dsign + assert _test_args(dsign(1, x)) + + +def test_sympy__codegen__fnodes__cmplx(): + from sympy.codegen.fnodes import cmplx + assert _test_args(cmplx(x, y)) + + +def test_sympy__codegen__fnodes__kind(): + from sympy.codegen.fnodes import kind + assert _test_args(kind(x)) + + +def test_sympy__codegen__fnodes__merge(): + from sympy.codegen.fnodes import merge + assert _test_args(merge(1, 2, Eq(x, 0))) + + +def test_sympy__codegen__fnodes___literal(): + from sympy.codegen.fnodes import _literal + assert _test_args(_literal(1)) + + +def test_sympy__codegen__fnodes__literal_sp(): + from sympy.codegen.fnodes import literal_sp + assert _test_args(literal_sp(1)) + + +def test_sympy__codegen__fnodes__literal_dp(): + from sympy.codegen.fnodes import literal_dp + assert _test_args(literal_dp(1)) + + +def test_sympy__codegen__matrix_nodes__MatrixSolve(): + from sympy.matrices import MatrixSymbol + from sympy.codegen.matrix_nodes import MatrixSolve + A = MatrixSymbol('A', 3, 3) + v = MatrixSymbol('x', 3, 1) + assert _test_args(MatrixSolve(A, v)) + + +def test_sympy__printing__rust__TypeCast(): + from sympy.printing.rust import TypeCast + from sympy.codegen.ast import real + assert _test_args(TypeCast(x, real)) + + +def test_sympy__printing__rust__float_floor(): + from sympy.printing.rust import float_floor + assert _test_args(float_floor(x)) + + +def test_sympy__printing__rust__float_ceiling(): + from sympy.printing.rust import float_ceiling + assert _test_args(float_ceiling(x)) + + +def test_sympy__vector__coordsysrect__CoordSys3D(): + from sympy.vector.coordsysrect import CoordSys3D + assert _test_args(CoordSys3D('C')) + + +def test_sympy__vector__point__Point(): + from sympy.vector.point import Point + assert _test_args(Point('P')) + + +def test_sympy__vector__basisdependent__BasisDependent(): + #from sympy.vector.basisdependent import BasisDependent + #These classes have been created to maintain an OOP hierarchy + #for Vectors and Dyadics. Are NOT meant to be initialized + pass + + +def test_sympy__vector__basisdependent__BasisDependentMul(): + #from sympy.vector.basisdependent import BasisDependentMul + #These classes have been created to maintain an OOP hierarchy + #for Vectors and Dyadics. Are NOT meant to be initialized + pass + + +def test_sympy__vector__basisdependent__BasisDependentAdd(): + #from sympy.vector.basisdependent import BasisDependentAdd + #These classes have been created to maintain an OOP hierarchy + #for Vectors and Dyadics. Are NOT meant to be initialized + pass + + +def test_sympy__vector__basisdependent__BasisDependentZero(): + #from sympy.vector.basisdependent import BasisDependentZero + #These classes have been created to maintain an OOP hierarchy + #for Vectors and Dyadics. Are NOT meant to be initialized + pass + + +def test_sympy__vector__vector__BaseVector(): + from sympy.vector.vector import BaseVector + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(BaseVector(0, C, ' ', ' ')) + + +def test_sympy__vector__vector__VectorAdd(): + from sympy.vector.vector import VectorAdd, VectorMul + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + from sympy.abc import a, b, c, x, y, z + v1 = a*C.i + b*C.j + c*C.k + v2 = x*C.i + y*C.j + z*C.k + assert _test_args(VectorAdd(v1, v2)) + assert _test_args(VectorMul(x, v1)) + + +def test_sympy__vector__vector__VectorMul(): + from sympy.vector.vector import VectorMul + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + from sympy.abc import a + assert _test_args(VectorMul(a, C.i)) + + +def test_sympy__vector__vector__VectorZero(): + from sympy.vector.vector import VectorZero + assert _test_args(VectorZero()) + + +def test_sympy__vector__vector__Vector(): + #from sympy.vector.vector import Vector + #Vector is never to be initialized using args + pass + + +def test_sympy__vector__vector__Cross(): + from sympy.vector.vector import Cross + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + _test_args(Cross(C.i, C.j)) + + +def test_sympy__vector__vector__Dot(): + from sympy.vector.vector import Dot + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + _test_args(Dot(C.i, C.j)) + + +def test_sympy__vector__dyadic__Dyadic(): + #from sympy.vector.dyadic import Dyadic + #Dyadic is never to be initialized using args + pass + + +def test_sympy__vector__dyadic__BaseDyadic(): + from sympy.vector.dyadic import BaseDyadic + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(BaseDyadic(C.i, C.j)) + + +def test_sympy__vector__dyadic__DyadicMul(): + from sympy.vector.dyadic import BaseDyadic, DyadicMul + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(DyadicMul(3, BaseDyadic(C.i, C.j))) + + +def test_sympy__vector__dyadic__DyadicAdd(): + from sympy.vector.dyadic import BaseDyadic, DyadicAdd + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(2 * DyadicAdd(BaseDyadic(C.i, C.i), + BaseDyadic(C.i, C.j))) + + +def test_sympy__vector__dyadic__DyadicZero(): + from sympy.vector.dyadic import DyadicZero + assert _test_args(DyadicZero()) + + +def test_sympy__vector__deloperator__Del(): + from sympy.vector.deloperator import Del + assert _test_args(Del()) + + +def test_sympy__vector__implicitregion__ImplicitRegion(): + from sympy.vector.implicitregion import ImplicitRegion + from sympy.abc import x, y + assert _test_args(ImplicitRegion((x, y), y**3 - 4*x)) + + +def test_sympy__vector__integrals__ParametricIntegral(): + from sympy.vector.integrals import ParametricIntegral + from sympy.vector.parametricregion import ParametricRegion + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(ParametricIntegral(C.y*C.i - 10*C.j,\ + ParametricRegion((x, y), (x, 1, 3), (y, -2, 2)))) + +def test_sympy__vector__operators__Curl(): + from sympy.vector.operators import Curl + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(Curl(C.i)) + + +def test_sympy__vector__operators__Laplacian(): + from sympy.vector.operators import Laplacian + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(Laplacian(C.i)) + + +def test_sympy__vector__operators__Divergence(): + from sympy.vector.operators import Divergence + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(Divergence(C.i)) + + +def test_sympy__vector__operators__Gradient(): + from sympy.vector.operators import Gradient + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(Gradient(C.x)) + + +def test_sympy__vector__orienters__Orienter(): + #from sympy.vector.orienters import Orienter + #Not to be initialized + pass + + +def test_sympy__vector__orienters__ThreeAngleOrienter(): + #from sympy.vector.orienters import ThreeAngleOrienter + #Not to be initialized + pass + + +def test_sympy__vector__orienters__AxisOrienter(): + from sympy.vector.orienters import AxisOrienter + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(AxisOrienter(x, C.i)) + + +def test_sympy__vector__orienters__BodyOrienter(): + from sympy.vector.orienters import BodyOrienter + assert _test_args(BodyOrienter(x, y, z, '123')) + + +def test_sympy__vector__orienters__SpaceOrienter(): + from sympy.vector.orienters import SpaceOrienter + assert _test_args(SpaceOrienter(x, y, z, '123')) + + +def test_sympy__vector__orienters__QuaternionOrienter(): + from sympy.vector.orienters import QuaternionOrienter + a, b, c, d = symbols('a b c d') + assert _test_args(QuaternionOrienter(a, b, c, d)) + + +def test_sympy__vector__parametricregion__ParametricRegion(): + from sympy.abc import t + from sympy.vector.parametricregion import ParametricRegion + assert _test_args(ParametricRegion((t, t**3), (t, 0, 2))) + + +def test_sympy__vector__scalar__BaseScalar(): + from sympy.vector.scalar import BaseScalar + from sympy.vector.coordsysrect import CoordSys3D + C = CoordSys3D('C') + assert _test_args(BaseScalar(0, C, ' ', ' ')) + + +def test_sympy__physics__wigner__Wigner3j(): + from sympy.physics.wigner import Wigner3j + assert _test_args(Wigner3j(0, 0, 0, 0, 0, 0)) + + +def test_sympy__combinatorics__schur_number__SchurNumber(): + from sympy.combinatorics.schur_number import SchurNumber + assert _test_args(SchurNumber(x)) + + +def test_sympy__combinatorics__perm_groups__SymmetricPermutationGroup(): + from sympy.combinatorics.perm_groups import SymmetricPermutationGroup + assert _test_args(SymmetricPermutationGroup(5)) + + +def test_sympy__combinatorics__perm_groups__Coset(): + from sympy.combinatorics.permutations import Permutation + from sympy.combinatorics.perm_groups import PermutationGroup, Coset + a = Permutation(1, 2) + b = Permutation(0, 1) + G = PermutationGroup([a, b]) + assert _test_args(Coset(a, G)) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_arit.py b/phivenv/Lib/site-packages/sympy/core/tests/test_arit.py new file mode 100644 index 0000000000000000000000000000000000000000..251fc4c4234cbd6e82adc9a24ccea536ed6a37b7 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_arit.py @@ -0,0 +1,2489 @@ +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.mod import Mod +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Integer, Rational, comp, nan, + oo, pi, zoo) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (im, re, sign) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import (Max, sqrt) +from sympy.functions.elementary.trigonometric import (atan, cos, sin) +from sympy.integrals.integrals import Integral +from sympy.polys.polytools import Poly +from sympy.sets.sets import FiniteSet + +from sympy.core.parameters import distribute, evaluate +from sympy.core.expr import unchanged +from sympy.utilities.iterables import permutations +from sympy.testing.pytest import XFAIL, raises, warns +from sympy.utilities.exceptions import SymPyDeprecationWarning +from sympy.core.random import verify_numerically +from sympy.functions.elementary.trigonometric import asin + +from itertools import product + +a, c, x, y, z = symbols('a,c,x,y,z') +b = Symbol("b", positive=True) + + +def same_and_same_prec(a, b): + # stricter matching for Floats + return a == b and a._prec == b._prec + + +def test_bug1(): + assert re(x) != x + x.series(x, 0, 1) + assert re(x) != x + + +def test_Symbol(): + e = a*b + assert e == a*b + assert a*b*b == a*b**2 + assert a*b*b + c == c + a*b**2 + assert a*b*b - c == -c + a*b**2 + + x = Symbol('x', complex=True, real=False) + assert x.is_imaginary is None # could be I or 1 + I + x = Symbol('x', complex=True, imaginary=False) + assert x.is_real is None # could be 1 or 1 + I + x = Symbol('x', real=True) + assert x.is_complex + x = Symbol('x', imaginary=True) + assert x.is_complex + x = Symbol('x', real=False, imaginary=False) + assert x.is_complex is None # might be a non-number + + +def test_arit0(): + p = Rational(5) + e = a*b + assert e == a*b + e = a*b + b*a + assert e == 2*a*b + e = a*b + b*a + a*b + p*b*a + assert e == 8*a*b + e = a*b + b*a + a*b + p*b*a + a + assert e == a + 8*a*b + e = a + a + assert e == 2*a + e = a + b + a + assert e == b + 2*a + e = a + b*b + a + b*b + assert e == 2*a + 2*b**2 + e = a + Rational(2) + b*b + a + b*b + p + assert e == 7 + 2*a + 2*b**2 + e = (a + b*b + a + b*b)*p + assert e == 5*(2*a + 2*b**2) + e = (a*b*c + c*b*a + b*a*c)*p + assert e == 15*a*b*c + e = (a*b*c + c*b*a + b*a*c)*p - Rational(15)*a*b*c + assert e == Rational(0) + e = Rational(50)*(a - a) + assert e == Rational(0) + e = b*a - b - a*b + b + assert e == Rational(0) + e = a*b + c**p + assert e == a*b + c**5 + e = a/b + assert e == a*b**(-1) + e = a*2*2 + assert e == 4*a + e = 2 + a*2/2 + assert e == 2 + a + e = 2 - a - 2 + assert e == -a + e = 2*a*2 + assert e == 4*a + e = 2/a/2 + assert e == a**(-1) + e = 2**a**2 + assert e == 2**(a**2) + e = -(1 + a) + assert e == -1 - a + e = S.Half*(1 + a) + assert e == S.Half + a/2 + + +def test_div(): + e = a/b + assert e == a*b**(-1) + e = a/b + c/2 + assert e == a*b**(-1) + Rational(1)/2*c + e = (1 - b)/(b - 1) + assert e == (1 + -b)*((-1) + b)**(-1) + + +def test_pow_arit(): + n1 = Rational(1) + n2 = Rational(2) + n5 = Rational(5) + e = a*a + assert e == a**2 + e = a*a*a + assert e == a**3 + e = a*a*a*a**Rational(6) + assert e == a**9 + e = a*a*a*a**Rational(6) - a**Rational(9) + assert e == Rational(0) + e = a**(b - b) + assert e == Rational(1) + e = (a + Rational(1) - a)**b + assert e == Rational(1) + + e = (a + b + c)**n2 + assert e == (a + b + c)**2 + assert e.expand() == 2*b*c + 2*a*c + 2*a*b + a**2 + c**2 + b**2 + + e = (a + b)**n2 + assert e == (a + b)**2 + assert e.expand() == 2*a*b + a**2 + b**2 + + e = (a + b)**(n1/n2) + assert e == sqrt(a + b) + assert e.expand() == sqrt(a + b) + + n = n5**(n1/n2) + assert n == sqrt(5) + e = n*a*b - n*b*a + assert e == Rational(0) + e = n*a*b + n*b*a + assert e == 2*a*b*sqrt(5) + assert e.diff(a) == 2*b*sqrt(5) + assert e.diff(a) == 2*b*sqrt(5) + e = a/b**2 + assert e == a*b**(-2) + + assert sqrt(2*(1 + sqrt(2))) == (2*(1 + 2**S.Half))**S.Half + + x = Symbol('x') + y = Symbol('y') + + assert ((x*y)**3).expand() == y**3 * x**3 + assert ((x*y)**-3).expand() == y**-3 * x**-3 + + assert (x**5*(3*x)**(3)).expand() == 27 * x**8 + assert (x**5*(-3*x)**(3)).expand() == -27 * x**8 + assert (x**5*(3*x)**(-3)).expand() == x**2 * Rational(1, 27) + assert (x**5*(-3*x)**(-3)).expand() == x**2 * Rational(-1, 27) + + # expand_power_exp + _x = Symbol('x', zero=False) + _y = Symbol('y', zero=False) + assert (_x**(y**(x + exp(x + y)) + z)).expand(deep=False) == \ + _x**z*_x**(y**(x + exp(x + y))) + assert (_x**(_y**(x + exp(x + y)) + z)).expand() == \ + _x**z*_x**(_y**x*_y**(exp(x)*exp(y))) + + n = Symbol('n', even=False) + k = Symbol('k', even=True) + o = Symbol('o', odd=True) + + assert unchanged(Pow, -1, x) + assert unchanged(Pow, -1, n) + assert (-2)**k == 2**k + assert (-1)**k == 1 + assert (-1)**o == -1 + + +def test_pow2(): + # x**(2*y) is always (x**y)**2 but is only (x**2)**y if + # x.is_positive or y.is_integer + # let x = 1 to see why the following are not true. + assert (-x)**Rational(2, 3) != x**Rational(2, 3) + assert (-x)**Rational(5, 7) != -x**Rational(5, 7) + assert ((-x)**2)**Rational(1, 3) != ((-x)**Rational(1, 3))**2 + assert sqrt(x**2) != x + + +def test_pow3(): + assert sqrt(2)**3 == 2 * sqrt(2) + assert sqrt(2)**3 == sqrt(8) + + +def test_mod_pow(): + for s, t, u, v in [(4, 13, 497, 445), (4, -3, 497, 365), + (3.2, 2.1, 1.9, 0.1031015682350942), (S(3)/2, 5, S(5)/6, S(3)/32)]: + assert pow(S(s), t, u) == v + assert pow(S(s), S(t), u) == v + assert pow(S(s), t, S(u)) == v + assert pow(S(s), S(t), S(u)) == v + assert pow(S(2), S(10000000000), S(3)) == 1 + assert pow(x, y, z) == x**y%z + raises(TypeError, lambda: pow(S(4), "13", 497)) + raises(TypeError, lambda: pow(S(4), 13, "497")) + + +def test_pow_E(): + assert 2**(y/log(2)) == S.Exp1**y + assert 2**(y/log(2)/3) == S.Exp1**(y/3) + assert 3**(1/log(-3)) != S.Exp1 + assert (3 + 2*I)**(1/(log(-3 - 2*I) + I*pi)) == S.Exp1 + assert (4 + 2*I)**(1/(log(-4 - 2*I) + I*pi)) == S.Exp1 + assert (3 + 2*I)**(1/(log(-3 - 2*I, 3)/2 + I*pi/log(3)/2)) == 9 + assert (3 + 2*I)**(1/(log(3 + 2*I, 3)/2)) == 9 + # every time tests are run they will affirm with a different random + # value that this identity holds + while 1: + b = x._random() + r, i = b.as_real_imag() + if i: + break + assert verify_numerically(b**(1/(log(-b) + sign(i)*I*pi).n()), S.Exp1) + + +def test_pow_issue_3516(): + assert 4**Rational(1, 4) == sqrt(2) + + +def test_pow_im(): + for m in (-2, -1, 2): + for d in (3, 4, 5): + b = m*I + for i in range(1, 4*d + 1): + e = Rational(i, d) + assert (b**e - b.n()**e.n()).n(2, chop=1e-10) == 0 + + e = Rational(7, 3) + assert (2*x*I)**e == 4*2**Rational(1, 3)*(I*x)**e # same as Wolfram Alpha + im = symbols('im', imaginary=True) + assert (2*im*I)**e == 4*2**Rational(1, 3)*(I*im)**e + + args = [I, I, I, I, 2] + e = Rational(1, 3) + ans = 2**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + args = [I, I, I, 2] + e = Rational(1, 3) + ans = 2**e*(-I)**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + args.append(-3) + ans = (6*I)**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + args.append(-1) + ans = (-6*I)**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + + args = [I, I, 2] + e = Rational(1, 3) + ans = (-2)**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + args.append(-3) + ans = (6)**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + args.append(-1) + ans = (-6)**e + assert Mul(*args, evaluate=False)**e == ans + assert Mul(*args)**e == ans + assert Mul(Pow(-1, Rational(3, 2), evaluate=False), I, I) == I + assert Mul(I*Pow(I, S.Half, evaluate=False)) == sqrt(I)*I + + +def test_real_mul(): + assert Float(0) * pi * x == 0 + assert set((Float(1) * pi * x).args) == {Float(1), pi, x} + + +def test_ncmul(): + A = Symbol("A", commutative=False) + B = Symbol("B", commutative=False) + C = Symbol("C", commutative=False) + assert A*B != B*A + assert A*B*C != C*B*A + assert A*b*B*3*C == 3*b*A*B*C + assert A*b*B*3*C != 3*b*B*A*C + assert A*b*B*3*C == 3*A*B*C*b + + assert A + B == B + A + assert (A + B)*C != C*(A + B) + + assert C*(A + B)*C != C*C*(A + B) + + assert A*A == A**2 + assert (A + B)*(A + B) == (A + B)**2 + + assert A**-1 * A == 1 + assert A/A == 1 + assert A/(A**2) == 1/A + + assert A/(1 + A) == A/(1 + A) + + assert set((A + B + 2*(A + B)).args) == \ + {A, B, 2*(A + B)} + + +def test_mul_add_identity(): + m = Mul(1, 2) + assert isinstance(m, Rational) and m.p == 2 and m.q == 1 + m = Mul(1, 2, evaluate=False) + assert isinstance(m, Mul) and m.args == (1, 2) + m = Mul(0, 1) + assert m is S.Zero + m = Mul(0, 1, evaluate=False) + assert isinstance(m, Mul) and m.args == (0, 1) + m = Add(0, 1) + assert m is S.One + m = Add(0, 1, evaluate=False) + assert isinstance(m, Add) and m.args == (0, 1) + + +def test_ncpow(): + x = Symbol('x', commutative=False) + y = Symbol('y', commutative=False) + z = Symbol('z', commutative=False) + a = Symbol('a') + b = Symbol('b') + c = Symbol('c') + + assert (x**2)*(y**2) != (y**2)*(x**2) + assert (x**-2)*y != y*(x**2) + assert 2**x*2**y != 2**(x + y) + assert 2**x*2**y*2**z != 2**(x + y + z) + assert 2**x*2**(2*x) == 2**(3*x) + assert 2**x*2**(2*x)*2**x == 2**(4*x) + assert exp(x)*exp(y) != exp(y)*exp(x) + assert exp(x)*exp(y)*exp(z) != exp(y)*exp(x)*exp(z) + assert exp(x)*exp(y)*exp(z) != exp(x + y + z) + assert x**a*x**b != x**(a + b) + assert x**a*x**b*x**c != x**(a + b + c) + assert x**3*x**4 == x**7 + assert x**3*x**4*x**2 == x**9 + assert x**a*x**(4*a) == x**(5*a) + assert x**a*x**(4*a)*x**a == x**(6*a) + + +def test_powerbug(): + x = Symbol("x") + assert x**1 != (-x)**1 + assert x**2 == (-x)**2 + assert x**3 != (-x)**3 + assert x**4 == (-x)**4 + assert x**5 != (-x)**5 + assert x**6 == (-x)**6 + + assert x**128 == (-x)**128 + assert x**129 != (-x)**129 + + assert (2*x)**2 == (-2*x)**2 + + +def test_Mul_doesnt_expand_exp(): + x = Symbol('x') + y = Symbol('y') + assert unchanged(Mul, exp(x), exp(y)) + assert unchanged(Mul, 2**x, 2**y) + assert x**2*x**3 == x**5 + assert 2**x*3**x == 6**x + assert x**(y)*x**(2*y) == x**(3*y) + assert sqrt(2)*sqrt(2) == 2 + assert 2**x*2**(2*x) == 2**(3*x) + assert sqrt(2)*2**Rational(1, 4)*5**Rational(3, 4) == 10**Rational(3, 4) + assert (x**(-log(5)/log(3))*x)/(x*x**( - log(5)/log(3))) == sympify(1) + + +def test_Mul_is_integer(): + k = Symbol('k', integer=True) + n = Symbol('n', integer=True) + nr = Symbol('nr', rational=False) + ir = Symbol('ir', irrational=True) + nz = Symbol('nz', integer=True, zero=False) + e = Symbol('e', even=True) + o = Symbol('o', odd=True) + i2 = Symbol('2', prime=True, even=True) + + assert (k/3).is_integer is None + assert (nz/3).is_integer is None + assert (nr/3).is_integer is False + assert (ir/3).is_integer is False + assert (x*k*n).is_integer is None + assert (e/2).is_integer is True + assert (e**2/2).is_integer is True + assert (2/k).is_integer is None + assert (2/k**2).is_integer is None + assert ((-1)**k*n).is_integer is True + assert (3*k*e/2).is_integer is True + assert (2*k*e/3).is_integer is None + assert (e/o).is_integer is None + assert (o/e).is_integer is False + assert (o/i2).is_integer is False + assert Mul(k, 1/k, evaluate=False).is_integer is None + assert Mul(2., S.Half, evaluate=False).is_integer is None + assert (2*sqrt(k)).is_integer is None + assert (2*k**n).is_integer is None + + s = 2**2**2**Pow(2, 1000, evaluate=False) + m = Mul(s, s, evaluate=False) + assert m.is_integer + + # broken in 1.6 and before, see #20161 + xq = Symbol('xq', rational=True) + yq = Symbol('yq', rational=True) + assert (xq*yq).is_integer is None + e_20161 = Mul(-1,Mul(1,Pow(2,-1,evaluate=False),evaluate=False),evaluate=False) + assert e_20161.is_integer is not True # expand(e_20161) -> -1/2, but no need to see that in the assumption without evaluation + + +def test_Add_Mul_is_integer(): + x = Symbol('x') + + k = Symbol('k', integer=True) + n = Symbol('n', integer=True) + nk = Symbol('nk', integer=False) + nr = Symbol('nr', rational=False) + nz = Symbol('nz', integer=True, zero=False) + + assert (-nk).is_integer is None + assert (-nr).is_integer is False + assert (2*k).is_integer is True + assert (-k).is_integer is True + + assert (k + nk).is_integer is False + assert (k + n).is_integer is True + assert (k + x).is_integer is None + assert (k + n*x).is_integer is None + assert (k + n/3).is_integer is None + assert (k + nz/3).is_integer is None + assert (k + nr/3).is_integer is False + + assert ((1 + sqrt(3))*(-sqrt(3) + 1)).is_integer is not False + assert (1 + (1 + sqrt(3))*(-sqrt(3) + 1)).is_integer is not False + + +def test_Add_Mul_is_finite(): + x = Symbol('x', extended_real=True, finite=False) + + assert sin(x).is_finite is True + assert (x*sin(x)).is_finite is None + assert (x*atan(x)).is_finite is False + assert (1024*sin(x)).is_finite is True + assert (sin(x)*exp(x)).is_finite is None + assert (sin(x)*cos(x)).is_finite is True + assert (x*sin(x)*exp(x)).is_finite is None + + assert (sin(x) - 67).is_finite is True + assert (sin(x) + exp(x)).is_finite is not True + assert (1 + x).is_finite is False + assert (1 + x**2 + (1 + x)*(1 - x)).is_finite is None + assert (sqrt(2)*(1 + x)).is_finite is False + assert (sqrt(2)*(1 + x)*(1 - x)).is_finite is False + + +def test_Mul_is_even_odd(): + x = Symbol('x', integer=True) + y = Symbol('y', integer=True) + + k = Symbol('k', odd=True) + n = Symbol('n', odd=True) + m = Symbol('m', even=True) + + assert (2*x).is_even is True + assert (2*x).is_odd is False + + assert (3*x).is_even is None + assert (3*x).is_odd is None + + assert (k/3).is_integer is None + assert (k/3).is_even is None + assert (k/3).is_odd is None + + assert (2*n).is_even is True + assert (2*n).is_odd is False + + assert (2*m).is_even is True + assert (2*m).is_odd is False + + assert (-n).is_even is False + assert (-n).is_odd is True + + assert (k*n).is_even is False + assert (k*n).is_odd is True + + assert (k*m).is_even is True + assert (k*m).is_odd is False + + assert (k*n*m).is_even is True + assert (k*n*m).is_odd is False + + assert (k*m*x).is_even is True + assert (k*m*x).is_odd is False + + # issue 6791: + assert (x/2).is_integer is None + assert (k/2).is_integer is False + assert (m/2).is_integer is True + + assert (x*y).is_even is None + assert (x*x).is_even is None + assert (x*(x + k)).is_even is True + assert (x*(x + m)).is_even is None + + assert (x*y).is_odd is None + assert (x*x).is_odd is None + assert (x*(x + k)).is_odd is False + assert (x*(x + m)).is_odd is None + + # issue 8648 + assert (m**2/2).is_even + assert (m**2/3).is_even is False + assert (2/m**2).is_odd is False + assert (2/m).is_odd is None + + +@XFAIL +def test_evenness_in_ternary_integer_product_with_odd(): + # Tests that oddness inference is independent of term ordering. + # Term ordering at the point of testing depends on SymPy's symbol order, so + # we try to force a different order by modifying symbol names. + x = Symbol('x', integer=True) + y = Symbol('y', integer=True) + k = Symbol('k', odd=True) + assert (x*y*(y + k)).is_even is True + assert (y*x*(x + k)).is_even is True + + +def test_evenness_in_ternary_integer_product_with_even(): + x = Symbol('x', integer=True) + y = Symbol('y', integer=True) + m = Symbol('m', even=True) + assert (x*y*(y + m)).is_even is None + + +@XFAIL +def test_oddness_in_ternary_integer_product_with_odd(): + # Tests that oddness inference is independent of term ordering. + # Term ordering at the point of testing depends on SymPy's symbol order, so + # we try to force a different order by modifying symbol names. + x = Symbol('x', integer=True) + y = Symbol('y', integer=True) + k = Symbol('k', odd=True) + assert (x*y*(y + k)).is_odd is False + assert (y*x*(x + k)).is_odd is False + + +def test_oddness_in_ternary_integer_product_with_even(): + x = Symbol('x', integer=True) + y = Symbol('y', integer=True) + m = Symbol('m', even=True) + assert (x*y*(y + m)).is_odd is None + + +def test_Mul_is_rational(): + x = Symbol('x') + n = Symbol('n', integer=True) + m = Symbol('m', integer=True, nonzero=True) + + assert (n/m).is_rational is True + assert (x/pi).is_rational is None + assert (x/n).is_rational is None + assert (m/pi).is_rational is False + + r = Symbol('r', rational=True) + assert (pi*r).is_rational is None + + # issue 8008 + z = Symbol('z', zero=True) + i = Symbol('i', imaginary=True) + assert (z*i).is_rational is True + bi = Symbol('i', imaginary=True, finite=True) + assert (z*bi).is_zero is True + + +def test_Add_is_rational(): + x = Symbol('x') + n = Symbol('n', rational=True) + m = Symbol('m', rational=True) + + assert (n + m).is_rational is True + assert (x + pi).is_rational is None + assert (x + n).is_rational is None + assert (n + pi).is_rational is False + + +def test_Add_is_even_odd(): + x = Symbol('x', integer=True) + + k = Symbol('k', odd=True) + n = Symbol('n', odd=True) + m = Symbol('m', even=True) + + assert (k + 7).is_even is True + assert (k + 7).is_odd is False + + assert (-k + 7).is_even is True + assert (-k + 7).is_odd is False + + assert (k - 12).is_even is False + assert (k - 12).is_odd is True + + assert (-k - 12).is_even is False + assert (-k - 12).is_odd is True + + assert (k + n).is_even is True + assert (k + n).is_odd is False + + assert (k + m).is_even is False + assert (k + m).is_odd is True + + assert (k + n + m).is_even is True + assert (k + n + m).is_odd is False + + assert (k + n + x + m).is_even is None + assert (k + n + x + m).is_odd is None + + +def test_Mul_is_negative_positive(): + x = Symbol('x', real=True) + y = Symbol('y', extended_real=False, complex=True) + z = Symbol('z', zero=True) + + e = 2*z + assert e.is_Mul and e.is_positive is False and e.is_negative is False + + neg = Symbol('neg', negative=True) + pos = Symbol('pos', positive=True) + nneg = Symbol('nneg', nonnegative=True) + npos = Symbol('npos', nonpositive=True) + + assert neg.is_negative is True + assert (-neg).is_negative is False + assert (2*neg).is_negative is True + + assert (2*pos)._eval_is_extended_negative() is False + assert (2*pos).is_negative is False + + assert pos.is_negative is False + assert (-pos).is_negative is True + assert (2*pos).is_negative is False + + assert (pos*neg).is_negative is True + assert (2*pos*neg).is_negative is True + assert (-pos*neg).is_negative is False + assert (pos*neg*y).is_negative is False # y.is_real=F; !real -> !neg + + assert nneg.is_negative is False + assert (-nneg).is_negative is None + assert (2*nneg).is_negative is False + + assert npos.is_negative is None + assert (-npos).is_negative is False + assert (2*npos).is_negative is None + + assert (nneg*npos).is_negative is None + + assert (neg*nneg).is_negative is None + assert (neg*npos).is_negative is False + + assert (pos*nneg).is_negative is False + assert (pos*npos).is_negative is None + + assert (npos*neg*nneg).is_negative is False + assert (npos*pos*nneg).is_negative is None + + assert (-npos*neg*nneg).is_negative is None + assert (-npos*pos*nneg).is_negative is False + + assert (17*npos*neg*nneg).is_negative is False + assert (17*npos*pos*nneg).is_negative is None + + assert (neg*npos*pos*nneg).is_negative is False + + assert (x*neg).is_negative is None + assert (nneg*npos*pos*x*neg).is_negative is None + + assert neg.is_positive is False + assert (-neg).is_positive is True + assert (2*neg).is_positive is False + + assert pos.is_positive is True + assert (-pos).is_positive is False + assert (2*pos).is_positive is True + + assert (pos*neg).is_positive is False + assert (2*pos*neg).is_positive is False + assert (-pos*neg).is_positive is True + assert (-pos*neg*y).is_positive is False # y.is_real=F; !real -> !neg + + assert nneg.is_positive is None + assert (-nneg).is_positive is False + assert (2*nneg).is_positive is None + + assert npos.is_positive is False + assert (-npos).is_positive is None + assert (2*npos).is_positive is False + + assert (nneg*npos).is_positive is False + + assert (neg*nneg).is_positive is False + assert (neg*npos).is_positive is None + + assert (pos*nneg).is_positive is None + assert (pos*npos).is_positive is False + + assert (npos*neg*nneg).is_positive is None + assert (npos*pos*nneg).is_positive is False + + assert (-npos*neg*nneg).is_positive is False + assert (-npos*pos*nneg).is_positive is None + + assert (17*npos*neg*nneg).is_positive is None + assert (17*npos*pos*nneg).is_positive is False + + assert (neg*npos*pos*nneg).is_positive is None + + assert (x*neg).is_positive is None + assert (nneg*npos*pos*x*neg).is_positive is None + + +def test_Mul_is_negative_positive_2(): + a = Symbol('a', nonnegative=True) + b = Symbol('b', nonnegative=True) + c = Symbol('c', nonpositive=True) + d = Symbol('d', nonpositive=True) + + assert (a*b).is_nonnegative is True + assert (a*b).is_negative is False + assert (a*b).is_zero is None + assert (a*b).is_positive is None + + assert (c*d).is_nonnegative is True + assert (c*d).is_negative is False + assert (c*d).is_zero is None + assert (c*d).is_positive is None + + assert (a*c).is_nonpositive is True + assert (a*c).is_positive is False + assert (a*c).is_zero is None + assert (a*c).is_negative is None + + +def test_Mul_is_nonpositive_nonnegative(): + x = Symbol('x', real=True) + + k = Symbol('k', negative=True) + n = Symbol('n', positive=True) + u = Symbol('u', nonnegative=True) + v = Symbol('v', nonpositive=True) + + assert k.is_nonpositive is True + assert (-k).is_nonpositive is False + assert (2*k).is_nonpositive is True + + assert n.is_nonpositive is False + assert (-n).is_nonpositive is True + assert (2*n).is_nonpositive is False + + assert (n*k).is_nonpositive is True + assert (2*n*k).is_nonpositive is True + assert (-n*k).is_nonpositive is False + + assert u.is_nonpositive is None + assert (-u).is_nonpositive is True + assert (2*u).is_nonpositive is None + + assert v.is_nonpositive is True + assert (-v).is_nonpositive is None + assert (2*v).is_nonpositive is True + + assert (u*v).is_nonpositive is True + + assert (k*u).is_nonpositive is True + assert (k*v).is_nonpositive is None + + assert (n*u).is_nonpositive is None + assert (n*v).is_nonpositive is True + + assert (v*k*u).is_nonpositive is None + assert (v*n*u).is_nonpositive is True + + assert (-v*k*u).is_nonpositive is True + assert (-v*n*u).is_nonpositive is None + + assert (17*v*k*u).is_nonpositive is None + assert (17*v*n*u).is_nonpositive is True + + assert (k*v*n*u).is_nonpositive is None + + assert (x*k).is_nonpositive is None + assert (u*v*n*x*k).is_nonpositive is None + + assert k.is_nonnegative is False + assert (-k).is_nonnegative is True + assert (2*k).is_nonnegative is False + + assert n.is_nonnegative is True + assert (-n).is_nonnegative is False + assert (2*n).is_nonnegative is True + + assert (n*k).is_nonnegative is False + assert (2*n*k).is_nonnegative is False + assert (-n*k).is_nonnegative is True + + assert u.is_nonnegative is True + assert (-u).is_nonnegative is None + assert (2*u).is_nonnegative is True + + assert v.is_nonnegative is None + assert (-v).is_nonnegative is True + assert (2*v).is_nonnegative is None + + assert (u*v).is_nonnegative is None + + assert (k*u).is_nonnegative is None + assert (k*v).is_nonnegative is True + + assert (n*u).is_nonnegative is True + assert (n*v).is_nonnegative is None + + assert (v*k*u).is_nonnegative is True + assert (v*n*u).is_nonnegative is None + + assert (-v*k*u).is_nonnegative is None + assert (-v*n*u).is_nonnegative is True + + assert (17*v*k*u).is_nonnegative is True + assert (17*v*n*u).is_nonnegative is None + + assert (k*v*n*u).is_nonnegative is True + + assert (x*k).is_nonnegative is None + assert (u*v*n*x*k).is_nonnegative is None + + +def test_Add_is_negative_positive(): + x = Symbol('x', real=True) + + k = Symbol('k', negative=True) + n = Symbol('n', positive=True) + u = Symbol('u', nonnegative=True) + v = Symbol('v', nonpositive=True) + + assert (k - 2).is_negative is True + assert (k + 17).is_negative is None + assert (-k - 5).is_negative is None + assert (-k + 123).is_negative is False + + assert (k - n).is_negative is True + assert (k + n).is_negative is None + assert (-k - n).is_negative is None + assert (-k + n).is_negative is False + + assert (k - n - 2).is_negative is True + assert (k + n + 17).is_negative is None + assert (-k - n - 5).is_negative is None + assert (-k + n + 123).is_negative is False + + assert (-2*k + 123*n + 17).is_negative is False + + assert (k + u).is_negative is None + assert (k + v).is_negative is True + assert (n + u).is_negative is False + assert (n + v).is_negative is None + + assert (u - v).is_negative is False + assert (u + v).is_negative is None + assert (-u - v).is_negative is None + assert (-u + v).is_negative is None + + assert (u - v + n + 2).is_negative is False + assert (u + v + n + 2).is_negative is None + assert (-u - v + n + 2).is_negative is None + assert (-u + v + n + 2).is_negative is None + + assert (k + x).is_negative is None + assert (k + x - n).is_negative is None + + assert (k - 2).is_positive is False + assert (k + 17).is_positive is None + assert (-k - 5).is_positive is None + assert (-k + 123).is_positive is True + + assert (k - n).is_positive is False + assert (k + n).is_positive is None + assert (-k - n).is_positive is None + assert (-k + n).is_positive is True + + assert (k - n - 2).is_positive is False + assert (k + n + 17).is_positive is None + assert (-k - n - 5).is_positive is None + assert (-k + n + 123).is_positive is True + + assert (-2*k + 123*n + 17).is_positive is True + + assert (k + u).is_positive is None + assert (k + v).is_positive is False + assert (n + u).is_positive is True + assert (n + v).is_positive is None + + assert (u - v).is_positive is None + assert (u + v).is_positive is None + assert (-u - v).is_positive is None + assert (-u + v).is_positive is False + + assert (u - v - n - 2).is_positive is None + assert (u + v - n - 2).is_positive is None + assert (-u - v - n - 2).is_positive is None + assert (-u + v - n - 2).is_positive is False + + assert (n + x).is_positive is None + assert (n + x - k).is_positive is None + + z = (-3 - sqrt(5) + (-sqrt(10)/2 - sqrt(2)/2)**2) + assert z.is_zero + z = sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) - sqrt(10 + 6*sqrt(3)) + assert z.is_zero + + +def test_Add_is_nonpositive_nonnegative(): + x = Symbol('x', real=True) + + k = Symbol('k', negative=True) + n = Symbol('n', positive=True) + u = Symbol('u', nonnegative=True) + v = Symbol('v', nonpositive=True) + + assert (u - 2).is_nonpositive is None + assert (u + 17).is_nonpositive is False + assert (-u - 5).is_nonpositive is True + assert (-u + 123).is_nonpositive is None + + assert (u - v).is_nonpositive is None + assert (u + v).is_nonpositive is None + assert (-u - v).is_nonpositive is None + assert (-u + v).is_nonpositive is True + + assert (u - v - 2).is_nonpositive is None + assert (u + v + 17).is_nonpositive is None + assert (-u - v - 5).is_nonpositive is None + assert (-u + v - 123).is_nonpositive is True + + assert (-2*u + 123*v - 17).is_nonpositive is True + + assert (k + u).is_nonpositive is None + assert (k + v).is_nonpositive is True + assert (n + u).is_nonpositive is False + assert (n + v).is_nonpositive is None + + assert (k - n).is_nonpositive is True + assert (k + n).is_nonpositive is None + assert (-k - n).is_nonpositive is None + assert (-k + n).is_nonpositive is False + + assert (k - n + u + 2).is_nonpositive is None + assert (k + n + u + 2).is_nonpositive is None + assert (-k - n + u + 2).is_nonpositive is None + assert (-k + n + u + 2).is_nonpositive is False + + assert (u + x).is_nonpositive is None + assert (v - x - n).is_nonpositive is None + + assert (u - 2).is_nonnegative is None + assert (u + 17).is_nonnegative is True + assert (-u - 5).is_nonnegative is False + assert (-u + 123).is_nonnegative is None + + assert (u - v).is_nonnegative is True + assert (u + v).is_nonnegative is None + assert (-u - v).is_nonnegative is None + assert (-u + v).is_nonnegative is None + + assert (u - v + 2).is_nonnegative is True + assert (u + v + 17).is_nonnegative is None + assert (-u - v - 5).is_nonnegative is None + assert (-u + v - 123).is_nonnegative is False + + assert (2*u - 123*v + 17).is_nonnegative is True + + assert (k + u).is_nonnegative is None + assert (k + v).is_nonnegative is False + assert (n + u).is_nonnegative is True + assert (n + v).is_nonnegative is None + + assert (k - n).is_nonnegative is False + assert (k + n).is_nonnegative is None + assert (-k - n).is_nonnegative is None + assert (-k + n).is_nonnegative is True + + assert (k - n - u - 2).is_nonnegative is False + assert (k + n - u - 2).is_nonnegative is None + assert (-k - n - u - 2).is_nonnegative is None + assert (-k + n - u - 2).is_nonnegative is None + + assert (u - x).is_nonnegative is None + assert (v + x + n).is_nonnegative is None + + +def test_Pow_is_integer(): + x = Symbol('x') + + k = Symbol('k', integer=True) + n = Symbol('n', integer=True, nonnegative=True) + m = Symbol('m', integer=True, positive=True) + + assert (k**2).is_integer is True + assert (k**(-2)).is_integer is None + assert ((m + 1)**(-2)).is_integer is False + assert (m**(-1)).is_integer is None # issue 8580 + + assert (2**k).is_integer is None + assert (2**(-k)).is_integer is None + + assert (2**n).is_integer is True + assert (2**(-n)).is_integer is None + + assert (2**m).is_integer is True + assert (2**(-m)).is_integer is False + + assert (x**2).is_integer is None + assert (2**x).is_integer is None + + assert (k**n).is_integer is True + assert (k**(-n)).is_integer is None + + assert (k**x).is_integer is None + assert (x**k).is_integer is None + + assert (k**(n*m)).is_integer is True + assert (k**(-n*m)).is_integer is None + + assert sqrt(3).is_integer is False + assert sqrt(.3).is_integer is False + assert Pow(3, 2, evaluate=False).is_integer is True + assert Pow(3, 0, evaluate=False).is_integer is True + assert Pow(3, -2, evaluate=False).is_integer is False + assert Pow(S.Half, 3, evaluate=False).is_integer is False + # decided by re-evaluating + assert Pow(3, S.Half, evaluate=False).is_integer is False + assert Pow(3, S.Half, evaluate=False).is_integer is False + assert Pow(4, S.Half, evaluate=False).is_integer is True + assert Pow(S.Half, -2, evaluate=False).is_integer is True + + assert ((-1)**k).is_integer + + # issue 8641 + x = Symbol('x', real=True, integer=False) + assert (x**2).is_integer is None + + # issue 10458 + x = Symbol('x', positive=True) + assert (1/(x + 1)).is_integer is False + assert (1/(-x - 1)).is_integer is False + assert (-1/(x + 1)).is_integer is False + # issue 23287 + assert (x**2/2).is_integer is None + + # issue 8648-like + k = Symbol('k', even=True) + assert (k**3/2).is_integer + assert (k**3/8).is_integer + assert (k**3/16).is_integer is None + assert (2/k).is_integer is None + assert (2/k**2).is_integer is False + o = Symbol('o', odd=True) + assert (k/o).is_integer is None + o = Symbol('o', odd=True, prime=True) + assert (k/o).is_integer is False + + +def test_Pow_is_real(): + x = Symbol('x', real=True) + y = Symbol('y', positive=True) + + assert (x**2).is_real is True + assert (x**3).is_real is True + assert (x**x).is_real is None + assert (y**x).is_real is True + + assert (x**Rational(1, 3)).is_real is None + assert (y**Rational(1, 3)).is_real is True + + assert sqrt(-1 - sqrt(2)).is_real is False + + i = Symbol('i', imaginary=True) + assert (i**i).is_real is None + assert (I**i).is_extended_real is True + assert ((-I)**i).is_extended_real is True + assert (2**i).is_real is None # (2**(pi/log(2) * I)) is real, 2**I is not + assert (2**I).is_real is False + assert (2**-I).is_real is False + assert (i**2).is_extended_real is True + assert (i**3).is_extended_real is False + assert (i**x).is_real is None # could be (-I)**(2/3) + e = Symbol('e', even=True) + o = Symbol('o', odd=True) + k = Symbol('k', integer=True) + assert (i**e).is_extended_real is True + assert (i**o).is_extended_real is False + assert (i**k).is_real is None + assert (i**(4*k)).is_extended_real is True + + x = Symbol("x", nonnegative=True) + y = Symbol("y", nonnegative=True) + assert im(x**y).expand(complex=True) is S.Zero + assert (x**y).is_real is True + i = Symbol('i', imaginary=True) + assert (exp(i)**I).is_extended_real is True + assert log(exp(i)).is_imaginary is None # i could be 2*pi*I + c = Symbol('c', complex=True) + assert log(c).is_real is None # c could be 0 or 2, too + assert log(exp(c)).is_real is None # log(0), log(E), ... + n = Symbol('n', negative=False) + assert log(n).is_real is None + n = Symbol('n', nonnegative=True) + assert log(n).is_real is None + + assert sqrt(-I).is_real is False # issue 7843 + + i = Symbol('i', integer=True) + assert (1/(i-1)).is_real is None + assert (1/(i-1)).is_extended_real is None + + # test issue 20715 + from sympy.core.parameters import evaluate + x = S(-1) + with evaluate(False): + assert x.is_negative is True + + f = Pow(x, -1) + with evaluate(False): + assert f.is_imaginary is False + + +def test_real_Pow(): + k = Symbol('k', integer=True, nonzero=True) + assert (k**(I*pi/log(k))).is_real + + +def test_Pow_is_finite(): + xe = Symbol('xe', extended_real=True) + xr = Symbol('xr', real=True) + p = Symbol('p', positive=True) + n = Symbol('n', negative=True) + i = Symbol('i', integer=True) + + assert (xe**2).is_finite is None # xe could be oo + assert (xr**2).is_finite is True + + assert (xe**xe).is_finite is None + assert (xr**xe).is_finite is None + assert (xe**xr).is_finite is None + # FIXME: The line below should be True rather than None + # assert (xr**xr).is_finite is True + assert (xr**xr).is_finite is None + + assert (p**xe).is_finite is None + assert (p**xr).is_finite is True + + assert (n**xe).is_finite is None + assert (n**xr).is_finite is True + + assert (sin(xe)**2).is_finite is True + assert (sin(xr)**2).is_finite is True + + assert (sin(xe)**xe).is_finite is None # xe, xr could be -pi + assert (sin(xr)**xr).is_finite is None + + # FIXME: Should the line below be True rather than None? + assert (sin(xe)**exp(xe)).is_finite is None + assert (sin(xr)**exp(xr)).is_finite is True + + assert (1/sin(xe)).is_finite is None # if zero, no, otherwise yes + assert (1/sin(xr)).is_finite is None + + assert (1/exp(xe)).is_finite is None # xe could be -oo + assert (1/exp(xr)).is_finite is True + + assert (1/S.Pi).is_finite is True + + assert (1/(i-1)).is_finite is None + + +def test_Pow_is_even_odd(): + x = Symbol('x') + + k = Symbol('k', even=True) + n = Symbol('n', odd=True) + m = Symbol('m', integer=True, nonnegative=True) + p = Symbol('p', integer=True, positive=True) + + assert ((-1)**n).is_odd + assert ((-1)**k).is_odd + assert ((-1)**(m - p)).is_odd + + assert (k**2).is_even is True + assert (n**2).is_even is False + assert (2**k).is_even is None + assert (x**2).is_even is None + + assert (k**m).is_even is None + assert (n**m).is_even is False + + assert (k**p).is_even is True + assert (n**p).is_even is False + + assert (m**k).is_even is None + assert (p**k).is_even is None + + assert (m**n).is_even is None + assert (p**n).is_even is None + + assert (k**x).is_even is None + assert (n**x).is_even is None + + assert (k**2).is_odd is False + assert (n**2).is_odd is True + assert (3**k).is_odd is None + + assert (k**m).is_odd is None + assert (n**m).is_odd is True + + assert (k**p).is_odd is False + assert (n**p).is_odd is True + + assert (m**k).is_odd is None + assert (p**k).is_odd is None + + assert (m**n).is_odd is None + assert (p**n).is_odd is None + + assert (k**x).is_odd is None + assert (n**x).is_odd is None + + +def test_Pow_is_negative_positive(): + r = Symbol('r', real=True) + + k = Symbol('k', integer=True, positive=True) + n = Symbol('n', even=True) + m = Symbol('m', odd=True) + + x = Symbol('x') + + assert (2**r).is_positive is True + assert ((-2)**r).is_positive is None + assert ((-2)**n).is_positive is True + assert ((-2)**m).is_positive is False + + assert (k**2).is_positive is True + assert (k**(-2)).is_positive is True + + assert (k**r).is_positive is True + assert ((-k)**r).is_positive is None + assert ((-k)**n).is_positive is True + assert ((-k)**m).is_positive is False + + assert (2**r).is_negative is False + assert ((-2)**r).is_negative is None + assert ((-2)**n).is_negative is False + assert ((-2)**m).is_negative is True + + assert (k**2).is_negative is False + assert (k**(-2)).is_negative is False + + assert (k**r).is_negative is False + assert ((-k)**r).is_negative is None + assert ((-k)**n).is_negative is False + assert ((-k)**m).is_negative is True + + assert (2**x).is_positive is None + assert (2**x).is_negative is None + + +def test_Pow_is_zero(): + z = Symbol('z', zero=True) + e = z**2 + assert e.is_zero + assert e.is_positive is False + assert e.is_negative is False + + assert Pow(0, 0, evaluate=False).is_zero is False + assert Pow(0, 3, evaluate=False).is_zero + assert Pow(0, oo, evaluate=False).is_zero + assert Pow(0, -3, evaluate=False).is_zero is False + assert Pow(0, -oo, evaluate=False).is_zero is False + assert Pow(2, 2, evaluate=False).is_zero is False + + a = Symbol('a', zero=False) + assert Pow(a, 3).is_zero is False # issue 7965 + + assert Pow(2, oo, evaluate=False).is_zero is False + assert Pow(2, -oo, evaluate=False).is_zero + assert Pow(S.Half, oo, evaluate=False).is_zero + assert Pow(S.Half, -oo, evaluate=False).is_zero is False + + # All combinations of real/complex base/exponent + h = S.Half + T = True + F = False + N = None + + pow_iszero = [ + ['**', 0, h, 1, 2, -h, -1,-2,-2*I,-I/2,I/2,1+I,oo,-oo,zoo], + [ 0, F, T, T, T, F, F, F, F, F, F, N, T, F, N], + [ h, F, F, F, F, F, F, F, F, F, F, F, T, F, N], + [ 1, F, F, F, F, F, F, F, F, F, F, F, F, F, N], + [ 2, F, F, F, F, F, F, F, F, F, F, F, F, T, N], + [ -h, F, F, F, F, F, F, F, F, F, F, F, T, F, N], + [ -1, F, F, F, F, F, F, F, F, F, F, F, F, F, N], + [ -2, F, F, F, F, F, F, F, F, F, F, F, F, T, N], + [-2*I, F, F, F, F, F, F, F, F, F, F, F, F, T, N], + [-I/2, F, F, F, F, F, F, F, F, F, F, F, T, F, N], + [ I/2, F, F, F, F, F, F, F, F, F, F, F, T, F, N], + [ 1+I, F, F, F, F, F, F, F, F, F, F, F, F, T, N], + [ oo, F, F, F, F, T, T, T, F, F, F, F, F, T, N], + [ -oo, F, F, F, F, T, T, T, F, F, F, F, F, T, N], + [ zoo, F, F, F, F, T, T, T, N, N, N, N, F, T, N] + ] + + def test_table(table): + n = len(table[0]) + for row in range(1, n): + base = table[row][0] + for col in range(1, n): + exp = table[0][col] + is_zero = table[row][col] + # The actual test here: + assert Pow(base, exp, evaluate=False).is_zero is is_zero + + test_table(pow_iszero) + + # A zero symbol... + zo, zo2 = symbols('zo, zo2', zero=True) + + # All combinations of finite symbols + zf, zf2 = symbols('zf, zf2', finite=True) + wf, wf2 = symbols('wf, wf2', nonzero=True) + xf, xf2 = symbols('xf, xf2', real=True) + yf, yf2 = symbols('yf, yf2', nonzero=True) + af, af2 = symbols('af, af2', positive=True) + bf, bf2 = symbols('bf, bf2', nonnegative=True) + cf, cf2 = symbols('cf, cf2', negative=True) + df, df2 = symbols('df, df2', nonpositive=True) + + # Without finiteness: + zi, zi2 = symbols('zi, zi2') + wi, wi2 = symbols('wi, wi2', zero=False) + xi, xi2 = symbols('xi, xi2', extended_real=True) + yi, yi2 = symbols('yi, yi2', zero=False, extended_real=True) + ai, ai2 = symbols('ai, ai2', extended_positive=True) + bi, bi2 = symbols('bi, bi2', extended_nonnegative=True) + ci, ci2 = symbols('ci, ci2', extended_negative=True) + di, di2 = symbols('di, di2', extended_nonpositive=True) + + pow_iszero_sym = [ + ['**',zo,wf,yf,af,cf,zf,xf,bf,df,zi,wi,xi,yi,ai,bi,ci,di], + [ zo2, F, N, N, T, F, N, N, N, F, N, N, N, N, T, N, F, F], + [ wf2, F, F, F, F, F, F, F, F, F, N, N, N, N, N, N, N, N], + [ yf2, F, F, F, F, F, F, F, F, F, N, N, N, N, N, N, N, N], + [ af2, F, F, F, F, F, F, F, F, F, N, N, N, N, N, N, N, N], + [ cf2, F, F, F, F, F, F, F, F, F, N, N, N, N, N, N, N, N], + [ zf2, N, N, N, N, F, N, N, N, N, N, N, N, N, N, N, N, N], + [ xf2, N, N, N, N, F, N, N, N, N, N, N, N, N, N, N, N, N], + [ bf2, N, N, N, N, F, N, N, N, N, N, N, N, N, N, N, N, N], + [ df2, N, N, N, N, F, N, N, N, N, N, N, N, N, N, N, N, N], + [ zi2, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N], + [ wi2, F, N, N, F, N, N, N, F, N, N, N, N, N, N, N, N, N], + [ xi2, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N], + [ yi2, F, N, N, F, N, N, N, F, N, N, N, N, N, N, N, N, N], + [ ai2, F, N, N, F, N, N, N, F, N, N, N, N, N, N, N, N, N], + [ bi2, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N], + [ ci2, F, N, N, F, N, N, N, F, N, N, N, N, N, N, N, N, N], + [ di2, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N] + ] + + test_table(pow_iszero_sym) + + # In some cases (x**x).is_zero is different from (x**y).is_zero even if y + # has the same assumptions as x. + assert (zo ** zo).is_zero is False + assert (wf ** wf).is_zero is False + assert (yf ** yf).is_zero is False + assert (af ** af).is_zero is False + assert (cf ** cf).is_zero is False + assert (zf ** zf).is_zero is None + assert (xf ** xf).is_zero is None + assert (bf ** bf).is_zero is False # None in table + assert (df ** df).is_zero is None + assert (zi ** zi).is_zero is None + assert (wi ** wi).is_zero is None + assert (xi ** xi).is_zero is None + assert (yi ** yi).is_zero is None + assert (ai ** ai).is_zero is False # None in table + assert (bi ** bi).is_zero is False # None in table + assert (ci ** ci).is_zero is None + assert (di ** di).is_zero is None + + +def test_Pow_is_nonpositive_nonnegative(): + x = Symbol('x', real=True) + + k = Symbol('k', integer=True, nonnegative=True) + l = Symbol('l', integer=True, positive=True) + n = Symbol('n', even=True) + m = Symbol('m', odd=True) + + assert (x**(4*k)).is_nonnegative is True + assert (2**x).is_nonnegative is True + assert ((-2)**x).is_nonnegative is None + assert ((-2)**n).is_nonnegative is True + assert ((-2)**m).is_nonnegative is False + + assert (k**2).is_nonnegative is True + assert (k**(-2)).is_nonnegative is None + assert (k**k).is_nonnegative is True + + assert (k**x).is_nonnegative is None # NOTE (0**x).is_real = U + assert (l**x).is_nonnegative is True + assert (l**x).is_positive is True + assert ((-k)**x).is_nonnegative is None + + assert ((-k)**m).is_nonnegative is None + + assert (2**x).is_nonpositive is False + assert ((-2)**x).is_nonpositive is None + assert ((-2)**n).is_nonpositive is False + assert ((-2)**m).is_nonpositive is True + + assert (k**2).is_nonpositive is None + assert (k**(-2)).is_nonpositive is None + + assert (k**x).is_nonpositive is None + assert ((-k)**x).is_nonpositive is None + assert ((-k)**n).is_nonpositive is None + + + assert (x**2).is_nonnegative is True + i = symbols('i', imaginary=True) + assert (i**2).is_nonpositive is True + assert (i**4).is_nonpositive is False + assert (i**3).is_nonpositive is False + assert (I**i).is_nonnegative is True + assert (exp(I)**i).is_nonnegative is True + + assert ((-l)**n).is_nonnegative is True + assert ((-l)**m).is_nonpositive is True + assert ((-k)**n).is_nonnegative is None + assert ((-k)**m).is_nonpositive is None + + +def test_Mul_is_imaginary_real(): + r = Symbol('r', real=True) + p = Symbol('p', positive=True) + i1 = Symbol('i1', imaginary=True) + i2 = Symbol('i2', imaginary=True) + x = Symbol('x') + + assert I.is_imaginary is True + assert I.is_real is False + assert (-I).is_imaginary is True + assert (-I).is_real is False + assert (3*I).is_imaginary is True + assert (3*I).is_real is False + assert (I*I).is_imaginary is False + assert (I*I).is_real is True + + e = (p + p*I) + j = Symbol('j', integer=True, zero=False) + assert (e**j).is_real is None + assert (e**(2*j)).is_real is None + assert (e**j).is_imaginary is None + assert (e**(2*j)).is_imaginary is None + + assert (e**-1).is_imaginary is False + assert (e**2).is_imaginary + assert (e**3).is_imaginary is False + assert (e**4).is_imaginary is False + assert (e**5).is_imaginary is False + assert (e**-1).is_real is False + assert (e**2).is_real is False + assert (e**3).is_real is False + assert (e**4).is_real is True + assert (e**5).is_real is False + assert (e**3).is_complex + + assert (r*i1).is_imaginary is None + assert (r*i1).is_real is None + + assert (x*i1).is_imaginary is None + assert (x*i1).is_real is None + + assert (i1*i2).is_imaginary is False + assert (i1*i2).is_real is True + + assert (r*i1*i2).is_imaginary is False + assert (r*i1*i2).is_real is True + + # Github's issue 5874: + nr = Symbol('nr', real=False, complex=True) # e.g. I or 1 + I + a = Symbol('a', real=True, nonzero=True) + b = Symbol('b', real=True) + assert (i1*nr).is_real is None + assert (a*nr).is_real is False + assert (b*nr).is_real is None + + ni = Symbol('ni', imaginary=False, complex=True) # e.g. 2 or 1 + I + a = Symbol('a', real=True, nonzero=True) + b = Symbol('b', real=True) + assert (i1*ni).is_real is False + assert (a*ni).is_real is None + assert (b*ni).is_real is None + + +def test_Mul_hermitian_antihermitian(): + xz, yz = symbols('xz, yz', zero=True, antihermitian=True) + xf, yf = symbols('xf, yf', hermitian=False, antihermitian=False, finite=True) + xh, yh = symbols('xh, yh', hermitian=True, antihermitian=False, nonzero=True) + xa, ya = symbols('xa, ya', hermitian=False, antihermitian=True, zero=False, finite=True) + assert (xz*xh).is_hermitian is True + assert (xz*xh).is_antihermitian is True + assert (xz*xa).is_hermitian is True + assert (xz*xa).is_antihermitian is True + assert (xf*yf).is_hermitian is None + assert (xf*yf).is_antihermitian is None + assert (xh*yh).is_hermitian is True + assert (xh*yh).is_antihermitian is False + assert (xh*ya).is_hermitian is False + assert (xh*ya).is_antihermitian is True + assert (xa*ya).is_hermitian is True + assert (xa*ya).is_antihermitian is False + + a = Symbol('a', hermitian=True, zero=False) + b = Symbol('b', hermitian=True) + c = Symbol('c', hermitian=False) + d = Symbol('d', antihermitian=True) + e1 = Mul(a, b, c, evaluate=False) + e2 = Mul(b, a, c, evaluate=False) + e3 = Mul(a, b, c, d, evaluate=False) + e4 = Mul(b, a, c, d, evaluate=False) + e5 = Mul(a, c, evaluate=False) + e6 = Mul(a, c, d, evaluate=False) + assert e1.is_hermitian is None + assert e2.is_hermitian is None + assert e1.is_antihermitian is None + assert e2.is_antihermitian is None + assert e3.is_antihermitian is None + assert e4.is_antihermitian is None + assert e5.is_antihermitian is None + assert e6.is_antihermitian is None + + +def test_Add_is_comparable(): + assert (x + y).is_comparable is False + assert (x + 1).is_comparable is False + assert (Rational(1, 3) - sqrt(8)).is_comparable is True + + +def test_Mul_is_comparable(): + assert (x*y).is_comparable is False + assert (x*2).is_comparable is False + assert (sqrt(2)*Rational(1, 3)).is_comparable is True + + +def test_Pow_is_comparable(): + assert (x**y).is_comparable is False + assert (x**2).is_comparable is False + assert (sqrt(Rational(1, 3))).is_comparable is True + + +def test_Add_is_positive_2(): + e = Rational(1, 3) - sqrt(8) + assert e.is_positive is False + assert e.is_negative is True + + e = pi - 1 + assert e.is_positive is True + assert e.is_negative is False + + +def test_Add_is_irrational(): + i = Symbol('i', irrational=True) + + assert i.is_irrational is True + assert i.is_rational is False + + assert (i + 1).is_irrational is True + assert (i + 1).is_rational is False + + +def test_Mul_is_irrational(): + expr = Mul(1, 2, 3, evaluate=False) + assert expr.is_irrational is False + expr = Mul(1, I, I, evaluate=False) + assert expr.is_rational is None # I * I = -1 but *no evaluation allowed* + # sqrt(2) * I * I = -sqrt(2) is irrational but + # this can't be determined without evaluating the + # expression and the eval_is routines shouldn't do that + expr = Mul(sqrt(2), I, I, evaluate=False) + assert expr.is_irrational is None + + +def test_issue_3531(): + # https://github.com/sympy/sympy/issues/3531 + # https://github.com/sympy/sympy/pull/18116 + class MightyNumeric(tuple): + __slots__ = () + + def __rtruediv__(self, other): + return "something" + + assert sympify(1)/MightyNumeric((1, 2)) == "something" + + +def test_issue_3531b(): + class Foo: + def __init__(self): + self.field = 1.0 + + def __mul__(self, other): + self.field = self.field * other + + def __rmul__(self, other): + self.field = other * self.field + f = Foo() + x = Symbol("x") + assert f*x == x*f + + +def test_bug3(): + a = Symbol("a") + b = Symbol("b", positive=True) + e = 2*a + b + f = b + 2*a + assert e == f + + +def test_suppressed_evaluation(): + a = Add(0, 3, 2, evaluate=False) + b = Mul(1, 3, 2, evaluate=False) + c = Pow(3, 2, evaluate=False) + assert a != 6 + assert a.func is Add + assert a.args == (0, 3, 2) + assert b != 6 + assert b.func is Mul + assert b.args == (1, 3, 2) + assert c != 9 + assert c.func is Pow + assert c.args == (3, 2) + + +def test_AssocOp_doit(): + a = Add(x,x, evaluate=False) + b = Mul(y,y, evaluate=False) + c = Add(b,b, evaluate=False) + d = Mul(a,a, evaluate=False) + assert c.doit(deep=False).func == Mul + assert c.doit(deep=False).args == (2,y,y) + assert c.doit().func == Mul + assert c.doit().args == (2, Pow(y,2)) + assert d.doit(deep=False).func == Pow + assert d.doit(deep=False).args == (a, 2*S.One) + assert d.doit().func == Mul + assert d.doit().args == (4*S.One, Pow(x,2)) + + +def test_Add_Mul_Expr_args(): + nonexpr = [Basic(), Poly(x, x), FiniteSet(x)] + for typ in [Add, Mul]: + for obj in nonexpr: + # The cache can mess with the stacklevel check + with warns(SymPyDeprecationWarning, test_stacklevel=False): + typ(obj, 1) + + +def test_Add_as_coeff_mul(): + # issue 5524. These should all be (1, self) + assert (x + 1).as_coeff_mul() == (1, (x + 1,)) + assert (x + 2).as_coeff_mul() == (1, (x + 2,)) + assert (x + 3).as_coeff_mul() == (1, (x + 3,)) + + assert (x - 1).as_coeff_mul() == (1, (x - 1,)) + assert (x - 2).as_coeff_mul() == (1, (x - 2,)) + assert (x - 3).as_coeff_mul() == (1, (x - 3,)) + + n = Symbol('n', integer=True) + assert (n + 1).as_coeff_mul() == (1, (n + 1,)) + assert (n + 2).as_coeff_mul() == (1, (n + 2,)) + assert (n + 3).as_coeff_mul() == (1, (n + 3,)) + + assert (n - 1).as_coeff_mul() == (1, (n - 1,)) + assert (n - 2).as_coeff_mul() == (1, (n - 2,)) + assert (n - 3).as_coeff_mul() == (1, (n - 3,)) + + +def test_Pow_as_coeff_mul_doesnt_expand(): + assert exp(x + y).as_coeff_mul() == (1, (exp(x + y),)) + assert exp(x + exp(x + y)) != exp(x + exp(x)*exp(y)) + +def test_issue_24751(): + expr = Add(-2, -3, evaluate=False) + expr1 = Add(-1, expr, evaluate=False) + assert int(expr1) == int((-3 - 2) - 1) + + +def test_issue_3514_18626(): + assert sqrt(S.Half) * sqrt(6) == 2 * sqrt(3)/2 + assert S.Half*sqrt(6)*sqrt(2) == sqrt(3) + assert sqrt(6)/2*sqrt(2) == sqrt(3) + assert sqrt(6)*sqrt(2)/2 == sqrt(3) + assert sqrt(8)**Rational(2, 3) == 2 + + +def test_make_args(): + assert Add.make_args(x) == (x,) + assert Mul.make_args(x) == (x,) + + assert Add.make_args(x*y*z) == (x*y*z,) + assert Mul.make_args(x*y*z) == (x*y*z).args + + assert Add.make_args(x + y + z) == (x + y + z).args + assert Mul.make_args(x + y + z) == (x + y + z,) + + assert Add.make_args((x + y)**z) == ((x + y)**z,) + assert Mul.make_args((x + y)**z) == ((x + y)**z,) + + +def test_issue_5126(): + assert (-2)**x*(-3)**x != 6**x + i = Symbol('i', integer=1) + assert (-2)**i*(-3)**i == 6**i + + +def test_Rational_as_content_primitive(): + c, p = S.One, S.Zero + assert (c*p).as_content_primitive() == (c, p) + c, p = S.Half, S.One + assert (c*p).as_content_primitive() == (c, p) + + +def test_Add_as_content_primitive(): + assert (x + 2).as_content_primitive() == (1, x + 2) + + assert (3*x + 2).as_content_primitive() == (1, 3*x + 2) + assert (3*x + 3).as_content_primitive() == (3, x + 1) + assert (3*x + 6).as_content_primitive() == (3, x + 2) + + assert (3*x + 2*y).as_content_primitive() == (1, 3*x + 2*y) + assert (3*x + 3*y).as_content_primitive() == (3, x + y) + assert (3*x + 6*y).as_content_primitive() == (3, x + 2*y) + + assert (3/x + 2*x*y*z**2).as_content_primitive() == (1, 3/x + 2*x*y*z**2) + assert (3/x + 3*x*y*z**2).as_content_primitive() == (3, 1/x + x*y*z**2) + assert (3/x + 6*x*y*z**2).as_content_primitive() == (3, 1/x + 2*x*y*z**2) + + assert (2*x/3 + 4*y/9).as_content_primitive() == \ + (Rational(2, 9), 3*x + 2*y) + assert (2*x/3 + 2.5*y).as_content_primitive() == \ + (Rational(1, 3), 2*x + 7.5*y) + + # the coefficient may sort to a position other than 0 + p = 3 + x + y + assert (2*p).expand().as_content_primitive() == (2, p) + assert (2.0*p).expand().as_content_primitive() == (1, 2.*p) + p *= -1 + assert (2*p).expand().as_content_primitive() == (2, p) + + +def test_Mul_as_content_primitive(): + assert (2*x).as_content_primitive() == (2, x) + assert (x*(2 + 2*x)).as_content_primitive() == (2, x*(1 + x)) + assert (x*(2 + 2*y)*(3*x + 3)**2).as_content_primitive() == \ + (18, x*(1 + y)*(x + 1)**2) + assert ((2 + 2*x)**2*(3 + 6*x) + S.Half).as_content_primitive() == \ + (S.Half, 24*(x + 1)**2*(2*x + 1) + 1) + + +def test_Pow_as_content_primitive(): + assert (x**y).as_content_primitive() == (1, x**y) + assert ((2*x + 2)**y).as_content_primitive() == \ + (1, (Mul(2, (x + 1), evaluate=False))**y) + assert ((2*x + 2)**3).as_content_primitive() == (8, (x + 1)**3) + + +def test_issue_5460(): + u = Mul(2, (1 + x), evaluate=False) + assert (2 + u).args == (2, u) + + +def test_product_irrational(): + assert (I*pi).is_irrational is False + # The following used to be deduced from the above bug: + assert (I*pi).is_positive is False + + +def test_issue_5919(): + assert (x/(y*(1 + y))).expand() == x/(y**2 + y) + + +def test_Mod(): + assert Mod(x, 1).func is Mod + assert pi % pi is S.Zero + assert Mod(5, 3) == 2 + assert Mod(-5, 3) == 1 + assert Mod(5, -3) == -1 + assert Mod(-5, -3) == -2 + assert type(Mod(3.2, 2, evaluate=False)) == Mod + assert 5 % x == Mod(5, x) + assert x % 5 == Mod(x, 5) + assert x % y == Mod(x, y) + assert (x % y).subs({x: 5, y: 3}) == 2 + assert Mod(nan, 1) is nan + assert Mod(1, nan) is nan + assert Mod(nan, nan) is nan + + assert Mod(0, x) == 0 + with raises(ZeroDivisionError): + Mod(x, 0) + + k = Symbol('k', integer=True) + m = Symbol('m', integer=True, positive=True) + assert (x**m % x).func is Mod + assert (k**(-m) % k).func is Mod + assert k**m % k == 0 + assert (-2*k)**m % k == 0 + + # Float handling + point3 = Float(3.3) % 1 + assert (x - 3.3) % 1 == Mod(1.*x + 1 - point3, 1) + assert Mod(-3.3, 1) == 1 - point3 + assert Mod(0.7, 1) == Float(0.7) + e = Mod(1.3, 1) + assert comp(e, .3) and e.is_Float + e = Mod(1.3, .7) + assert comp(e, .6) and e.is_Float + e = Mod(1.3, Rational(7, 10)) + assert comp(e, .6) and e.is_Float + e = Mod(Rational(13, 10), 0.7) + assert comp(e, .6) and e.is_Float + e = Mod(Rational(13, 10), Rational(7, 10)) + assert comp(e, .6) and e.is_Rational + + # check that sign is right + r2 = sqrt(2) + r3 = sqrt(3) + for i in [-r3, -r2, r2, r3]: + for j in [-r3, -r2, r2, r3]: + assert verify_numerically(i % j, i.n() % j.n()) + for _x in range(4): + for _y in range(9): + reps = [(x, _x), (y, _y)] + assert Mod(3*x + y, 9).subs(reps) == (3*_x + _y) % 9 + + # denesting + t = Symbol('t', real=True) + assert Mod(Mod(x, t), t) == Mod(x, t) + assert Mod(-Mod(x, t), t) == Mod(-x, t) + assert Mod(Mod(x, 2*t), t) == Mod(x, t) + assert Mod(-Mod(x, 2*t), t) == Mod(-x, t) + assert Mod(Mod(x, t), 2*t) == Mod(x, t) + assert Mod(-Mod(x, t), -2*t) == -Mod(x, t) + for i in [-4, -2, 2, 4]: + for j in [-4, -2, 2, 4]: + for k in range(4): + assert Mod(Mod(x, i), j).subs({x: k}) == (k % i) % j + assert Mod(-Mod(x, i), j).subs({x: k}) == -(k % i) % j + + # known difference + assert Mod(5*sqrt(2), sqrt(5)) == 5*sqrt(2) - 3*sqrt(5) + p = symbols('p', positive=True) + assert Mod(2, p + 3) == 2 + assert Mod(-2, p + 3) == p + 1 + assert Mod(2, -p - 3) == -p - 1 + assert Mod(-2, -p - 3) == -2 + assert Mod(p + 5, p + 3) == 2 + assert Mod(-p - 5, p + 3) == p + 1 + assert Mod(p + 5, -p - 3) == -p - 1 + assert Mod(-p - 5, -p - 3) == -2 + assert Mod(p + 1, p - 1).func is Mod + + # issue 27749 + n = symbols('n', integer=True, positive=True) + assert unchanged(Mod, 1, n) + n = symbols('n', prime=True) + assert Mod(1, n) == 1 + + # handling sums + assert (x + 3) % 1 == Mod(x, 1) + assert (x + 3.0) % 1 == Mod(1.*x, 1) + assert (x - S(33)/10) % 1 == Mod(x + S(7)/10, 1) + + a = Mod(.6*x + y, .3*y) + b = Mod(0.1*y + 0.6*x, 0.3*y) + # Test that a, b are equal, with 1e-14 accuracy in coefficients + eps = 1e-14 + assert abs((a.args[0] - b.args[0]).subs({x: 1, y: 1})) < eps + assert abs((a.args[1] - b.args[1]).subs({x: 1, y: 1})) < eps + + assert (x + 1) % x == 1 % x + assert (x + y) % x == y % x + assert (x + y + 2) % x == (y + 2) % x + assert (a + 3*x + 1) % (2*x) == Mod(a + x + 1, 2*x) + assert (12*x + 18*y) % (3*x) == 3*Mod(6*y, x) + + # gcd extraction + assert (-3*x) % (-2*y) == -Mod(3*x, 2*y) + assert (.6*pi) % (.3*x*pi) == 0.3*pi*Mod(2, x) + assert (.6*pi) % (.31*x*pi) == pi*Mod(0.6, 0.31*x) + assert (6*pi) % (.3*x*pi) == 0.3*pi*Mod(20, x) + assert (6*pi) % (.31*x*pi) == pi*Mod(6, 0.31*x) + assert (6*pi) % (.42*x*pi) == pi*Mod(6, 0.42*x) + assert (12*x) % (2*y) == 2*Mod(6*x, y) + assert (12*x) % (3*5*y) == 3*Mod(4*x, 5*y) + assert (12*x) % (15*x*y) == 3*x*Mod(4, 5*y) + assert (-2*pi) % (3*pi) == pi + assert (2*x + 2) % (x + 1) == 0 + assert (x*(x + 1)) % (x + 1) == (x + 1)*Mod(x, 1) + assert Mod(5.0*x, 0.1*y) == 0.1*Mod(50*x, y) + i = Symbol('i', integer=True) + assert (3*i*x) % (2*i*y) == i*Mod(3*x, 2*y) + assert Mod(4*i, 4) == 0 + + # issue 8677 + n = Symbol('n', integer=True, positive=True) + assert factorial(n) % n == 0 + assert factorial(n + 2) % n == 0 + assert (factorial(n + 4) % (n + 5)).func is Mod + + # Wilson's theorem + assert factorial(18042, evaluate=False) % 18043 == 18042 + p = Symbol('n', prime=True) + assert factorial(p - 1) % p == p - 1 + assert factorial(p - 1) % -p == -1 + assert (factorial(3, evaluate=False) % 4).doit() == 2 + n = Symbol('n', composite=True, odd=True) + assert factorial(n - 1) % n == 0 + + # symbolic with known parity + n = Symbol('n', even=True) + assert Mod(n, 2) == 0 + n = Symbol('n', odd=True) + assert Mod(n, 2) == 1 + + # issue 10963 + assert (x**6000%400).args[1] == 400 + + #issue 13543 + assert Mod(Mod(x + 1, 2) + 1, 2) == Mod(x, 2) + + x1 = Symbol('x1', integer=True) + assert Mod(Mod(x1 + 2, 4)*(x1 + 4), 4) == Mod(x1*(x1 + 2), 4) + assert Mod(Mod(x1 + 2, 4)*4, 4) == 0 + + # issue 15493 + i, j = symbols('i j', integer=True, positive=True) + assert Mod(3*i, 2) == Mod(i, 2) + assert Mod(8*i/j, 4) == 4*Mod(2*i/j, 1) + assert Mod(8*i, 4) == 0 + + # rewrite + assert Mod(x, y).rewrite(floor) == x - y*floor(x/y) + assert ((x - Mod(x, y))/y).rewrite(floor) == floor(x/y) + + # issue 21373 + from sympy.functions.elementary.hyperbolic import sinh + from sympy.functions.elementary.piecewise import Piecewise + + x_r, y_r = symbols('x_r y_r', real=True) + assert (Piecewise((x_r, y_r > x_r), (y_r, True)) / z) % 1 + expr = exp(sinh(Piecewise((x_r, y_r > x_r), (y_r, True)) / z)) + expr.subs({1: 1.0}) + sinh(Piecewise((x_r, y_r > x_r), (y_r, True)) * z ** -1.0).is_zero + + # issue 24215 + from sympy.abc import phi + assert Mod(4.0*Mod(phi, 1) , 2) == 2.0*(Mod(2*(Mod(phi, 1)), 1)) + + xi = symbols('x', integer=True) + assert unchanged(Mod, xi, 2) + assert Mod(3*xi, 2) == Mod(xi, 2) + assert unchanged(Mod, 3*x, 2) + + +def test_Mod_Pow(): + # modular exponentiation + assert isinstance(Mod(Pow(2, 2, evaluate=False), 3), Integer) + + assert Mod(Pow(4, 13, evaluate=False), 497) == Mod(Pow(4, 13), 497) + assert Mod(Pow(2, 10000000000, evaluate=False), 3) == 1 + assert Mod(Pow(32131231232, 9**10**6, evaluate=False),10**12) == \ + pow(32131231232,9**10**6,10**12) + assert Mod(Pow(33284959323, 123**999, evaluate=False),11**13) == \ + pow(33284959323,123**999,11**13) + assert Mod(Pow(78789849597, 333**555, evaluate=False),12**9) == \ + pow(78789849597,333**555,12**9) + + # modular nested exponentiation + expr = Pow(2, 2, evaluate=False) + expr = Pow(2, expr, evaluate=False) + assert Mod(expr, 3**10) == 16 + expr = Pow(2, expr, evaluate=False) + assert Mod(expr, 3**10) == 6487 + expr = Pow(2, expr, evaluate=False) + assert Mod(expr, 3**10) == 32191 + expr = Pow(2, expr, evaluate=False) + assert Mod(expr, 3**10) == 18016 + expr = Pow(2, expr, evaluate=False) + assert Mod(expr, 3**10) == 5137 + + expr = Pow(2, 2, evaluate=False) + expr = Pow(expr, 2, evaluate=False) + assert Mod(expr, 3**10) == 16 + expr = Pow(expr, 2, evaluate=False) + assert Mod(expr, 3**10) == 256 + expr = Pow(expr, 2, evaluate=False) + assert Mod(expr, 3**10) == 6487 + expr = Pow(expr, 2, evaluate=False) + assert Mod(expr, 3**10) == 38281 + expr = Pow(expr, 2, evaluate=False) + assert Mod(expr, 3**10) == 15928 + + expr = Pow(2, 2, evaluate=False) + expr = Pow(expr, expr, evaluate=False) + assert Mod(expr, 3**10) == 256 + expr = Pow(expr, expr, evaluate=False) + assert Mod(expr, 3**10) == 9229 + expr = Pow(expr, expr, evaluate=False) + assert Mod(expr, 3**10) == 25708 + expr = Pow(expr, expr, evaluate=False) + assert Mod(expr, 3**10) == 26608 + expr = Pow(expr, expr, evaluate=False) + # XXX This used to fail in a nondeterministic way because of overflow + # error. + assert Mod(expr, 3**10) == 1966 + + +def test_Mod_is_integer(): + p = Symbol('p', integer=True) + q1 = Symbol('q1', integer=True) + q2 = Symbol('q2', integer=True, nonzero=True) + assert Mod(x, y).is_integer is None + assert Mod(p, q1).is_integer is None + assert Mod(x, q2).is_integer is None + assert Mod(p, q2).is_integer + + +def test_Mod_is_nonposneg(): + n = Symbol('n', integer=True) + k = Symbol('k', integer=True, positive=True) + assert (n%3).is_nonnegative + assert Mod(n, -3).is_nonpositive + assert Mod(n, k).is_nonnegative + assert Mod(n, -k).is_nonpositive + assert Mod(k, n).is_nonnegative is None + + +def test_issue_6001(): + A = Symbol("A", commutative=False) + eq = A + A**2 + # it doesn't matter whether it's True or False; they should + # just all be the same + assert ( + eq.is_commutative == + (eq + 1).is_commutative == + (A + 1).is_commutative) + + B = Symbol("B", commutative=False) + # Although commutative terms could cancel we return True + # meaning "there are non-commutative symbols; aftersubstitution + # that definition can change, e.g. (A*B).subs(B,A**-1) -> 1 + assert (sqrt(2)*A).is_commutative is False + assert (sqrt(2)*A*B).is_commutative is False + + +def test_polar(): + from sympy.functions.elementary.complexes import polar_lift + p = Symbol('p', polar=True) + x = Symbol('x') + assert p.is_polar + assert x.is_polar is None + assert S.One.is_polar is None + assert (p**x).is_polar is True + assert (x**p).is_polar is None + assert ((2*p)**x).is_polar is True + assert (2*p).is_polar is True + assert (-2*p).is_polar is not True + assert (polar_lift(-2)*p).is_polar is True + + q = Symbol('q', polar=True) + assert (p*q)**2 == p**2 * q**2 + assert (2*q)**2 == 4 * q**2 + assert ((p*q)**x).expand() == p**x * q**x + + +def test_issue_6040(): + a, b = Pow(1, 2, evaluate=False), S.One + assert a != b + assert b != a + assert not (a == b) + assert not (b == a) + + +def test_issue_6082(): + # Comparison is symmetric + assert Basic.compare(Max(x, 1), Max(x, 2)) == \ + - Basic.compare(Max(x, 2), Max(x, 1)) + # Equal expressions compare equal + assert Basic.compare(Max(x, 1), Max(x, 1)) == 0 + # Basic subtypes (such as Max) compare different than standard types + assert Basic.compare(Max(1, x), frozenset((1, x))) != 0 + + +def test_issue_6077(): + assert x**2.0/x == x**1.0 + assert x/x**2.0 == x**-1.0 + assert x*x**2.0 == x**3.0 + assert x**1.5*x**2.5 == x**4.0 + + assert 2**(2.0*x)/2**x == 2**(1.0*x) + assert 2**x/2**(2.0*x) == 2**(-1.0*x) + assert 2**x*2**(2.0*x) == 2**(3.0*x) + assert 2**(1.5*x)*2**(2.5*x) == 2**(4.0*x) + + +def test_mul_flatten_oo(): + p = symbols('p', positive=True) + n, m = symbols('n,m', negative=True) + x_im = symbols('x_im', imaginary=True) + assert n*oo is -oo + assert n*m*oo is oo + assert p*oo is oo + assert x_im*oo != I*oo # i could be +/- 3*I -> +/-oo + + +def test_add_flatten(): + # see https://github.com/sympy/sympy/issues/2633#issuecomment-29545524 + a = oo + I*oo + b = oo - I*oo + assert a + b is nan + assert a - b is nan + # FIXME: This evaluates as: + # >>> 1/a + # 0*(oo + oo*I) + # which should not simplify to 0. Should be fixed in Pow.eval + #assert (1/a).simplify() == (1/b).simplify() == 0 + + a = Pow(2, 3, evaluate=False) + assert a + a == 16 + + +def test_issue_5160_6087_6089_6090(): + # issue 6087 + assert ((-2*x*y**y)**3.2).n(2) == (2**3.2*(-x*y**y)**3.2).n(2) + # issue 6089 + A, B, C = symbols('A,B,C', commutative=False) + assert (2.*B*C)**3 == 8.0*(B*C)**3 + assert (-2.*B*C)**3 == -8.0*(B*C)**3 + assert (-2*B*C)**2 == 4*(B*C)**2 + # issue 5160 + assert sqrt(-1.0*x) == 1.0*sqrt(-x) + assert sqrt(1.0*x) == 1.0*sqrt(x) + # issue 6090 + assert (-2*x*y*A*B)**2 == 4*x**2*y**2*(A*B)**2 + + +def test_float_int_round(): + assert int(float(sqrt(10))) == int(sqrt(10)) + assert int(pi**1000) % 10 == 2 + assert int(Float('1.123456789012345678901234567890e20', '')) == \ + int(112345678901234567890) + assert int(Float('1.123456789012345678901234567890e25', '')) == \ + int(11234567890123456789012345) + # decimal forces float so it's not an exact integer ending in 000000 + assert int(Float('1.123456789012345678901234567890e35', '')) == \ + 112345678901234567890123456789000192 + assert int(Float('123456789012345678901234567890e5', '')) == \ + 12345678901234567890123456789000000 + assert Integer(Float('1.123456789012345678901234567890e20', '')) == \ + 112345678901234567890 + assert Integer(Float('1.123456789012345678901234567890e25', '')) == \ + 11234567890123456789012345 + # decimal forces float so it's not an exact integer ending in 000000 + assert Integer(Float('1.123456789012345678901234567890e35', '')) == \ + 112345678901234567890123456789000192 + assert Integer(Float('123456789012345678901234567890e5', '')) == \ + 12345678901234567890123456789000000 + assert same_and_same_prec(Float('123000e-2',''), Float('1230.00', '')) + assert same_and_same_prec(Float('123000e2',''), Float('12300000', '')) + + assert int(1 + Rational('.9999999999999999999999999')) == 1 + assert int(pi/1e20) == 0 + assert int(1 + pi/1e20) == 1 + assert int(Add(1.2, -2, evaluate=False)) == int(1.2 - 2) + assert int(Add(1.2, +2, evaluate=False)) == int(1.2 + 2) + assert int(Add(1 + Float('.99999999999999999', ''), evaluate=False)) == 1 + raises(TypeError, lambda: float(x)) + raises(TypeError, lambda: float(sqrt(-1))) + + assert int(12345678901234567890 + cos(1)**2 + sin(1)**2) == \ + 12345678901234567891 + + +def test_issue_6611a(): + assert Mul.flatten([3**Rational(1, 3), + Pow(-Rational(1, 9), Rational(2, 3), evaluate=False)]) == \ + ([Rational(1, 3), (-1)**Rational(2, 3)], [], None) + + +def test_denest_add_mul(): + # when working with evaluated expressions make sure they denest + eq = x + 1 + eq = Add(eq, 2, evaluate=False) + eq = Add(eq, 2, evaluate=False) + assert Add(*eq.args) == x + 5 + eq = x*2 + eq = Mul(eq, 2, evaluate=False) + eq = Mul(eq, 2, evaluate=False) + assert Mul(*eq.args) == 8*x + # but don't let them denest unnecessarily + eq = Mul(-2, x - 2, evaluate=False) + assert 2*eq == Mul(-4, x - 2, evaluate=False) + assert -eq == Mul(2, x - 2, evaluate=False) + + +def test_mul_coeff(): + # It is important that all Numbers be removed from the seq; + # This can be tricky when powers combine to produce those numbers + p = exp(I*pi/3) + assert p**2*x*p*y*p*x*p**2 == x**2*y + + +def test_mul_zero_detection(): + nz = Dummy(real=True, zero=False) + r = Dummy(extended_real=True) + c = Dummy(real=False, complex=True) + c2 = Dummy(real=False, complex=True) + i = Dummy(imaginary=True) + e = nz*r*c + assert e.is_imaginary is None + assert e.is_extended_real is None + e = nz*c + assert e.is_imaginary is None + assert e.is_extended_real is False + e = nz*i*c + assert e.is_imaginary is False + assert e.is_extended_real is None + # check for more than one complex; it is important to use + # uniquely named Symbols to ensure that two factors appear + # e.g. if the symbols have the same name they just become + # a single factor, a power. + e = nz*i*c*c2 + assert e.is_imaginary is None + assert e.is_extended_real is None + + # _eval_is_extended_real and _eval_is_zero both employ trapping of the + # zero value so args should be tested in both directions and + # TO AVOID GETTING THE CACHED RESULT, Dummy MUST BE USED + + # real is unknown + def test(z, b, e): + if z.is_zero and b.is_finite: + assert e.is_extended_real and e.is_zero + else: + assert e.is_extended_real is None + if b.is_finite: + if z.is_zero: + assert e.is_zero + else: + assert e.is_zero is None + elif b.is_finite is False: + if z.is_zero is None: + assert e.is_zero is None + else: + assert e.is_zero is False + + + for iz, ib in product(*[[True, False, None]]*2): + z = Dummy('z', nonzero=iz) + b = Dummy('f', finite=ib) + e = Mul(z, b, evaluate=False) + test(z, b, e) + z = Dummy('nz', nonzero=iz) + b = Dummy('f', finite=ib) + e = Mul(b, z, evaluate=False) + test(z, b, e) + + # real is True + def test(z, b, e): + if z.is_zero and not b.is_finite: + assert e.is_extended_real is None + else: + assert e.is_extended_real is True + + for iz, ib in product(*[[True, False, None]]*2): + z = Dummy('z', nonzero=iz, extended_real=True) + b = Dummy('b', finite=ib, extended_real=True) + e = Mul(z, b, evaluate=False) + test(z, b, e) + z = Dummy('z', nonzero=iz, extended_real=True) + b = Dummy('b', finite=ib, extended_real=True) + e = Mul(b, z, evaluate=False) + test(z, b, e) + + +def test_Mul_with_zero_infinite(): + zer = Dummy(zero=True) + inf = Dummy(finite=False) + + e = Mul(zer, inf, evaluate=False) + assert e.is_extended_positive is None + assert e.is_hermitian is None + + e = Mul(inf, zer, evaluate=False) + assert e.is_extended_positive is None + assert e.is_hermitian is None + + +def test_Mul_does_not_cancel_infinities(): + a, b = symbols('a b') + assert ((zoo + 3*a)/(3*a + zoo)) is nan + assert ((b - oo)/(b - oo)) is nan + # issue 13904 + expr = (1/(a+b) + 1/(a-b))/(1/(a+b) - 1/(a-b)) + assert expr.subs(b, a) is nan + + +def test_Mul_does_not_distribute_infinity(): + a, b = symbols('a b') + assert ((1 + I)*oo).is_Mul + assert ((a + b)*(-oo)).is_Mul + assert ((a + 1)*zoo).is_Mul + assert ((1 + I)*oo).is_finite is False + z = (1 + I)*oo + assert ((1 - I)*z).expand() is oo + + +def test_Mul_does_not_let_0_trump_inf(): + assert Mul(*[0, a + zoo]) is S.NaN + assert Mul(*[0, a + oo]) is S.NaN + assert Mul(*[0, a + Integral(1/x**2, (x, 1, oo))]) is S.Zero + # Integral is treated like an unknown like 0*x -> 0 + assert Mul(*[0, a + Integral(x, (x, 1, oo))]) is S.Zero + + +def test_issue_8247_8354(): + from sympy.functions.elementary.trigonometric import tan + z = sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) - sqrt(10 + 6*sqrt(3)) + assert z.is_positive is False # it's 0 + z = S('''-2**(1/3)*(3*sqrt(93) + 29)**2 - 4*(3*sqrt(93) + 29)**(4/3) + + 12*sqrt(93)*(3*sqrt(93) + 29)**(1/3) + 116*(3*sqrt(93) + 29)**(1/3) + + 174*2**(1/3)*sqrt(93) + 1678*2**(1/3)''') + assert z.is_positive is False # it's 0 + z = 2*(-3*tan(19*pi/90) + sqrt(3))*cos(11*pi/90)*cos(19*pi/90) - \ + sqrt(3)*(-3 + 4*cos(19*pi/90)**2) + assert z.is_positive is not True # it's zero and it shouldn't hang + z = S('''9*(3*sqrt(93) + 29)**(2/3)*((3*sqrt(93) + + 29)**(1/3)*(-2**(2/3)*(3*sqrt(93) + 29)**(1/3) - 2) - 2*2**(1/3))**3 + + 72*(3*sqrt(93) + 29)**(2/3)*(81*sqrt(93) + 783) + (162*sqrt(93) + + 1566)*((3*sqrt(93) + 29)**(1/3)*(-2**(2/3)*(3*sqrt(93) + 29)**(1/3) - + 2) - 2*2**(1/3))**2''') + assert z.is_positive is False # it's 0 (and a single _mexpand isn't enough) + + +def test_Add_is_zero(): + x, y = symbols('x y', zero=True) + assert (x + y).is_zero + + # Issue 15873 + e = -2*I + (1 + I)**2 + assert e.is_zero is None + + +def test_issue_14392(): + assert (sin(zoo)**2).as_real_imag() == (nan, nan) + + +def test_divmod(): + assert divmod(x, y) == (x//y, x % y) + assert divmod(x, 3) == (x//3, x % 3) + assert divmod(3, x) == (3//x, 3 % x) + + +def test__neg__(): + assert -(x*y) == -x*y + assert -(-x*y) == x*y + assert -(1.*x) == -1.*x + assert -(-1.*x) == 1.*x + assert -(2.*x) == -2.*x + assert -(-2.*x) == 2.*x + with distribute(False): + eq = -(x + y) + assert eq.is_Mul and eq.args == (-1, x + y) + with evaluate(False): + eq = -(x + y) + assert eq.is_Mul and eq.args == (-1, x + y) + + +def test_issue_18507(): + assert Mul(zoo, zoo, 0) is nan + + +def test_issue_17130(): + e = Add(b, -b, I, -I, evaluate=False) + assert e.is_zero is None # ideally this would be True + + +def test_issue_21034(): + e = -I*log((re(asin(5)) + I*im(asin(5)))/sqrt(re(asin(5))**2 + im(asin(5))**2))/pi + assert e.round(2) + + +def test_issue_22021(): + from sympy.calculus.accumulationbounds import AccumBounds + # these objects are special cases in Mul + from sympy.tensor.tensor import TensorIndexType, tensor_indices, tensor_heads + L = TensorIndexType("L") + i = tensor_indices("i", L) + A, B = tensor_heads("A B", [L]) + e = A(i) + B(i) + assert -e == -1*e + e = zoo + x + assert -e == -1*e + a = AccumBounds(1, 2) + e = a + x + assert -e == -1*e + for args in permutations((zoo, a, x)): + e = Add(*args, evaluate=False) + assert -e == -1*e + assert 2*Add(1, x, x, evaluate=False) == 4*x + 2 + + +def test_issue_22244(): + assert -(zoo*x) == zoo*x + + +def test_issue_22453(): + from sympy.utilities.iterables import cartes + e = Symbol('e', extended_positive=True) + for a, b in cartes(*[[oo, -oo, 3]]*2): + if a == b == 3: + continue + i = a + I*b + assert i**(1 + e) is S.ComplexInfinity + assert i**-e is S.Zero + assert unchanged(Pow, i, e) + assert 1/(oo + I*oo) is S.Zero + r, i = [Dummy(infinite=True, extended_real=True) for _ in range(2)] + assert 1/(r + I*i) is S.Zero + assert 1/(3 + I*i) is S.Zero + assert 1/(r + I*3) is S.Zero + + +def test_issue_22613(): + assert (0**(x - 2)).as_content_primitive() == (1, 0**(x - 2)) + assert (0**(x + 2)).as_content_primitive() == (1, 0**(x + 2)) + + +def test_issue_25176(): + assert sqrt(-4*3**(S(3)/4)*I/3) == 2*3**(S(7)/8)*sqrt(-I)/3 diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_assumptions.py b/phivenv/Lib/site-packages/sympy/core/tests/test_assumptions.py new file mode 100644 index 0000000000000000000000000000000000000000..574e90178fb489fe99c99ea0c72df57ceec4b249 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_assumptions.py @@ -0,0 +1,1335 @@ +from sympy.core.mod import Mod +from sympy.core.numbers import (I, oo, pi) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (asin, sin) +from sympy.simplify.simplify import simplify +from sympy.core import Symbol, S, Rational, Integer, Dummy, Wild, Pow +from sympy.core.assumptions import (assumptions, check_assumptions, + failing_assumptions, common_assumptions, _generate_assumption_rules, + _load_pre_generated_assumption_rules) +from sympy.core.facts import InconsistentAssumptions +from sympy.core.random import seed +from sympy.combinatorics import Permutation +from sympy.combinatorics.perm_groups import PermutationGroup + +from sympy.testing.pytest import raises, XFAIL + + +def test_symbol_unset(): + x = Symbol('x', real=True, integer=True) + assert x.is_real is True + assert x.is_integer is True + assert x.is_imaginary is False + assert x.is_noninteger is False + assert x.is_number is False + + +def test_zero(): + z = Integer(0) + assert z.is_commutative is True + assert z.is_integer is True + assert z.is_rational is True + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is False + assert z.is_positive is False + assert z.is_negative is False + assert z.is_nonpositive is True + assert z.is_nonnegative is True + assert z.is_even is True + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + assert z.is_number is True + + +def test_one(): + z = Integer(1) + assert z.is_commutative is True + assert z.is_integer is True + assert z.is_rational is True + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is False + assert z.is_positive is True + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is True + assert z.is_even is False + assert z.is_odd is True + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_number is True + assert z.is_composite is False # issue 8807 + + +def test_negativeone(): + z = Integer(-1) + assert z.is_commutative is True + assert z.is_integer is True + assert z.is_rational is True + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is False + assert z.is_positive is False + assert z.is_negative is True + assert z.is_nonpositive is True + assert z.is_nonnegative is False + assert z.is_even is False + assert z.is_odd is True + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + assert z.is_number is True + + +def test_infinity(): + oo = S.Infinity + + assert oo.is_commutative is True + assert oo.is_integer is False + assert oo.is_rational is False + assert oo.is_algebraic is False + assert oo.is_transcendental is False + assert oo.is_extended_real is True + assert oo.is_real is False + assert oo.is_complex is False + assert oo.is_noninteger is True + assert oo.is_irrational is False + assert oo.is_imaginary is False + assert oo.is_nonzero is False + assert oo.is_positive is False + assert oo.is_negative is False + assert oo.is_nonpositive is False + assert oo.is_nonnegative is False + assert oo.is_extended_nonzero is True + assert oo.is_extended_positive is True + assert oo.is_extended_negative is False + assert oo.is_extended_nonpositive is False + assert oo.is_extended_nonnegative is True + assert oo.is_even is False + assert oo.is_odd is False + assert oo.is_finite is False + assert oo.is_infinite is True + assert oo.is_comparable is True + assert oo.is_prime is False + assert oo.is_composite is False + assert oo.is_number is True + + +def test_neg_infinity(): + mm = S.NegativeInfinity + + assert mm.is_commutative is True + assert mm.is_integer is False + assert mm.is_rational is False + assert mm.is_algebraic is False + assert mm.is_transcendental is False + assert mm.is_extended_real is True + assert mm.is_real is False + assert mm.is_complex is False + assert mm.is_noninteger is True + assert mm.is_irrational is False + assert mm.is_imaginary is False + assert mm.is_nonzero is False + assert mm.is_positive is False + assert mm.is_negative is False + assert mm.is_nonpositive is False + assert mm.is_nonnegative is False + assert mm.is_extended_nonzero is True + assert mm.is_extended_positive is False + assert mm.is_extended_negative is True + assert mm.is_extended_nonpositive is True + assert mm.is_extended_nonnegative is False + assert mm.is_even is False + assert mm.is_odd is False + assert mm.is_finite is False + assert mm.is_infinite is True + assert mm.is_comparable is True + assert mm.is_prime is False + assert mm.is_composite is False + assert mm.is_number is True + + +def test_zoo(): + zoo = S.ComplexInfinity + assert zoo.is_complex is False + assert zoo.is_real is False + assert zoo.is_prime is False + + +def test_nan(): + nan = S.NaN + + assert nan.is_commutative is True + assert nan.is_integer is None + assert nan.is_rational is None + assert nan.is_algebraic is None + assert nan.is_transcendental is None + assert nan.is_real is None + assert nan.is_complex is None + assert nan.is_noninteger is None + assert nan.is_irrational is None + assert nan.is_imaginary is None + assert nan.is_positive is None + assert nan.is_negative is None + assert nan.is_nonpositive is None + assert nan.is_nonnegative is None + assert nan.is_even is None + assert nan.is_odd is None + assert nan.is_finite is None + assert nan.is_infinite is None + assert nan.is_comparable is False + assert nan.is_prime is None + assert nan.is_composite is None + assert nan.is_number is True + + +def test_pos_rational(): + r = Rational(3, 4) + assert r.is_commutative is True + assert r.is_integer is False + assert r.is_rational is True + assert r.is_algebraic is True + assert r.is_transcendental is False + assert r.is_real is True + assert r.is_complex is True + assert r.is_noninteger is True + assert r.is_irrational is False + assert r.is_imaginary is False + assert r.is_positive is True + assert r.is_negative is False + assert r.is_nonpositive is False + assert r.is_nonnegative is True + assert r.is_even is False + assert r.is_odd is False + assert r.is_finite is True + assert r.is_infinite is False + assert r.is_comparable is True + assert r.is_prime is False + assert r.is_composite is False + + r = Rational(1, 4) + assert r.is_nonpositive is False + assert r.is_positive is True + assert r.is_negative is False + assert r.is_nonnegative is True + r = Rational(5, 4) + assert r.is_negative is False + assert r.is_positive is True + assert r.is_nonpositive is False + assert r.is_nonnegative is True + r = Rational(5, 3) + assert r.is_nonnegative is True + assert r.is_positive is True + assert r.is_negative is False + assert r.is_nonpositive is False + + +def test_neg_rational(): + r = Rational(-3, 4) + assert r.is_positive is False + assert r.is_nonpositive is True + assert r.is_negative is True + assert r.is_nonnegative is False + r = Rational(-1, 4) + assert r.is_nonpositive is True + assert r.is_positive is False + assert r.is_negative is True + assert r.is_nonnegative is False + r = Rational(-5, 4) + assert r.is_negative is True + assert r.is_positive is False + assert r.is_nonpositive is True + assert r.is_nonnegative is False + r = Rational(-5, 3) + assert r.is_nonnegative is False + assert r.is_positive is False + assert r.is_negative is True + assert r.is_nonpositive is True + + +def test_pi(): + z = S.Pi + assert z.is_commutative is True + assert z.is_integer is False + assert z.is_rational is False + assert z.is_algebraic is False + assert z.is_transcendental is True + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is True + assert z.is_irrational is True + assert z.is_imaginary is False + assert z.is_positive is True + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is True + assert z.is_even is False + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + + +def test_E(): + z = S.Exp1 + assert z.is_commutative is True + assert z.is_integer is False + assert z.is_rational is False + assert z.is_algebraic is False + assert z.is_transcendental is True + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is True + assert z.is_irrational is True + assert z.is_imaginary is False + assert z.is_positive is True + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is True + assert z.is_even is False + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + + +def test_I(): + z = S.ImaginaryUnit + assert z.is_commutative is True + assert z.is_integer is False + assert z.is_rational is False + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is False + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is True + assert z.is_positive is False + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is False + assert z.is_even is False + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is False + assert z.is_prime is False + assert z.is_composite is False + + +def test_symbol_real_false(): + # issue 3848 + a = Symbol('a', real=False) + + assert a.is_real is False + assert a.is_integer is False + assert a.is_zero is False + + assert a.is_negative is False + assert a.is_positive is False + assert a.is_nonnegative is False + assert a.is_nonpositive is False + assert a.is_nonzero is False + + assert a.is_extended_negative is None + assert a.is_extended_positive is None + assert a.is_extended_nonnegative is None + assert a.is_extended_nonpositive is None + assert a.is_extended_nonzero is None + + +def test_symbol_extended_real_false(): + # issue 3848 + a = Symbol('a', extended_real=False) + + assert a.is_real is False + assert a.is_integer is False + assert a.is_zero is False + + assert a.is_negative is False + assert a.is_positive is False + assert a.is_nonnegative is False + assert a.is_nonpositive is False + assert a.is_nonzero is False + + assert a.is_extended_negative is False + assert a.is_extended_positive is False + assert a.is_extended_nonnegative is False + assert a.is_extended_nonpositive is False + assert a.is_extended_nonzero is False + + +def test_symbol_imaginary(): + a = Symbol('a', imaginary=True) + + assert a.is_real is False + assert a.is_integer is False + assert a.is_negative is False + assert a.is_positive is False + assert a.is_nonnegative is False + assert a.is_nonpositive is False + assert a.is_zero is False + assert a.is_nonzero is False # since nonzero -> real + + +def test_symbol_zero(): + x = Symbol('x', zero=True) + assert x.is_positive is False + assert x.is_nonpositive + assert x.is_negative is False + assert x.is_nonnegative + assert x.is_zero is True + # TODO Change to x.is_nonzero is None + # See https://github.com/sympy/sympy/pull/9583 + assert x.is_nonzero is False + assert x.is_finite is True + + +def test_symbol_positive(): + x = Symbol('x', positive=True) + assert x.is_positive is True + assert x.is_nonpositive is False + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_neg_symbol_positive(): + x = -Symbol('x', positive=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is True + assert x.is_nonnegative is False + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_symbol_nonpositive(): + x = Symbol('x', nonpositive=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_neg_symbol_nonpositive(): + x = -Symbol('x', nonpositive=True) + assert x.is_positive is None + assert x.is_nonpositive is None + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsepositive(): + x = Symbol('x', positive=False) + assert x.is_positive is False + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsepositive_mul(): + # To test pull request 9379 + # Explicit handling of arg.is_positive=False was added to Mul._eval_is_positive + x = 2*Symbol('x', positive=False) + assert x.is_positive is False # This was None before + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +@XFAIL +def test_symbol_infinitereal_mul(): + ix = Symbol('ix', infinite=True, extended_real=True) + assert (-ix).is_extended_positive is None + + +def test_neg_symbol_falsepositive(): + x = -Symbol('x', positive=False) + assert x.is_positive is None + assert x.is_nonpositive is None + assert x.is_negative is False + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_neg_symbol_falsenegative(): + # To test pull request 9379 + # Explicit handling of arg.is_negative=False was added to Mul._eval_is_positive + x = -Symbol('x', negative=False) + assert x.is_positive is False # This was None before + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsepositive_real(): + x = Symbol('x', positive=False, real=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_neg_symbol_falsepositive_real(): + x = -Symbol('x', positive=False, real=True) + assert x.is_positive is None + assert x.is_nonpositive is None + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsenonnegative(): + x = Symbol('x', nonnegative=False) + assert x.is_positive is False + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is False + assert x.is_zero is False + assert x.is_nonzero is None + + +@XFAIL +def test_neg_symbol_falsenonnegative(): + x = -Symbol('x', nonnegative=False) + assert x.is_positive is None + assert x.is_nonpositive is False # this currently returns None + assert x.is_negative is False # this currently returns None + assert x.is_nonnegative is None + assert x.is_zero is False # this currently returns None + assert x.is_nonzero is True # this currently returns None + + +def test_symbol_falsenonnegative_real(): + x = Symbol('x', nonnegative=False, real=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is True + assert x.is_nonnegative is False + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_neg_symbol_falsenonnegative_real(): + x = -Symbol('x', nonnegative=False, real=True) + assert x.is_positive is True + assert x.is_nonpositive is False + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_prime(): + assert S.NegativeOne.is_prime is False + assert S(-2).is_prime is False + assert S(-4).is_prime is False + assert S.Zero.is_prime is False + assert S.One.is_prime is False + assert S(2).is_prime is True + assert S(17).is_prime is True + assert S(4).is_prime is False + + +def test_composite(): + assert S.NegativeOne.is_composite is False + assert S(-2).is_composite is False + assert S(-4).is_composite is False + assert S.Zero.is_composite is False + assert S(2).is_composite is False + assert S(17).is_composite is False + assert S(4).is_composite is True + x = Dummy(integer=True, positive=True, prime=False) + assert x.is_composite is None # x could be 1 + assert (x + 1).is_composite is None + x = Dummy(positive=True, even=True, prime=False) + assert x.is_integer is True + assert x.is_composite is True + + +def test_prime_symbol(): + x = Symbol('x', prime=True) + assert x.is_prime is True + assert x.is_integer is True + assert x.is_positive is True + assert x.is_negative is False + assert x.is_nonpositive is False + assert x.is_nonnegative is True + + x = Symbol('x', prime=False) + assert x.is_prime is False + assert x.is_integer is None + assert x.is_positive is None + assert x.is_negative is None + assert x.is_nonpositive is None + assert x.is_nonnegative is None + + +def test_symbol_noncommutative(): + x = Symbol('x', commutative=True) + assert x.is_complex is None + + x = Symbol('x', commutative=False) + assert x.is_integer is False + assert x.is_rational is False + assert x.is_algebraic is False + assert x.is_irrational is False + assert x.is_real is False + assert x.is_complex is False + + +def test_other_symbol(): + x = Symbol('x', integer=True) + assert x.is_integer is True + assert x.is_real is True + assert x.is_finite is True + + x = Symbol('x', integer=True, nonnegative=True) + assert x.is_integer is True + assert x.is_nonnegative is True + assert x.is_negative is False + assert x.is_positive is None + assert x.is_finite is True + + x = Symbol('x', integer=True, nonpositive=True) + assert x.is_integer is True + assert x.is_nonpositive is True + assert x.is_positive is False + assert x.is_negative is None + assert x.is_finite is True + + x = Symbol('x', odd=True) + assert x.is_odd is True + assert x.is_even is False + assert x.is_integer is True + assert x.is_finite is True + + x = Symbol('x', odd=False) + assert x.is_odd is False + assert x.is_even is None + assert x.is_integer is None + assert x.is_finite is None + + x = Symbol('x', even=True) + assert x.is_even is True + assert x.is_odd is False + assert x.is_integer is True + assert x.is_finite is True + + x = Symbol('x', even=False) + assert x.is_even is False + assert x.is_odd is None + assert x.is_integer is None + assert x.is_finite is None + + x = Symbol('x', integer=True, nonnegative=True) + assert x.is_integer is True + assert x.is_nonnegative is True + assert x.is_finite is True + + x = Symbol('x', integer=True, nonpositive=True) + assert x.is_integer is True + assert x.is_nonpositive is True + assert x.is_finite is True + + x = Symbol('x', rational=True) + assert x.is_real is True + assert x.is_finite is True + + x = Symbol('x', rational=False) + assert x.is_real is None + assert x.is_finite is None + + x = Symbol('x', irrational=True) + assert x.is_real is True + assert x.is_finite is True + + x = Symbol('x', irrational=False) + assert x.is_real is None + assert x.is_finite is None + + with raises(AttributeError): + x.is_real = False + + x = Symbol('x', algebraic=True) + assert x.is_transcendental is False + x = Symbol('x', transcendental=True) + assert x.is_algebraic is False + assert x.is_rational is False + assert x.is_integer is False + + +def test_evaluate_false(): + # Previously this failed because the assumptions query would make new + # expressions and some of the evaluation logic would fail under + # evaluate(False). + from sympy.core.parameters import evaluate + from sympy.abc import x, h + f = 2**x**7 + with evaluate(False): + fh = f.xreplace({x: x+h}) + assert fh.exp.is_rational is None + + +def test_issue_3825(): + """catch: hash instability""" + x = Symbol("x") + y = Symbol("y") + a1 = x + y + a2 = y + x + a2.is_comparable + + h1 = hash(a1) + h2 = hash(a2) + assert h1 == h2 + + +def test_issue_4822(): + z = (-1)**Rational(1, 3)*(1 - I*sqrt(3)) + assert z.is_real in [True, None] + + +def test_hash_vs_typeinfo(): + """seemingly different typeinfo, but in fact equal""" + + # the following two are semantically equal + x1 = Symbol('x', even=True) + x2 = Symbol('x', integer=True, odd=False) + + assert hash(x1) == hash(x2) + assert x1 == x2 + + +def test_hash_vs_typeinfo_2(): + """different typeinfo should mean !eq""" + # the following two are semantically different + x = Symbol('x') + x1 = Symbol('x', even=True) + + assert x != x1 + assert hash(x) != hash(x1) # This might fail with very low probability + + +def test_hash_vs_eq(): + """catch: different hash for equal objects""" + a = 1 + S.Pi # important: do not fold it into a Number instance + ha = hash(a) # it should be Add/Mul/... to trigger the bug + + a.is_positive # this uses .evalf() and deduces it is positive + assert a.is_positive is True + + # be sure that hash stayed the same + assert ha == hash(a) + + # now b should be the same expression + b = a.expand(trig=True) + hb = hash(b) + + assert a == b + assert ha == hb + + +def test_Add_is_pos_neg(): + # these cover lines not covered by the rest of tests in core + n = Symbol('n', extended_negative=True, infinite=True) + nn = Symbol('n', extended_nonnegative=True, infinite=True) + np = Symbol('n', extended_nonpositive=True, infinite=True) + p = Symbol('p', extended_positive=True, infinite=True) + r = Dummy(extended_real=True, finite=False) + x = Symbol('x') + xf = Symbol('xf', finite=True) + assert (n + p).is_extended_positive is None + assert (n + x).is_extended_positive is None + assert (p + x).is_extended_positive is None + assert (n + p).is_extended_negative is None + assert (n + x).is_extended_negative is None + assert (p + x).is_extended_negative is None + + assert (n + xf).is_extended_positive is False + assert (p + xf).is_extended_positive is True + assert (n + xf).is_extended_negative is True + assert (p + xf).is_extended_negative is False + + assert (x - S.Infinity).is_extended_negative is None # issue 7798 + # issue 8046, 16.2 + assert (p + nn).is_extended_positive + assert (n + np).is_extended_negative + assert (p + r).is_extended_positive is None + + +def test_Add_is_imaginary(): + nn = Dummy(nonnegative=True) + assert (I*nn + I).is_imaginary # issue 8046, 17 + + +def test_Add_is_algebraic(): + a = Symbol('a', algebraic=True) + b = Symbol('a', algebraic=True) + na = Symbol('na', algebraic=False) + nb = Symbol('nb', algebraic=False) + x = Symbol('x') + assert (a + b).is_algebraic + assert (na + nb).is_algebraic is None + assert (a + na).is_algebraic is False + assert (a + x).is_algebraic is None + assert (na + x).is_algebraic is None + + +def test_Mul_is_algebraic(): + a = Symbol('a', algebraic=True) + b = Symbol('b', algebraic=True) + na = Symbol('na', algebraic=False) + an = Symbol('an', algebraic=True, nonzero=True) + nb = Symbol('nb', algebraic=False) + x = Symbol('x') + assert (a*b).is_algebraic is True + assert (na*nb).is_algebraic is None + assert (a*na).is_algebraic is None + assert (an*na).is_algebraic is False + assert (a*x).is_algebraic is None + assert (na*x).is_algebraic is None + + +def test_Pow_is_algebraic(): + e = Symbol('e', algebraic=True) + + assert Pow(1, e, evaluate=False).is_algebraic + assert Pow(0, e, evaluate=False).is_algebraic + + a = Symbol('a', algebraic=True) + azf = Symbol('azf', algebraic=True, zero=False) + na = Symbol('na', algebraic=False) + ia = Symbol('ia', algebraic=True, irrational=True) + ib = Symbol('ib', algebraic=True, irrational=True) + r = Symbol('r', rational=True) + x = Symbol('x') + assert (a**2).is_algebraic is True + assert (a**r).is_algebraic is None + assert (azf**r).is_algebraic is True + assert (a**x).is_algebraic is None + assert (na**r).is_algebraic is None + assert (ia**r).is_algebraic is True + assert (ia**ib).is_algebraic is False + + assert (a**e).is_algebraic is None + + # Gelfond-Schneider constant: + assert Pow(2, sqrt(2), evaluate=False).is_algebraic is False + + assert Pow(S.GoldenRatio, sqrt(3), evaluate=False).is_algebraic is False + + # issue 8649 + t = Symbol('t', real=True, transcendental=True) + n = Symbol('n', integer=True) + assert (t**n).is_algebraic is None + assert (t**n).is_integer is None + + assert (pi**3).is_algebraic is False + r = Symbol('r', zero=True) + assert (pi**r).is_algebraic is True + + +def test_Mul_is_prime_composite(): + x = Symbol('x', positive=True, integer=True) + y = Symbol('y', positive=True, integer=True) + assert (x*y).is_prime is None + assert ( (x+1)*(y+1) ).is_prime is False + assert ( (x+1)*(y+1) ).is_composite is True + + x = Symbol('x', positive=True) + assert ( (x+1)*(y+1) ).is_prime is None + assert ( (x+1)*(y+1) ).is_composite is None + + +def test_Pow_is_pos_neg(): + z = Symbol('z', real=True) + w = Symbol('w', nonpositive=True) + + assert (S.NegativeOne**S(2)).is_positive is True + assert (S.One**z).is_positive is True + assert (S.NegativeOne**S(3)).is_positive is False + assert (S.Zero**S.Zero).is_positive is True # 0**0 is 1 + assert (w**S(3)).is_positive is False + assert (w**S(2)).is_positive is None + assert (I**2).is_positive is False + assert (I**4).is_positive is True + + # tests emerging from #16332 issue + p = Symbol('p', zero=True) + q = Symbol('q', zero=False, real=True) + j = Symbol('j', zero=False, even=True) + x = Symbol('x', zero=True) + y = Symbol('y', zero=True) + assert (p**q).is_positive is False + assert (p**q).is_negative is False + assert (p**j).is_positive is False + assert (x**y).is_positive is True # 0**0 + assert (x**y).is_negative is False + + +def test_Pow_is_prime_composite(): + x = Symbol('x', positive=True, integer=True) + y = Symbol('y', positive=True, integer=True) + assert (x**y).is_prime is None + assert ( x**(y+1) ).is_prime is False + assert ( x**(y+1) ).is_composite is None + assert ( (x+1)**(y+1) ).is_composite is True + assert ( (-x-1)**(2*y) ).is_composite is True + + x = Symbol('x', positive=True) + assert (x**y).is_prime is None + + +def test_Mul_is_infinite(): + x = Symbol('x') + f = Symbol('f', finite=True) + i = Symbol('i', infinite=True) + z = Dummy(zero=True) + nzf = Dummy(finite=True, zero=False) + from sympy.core.mul import Mul + assert (x*f).is_finite is None + assert (x*i).is_finite is None + assert (f*i).is_finite is None + assert (x*f*i).is_finite is None + assert (z*i).is_finite is None + assert (nzf*i).is_finite is False + assert (z*f).is_finite is True + assert Mul(0, f, evaluate=False).is_finite is True + assert Mul(0, i, evaluate=False).is_finite is None + + assert (x*f).is_infinite is None + assert (x*i).is_infinite is None + assert (f*i).is_infinite is None + assert (x*f*i).is_infinite is None + assert (z*i).is_infinite is S.NaN.is_infinite + assert (nzf*i).is_infinite is True + assert (z*f).is_infinite is False + assert Mul(0, f, evaluate=False).is_infinite is False + assert Mul(0, i, evaluate=False).is_infinite is S.NaN.is_infinite + + +def test_Add_is_infinite(): + x = Symbol('x') + f = Symbol('f', finite=True) + i = Symbol('i', infinite=True) + i2 = Symbol('i2', infinite=True) + z = Dummy(zero=True) + nzf = Dummy(finite=True, zero=False) + from sympy.core.add import Add + assert (x+f).is_finite is None + assert (x+i).is_finite is None + assert (f+i).is_finite is False + assert (x+f+i).is_finite is None + assert (z+i).is_finite is False + assert (nzf+i).is_finite is False + assert (z+f).is_finite is True + assert (i+i2).is_finite is None + assert Add(0, f, evaluate=False).is_finite is True + assert Add(0, i, evaluate=False).is_finite is False + + assert (x+f).is_infinite is None + assert (x+i).is_infinite is None + assert (f+i).is_infinite is True + assert (x+f+i).is_infinite is None + assert (z+i).is_infinite is True + assert (nzf+i).is_infinite is True + assert (z+f).is_infinite is False + assert (i+i2).is_infinite is None + assert Add(0, f, evaluate=False).is_infinite is False + assert Add(0, i, evaluate=False).is_infinite is True + + +def test_special_is_rational(): + i = Symbol('i', integer=True) + i2 = Symbol('i2', integer=True) + ni = Symbol('ni', integer=True, nonzero=True) + r = Symbol('r', rational=True) + rn = Symbol('r', rational=True, nonzero=True) + nr = Symbol('nr', irrational=True) + x = Symbol('x') + assert sqrt(3).is_rational is False + assert (3 + sqrt(3)).is_rational is False + assert (3*sqrt(3)).is_rational is False + assert exp(3).is_rational is False + assert exp(ni).is_rational is False + assert exp(rn).is_rational is False + assert exp(x).is_rational is None + assert exp(log(3), evaluate=False).is_rational is True + assert log(exp(3), evaluate=False).is_rational is True + assert log(3).is_rational is False + assert log(ni + 1).is_rational is False + assert log(rn + 1).is_rational is False + assert log(x).is_rational is None + assert (sqrt(3) + sqrt(5)).is_rational is None + assert (sqrt(3) + S.Pi).is_rational is False + assert (x**i).is_rational is None + assert (i**i).is_rational is True + assert (i**i2).is_rational is None + assert (r**i).is_rational is None + assert (r**r).is_rational is None + assert (r**x).is_rational is None + assert (nr**i).is_rational is None # issue 8598 + assert (nr**Symbol('z', zero=True)).is_rational + assert sin(1).is_rational is False + assert sin(ni).is_rational is False + assert sin(rn).is_rational is False + assert sin(x).is_rational is None + assert asin(r).is_rational is False + assert sin(asin(3), evaluate=False).is_rational is True + + +@XFAIL +def test_issue_6275(): + x = Symbol('x') + # both zero or both Muls...but neither "change would be very appreciated. + # This is similar to x/x => 1 even though if x = 0, it is really nan. + assert isinstance(x*0, type(0*S.Infinity)) + if 0*S.Infinity is S.NaN: + b = Symbol('b', finite=None) + assert (b*0).is_zero is None + + +def test_sanitize_assumptions(): + # issue 6666 + for cls in (Symbol, Dummy, Wild): + x = cls('x', real=1, positive=0) + assert x.is_real is True + assert x.is_positive is False + assert cls('', real=True, positive=None).is_positive is None + raises(ValueError, lambda: cls('', commutative=None)) + raises(ValueError, lambda: Symbol._sanitize({"commutative": None})) + + +def test_special_assumptions(): + e = -3 - sqrt(5) + (-sqrt(10)/2 - sqrt(2)/2)**2 + assert simplify(e < 0) is S.false + assert simplify(e > 0) is S.false + assert (e == 0) is False # it's not a literal 0 + assert e.equals(0) is True + + +def test_inconsistent(): + # cf. issues 5795 and 5545 + raises(InconsistentAssumptions, lambda: Symbol('x', real=True, + commutative=False)) + + +def test_issue_6631(): + assert ((-1)**(I)).is_real is True + assert ((-1)**(I*2)).is_real is True + assert ((-1)**(I/2)).is_real is True + assert ((-1)**(I*S.Pi)).is_real is True + assert (I**(I + 2)).is_real is True + + +def test_issue_2730(): + assert (1/(1 + I)).is_real is False + + +def test_issue_4149(): + assert (3 + I).is_complex + assert (3 + I).is_imaginary is False + assert (3*I + S.Pi*I).is_imaginary + # as Zero.is_imaginary is False, see issue 7649 + y = Symbol('y', real=True) + assert (3*I + S.Pi*I + y*I).is_imaginary is None + p = Symbol('p', positive=True) + assert (3*I + S.Pi*I + p*I).is_imaginary + n = Symbol('n', negative=True) + assert (-3*I - S.Pi*I + n*I).is_imaginary + + i = Symbol('i', imaginary=True) + assert ([(i**a).is_imaginary for a in range(4)] == + [False, True, False, True]) + + # tests from the PR #7887: + e = S("-sqrt(3)*I/2 + 0.866025403784439*I") + assert e.is_real is False + assert e.is_imaginary + + +def test_issue_2920(): + n = Symbol('n', negative=True) + assert sqrt(n).is_imaginary + + +def test_issue_7899(): + x = Symbol('x', real=True) + assert (I*x).is_real is None + assert ((x - I)*(x - 1)).is_zero is None + assert ((x - I)*(x - 1)).is_real is None + + +@XFAIL +def test_issue_7993(): + x = Dummy(integer=True) + y = Dummy(noninteger=True) + assert (x - y).is_zero is False + + +def test_issue_8075(): + raises(InconsistentAssumptions, lambda: Dummy(zero=True, finite=False)) + raises(InconsistentAssumptions, lambda: Dummy(zero=True, infinite=True)) + + +def test_issue_8642(): + x = Symbol('x', real=True, integer=False) + assert (x*2).is_integer is None, (x*2).is_integer + + +def test_issues_8632_8633_8638_8675_8992(): + p = Dummy(integer=True, positive=True) + nn = Dummy(integer=True, nonnegative=True) + assert (p - S.Half).is_positive + assert (p - 1).is_nonnegative + assert (nn + 1).is_positive + assert (-p + 1).is_nonpositive + assert (-nn - 1).is_negative + prime = Dummy(prime=True) + assert (prime - 2).is_nonnegative + assert (prime - 3).is_nonnegative is None + even = Dummy(positive=True, even=True) + assert (even - 2).is_nonnegative + + p = Dummy(positive=True) + assert (p/(p + 1) - 1).is_negative + assert ((p + 2)**3 - S.Half).is_positive + n = Dummy(negative=True) + assert (n - 3).is_nonpositive + + +def test_issue_9115_9150(): + n = Dummy('n', integer=True, nonnegative=True) + assert (factorial(n) >= 1) == True + assert (factorial(n) < 1) == False + + assert factorial(n + 1).is_even is None + assert factorial(n + 2).is_even is True + assert factorial(n + 2) >= 2 + + +def test_issue_9165(): + z = Symbol('z', zero=True) + f = Symbol('f', finite=False) + assert 0/z is S.NaN + assert 0*(1/z) is S.NaN + assert 0*f is S.NaN + + +def test_issue_10024(): + x = Dummy('x') + assert Mod(x, 2*pi).is_zero is None + + +def test_issue_10302(): + x = Symbol('x') + r = Symbol('r', real=True) + u = -(3*2**pi)**(1/pi) + 2*3**(1/pi) + i = u + u*I + + assert i.is_real is None # w/o simplification this should fail + assert (u + i).is_zero is None + assert (1 + i).is_zero is False + + a = Dummy('a', zero=True) + assert (a + I).is_zero is False + assert (a + r*I).is_zero is None + assert (a + I).is_imaginary + assert (a + x + I).is_imaginary is None + assert (a + r*I + I).is_imaginary is None + + +def test_complex_reciprocal_imaginary(): + assert (1 / (4 + 3*I)).is_imaginary is False + + +def test_issue_16313(): + x = Symbol('x', extended_real=False) + k = Symbol('k', real=True) + l = Symbol('l', real=True, zero=False) + assert (-x).is_real is False + assert (k*x).is_real is None # k can be zero also + assert (l*x).is_real is False + assert (l*x*x).is_real is None # since x*x can be a real number + assert (-x).is_positive is False + + +def test_issue_16579(): + # extended_real -> finite | infinite + x = Symbol('x', extended_real=True, infinite=False) + y = Symbol('y', extended_real=True, finite=False) + assert x.is_finite is True + assert y.is_infinite is True + + # With PR 16978, complex now implies finite + c = Symbol('c', complex=True) + assert c.is_finite is True + raises(InconsistentAssumptions, lambda: Dummy(complex=True, finite=False)) + + # Now infinite == !finite + nf = Symbol('nf', finite=False) + assert nf.is_infinite is True + + +def test_issue_17556(): + z = I*oo + assert z.is_imaginary is False + assert z.is_finite is False + + +def test_issue_21651(): + k = Symbol('k', positive=True, integer=True) + exp = 2*2**(-k) + assert exp.is_integer is None + + +def test_assumptions_copy(): + assert assumptions(Symbol('x'), {"commutative": True} + ) == {'commutative': True} + assert assumptions(Symbol('x'), ['integer']) == {} + assert assumptions(Symbol('x'), ['commutative'] + ) == {'commutative': True} + assert assumptions(Symbol('x')) == {'commutative': True} + assert assumptions(1)['positive'] + assert assumptions(3 + I) == { + 'algebraic': True, + 'commutative': True, + 'complex': True, + 'composite': False, + 'even': False, + 'extended_negative': False, + 'extended_nonnegative': False, + 'extended_nonpositive': False, + 'extended_nonzero': False, + 'extended_positive': False, + 'extended_real': False, + 'finite': True, + 'imaginary': False, + 'infinite': False, + 'integer': False, + 'irrational': False, + 'negative': False, + 'noninteger': False, + 'nonnegative': False, + 'nonpositive': False, + 'nonzero': False, + 'odd': False, + 'positive': False, + 'prime': False, + 'rational': False, + 'real': False, + 'transcendental': False, + 'zero': False} + + +def test_check_assumptions(): + assert check_assumptions(1, 0) is False + x = Symbol('x', positive=True) + assert check_assumptions(1, x) is True + assert check_assumptions(1, 1) is True + assert check_assumptions(-1, 1) is False + i = Symbol('i', integer=True) + # don't know if i is positive (or prime, etc...) + assert check_assumptions(i, 1) is None + assert check_assumptions(Dummy(integer=None), integer=True) is None + assert check_assumptions(Dummy(integer=None), integer=False) is None + assert check_assumptions(Dummy(integer=False), integer=True) is False + assert check_assumptions(Dummy(integer=True), integer=False) is False + # no T/F assumptions to check + assert check_assumptions(Dummy(integer=False), integer=None) is True + raises(ValueError, lambda: check_assumptions(2*x, x, positive=True)) + + +def test_failing_assumptions(): + x = Symbol('x', positive=True) + y = Symbol('y') + assert failing_assumptions(6*x + y, **x.assumptions0) == \ + {'real': None, 'imaginary': None, 'complex': None, 'hermitian': None, + 'positive': None, 'nonpositive': None, 'nonnegative': None, 'nonzero': None, + 'negative': None, 'zero': None, 'extended_real': None, 'finite': None, + 'infinite': None, 'extended_negative': None, 'extended_nonnegative': None, + 'extended_nonpositive': None, 'extended_nonzero': None, + 'extended_positive': None } + + +def test_common_assumptions(): + assert common_assumptions([0, 1, 2] + ) == {'algebraic': True, 'irrational': False, 'hermitian': + True, 'extended_real': True, 'real': True, 'extended_negative': + False, 'extended_nonnegative': True, 'integer': True, + 'rational': True, 'imaginary': False, 'complex': True, + 'commutative': True,'noninteger': False, 'composite': False, + 'infinite': False, 'nonnegative': True, 'finite': True, + 'transcendental': False,'negative': False} + assert common_assumptions([0, 1, 2], 'positive integer'.split() + ) == {'integer': True} + assert common_assumptions([0, 1, 2], []) == {} + assert common_assumptions([], ['integer']) == {} + assert common_assumptions([0], ['integer']) == {'integer': True} + +def test_pre_generated_assumption_rules_are_valid(): + # check the pre-generated assumptions match freshly generated assumptions + # if this check fails, consider updating the assumptions + # see sympy.core.assumptions._generate_assumption_rules + pre_generated_assumptions =_load_pre_generated_assumption_rules() + generated_assumptions =_generate_assumption_rules() + assert pre_generated_assumptions._to_python() == generated_assumptions._to_python(), "pre-generated assumptions are invalid, see sympy.core.assumptions._generate_assumption_rules" + + +def test_ask_shuffle(): + grp = PermutationGroup(Permutation(1, 0, 2), Permutation(2, 1, 3)) + + seed(123) + first = grp.random() + seed(123) + simplify(I) + second = grp.random() + seed(123) + simplify(-I) + third = grp.random() + + assert first == second == third diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_basic.py b/phivenv/Lib/site-packages/sympy/core/tests/test_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..3a7adbb5dcf0d70089ff79028afa943b24ee0c42 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_basic.py @@ -0,0 +1,343 @@ +"""This tests sympy/core/basic.py with (ideally) no reference to subclasses +of Basic or Atom.""" +import collections +from typing import TypeVar, Generic + +from sympy.assumptions.ask import Q +from sympy.core.basic import (Basic, Atom, as_Basic, + _atomic, _aresame) +from sympy.core.containers import Tuple +from sympy.core.function import Function, Lambda +from sympy.core.numbers import I, pi, Float +from sympy.core.singleton import S +from sympy.core.symbol import symbols, Symbol, Dummy +from sympy.concrete.summations import Sum +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral +from sympy.functions.elementary.exponential import exp +from sympy.testing.pytest import raises, warns_deprecated_sympy +from sympy.functions.elementary.complexes import Abs, sign +from sympy.functions.elementary.piecewise import Piecewise +from sympy.core.relational import Eq + +b1 = Basic() +b2 = Basic(b1) +b3 = Basic(b2) +b21 = Basic(b2, b1) +T = TypeVar('T') + + +def test__aresame(): + assert not _aresame(Basic(Tuple()), Basic()) + for i, j in [(S(2), S(2.)), (1., Float(1))]: + for do in range(2): + assert not _aresame(Basic(i), Basic(j)) + assert not _aresame(i, j) + i, j = j, i + + +def test_structure(): + assert b21.args == (b2, b1) + assert b21.func(*b21.args) == b21 + assert bool(b1) + + +def test_immutable(): + assert not hasattr(b1, '__dict__') + with raises(AttributeError): + b1.x = 1 + + +def test_equality(): + instances = [b1, b2, b3, b21, Basic(b1, b1, b1), Basic] + for i, b_i in enumerate(instances): + for j, b_j in enumerate(instances): + assert (b_i == b_j) == (i == j) + assert (b_i != b_j) == (i != j) + + assert Basic() != [] + assert not(Basic() == []) + assert Basic() != 0 + assert not(Basic() == 0) + + class Foo: + """ + Class that is unaware of Basic, and relies on both classes returning + the NotImplemented singleton for equivalence to evaluate to False. + + """ + + b = Basic() + foo = Foo() + + assert b != foo + assert foo != b + assert not b == foo + assert not foo == b + + class Bar: + """ + Class that considers itself equal to any instance of Basic, and relies + on Basic returning the NotImplemented singleton in order to achieve + a symmetric equivalence relation. + + """ + def __eq__(self, other): + if isinstance(other, Basic): + return True + return NotImplemented + + def __ne__(self, other): + return not self == other + + bar = Bar() + + assert b == bar + assert bar == b + assert not b != bar + assert not bar != b + + +def test_matches_basic(): + instances = [Basic(b1, b1, b2), Basic(b1, b2, b1), Basic(b2, b1, b1), + Basic(b1, b2), Basic(b2, b1), b2, b1] + for i, b_i in enumerate(instances): + for j, b_j in enumerate(instances): + if i == j: + assert b_i.matches(b_j) == {} + else: + assert b_i.matches(b_j) is None + assert b1.match(b1) == {} + + +def test_has(): + assert b21.has(b1) + assert b21.has(b3, b1) + assert b21.has(Basic) + assert not b1.has(b21, b3) + assert not b21.has() + assert not b21.has(str) + assert not Symbol("x").has("x") + + +def test_subs(): + assert b21.subs(b2, b1) == Basic(b1, b1) + assert b21.subs(b2, b21) == Basic(b21, b1) + assert b3.subs(b2, b1) == b2 + + assert b21.subs([(b2, b1), (b1, b2)]) == Basic(b2, b2) + + assert b21.subs({b1: b2, b2: b1}) == Basic(b2, b2) + assert b21.subs(collections.ChainMap({b1: b2}, {b2: b1})) == Basic(b2, b2) + assert b21.subs(collections.OrderedDict([(b2, b1), (b1, b2)])) == Basic(b2, b2) + + raises(ValueError, lambda: b21.subs('bad arg')) + raises(TypeError, lambda: b21.subs(b1, b2, b3)) + # dict(b1=foo) creates a string 'b1' but leaves foo unchanged; subs + # will convert the first to a symbol but will raise an error if foo + # cannot be sympified; sympification is strict if foo is not string + raises(TypeError, lambda: b21.subs(b1='bad arg')) + + assert Symbol("text").subs({"text": b1}) == b1 + assert Symbol("s").subs({"s": 1}) == 1 + + +def test_subs_with_unicode_symbols(): + expr = Symbol('var1') + replaced = expr.subs('var1', 'x') + assert replaced.name == 'x' + + replaced = expr.subs('var1', 'x') + assert replaced.name == 'x' + + +def test_atoms(): + assert b21.atoms() == {Basic()} + + +def test_free_symbols_empty(): + assert b21.free_symbols == set() + + +def test_doit(): + assert b21.doit() == b21 + assert b21.doit(deep=False) == b21 + + +def test_S(): + assert repr(S) == 'S' + + +def test_xreplace(): + assert b21.xreplace({b2: b1}) == Basic(b1, b1) + assert b21.xreplace({b2: b21}) == Basic(b21, b1) + assert b3.xreplace({b2: b1}) == b2 + assert Basic(b1, b2).xreplace({b1: b2, b2: b1}) == Basic(b2, b1) + assert Atom(b1).xreplace({b1: b2}) == Atom(b1) + assert Atom(b1).xreplace({Atom(b1): b2}) == b2 + raises(TypeError, lambda: b1.xreplace()) + raises(TypeError, lambda: b1.xreplace([b1, b2])) + for f in (exp, Function('f')): + assert f.xreplace({}) == f + assert f.xreplace({}, hack2=True) == f + assert f.xreplace({f: b1}) == b1 + assert f.xreplace({f: b1}, hack2=True) == b1 + + +def test_sorted_args(): + x = symbols('x') + assert b21._sorted_args == b21.args + raises(AttributeError, lambda: x._sorted_args) + +def test_call(): + x, y = symbols('x y') + # See the long history of this in issues 5026 and 5105. + + raises(TypeError, lambda: sin(x)({ x : 1, sin(x) : 2})) + raises(TypeError, lambda: sin(x)(1)) + + # No effect as there are no callables + assert sin(x).rcall(1) == sin(x) + assert (1 + sin(x)).rcall(1) == 1 + sin(x) + + # Effect in the presence of callables + l = Lambda(x, 2*x) + assert (l + x).rcall(y) == 2*y + x + assert (x**l).rcall(2) == x**4 + # TODO UndefinedFunction does not subclass Expr + #f = Function('f') + #assert (2*f)(x) == 2*f(x) + + assert (Q.real & Q.positive).rcall(x) == Q.real(x) & Q.positive(x) + + +def test_rewrite(): + x, y, z = symbols('x y z') + a, b = symbols('a b') + f1 = sin(x) + cos(x) + assert f1.rewrite(cos,exp) == exp(I*x)/2 + sin(x) + exp(-I*x)/2 + assert f1.rewrite([cos],sin) == sin(x) + sin(x + pi/2, evaluate=False) + f2 = sin(x) + cos(y)/gamma(z) + assert f2.rewrite(sin,exp) == -I*(exp(I*x) - exp(-I*x))/2 + cos(y)/gamma(z) + + assert f1.rewrite() == f1 + +def test_literal_evalf_is_number_is_zero_is_comparable(): + x = symbols('x') + f = Function('f') + + # issue 5033 + assert f.is_number is False + # issue 6646 + assert f(1).is_number is False + i = Integral(0, (x, x, x)) + # expressions that are symbolically 0 can be difficult to prove + # so in case there is some easy way to know if something is 0 + # it should appear in the is_zero property for that object; + # if is_zero is true evalf should always be able to compute that + # zero + assert i.n() == 0 + assert i.is_zero + assert i.is_number is False + assert i.evalf(2, strict=False) == 0 + + # issue 10268 + n = sin(1)**2 + cos(1)**2 - 1 + assert n.is_comparable is False + assert n.n(2).is_comparable is False + assert n.n(2).n(2).is_comparable + + +def test_as_Basic(): + assert as_Basic(1) is S.One + assert as_Basic(()) == Tuple() + raises(TypeError, lambda: as_Basic([])) + + +def test_atomic(): + g, h = map(Function, 'gh') + x = symbols('x') + assert _atomic(g(x + h(x))) == {g(x + h(x))} + assert _atomic(g(x + h(x)), recursive=True) == {h(x), x, g(x + h(x))} + assert _atomic(1) == set() + assert _atomic(Basic(S(1), S(2))) == set() + + +def test_as_dummy(): + u, v, x, y, z, _0, _1 = symbols('u v x y z _0 _1') + assert Lambda(x, x + 1).as_dummy() == Lambda(_0, _0 + 1) + assert Lambda(x, x + _0).as_dummy() == Lambda(_1, _0 + _1) + eq = (1 + Sum(x, (x, 1, x))) + ans = 1 + Sum(_0, (_0, 1, x)) + once = eq.as_dummy() + assert once == ans + twice = once.as_dummy() + assert twice == ans + assert Integral(x + _0, (x, x + 1), (_0, 1, 2) + ).as_dummy() == Integral(_0 + _1, (_0, x + 1), (_1, 1, 2)) + for T in (Symbol, Dummy): + d = T('x', real=True) + D = d.as_dummy() + assert D != d and D.func == Dummy and D.is_real is None + assert Dummy().as_dummy().is_commutative + assert Dummy(commutative=False).as_dummy().is_commutative is False + + +def test_canonical_variables(): + x, i0, i1 = symbols('x _:2') + assert Integral(x, (x, x + 1)).canonical_variables == {x: i0} + assert Integral(x, (x, x + 1), (i0, 1, 2)).canonical_variables == { + x: i0, i0: i1} + assert Integral(x, (x, x + i0)).canonical_variables == {x: i1} + + +def test_replace_exceptions(): + from sympy.core.symbol import Wild + x, y = symbols('x y') + e = (x**2 + x*y) + raises(TypeError, lambda: e.replace(sin, 2)) + b = Wild('b') + c = Wild('c') + raises(TypeError, lambda: e.replace(b*c, c.is_real)) + raises(TypeError, lambda: e.replace(b.is_real, 1)) + raises(TypeError, lambda: e.replace(lambda d: d.is_Number, 1)) + + +def test_ManagedProperties(): + # ManagedProperties is now deprecated. Here we do our best to check that if + # someone is using it then it does work in the way that it previously did + # but gives a deprecation warning. + from sympy.core.assumptions import ManagedProperties + + myclasses = [] + + class MyMeta(ManagedProperties): + def __init__(cls, *args, **kwargs): + myclasses.append('executed') + super().__init__(*args, **kwargs) + + code = """ +class MySubclass(Basic, metaclass=MyMeta): + pass +""" + with warns_deprecated_sympy(): + exec(code) + + assert myclasses == ['executed'] + + +def test_generic(): + # https://github.com/sympy/sympy/issues/25399 + class A(Symbol, Generic[T]): + pass + + class B(A[T]): + pass + + +def test_rewrite_abs(): + # https://github.com/sympy/sympy/issues/27323 + x = Symbol('x') + assert sign(x).rewrite(abs) == sign(x).rewrite(Abs) + assert sign(x).rewrite(abs) == Piecewise((0, Eq(x, 0)), (x / Abs(x), True)) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_cache.py b/phivenv/Lib/site-packages/sympy/core/tests/test_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..9124fca70718299252929a9923f335dde25256eb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_cache.py @@ -0,0 +1,91 @@ +import sys +from sympy.core.cache import cacheit, cached_property, lazy_function +from sympy.testing.pytest import raises + +def test_cacheit_doc(): + @cacheit + def testfn(): + "test docstring" + pass + + assert testfn.__doc__ == "test docstring" + assert testfn.__name__ == "testfn" + +def test_cacheit_unhashable(): + @cacheit + def testit(x): + return x + + assert testit(1) == 1 + assert testit(1) == 1 + a = {} + assert testit(a) == {} + a[1] = 2 + assert testit(a) == {1: 2} + +def test_cachit_exception(): + # Make sure the cache doesn't call functions multiple times when they + # raise TypeError + + a = [] + + @cacheit + def testf(x): + a.append(0) + raise TypeError + + raises(TypeError, lambda: testf(1)) + assert len(a) == 1 + + a.clear() + # Unhashable type + raises(TypeError, lambda: testf([])) + assert len(a) == 1 + + @cacheit + def testf2(x): + a.append(0) + raise TypeError("Error") + + a.clear() + raises(TypeError, lambda: testf2(1)) + assert len(a) == 1 + + a.clear() + # Unhashable type + raises(TypeError, lambda: testf2([])) + assert len(a) == 1 + +def test_cached_property(): + class A: + def __init__(self, value): + self.value = value + self.calls = 0 + + @cached_property + def prop(self): + self.calls = self.calls + 1 + return self.value + + a = A(2) + assert a.calls == 0 + assert a.prop == 2 + assert a.calls == 1 + assert a.prop == 2 + assert a.calls == 1 + b = A(None) + assert b.prop == None + + +def test_lazy_function(): + module_name='xmlrpc.client' + function_name = 'gzip_decode' + lazy = lazy_function(module_name, function_name) + assert lazy(b'') == b'' + assert module_name in sys.modules + assert function_name in str(lazy) + repr_lazy = repr(lazy) + assert 'LazyFunction' in repr_lazy + assert function_name in repr_lazy + + lazy = lazy_function('sympy.core.cache', 'cheap') diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_compatibility.py b/phivenv/Lib/site-packages/sympy/core/tests/test_compatibility.py new file mode 100644 index 0000000000000000000000000000000000000000..31d2bed07b21aa2fa489273dca9edfc9993cfd86 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_compatibility.py @@ -0,0 +1,6 @@ +from sympy.testing.pytest import warns_deprecated_sympy + +def test_compatibility_submodule(): + # Test the sympy.core.compatibility deprecation warning + with warns_deprecated_sympy(): + import sympy.core.compatibility # noqa:F401 diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_complex.py b/phivenv/Lib/site-packages/sympy/core/tests/test_complex.py new file mode 100644 index 0000000000000000000000000000000000000000..a607e0bdb4db859336aa30aa61f43bfb57d5df88 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_complex.py @@ -0,0 +1,226 @@ +from sympy.core.function import expand_complex +from sympy.core.numbers import (I, Integer, Rational, pi) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (Abs, conjugate, im, re, sign) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.hyperbolic import (cosh, coth, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, cot, sin, tan) + +def test_complex(): + a = Symbol("a", real=True) + b = Symbol("b", real=True) + e = (a + I*b)*(a - I*b) + assert e.expand() == a**2 + b**2 + assert sqrt(I) == Pow(I, S.Half) + + +def test_conjugate(): + a = Symbol("a", real=True) + b = Symbol("b", real=True) + c = Symbol("c", imaginary=True) + d = Symbol("d", imaginary=True) + x = Symbol('x') + z = a + I*b + c + I*d + zc = a - I*b - c + I*d + assert conjugate(z) == zc + assert conjugate(exp(z)) == exp(zc) + assert conjugate(exp(I*x)) == exp(-I*conjugate(x)) + assert conjugate(z**5) == zc**5 + assert conjugate(abs(x)) == abs(x) + assert conjugate(sign(z)) == sign(zc) + assert conjugate(sin(z)) == sin(zc) + assert conjugate(cos(z)) == cos(zc) + assert conjugate(tan(z)) == tan(zc) + assert conjugate(cot(z)) == cot(zc) + assert conjugate(sinh(z)) == sinh(zc) + assert conjugate(cosh(z)) == cosh(zc) + assert conjugate(tanh(z)) == tanh(zc) + assert conjugate(coth(z)) == coth(zc) + + +def test_abs1(): + a = Symbol("a", real=True) + b = Symbol("b", real=True) + assert abs(a) == Abs(a) + assert abs(-a) == abs(a) + assert abs(a + I*b) == sqrt(a**2 + b**2) + + +def test_abs2(): + a = Symbol("a", real=False) + b = Symbol("b", real=False) + assert abs(a) != a + assert abs(-a) != a + assert abs(a + I*b) != sqrt(a**2 + b**2) + + +def test_evalc(): + x = Symbol("x", real=True) + y = Symbol("y", real=True) + z = Symbol("z") + assert ((x + I*y)**2).expand(complex=True) == x**2 + 2*I*x*y - y**2 + assert expand_complex(z**(2*I)) == (re((re(z) + I*im(z))**(2*I)) + + I*im((re(z) + I*im(z))**(2*I))) + assert expand_complex( + z**(2*I), deep=False) == I*im(z**(2*I)) + re(z**(2*I)) + + assert exp(I*x) != cos(x) + I*sin(x) + assert exp(I*x).expand(complex=True) == cos(x) + I*sin(x) + assert exp(I*x + y).expand(complex=True) == exp(y)*cos(x) + I*sin(x)*exp(y) + + assert sin(I*x).expand(complex=True) == I * sinh(x) + assert sin(x + I*y).expand(complex=True) == sin(x)*cosh(y) + \ + I * sinh(y) * cos(x) + + assert cos(I*x).expand(complex=True) == cosh(x) + assert cos(x + I*y).expand(complex=True) == cos(x)*cosh(y) - \ + I * sinh(y) * sin(x) + + assert tan(I*x).expand(complex=True) == tanh(x) * I + assert tan(x + I*y).expand(complex=True) == ( + sin(2*x)/(cos(2*x) + cosh(2*y)) + + I*sinh(2*y)/(cos(2*x) + cosh(2*y))) + + assert sinh(I*x).expand(complex=True) == I * sin(x) + assert sinh(x + I*y).expand(complex=True) == sinh(x)*cos(y) + \ + I * sin(y) * cosh(x) + + assert cosh(I*x).expand(complex=True) == cos(x) + assert cosh(x + I*y).expand(complex=True) == cosh(x)*cos(y) + \ + I * sin(y) * sinh(x) + + assert tanh(I*x).expand(complex=True) == tan(x) * I + assert tanh(x + I*y).expand(complex=True) == ( + (sinh(x)*cosh(x) + I*cos(y)*sin(y)) / + (sinh(x)**2 + cos(y)**2)).expand() + + +def test_pythoncomplex(): + x = Symbol("x") + assert 4j*x != 4*x*I + assert 4j*x == 4.0*x*I + assert 4.1j*x != 4*x*I + + +def test_rootcomplex(): + R = Rational + assert ((+1 + I)**R(1, 2)).expand( + complex=True) == 2**R(1, 4)*cos( pi/8) + 2**R(1, 4)*sin( pi/8)*I + assert ((-1 - I)**R(1, 2)).expand( + complex=True) == 2**R(1, 4)*cos(3*pi/8) - 2**R(1, 4)*sin(3*pi/8)*I + assert (sqrt(-10)*I).as_real_imag() == (-sqrt(10), 0) + + +def test_expand_inverse(): + assert (1/(1 + I)).expand(complex=True) == (1 - I)/2 + assert ((1 + 2*I)**(-2)).expand(complex=True) == (-3 - 4*I)/25 + assert ((1 + I)**(-8)).expand(complex=True) == Rational(1, 16) + + +def test_expand_complex(): + assert ((2 + 3*I)**10).expand(complex=True) == -341525 - 145668*I + # the following two tests are to ensure the SymPy uses an efficient + # algorithm for calculating powers of complex numbers. They should execute + # in something like 0.01s. + assert ((2 + 3*I)**1000).expand(complex=True) == \ + -81079464736246615951519029367296227340216902563389546989376269312984127074385455204551402940331021387412262494620336565547972162814110386834027871072723273110439771695255662375718498785908345629702081336606863762777939617745464755635193139022811989314881997210583159045854968310911252660312523907616129080027594310008539817935736331124833163907518549408018652090650537035647520296539436440394920287688149200763245475036722326561143851304795139005599209239350981457301460233967137708519975586996623552182807311159141501424576682074392689622074945519232029999 + \ + 46938745946789557590804551905243206242164799136976022474337918748798900569942573265747576032611189047943842446167719177749107138603040963603119861476016947257034472364028585381714774667326478071264878108114128915685688115488744955550920239128462489496563930809677159214598114273887061533057125164518549173898349061972857446844052995037423459472376202251620778517659247970283904820245958198842631651569984310559418135975795868314764489884749573052997832686979294085577689571149679540256349988338406458116270429842222666345146926395233040564229555893248370000*I + assert ((2 + 3*I/4)**1000).expand(complex=True) == \ + Integer(1)*37079892761199059751745775382463070250205990218394308874593455293485167797989691280095867197640410033222367257278387021789651672598831503296531725827158233077451476545928116965316544607115843772405184272449644892857783761260737279675075819921259597776770965829089907990486964515784097181964312256560561065607846661496055417619388874421218472707497847700629822858068783288579581649321248495739224020822198695759609598745114438265083593711851665996586461937988748911532242908776883696631067311443171682974330675406616373422505939887984366289623091300746049101284856530270685577940283077888955692921951247230006346681086274961362500646889925803654263491848309446197554307105991537357310209426736453173441104334496173618419659521888945605315751089087820455852582920963561495787655250624781448951403353654348109893478206364632640344111022531861683064175862889459084900614967785405977231549003280842218501570429860550379522498497412180001/114813069527425452423283320117768198402231770208869520047764273682576626139237031385665948631650626991844596463898746277344711896086305533142593135616665318539129989145312280000688779148240044871428926990063486244781615463646388363947317026040466353970904996558162398808944629605623311649536164221970332681344168908984458505602379484807914058900934776500429002716706625830522008132236281291761267883317206598995396418127021779858404042159853183251540889433902091920554957783589672039160081957216630582755380425583726015528348786419432054508915275783882625175435528800822842770817965453762184851149029376 + \ + I*421638390580169706973991429333213477486930178424989246669892530737775352519112934278994501272111385966211392610029433824534634841747911783746811994443436271013377059560245191441549885048056920190833693041257216263519792201852046825443439142932464031501882145407459174948712992271510309541474392303461939389368955986650538525895866713074543004916049550090364398070215427272240155060576252568700906004691224321432509053286859100920489253598392100207663785243368195857086816912514025693453058403158416856847185079684216151337200057494966741268925263085619240941610301610538225414050394612058339070756009433535451561664522479191267503989904464718368605684297071150902631208673621618217106272361061676184840810762902463998065947687814692402219182668782278472952758690939877465065070481351343206840649517150634973307937551168752642148704904383991876969408056379195860410677814566225456558230131911142229028179902418223009651437985670625/1793954211366022694113801876840128100034871409513586250746316776290259783425578615401030447369541046747571819748417910583511123376348523955353017744010395602173906080395504375010762174191250701116076984219741972574712741619474818186676828531882286780795390571221287481389759837587864244524002565968286448146002639202882164150037179450123657170327105882819203167448541028601906377066191895183769810676831353109303069033234715310287563158747705988305326397404720186258671215368588625611876280581509852855552819149745718992630449787803625851701801184123166018366180137512856918294030710215034138299203584 + assert ((2 + 3*I)**-1000).expand(complex=True) == \ + Integer(1)*-81079464736246615951519029367296227340216902563389546989376269312984127074385455204551402940331021387412262494620336565547972162814110386834027871072723273110439771695255662375718498785908345629702081336606863762777939617745464755635193139022811989314881997210583159045854968310911252660312523907616129080027594310008539817935736331124833163907518549408018652090650537035647520296539436440394920287688149200763245475036722326561143851304795139005599209239350981457301460233967137708519975586996623552182807311159141501424576682074392689622074945519232029999/8777125472973511649630750050295188683351430110097915876250894978429797369155961290321829625004920141758416719066805645579710744290541337680113772670040386863849283653078324415471816788604945889094925784900885812724984087843737442111926413818245854362613018058774368703971604921858023116665586358870612944209398056562604561248859926344335598822815885851096698226775053153403320782439987679978321289537645645163767251396759519805603090332694449553371530571613352311006350058217982509738362083094920649452123351717366337410243853659113315547584871655479914439219520157174729130746351059075207407866012574386726064196992865627149566238044625779078186624347183905913357718850537058578084932880569701242598663149911276357125355850792073635533676541250531086757377369962506979378337216411188347761901006460813413505861461267545723590468627854202034450569581626648934062198718362303420281555886394558137408159453103395918783625713213314350531051312551733021627153081075080140680608080529736975658786227362251632725009435866547613598753584705455955419696609282059191031962604169242974038517575645939316377801594539335940001 - Integer(1)*46938745946789557590804551905243206242164799136976022474337918748798900569942573265747576032611189047943842446167719177749107138603040963603119861476016947257034472364028585381714774667326478071264878108114128915685688115488744955550920239128462489496563930809677159214598114273887061533057125164518549173898349061972857446844052995037423459472376202251620778517659247970283904820245958198842631651569984310559418135975795868314764489884749573052997832686979294085577689571149679540256349988338406458116270429842222666345146926395233040564229555893248370000*I/8777125472973511649630750050295188683351430110097915876250894978429797369155961290321829625004920141758416719066805645579710744290541337680113772670040386863849283653078324415471816788604945889094925784900885812724984087843737442111926413818245854362613018058774368703971604921858023116665586358870612944209398056562604561248859926344335598822815885851096698226775053153403320782439987679978321289537645645163767251396759519805603090332694449553371530571613352311006350058217982509738362083094920649452123351717366337410243853659113315547584871655479914439219520157174729130746351059075207407866012574386726064196992865627149566238044625779078186624347183905913357718850537058578084932880569701242598663149911276357125355850792073635533676541250531086757377369962506979378337216411188347761901006460813413505861461267545723590468627854202034450569581626648934062198718362303420281555886394558137408159453103395918783625713213314350531051312551733021627153081075080140680608080529736975658786227362251632725009435866547613598753584705455955419696609282059191031962604169242974038517575645939316377801594539335940001 + assert ((2 + 3*I/4)**-1000).expand(complex=True) == \ + Integer(1)*4257256305661027385394552848555894604806501409793288342610746813288539790051927148781268212212078237301273165351052934681382567968787279534591114913777456610214738290619922068269909423637926549603264174216950025398244509039145410016404821694746262142525173737175066432954496592560621330313807235750500564940782099283410261748370262433487444897446779072067625787246390824312580440138770014838135245148574339248259670887549732495841810961088930810608893772914812838358159009303794863047635845688453859317690488124382253918725010358589723156019888846606295866740117645571396817375322724096486161308083462637370825829567578309445855481578518239186117686659177284332344643124760453112513611749309168470605289172320376911472635805822082051716625171429727162039621902266619821870482519063133136820085579315127038372190224739238686708451840610064871885616258831386810233957438253532027049148030157164346719204500373766157143311767338973363806106967439378604898250533766359989107510507493549529158818602327525235240510049484816090584478644771183158342479140194633579061295740839490629457435283873180259847394582069479062820225159699506175855369539201399183443253793905149785994830358114153241481884290274629611529758663543080724574566578220908907477622643689220814376054314972190402285121776593824615083669045183404206291739005554569305329760211752815718335731118664756831942466773261465213581616104242113894521054475516019456867271362053692785300826523328020796670205463390909136593859765912483565093461468865534470710132881677639651348709376/2103100954337624833663208713697737151593634525061637972297915388685604042449504336765884978184588688426595940401280828953096857809292320006227881797146858511436638446932833617514351442216409828605662238790280753075176269765767010004889778647709740770757817960711900340755635772183674511158570690702969774966791073165467918123298694584729211212414462628433370481195120564586361368504153395406845170075275051749019600057116719726628746724489572189061061036426955163696859127711110719502594479795200686212257570291758725259007379710596548777812659422174199194837355646482046783616494013289495563083118517507178847555801163089723056310287760875135196081975602765511153122381201303871673391366630940702817360340900568748719988954847590748960761446218262344767250783946365392689256634180417145926390656439421745644011831124277463643383712803287985472471755648426749842410972650924240795946699346613614779460399530274263580007672855851663196114585312432954432654691485867618908420370875753749297487803461900447407917655296784879220450937110470920633595689721819488638484547259978337741496090602390463594556401615298457456112485536498177883358587125449801777718900375736758266215245325999241624148841915093787519330809347240990363802360596034171167818310322276373120180985148650099673289383722502488957717848531612020897298448601714154586319660314294591620415272119454982220034319689607295960162971300417552364254983071768070124456169427638371140064235083443242844616326538396503937972586505546495649094344512270582463639152160238137952390380581401171977159154009407415523525096743009110916334144716516647041176989758534635251844947906038080852185583742296318878233394998111078843229681030277039104786225656992262073797524057992347971177720807155842376332851559276430280477639539393920006008737472164850104411971830120295750221200029811143140323763349636629725073624360001 - Integer(1)*3098214262599218784594285246258841485430681674561917573155883806818465520660668045042109232930382494608383663464454841313154390741655282039877410154577448327874989496074260116195788919037407420625081798124301494353693248757853222257918294662198297114746822817460991242508743651430439120439020484502408313310689912381846149597061657483084652685283853595100434135149479564507015504022249330340259111426799121454516345905101620532787348293877485702600390665276070250119465888154331218827342488849948540687659846652377277250614246402784754153678374932540789808703029043827352976139228402417432199779415751301480406673762521987999573209628597459357964214510139892316208670927074795773830798600837815329291912002136924506221066071242281626618211060464126372574400100990746934953437169840312584285942093951405864225230033279614235191326102697164613004299868695519642598882914862568516635347204441042798206770888274175592401790040170576311989738272102077819127459014286741435419468254146418098278519775722104890854275995510700298782146199325790002255362719776098816136732897323406228294203133323296591166026338391813696715894870956511298793595675308998014158717167429941371979636895553724830981754579086664608880698350866487717403917070872269853194118364230971216854931998642990452908852258008095741042117326241406479532880476938937997238098399302185675832474590293188864060116934035867037219176916416481757918864533515526389079998129329045569609325290897577497835388451456680707076072624629697883854217331728051953671643278797380171857920000*I/2103100954337624833663208713697737151593634525061637972297915388685604042449504336765884978184588688426595940401280828953096857809292320006227881797146858511436638446932833617514351442216409828605662238790280753075176269765767010004889778647709740770757817960711900340755635772183674511158570690702969774966791073165467918123298694584729211212414462628433370481195120564586361368504153395406845170075275051749019600057116719726628746724489572189061061036426955163696859127711110719502594479795200686212257570291758725259007379710596548777812659422174199194837355646482046783616494013289495563083118517507178847555801163089723056310287760875135196081975602765511153122381201303871673391366630940702817360340900568748719988954847590748960761446218262344767250783946365392689256634180417145926390656439421745644011831124277463643383712803287985472471755648426749842410972650924240795946699346613614779460399530274263580007672855851663196114585312432954432654691485867618908420370875753749297487803461900447407917655296784879220450937110470920633595689721819488638484547259978337741496090602390463594556401615298457456112485536498177883358587125449801777718900375736758266215245325999241624148841915093787519330809347240990363802360596034171167818310322276373120180985148650099673289383722502488957717848531612020897298448601714154586319660314294591620415272119454982220034319689607295960162971300417552364254983071768070124456169427638371140064235083443242844616326538396503937972586505546495649094344512270582463639152160238137952390380581401171977159154009407415523525096743009110916334144716516647041176989758534635251844947906038080852185583742296318878233394998111078843229681030277039104786225656992262073797524057992347971177720807155842376332851559276430280477639539393920006008737472164850104411971830120295750221200029811143140323763349636629725073624360001 + + a = Symbol('a', real=True) + b = Symbol('b', real=True) + assert exp(a*(2 + I*b)).expand(complex=True) == \ + I*exp(2*a)*sin(a*b) + exp(2*a)*cos(a*b) + + +def test_expand(): + f = (16 - 2*sqrt(29))**2 + assert f.expand() == 372 - 64*sqrt(29) + f = (Integer(1)/2 + I/2)**10 + assert f.expand() == I/32 + f = (Integer(1)/2 + I)**10 + assert f.expand() == Integer(237)/1024 - 779*I/256 + + +def test_re_im1652(): + x = Symbol('x') + assert re(x) == re(conjugate(x)) + assert im(x) == - im(conjugate(x)) + assert im(x)*re(conjugate(x)) + im(conjugate(x)) * re(x) == 0 + + +def test_issue_5084(): + x = Symbol('x') + assert ((x + x*I)/(1 + I)).as_real_imag() == (re((x + I*x)/(1 + I) + ), im((x + I*x)/(1 + I))) + + +def test_issue_5236(): + assert (cos(1 + I)**3).as_real_imag() == (-3*sin(1)**2*sinh(1)**2*cos(1)*cosh(1) + + cos(1)**3*cosh(1)**3, -3*cos(1)**2*cosh(1)**2*sin(1)*sinh(1) + sin(1)**3*sinh(1)**3) + + +def test_real_imag(): + x, y, z = symbols('x, y, z') + X, Y, Z = symbols('X, Y, Z', commutative=False) + a = Symbol('a', real=True) + assert (2*a*x).as_real_imag() == (2*a*re(x), 2*a*im(x)) + + # issue 5395: + assert (x*x.conjugate()).as_real_imag() == (Abs(x)**2, 0) + assert im(x*x.conjugate()) == 0 + assert im(x*y.conjugate()*z*y) == im(x*z)*Abs(y)**2 + assert im(x*y.conjugate()*x*y) == im(x**2)*Abs(y)**2 + assert im(Z*y.conjugate()*X*y) == im(Z*X)*Abs(y)**2 + assert im(X*X.conjugate()) == im(X*X.conjugate(), evaluate=False) + assert (sin(x)*sin(x).conjugate()).as_real_imag() == \ + (Abs(sin(x))**2, 0) + + # issue 6573: + assert (x**2).as_real_imag() == (re(x)**2 - im(x)**2, 2*re(x)*im(x)) + + # issue 6428: + r = Symbol('r', real=True) + i = Symbol('i', imaginary=True) + assert (i*r*x).as_real_imag() == (I*i*r*im(x), -I*i*r*re(x)) + assert (i*r*x*(y + 2)).as_real_imag() == ( + I*i*r*(re(y) + 2)*im(x) + I*i*r*re(x)*im(y), + -I*i*r*(re(y) + 2)*re(x) + I*i*r*im(x)*im(y)) + + # issue 7106: + assert ((1 + I)/(1 - I)).as_real_imag() == (0, 1) + assert ((1 + 2*I)*(1 + 3*I)).as_real_imag() == (-5, 5) + + +def test_pow_issue_1724(): + e = ((S.NegativeOne)**(S.One/3)) + assert e.conjugate().n() == e.n().conjugate() + e = S('-2/3 - (-29/54 + sqrt(93)/18)**(1/3) - 1/(9*(-29/54 + sqrt(93)/18)**(1/3))') + assert e.conjugate().n() == e.n().conjugate() + e = 2**I + assert e.conjugate().n() == e.n().conjugate() + + +def test_issue_5429(): + assert sqrt(I).conjugate() != sqrt(I) + +def test_issue_4124(): + from sympy.core.numbers import oo + assert expand_complex(I*oo) == oo*I + +def test_issue_11518(): + x = Symbol("x", real=True) + y = Symbol("y", real=True) + r = sqrt(x**2 + y**2) + assert conjugate(r) == r + s = abs(x + I * y) + assert conjugate(s) == r diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_constructor_postprocessor.py b/phivenv/Lib/site-packages/sympy/core/tests/test_constructor_postprocessor.py new file mode 100644 index 0000000000000000000000000000000000000000..c199e24eddf8ef7c2a14e38d1ad2dc95e4acc0cc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_constructor_postprocessor.py @@ -0,0 +1,87 @@ +from sympy.core.basic import Basic +from sympy.core.mul import Mul +from sympy.core.symbol import (Symbol, symbols) + +from sympy.testing.pytest import XFAIL + +class SymbolInMulOnce(Symbol): + # Test class for a symbol that can only appear once in a `Mul` expression. + pass + + +Basic._constructor_postprocessor_mapping[SymbolInMulOnce] = { + "Mul": [lambda x: x], + "Pow": [lambda x: x.base if isinstance(x.base, SymbolInMulOnce) else x], + "Add": [lambda x: x], +} + + +def _postprocess_SymbolRemovesOtherSymbols(expr): + args = tuple(i for i in expr.args if not isinstance(i, Symbol) or isinstance(i, SymbolRemovesOtherSymbols)) + if args == expr.args: + return expr + return Mul.fromiter(args) + + +class SymbolRemovesOtherSymbols(Symbol): + # Test class for a symbol that removes other symbols in `Mul`. + pass + +Basic._constructor_postprocessor_mapping[SymbolRemovesOtherSymbols] = { + "Mul": [_postprocess_SymbolRemovesOtherSymbols], +} + +class SubclassSymbolInMulOnce(SymbolInMulOnce): + pass + +class SubclassSymbolRemovesOtherSymbols(SymbolRemovesOtherSymbols): + pass + + +def test_constructor_postprocessors1(): + x = SymbolInMulOnce("x") + y = SymbolInMulOnce("y") + assert isinstance(3*x, Mul) + assert (3*x).args == (3, x) + assert x*x == x + assert 3*x*x == 3*x + assert 2*x*x + x == 3*x + assert x**3*y*y == x*y + assert x**5 + y*x**3 == x + x*y + + w = SymbolRemovesOtherSymbols("w") + assert x*w == w + assert (3*w).args == (3, w) + assert set((w + x).args) == {x, w} + +def test_constructor_postprocessors2(): + x = SubclassSymbolInMulOnce("x") + y = SubclassSymbolInMulOnce("y") + assert isinstance(3*x, Mul) + assert (3*x).args == (3, x) + assert x*x == x + assert 3*x*x == 3*x + assert 2*x*x + x == 3*x + assert x**3*y*y == x*y + assert x**5 + y*x**3 == x + x*y + + w = SubclassSymbolRemovesOtherSymbols("w") + assert x*w == w + assert (3*w).args == (3, w) + assert set((w + x).args) == {x, w} + + +@XFAIL +def test_subexpression_postprocessors(): + # The postprocessors used to work with subexpressions, but the + # functionality was removed. See #15948. + a = symbols("a") + x = SymbolInMulOnce("x") + w = SymbolRemovesOtherSymbols("w") + assert 3*a*w**2 == 3*w**2 + assert 3*a*x**3*w**2 == 3*w**2 + + x = SubclassSymbolInMulOnce("x") + w = SubclassSymbolRemovesOtherSymbols("w") + assert 3*a*w**2 == 3*w**2 + assert 3*a*x**3*w**2 == 3*w**2 diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_containers.py b/phivenv/Lib/site-packages/sympy/core/tests/test_containers.py new file mode 100644 index 0000000000000000000000000000000000000000..23357b9f667fffc82d93b2b1adb42b495114c67e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_containers.py @@ -0,0 +1,217 @@ +from collections import defaultdict + +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.numbers import Integer +from sympy.core.kind import NumberKind +from sympy.matrices.kind import MatrixKind +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.core.sympify import sympify +from sympy.matrices.dense import Matrix +from sympy.sets.sets import FiniteSet +from sympy.core.containers import tuple_wrapper, TupleKind +from sympy.core.expr import unchanged +from sympy.core.function import Function, Lambda +from sympy.core.relational import Eq +from sympy.testing.pytest import raises +from sympy.utilities.iterables import is_sequence, iterable + +from sympy.abc import x, y + + +def test_Tuple(): + t = (1, 2, 3, 4) + st = Tuple(*t) + assert set(sympify(t)) == set(st) + assert len(t) == len(st) + assert set(sympify(t[:2])) == set(st[:2]) + assert isinstance(st[:], Tuple) + assert st == Tuple(1, 2, 3, 4) + assert st.func(*st.args) == st + p, q, r, s = symbols('p q r s') + t2 = (p, q, r, s) + st2 = Tuple(*t2) + assert st2.atoms() == set(t2) + assert st == st2.subs({p: 1, q: 2, r: 3, s: 4}) + # issue 5505 + assert all(isinstance(arg, Basic) for arg in st.args) + assert Tuple(p, 1).subs(p, 0) == Tuple(0, 1) + assert Tuple(p, Tuple(p, 1)).subs(p, 0) == Tuple(0, Tuple(0, 1)) + + assert Tuple(t2) == Tuple(Tuple(*t2)) + assert Tuple.fromiter(t2) == Tuple(*t2) + assert Tuple.fromiter(x for x in range(4)) == Tuple(0, 1, 2, 3) + assert st2.fromiter(st2.args) == st2 + + +def test_Tuple_contains(): + t1, t2 = Tuple(1), Tuple(2) + assert t1 in Tuple(1, 2, 3, t1, Tuple(t2)) + assert t2 not in Tuple(1, 2, 3, t1, Tuple(t2)) + + +def test_Tuple_concatenation(): + assert Tuple(1, 2) + Tuple(3, 4) == Tuple(1, 2, 3, 4) + assert (1, 2) + Tuple(3, 4) == Tuple(1, 2, 3, 4) + assert Tuple(1, 2) + (3, 4) == Tuple(1, 2, 3, 4) + raises(TypeError, lambda: Tuple(1, 2) + 3) + raises(TypeError, lambda: 1 + Tuple(2, 3)) + + #the Tuple case in __radd__ is only reached when a subclass is involved + class Tuple2(Tuple): + def __radd__(self, other): + return Tuple.__radd__(self, other + other) + assert Tuple(1, 2) + Tuple2(3, 4) == Tuple(1, 2, 1, 2, 3, 4) + assert Tuple2(1, 2) + Tuple(3, 4) == Tuple(1, 2, 3, 4) + + +def test_Tuple_equality(): + assert not isinstance(Tuple(1, 2), tuple) + assert (Tuple(1, 2) == (1, 2)) is True + assert (Tuple(1, 2) != (1, 2)) is False + assert (Tuple(1, 2) == (1, 3)) is False + assert (Tuple(1, 2) != (1, 3)) is True + assert (Tuple(1, 2) == Tuple(1, 2)) is True + assert (Tuple(1, 2) != Tuple(1, 2)) is False + assert (Tuple(1, 2) == Tuple(1, 3)) is False + assert (Tuple(1, 2) != Tuple(1, 3)) is True + + +def test_Tuple_Eq(): + assert Eq(Tuple(), Tuple()) is S.true + assert Eq(Tuple(1), 1) is S.false + assert Eq(Tuple(1, 2), Tuple(1)) is S.false + assert Eq(Tuple(1), Tuple(1)) is S.true + assert Eq(Tuple(1, 2), Tuple(1, 3)) is S.false + assert Eq(Tuple(1, 2), Tuple(1, 2)) is S.true + assert unchanged(Eq, Tuple(1, x), Tuple(1, 2)) + assert Eq(Tuple(1, x), Tuple(1, 2)).subs(x, 2) is S.true + assert unchanged(Eq, Tuple(1, 2), x) + f = Function('f') + assert unchanged(Eq, Tuple(1), f(x)) + assert Eq(Tuple(1), f(x)).subs(x, 1).subs(f, Lambda(y, (y,))) is S.true + + +def test_Tuple_comparision(): + assert (Tuple(1, 3) >= Tuple(-10, 30)) is S.true + assert (Tuple(1, 3) <= Tuple(-10, 30)) is S.false + assert (Tuple(1, 3) >= Tuple(1, 3)) is S.true + assert (Tuple(1, 3) <= Tuple(1, 3)) is S.true + + +def test_Tuple_tuple_count(): + assert Tuple(0, 1, 2, 3).tuple_count(4) == 0 + assert Tuple(0, 4, 1, 2, 3).tuple_count(4) == 1 + assert Tuple(0, 4, 1, 4, 2, 3).tuple_count(4) == 2 + assert Tuple(0, 4, 1, 4, 2, 4, 3).tuple_count(4) == 3 + + +def test_Tuple_index(): + assert Tuple(4, 0, 1, 2, 3).index(4) == 0 + assert Tuple(0, 4, 1, 2, 3).index(4) == 1 + assert Tuple(0, 1, 4, 2, 3).index(4) == 2 + assert Tuple(0, 1, 2, 4, 3).index(4) == 3 + assert Tuple(0, 1, 2, 3, 4).index(4) == 4 + + raises(ValueError, lambda: Tuple(0, 1, 2, 3).index(4)) + raises(ValueError, lambda: Tuple(4, 0, 1, 2, 3).index(4, 1)) + raises(ValueError, lambda: Tuple(0, 1, 2, 3, 4).index(4, 1, 4)) + + +def test_Tuple_mul(): + assert Tuple(1, 2, 3)*2 == Tuple(1, 2, 3, 1, 2, 3) + assert 2*Tuple(1, 2, 3) == Tuple(1, 2, 3, 1, 2, 3) + assert Tuple(1, 2, 3)*Integer(2) == Tuple(1, 2, 3, 1, 2, 3) + assert Integer(2)*Tuple(1, 2, 3) == Tuple(1, 2, 3, 1, 2, 3) + + raises(TypeError, lambda: Tuple(1, 2, 3)*S.Half) + raises(TypeError, lambda: S.Half*Tuple(1, 2, 3)) + + +def test_tuple_wrapper(): + + @tuple_wrapper + def wrap_tuples_and_return(*t): + return t + + p = symbols('p') + assert wrap_tuples_and_return(p, 1) == (p, 1) + assert wrap_tuples_and_return((p, 1)) == (Tuple(p, 1),) + assert wrap_tuples_and_return(1, (p, 2), 3) == (1, Tuple(p, 2), 3) + + +def test_iterable_is_sequence(): + ordered = [[], (), Tuple(), Matrix([[]])] + unordered = [set()] + not_sympy_iterable = [{}, '', ''] + assert all(is_sequence(i) for i in ordered) + assert all(not is_sequence(i) for i in unordered) + assert all(iterable(i) for i in ordered + unordered) + assert all(not iterable(i) for i in not_sympy_iterable) + assert all(iterable(i, exclude=None) for i in not_sympy_iterable) + + +def test_TupleKind(): + kind = TupleKind(NumberKind, MatrixKind(NumberKind)) + assert Tuple(1, Matrix([1, 2])).kind is kind + assert Tuple(1, 2).kind is TupleKind(NumberKind, NumberKind) + assert Tuple(1, 2).kind.element_kind == (NumberKind, NumberKind) + + +def test_Dict(): + x, y, z = symbols('x y z') + d = Dict({x: 1, y: 2, z: 3}) + assert d[x] == 1 + assert d[y] == 2 + raises(KeyError, lambda: d[2]) + raises(KeyError, lambda: d['2']) + assert len(d) == 3 + assert set(d.keys()) == {x, y, z} + assert set(d.values()) == {S.One, S(2), S(3)} + assert d.get(5, 'default') == 'default' + assert d.get('5', 'default') == 'default' + assert x in d and z in d and 5 not in d and '5' not in d + assert d.has(x) and d.has(1) # SymPy Basic .has method + + # Test input types + # input - a Python dict + # input - items as args - SymPy style + assert (Dict({x: 1, y: 2, z: 3}) == + Dict((x, 1), (y, 2), (z, 3))) + + raises(TypeError, lambda: Dict(((x, 1), (y, 2), (z, 3)))) + with raises(NotImplementedError): + d[5] = 6 # assert immutability + + assert set( + d.items()) == {Tuple(x, S.One), Tuple(y, S(2)), Tuple(z, S(3))} + assert set(d) == {x, y, z} + assert str(d) == '{x: 1, y: 2, z: 3}' + assert d.__repr__() == '{x: 1, y: 2, z: 3}' + + # Test creating a Dict from a Dict. + d = Dict({x: 1, y: 2, z: 3}) + assert d == Dict(d) + + # Test for supporting defaultdict + d = defaultdict(int) + assert d[x] == 0 + assert d[y] == 0 + assert d[z] == 0 + assert Dict(d) + d = Dict(d) + assert len(d) == 3 + assert set(d.keys()) == {x, y, z} + assert set(d.values()) == {S.Zero, S.Zero, S.Zero} + + +def test_issue_5788(): + args = [(1, 2), (2, 1)] + for o in [Dict, Tuple, FiniteSet]: + # __eq__ and arg handling + if o != Tuple: + assert o(*args) == o(*reversed(args)) + pair = [o(*args), o(*reversed(args))] + assert sorted(pair) == sorted(pair) + assert set(o(*args)) # doesn't fail diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_count_ops.py b/phivenv/Lib/site-packages/sympy/core/tests/test_count_ops.py new file mode 100644 index 0000000000000000000000000000000000000000..bc95004ef5ba4421927289a049a9197d208c30d0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_count_ops.py @@ -0,0 +1,155 @@ +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.function import (Derivative, Function, count_ops) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import (Eq, Rel) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (And, Equivalent, ITE, Implies, Nand, + Nor, Not, Or, Xor) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.core.containers import Tuple + +x, y, z = symbols('x,y,z') +a, b, c = symbols('a,b,c') + +def test_count_ops_non_visual(): + def count(val): + return count_ops(val, visual=False) + assert count(x) == 0 + assert count(x) is not S.Zero + assert count(x + y) == 1 + assert count(x + y) is not S.One + assert count(x + y*x + 2*y) == 4 + assert count({x + y: x}) == 1 + assert count({x + y: S(2) + x}) is not S.One + assert count(x < y) == 1 + assert count(Or(x,y)) == 1 + assert count(And(x,y)) == 1 + assert count(Not(x)) == 1 + assert count(Nor(x,y)) == 2 + assert count(Nand(x,y)) == 2 + assert count(Xor(x,y)) == 1 + assert count(Implies(x,y)) == 1 + assert count(Equivalent(x,y)) == 1 + assert count(ITE(x,y,z)) == 1 + assert count(ITE(True,x,y)) == 0 + + +def test_count_ops_visual(): + ADD, MUL, POW, SIN, COS, EXP, AND, D, G, M = symbols( + 'Add Mul Pow sin cos exp And Derivative Integral Sum'.upper()) + DIV, SUB, NEG = symbols('DIV SUB NEG') + LT, LE, GT, GE, EQ, NE = symbols('LT LE GT GE EQ NE') + NOT, OR, AND, XOR, IMPLIES, EQUIVALENT, _ITE, BASIC, TUPLE = symbols( + 'Not Or And Xor Implies Equivalent ITE Basic Tuple'.upper()) + + def count(val): + return count_ops(val, visual=True) + + assert count(7) is S.Zero + assert count(S(7)) is S.Zero + assert count(-1) == NEG + assert count(-2) == NEG + assert count(S(2)/3) == DIV + assert count(Rational(2, 3)) == DIV + assert count(pi/3) == DIV + assert count(-pi/3) == DIV + NEG + assert count(I - 1) == SUB + assert count(1 - I) == SUB + assert count(1 - 2*I) == SUB + MUL + + assert count(x) is S.Zero + assert count(-x) == NEG + assert count(-2*x/3) == NEG + DIV + MUL + assert count(Rational(-2, 3)*x) == NEG + DIV + MUL + assert count(1/x) == DIV + assert count(1/(x*y)) == DIV + MUL + assert count(-1/x) == NEG + DIV + assert count(-2/x) == NEG + DIV + assert count(x/y) == DIV + assert count(-x/y) == NEG + DIV + + assert count(x**2) == POW + assert count(-x**2) == POW + NEG + assert count(-2*x**2) == POW + MUL + NEG + + assert count(x + pi/3) == ADD + DIV + assert count(x + S.One/3) == ADD + DIV + assert count(x + Rational(1, 3)) == ADD + DIV + assert count(x + y) == ADD + assert count(x - y) == SUB + assert count(y - x) == SUB + assert count(-1/(x - y)) == DIV + NEG + SUB + assert count(-1/(y - x)) == DIV + NEG + SUB + assert count(1 + x**y) == ADD + POW + assert count(1 + x + y) == 2*ADD + assert count(1 + x + y + z) == 3*ADD + assert count(1 + x**y + 2*x*y + y**2) == 3*ADD + 2*POW + 2*MUL + assert count(2*z + y + x + 1) == 3*ADD + MUL + assert count(2*z + y**17 + x + 1) == 3*ADD + MUL + POW + assert count(2*z + y**17 + x + sin(x)) == 3*ADD + POW + MUL + SIN + assert count(2*z + y**17 + x + sin(x**2)) == 3*ADD + MUL + 2*POW + SIN + assert count(2*z + y**17 + x + sin( + x**2) + exp(cos(x))) == 4*ADD + MUL + 2*POW + EXP + COS + SIN + + assert count(Derivative(x, x)) == D + assert count(Integral(x, x) + 2*x/(1 + x)) == G + DIV + MUL + 2*ADD + assert count(Sum(x, (x, 1, x + 1)) + 2*x/(1 + x)) == M + DIV + MUL + 3*ADD + assert count(Basic()) is S.Zero + + assert count({x + 1: sin(x)}) == ADD + SIN + assert count([x + 1, sin(x) + y, None]) == ADD + SIN + ADD + assert count({x + 1: sin(x), y: cos(x) + 1}) == SIN + COS + 2*ADD + assert count({}) is S.Zero + assert count([x + 1, sin(x)*y, None]) == SIN + ADD + MUL + assert count([]) is S.Zero + + assert count(Basic()) == 0 + assert count(Basic(Basic(),Basic(x,x+y))) == ADD + 2*BASIC + assert count(Basic(x, x + y)) == ADD + BASIC + assert [count(Rel(x, y, op)) for op in '< <= > >= == <> !='.split() + ] == [LT, LE, GT, GE, EQ, NE, NE] + assert count(Or(x, y)) == OR + assert count(And(x, y)) == AND + assert count(Or(x, Or(y, And(z, a)))) == AND + OR + assert count(Nor(x, y)) == NOT + OR + assert count(Nand(x, y)) == NOT + AND + assert count(Xor(x, y)) == XOR + assert count(Implies(x, y)) == IMPLIES + assert count(Equivalent(x, y)) == EQUIVALENT + assert count(ITE(x, y, z)) == _ITE + assert count([Or(x, y), And(x, y), Basic(x + y)] + ) == ADD + AND + BASIC + OR + + assert count(Basic(Tuple(x))) == BASIC + TUPLE + #It checks that TUPLE is counted as an operation. + + assert count(Eq(x + y, S(2))) == ADD + EQ + + +def test_issue_9324(): + def count(val): + return count_ops(val, visual=False) + + M = MatrixSymbol('M', 10, 10) + assert count(M[0, 0]) == 0 + assert count(2 * M[0, 0] + M[5, 7]) == 2 + P = MatrixSymbol('P', 3, 3) + Q = MatrixSymbol('Q', 3, 3) + assert count(P + Q) == 1 + m = Symbol('m', integer=True) + n = Symbol('n', integer=True) + M = MatrixSymbol('M', m + n, m * m) + assert count(M[0, 1]) == 2 + + +def test_issue_21532(): + f = Function('f') + g = Function('g') + FUNC_F, FUNC_G = symbols('FUNC_F, FUNC_G') + assert f(x).count_ops(visual=True) == FUNC_F + assert g(x).count_ops(visual=True) == FUNC_G diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_diff.py b/phivenv/Lib/site-packages/sympy/core/tests/test_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..effc9cd91d2e7b6f8f8e5fd04bb667ed71c0ffaf --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_diff.py @@ -0,0 +1,160 @@ +from sympy.concrete.summations import Sum +from sympy.core.expr import Expr +from sympy.core.function import (Derivative, Function, diff, Subs) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (cos, cot, sin, tan) +from sympy.tensor.array.ndim_array import NDimArray +from sympy.testing.pytest import raises +from sympy.abc import a, b, c, x, y, z + +def test_diff(): + assert Rational(1, 3).diff(x) is S.Zero + assert I.diff(x) is S.Zero + assert pi.diff(x) is S.Zero + assert x.diff(x, 0) == x + assert (x**2).diff(x, 2, x) == 0 + assert (x**2).diff((x, 2), x) == 0 + assert (x**2).diff((x, 1), x) == 2 + assert (x**2).diff((x, 1), (x, 1)) == 2 + assert (x**2).diff((x, 2)) == 2 + assert (x**2).diff(x, y, 0) == 2*x + assert (x**2).diff(x, (y, 0)) == 2*x + assert (x**2).diff(x, y) == 0 + raises(ValueError, lambda: x.diff(1, x)) + + p = Rational(5) + e = a*b + b**p + assert e.diff(a) == b + assert e.diff(b) == a + 5*b**4 + assert e.diff(b).diff(a) == Rational(1) + e = a*(b + c) + assert e.diff(a) == b + c + assert e.diff(b) == a + assert e.diff(b).diff(a) == Rational(1) + e = c**p + assert e.diff(c, 6) == Rational(0) + assert e.diff(c, 5) == Rational(120) + e = c**Rational(2) + assert e.diff(c) == 2*c + e = a*b*c + assert e.diff(c) == a*b + + +def test_diff2(): + n3 = Rational(3) + n2 = Rational(2) + n6 = Rational(6) + + e = n3*(-n2 + x**n2)*cos(x) + x*(-n6 + x**n2)*sin(x) + assert e == 3*(-2 + x**2)*cos(x) + x*(-6 + x**2)*sin(x) + assert e.diff(x).expand() == x**3*cos(x) + + e = (x + 1)**3 + assert e.diff(x) == 3*(x + 1)**2 + e = x*(x + 1)**3 + assert e.diff(x) == (x + 1)**3 + 3*x*(x + 1)**2 + e = 2*exp(x*x)*x + assert e.diff(x) == 2*exp(x**2) + 4*x**2*exp(x**2) + + +def test_diff3(): + p = Rational(5) + e = a*b + sin(b**p) + assert e == a*b + sin(b**5) + assert e.diff(a) == b + assert e.diff(b) == a + 5*b**4*cos(b**5) + e = tan(c) + assert e == tan(c) + assert e.diff(c) in [cos(c)**(-2), 1 + sin(c)**2/cos(c)**2, 1 + tan(c)**2] + e = c*log(c) - c + assert e == -c + c*log(c) + assert e.diff(c) == log(c) + e = log(sin(c)) + assert e == log(sin(c)) + assert e.diff(c) in [sin(c)**(-1)*cos(c), cot(c)] + e = (Rational(2)**a/log(Rational(2))) + assert e == 2**a*log(Rational(2))**(-1) + assert e.diff(a) == 2**a + + +def test_diff_no_eval_derivative(): + class My(Expr): + def __new__(cls, x): + return Expr.__new__(cls, x) + + # My doesn't have its own _eval_derivative method + assert My(x).diff(x).func is Derivative + assert My(x).diff(x, 3).func is Derivative + assert re(x).diff(x, 2) == Derivative(re(x), (x, 2)) # issue 15518 + assert diff(NDimArray([re(x), im(x)]), (x, 2)) == NDimArray( + [Derivative(re(x), (x, 2)), Derivative(im(x), (x, 2))]) + # it doesn't have y so it shouldn't need a method for this case + assert My(x).diff(y) == 0 + + +def test_speed(): + # this should return in 0.0s. If it takes forever, it's wrong. + assert x.diff(x, 10**8) == 0 + + +def test_deriv_noncommutative(): + A = Symbol("A", commutative=False) + f = Function("f") + assert A*f(x)*A == f(x)*A**2 + assert A*f(x).diff(x)*A == f(x).diff(x) * A**2 + + +def test_diff_nth_derivative(): + f = Function("f") + n = Symbol("n", integer=True) + + expr = diff(sin(x), (x, n)) + expr2 = diff(f(x), (x, 2)) + expr3 = diff(f(x), (x, n)) + + assert expr.subs(sin(x), cos(-x)) == Derivative(cos(-x), (x, n)) + assert expr.subs(n, 1).doit() == cos(x) + assert expr.subs(n, 2).doit() == -sin(x) + + assert expr2.subs(Derivative(f(x), x), y) == Derivative(y, x) + # Currently not supported (cannot determine if `n > 1`): + #assert expr3.subs(Derivative(f(x), x), y) == Derivative(y, (x, n-1)) + assert expr3 == Derivative(f(x), (x, n)) + + assert diff(x, (x, n)) == Piecewise((x, Eq(n, 0)), (1, Eq(n, 1)), (0, True)) + assert diff(2*x, (x, n)).dummy_eq( + Sum(Piecewise((2*x*factorial(n)/(factorial(y)*factorial(-y + n)), + Eq(y, 0) & Eq(Max(0, -y + n), 0)), + (2*factorial(n)/(factorial(y)*factorial(-y + n)), Eq(y, 0) & Eq(Max(0, + -y + n), 1)), (0, True)), (y, 0, n))) + # TODO: assert diff(x**2, (x, n)) == x**(2-n)*ff(2, n) + exprm = x*sin(x) + mul_diff = diff(exprm, (x, n)) + assert isinstance(mul_diff, Sum) + for i in range(5): + assert mul_diff.subs(n, i).doit() == exprm.diff((x, i)).expand() + + exprm2 = 2*y*x*sin(x)*cos(x)*log(x)*exp(x) + dex = exprm2.diff((x, n)) + assert isinstance(dex, Sum) + for i in range(7): + assert dex.subs(n, i).doit().expand() == \ + exprm2.diff((x, i)).expand() + + assert (cos(x)*sin(y)).diff([[x, y, z]]) == NDimArray([ + -sin(x)*sin(y), cos(x)*cos(y), 0]) + + +def test_issue_16160(): + assert Derivative(x**3, (x, x)).subs(x, 2) == Subs( + Derivative(x**3, (x, 2)), x, 2) + assert Derivative(1 + x**3, (x, x)).subs(x, 0 + ) == Derivative(1 + y**3, (y, 0)).subs(y, 0) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_equal.py b/phivenv/Lib/site-packages/sympy/core/tests/test_equal.py new file mode 100644 index 0000000000000000000000000000000000000000..82213b757cda5fbd80310e387bdf00cc1c9c25fe --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_equal.py @@ -0,0 +1,89 @@ +from sympy.core.numbers import Rational +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.elementary.exponential import exp + + +def test_equal(): + b = Symbol("b") + a = Symbol("a") + e1 = a + b + e2 = 2*a*b + e3 = a**3*b**2 + e4 = a*b + b*a + assert not e1 == e2 + assert not e1 == e2 + assert e1 != e2 + assert e2 == e4 + assert e2 != e3 + assert not e2 == e3 + + x = Symbol("x") + e1 = exp(x + 1/x) + y = Symbol("x") + e2 = exp(y + 1/y) + assert e1 == e2 + assert not e1 != e2 + y = Symbol("y") + e2 = exp(y + 1/y) + assert not e1 == e2 + assert e1 != e2 + + e5 = Rational(3) + 2*x - x - x + assert e5 == 3 + assert 3 == e5 + assert e5 != 4 + assert 4 != e5 + assert e5 != 3 + x + assert 3 + x != e5 + + +def test_expevalbug(): + x = Symbol("x") + e1 = exp(1*x) + e3 = exp(x) + assert e1 == e3 + + +def test_cmp_bug1(): + class T: + pass + + t = T() + x = Symbol("x") + + assert not (x == t) + assert (x != t) + + +def test_cmp_bug2(): + class T: + pass + + t = T() + + assert not (Symbol == t) + assert (Symbol != t) + + +def test_cmp_issue_4357(): + """ Check that Basic subclasses can be compared with sympifiable objects. + + https://github.com/sympy/sympy/issues/4357 + """ + assert not (Symbol == 1) + assert (Symbol != 1) + assert not (Symbol == 'x') + assert (Symbol != 'x') + + +def test_dummy_eq(): + x = Symbol('x') + y = Symbol('y') + + u = Dummy('u') + + assert (u**2 + 1).dummy_eq(x**2 + 1) is True + assert ((u**2 + 1) == (x**2 + 1)) is False + + assert (u**2 + y).dummy_eq(x**2 + y, x) is True + assert (u**2 + y).dummy_eq(x**2 + y, y) is False diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_eval.py b/phivenv/Lib/site-packages/sympy/core/tests/test_eval.py new file mode 100644 index 0000000000000000000000000000000000000000..9c1633f77b50483afee21c6d9fca232b1279d2b9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_eval.py @@ -0,0 +1,95 @@ +from sympy.core.function import Function +from sympy.core.numbers import (I, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, tan) +from sympy.testing.pytest import XFAIL + + +def test_add_eval(): + a = Symbol("a") + b = Symbol("b") + c = Rational(1) + p = Rational(5) + assert a*b + c + p == a*b + 6 + assert c + a + p == a + 6 + assert c + a - p == a + (-4) + assert a + a == 2*a + assert a + p + a == 2*a + 5 + assert c + p == Rational(6) + assert b + a - b == a + + +def test_addmul_eval(): + a = Symbol("a") + b = Symbol("b") + c = Rational(1) + p = Rational(5) + assert c + a + b*c + a - p == 2*a + b + (-4) + assert a*2 + p + a == a*2 + 5 + a + assert a*2 + p + a == 3*a + 5 + assert a*2 + a == 3*a + + +def test_pow_eval(): + # XXX Pow does not fully support conversion of negative numbers + # to their complex equivalent + + assert sqrt(-1) == I + + assert sqrt(-4) == 2*I + assert sqrt( 4) == 2 + assert (8)**Rational(1, 3) == 2 + assert (-8)**Rational(1, 3) == 2*((-1)**Rational(1, 3)) + + assert sqrt(-2) == I*sqrt(2) + assert (-1)**Rational(1, 3) != I + assert (-10)**Rational(1, 3) != I*((10)**Rational(1, 3)) + assert (-2)**Rational(1, 4) != (2)**Rational(1, 4) + + assert 64**Rational(1, 3) == 4 + assert 64**Rational(2, 3) == 16 + assert 24/sqrt(64) == 3 + assert (-27)**Rational(1, 3) == 3*(-1)**Rational(1, 3) + + assert (cos(2) / tan(2))**2 == (cos(2) / tan(2))**2 + + +@XFAIL +def test_pow_eval_X1(): + assert (-1)**Rational(1, 3) == S.Half + S.Half*I*sqrt(3) + + +def test_mulpow_eval(): + x = Symbol('x') + assert sqrt(50)/(sqrt(2)*x) == 5/x + assert sqrt(27)/sqrt(3) == 3 + + +def test_evalpow_bug(): + x = Symbol("x") + assert 1/(1/x) == x + assert 1/(-1/x) == -x + + +def test_symbol_expand(): + x = Symbol('x') + y = Symbol('y') + + f = x**4*y**4 + assert f == x**4*y**4 + assert f == f.expand() + + g = (x*y)**4 + assert g == f + assert g.expand() == f + assert g.expand() == g.expand().expand() + + +def test_function(): + f, l = map(Function, 'fl') + x = Symbol('x') + assert exp(l(x))*l(x)/exp(l(x)) == l(x) + assert exp(f(x))*f(x)/exp(f(x)) == f(x) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_evalf.py b/phivenv/Lib/site-packages/sympy/core/tests/test_evalf.py new file mode 100644 index 0000000000000000000000000000000000000000..2c3c26a2d265da9ea2daa73a9eea3091b2af1999 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_evalf.py @@ -0,0 +1,738 @@ +import math + +from sympy.concrete.products import (Product, product) +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.evalf import N +from sympy.core.function import (Function, nfloat) +from sympy.core.mul import Mul +from sympy.core import (GoldenRatio) +from sympy.core.numbers import (AlgebraicNumber, E, Float, I, Rational, + oo, zoo, nan, pi) +from sympy.core.power import Pow +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.combinatorial.numbers import fibonacci +from sympy.functions.elementary.complexes import (Abs, re, im) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (acosh, cosh) +from sympy.functions.elementary.integers import (ceiling, floor) +from sympy.functions.elementary.miscellaneous import (Max, sqrt) +from sympy.functions.elementary.trigonometric import (acos, atan, cos, sin, tan) +from sympy.integrals.integrals import (Integral, integrate) +from sympy.polys.polytools import factor +from sympy.polys.rootoftools import CRootOf +from sympy.polys.specialpolys import cyclotomic_poly +from sympy.printing import srepr +from sympy.printing.str import sstr +from sympy.simplify.simplify import simplify +from sympy.core.numbers import comp +from sympy.core.evalf import (complex_accuracy, PrecisionExhausted, + scaled_zero, get_integer_part, as_mpmath, evalf, _evalf_with_bounded_error) +from mpmath import inf, ninf, make_mpc +from mpmath.libmp.libmpf import from_float, fzero +from sympy.core.expr import unchanged +from sympy.testing.pytest import raises, XFAIL +from sympy.abc import n, x, y + + +def NS(e, n=15, **options): + return sstr(sympify(e).evalf(n, **options), full_prec=True) + + +def test_evalf_helpers(): + from mpmath.libmp import finf + assert complex_accuracy((from_float(2.0), None, 35, None)) == 35 + assert complex_accuracy((from_float(2.0), from_float(10.0), 35, 100)) == 37 + assert complex_accuracy( + (from_float(2.0), from_float(1000.0), 35, 100)) == 43 + assert complex_accuracy((from_float(2.0), from_float(10.0), 100, 35)) == 35 + assert complex_accuracy( + (from_float(2.0), from_float(1000.0), 100, 35)) == 35 + assert complex_accuracy(finf) == math.inf + assert complex_accuracy(zoo) == math.inf + raises(ValueError, lambda: get_integer_part(zoo, 1, {})) + + +def test_evalf_basic(): + assert NS('pi', 15) == '3.14159265358979' + assert NS('2/3', 10) == '0.6666666667' + assert NS('355/113-pi', 6) == '2.66764e-7' + assert NS('16*atan(1/5)-4*atan(1/239)', 15) == '3.14159265358979' + + +def test_cancellation(): + assert NS(Add(pi, Rational(1, 10**1000), -pi, evaluate=False), 15, + maxn=1200) == '1.00000000000000e-1000' + + +def test_evalf_powers(): + assert NS('pi**(10**20)', 10) == '1.339148777e+49714987269413385435' + assert NS(pi**(10**100), 10) == ('4.946362032e+4971498726941338543512682882' + '9089887365167832438044244613405349992494711208' + '95526746555473864642912223') + assert NS('2**(1/10**50)', 15) == '1.00000000000000' + assert NS('2**(1/10**50)-1', 15) == '6.93147180559945e-51' + +# Evaluation of Rump's ill-conditioned polynomial + + +def test_evalf_rump(): + a = 1335*y**6/4 + x**2*(11*x**2*y**2 - y**6 - 121*y**4 - 2) + 11*y**8/2 + x/(2*y) + assert NS(a, 15, subs={x: 77617, y: 33096}) == '-0.827396059946821' + + +def test_evalf_complex(): + assert NS('2*sqrt(pi)*I', 10) == '3.544907702*I' + assert NS('3+3*I', 15) == '3.00000000000000 + 3.00000000000000*I' + assert NS('E+pi*I', 15) == '2.71828182845905 + 3.14159265358979*I' + assert NS('pi * (3+4*I)', 15) == '9.42477796076938 + 12.5663706143592*I' + assert NS('I*(2+I)', 15) == '-1.00000000000000 + 2.00000000000000*I' + + +@XFAIL +def test_evalf_complex_bug(): + assert NS('(pi+E*I)*(E+pi*I)', 15) in ('0.e-15 + 17.25866050002*I', + '0.e-17 + 17.25866050002*I', '-0.e-17 + 17.25866050002*I') + + +def test_evalf_complex_powers(): + assert NS('(E+pi*I)**100000000000000000') == \ + '-3.58896782867793e+61850354284995199 + 4.58581754997159e+61850354284995199*I' + # XXX: rewrite if a+a*I simplification introduced in SymPy + #assert NS('(pi + pi*I)**2') in ('0.e-15 + 19.7392088021787*I', '0.e-16 + 19.7392088021787*I') + assert NS('(pi + pi*I)**2', chop=True) == '19.7392088021787*I' + assert NS( + '(pi + 1/10**8 + pi*I)**2') == '6.2831853e-8 + 19.7392088650106*I' + assert NS('(pi + 1/10**12 + pi*I)**2') == '6.283e-12 + 19.7392088021850*I' + assert NS('(pi + pi*I)**4', chop=True) == '-389.636364136010' + assert NS( + '(pi + 1/10**8 + pi*I)**4') == '-389.636366616512 + 2.4805021e-6*I' + assert NS('(pi + 1/10**12 + pi*I)**4') == '-389.636364136258 + 2.481e-10*I' + assert NS( + '(10000*pi + 10000*pi*I)**4', chop=True) == '-3.89636364136010e+18' + + +@XFAIL +def test_evalf_complex_powers_bug(): + assert NS('(pi + pi*I)**4') == '-389.63636413601 + 0.e-14*I' + + +def test_evalf_exponentiation(): + assert NS(sqrt(-pi)) == '1.77245385090552*I' + assert NS(Pow(pi*I, Rational( + 1, 2), evaluate=False)) == '1.25331413731550 + 1.25331413731550*I' + assert NS(pi**I) == '0.413292116101594 + 0.910598499212615*I' + assert NS(pi**(E + I/3)) == '20.8438653991931 + 8.36343473930031*I' + assert NS((pi + I/3)**(E + I/3)) == '17.2442906093590 + 13.6839376767037*I' + assert NS(exp(pi)) == '23.1406926327793' + assert NS(exp(pi + E*I)) == '-21.0981542849657 + 9.50576358282422*I' + assert NS(pi**pi) == '36.4621596072079' + assert NS((-pi)**pi) == '-32.9138577418939 - 15.6897116534332*I' + assert NS((-pi)**(-pi)) == '-0.0247567717232697 + 0.0118013091280262*I' + +# An example from Smith, "Multiple Precision Complex Arithmetic and Functions" + + +def test_evalf_complex_cancellation(): + A = Rational('63287/100000') + B = Rational('52498/100000') + C = Rational('69301/100000') + D = Rational('83542/100000') + F = Rational('2231321613/2500000000') + # XXX: the number of returned mantissa digits in the real part could + # change with the implementation. What matters is that the returned digits are + # correct; those that are showing now are correct. + # >>> ((A+B*I)*(C+D*I)).expand() + # 64471/10000000000 + 2231321613*I/2500000000 + # >>> 2231321613*4 + # 8925286452L + assert NS((A + B*I)*(C + D*I), 6) == '6.44710e-6 + 0.892529*I' + assert NS((A + B*I)*(C + D*I), 10) == '6.447100000e-6 + 0.8925286452*I' + assert NS((A + B*I)*( + C + D*I) - F*I, 5) in ('6.4471e-6 + 0.e-14*I', '6.4471e-6 - 0.e-14*I') + + +def test_evalf_logs(): + assert NS("log(3+pi*I)", 15) == '1.46877619736226 + 0.808448792630022*I' + assert NS("log(pi*I)", 15) == '1.14472988584940 + 1.57079632679490*I' + assert NS('log(-1 + 0.00001)', 2) == '-1.0e-5 + 3.1*I' + assert NS('log(100, 10, evaluate=False)', 15) == '2.00000000000000' + assert NS('-2*I*log(-(-1)**(S(1)/9))', 15) == '-5.58505360638185' + + +def test_evalf_trig(): + assert NS('sin(1)', 15) == '0.841470984807897' + assert NS('cos(1)', 15) == '0.540302305868140' + assert NS('tan(1)', 15) == '1.55740772465490' + assert NS('sin(10**-6)', 15) == '9.99999999999833e-7' + assert NS('cos(10**-6)', 15) == '0.999999999999500' + assert NS('tan(10**-6)', 15) == '1.00000000000033e-6' + assert NS('sin(E*10**100)', 15) == '0.409160531722613' + assert NS('tan(I)',15) =='0.761594155955765*I' + assert NS('tan(1000*I)',15)== '1.00000000000000*I' + # Some input near roots + assert NS(sin(exp(pi*sqrt(163))*pi), 15) == '-2.35596641936785e-12' + assert NS(sin(pi*10**100 + Rational(7, 10**5), evaluate=False), 15, maxn=120) == \ + '6.99999999428333e-5' + assert NS(sin(Rational(7, 10**5), evaluate=False), 15) == \ + '6.99999999428333e-5' + +# Check detection of various false identities + + +def test_evalf_near_integers(): + # Binet's formula + f = lambda n: ((1 + sqrt(5))**n)/(2**n * sqrt(5)) + assert NS(f(5000) - fibonacci(5000), 10, maxn=1500) == '5.156009964e-1046' + # Some near-integer identities from + # http://mathworld.wolfram.com/AlmostInteger.html + assert NS('sin(2017*2**(1/5))', 15) == '-1.00000000000000' + assert NS('sin(2017*2**(1/5))', 20) == '-0.99999999999999997857' + assert NS('1+sin(2017*2**(1/5))', 15) == '2.14322287389390e-17' + assert NS('45 - 613*E/37 + 35/991', 15) == '6.03764498766326e-11' + + +def test_evalf_ramanujan(): + assert NS(exp(pi*sqrt(163)) - 640320**3 - 744, 10) == '-7.499274028e-13' + # A related identity + A = 262537412640768744*exp(-pi*sqrt(163)) + B = 196884*exp(-2*pi*sqrt(163)) + C = 103378831900730205293632*exp(-3*pi*sqrt(163)) + assert NS(1 - A - B + C, 10) == '1.613679005e-59' + +# Input that for various reasons have failed at some point + + +def test_evalf_bugs(): + assert NS(sin(1) + exp(-10**10), 10) == NS(sin(1), 10) + assert NS(exp(10**10) + sin(1), 10) == NS(exp(10**10), 10) + assert NS('expand_log(log(1+1/10**50))', 20) == '1.0000000000000000000e-50' + assert NS('log(10**100,10)', 10) == '100.0000000' + assert NS('log(2)', 10) == '0.6931471806' + assert NS( + '(sin(x)-x)/x**3', 15, subs={x: '1/10**50'}) == '-0.166666666666667' + assert NS(sin(1) + Rational( + 1, 10**100)*I, 15) == '0.841470984807897 + 1.00000000000000e-100*I' + assert x.evalf() == x + assert NS((1 + I)**2*I, 6) == '-2.00000' + d = {n: ( + -1)**Rational(6, 7), y: (-1)**Rational(4, 7), x: (-1)**Rational(2, 7)} + assert NS((x*(1 + y*(1 + n))).subs(d).evalf(), 6) == '0.346011 + 0.433884*I' + assert NS(((-I - sqrt(2)*I)**2).evalf()) == '-5.82842712474619' + assert NS((1 + I)**2*I, 15) == '-2.00000000000000' + # issue 4758 (1/2): + assert NS(pi.evalf(69) - pi) == '-4.43863937855894e-71' + # issue 4758 (2/2): With the bug present, this still only fails if the + # terms are in the order given here. This is not generally the case, + # because the order depends on the hashes of the terms. + assert NS(20 - 5008329267844*n**25 - 477638700*n**37 - 19*n, + subs={n: .01}) == '19.8100000000000' + assert NS(((x - 1)*(1 - x)**1000).n() + ) == '(1.00000000000000 - x)**1000*(x - 1.00000000000000)' + assert NS((-x).n()) == '-x' + assert NS((-2*x).n()) == '-2.00000000000000*x' + assert NS((-2*x*y).n()) == '-2.00000000000000*x*y' + assert cos(x).n(subs={x: 1+I}) == cos(x).subs(x, 1+I).n() + # issue 6660. Also NaN != mpmath.nan + # In this order: + # 0*nan, 0/nan, 0*inf, 0/inf + # 0+nan, 0-nan, 0+inf, 0-inf + # >>> n = Some Number + # n*nan, n/nan, n*inf, n/inf + # n+nan, n-nan, n+inf, n-inf + assert (0*E**(oo)).n() is S.NaN + assert (0/E**(oo)).n() is S.Zero + + assert (0+E**(oo)).n() is S.Infinity + assert (0-E**(oo)).n() is S.NegativeInfinity + + assert (5*E**(oo)).n() is S.Infinity + assert (5/E**(oo)).n() is S.Zero + + assert (5+E**(oo)).n() is S.Infinity + assert (5-E**(oo)).n() is S.NegativeInfinity + + #issue 7416 + assert as_mpmath(0.0, 10, {'chop': True}) == 0 + + #issue 5412 + assert ((oo*I).n() == S.Infinity*I) + assert ((oo+oo*I).n() == S.Infinity + S.Infinity*I) + + #issue 11518 + assert NS(2*x**2.5, 5) == '2.0000*x**2.5000' + + #issue 13076 + assert NS(Mul(Max(0, y), x, evaluate=False).evalf()) == 'x*Max(0, y)' + + #issue 18516 + assert NS(log(S(3273390607896141870013189696827599152216642046043064789483291368096133796404674554883270092325904157150886684127560071009217256545885393053328527589376)/36360291795869936842385267079543319118023385026001623040346035832580600191583895484198508262979388783308179702534403855752855931517013066142992430916562025780021771247847643450125342836565813209972590371590152578728008385990139795377610001).evalf(15, chop=True)) == '-oo' + + +def test_evalf_integer_parts(): + a = floor(log(8)/log(2) - exp(-1000), evaluate=False) + b = floor(log(8)/log(2), evaluate=False) + assert a.evalf() == 3.0 + assert b.evalf() == 3.0 + # equals, as a fallback, can still fail but it might succeed as here + assert ceiling(10*(sin(1)**2 + cos(1)**2)) == 10 + + assert int(floor(factorial(50)/E, evaluate=False).evalf(70)) == \ + int(11188719610782480504630258070757734324011354208865721592720336800) + assert int(ceiling(factorial(50)/E, evaluate=False).evalf(70)) == \ + int(11188719610782480504630258070757734324011354208865721592720336801) + assert int(floor(GoldenRatio**999 / sqrt(5) + S.Half) + .evalf(1000)) == fibonacci(999) + assert int(floor(GoldenRatio**1000 / sqrt(5) + S.Half) + .evalf(1000)) == fibonacci(1000) + + assert ceiling(x).evalf(subs={x: 3}) == 3.0 + assert ceiling(x).evalf(subs={x: 3*I}) == 3.0*I + assert ceiling(x).evalf(subs={x: 2 + 3*I}) == 2.0 + 3.0*I + assert ceiling(x).evalf(subs={x: 3.}) == 3.0 + assert ceiling(x).evalf(subs={x: 3.*I}) == 3.0*I + assert ceiling(x).evalf(subs={x: 2. + 3*I}) == 2.0 + 3.0*I + + assert float((floor(1.5, evaluate=False)+1/9).evalf()) == 1 + 1/9 + assert float((floor(0.5, evaluate=False)+20).evalf()) == 20 + + # issue 19991 + n = 1169809367327212570704813632106852886389036911 + r = 744723773141314414542111064094745678855643068 + + assert floor(n / (pi / 2)) == r + assert floor(80782 * sqrt(2)) == 114242 + + # issue 20076 + assert 260515 - floor(260515/pi + 1/2) * pi == atan(tan(260515)) + + assert floor(x).evalf(subs={x: sqrt(2)}) == 1.0 + + +def test_evalf_trig_zero_detection(): + a = sin(160*pi, evaluate=False) + t = a.evalf(maxn=100) + assert abs(t) < 1e-100 + assert t._prec < 2 + assert a.evalf(chop=True) == 0 + raises(PrecisionExhausted, lambda: a.evalf(strict=True)) + + +def test_evalf_sum(): + assert Sum(n,(n,1,2)).evalf() == 3. + assert Sum(n,(n,1,2)).doit().evalf() == 3. + # the next test should return instantly + assert Sum(1/n,(n,1,2)).evalf() == 1.5 + + # issue 8219 + assert Sum(E/factorial(n), (n, 0, oo)).evalf() == (E*E).evalf() + # issue 8254 + assert Sum(2**n*n/factorial(n), (n, 0, oo)).evalf() == (2*E*E).evalf() + # issue 8411 + s = Sum(1/x**2, (x, 100, oo)) + assert s.n() == s.doit().n() + + +def test_evalf_divergent_series(): + raises(ValueError, lambda: Sum(1/n, (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum(n/(n**2 + 1), (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum((-1)**n, (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum((-1)**n, (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum(n**2, (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum(2**n, (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum((-2)**n, (n, 1, oo)).evalf()) + raises(ValueError, lambda: Sum((2*n + 3)/(3*n**2 + 4), (n, 0, oo)).evalf()) + raises(ValueError, lambda: Sum((0.5*n**3)/(n**4 + 1), (n, 0, oo)).evalf()) + + +def test_evalf_product(): + assert Product(n, (n, 1, 10)).evalf() == 3628800. + assert comp(Product(1 - S.Half**2/n**2, (n, 1, oo)).n(5), 0.63662) + assert Product(n, (n, -1, 3)).evalf() == 0 + + +def test_evalf_py_methods(): + assert abs(float(pi + 1) - 4.1415926535897932) < 1e-10 + assert abs(complex(pi + 1) - 4.1415926535897932) < 1e-10 + assert abs( + complex(pi + E*I) - (3.1415926535897931 + 2.7182818284590451j)) < 1e-10 + raises(TypeError, lambda: float(pi + x)) + + +def test_evalf_power_subs_bugs(): + assert (x**2).evalf(subs={x: 0}) == 0 + assert sqrt(x).evalf(subs={x: 0}) == 0 + assert (x**Rational(2, 3)).evalf(subs={x: 0}) == 0 + assert (x**x).evalf(subs={x: 0}) == 1.0 + assert (3**x).evalf(subs={x: 0}) == 1.0 + assert exp(x).evalf(subs={x: 0}) == 1.0 + assert ((2 + I)**x).evalf(subs={x: 0}) == 1.0 + assert (0**x).evalf(subs={x: 0}) == 1.0 + + +def test_evalf_arguments(): + raises(TypeError, lambda: pi.evalf(method="garbage")) + + +def test_implemented_function_evalf(): + from sympy.utilities.lambdify import implemented_function + f = Function('f') + f = implemented_function(f, lambda x: x + 1) + assert str(f(x)) == "f(x)" + assert str(f(2)) == "f(2)" + assert f(2).evalf() == 3.0 + assert f(x).evalf() == f(x) + f = implemented_function(Function('sin'), lambda x: x + 1) + assert f(2).evalf() != sin(2) + del f._imp_ # XXX: due to caching _imp_ would influence all other tests + + +def test_evaluate_false(): + for no in [0, False]: + assert Add(3, 2, evaluate=no).is_Add + assert Mul(3, 2, evaluate=no).is_Mul + assert Pow(3, 2, evaluate=no).is_Pow + assert Pow(y, 2, evaluate=True) - Pow(y, 2, evaluate=True) == 0 + + +def test_evalf_relational(): + assert Eq(x/5, y/10).evalf() == Eq(0.2*x, 0.1*y) + # if this first assertion fails it should be replaced with + # one that doesn't + assert unchanged(Eq, (3 - I)**2/2 + I, 0) + assert Eq((3 - I)**2/2 + I, 0).n() is S.false + assert nfloat(Eq((3 - I)**2 + I, 0)) == S.false + + +def test_issue_5486(): + assert not cos(sqrt(0.5 + I)).n().is_Function + + +def test_issue_5486_bug(): + from sympy.core.expr import Expr + from sympy.core.numbers import I + assert abs(Expr._from_mpmath(I._to_mpmath(15), 15) - I) < 1.0e-15 + + +def test_bugs(): + from sympy.functions.elementary.complexes import (polar_lift, re) + + assert abs(re((1 + I)**2)) < 1e-15 + + # anything that evalf's to 0 will do in place of polar_lift + assert abs(polar_lift(0)).n() == 0 + + +def test_subs(): + assert NS('besseli(-x, y) - besseli(x, y)', subs={x: 3.5, y: 20.0}) == \ + '-4.92535585957223e-10' + assert NS('Piecewise((x, x>0)) + Piecewise((1-x, x>0))', subs={x: 0.1}) == \ + '1.00000000000000' + raises(TypeError, lambda: x.evalf(subs=(x, 1))) + + +def test_issue_4956_5204(): + # issue 4956 + v = S('''(-27*12**(1/3)*sqrt(31)*I + + 27*2**(2/3)*3**(1/3)*sqrt(31)*I)/(-2511*2**(2/3)*3**(1/3) + + (29*18**(1/3) + 9*2**(1/3)*3**(2/3)*sqrt(31)*I + + 87*2**(1/3)*3**(1/6)*I)**2)''') + assert NS(v, 1) == '0.e-118 - 0.e-118*I' + + # issue 5204 + v = S('''-(357587765856 + 18873261792*249**(1/2) + 56619785376*I*83**(1/2) + + 108755765856*I*3**(1/2) + 41281887168*6**(1/3)*(1422 + + 54*249**(1/2))**(1/3) - 1239810624*6**(1/3)*249**(1/2)*(1422 + + 54*249**(1/2))**(1/3) - 3110400000*I*6**(1/3)*83**(1/2)*(1422 + + 54*249**(1/2))**(1/3) + 13478400000*I*3**(1/2)*6**(1/3)*(1422 + + 54*249**(1/2))**(1/3) + 1274950152*6**(2/3)*(1422 + + 54*249**(1/2))**(2/3) + 32347944*6**(2/3)*249**(1/2)*(1422 + + 54*249**(1/2))**(2/3) - 1758790152*I*3**(1/2)*6**(2/3)*(1422 + + 54*249**(1/2))**(2/3) - 304403832*I*6**(2/3)*83**(1/2)*(1422 + + 4*249**(1/2))**(2/3))/(175732658352 + (1106028 + 25596*249**(1/2) + + 76788*I*83**(1/2))**2)''') + assert NS(v, 5) == '0.077284 + 1.1104*I' + assert NS(v, 1) == '0.08 + 1.*I' + + +def test_old_docstring(): + a = (E + pi*I)*(E - pi*I) + assert NS(a) == '17.2586605000200' + assert a.n() == 17.25866050002001 + + +def test_issue_4806(): + assert integrate(atan(x)**2, (x, -1, 1)).evalf().round(1) == Float(0.5, 1) + assert atan(0, evaluate=False).n() == 0 + + +def test_evalf_mul(): + # SymPy should not try to expand this; it should be handled term-wise + # in evalf through mpmath + assert NS(product(1 + sqrt(n)*I, (n, 1, 500)), 1) == '5.e+567 + 2.e+568*I' + + +def test_scaled_zero(): + a, b = (([0], 1, 100, 1), -1) + assert scaled_zero(100) == (a, b) + assert scaled_zero(a) == (0, 1, 100, 1) + a, b = (([1], 1, 100, 1), -1) + assert scaled_zero(100, -1) == (a, b) + assert scaled_zero(a) == (1, 1, 100, 1) + raises(ValueError, lambda: scaled_zero(scaled_zero(100))) + raises(ValueError, lambda: scaled_zero(100, 2)) + raises(ValueError, lambda: scaled_zero(100, 0)) + raises(ValueError, lambda: scaled_zero((1, 5, 1, 3))) + + +def test_chop_value(): + for i in range(-27, 28): + assert (Pow(10, i)*2).n(chop=10**i) and not (Pow(10, i)).n(chop=10**i) + + +def test_infinities(): + assert oo.evalf(chop=True) == inf + assert (-oo).evalf(chop=True) == ninf + + +def test_to_mpmath(): + assert sqrt(3)._to_mpmath(20)._mpf_ == (0, int(908093), -19, 20) + assert S(3.2)._to_mpmath(20)._mpf_ == (0, int(838861), -18, 20) + + +def test_issue_6632_evalf(): + add = (-100000*sqrt(2500000001) + 5000000001) + assert add.n() == 9.999999998e-11 + assert (add*add).n() == 9.999999996e-21 + + +def test_issue_4945(): + from sympy.abc import H + assert (H/0).evalf(subs={H:1}) == zoo + + +def test_evalf_integral(): + # test that workprec has to increase in order to get a result other than 0 + eps = Rational(1, 1000000) + assert Integral(sin(x), (x, -pi, pi + eps)).n(2)._prec == 10 + + +def test_issue_8821_highprec_from_str(): + s = str(pi.evalf(128)) + p = N(s) + assert Abs(sin(p)) < 1e-15 + p = N(s, 64) + assert Abs(sin(p)) < 1e-64 + + +def test_issue_8853(): + p = Symbol('x', even=True, positive=True) + assert floor(-p - S.Half).is_even == False + assert floor(-p + S.Half).is_even == True + assert ceiling(p - S.Half).is_even == True + assert ceiling(p + S.Half).is_even == False + + assert get_integer_part(S.Half, -1, {}, True) == (0, 0) + assert get_integer_part(S.Half, 1, {}, True) == (1, 0) + assert get_integer_part(Rational(-1, 2), -1, {}, True) == (-1, 0) + assert get_integer_part(Rational(-1, 2), 1, {}, True) == (0, 0) + + +def test_issue_17681(): + class identity_func(Function): + + def _eval_evalf(self, *args, **kwargs): + return self.args[0].evalf(*args, **kwargs) + + assert floor(identity_func(S(0))) == 0 + assert get_integer_part(S(0), 1, {}, True) == (0, 0) + + +def test_issue_9326(): + from sympy.core.symbol import Dummy + d1 = Dummy('d') + d2 = Dummy('d') + e = d1 + d2 + assert e.evalf(subs = {d1: 1, d2: 2}) == 3.0 + + +def test_issue_10323(): + assert ceiling(sqrt(2**30 + 1)) == 2**15 + 1 + + +def test_AssocOp_Function(): + # the first arg of Min is not comparable in the imaginary part + raises(ValueError, lambda: S(''' + Min(-sqrt(3)*cos(pi/18)/6 + re(1/((-1/2 - sqrt(3)*I/2)*(1/6 + + sqrt(3)*I/18)**(1/3)))/3 + sin(pi/18)/2 + 2 + I*(-cos(pi/18)/2 - + sqrt(3)*sin(pi/18)/6 + im(1/((-1/2 - sqrt(3)*I/2)*(1/6 + + sqrt(3)*I/18)**(1/3)))/3), re(1/((-1/2 + sqrt(3)*I/2)*(1/6 + + sqrt(3)*I/18)**(1/3)))/3 - sqrt(3)*cos(pi/18)/6 - sin(pi/18)/2 + 2 + + I*(im(1/((-1/2 + sqrt(3)*I/2)*(1/6 + sqrt(3)*I/18)**(1/3)))/3 - + sqrt(3)*sin(pi/18)/6 + cos(pi/18)/2))''')) + # if that is changed so a non-comparable number remains as + # an arg, then the Min/Max instantiation needs to be changed + # to watch out for non-comparable args when making simplifications + # and the following test should be added instead (with e being + # the sympified expression above): + # raises(ValueError, lambda: e._eval_evalf(2)) + + +def test_issue_10395(): + eq = x*Max(0, y) + assert nfloat(eq) == eq + eq = x*Max(y, -1.1) + assert nfloat(eq) == eq + assert Max(y, 4).n() == Max(4.0, y) + + +def test_issue_13098(): + assert floor(log(S('9.'+'9'*20), 10)) == 0 + assert ceiling(log(S('9.'+'9'*20), 10)) == 1 + assert floor(log(20 - S('9.'+'9'*20), 10)) == 1 + assert ceiling(log(20 - S('9.'+'9'*20), 10)) == 2 + + +def test_issue_14601(): + e = 5*x*y/2 - y*(35*(x**3)/2 - 15*x/2) + subst = {x:0.0, y:0.0} + e2 = e.evalf(subs=subst) + assert float(e2) == 0.0 + assert float((x + x*(x**2 + x)).evalf(subs={x: 0.0})) == 0.0 + + +def test_issue_11151(): + z = S.Zero + e = Sum(z, (x, 1, 2)) + assert e != z # it shouldn't evaluate + # when it does evaluate, this is what it should give + assert evalf(e, 15, {}) == \ + evalf(z, 15, {}) == (None, None, 15, None) + # so this shouldn't fail + assert (e/2).n() == 0 + # this was where the issue appeared + expr0 = Sum(x**2 + x, (x, 1, 2)) + expr1 = Sum(0, (x, 1, 2)) + expr2 = expr1/expr0 + assert simplify(factor(expr2) - expr2) == 0 + + +def test_issue_13425(): + assert N('2**.5', 30) == N('sqrt(2)', 30) + assert N('x - x', 30) == 0 + assert abs((N('pi*.1', 22)*10 - pi).n()) < 1e-22 + + +def test_issue_17421(): + assert N(acos(-I + acosh(cosh(cosh(1) + I)))) == 1.0*I + + +def test_issue_20291(): + from sympy.sets import EmptySet, Reals + from sympy.sets.sets import (Complement, FiniteSet, Intersection) + a = Symbol('a') + b = Symbol('b') + A = FiniteSet(a, b) + assert A.evalf(subs={a: 1, b: 2}) == FiniteSet(1.0, 2.0) + B = FiniteSet(a-b, 1) + assert B.evalf(subs={a: 1, b: 2}) == FiniteSet(-1.0, 1.0) + + sol = Complement(Intersection(FiniteSet(-b/2 - sqrt(b**2-4*pi)/2), Reals), FiniteSet(0)) + assert sol.evalf(subs={b: 1}) == EmptySet + + +def test_evalf_with_zoo(): + assert (1/x).evalf(subs={x: 0}) == zoo # issue 8242 + assert (-1/x).evalf(subs={x: 0}) == zoo # PR 16150 + assert (0 ** x).evalf(subs={x: -1}) == zoo # PR 16150 + assert (0 ** x).evalf(subs={x: -1 + I}) == nan + assert Mul(2, Pow(0, -1, evaluate=False), evaluate=False).evalf() == zoo # issue 21147 + assert Mul(x, 1/x, evaluate=False).evalf(subs={x: 0}) == Mul(x, 1/x, evaluate=False).subs(x, 0) == nan + assert Mul(1/x, 1/x, evaluate=False).evalf(subs={x: 0}) == zoo + assert Mul(1/x, Abs(1/x), evaluate=False).evalf(subs={x: 0}) == zoo + assert Abs(zoo, evaluate=False).evalf() == oo + assert re(zoo, evaluate=False).evalf() == nan + assert im(zoo, evaluate=False).evalf() == nan + assert Add(zoo, zoo, evaluate=False).evalf() == nan + assert Add(oo, zoo, evaluate=False).evalf() == nan + assert Pow(zoo, -1, evaluate=False).evalf() == 0 + assert Pow(zoo, Rational(-1, 3), evaluate=False).evalf() == 0 + assert Pow(zoo, Rational(1, 3), evaluate=False).evalf() == zoo + assert Pow(zoo, S.Half, evaluate=False).evalf() == zoo + assert Pow(zoo, 2, evaluate=False).evalf() == zoo + assert Pow(0, zoo, evaluate=False).evalf() == nan + assert log(zoo, evaluate=False).evalf() == zoo + assert zoo.evalf(chop=True) == zoo + assert x.evalf(subs={x: zoo}) == zoo + + +def test_evalf_with_bounded_error(): + cases = [ + # zero + (Rational(0), None, 1), + # zero im part + (pi, None, 10), + # zero real part + (pi*I, None, 10), + # re and im nonzero + (2-3*I, None, 5), + # similar tests again, but using eps instead of m + (Rational(0), Rational(1, 2), None), + (pi, Rational(1, 1000), None), + (pi * I, Rational(1, 1000), None), + (2 - 3 * I, Rational(1, 1000), None), + # very large eps + (2 - 3 * I, Rational(1000), None), + # case where x already small, hence some cancellation in p = m + n - 1 + (Rational(1234, 10**8), Rational(1, 10**12), None), + ] + for x0, eps, m in cases: + a, b, _, _ = evalf(x0, 53, {}) + c, d, _, _ = _evalf_with_bounded_error(x0, eps, m) + if eps is None: + eps = 2**(-m) + z = make_mpc((a or fzero, b or fzero)) + w = make_mpc((c or fzero, d or fzero)) + assert abs(w - z) < eps + + # eps must be positive + raises(ValueError, lambda: _evalf_with_bounded_error(pi, Rational(0))) + raises(ValueError, lambda: _evalf_with_bounded_error(pi, -pi)) + raises(ValueError, lambda: _evalf_with_bounded_error(pi, I)) + + +def test_issue_22849(): + a = -8 + 3 * sqrt(3) + x = AlgebraicNumber(a) + assert evalf(a, 1, {}) == evalf(x, 1, {}) + + +def test_evalf_real_alg_num(): + # This test demonstrates why the entry for `AlgebraicNumber` in + # `sympy.core.evalf._create_evalf_table()` has to use `x.to_root()`, + # instead of `x.as_expr()`. If the latter is used, then `z` will be + # a complex number with `0.e-20` for imaginary part, even though `a5` + # is a real number. + zeta = Symbol('zeta') + a5 = AlgebraicNumber(CRootOf(cyclotomic_poly(5), -1), [-1, -1, 0, 0], alias=zeta) + z = a5.evalf() + assert isinstance(z, Float) + assert not hasattr(z, '_mpc_') + assert hasattr(z, '_mpf_') + + +def test_issue_20733(): + expr = 1/((x - 9)*(x - 8)*(x - 7)*(x - 4)**2*(x - 3)**3*(x - 2)) + assert str(expr.evalf(1, subs={x:1})) == '-4.e-5' + assert str(expr.evalf(2, subs={x:1})) == '-4.1e-5' + assert str(expr.evalf(11, subs={x:1})) == '-4.1335978836e-5' + assert str(expr.evalf(20, subs={x:1})) == '-0.000041335978835978835979' + + expr = Mul(*((x - i) for i in range(2, 1000))) + assert srepr(expr.evalf(2, subs={x: 1})) == "Float('4.0271e+2561', precision=10)" + assert srepr(expr.evalf(10, subs={x: 1})) == "Float('4.02790050126e+2561', precision=37)" + assert srepr(expr.evalf(53, subs={x: 1})) == "Float('4.0279005012722099453824067459760158730668154575647110393e+2561', precision=179)" diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_expand.py b/phivenv/Lib/site-packages/sympy/core/tests/test_expand.py new file mode 100644 index 0000000000000000000000000000000000000000..e7abb5daacebbe81664b3de3a7ac35a490ab31bc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_expand.py @@ -0,0 +1,364 @@ +from sympy.core.expr import unchanged +from sympy.core.mul import Mul +from sympy.core.numbers import (I, Rational as R, pi) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.series.order import O +from sympy.simplify.radsimp import expand_numer +from sympy.core.function import (expand, expand_multinomial, + expand_power_base, expand_log) + +from sympy.testing.pytest import raises +from sympy.core.random import verify_numerically + +from sympy.abc import x, y, z + + +def test_expand_no_log(): + assert ( + (1 + log(x**4))**2).expand(log=False) == 1 + 2*log(x**4) + log(x**4)**2 + assert ((1 + log(x**4))*(1 + log(x**3))).expand( + log=False) == 1 + log(x**4) + log(x**3) + log(x**4)*log(x**3) + + +def test_expand_no_multinomial(): + assert ((1 + x)*(1 + (1 + x)**4)).expand(multinomial=False) == \ + 1 + x + (1 + x)**4 + x*(1 + x)**4 + + +def test_expand_negative_integer_powers(): + expr = (x + y)**(-2) + assert expr.expand() == 1 / (2*x*y + x**2 + y**2) + assert expr.expand(multinomial=False) == (x + y)**(-2) + expr = (x + y)**(-3) + assert expr.expand() == 1 / (3*x*x*y + 3*x*y*y + x**3 + y**3) + assert expr.expand(multinomial=False) == (x + y)**(-3) + expr = (x + y)**(2) * (x + y)**(-4) + assert expr.expand() == 1 / (2*x*y + x**2 + y**2) + assert expr.expand(multinomial=False) == (x + y)**(-2) + + +def test_expand_non_commutative(): + A = Symbol('A', commutative=False) + B = Symbol('B', commutative=False) + C = Symbol('C', commutative=False) + a = Symbol('a') + b = Symbol('b') + i = Symbol('i', integer=True) + n = Symbol('n', negative=True) + m = Symbol('m', negative=True) + p = Symbol('p', polar=True) + np = Symbol('p', polar=False) + + assert (C*(A + B)).expand() == C*A + C*B + assert (C*(A + B)).expand() != A*C + B*C + assert ((A + B)**2).expand() == A**2 + A*B + B*A + B**2 + assert ((A + B)**3).expand() == (A**2*B + B**2*A + A*B**2 + B*A**2 + + A**3 + B**3 + A*B*A + B*A*B) + # issue 6219 + assert ((a*A*B*A**-1)**2).expand() == a**2*A*B**2/A + # Note that (a*A*B*A**-1)**2 is automatically converted to a**2*(A*B*A**-1)**2 + assert ((a*A*B*A**-1)**2).expand(deep=False) == a**2*(A*B*A**-1)**2 + assert ((a*A*B*A**-1)**2).expand() == a**2*(A*B**2*A**-1) + assert ((a*A*B*A**-1)**2).expand(force=True) == a**2*A*B**2*A**(-1) + assert ((a*A*B)**2).expand() == a**2*A*B*A*B + assert ((a*A)**2).expand() == a**2*A**2 + assert ((a*A*B)**i).expand() == a**i*(A*B)**i + assert ((a*A*(B*(A*B/A)**2))**i).expand() == a**i*(A*B*A*B**2/A)**i + # issue 6558 + assert (A*B*(A*B)**-1).expand() == 1 + assert ((a*A)**i).expand() == a**i*A**i + assert ((a*A*B*A**-1)**3).expand() == a**3*A*B**3/A + assert ((a*A*B*A*B/A)**3).expand() == \ + a**3*A*B*(A*B**2)*(A*B**2)*A*B*A**(-1) + assert ((a*A*B*A*B/A)**-2).expand() == \ + A*B**-1*A**-1*B**-2*A**-1*B**-1*A**-1/a**2 + assert ((a*b*A*B*A**-1)**i).expand() == a**i*b**i*(A*B/A)**i + assert ((a*(a*b)**i)**i).expand() == a**i*a**(i**2)*b**(i**2) + e = Pow(Mul(a, 1/a, A, B, evaluate=False), S(2), evaluate=False) + assert e.expand() == A*B*A*B + assert sqrt(a*(A*b)**i).expand() == sqrt(a*b**i*A**i) + assert (sqrt(-a)**a).expand() == sqrt(-a)**a + assert expand((-2*n)**(i/3)) == 2**(i/3)*(-n)**(i/3) + assert expand((-2*n*m)**(i/a)) == (-2)**(i/a)*(-n)**(i/a)*(-m)**(i/a) + assert expand((-2*a*p)**b) == 2**b*p**b*(-a)**b + assert expand((-2*a*np)**b) == 2**b*(-a*np)**b + assert expand(sqrt(A*B)) == sqrt(A*B) + assert expand(sqrt(-2*a*b)) == sqrt(2)*sqrt(-a*b) + + +def test_expand_radicals(): + a = (x + y)**R(1, 2) + + assert (a**1).expand() == a + assert (a**3).expand() == x*a + y*a + assert (a**5).expand() == x**2*a + 2*x*y*a + y**2*a + + assert (1/a**1).expand() == 1/a + assert (1/a**3).expand() == 1/(x*a + y*a) + assert (1/a**5).expand() == 1/(x**2*a + 2*x*y*a + y**2*a) + + a = (x + y)**R(1, 3) + + assert (a**1).expand() == a + assert (a**2).expand() == a**2 + assert (a**4).expand() == x*a + y*a + assert (a**5).expand() == x*a**2 + y*a**2 + assert (a**7).expand() == x**2*a + 2*x*y*a + y**2*a + + +def test_expand_modulus(): + assert ((x + y)**11).expand(modulus=11) == x**11 + y**11 + assert ((x + sqrt(2)*y)**11).expand(modulus=11) == x**11 + 10*sqrt(2)*y**11 + assert (x + y/2).expand(modulus=1) == y/2 + + raises(ValueError, lambda: ((x + y)**11).expand(modulus=0)) + raises(ValueError, lambda: ((x + y)**11).expand(modulus=x)) + + +def test_issue_5743(): + assert (x*sqrt( + x + y)*(1 + sqrt(x + y))).expand() == x**2 + x*y + x*sqrt(x + y) + assert (x*sqrt( + x + y)*(1 + x*sqrt(x + y))).expand() == x**3 + x**2*y + x*sqrt(x + y) + + +def test_expand_frac(): + assert expand((x + y)*y/x/(x + 1), frac=True) == \ + (x*y + y**2)/(x**2 + x) + assert expand((x + y)*y/x/(x + 1), numer=True) == \ + (x*y + y**2)/(x*(x + 1)) + assert expand((x + y)*y/x/(x + 1), denom=True) == \ + y*(x + y)/(x**2 + x) + eq = (x + 1)**2/y + assert expand_numer(eq, multinomial=False) == eq + # issue 26329 + eq = (exp(x*z) - exp(y*z))/exp(z*(x + y)) + ans = exp(-y*z) - exp(-x*z) + assert eq.expand(numer=True) != ans + assert eq.expand(numer=True, exact=True) == ans + assert expand_numer(eq) != ans + assert expand_numer(eq, exact=True) == ans + + +def test_issue_6121(): + eq = -I*exp(-3*I*pi/4)/(4*pi**(S(3)/2)*sqrt(x)) + assert eq.expand(complex=True) # does not give oo recursion + eq = -I*exp(-3*I*pi/4)/(4*pi**(R(3, 2))*sqrt(x)) + assert eq.expand(complex=True) # does not give oo recursion + + +def test_expand_power_base(): + assert expand_power_base((x*y*z)**4) == x**4*y**4*z**4 + assert expand_power_base((x*y*z)**x).is_Pow + assert expand_power_base((x*y*z)**x, force=True) == x**x*y**x*z**x + assert expand_power_base((x*(y*z)**2)**3) == x**3*y**6*z**6 + + assert expand_power_base((sin((x*y)**2)*y)**z).is_Pow + assert expand_power_base( + (sin((x*y)**2)*y)**z, force=True) == sin((x*y)**2)**z*y**z + assert expand_power_base( + (sin((x*y)**2)*y)**z, deep=True) == (sin(x**2*y**2)*y)**z + + assert expand_power_base(exp(x)**2) == exp(2*x) + assert expand_power_base((exp(x)*exp(y))**2) == exp(2*x)*exp(2*y) + + assert expand_power_base( + (exp((x*y)**z)*exp(y))**2) == exp(2*(x*y)**z)*exp(2*y) + assert expand_power_base((exp((x*y)**z)*exp( + y))**2, deep=True, force=True) == exp(2*x**z*y**z)*exp(2*y) + + assert expand_power_base((exp(x)*exp(y))**z).is_Pow + assert expand_power_base( + (exp(x)*exp(y))**z, force=True) == exp(x)**z*exp(y)**z + + +def test_expand_arit(): + a = Symbol("a") + b = Symbol("b", positive=True) + c = Symbol("c") + + p = R(5) + e = (a + b)*c + assert e == c*(a + b) + assert (e.expand() - a*c - b*c) == R(0) + e = (a + b)*(a + b) + assert e == (a + b)**2 + assert e.expand() == 2*a*b + a**2 + b**2 + e = (a + b)*(a + b)**R(2) + assert e == (a + b)**3 + assert e.expand() == 3*b*a**2 + 3*a*b**2 + a**3 + b**3 + assert e.expand() == 3*b*a**2 + 3*a*b**2 + a**3 + b**3 + e = (a + b)*(a + c)*(b + c) + assert e == (a + c)*(a + b)*(b + c) + assert e.expand() == 2*a*b*c + b*a**2 + c*a**2 + b*c**2 + a*c**2 + c*b**2 + a*b**2 + e = (a + R(1))**p + assert e == (1 + a)**5 + assert e.expand() == 1 + 5*a + 10*a**2 + 10*a**3 + 5*a**4 + a**5 + e = (a + b + c)*(a + c + p) + assert e == (5 + a + c)*(a + b + c) + assert e.expand() == 5*a + 5*b + 5*c + 2*a*c + b*c + a*b + a**2 + c**2 + x = Symbol("x") + s = exp(x*x) - 1 + e = s.nseries(x, 0, 6)/x**2 + assert e.expand() == 1 + x**2/2 + O(x**4) + + e = (x*(y + z))**(x*(y + z))*(x + y) + assert e.expand(power_exp=False, power_base=False) == x*(x*y + x* + z)**(x*y + x*z) + y*(x*y + x*z)**(x*y + x*z) + assert e.expand(power_exp=False, power_base=False, deep=False) == x* \ + (x*(y + z))**(x*(y + z)) + y*(x*(y + z))**(x*(y + z)) + e = x * (x + (y + 1)**2) + assert e.expand(deep=False) == x**2 + x*(y + 1)**2 + e = (x*(y + z))**z + assert e.expand(power_base=True, mul=True, deep=True) in [x**z*(y + + z)**z, (x*y + x*z)**z] + assert ((2*y)**z).expand() == 2**z*y**z + p = Symbol('p', positive=True) + assert sqrt(-x).expand().is_Pow + assert sqrt(-x).expand(force=True) == I*sqrt(x) + assert ((2*y*p)**z).expand() == 2**z*p**z*y**z + assert ((2*y*p*x)**z).expand() == 2**z*p**z*(x*y)**z + assert ((2*y*p*x)**z).expand(force=True) == 2**z*p**z*x**z*y**z + assert ((2*y*p*-pi)**z).expand() == 2**z*pi**z*p**z*(-y)**z + assert ((2*y*p*-pi*x)**z).expand() == 2**z*pi**z*p**z*(-x*y)**z + n = Symbol('n', negative=True) + m = Symbol('m', negative=True) + assert ((-2*x*y*n)**z).expand() == 2**z*(-n)**z*(x*y)**z + assert ((-2*x*y*n*m)**z).expand() == 2**z*(-m)**z*(-n)**z*(-x*y)**z + # issue 5482 + assert sqrt(-2*x*n) == sqrt(2)*sqrt(-n)*sqrt(x) + # issue 5605 (2) + assert (cos(x + y)**2).expand(trig=True) in [ + (-sin(x)*sin(y) + cos(x)*cos(y))**2, + sin(x)**2*sin(y)**2 - 2*sin(x)*sin(y)*cos(x)*cos(y) + cos(x)**2*cos(y)**2 + ] + + # Check that this isn't too slow + x = Symbol('x') + W = 1 + for i in range(1, 21): + W = W * (x - i) + W = W.expand() + assert W.has(-1672280820*x**15) + +def test_expand_mul(): + # part of issue 20597 + e = Mul(2, 3, evaluate=False) + assert e.expand() == 6 + + e = Mul(2, 3, 1/x, evaluate=False) + assert e.expand() == 6/x + e = Mul(2, R(1, 3), evaluate=False) + assert e.expand() == R(2, 3) + +def test_power_expand(): + """Test for Pow.expand()""" + a = Symbol('a') + b = Symbol('b') + p = (a + b)**2 + assert p.expand() == a**2 + b**2 + 2*a*b + + p = (1 + 2*(1 + a))**2 + assert p.expand() == 9 + 4*(a**2) + 12*a + + p = 2**(a + b) + assert p.expand() == 2**a*2**b + + A = Symbol('A', commutative=False) + B = Symbol('B', commutative=False) + assert (2**(A + B)).expand() == 2**(A + B) + assert (A**(a + b)).expand() != A**(a + b) + + +def test_issues_5919_6830(): + # issue 5919 + n = -1 + 1/x + z = n/x/(-n)**2 - 1/n/x + assert expand(z) == 1/(x**2 - 2*x + 1) - 1/(x - 2 + 1/x) - 1/(-x + 1) + + # issue 6830 + p = (1 + x)**2 + assert expand_multinomial((1 + x*p)**2) == ( + x**2*(x**4 + 4*x**3 + 6*x**2 + 4*x + 1) + 2*x*(x**2 + 2*x + 1) + 1) + assert expand_multinomial((1 + (y + x)*p)**2) == ( + 2*((x + y)*(x**2 + 2*x + 1)) + (x**2 + 2*x*y + y**2)* + (x**4 + 4*x**3 + 6*x**2 + 4*x + 1) + 1) + A = Symbol('A', commutative=False) + p = (1 + A)**2 + assert expand_multinomial((1 + x*p)**2) == ( + x**2*(1 + 4*A + 6*A**2 + 4*A**3 + A**4) + 2*x*(1 + 2*A + A**2) + 1) + assert expand_multinomial((1 + (y + x)*p)**2) == ( + (x + y)*(1 + 2*A + A**2)*2 + (x**2 + 2*x*y + y**2)* + (1 + 4*A + 6*A**2 + 4*A**3 + A**4) + 1) + assert expand_multinomial((1 + (y + x)*p)**3) == ( + (x + y)*(1 + 2*A + A**2)*3 + (x**2 + 2*x*y + y**2)*(1 + 4*A + + 6*A**2 + 4*A**3 + A**4)*3 + (x**3 + 3*x**2*y + 3*x*y**2 + y**3)*(1 + 6*A + + 15*A**2 + 20*A**3 + 15*A**4 + 6*A**5 + A**6) + 1) + # unevaluate powers + eq = (Pow((x + 1)*((A + 1)**2), 2, evaluate=False)) + # - in this case the base is not an Add so no further + # expansion is done + assert expand_multinomial(eq) == \ + (x**2 + 2*x + 1)*(1 + 4*A + 6*A**2 + 4*A**3 + A**4) + # - but here, the expanded base *is* an Add so it gets expanded + eq = (Pow(((A + 1)**2), 2, evaluate=False)) + assert expand_multinomial(eq) == 1 + 4*A + 6*A**2 + 4*A**3 + A**4 + + # coverage + def ok(a, b, n): + e = (a + I*b)**n + return verify_numerically(e, expand_multinomial(e)) + + for a in [2, S.Half]: + for b in [3, R(1, 3)]: + for n in range(2, 6): + assert ok(a, b, n) + + assert expand_multinomial((x + 1 + O(z))**2) == \ + 1 + 2*x + x**2 + O(z) + assert expand_multinomial((x + 1 + O(z))**3) == \ + 1 + 3*x + 3*x**2 + x**3 + O(z) + + assert expand_multinomial(3**(x + y + 3)) == 27*3**(x + y) + +def test_expand_log(): + t = Symbol('t', positive=True) + # after first expansion, -2*log(2) + log(4); then 0 after second + assert expand(log(t**2) - log(t**2/4) - 2*log(2)) == 0 + assert expand_log(log(7*6)/log(6)) == 1 + log(7)/log(6) + b = factorial(10) + assert expand_log(log(7*b**4)/log(b) + ) == 4 + log(7)/log(b) + + +def test_issue_23952(): + assert (x**(y + z)).expand(force=True) == x**y*x**z + one = Symbol('1', integer=True, prime=True, odd=True, positive=True) + two = Symbol('2', integer=True, prime=True, even=True) + e = two - one + for b in (0, x): + # 0**e = 0, 0**-e = zoo; but if expanded then nan + assert unchanged(Pow, b, e) # power_exp + assert unchanged(Pow, b, -e) # power_exp + assert unchanged(Pow, b, y - x) # power_exp + assert unchanged(Pow, b, 3 - x) # multinomial + assert (b**e).expand().is_Pow # power_exp + assert (b**-e).expand().is_Pow # power_exp + assert (b**(y - x)).expand().is_Pow # power_exp + assert (b**(3 - x)).expand().is_Pow # multinomial + nn1 = Symbol('nn1', nonnegative=True) + nn2 = Symbol('nn2', nonnegative=True) + nn3 = Symbol('nn3', nonnegative=True) + assert (x**(nn1 + nn2)).expand() == x**nn1*x**nn2 + assert (x**(-nn1 - nn2)).expand() == x**-nn1*x**-nn2 + assert unchanged(Pow, x, nn1 + nn2 - nn3) + assert unchanged(Pow, x, 1 + nn2 - nn3) + assert unchanged(Pow, x, nn1 - nn2) + assert unchanged(Pow, x, 1 - nn2) + assert unchanged(Pow, x, -1 + nn2) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_expr.py b/phivenv/Lib/site-packages/sympy/core/tests/test_expr.py new file mode 100644 index 0000000000000000000000000000000000000000..af63823345e2b0564ebb7e9015bfe1b423c9bafa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_expr.py @@ -0,0 +1,2313 @@ +from sympy.assumptions.refine import refine +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import (ExprBuilder, unchanged, Expr, + UnevaluatedExpr) +from sympy.core.function import (Function, expand, WildFunction, + AppliedUndef, Derivative, diff, Subs) +from sympy.core.mul import Mul, _unevaluated_Mul +from sympy.core.numbers import (NumberSymbol, E, zoo, oo, Float, I, + Rational, nan, Integer, Number, pi, _illegal) +from sympy.core.power import Pow +from sympy.core.relational import Ge, Lt, Gt, Le +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Symbol, symbols, Dummy, Wild +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import exp_polar, exp, log +from sympy.functions.elementary.hyperbolic import sinh, tanh +from sympy.functions.elementary.miscellaneous import sqrt, Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import tan, sin, cos +from sympy.functions.special.delta_functions import (Heaviside, + DiracDelta) +from sympy.functions.special.error_functions import Si +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import integrate, Integral +from sympy.physics.secondquant import FockState +from sympy.polys.partfrac import apart +from sympy.polys.polytools import factor, cancel, Poly +from sympy.polys.rationaltools import together +from sympy.series.order import O +from sympy.sets.sets import FiniteSet +from sympy.simplify.combsimp import combsimp +from sympy.simplify.gammasimp import gammasimp +from sympy.simplify.powsimp import powsimp +from sympy.simplify.radsimp import collect, radsimp +from sympy.simplify.ratsimp import ratsimp +from sympy.simplify.simplify import simplify, nsimplify +from sympy.simplify.trigsimp import trigsimp +from sympy.tensor.indexed import Indexed +from sympy.physics.units import meter + +from sympy.testing.pytest import raises, XFAIL + +from sympy.abc import a, b, c, n, t, u, x, y, z + + +f, g, h = symbols('f,g,h', cls=Function) + + +class DummyNumber: + """ + Minimal implementation of a number that works with SymPy. + + If one has a Number class (e.g. Sage Integer, or some other custom class) + that one wants to work well with SymPy, one has to implement at least the + methods of this class DummyNumber, resp. its subclasses I5 and F1_1. + + Basically, one just needs to implement either __int__() or __float__() and + then one needs to make sure that the class works with Python integers and + with itself. + """ + + def __radd__(self, a): + if isinstance(a, (int, float)): + return a + self.number + return NotImplemented + + def __add__(self, a): + if isinstance(a, (int, float, DummyNumber)): + return self.number + a + return NotImplemented + + def __rsub__(self, a): + if isinstance(a, (int, float)): + return a - self.number + return NotImplemented + + def __sub__(self, a): + if isinstance(a, (int, float, DummyNumber)): + return self.number - a + return NotImplemented + + def __rmul__(self, a): + if isinstance(a, (int, float)): + return a * self.number + return NotImplemented + + def __mul__(self, a): + if isinstance(a, (int, float, DummyNumber)): + return self.number * a + return NotImplemented + + def __rtruediv__(self, a): + if isinstance(a, (int, float)): + return a / self.number + return NotImplemented + + def __truediv__(self, a): + if isinstance(a, (int, float, DummyNumber)): + return self.number / a + return NotImplemented + + def __rpow__(self, a): + if isinstance(a, (int, float)): + return a ** self.number + return NotImplemented + + def __pow__(self, a): + if isinstance(a, (int, float, DummyNumber)): + return self.number ** a + return NotImplemented + + def __pos__(self): + return self.number + + def __neg__(self): + return - self.number + + +class I5(DummyNumber): + number = 5 + + def __int__(self): + return self.number + + +class F1_1(DummyNumber): + number = 1.1 + + def __float__(self): + return self.number + +i5 = I5() +f1_1 = F1_1() + +# basic SymPy objects +basic_objs = [ + Rational(2), + Float("1.3"), + x, + y, + pow(x, y)*y, +] + +# all supported objects +all_objs = basic_objs + [ + 5, + 5.5, + i5, + f1_1 +] + + +def dotest(s): + for xo in all_objs: + for yo in all_objs: + s(xo, yo) + return True + + +def test_basic(): + def j(a, b): + x = a + x = +a + x = -a + x = a + b + x = a - b + x = a*b + x = a/b + x = a**b + del x + assert dotest(j) + + +def test_ibasic(): + def s(a, b): + x = a + x += b + x = a + x -= b + x = a + x *= b + x = a + x /= b + assert dotest(s) + + +class NonBasic: + '''This class represents an object that knows how to implement binary + operations like +, -, etc with Expr but is not a subclass of Basic itself. + The NonExpr subclass below does subclass Basic but not Expr. + + For both NonBasic and NonExpr it should be possible for them to override + Expr.__add__ etc because Expr.__add__ should be returning NotImplemented + for non Expr classes. Otherwise Expr.__add__ would create meaningless + objects like Add(Integer(1), FiniteSet(2)) and it wouldn't be possible for + other classes to override these operations when interacting with Expr. + ''' + def __add__(self, other): + return SpecialOp('+', self, other) + + def __radd__(self, other): + return SpecialOp('+', other, self) + + def __sub__(self, other): + return SpecialOp('-', self, other) + + def __rsub__(self, other): + return SpecialOp('-', other, self) + + def __mul__(self, other): + return SpecialOp('*', self, other) + + def __rmul__(self, other): + return SpecialOp('*', other, self) + + def __truediv__(self, other): + return SpecialOp('/', self, other) + + def __rtruediv__(self, other): + return SpecialOp('/', other, self) + + def __floordiv__(self, other): + return SpecialOp('//', self, other) + + def __rfloordiv__(self, other): + return SpecialOp('//', other, self) + + def __mod__(self, other): + return SpecialOp('%', self, other) + + def __rmod__(self, other): + return SpecialOp('%', other, self) + + def __divmod__(self, other): + return SpecialOp('divmod', self, other) + + def __rdivmod__(self, other): + return SpecialOp('divmod', other, self) + + def __pow__(self, other): + return SpecialOp('**', self, other) + + def __rpow__(self, other): + return SpecialOp('**', other, self) + + def __lt__(self, other): + return SpecialOp('<', self, other) + + def __gt__(self, other): + return SpecialOp('>', self, other) + + def __le__(self, other): + return SpecialOp('<=', self, other) + + def __ge__(self, other): + return SpecialOp('>=', self, other) + + +class NonExpr(Basic, NonBasic): + '''Like NonBasic above except this is a subclass of Basic but not Expr''' + pass + + +class SpecialOp(): + '''Represents the results of operations with NonBasic and NonExpr''' + def __new__(cls, op, arg1, arg2): + obj = object.__new__(cls) + obj.args = (op, arg1, arg2) + return obj + + +class NonArithmetic(Basic): + '''Represents a Basic subclass that does not support arithmetic operations''' + pass + + +def test_cooperative_operations(): + '''Tests that Expr uses binary operations cooperatively. + + In particular it should be possible for non-Expr classes to override + binary operators like +, - etc when used with Expr instances. This should + work for non-Expr classes whether they are Basic subclasses or not. Also + non-Expr classes that do not define binary operators with Expr should give + TypeError. + ''' + # A bunch of instances of Expr subclasses + exprs = [ + Expr(), + S.Zero, + S.One, + S.Infinity, + S.NegativeInfinity, + S.ComplexInfinity, + S.Half, + Float(0.5), + Integer(2), + Symbol('x'), + Mul(2, Symbol('x')), + Add(2, Symbol('x')), + Pow(2, Symbol('x')), + ] + + for e in exprs: + # Test that these classes can override arithmetic operations in + # combination with various Expr types. + for ne in [NonBasic(), NonExpr()]: + + results = [ + (ne + e, ('+', ne, e)), + (e + ne, ('+', e, ne)), + (ne - e, ('-', ne, e)), + (e - ne, ('-', e, ne)), + (ne * e, ('*', ne, e)), + (e * ne, ('*', e, ne)), + (ne / e, ('/', ne, e)), + (e / ne, ('/', e, ne)), + (ne // e, ('//', ne, e)), + (e // ne, ('//', e, ne)), + (ne % e, ('%', ne, e)), + (e % ne, ('%', e, ne)), + (divmod(ne, e), ('divmod', ne, e)), + (divmod(e, ne), ('divmod', e, ne)), + (ne ** e, ('**', ne, e)), + (e ** ne, ('**', e, ne)), + (e < ne, ('>', ne, e)), + (ne < e, ('<', ne, e)), + (e > ne, ('<', ne, e)), + (ne > e, ('>', ne, e)), + (e <= ne, ('>=', ne, e)), + (ne <= e, ('<=', ne, e)), + (e >= ne, ('<=', ne, e)), + (ne >= e, ('>=', ne, e)), + ] + + for res, args in results: + assert type(res) is SpecialOp and res.args == args + + # These classes do not support binary operators with Expr. Every + # operation should raise in combination with any of the Expr types. + for na in [NonArithmetic(), object()]: + + raises(TypeError, lambda : e + na) + raises(TypeError, lambda : na + e) + raises(TypeError, lambda : e - na) + raises(TypeError, lambda : na - e) + raises(TypeError, lambda : e * na) + raises(TypeError, lambda : na * e) + raises(TypeError, lambda : e / na) + raises(TypeError, lambda : na / e) + raises(TypeError, lambda : e // na) + raises(TypeError, lambda : na // e) + raises(TypeError, lambda : e % na) + raises(TypeError, lambda : na % e) + raises(TypeError, lambda : divmod(e, na)) + raises(TypeError, lambda : divmod(na, e)) + raises(TypeError, lambda : e ** na) + raises(TypeError, lambda : na ** e) + raises(TypeError, lambda : e > na) + raises(TypeError, lambda : na > e) + raises(TypeError, lambda : e < na) + raises(TypeError, lambda : na < e) + raises(TypeError, lambda : e >= na) + raises(TypeError, lambda : na >= e) + raises(TypeError, lambda : e <= na) + raises(TypeError, lambda : na <= e) + + +def test_relational(): + from sympy.core.relational import Lt + assert (pi < 3) is S.false + assert (pi <= 3) is S.false + assert (pi > 3) is S.true + assert (pi >= 3) is S.true + assert (-pi < 3) is S.true + assert (-pi <= 3) is S.true + assert (-pi > 3) is S.false + assert (-pi >= 3) is S.false + r = Symbol('r', real=True) + assert (r - 2 < r - 3) is S.false + assert Lt(x + I, x + I + 2).func == Lt # issue 8288 + + +def test_relational_assumptions(): + m1 = Symbol("m1", nonnegative=False) + m2 = Symbol("m2", positive=False) + m3 = Symbol("m3", nonpositive=False) + m4 = Symbol("m4", negative=False) + assert (m1 < 0) == Lt(m1, 0) + assert (m2 <= 0) == Le(m2, 0) + assert (m3 > 0) == Gt(m3, 0) + assert (m4 >= 0) == Ge(m4, 0) + m1 = Symbol("m1", nonnegative=False, real=True) + m2 = Symbol("m2", positive=False, real=True) + m3 = Symbol("m3", nonpositive=False, real=True) + m4 = Symbol("m4", negative=False, real=True) + assert (m1 < 0) is S.true + assert (m2 <= 0) is S.true + assert (m3 > 0) is S.true + assert (m4 >= 0) is S.true + m1 = Symbol("m1", negative=True) + m2 = Symbol("m2", nonpositive=True) + m3 = Symbol("m3", positive=True) + m4 = Symbol("m4", nonnegative=True) + assert (m1 < 0) is S.true + assert (m2 <= 0) is S.true + assert (m3 > 0) is S.true + assert (m4 >= 0) is S.true + m1 = Symbol("m1", negative=False, real=True) + m2 = Symbol("m2", nonpositive=False, real=True) + m3 = Symbol("m3", positive=False, real=True) + m4 = Symbol("m4", nonnegative=False, real=True) + assert (m1 < 0) is S.false + assert (m2 <= 0) is S.false + assert (m3 > 0) is S.false + assert (m4 >= 0) is S.false + + +# See https://github.com/sympy/sympy/issues/17708 +#def test_relational_noncommutative(): +# from sympy import Lt, Gt, Le, Ge +# A, B = symbols('A,B', commutative=False) +# assert (A < B) == Lt(A, B) +# assert (A <= B) == Le(A, B) +# assert (A > B) == Gt(A, B) +# assert (A >= B) == Ge(A, B) + + +def test_basic_nostr(): + for obj in basic_objs: + raises(TypeError, lambda: obj + '1') + raises(TypeError, lambda: obj - '1') + if obj == 2: + assert obj * '1' == '11' + else: + raises(TypeError, lambda: obj * '1') + raises(TypeError, lambda: obj / '1') + raises(TypeError, lambda: obj ** '1') + + +def test_series_expansion_for_uniform_order(): + assert (1/x + y + x).series(x, 0, 0) == 1/x + O(1, x) + assert (1/x + y + x).series(x, 0, 1) == 1/x + y + O(x) + assert (1/x + 1 + x).series(x, 0, 0) == 1/x + O(1, x) + assert (1/x + 1 + x).series(x, 0, 1) == 1/x + 1 + O(x) + assert (1/x + x).series(x, 0, 0) == 1/x + O(1, x) + assert (1/x + y + y*x + x).series(x, 0, 0) == 1/x + O(1, x) + assert (1/x + y + y*x + x).series(x, 0, 1) == 1/x + y + O(x) + + +def test_leadterm(): + assert (3 + 2*x**(log(3)/log(2) - 1)).leadterm(x) == (3, 0) + + assert (1/x**2 + 1 + x + x**2).leadterm(x)[1] == -2 + assert (1/x + 1 + x + x**2).leadterm(x)[1] == -1 + assert (x**2 + 1/x).leadterm(x)[1] == -1 + assert (1 + x**2).leadterm(x)[1] == 0 + assert (x + 1).leadterm(x)[1] == 0 + assert (x + x**2).leadterm(x)[1] == 1 + assert (x**2).leadterm(x)[1] == 2 + + +def test_as_leading_term(): + assert (3 + 2*x**(log(3)/log(2) - 1)).as_leading_term(x) == 3 + assert (1/x**2 + 1 + x + x**2).as_leading_term(x) == 1/x**2 + assert (1/x + 1 + x + x**2).as_leading_term(x) == 1/x + assert (x**2 + 1/x).as_leading_term(x) == 1/x + assert (1 + x**2).as_leading_term(x) == 1 + assert (x + 1).as_leading_term(x) == 1 + assert (x + x**2).as_leading_term(x) == x + assert (x**2).as_leading_term(x) == x**2 + assert (x + oo).as_leading_term(x) is oo + + raises(ValueError, lambda: (x + 1).as_leading_term(1)) + + # https://github.com/sympy/sympy/issues/21177 + e = -3*x + (x + Rational(3, 2) - sqrt(3)*S.ImaginaryUnit/2)**2\ + - Rational(3, 2) + 3*sqrt(3)*S.ImaginaryUnit/2 + assert e.as_leading_term(x) == -sqrt(3)*I*x + + # https://github.com/sympy/sympy/issues/21245 + e = 1 - x - x**2 + d = (1 + sqrt(5))/2 + assert e.subs(x, y + 1/d).as_leading_term(y) == \ + (-40*y - 16*sqrt(5)*y)/(16 + 8*sqrt(5)) + + # https://github.com/sympy/sympy/issues/26991 + assert sinh(tanh(3/(100*x))).as_leading_term(x, cdir = 1) == sinh(1) + + +def test_leadterm2(): + assert (x*cos(1)*cos(1 + sin(1)) + sin(1 + sin(1))).leadterm(x) == \ + (sin(1 + sin(1)), 0) + + +def test_leadterm3(): + assert (y + z + x).leadterm(x) == (y + z, 0) + + +def test_as_leading_term2(): + assert (x*cos(1)*cos(1 + sin(1)) + sin(1 + sin(1))).as_leading_term(x) == \ + sin(1 + sin(1)) + + +def test_as_leading_term3(): + assert (2 + pi + x).as_leading_term(x) == 2 + pi + assert (2*x + pi*x + x**2).as_leading_term(x) == 2*x + pi*x + + +def test_as_leading_term4(): + # see issue 6843 + n = Symbol('n', integer=True, positive=True) + r = -n**3/(2*n**2 + 4*n + 2) - n**2/(n**2 + 2*n + 1) + \ + n**2/(n + 1) - n/(2*n**2 + 4*n + 2) + n/(n*x + x) + 2*n/(n + 1) - \ + 1 + 1/(n*x + x) + 1/(n + 1) - 1/x + assert r.as_leading_term(x).cancel() == n/2 + + +def test_as_leading_term_stub(): + class foo(Function): + pass + assert foo(1/x).as_leading_term(x) == foo(1/x) + assert foo(1).as_leading_term(x) == foo(1) + raises(NotImplementedError, lambda: foo(x).as_leading_term(x)) + + +def test_as_leading_term_deriv_integral(): + # related to issue 11313 + assert Derivative(x ** 3, x).as_leading_term(x) == 3*x**2 + assert Derivative(x ** 3, y).as_leading_term(x) == 0 + + assert Integral(x ** 3, x).as_leading_term(x) == x**4/4 + assert Integral(x ** 3, y).as_leading_term(x) == y*x**3 + + assert Derivative(exp(x), x).as_leading_term(x) == 1 + assert Derivative(log(x), x).as_leading_term(x) == (1/x).as_leading_term(x) + + +def test_atoms(): + assert x.atoms() == {x} + assert (1 + x).atoms() == {x, S.One} + + assert (1 + 2*cos(x)).atoms(Symbol) == {x} + assert (1 + 2*cos(x)).atoms(Symbol, Number) == {S.One, S(2), x} + + assert (2*(x**(y**x))).atoms() == {S(2), x, y} + + assert S.Half.atoms() == {S.Half} + assert S.Half.atoms(Symbol) == set() + + assert sin(oo).atoms(oo) == set() + + assert Poly(0, x).atoms() == {S.Zero, x} + assert Poly(1, x).atoms() == {S.One, x} + + assert Poly(x, x).atoms() == {x} + assert Poly(x, x, y).atoms() == {x, y} + assert Poly(x + y, x, y).atoms() == {x, y} + assert Poly(x + y, x, y, z).atoms() == {x, y, z} + assert Poly(x + y*t, x, y, z).atoms() == {t, x, y, z} + + assert (I*pi).atoms(NumberSymbol) == {pi} + assert (I*pi).atoms(NumberSymbol, I) == \ + (I*pi).atoms(I, NumberSymbol) == {pi, I} + + assert exp(exp(x)).atoms(exp) == {exp(exp(x)), exp(x)} + assert (1 + x*(2 + y) + exp(3 + z)).atoms(Add) == \ + {1 + x*(2 + y) + exp(3 + z), 2 + y, 3 + z} + + # issue 6132 + e = (f(x) + sin(x) + 2) + assert e.atoms(AppliedUndef) == \ + {f(x)} + assert e.atoms(AppliedUndef, Function) == \ + {f(x), sin(x)} + assert e.atoms(Function) == \ + {f(x), sin(x)} + assert e.atoms(AppliedUndef, Number) == \ + {f(x), S(2)} + assert e.atoms(Function, Number) == \ + {S(2), sin(x), f(x)} + + +def test_is_polynomial(): + k = Symbol('k', nonnegative=True, integer=True) + + assert Rational(2).is_polynomial(x, y, z) is True + assert (S.Pi).is_polynomial(x, y, z) is True + + assert x.is_polynomial(x) is True + assert x.is_polynomial(y) is True + + assert (x**2).is_polynomial(x) is True + assert (x**2).is_polynomial(y) is True + + assert (x**(-2)).is_polynomial(x) is False + assert (x**(-2)).is_polynomial(y) is True + + assert (2**x).is_polynomial(x) is False + assert (2**x).is_polynomial(y) is True + + assert (x**k).is_polynomial(x) is False + assert (x**k).is_polynomial(k) is False + assert (x**x).is_polynomial(x) is False + assert (k**k).is_polynomial(k) is False + assert (k**x).is_polynomial(k) is False + + assert (x**(-k)).is_polynomial(x) is False + assert ((2*x)**k).is_polynomial(x) is False + + assert (x**2 + 3*x - 8).is_polynomial(x) is True + assert (x**2 + 3*x - 8).is_polynomial(y) is True + + assert (x**2 + 3*x - 8).is_polynomial() is True + + assert sqrt(x).is_polynomial(x) is False + assert (sqrt(x)**3).is_polynomial(x) is False + + assert (x**2 + 3*x*sqrt(y) - 8).is_polynomial(x) is True + assert (x**2 + 3*x*sqrt(y) - 8).is_polynomial(y) is False + + assert ((x**2)*(y**2) + x*(y**2) + y*x + exp(2)).is_polynomial() is True + assert ((x**2)*(y**2) + x*(y**2) + y*x + exp(x)).is_polynomial() is False + + assert ( + (x**2)*(y**2) + x*(y**2) + y*x + exp(2)).is_polynomial(x, y) is True + assert ( + (x**2)*(y**2) + x*(y**2) + y*x + exp(x)).is_polynomial(x, y) is False + + assert (1/f(x) + 1).is_polynomial(f(x)) is False + + +def test_is_rational_function(): + assert Integer(1).is_rational_function() is True + assert Integer(1).is_rational_function(x) is True + + assert Rational(17, 54).is_rational_function() is True + assert Rational(17, 54).is_rational_function(x) is True + + assert (12/x).is_rational_function() is True + assert (12/x).is_rational_function(x) is True + + assert (x/y).is_rational_function() is True + assert (x/y).is_rational_function(x) is True + assert (x/y).is_rational_function(x, y) is True + + assert (x**2 + 1/x/y).is_rational_function() is True + assert (x**2 + 1/x/y).is_rational_function(x) is True + assert (x**2 + 1/x/y).is_rational_function(x, y) is True + + assert (sin(y)/x).is_rational_function() is False + assert (sin(y)/x).is_rational_function(y) is False + assert (sin(y)/x).is_rational_function(x) is True + assert (sin(y)/x).is_rational_function(x, y) is False + + for i in _illegal: + assert not i.is_rational_function() + for d in (1, x): + assert not (i/d).is_rational_function() + + +def test_is_meromorphic(): + f = a/x**2 + b + x + c*x**2 + assert f.is_meromorphic(x, 0) is True + assert f.is_meromorphic(x, 1) is True + assert f.is_meromorphic(x, zoo) is True + + g = 3 + 2*x**(log(3)/log(2) - 1) + assert g.is_meromorphic(x, 0) is False + assert g.is_meromorphic(x, 1) is True + assert g.is_meromorphic(x, zoo) is False + + n = Symbol('n', integer=True) + e = sin(1/x)**n*x + assert e.is_meromorphic(x, 0) is False + assert e.is_meromorphic(x, 1) is True + assert e.is_meromorphic(x, zoo) is False + + e = log(x)**pi + assert e.is_meromorphic(x, 0) is False + assert e.is_meromorphic(x, 1) is False + assert e.is_meromorphic(x, 2) is True + assert e.is_meromorphic(x, zoo) is False + + assert (log(x)**a).is_meromorphic(x, 0) is False + assert (log(x)**a).is_meromorphic(x, 1) is False + assert (a**log(x)).is_meromorphic(x, 0) is None + assert (3**log(x)).is_meromorphic(x, 0) is False + assert (3**log(x)).is_meromorphic(x, 1) is True + +def test_is_algebraic_expr(): + assert sqrt(3).is_algebraic_expr(x) is True + assert sqrt(3).is_algebraic_expr() is True + + eq = ((1 + x**2)/(1 - y**2))**(S.One/3) + assert eq.is_algebraic_expr(x) is True + assert eq.is_algebraic_expr(y) is True + + assert (sqrt(x) + y**(S(2)/3)).is_algebraic_expr(x) is True + assert (sqrt(x) + y**(S(2)/3)).is_algebraic_expr(y) is True + assert (sqrt(x) + y**(S(2)/3)).is_algebraic_expr() is True + + assert (cos(y)/sqrt(x)).is_algebraic_expr() is False + assert (cos(y)/sqrt(x)).is_algebraic_expr(x) is True + assert (cos(y)/sqrt(x)).is_algebraic_expr(y) is False + assert (cos(y)/sqrt(x)).is_algebraic_expr(x, y) is False + + +def test_SAGE1(): + #see https://github.com/sympy/sympy/issues/3346 + class MyInt: + def _sympy_(self): + return Integer(5) + m = MyInt() + e = Rational(2)*m + assert e == 10 + + raises(TypeError, lambda: Rational(2)*MyInt) + + +def test_SAGE2(): + class MyInt: + def __int__(self): + return 5 + assert sympify(MyInt()) == 5 + e = Rational(2)*MyInt() + assert e == 10 + + raises(TypeError, lambda: Rational(2)*MyInt) + + +def test_SAGE3(): + class MySymbol: + def __rmul__(self, other): + return ('mys', other, self) + + o = MySymbol() + e = x*o + + assert e == ('mys', x, o) + + +def test_len(): + e = x*y + assert len(e.args) == 2 + e = x + y + z + assert len(e.args) == 3 + + +def test_doit(): + a = Integral(x**2, x) + + assert isinstance(a.doit(), Integral) is False + + assert isinstance(a.doit(integrals=True), Integral) is False + assert isinstance(a.doit(integrals=False), Integral) is True + + assert (2*Integral(x, x)).doit() == x**2 + + +def test_attribute_error(): + raises(AttributeError, lambda: x.cos()) + raises(AttributeError, lambda: x.sin()) + raises(AttributeError, lambda: x.exp()) + + +def test_args(): + assert (x*y).args in ((x, y), (y, x)) + assert (x + y).args in ((x, y), (y, x)) + assert (x*y + 1).args in ((x*y, 1), (1, x*y)) + assert sin(x*y).args == (x*y,) + assert sin(x*y).args[0] == x*y + assert (x**y).args == (x, y) + assert (x**y).args[0] == x + assert (x**y).args[1] == y + + +def test_noncommutative_expand_issue_3757(): + A, B, C = symbols('A,B,C', commutative=False) + assert A*B - B*A != 0 + assert (A*(A + B)*B).expand() == A**2*B + A*B**2 + assert (A*(A + B + C)*B).expand() == A**2*B + A*B**2 + A*C*B + + +def test_as_numer_denom(): + a, b, c = symbols('a, b, c') + + assert nan.as_numer_denom() == (nan, 1) + assert oo.as_numer_denom() == (oo, 1) + assert (-oo).as_numer_denom() == (-oo, 1) + assert zoo.as_numer_denom() == (zoo, 1) + assert (-zoo).as_numer_denom() == (zoo, 1) + + assert x.as_numer_denom() == (x, 1) + assert (1/x).as_numer_denom() == (1, x) + assert (x/y).as_numer_denom() == (x, y) + assert (x/2).as_numer_denom() == (x, 2) + assert (x*y/z).as_numer_denom() == (x*y, z) + assert (x/(y*z)).as_numer_denom() == (x, y*z) + assert S.Half.as_numer_denom() == (1, 2) + assert (1/y**2).as_numer_denom() == (1, y**2) + assert (x/y**2).as_numer_denom() == (x, y**2) + assert ((x**2 + 1)/y).as_numer_denom() == (x**2 + 1, y) + assert (x*(y + 1)/y**7).as_numer_denom() == (x*(y + 1), y**7) + assert (x**-2).as_numer_denom() == (1, x**2) + assert (a/x + b/2/x + c/3/x).as_numer_denom() == \ + (6*a + 3*b + 2*c, 6*x) + assert (a/x + b/2/x + c/3/y).as_numer_denom() == \ + (2*c*x + y*(6*a + 3*b), 6*x*y) + assert (a/x + b/2/x + c/.5/x).as_numer_denom() == \ + (2*a + b + 4.0*c, 2*x) + # this should take no more than a few seconds + assert int(log(Add(*[Dummy()/i/x for i in range(1, 705)] + ).as_numer_denom()[1]/x).n(4)) == 705 + for i in [S.Infinity, S.NegativeInfinity, S.ComplexInfinity]: + assert (i + x/3).as_numer_denom() == \ + (x + i, 3) + assert (S.Infinity + x/3 + y/4).as_numer_denom() == \ + (4*x + 3*y + S.Infinity, 12) + assert (oo*x + zoo*y).as_numer_denom() == \ + (zoo*y + oo*x, 1) + + A, B, C = symbols('A,B,C', commutative=False) + + assert (A*B*C**-1).as_numer_denom() == (A*B*C**-1, 1) + assert (A*B*C**-1/x).as_numer_denom() == (A*B*C**-1, x) + assert (C**-1*A*B).as_numer_denom() == (C**-1*A*B, 1) + assert (C**-1*A*B/x).as_numer_denom() == (C**-1*A*B, x) + assert ((A*B*C)**-1).as_numer_denom() == ((A*B*C)**-1, 1) + assert ((A*B*C)**-1/x).as_numer_denom() == ((A*B*C)**-1, x) + + # the following morphs from Add to Mul during processing + assert Add(0, (x + y)/z/-2, evaluate=False).as_numer_denom( + ) == (-x - y, 2*z) + + +def test_trunc(): + import math + x, y = symbols('x y') + assert math.trunc(2) == 2 + assert math.trunc(4.57) == 4 + assert math.trunc(-5.79) == -5 + assert math.trunc(pi) == 3 + assert math.trunc(log(7)) == 1 + assert math.trunc(exp(5)) == 148 + assert math.trunc(cos(pi)) == -1 + assert math.trunc(sin(5)) == 0 + + raises(TypeError, lambda: math.trunc(x)) + raises(TypeError, lambda: math.trunc(x + y**2)) + raises(TypeError, lambda: math.trunc(oo)) + + +def test_as_independent(): + assert S.Zero.as_independent(x, as_Add=True) == (0, 0) + assert S.Zero.as_independent(x, as_Add=False) == (0, 0) + assert (2*x*sin(x) + y + x).as_independent(x) == (y, x + 2*x*sin(x)) + assert (2*x*sin(x) + y + x).as_independent(y) == (x + 2*x*sin(x), y) + + assert (2*x*sin(x) + y + x).as_independent(x, y) == (0, y + x + 2*x*sin(x)) + + assert (x*sin(x)*cos(y)).as_independent(x) == (cos(y), x*sin(x)) + assert (x*sin(x)*cos(y)).as_independent(y) == (x*sin(x), cos(y)) + + assert (x*sin(x)*cos(y)).as_independent(x, y) == (1, x*sin(x)*cos(y)) + + assert (sin(x)).as_independent(x) == (1, sin(x)) + assert (sin(x)).as_independent(y) == (sin(x), 1) + + assert (2*sin(x)).as_independent(x) == (2, sin(x)) + assert (2*sin(x)).as_independent(y) == (2*sin(x), 1) + + # issue 4903 = 1766b + n1, n2, n3 = symbols('n1 n2 n3', commutative=False) + assert (n1 + n1*n2).as_independent(n2) == (n1, n1*n2) + assert (n2*n1 + n1*n2).as_independent(n2) == (0, n1*n2 + n2*n1) + assert (n1*n2*n1).as_independent(n2) == (n1, n2*n1) + assert (n1*n2*n1).as_independent(n1) == (1, n1*n2*n1) + + assert (3*x).as_independent(x, as_Add=True) == (0, 3*x) + assert (3*x).as_independent(x, as_Add=False) == (3, x) + assert (3 + x).as_independent(x, as_Add=True) == (3, x) + assert (3 + x).as_independent(x, as_Add=False) == (1, 3 + x) + + # issue 5479 + assert (3*x).as_independent(Symbol) == (3, x) + + # issue 5648 + assert (n1*x*y).as_independent(x) == (n1*y, x) + assert ((x + n1)*(x - y)).as_independent(x) == (1, (x + n1)*(x - y)) + assert ((x + n1)*(x - y)).as_independent(y) == (x + n1, x - y) + assert (DiracDelta(x - n1)*DiracDelta(x - y)).as_independent(x) \ + == (1, DiracDelta(x - n1)*DiracDelta(x - y)) + assert (x*y*n1*n2*n3).as_independent(n2) == (x*y*n1, n2*n3) + assert (x*y*n1*n2*n3).as_independent(n1) == (x*y, n1*n2*n3) + assert (x*y*n1*n2*n3).as_independent(n3) == (x*y*n1*n2, n3) + assert (DiracDelta(x - n1)*DiracDelta(y - n1)*DiracDelta(x - n2)).as_independent(y) == \ + (DiracDelta(x - n1)*DiracDelta(x - n2), DiracDelta(y - n1)) + + # issue 5784 + assert (x + Integral(x, (x, 1, 2))).as_independent(x, strict=True) == \ + (Integral(x, (x, 1, 2)), x) + + eq = Add(x, -x, 2, -3, evaluate=False) + assert eq.as_independent(x) == (-1, Add(x, -x, evaluate=False)) + eq = Mul(x, 1/x, 2, -3, evaluate=False) + assert eq.as_independent(x) == (-6, Mul(x, 1/x, evaluate=False)) + + assert (x*y).as_independent(z, as_Add=True) == (x*y, 0) + +@XFAIL +def test_call_2(): + # TODO UndefinedFunction does not subclass Expr + assert (2*f)(x) == 2*f(x) + + +def test_replace(): + e = log(sin(x)) + tan(sin(x**2)) + + assert e.replace(sin, cos) == log(cos(x)) + tan(cos(x**2)) + assert e.replace( + sin, lambda a: sin(2*a)) == log(sin(2*x)) + tan(sin(2*x**2)) + + a = Wild('a') + b = Wild('b') + + assert e.replace(sin(a), cos(a)) == log(cos(x)) + tan(cos(x**2)) + assert e.replace( + sin(a), lambda a: sin(2*a)) == log(sin(2*x)) + tan(sin(2*x**2)) + # test exact + assert (2*x).replace(a*x + b, b - a, exact=True) == 2*x + assert (2*x).replace(a*x + b, b - a) == 2*x + assert (2*x).replace(a*x + b, b - a, exact=False) == 2/x + assert (2*x).replace(a*x + b, lambda a, b: b - a, exact=True) == 2*x + assert (2*x).replace(a*x + b, lambda a, b: b - a) == 2*x + assert (2*x).replace(a*x + b, lambda a, b: b - a, exact=False) == 2/x + + g = 2*sin(x**3) + + assert g.replace( + lambda expr: expr.is_Number, lambda expr: expr**2) == 4*sin(x**9) + + assert cos(x).replace(cos, sin, map=True) == (sin(x), {cos(x): sin(x)}) + assert sin(x).replace(cos, sin) == sin(x) + + cond, func = lambda x: x.is_Mul, lambda x: 2*x + assert (x*y).replace(cond, func, map=True) == (2*x*y, {x*y: 2*x*y}) + assert (x*(1 + x*y)).replace(cond, func, map=True) == \ + (2*x*(2*x*y + 1), {x*(2*x*y + 1): 2*x*(2*x*y + 1), x*y: 2*x*y}) + assert (y*sin(x)).replace(sin, lambda expr: sin(expr)/y, map=True) == \ + (sin(x), {sin(x): sin(x)/y}) + # if not simultaneous then y*sin(x) -> y*sin(x)/y = sin(x) -> sin(x)/y + assert (y*sin(x)).replace(sin, lambda expr: sin(expr)/y, + simultaneous=False) == sin(x)/y + assert (x**2 + O(x**3)).replace(Pow, lambda b, e: b**e/e + ) == x**2/2 + O(x**3) + assert (x**2 + O(x**3)).replace(Pow, lambda b, e: b**e/e, + simultaneous=False) == x**2/2 + O(x**3) + assert (x*(x*y + 3)).replace(lambda x: x.is_Mul, lambda x: 2 + x) == \ + x*(x*y + 5) + 2 + e = (x*y + 1)*(2*x*y + 1) + 1 + assert e.replace(cond, func, map=True) == ( + 2*((2*x*y + 1)*(4*x*y + 1)) + 1, + {2*x*y: 4*x*y, x*y: 2*x*y, (2*x*y + 1)*(4*x*y + 1): + 2*((2*x*y + 1)*(4*x*y + 1))}) + assert x.replace(x, y) == y + assert (x + 1).replace(1, 2) == x + 2 + + # https://groups.google.com/forum/#!topic/sympy/8wCgeC95tz0 + n1, n2, n3 = symbols('n1:4', commutative=False) + assert (n1*f(n2)).replace(f, lambda x: x) == n1*n2 + assert (n3*f(n2)).replace(f, lambda x: x) == n3*n2 + + # issue 16725 + assert S.Zero.replace(Wild('x'), 1) == 1 + # let the user override the default decision of False + assert S.Zero.replace(Wild('x'), 1, exact=True) == 0 + + +def test_replace_integral(): + # https://github.com/sympy/sympy/issues/27142 + q, p, s, t = symbols('q p s t', cls=Wild) + a, b, c, d = symbols('a b c d') + i = Integral(a + b, (b, c, d)) + pattern = Integral(q, (p, s, t)) + assert i.replace(pattern, q) == a + b + + +def test_find(): + expr = (x + y + 2 + sin(3*x)) + + assert expr.find(lambda u: u.is_Integer) == {S(2), S(3)} + assert expr.find(lambda u: u.is_Symbol) == {x, y} + + assert expr.find(lambda u: u.is_Integer, group=True) == {S(2): 1, S(3): 1} + assert expr.find(lambda u: u.is_Symbol, group=True) == {x: 2, y: 1} + + assert expr.find(Integer) == {S(2), S(3)} + assert expr.find(Symbol) == {x, y} + + assert expr.find(Integer, group=True) == {S(2): 1, S(3): 1} + assert expr.find(Symbol, group=True) == {x: 2, y: 1} + + a = Wild('a') + + expr = sin(sin(x)) + sin(x) + cos(x) + x + + assert expr.find(lambda u: type(u) is sin) == {sin(x), sin(sin(x))} + assert expr.find( + lambda u: type(u) is sin, group=True) == {sin(x): 2, sin(sin(x)): 1} + + assert expr.find(sin(a)) == {sin(x), sin(sin(x))} + assert expr.find(sin(a), group=True) == {sin(x): 2, sin(sin(x)): 1} + + assert expr.find(sin) == {sin(x), sin(sin(x))} + assert expr.find(sin, group=True) == {sin(x): 2, sin(sin(x)): 1} + + +def test_count(): + expr = (x + y + 2 + sin(3*x)) + + assert expr.count(lambda u: u.is_Integer) == 2 + assert expr.count(lambda u: u.is_Symbol) == 3 + + assert expr.count(Integer) == 2 + assert expr.count(Symbol) == 3 + assert expr.count(2) == 1 + + a = Wild('a') + + assert expr.count(sin) == 1 + assert expr.count(sin(a)) == 1 + assert expr.count(lambda u: type(u) is sin) == 1 + + assert f(x).count(f(x)) == 1 + assert f(x).diff(x).count(f(x)) == 1 + assert f(x).diff(x).count(x) == 2 + + +def test_has_basics(): + p = Wild('p') + + assert sin(x).has(x) + assert sin(x).has(sin) + assert not sin(x).has(y) + assert not sin(x).has(cos) + assert f(x).has(x) + assert f(x).has(f) + assert not f(x).has(y) + assert not f(x).has(g) + + assert f(x).diff(x).has(x) + assert f(x).diff(x).has(f) + assert f(x).diff(x).has(Derivative) + assert not f(x).diff(x).has(y) + assert not f(x).diff(x).has(g) + assert not f(x).diff(x).has(sin) + + assert (x**2).has(Symbol) + assert not (x**2).has(Wild) + assert (2*p).has(Wild) + + assert not x.has() + + # see issue at https://github.com/sympy/sympy/issues/5190 + assert not S(1).has(Wild) + assert not x.has(Wild) + + +def test_has_multiple(): + f = x**2*y + sin(2**t + log(z)) + + assert f.has(x) + assert f.has(y) + assert f.has(z) + assert f.has(t) + + assert not f.has(u) + + assert f.has(x, y, z, t) + assert f.has(x, y, z, t, u) + + i = Integer(4400) + + assert not i.has(x) + + assert (i*x**i).has(x) + assert not (i*y**i).has(x) + assert (i*y**i).has(x, y) + assert not (i*y**i).has(x, z) + + +def test_has_piecewise(): + f = (x*y + 3/y)**(3 + 2) + p = Piecewise((g(x), x < -1), (1, x <= 1), (f, True)) + + assert p.has(x) + assert p.has(y) + assert not p.has(z) + assert p.has(1) + assert p.has(3) + assert not p.has(4) + assert p.has(f) + assert p.has(g) + assert not p.has(h) + + +def test_has_iterative(): + A, B, C = symbols('A,B,C', commutative=False) + f = x*gamma(x)*sin(x)*exp(x*y)*A*B*C*cos(x*A*B) + + assert f.has(x) + assert f.has(x*y) + assert f.has(x*sin(x)) + assert not f.has(x*sin(y)) + assert f.has(x*A) + assert f.has(x*A*B) + assert not f.has(x*A*C) + assert f.has(x*A*B*C) + assert not f.has(x*A*C*B) + assert f.has(x*sin(x)*A*B*C) + assert not f.has(x*sin(x)*A*C*B) + assert not f.has(x*sin(y)*A*B*C) + assert f.has(x*gamma(x)) + assert not f.has(x + sin(x)) + + assert (x & y & z).has(x & z) + + +def test_has_integrals(): + f = Integral(x**2 + sin(x*y*z), (x, 0, x + y + z)) + + assert f.has(x + y) + assert f.has(x + z) + assert f.has(y + z) + + assert f.has(x*y) + assert f.has(x*z) + assert f.has(y*z) + + assert not f.has(2*x + y) + assert not f.has(2*x*y) + + +def test_has_tuple(): + assert Tuple(x, y).has(x) + assert not Tuple(x, y).has(z) + assert Tuple(f(x), g(x)).has(x) + assert not Tuple(f(x), g(x)).has(y) + assert Tuple(f(x), g(x)).has(f) + assert Tuple(f(x), g(x)).has(f(x)) + # XXX to be deprecated + #assert not Tuple(f, g).has(x) + #assert Tuple(f, g).has(f) + #assert not Tuple(f, g).has(h) + assert Tuple(True).has(True) + assert Tuple(True).has(S.true) + assert not Tuple(True).has(1) + + +def test_has_units(): + from sympy.physics.units import m, s + + assert (x*m/s).has(x) + assert (x*m/s).has(y, z) is False + + +def test_has_polys(): + poly = Poly(x**2 + x*y*sin(z), x, y, t) + + assert poly.has(x) + assert poly.has(x, y, z) + assert poly.has(x, y, z, t) + + +def test_has_physics(): + assert FockState((x, y)).has(x) + + +def test_as_poly_as_expr(): + f = x**2 + 2*x*y + + assert f.as_poly().as_expr() == f + assert f.as_poly(x, y).as_expr() == f + + assert (f + sin(x)).as_poly(x, y) is None + + p = Poly(f, x, y) + + assert p.as_poly() == p + + # https://github.com/sympy/sympy/issues/20610 + assert S(2).as_poly() is None + assert sqrt(2).as_poly(extension=True) is None + + raises(AttributeError, lambda: Tuple(x, x).as_poly(x)) + raises(AttributeError, lambda: Tuple(x ** 2, x, y).as_poly(x)) + + +def test_nonzero(): + assert bool(S.Zero) is False + assert bool(S.One) is True + assert bool(x) is True + assert bool(x + y) is True + assert bool(x - x) is False + assert bool(x*y) is True + assert bool(x*1) is True + assert bool(x*0) is False + + +def test_is_number(): + assert Float(3.14).is_number is True + assert Integer(737).is_number is True + assert Rational(3, 2).is_number is True + assert Rational(8).is_number is True + assert x.is_number is False + assert (2*x).is_number is False + assert (x + y).is_number is False + assert log(2).is_number is True + assert log(x).is_number is False + assert (2 + log(2)).is_number is True + assert (8 + log(2)).is_number is True + assert (2 + log(x)).is_number is False + assert (8 + log(2) + x).is_number is False + assert (1 + x**2/x - x).is_number is True + assert Tuple(Integer(1)).is_number is False + assert Add(2, x).is_number is False + assert Mul(3, 4).is_number is True + assert Pow(log(2), 2).is_number is True + assert oo.is_number is True + g = WildFunction('g') + assert g.is_number is False + assert (2*g).is_number is False + assert (x**2).subs(x, 3).is_number is True + + # test extensibility of .is_number + # on subinstances of Basic + class A(Basic): + pass + a = A() + assert a.is_number is False + + +def test_as_coeff_add(): + assert S(2).as_coeff_add() == (2, ()) + assert S(3.0).as_coeff_add() == (0, (S(3.0),)) + assert S(-3.0).as_coeff_add() == (0, (S(-3.0),)) + assert x.as_coeff_add() == (0, (x,)) + assert (x - 1).as_coeff_add() == (-1, (x,)) + assert (x + 1).as_coeff_add() == (1, (x,)) + assert (x + 2).as_coeff_add() == (2, (x,)) + assert (x + y).as_coeff_add(y) == (x, (y,)) + assert (3*x).as_coeff_add(y) == (3*x, ()) + # don't do expansion + e = (x + y)**2 + assert e.as_coeff_add(y) == (0, (e,)) + + +def test_as_coeff_mul(): + assert S(2).as_coeff_mul() == (2, ()) + assert S(3.0).as_coeff_mul() == (1, (S(3.0),)) + assert S(-3.0).as_coeff_mul() == (-1, (S(3.0),)) + assert S(-3.0).as_coeff_mul(rational=False) == (-S(3.0), ()) + assert x.as_coeff_mul() == (1, (x,)) + assert (-x).as_coeff_mul() == (-1, (x,)) + assert (2*x).as_coeff_mul() == (2, (x,)) + assert (x*y).as_coeff_mul(y) == (x, (y,)) + assert (3 + x).as_coeff_mul() == (1, (3 + x,)) + assert (3 + x).as_coeff_mul(y) == (3 + x, ()) + # don't do expansion + e = exp(x + y) + assert e.as_coeff_mul(y) == (1, (e,)) + e = 2**(x + y) + assert e.as_coeff_mul(y) == (1, (e,)) + assert (1.1*x).as_coeff_mul(rational=False) == (1.1, (x,)) + assert (1.1*x).as_coeff_mul() == (1, (1.1, x)) + assert (-oo*x).as_coeff_mul(rational=True) == (-1, (oo, x)) + + +def test_as_coeff_exponent(): + assert (3*x**4).as_coeff_exponent(x) == (3, 4) + assert (2*x**3).as_coeff_exponent(x) == (2, 3) + assert (4*x**2).as_coeff_exponent(x) == (4, 2) + assert (6*x**1).as_coeff_exponent(x) == (6, 1) + assert (3*x**0).as_coeff_exponent(x) == (3, 0) + assert (2*x**0).as_coeff_exponent(x) == (2, 0) + assert (1*x**0).as_coeff_exponent(x) == (1, 0) + assert (0*x**0).as_coeff_exponent(x) == (0, 0) + assert (-1*x**0).as_coeff_exponent(x) == (-1, 0) + assert (-2*x**0).as_coeff_exponent(x) == (-2, 0) + assert (2*x**3 + pi*x**3).as_coeff_exponent(x) == (2 + pi, 3) + assert (x*log(2)/(2*x + pi*x)).as_coeff_exponent(x) == \ + (log(2)/(2 + pi), 0) + # issue 4784 + D = Derivative + fx = D(f(x), x) + assert fx.as_coeff_exponent(f(x)) == (fx, 0) + + +def test_extractions(): + for base in (2, S.Exp1): + assert Pow(base**x, 3, evaluate=False + ).extract_multiplicatively(base**x) == base**(2*x) + assert (base**(5*x)).extract_multiplicatively( + base**(3*x)) == base**(2*x) + assert ((x*y)**3).extract_multiplicatively(x**2 * y) == x*y**2 + assert ((x*y)**3).extract_multiplicatively(x**4 * y) is None + assert (2*x).extract_multiplicatively(2) == x + assert (2*x).extract_multiplicatively(3) is None + assert (2*x).extract_multiplicatively(-1) is None + assert (S.Half*x).extract_multiplicatively(3) == x/6 + assert (sqrt(x)).extract_multiplicatively(x) is None + assert (sqrt(x)).extract_multiplicatively(1/x) is None + assert x.extract_multiplicatively(-x) is None + assert (-2 - 4*I).extract_multiplicatively(-2) == 1 + 2*I + assert (-2 - 4*I).extract_multiplicatively(3) is None + assert (-2*x - 4*y - 8).extract_multiplicatively(-2) == x + 2*y + 4 + assert (-2*x*y - 4*x**2*y).extract_multiplicatively(-2*y) == 2*x**2 + x + assert (2*x*y + 4*x**2*y).extract_multiplicatively(2*y) == 2*x**2 + x + assert (-4*y**2*x).extract_multiplicatively(-3*y) is None + assert (2*x).extract_multiplicatively(1) == 2*x + assert (-oo).extract_multiplicatively(5) is -oo + assert (oo).extract_multiplicatively(5) is oo + + assert ((x*y)**3).extract_additively(1) is None + assert (x + 1).extract_additively(x) == 1 + assert (x + 1).extract_additively(2*x) is None + assert (x + 1).extract_additively(-x) is None + assert (-x + 1).extract_additively(2*x) is None + assert (2*x + 3).extract_additively(x) == x + 3 + assert (2*x + 3).extract_additively(2) == 2*x + 1 + assert (2*x + 3).extract_additively(3) == 2*x + assert (2*x + 3).extract_additively(-2) is None + assert (2*x + 3).extract_additively(3*x) is None + assert (2*x + 3).extract_additively(2*x) == 3 + assert x.extract_additively(0) == x + assert S(2).extract_additively(x) is None + assert S(2.).extract_additively(2.) is S.Zero + assert S(2.).extract_additively(2) is S.Zero + assert S(2*x + 3).extract_additively(x + 1) == x + 2 + assert S(2*x + 3).extract_additively(y + 1) is None + assert S(2*x - 3).extract_additively(x + 1) is None + assert S(2*x - 3).extract_additively(y + z) is None + assert ((a + 1)*x*4 + y).extract_additively(x).expand() == \ + 4*a*x + 3*x + y + assert ((a + 1)*x*4 + 3*y).extract_additively(x + 2*y).expand() == \ + 4*a*x + 3*x + y + assert (y*(x + 1)).extract_additively(x + 1) is None + assert ((y + 1)*(x + 1) + 3).extract_additively(x + 1) == \ + y*(x + 1) + 3 + assert ((x + y)*(x + 1) + x + y + 3).extract_additively(x + y) == \ + x*(x + y) + 3 + assert (x + y + 2*((x + y)*(x + 1)) + 3).extract_additively((x + y)*(x + 1)) == \ + x + y + (x + 1)*(x + y) + 3 + assert ((y + 1)*(x + 2*y + 1) + 3).extract_additively(y + 1) == \ + (x + 2*y)*(y + 1) + 3 + assert (-x - x*I).extract_additively(-x) == -I*x + # extraction does not leave artificats, now + assert (4*x*(y + 1) + y).extract_additively(x) == x*(4*y + 3) + y + + n = Symbol("n", integer=True) + assert (Integer(-3)).could_extract_minus_sign() is True + assert (-n*x + x).could_extract_minus_sign() != \ + (n*x - x).could_extract_minus_sign() + assert (x - y).could_extract_minus_sign() != \ + (-x + y).could_extract_minus_sign() + assert (1 - x - y).could_extract_minus_sign() is True + assert (1 - x + y).could_extract_minus_sign() is False + assert ((-x - x*y)/y).could_extract_minus_sign() is False + assert ((x + x*y)/(-y)).could_extract_minus_sign() is True + assert ((x + x*y)/y).could_extract_minus_sign() is False + assert ((-x - y)/(x + y)).could_extract_minus_sign() is False + + class sign_invariant(Function, Expr): + nargs = 1 + def __neg__(self): + return self + foo = sign_invariant(x) + assert foo == -foo + assert foo.could_extract_minus_sign() is False + assert (x - y).could_extract_minus_sign() is False + assert (-x + y).could_extract_minus_sign() is True + assert (x - 1).could_extract_minus_sign() is False + assert (1 - x).could_extract_minus_sign() is True + assert (sqrt(2) - 1).could_extract_minus_sign() is True + assert (1 - sqrt(2)).could_extract_minus_sign() is False + # check that result is canonical + eq = (3*x + 15*y).extract_multiplicatively(3) + assert eq.args == eq.func(*eq.args).args + + +def test_nan_extractions(): + for r in (1, 0, I, nan): + assert nan.extract_additively(r) is None + assert nan.extract_multiplicatively(r) is None + + +def test_coeff(): + assert (x + 1).coeff(x + 1) == 1 + assert (3*x).coeff(0) == 0 + assert (z*(1 + x)*x**2).coeff(1 + x) == z*x**2 + assert (1 + 2*x*x**(1 + x)).coeff(x*x**(1 + x)) == 2 + assert (1 + 2*x**(y + z)).coeff(x**(y + z)) == 2 + assert (3 + 2*x + 4*x**2).coeff(1) == 0 + assert (3 + 2*x + 4*x**2).coeff(-1) == 0 + assert (3 + 2*x + 4*x**2).coeff(x) == 2 + assert (3 + 2*x + 4*x**2).coeff(x**2) == 4 + assert (3 + 2*x + 4*x**2).coeff(x**3) == 0 + + assert (-x/8 + x*y).coeff(x) == Rational(-1, 8) + y + assert (-x/8 + x*y).coeff(-x) == S.One/8 + assert (4*x).coeff(2*x) == 0 + assert (2*x).coeff(2*x) == 1 + assert (-oo*x).coeff(x*oo) == -1 + assert (10*x).coeff(x, 0) == 0 + assert (10*x).coeff(10*x, 0) == 0 + + n1, n2 = symbols('n1 n2', commutative=False) + assert (n1*n2).coeff(n1) == 1 + assert (n1*n2).coeff(n2) == n1 + assert (n1*n2 + x*n1).coeff(n1) == 1 # 1*n1*(n2+x) + assert (n2*n1 + x*n1).coeff(n1) == n2 + x + assert (n2*n1 + x*n1**2).coeff(n1) == n2 + assert (n1**x).coeff(n1) == 0 + assert (n1*n2 + n2*n1).coeff(n1) == 0 + assert (2*(n1 + n2)*n2).coeff(n1 + n2, right=1) == n2 + assert (2*(n1 + n2)*n2).coeff(n1 + n2, right=0) == 2 + + assert (2*f(x) + 3*f(x).diff(x)).coeff(f(x)) == 2 + + expr = z*(x + y)**2 + expr2 = z*(x + y)**2 + z*(2*x + 2*y)**2 + assert expr.coeff(z) == (x + y)**2 + assert expr.coeff(x + y) == 0 + assert expr2.coeff(z) == (x + y)**2 + (2*x + 2*y)**2 + + assert (x + y + 3*z).coeff(1) == x + y + assert (-x + 2*y).coeff(-1) == x + assert (x - 2*y).coeff(-1) == 2*y + assert (3 + 2*x + 4*x**2).coeff(1) == 0 + assert (-x - 2*y).coeff(2) == -y + assert (x + sqrt(2)*x).coeff(sqrt(2)) == x + assert (3 + 2*x + 4*x**2).coeff(x) == 2 + assert (3 + 2*x + 4*x**2).coeff(x**2) == 4 + assert (3 + 2*x + 4*x**2).coeff(x**3) == 0 + assert (z*(x + y)**2).coeff((x + y)**2) == z + assert (z*(x + y)**2).coeff(x + y) == 0 + assert (2 + 2*x + (x + 1)*y).coeff(x + 1) == y + + assert (x + 2*y + 3).coeff(1) == x + assert (x + 2*y + 3).coeff(x, 0) == 2*y + 3 + assert (x**2 + 2*y + 3*x).coeff(x**2, 0) == 2*y + 3*x + assert x.coeff(0, 0) == 0 + assert x.coeff(x, 0) == 0 + + n, m, o, l = symbols('n m o l', commutative=False) + assert n.coeff(n) == 1 + assert y.coeff(n) == 0 + assert (3*n).coeff(n) == 3 + assert (2 + n).coeff(x*m) == 0 + assert (2*x*n*m).coeff(x) == 2*n*m + assert (2 + n).coeff(x*m*n + y) == 0 + assert (2*x*n*m).coeff(3*n) == 0 + assert (n*m + m*n*m).coeff(n) == 1 + m + assert (n*m + m*n*m).coeff(n, right=True) == m # = (1 + m)*n*m + assert (n*m + m*n).coeff(n) == 0 + assert (n*m + o*m*n).coeff(m*n) == o + assert (n*m + o*m*n).coeff(m*n, right=True) == 1 + assert (n*m + n*m*n).coeff(n*m, right=True) == 1 + n # = n*m*(n + 1) + + assert (x*y).coeff(z, 0) == x*y + + assert (x*n + y*n + z*m).coeff(n) == x + y + assert (n*m + n*o + o*l).coeff(n, right=True) == m + o + assert (x*n*m*n + y*n*m*o + z*l).coeff(m, right=True) == x*n + y*o + assert (x*n*m*n + x*n*m*o + z*l).coeff(m, right=True) == n + o + assert (x*n*m*n + x*n*m*o + z*l).coeff(m) == x*n + + +def test_coeff2(): + r, kappa = symbols('r, kappa') + psi = Function("psi") + g = 1/r**2 * (2*r*psi(r).diff(r, 1) + r**2 * psi(r).diff(r, 2)) + g = g.expand() + assert g.coeff(psi(r).diff(r)) == 2/r + + +def test_coeff2_0(): + r, kappa = symbols('r, kappa') + psi = Function("psi") + g = 1/r**2 * (2*r*psi(r).diff(r, 1) + r**2 * psi(r).diff(r, 2)) + g = g.expand() + + assert g.coeff(psi(r).diff(r, 2)) == 1 + + +def test_coeff_expand(): + expr = z*(x + y)**2 + expr2 = z*(x + y)**2 + z*(2*x + 2*y)**2 + assert expr.coeff(z) == (x + y)**2 + assert expr2.coeff(z) == (x + y)**2 + (2*x + 2*y)**2 + + +def test_integrate(): + assert x.integrate(x) == x**2/2 + assert x.integrate((x, 0, 1)) == S.Half + + +def test_as_base_exp(): + assert x.as_base_exp() == (x, S.One) + assert (x*y*z).as_base_exp() == (x*y*z, S.One) + assert (x + y + z).as_base_exp() == (x + y + z, S.One) + assert ((x + y)**z).as_base_exp() == (x + y, z) + assert (x**2*y**2).as_base_exp() == (x*y, 2) + assert (x**z*y**z).as_base_exp() == (x**z*y**z, S.One) + + +def test_issue_4963(): + assert hasattr(Mul(x, y), "is_commutative") + assert hasattr(Mul(x, y, evaluate=False), "is_commutative") + assert hasattr(Pow(x, y), "is_commutative") + assert hasattr(Pow(x, y, evaluate=False), "is_commutative") + expr = Mul(Pow(2, 2, evaluate=False), 3, evaluate=False) + 1 + assert hasattr(expr, "is_commutative") + + +def test_action_verbs(): + assert nsimplify(1/(exp(3*pi*x/5) + 1)) == \ + (1/(exp(3*pi*x/5) + 1)).nsimplify() + assert ratsimp(1/x + 1/y) == (1/x + 1/y).ratsimp() + assert trigsimp(log(x), deep=True) == (log(x)).trigsimp(deep=True) + assert radsimp(1/(2 + sqrt(2))) == (1/(2 + sqrt(2))).radsimp() + assert radsimp(1/(a + b*sqrt(c)), symbolic=False) == \ + (1/(a + b*sqrt(c))).radsimp(symbolic=False) + assert powsimp(x**y*x**z*y**z, combine='all') == \ + (x**y*x**z*y**z).powsimp(combine='all') + assert (x**t*y**t).powsimp(force=True) == (x*y)**t + assert simplify(x**y*x**z*y**z) == (x**y*x**z*y**z).simplify() + assert together(1/x + 1/y) == (1/x + 1/y).together() + assert collect(a*x**2 + b*x**2 + a*x - b*x + c, x) == \ + (a*x**2 + b*x**2 + a*x - b*x + c).collect(x) + assert apart(y/(y + 2)/(y + 1), y) == (y/(y + 2)/(y + 1)).apart(y) + assert combsimp(y/(x + 2)/(x + 1)) == (y/(x + 2)/(x + 1)).combsimp() + assert gammasimp(gamma(x)/gamma(x-5)) == (gamma(x)/gamma(x-5)).gammasimp() + assert factor(x**2 + 5*x + 6) == (x**2 + 5*x + 6).factor() + assert refine(sqrt(x**2)) == sqrt(x**2).refine() + assert cancel((x**2 + 5*x + 6)/(x + 2)) == ((x**2 + 5*x + 6)/(x + 2)).cancel() + + +def test_as_powers_dict(): + assert x.as_powers_dict() == {x: 1} + assert (x**y*z).as_powers_dict() == {x: y, z: 1} + assert Mul(2, 2, evaluate=False).as_powers_dict() == {S(2): S(2)} + assert (x*y).as_powers_dict()[z] == 0 + assert (x + y).as_powers_dict()[z] == 0 + + +def test_as_coefficients_dict(): + check = [S.One, x, y, x*y, 1] + assert [Add(3*x, 2*x, y, 3).as_coefficients_dict()[i] for i in check] == \ + [3, 5, 1, 0, 3] + assert [Add(3*x, 2*x, y, 3, evaluate=False).as_coefficients_dict()[i] + for i in check] == [3, 5, 1, 0, 3] + assert [(3*x*y).as_coefficients_dict()[i] for i in check] == \ + [0, 0, 0, 3, 0] + assert [(3.0*x*y).as_coefficients_dict()[i] for i in check] == \ + [0, 0, 0, 3.0, 0] + assert (3.0*x*y).as_coefficients_dict()[3.0*x*y] == 0 + eq = x*(x + 1)*a + x*b + c/x + assert eq.as_coefficients_dict(x) == {x: b, 1/x: c, + x*(x + 1): a} + assert eq.expand().as_coefficients_dict(x) == {x**2: a, x: a + b, 1/x: c} + assert x.as_coefficients_dict() == {x: S.One} + + +def test_args_cnc(): + A = symbols('A', commutative=False) + assert (x + A).args_cnc() == \ + [[], [x + A]] + assert (x + a).args_cnc() == \ + [[a + x], []] + assert (x*a).args_cnc() == \ + [[a, x], []] + assert (x*y*A*(A + 1)).args_cnc(cset=True) == \ + [{x, y}, [A, 1 + A]] + assert Mul(x, x, evaluate=False).args_cnc(cset=True, warn=False) == \ + [{x}, []] + assert Mul(x, x**2, evaluate=False).args_cnc(cset=True, warn=False) == \ + [{x, x**2}, []] + raises(ValueError, lambda: Mul(x, x, evaluate=False).args_cnc(cset=True)) + assert Mul(x, y, x, evaluate=False).args_cnc() == \ + [[x, y, x], []] + # always split -1 from leading number + assert (-1.*x).args_cnc() == [[-1, 1.0, x], []] + + +def test_new_rawargs(): + n = Symbol('n', commutative=False) + a = x + n + assert a.is_commutative is False + assert a._new_rawargs(x).is_commutative + assert a._new_rawargs(x, y).is_commutative + assert a._new_rawargs(x, n).is_commutative is False + assert a._new_rawargs(x, y, n).is_commutative is False + m = x*n + assert m.is_commutative is False + assert m._new_rawargs(x).is_commutative + assert m._new_rawargs(n).is_commutative is False + assert m._new_rawargs(x, y).is_commutative + assert m._new_rawargs(x, n).is_commutative is False + assert m._new_rawargs(x, y, n).is_commutative is False + + assert m._new_rawargs(x, n, reeval=False).is_commutative is False + assert m._new_rawargs(S.One) is S.One + + +def test_issue_5226(): + assert Add(evaluate=False) == 0 + assert Mul(evaluate=False) == 1 + assert Mul(x + y, evaluate=False).is_Add + + +def test_free_symbols(): + # free_symbols should return the free symbols of an object + assert S.One.free_symbols == set() + assert x.free_symbols == {x} + assert Integral(x, (x, 1, y)).free_symbols == {y} + assert (-Integral(x, (x, 1, y))).free_symbols == {y} + assert meter.free_symbols == set() + assert (meter**x).free_symbols == {x} + + +def test_has_free(): + assert x.has_free(x) + assert not x.has_free(y) + assert (x + y).has_free(x) + assert (x + y).has_free(*(x, z)) + assert f(x).has_free(x) + assert f(x).has_free(f(x)) + assert Integral(f(x), (f(x), 1, y)).has_free(y) + assert not Integral(f(x), (f(x), 1, y)).has_free(x) + assert not Integral(f(x), (f(x), 1, y)).has_free(f(x)) + # simple extraction + assert (x + 1 + y).has_free(x + 1) + assert not (x + 2 + y).has_free(x + 1) + assert (2 + 3*x*y).has_free(3*x) + raises(TypeError, lambda: x.has_free({x, y})) + s = FiniteSet(1, 2) + assert Piecewise((s, x > 3), (4, True)).has_free(s) + assert not Piecewise((1, x > 3), (4, True)).has_free(s) + # can't make set of these, but fallback will handle + raises(TypeError, lambda: x.has_free(y, [])) + + +def test_has_xfree(): + assert (x + 1).has_xfree({x}) + assert ((x + 1)**2).has_xfree({x + 1}) + assert not (x + y + 1).has_xfree({x + 1}) + raises(TypeError, lambda: x.has_xfree(x)) + raises(TypeError, lambda: x.has_xfree([x])) + + +def test_issue_5300(): + x = Symbol('x', commutative=False) + assert x*sqrt(2)/sqrt(6) == x*sqrt(3)/3 + + +def test_floordiv(): + from sympy.functions.elementary.integers import floor + assert x // y == floor(x / y) + + +def test_as_coeff_Mul(): + assert Integer(3).as_coeff_Mul() == (Integer(3), Integer(1)) + assert Rational(3, 4).as_coeff_Mul() == (Rational(3, 4), Integer(1)) + assert Float(5.0).as_coeff_Mul() == (Float(5.0), Integer(1)) + assert Float(0.0).as_coeff_Mul() == (Float(0.0), Integer(1)) + + assert (Integer(3)*x).as_coeff_Mul() == (Integer(3), x) + assert (Rational(3, 4)*x).as_coeff_Mul() == (Rational(3, 4), x) + assert (Float(5.0)*x).as_coeff_Mul() == (Float(5.0), x) + + assert (Integer(3)*x*y).as_coeff_Mul() == (Integer(3), x*y) + assert (Rational(3, 4)*x*y).as_coeff_Mul() == (Rational(3, 4), x*y) + assert (Float(5.0)*x*y).as_coeff_Mul() == (Float(5.0), x*y) + + assert (x).as_coeff_Mul() == (S.One, x) + assert (x*y).as_coeff_Mul() == (S.One, x*y) + assert (-oo*x).as_coeff_Mul(rational=True) == (-1, oo*x) + + +def test_as_coeff_Add(): + assert Integer(3).as_coeff_Add() == (Integer(3), Integer(0)) + assert Rational(3, 4).as_coeff_Add() == (Rational(3, 4), Integer(0)) + assert Float(5.0).as_coeff_Add() == (Float(5.0), Integer(0)) + + assert (Integer(3) + x).as_coeff_Add() == (Integer(3), x) + assert (Rational(3, 4) + x).as_coeff_Add() == (Rational(3, 4), x) + assert (Float(5.0) + x).as_coeff_Add() == (Float(5.0), x) + assert (Float(5.0) + x).as_coeff_Add(rational=True) == (0, Float(5.0) + x) + + assert (Integer(3) + x + y).as_coeff_Add() == (Integer(3), x + y) + assert (Rational(3, 4) + x + y).as_coeff_Add() == (Rational(3, 4), x + y) + assert (Float(5.0) + x + y).as_coeff_Add() == (Float(5.0), x + y) + + assert (x).as_coeff_Add() == (S.Zero, x) + assert (x*y).as_coeff_Add() == (S.Zero, x*y) + + +def test_expr_sorting(): + + exprs = [1/x**2, 1/x, sqrt(sqrt(x)), sqrt(x), x, sqrt(x)**3, x**2] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [x, 2*x, 2*x**2, 2*x**3, x**n, 2*x**n, sin(x), sin(x)**n, + sin(x**2), cos(x), cos(x**2), tan(x)] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [x + 1, x**2 + x + 1, x**3 + x**2 + x + 1] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [S(4), x - 3*I/2, x + 3*I/2, x - 4*I + 1, x + 4*I + 1] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [f(1), f(2), f(3), f(1, 2, 3), g(1), g(2), g(3), g(1, 2, 3)] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [f(x), g(x), exp(x), sin(x), cos(x), factorial(x)] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [Tuple(x, y), Tuple(x, z), Tuple(x, y, z)] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [[3], [1, 2]] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [[1, 2], [2, 3]] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [[1, 2], [1, 2, 3]] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [{x: -y}, {x: y}] + assert sorted(exprs, key=default_sort_key) == exprs + + exprs = [{1}, {1, 2}] + assert sorted(exprs, key=default_sort_key) == exprs + + a, b = exprs = [Dummy('x'), Dummy('x')] + assert sorted([b, a], key=default_sort_key) == exprs + + +def test_as_ordered_factors(): + + assert x.as_ordered_factors() == [x] + assert (2*x*x**n*sin(x)*cos(x)).as_ordered_factors() \ + == [Integer(2), x, x**n, sin(x), cos(x)] + + args = [f(1), f(2), f(3), f(1, 2, 3), g(1), g(2), g(3), g(1, 2, 3)] + expr = Mul(*args) + + assert expr.as_ordered_factors() == args + + A, B = symbols('A,B', commutative=False) + + assert (A*B).as_ordered_factors() == [A, B] + assert (B*A).as_ordered_factors() == [B, A] + + +def test_as_ordered_terms(): + + assert x.as_ordered_terms() == [x] + assert (sin(x)**2*cos(x) + sin(x)*cos(x)**2 + 1).as_ordered_terms() \ + == [sin(x)**2*cos(x), sin(x)*cos(x)**2, 1] + + args = [f(1), f(2), f(3), f(1, 2, 3), g(1), g(2), g(3), g(1, 2, 3)] + expr = Add(*args) + + assert expr.as_ordered_terms() == args + + assert (1 + 4*sqrt(3)*pi*x).as_ordered_terms() == [4*pi*x*sqrt(3), 1] + + assert ( 2 + 3*I).as_ordered_terms() == [2, 3*I] + assert (-2 + 3*I).as_ordered_terms() == [-2, 3*I] + assert ( 2 - 3*I).as_ordered_terms() == [2, -3*I] + assert (-2 - 3*I).as_ordered_terms() == [-2, -3*I] + + assert ( 4 + 3*I).as_ordered_terms() == [4, 3*I] + assert (-4 + 3*I).as_ordered_terms() == [-4, 3*I] + assert ( 4 - 3*I).as_ordered_terms() == [4, -3*I] + assert (-4 - 3*I).as_ordered_terms() == [-4, -3*I] + + e = x**2*y**2 + x*y**4 + y + 2 + + assert e.as_ordered_terms(order="lex") == [x**2*y**2, x*y**4, y, 2] + assert e.as_ordered_terms(order="grlex") == [x*y**4, x**2*y**2, y, 2] + assert e.as_ordered_terms(order="rev-lex") == [2, y, x*y**4, x**2*y**2] + assert e.as_ordered_terms(order="rev-grlex") == [2, y, x**2*y**2, x*y**4] + + k = symbols('k') + assert k.as_ordered_terms(data=True) == ([(k, ((1.0, 0.0), (1,), ()))], [k]) + + +def test_sort_key_atomic_expr(): + from sympy.physics.units import m, s + assert sorted([-m, s], key=lambda arg: arg.sort_key()) == [-m, s] + + +def test_eval_interval(): + assert exp(x)._eval_interval(*Tuple(x, 0, 1)) == exp(1) - exp(0) + + # issue 4199 + a = x/y + raises(NotImplementedError, lambda: a._eval_interval(x, S.Zero, oo)._eval_interval(y, oo, S.Zero)) + raises(NotImplementedError, lambda: a._eval_interval(x, S.Zero, oo)._eval_interval(y, S.Zero, oo)) + a = x - y + raises(NotImplementedError, lambda: a._eval_interval(x, S.One, oo)._eval_interval(y, oo, S.One)) + raises(ValueError, lambda: x._eval_interval(x, None, None)) + a = -y*Heaviside(x - y) + assert a._eval_interval(x, -oo, oo) == -y + assert a._eval_interval(x, oo, -oo) == y + + +def test_eval_interval_zoo(): + # Test that limit is used when zoo is returned + assert Si(1/x)._eval_interval(x, S.Zero, S.One) == -pi/2 + Si(1) + + +def test_primitive(): + assert (3*(x + 1)**2).primitive() == (3, (x + 1)**2) + assert (6*x + 2).primitive() == (2, 3*x + 1) + assert (x/2 + 3).primitive() == (S.Half, x + 6) + eq = (6*x + 2)*(x/2 + 3) + assert eq.primitive()[0] == 1 + eq = (2 + 2*x)**2 + assert eq.primitive()[0] == 1 + assert (4.0*x).primitive() == (1, 4.0*x) + assert (4.0*x + y/2).primitive() == (S.Half, 8.0*x + y) + assert (-2*x).primitive() == (2, -x) + assert Add(5*z/7, 0.5*x, 3*y/2, evaluate=False).primitive() == \ + (S.One/14, 7.0*x + 21*y + 10*z) + for i in [S.Infinity, S.NegativeInfinity, S.ComplexInfinity]: + assert (i + x/3).primitive() == \ + (S.One/3, i + x) + assert (S.Infinity + 2*x/3 + 4*y/7).primitive() == \ + (S.One/21, 14*x + 12*y + oo) + assert S.Zero.primitive() == (S.One, S.Zero) + + +def test_issue_5843(): + a = 1 + x + assert (2*a).extract_multiplicatively(a) == 2 + assert (4*a).extract_multiplicatively(2*a) == 2 + assert ((3*a)*(2*a)).extract_multiplicatively(a) == 6*a + + +def test_is_constant(): + from sympy.solvers.solvers import checksol + assert Sum(x, (x, 1, 10)).is_constant() is True + assert Sum(x, (x, 1, n)).is_constant() is False + assert Sum(x, (x, 1, n)).is_constant(y) is True + assert Sum(x, (x, 1, n)).is_constant(n) is False + assert Sum(x, (x, 1, n)).is_constant(x) is True + eq = a*cos(x)**2 + a*sin(x)**2 - a + assert eq.is_constant() is True + assert eq.subs({x: pi, a: 2}) == eq.subs({x: pi, a: 3}) == 0 + assert x.is_constant() is False + assert x.is_constant(y) is True + assert log(x/y).is_constant() is False + + assert checksol(x, x, Sum(x, (x, 1, n))) is False + assert checksol(x, x, Sum(x, (x, 1, n))) is False + assert f(1).is_constant + assert checksol(x, x, f(x)) is False + + assert Pow(x, S.Zero, evaluate=False).is_constant() is True # == 1 + assert Pow(S.Zero, x, evaluate=False).is_constant() is False # == 0 or 1 + assert (2**x).is_constant() is False + assert Pow(S(2), S(3), evaluate=False).is_constant() is True + + z1, z2 = symbols('z1 z2', zero=True) + assert (z1 + 2*z2).is_constant() is True + + assert meter.is_constant() is True + assert (3*meter).is_constant() is True + assert (x*meter).is_constant() is False + + +def test_equals(): + assert (-3 - sqrt(5) + (-sqrt(10)/2 - sqrt(2)/2)**2).equals(0) + assert (x**2 - 1).equals((x + 1)*(x - 1)) + assert (cos(x)**2 + sin(x)**2).equals(1) + assert (a*cos(x)**2 + a*sin(x)**2).equals(a) + r = sqrt(2) + assert (-1/(r + r*x) + 1/r/(1 + x)).equals(0) + assert factorial(x + 1).equals((x + 1)*factorial(x)) + assert sqrt(3).equals(2*sqrt(3)) is False + assert (sqrt(5)*sqrt(3)).equals(sqrt(3)) is False + assert (sqrt(5) + sqrt(3)).equals(0) is False + assert (sqrt(5) + pi).equals(0) is False + assert meter.equals(0) is False + assert (3*meter**2).equals(0) is False + eq = -(-1)**(S(3)/4)*6**(S.One/4) + (-6)**(S.One/4)*I + if eq != 0: # if canonicalization makes this zero, skip the test + assert eq.equals(0) + assert sqrt(x).equals(0) is False + + # from integrate(x*sqrt(1 + 2*x), x); + # diff is zero only when assumptions allow + i = 2*sqrt(2)*x**(S(5)/2)*(1 + 1/(2*x))**(S(5)/2)/5 + \ + 2*sqrt(2)*x**(S(3)/2)*(1 + 1/(2*x))**(S(5)/2)/(-6 - 3/x) + ans = sqrt(2*x + 1)*(6*x**2 + x - 1)/15 + diff = i - ans + assert diff.equals(0) is None # should be False, but previously this was False due to wrong intermediate result + assert diff.subs(x, Rational(-1, 2)/2) == 7*sqrt(2)/120 + # there are regions for x for which the expression is True, for + # example, when x < -1/2 or x > 0 the expression is zero + p = Symbol('p', positive=True) + assert diff.subs(x, p).equals(0) is True + assert diff.subs(x, -1).equals(0) is True + + # prove via minimal_polynomial or self-consistency + eq = sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) - sqrt(10 + 6*sqrt(3)) + assert eq.equals(0) + q = 3**Rational(1, 3) + 3 + p = expand(q**3)**Rational(1, 3) + assert (p - q).equals(0) + + # issue 6829 + # eq = q*x + q/4 + x**4 + x**3 + 2*x**2 - S.One/3 + # z = eq.subs(x, solve(eq, x)[0]) + q = symbols('q') + z = (q*(-sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/12)/2 - sqrt((2*q - S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - + S(2197)/13824)**(S.One/3) - S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - + S(2197)/13824)**(S.One/3) - S(13)/6)/2 - S.One/4) + q/4 + (-sqrt(-2*(-(q + - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - S(13)/12)/2 - sqrt((2*q + - S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/6)/2 - S.One/4)**4 + (-sqrt(-2*(-(q - S(7)/8)**S(2)/8 - + S(2197)/13824)**(S.One/3) - S(13)/12)/2 - sqrt((2*q - + S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/6)/2 - S.One/4)**3 + 2*(-sqrt(-2*(-(q - S(7)/8)**S(2)/8 - + S(2197)/13824)**(S.One/3) - S(13)/12)/2 - sqrt((2*q - + S(7)/4)/sqrt(-2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/12) + 2*(-(q - S(7)/8)**S(2)/8 - S(2197)/13824)**(S.One/3) - + S(13)/6)/2 - S.One/4)**2 - Rational(1, 3)) + assert z.equals(0) + + +def test_random(): + from sympy.functions.combinatorial.numbers import lucas + from sympy.simplify.simplify import posify + assert posify(x)[0]._random() is not None + assert lucas(n)._random(2, -2, 0, -1, 1) is None + + # issue 8662 + assert Piecewise((Max(x, y), z))._random() is None + + +def test_round(): + assert str(Float('0.1249999').round(2)) == '0.12' + d20 = 12345678901234567890 + ans = S(d20).round(2) + assert ans.is_Integer and ans == d20 + ans = S(d20).round(-2) + assert ans.is_Integer and ans == 12345678901234567900 + assert str(S('1/7').round(4)) == '0.1429' + assert str(S('.[12345]').round(4)) == '0.1235' + assert str(S('.1349').round(2)) == '0.13' + n = S(12345) + ans = n.round() + assert ans.is_Integer + assert ans == n + ans = n.round(1) + assert ans.is_Integer + assert ans == n + ans = n.round(4) + assert ans.is_Integer + assert ans == n + assert n.round(-1) == 12340 + + r = Float(str(n)).round(-4) + assert r == 10000.0 + + assert n.round(-5) == 0 + + assert str((pi + sqrt(2)).round(2)) == '4.56' + assert (10*(pi + sqrt(2))).round(-1) == 50.0 + raises(TypeError, lambda: round(x + 2, 2)) + assert str(S(2.3).round(1)) == '2.3' + # rounding in SymPy (as in Decimal) should be + # exact for the given precision; we check here + # that when a 5 follows the last digit that + # the rounded digit will be even. + for i in range(-99, 100): + # construct a decimal that ends in 5, e.g. 123 -> 0.1235 + s = str(abs(i)) + p = len(s) # we are going to round to the last digit of i + n = '0.%s5' % s # put a 5 after i's digits + j = p + 2 # 2 for '0.' + if i < 0: # 1 for '-' + j += 1 + n = '-' + n + v = str(Float(n).round(p))[:j] # pertinent digits + if v.endswith('.'): + continue # it ends with 0 which is even + L = int(v[-1]) # last digit + assert L % 2 == 0, (n, '->', v) + + assert (Float(.3, 3) + 2*pi).round() == 7 + assert (Float(.3, 3) + 2*pi*100).round() == 629 + assert (pi + 2*E*I).round() == 3 + 5*I + # don't let request for extra precision give more than + # what is known (in this case, only 3 digits) + assert str((Float(.03, 3) + 2*pi/100).round(5)) == '0.0928' + assert str((Float(.03, 3) + 2*pi/100).round(4)) == '0.0928' + + assert S.Zero.round() == 0 + + a = (Add(1, Float('1.' + '9'*27, ''), evaluate=False)) + assert a.round(10) == Float('3.000000000000000000000000000', '') + assert a.round(25) == Float('3.000000000000000000000000000', '') + assert a.round(26) == Float('3.000000000000000000000000000', '') + assert a.round(27) == Float('2.999999999999999999999999999', '') + assert a.round(30) == Float('2.999999999999999999999999999', '') + #assert a.round(10) == Float('3.0000000000', '') + #assert a.round(25) == Float('3.0000000000000000000000000', '') + #assert a.round(26) == Float('3.00000000000000000000000000', '') + #assert a.round(27) == Float('2.999999999999999999999999999', '') + #assert a.round(30) == Float('2.999999999999999999999999999', '') + + # XXX: Should round set the precision of the result? + # The previous version of the tests above is this but they only pass + # because Floats with unequal precision compare equal: + # + # assert a.round(10) == Float('3.0000000000', '') + # assert a.round(25) == Float('3.0000000000000000000000000', '') + # assert a.round(26) == Float('3.00000000000000000000000000', '') + # assert a.round(27) == Float('2.999999999999999999999999999', '') + # assert a.round(30) == Float('2.999999999999999999999999999', '') + + raises(TypeError, lambda: x.round()) + raises(TypeError, lambda: f(1).round()) + + # exact magnitude of 10 + assert str(S.One.round()) == '1' + assert str(S(100).round()) == '100' + + # applied to real and imaginary portions + assert (2*pi + E*I).round() == 6 + 3*I + assert (2*pi + I/10).round() == 6 + assert (pi/10 + 2*I).round() == 2*I + # the lhs re and im parts are Float with dps of 2 + # and those on the right have dps of 15 so they won't compare + # equal unless we use string or compare components (which will + # then coerce the floats to the same precision) or re-create + # the floats + assert str((pi/10 + E*I).round(2)) == '0.31 + 2.72*I' + assert str((pi/10 + E*I).round(2).as_real_imag()) == '(0.31, 2.72)' + assert str((pi/10 + E*I).round(2)) == '0.31 + 2.72*I' + + # issue 6914 + assert (I**(I + 3)).round(3) == Float('-0.208', '')*I + + # issue 8720 + assert S(-123.6).round() == -124 + assert S(-1.5).round() == -2 + assert S(-100.5).round() == -100 + assert S(-1.5 - 10.5*I).round() == -2 - 10*I + + # issue 7961 + assert str(S(0.006).round(2)) == '0.01' + assert str(S(0.00106).round(4)) == '0.0011' + + # issue 8147 + assert S.NaN.round() is S.NaN + assert S.Infinity.round() is S.Infinity + assert S.NegativeInfinity.round() is S.NegativeInfinity + assert S.ComplexInfinity.round() is S.ComplexInfinity + + # check that types match + for i in range(2): + fi = float(i) + # 2 args + assert all(type(round(i, p)) is int for p in (-1, 0, 1)) + assert all(S(i).round(p).is_Integer for p in (-1, 0, 1)) + assert all(type(round(fi, p)) is float for p in (-1, 0, 1)) + assert all(S(fi).round(p).is_Float for p in (-1, 0, 1)) + # 1 arg (p is None) + assert type(round(i)) is int + assert S(i).round().is_Integer + assert type(round(fi)) is int + assert S(fi).round().is_Integer + + # issue 25698 + n = 6000002 + assert int(n*(log(n) + log(log(n)))) == 110130079 + one = cos(2)**2 + sin(2)**2 + eq = exp(one*I*pi) + qr, qi = eq.as_real_imag() + assert qi.round(2) == 0.0 + assert eq.round(2) == -1.0 + eq = one - 1/S(10**120) + assert S.true not in (eq > 1, eq < 1) + assert int(eq) == int(.9) == 0 + assert int(-eq) == int(-.9) == 0 + + +def test_held_expression_UnevaluatedExpr(): + x = symbols("x") + he = UnevaluatedExpr(1/x) + e1 = x*he + + assert isinstance(e1, Mul) + assert e1.args == (x, he) + assert e1.doit() == 1 + assert UnevaluatedExpr(Derivative(x, x)).doit(deep=False + ) == Derivative(x, x) + assert UnevaluatedExpr(Derivative(x, x)).doit() == 1 + + xx = Mul(x, x, evaluate=False) + assert xx != x**2 + + ue2 = UnevaluatedExpr(xx) + assert isinstance(ue2, UnevaluatedExpr) + assert ue2.args == (xx,) + assert ue2.doit() == x**2 + assert ue2.doit(deep=False) == xx + + x2 = UnevaluatedExpr(2)*2 + assert type(x2) is Mul + assert x2.args == (2, UnevaluatedExpr(2)) + +def test_round_exception_nostr(): + # Don't use the string form of the expression in the round exception, as + # it's too slow + s = Symbol('bad') + try: + s.round() + except TypeError as e: + assert 'bad' not in str(e) + else: + # Did not raise + raise AssertionError("Did not raise") + + +def test_extract_branch_factor(): + assert exp_polar(2.0*I*pi).extract_branch_factor() == (1, 1) + + +def test_identity_removal(): + assert Add.make_args(x + 0) == (x,) + assert Mul.make_args(x*1) == (x,) + + +def test_float_0(): + assert Float(0.0) + 1 == Float(1.0) + + +@XFAIL +def test_float_0_fail(): + assert Float(0.0)*x == Float(0.0) + assert (x + Float(0.0)).is_Add + + +def test_issue_6325(): + ans = (b**2 + z**2 - (b*(a + b*t) + z*(c + t*z))**2/( + (a + b*t)**2 + (c + t*z)**2))/sqrt((a + b*t)**2 + (c + t*z)**2) + e = sqrt((a + b*t)**2 + (c + z*t)**2) + assert diff(e, t, 2) == ans + assert e.diff(t, 2) == ans + assert diff(e, t, 2, simplify=False) != ans + + +def test_issue_7426(): + f1 = a % c + f2 = x % z + assert f1.equals(f2) is None + + +def test_issue_11122(): + x = Symbol('x', extended_positive=False) + assert unchanged(Gt, x, 0) # (x > 0) + # (x > 0) should remain unevaluated after PR #16956 + + x = Symbol('x', positive=False, real=True) + assert (x > 0) is S.false + + +def test_issue_10651(): + x = Symbol('x', real=True) + e1 = (-1 + x)/(1 - x) + e3 = (4*x**2 - 4)/((1 - x)*(1 + x)) + e4 = 1/(cos(x)**2) - (tan(x))**2 + x = Symbol('x', positive=True) + e5 = (1 + x)/x + assert e1.is_constant() is None + assert e3.is_constant() is None + assert e4.is_constant() is None + assert e5.is_constant() is False + + +def test_issue_10161(): + x = symbols('x', real=True) + assert x*abs(x)*abs(x) == x**3 + + +def test_issue_10755(): + x = symbols('x') + raises(TypeError, lambda: int(log(x))) + raises(TypeError, lambda: log(x).round(2)) + + +def test_issue_11877(): + x = symbols('x') + assert integrate(log(S.Half - x), (x, 0, S.Half)) == Rational(-1, 2) -log(2)/2 + + +def test_normal(): + x = symbols('x') + e = Mul(S.Half, 1 + x, evaluate=False) + assert e.normal() == e + + +def test_expr(): + x = symbols('x') + raises(TypeError, lambda: tan(x).series(x, 2, oo, "+")) + + +def test_ExprBuilder(): + eb = ExprBuilder(Mul) + eb.args.extend([x, x]) + assert eb.build() == x**2 + + +def test_issue_22020(): + from sympy.parsing.sympy_parser import parse_expr + x = parse_expr("log((2*V/3-V)/C)/-(R+r)*C") + y = parse_expr("log((2*V/3-V)/C)/-(R+r)*2") + assert x.equals(y) is False + + +def test_non_string_equality(): + # Expressions should not compare equal to strings + x = symbols('x') + one = sympify(1) + assert (x == 'x') is False + assert (x != 'x') is True + assert (one == '1') is False + assert (one != '1') is True + assert (x + 1 == 'x + 1') is False + assert (x + 1 != 'x + 1') is True + + # Make sure == doesn't try to convert the resulting expression to a string + # (e.g., by calling sympify() instead of _sympify()) + + class BadRepr: + def __repr__(self): + raise RuntimeError + + assert (x == BadRepr()) is False + assert (x != BadRepr()) is True + + +def test_21494(): + from sympy.testing.pytest import warns_deprecated_sympy + + with warns_deprecated_sympy(): + assert x.expr_free_symbols == {x} + + with warns_deprecated_sympy(): + assert Basic().expr_free_symbols == set() + + with warns_deprecated_sympy(): + assert S(2).expr_free_symbols == {S(2)} + + with warns_deprecated_sympy(): + assert Indexed("A", x).expr_free_symbols == {Indexed("A", x)} + + with warns_deprecated_sympy(): + assert Subs(x, x, 0).expr_free_symbols == set() + + +def test_Expr__eq__iterable_handling(): + assert x != range(3) + + +def test_format(): + assert '{:1.2f}'.format(S.Zero) == '0.00' + assert '{:+3.0f}'.format(S(3)) == ' +3' + assert '{:23.20f}'.format(pi) == ' 3.14159265358979323846' + assert '{:50.48f}'.format(exp(sin(1))) == '2.319776824715853173956590377503266813254904772376' + + +def test_issue_24045(): + assert powsimp(exp(a)/((c*a - c*b)*(Float(1.0)*c*a - Float(1.0)*c*b))) # doesn't raise + + +def test__unevaluated_Mul(): + A, B = symbols('A B', commutative=False) + assert _unevaluated_Mul(x, A, B, S(2), A).args == (2, x, A, B, A) + assert _unevaluated_Mul(-x*A*B, S(2), A).args == (-2, x, A, B, A) + + +def test_Float_zero_division_error(): + # issue 27165 + assert Float('1.7567e-1417').round(15) == Float(0) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_exprtools.py b/phivenv/Lib/site-packages/sympy/core/tests/test_exprtools.py new file mode 100644 index 0000000000000000000000000000000000000000..b550db1606866fb76442980ea2139aaf61219525 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_exprtools.py @@ -0,0 +1,493 @@ +"""Tests for tools for manipulating of large commutative expressions. """ + +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.function import Function +from sympy.core.mul import Mul +from sympy.core.numbers import (I, Rational, oo) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import (root, sqrt) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.integrals.integrals import Integral +from sympy.series.order import O +from sympy.sets.sets import Interval +from sympy.simplify.radsimp import collect +from sympy.simplify.simplify import simplify +from sympy.core.exprtools import (decompose_power, Factors, Term, _gcd_terms, + gcd_terms, factor_terms, factor_nc, _mask_nc, + _monotonic_sign) +from sympy.core.mul import _keep_coeff as _keep_coeff +from sympy.simplify.cse_opts import sub_pre +from sympy.testing.pytest import raises + +from sympy.abc import a, b, t, x, y, z + + +def test_decompose_power(): + assert decompose_power(x) == (x, 1) + assert decompose_power(x**2) == (x, 2) + assert decompose_power(x**(2*y)) == (x**y, 2) + assert decompose_power(x**(2*y/3)) == (x**(y/3), 2) + assert decompose_power(x**(y*Rational(2, 3))) == (x**(y/3), 2) + + +def test_Factors(): + assert Factors() == Factors({}) == Factors(S.One) + assert Factors().as_expr() is S.One + assert Factors({x: 2, y: 3, sin(x): 4}).as_expr() == x**2*y**3*sin(x)**4 + assert Factors(S.Infinity) == Factors({oo: 1}) + assert Factors(S.NegativeInfinity) == Factors({oo: 1, -1: 1}) + # issue #18059: + assert Factors((x**2)**S.Half).as_expr() == (x**2)**S.Half + + a = Factors({x: 5, y: 3, z: 7}) + b = Factors({ y: 4, z: 3, t: 10}) + + assert a.mul(b) == a*b == Factors({x: 5, y: 7, z: 10, t: 10}) + + assert a.div(b) == divmod(a, b) == \ + (Factors({x: 5, z: 4}), Factors({y: 1, t: 10})) + assert a.quo(b) == a/b == Factors({x: 5, z: 4}) + assert a.rem(b) == a % b == Factors({y: 1, t: 10}) + + assert a.pow(3) == a**3 == Factors({x: 15, y: 9, z: 21}) + assert b.pow(3) == b**3 == Factors({y: 12, z: 9, t: 30}) + + assert a.gcd(b) == Factors({y: 3, z: 3}) + assert a.lcm(b) == Factors({x: 5, y: 4, z: 7, t: 10}) + + a = Factors({x: 4, y: 7, t: 7}) + b = Factors({z: 1, t: 3}) + + assert a.normal(b) == (Factors({x: 4, y: 7, t: 4}), Factors({z: 1})) + + assert Factors(sqrt(2)*x).as_expr() == sqrt(2)*x + + assert Factors(-I)*I == Factors() + assert Factors({S.NegativeOne: S(3)})*Factors({S.NegativeOne: S.One, I: S(5)}) == \ + Factors(I) + assert Factors(sqrt(I)*I) == Factors(I**(S(3)/2)) == Factors({I: S(3)/2}) + assert Factors({I: S(3)/2}).as_expr() == I**(S(3)/2) + + assert Factors(S(2)**x).div(S(3)**x) == \ + (Factors({S(2): x}), Factors({S(3): x})) + assert Factors(2**(2*x + 2)).div(S(8)) == \ + (Factors({S(2): 2*x + 2}), Factors({S(8): S.One})) + + # coverage + # /!\ things break if this is not True + assert Factors({S.NegativeOne: Rational(3, 2)}) == Factors({I: S.One, S.NegativeOne: S.One}) + assert Factors({I: S.One, S.NegativeOne: Rational(1, 3)}).as_expr() == I*(-1)**Rational(1, 3) + + assert Factors(-1.) == Factors({S.NegativeOne: S.One, S(1.): 1}) + assert Factors(-2.) == Factors({S.NegativeOne: S.One, S(2.): 1}) + assert Factors((-2.)**x) == Factors({S(-2.): x}) + assert Factors(S(-2)) == Factors({S.NegativeOne: S.One, S(2): 1}) + assert Factors(S.Half) == Factors({S(2): -S.One}) + assert Factors(Rational(3, 2)) == Factors({S(3): S.One, S(2): S.NegativeOne}) + assert Factors({I: S.One}) == Factors(I) + assert Factors({-1.0: 2, I: 1}) == Factors({S(1.0): 1, I: 1}) + assert Factors({S.NegativeOne: Rational(-3, 2)}).as_expr() == I + A = symbols('A', commutative=False) + assert Factors(2*A**2) == Factors({S(2): 1, A**2: 1}) + assert Factors(I) == Factors({I: S.One}) + assert Factors(x).normal(S(2)) == (Factors(x), Factors(S(2))) + assert Factors(x).normal(S.Zero) == (Factors(), Factors(S.Zero)) + raises(ZeroDivisionError, lambda: Factors(x).div(S.Zero)) + assert Factors(x).mul(S(2)) == Factors(2*x) + assert Factors(x).mul(S.Zero).is_zero + assert Factors(x).mul(1/x).is_one + assert Factors(x**sqrt(2)**3).as_expr() == x**(2*sqrt(2)) + assert Factors(x)**Factors(S(2)) == Factors(x**2) + assert Factors(x).gcd(S.Zero) == Factors(x) + assert Factors(x).lcm(S.Zero).is_zero + assert Factors(S.Zero).div(x) == (Factors(S.Zero), Factors()) + assert Factors(x).div(x) == (Factors(), Factors()) + assert Factors({x: .2})/Factors({x: .2}) == Factors() + assert Factors(x) != Factors() + assert Factors(S.Zero).normal(x) == (Factors(S.Zero), Factors()) + n, d = x**(2 + y), x**2 + f = Factors(n) + assert f.div(d) == f.normal(d) == (Factors(x**y), Factors()) + assert f.gcd(d) == Factors() + d = x**y + assert f.div(d) == f.normal(d) == (Factors(x**2), Factors()) + assert f.gcd(d) == Factors(d) + n = d = 2**x + f = Factors(n) + assert f.div(d) == f.normal(d) == (Factors(), Factors()) + assert f.gcd(d) == Factors(d) + n, d = 2**x, 2**y + f = Factors(n) + assert f.div(d) == f.normal(d) == (Factors({S(2): x}), Factors({S(2): y})) + assert f.gcd(d) == Factors() + + # extraction of constant only + n = x**(x + 3) + assert Factors(n).normal(x**-3) == (Factors({x: x + 6}), Factors({})) + assert Factors(n).normal(x**3) == (Factors({x: x}), Factors({})) + assert Factors(n).normal(x**4) == (Factors({x: x}), Factors({x: 1})) + assert Factors(n).normal(x**(y - 3)) == \ + (Factors({x: x + 6}), Factors({x: y})) + assert Factors(n).normal(x**(y + 3)) == (Factors({x: x}), Factors({x: y})) + assert Factors(n).normal(x**(y + 4)) == \ + (Factors({x: x}), Factors({x: y + 1})) + + assert Factors(n).div(x**-3) == (Factors({x: x + 6}), Factors({})) + assert Factors(n).div(x**3) == (Factors({x: x}), Factors({})) + assert Factors(n).div(x**4) == (Factors({x: x}), Factors({x: 1})) + assert Factors(n).div(x**(y - 3)) == \ + (Factors({x: x + 6}), Factors({x: y})) + assert Factors(n).div(x**(y + 3)) == (Factors({x: x}), Factors({x: y})) + assert Factors(n).div(x**(y + 4)) == \ + (Factors({x: x}), Factors({x: y + 1})) + + assert Factors(3 * x / 2) == Factors({3: 1, 2: -1, x: 1}) + assert Factors(x * x / y) == Factors({x: 2, y: -1}) + assert Factors(27 * x / y**9) == Factors({27: 1, x: 1, y: -9}) + + +def test_Term(): + a = Term(4*x*y**2/z/t**3) + b = Term(2*x**3*y**5/t**3) + + assert a == Term(4, Factors({x: 1, y: 2}), Factors({z: 1, t: 3})) + assert b == Term(2, Factors({x: 3, y: 5}), Factors({t: 3})) + + assert a.as_expr() == 4*x*y**2/z/t**3 + assert b.as_expr() == 2*x**3*y**5/t**3 + + assert a.inv() == \ + Term(S.One/4, Factors({z: 1, t: 3}), Factors({x: 1, y: 2})) + assert b.inv() == Term(S.Half, Factors({t: 3}), Factors({x: 3, y: 5})) + + assert a.mul(b) == a*b == \ + Term(8, Factors({x: 4, y: 7}), Factors({z: 1, t: 6})) + assert a.quo(b) == a/b == Term(2, Factors({}), Factors({x: 2, y: 3, z: 1})) + + assert a.pow(3) == a**3 == \ + Term(64, Factors({x: 3, y: 6}), Factors({z: 3, t: 9})) + assert b.pow(3) == b**3 == Term(8, Factors({x: 9, y: 15}), Factors({t: 9})) + + assert a.pow(-3) == a**(-3) == \ + Term(S.One/64, Factors({z: 3, t: 9}), Factors({x: 3, y: 6})) + assert b.pow(-3) == b**(-3) == \ + Term(S.One/8, Factors({t: 9}), Factors({x: 9, y: 15})) + + assert a.gcd(b) == Term(2, Factors({x: 1, y: 2}), Factors({t: 3})) + assert a.lcm(b) == Term(4, Factors({x: 3, y: 5}), Factors({z: 1, t: 3})) + + a = Term(4*x*y**2/z/t**3) + b = Term(2*x**3*y**5*t**7) + + assert a.mul(b) == Term(8, Factors({x: 4, y: 7, t: 4}), Factors({z: 1})) + + assert Term((2*x + 2)**3) == Term(8, Factors({x + 1: 3}), Factors({})) + assert Term((2*x + 2)*(3*x + 6)**2) == \ + Term(18, Factors({x + 1: 1, x + 2: 2}), Factors({})) + + +def test_gcd_terms(): + f = 2*(x + 1)*(x + 4)/(5*x**2 + 5) + (2*x + 2)*(x + 5)/(x**2 + 1)/5 + \ + (2*x + 2)*(x + 6)/(5*x**2 + 5) + + assert _gcd_terms(f) == ((Rational(6, 5))*((1 + x)/(1 + x**2)), 5 + x, 1) + assert _gcd_terms(Add.make_args(f)) == \ + ((Rational(6, 5))*((1 + x)/(1 + x**2)), 5 + x, 1) + + newf = (Rational(6, 5))*((1 + x)*(5 + x)/(1 + x**2)) + assert gcd_terms(f) == newf + args = Add.make_args(f) + # non-Basic sequences of terms treated as terms of Add + assert gcd_terms(list(args)) == newf + assert gcd_terms(tuple(args)) == newf + assert gcd_terms(set(args)) == newf + # but a Basic sequence is treated as a container + assert gcd_terms(Tuple(*args)) != newf + assert gcd_terms(Basic(Tuple(S(1), 3*y + 3*x*y), Tuple(S(1), S(3)))) == \ + Basic(Tuple(S(1), 3*y*(x + 1)), Tuple(S(1), S(3))) + # but we shouldn't change keys of a dictionary or some may be lost + assert gcd_terms(Dict((x*(1 + y), S(2)), (x + x*y, y + x*y))) == \ + Dict({x*(y + 1): S(2), x + x*y: y*(1 + x)}) + + assert gcd_terms((2*x + 2)**3 + (2*x + 2)**2) == 4*(x + 1)**2*(2*x + 3) + + assert gcd_terms(0) == 0 + assert gcd_terms(1) == 1 + assert gcd_terms(x) == x + assert gcd_terms(2 + 2*x) == Mul(2, 1 + x, evaluate=False) + arg = x*(2*x + 4*y) + garg = 2*x*(x + 2*y) + assert gcd_terms(arg) == garg + assert gcd_terms(sin(arg)) == sin(garg) + + # issue 6139-like + alpha, alpha1, alpha2, alpha3 = symbols('alpha:4') + a = alpha**2 - alpha*x**2 + alpha + x**3 - x*(alpha + 1) + rep = (alpha, (1 + sqrt(5))/2 + alpha1*x + alpha2*x**2 + alpha3*x**3) + s = (a/(x - alpha)).subs(*rep).series(x, 0, 1) + assert simplify(collect(s, x)) == -sqrt(5)/2 - Rational(3, 2) + O(x) + + # issue 5917 + assert _gcd_terms([S.Zero, S.Zero]) == (0, 0, 1) + assert _gcd_terms([2*x + 4]) == (2, x + 2, 1) + + eq = x/(x + 1/x) + assert gcd_terms(eq, fraction=False) == eq + eq = x/2/y + 1/x/y + assert gcd_terms(eq, fraction=True, clear=True) == \ + (x**2 + 2)/(2*x*y) + assert gcd_terms(eq, fraction=True, clear=False) == \ + (x**2/2 + 1)/(x*y) + assert gcd_terms(eq, fraction=False, clear=True) == \ + (x + 2/x)/(2*y) + assert gcd_terms(eq, fraction=False, clear=False) == \ + (x/2 + 1/x)/y + + +def test_factor_terms(): + A = Symbol('A', commutative=False) + assert factor_terms(9*(x + x*y + 1) + (3*x + 3)**(2 + 2*x)) == \ + 9*x*y + 9*x + _keep_coeff(S(3), x + 1)**_keep_coeff(S(2), x + 1) + 9 + assert factor_terms(9*(x + x*y + 1) + (3)**(2 + 2*x)) == \ + _keep_coeff(S(9), 3**(2*x) + x*y + x + 1) + assert factor_terms(3**(2 + 2*x) + a*3**(2 + 2*x)) == \ + 9*3**(2*x)*(a + 1) + assert factor_terms(x + x*A) == \ + x*(1 + A) + assert factor_terms(sin(x + x*A)) == \ + sin(x*(1 + A)) + assert factor_terms((3*x + 3)**((2 + 2*x)/3)) == \ + _keep_coeff(S(3), x + 1)**_keep_coeff(Rational(2, 3), x + 1) + assert factor_terms(x + (x*y + x)**(3*x + 3)) == \ + x + (x*(y + 1))**_keep_coeff(S(3), x + 1) + assert factor_terms(a*(x + x*y) + b*(x*2 + y*x*2)) == \ + x*(a + 2*b)*(y + 1) + i = Integral(x, (x, 0, oo)) + assert factor_terms(i) == i + + assert factor_terms(x/2 + y) == x/2 + y + # fraction doesn't apply to integer denominators + assert factor_terms(x/2 + y, fraction=True) == x/2 + y + # clear *does* apply to the integer denominators + assert factor_terms(x/2 + y, clear=True) == Mul(S.Half, x + 2*y, evaluate=False) + + # check radical extraction + eq = sqrt(2) + sqrt(10) + assert factor_terms(eq) == eq + assert factor_terms(eq, radical=True) == sqrt(2)*(1 + sqrt(5)) + eq = root(-6, 3) + root(6, 3) + assert factor_terms(eq, radical=True) == 6**(S.One/3)*(1 + (-1)**(S.One/3)) + + eq = [x + x*y] + ans = [x*(y + 1)] + for c in [list, tuple, set]: + assert factor_terms(c(eq)) == c(ans) + assert factor_terms(Tuple(x + x*y)) == Tuple(x*(y + 1)) + assert factor_terms(Interval(0, 1)) == Interval(0, 1) + e = 1/sqrt(a/2 + 1) + assert factor_terms(e, clear=False) == 1/sqrt(a/2 + 1) + assert factor_terms(e, clear=True) == sqrt(2)/sqrt(a + 2) + + eq = x/(x + 1/x) + 1/(x**2 + 1) + assert factor_terms(eq, fraction=False) == eq + assert factor_terms(eq, fraction=True) == 1 + + assert factor_terms((1/(x**3 + x**2) + 2/x**2)*y) == \ + y*(2 + 1/(x + 1))/x**2 + + # if not True, then processesing for this in factor_terms is not necessary + assert gcd_terms(-x - y) == -x - y + assert factor_terms(-x - y) == Mul(-1, x + y, evaluate=False) + + # if not True, then "special" processesing in factor_terms is not necessary + assert gcd_terms(exp(Mul(-1, x + 1))) == exp(-x - 1) + e = exp(-x - 2) + x + assert factor_terms(e) == exp(Mul(-1, x + 2, evaluate=False)) + x + assert factor_terms(e, sign=False) == e + assert factor_terms(exp(-4*x - 2) - x) == -x + exp(Mul(-2, 2*x + 1, evaluate=False)) + + # sum/integral tests + for F in (Sum, Integral): + assert factor_terms(F(x, (y, 1, 10))) == x * F(1, (y, 1, 10)) + assert factor_terms(F(x, (y, 1, 10)) + x) == x * (1 + F(1, (y, 1, 10))) + assert factor_terms(F(x*y + x*y**2, (y, 1, 10))) == x*F(y*(y + 1), (y, 1, 10)) + + # expressions involving Pow terms with base 0 + assert factor_terms(0**(x - 2) - 1) == 0**(x - 2) - 1 + assert factor_terms(0**(x + 2) - 1) == 0**(x + 2) - 1 + assert factor_terms((0**(x + 2) - 1).subs(x,-2)) == 0 + + +def test_xreplace(): + e = Mul(2, 1 + x, evaluate=False) + assert e.xreplace({}) == e + assert e.xreplace({y: x}) == e + + +def test_factor_nc(): + x, y = symbols('x,y') + k = symbols('k', integer=True) + n, m, o = symbols('n,m,o', commutative=False) + + # mul and multinomial expansion is needed + from sympy.core.function import _mexpand + e = x*(1 + y)**2 + assert _mexpand(e) == x + x*2*y + x*y**2 + + def factor_nc_test(e): + ex = _mexpand(e) + assert ex.is_Add + f = factor_nc(ex) + assert not f.is_Add and _mexpand(f) == ex + + factor_nc_test(x*(1 + y)) + factor_nc_test(n*(x + 1)) + factor_nc_test(n*(x + m)) + factor_nc_test((x + m)*n) + factor_nc_test(n*m*(x*o + n*o*m)*n) + s = Sum(x, (x, 1, 2)) + factor_nc_test(x*(1 + s)) + factor_nc_test(x*(1 + s)*s) + factor_nc_test(x*(1 + sin(s))) + factor_nc_test((1 + n)**2) + + factor_nc_test((x + n)*(x + m)*(x + y)) + factor_nc_test(x*(n*m + 1)) + factor_nc_test(x*(n*m + x)) + factor_nc_test(x*(x*n*m + 1)) + factor_nc_test(n*(m/x + o)) + factor_nc_test(m*(n + o/2)) + factor_nc_test(x*n*(x*m + 1)) + factor_nc_test(x*(m*n + x*n*m)) + factor_nc_test(n*(1 - m)*n**2) + + factor_nc_test((n + m)**2) + factor_nc_test((n - m)*(n + m)**2) + factor_nc_test((n + m)**2*(n - m)) + factor_nc_test((m - n)*(n + m)**2*(n - m)) + + assert factor_nc(n*(n + n*m)) == n**2*(1 + m) + assert factor_nc(m*(m*n + n*m*n**2)) == m*(m + n*m*n)*n + eq = m*sin(n) - sin(n)*m + assert factor_nc(eq) == eq + + # for coverage: + from sympy.physics.secondquant import Commutator + from sympy.polys.polytools import factor + eq = 1 + x*Commutator(m, n) + assert factor_nc(eq) == eq + eq = x*Commutator(m, n) + x*Commutator(m, o)*Commutator(m, n) + assert factor(eq) == x*(1 + Commutator(m, o))*Commutator(m, n) + + # issue 6534 + assert (2*n + 2*m).factor() == 2*(n + m) + + # issue 6701 + _n = symbols('nz', zero=False, commutative=False) + assert factor_nc(_n**k + _n**(k + 1)) == _n**k*(1 + _n) + assert factor_nc((m*n)**k + (m*n)**(k + 1)) == (1 + m*n)*(m*n)**k + + # issue 6918 + assert factor_nc(-n*(2*x**2 + 2*x)) == -2*n*x*(x + 1) + + +def test_issue_6360(): + a, b = symbols("a b") + apb = a + b + eq = apb + apb**2*(-2*a - 2*b) + assert factor_terms(sub_pre(eq)) == a + b - 2*(a + b)**3 + + +def test_issue_7903(): + a = symbols(r'a', real=True) + t = exp(I*cos(a)) + exp(-I*sin(a)) + assert t.simplify() + +def test_issue_8263(): + F, G = symbols('F, G', commutative=False, cls=Function) + x, y = symbols('x, y') + expr, dummies, _ = _mask_nc(F(x)*G(y) - G(y)*F(x)) + for v in dummies.values(): + assert not v.is_commutative + assert not expr.is_zero + +def test_monotonic_sign(): + F = _monotonic_sign + x = symbols('x') + assert F(x) is None + assert F(-x) is None + assert F(Dummy(prime=True)) == 2 + assert F(Dummy(prime=True, odd=True)) == 3 + assert F(Dummy(composite=True)) == 4 + assert F(Dummy(composite=True, odd=True)) == 9 + assert F(Dummy(positive=True, integer=True)) == 1 + assert F(Dummy(positive=True, even=True)) == 2 + assert F(Dummy(positive=True, even=True, prime=False)) == 4 + assert F(Dummy(negative=True, integer=True)) == -1 + assert F(Dummy(negative=True, even=True)) == -2 + assert F(Dummy(zero=True)) == 0 + assert F(Dummy(nonnegative=True)) == 0 + assert F(Dummy(nonpositive=True)) == 0 + + assert F(Dummy(positive=True) + 1).is_positive + assert F(Dummy(positive=True, integer=True) - 1).is_nonnegative + assert F(Dummy(positive=True) - 1) is None + assert F(Dummy(negative=True) + 1) is None + assert F(Dummy(negative=True, integer=True) - 1).is_nonpositive + assert F(Dummy(negative=True) - 1).is_negative + assert F(-Dummy(positive=True) + 1) is None + assert F(-Dummy(positive=True, integer=True) - 1).is_negative + assert F(-Dummy(positive=True) - 1).is_negative + assert F(-Dummy(negative=True) + 1).is_positive + assert F(-Dummy(negative=True, integer=True) - 1).is_nonnegative + assert F(-Dummy(negative=True) - 1) is None + x = Dummy(negative=True) + assert F(x**3).is_nonpositive + assert F(x**3 + log(2)*x - 1).is_negative + x = Dummy(positive=True) + assert F(-x**3).is_nonpositive + + p = Dummy(positive=True) + assert F(1/p).is_positive + assert F(p/(p + 1)).is_positive + p = Dummy(nonnegative=True) + assert F(p/(p + 1)).is_nonnegative + p = Dummy(positive=True) + assert F(-1/p).is_negative + p = Dummy(nonpositive=True) + assert F(p/(-p + 1)).is_nonpositive + + p = Dummy(positive=True, integer=True) + q = Dummy(positive=True, integer=True) + assert F(-2/p/q).is_negative + assert F(-2/(p - 1)/q) is None + + assert F((p - 1)*q + 1).is_positive + assert F(-(p - 1)*q - 1).is_negative + +def test_issue_17256(): + from sympy.sets.fancysets import Range + x = Symbol('x') + s1 = Sum(x + 1, (x, 1, 9)) + s2 = Sum(x + 1, (x, Range(1, 10))) + a = Symbol('a') + r1 = s1.xreplace({x:a}) + r2 = s2.xreplace({x:a}) + + assert r1.doit() == r2.doit() + s1 = Sum(x + 1, (x, 0, 9)) + s2 = Sum(x + 1, (x, Range(10))) + a = Symbol('a') + r1 = s1.xreplace({x:a}) + r2 = s2.xreplace({x:a}) + assert r1 == r2 + +def test_issue_21623(): + from sympy.matrices.expressions.matexpr import MatrixSymbol + M = MatrixSymbol('X', 2, 2) + assert gcd_terms(M[0,0], 1) == M[0,0] diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_facts.py b/phivenv/Lib/site-packages/sympy/core/tests/test_facts.py new file mode 100644 index 0000000000000000000000000000000000000000..7ca04877d0bdaf8124258ea1d25a10bcfa0f5f3a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_facts.py @@ -0,0 +1,312 @@ +from sympy.core.facts import (deduce_alpha_implications, + apply_beta_to_alpha_route, rules_2prereq, FactRules, FactKB) +from sympy.core.logic import And, Not +from sympy.testing.pytest import raises + +T = True +F = False +U = None + + +def test_deduce_alpha_implications(): + def D(i): + I = deduce_alpha_implications(i) + P = rules_2prereq({ + (k, True): {(v, True) for v in S} for k, S in I.items()}) + return I, P + + # transitivity + I, P = D([('a', 'b'), ('b', 'c')]) + assert I == {'a': {'b', 'c'}, 'b': {'c'}, Not('b'): + {Not('a')}, Not('c'): {Not('a'), Not('b')}} + assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}} + + # Duplicate entry + I, P = D([('a', 'b'), ('b', 'c'), ('b', 'c')]) + assert I == {'a': {'b', 'c'}, 'b': {'c'}, Not('b'): {Not('a')}, Not('c'): {Not('a'), Not('b')}} + assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}} + + # see if it is tolerant to cycles + assert D([('a', 'a'), ('a', 'a')]) == ({}, {}) + assert D([('a', 'b'), ('b', 'a')]) == ( + {'a': {'b'}, 'b': {'a'}, Not('a'): {Not('b')}, Not('b'): {Not('a')}}, + {'a': {'b'}, 'b': {'a'}}) + + # see if it catches inconsistency + raises(ValueError, lambda: D([('a', Not('a'))])) + raises(ValueError, lambda: D([('a', 'b'), ('b', Not('a'))])) + raises(ValueError, lambda: D([('a', 'b'), ('b', 'c'), ('b', 'na'), + ('na', Not('a'))])) + + # see if it handles implications with negations + I, P = D([('a', Not('b')), ('c', 'b')]) + assert I == {'a': {Not('b'), Not('c')}, 'b': {Not('a')}, 'c': {'b', Not('a')}, Not('b'): {Not('c')}} + assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}} + I, P = D([(Not('a'), 'b'), ('a', 'c')]) + assert I == {'a': {'c'}, Not('a'): {'b'}, Not('b'): {'a', + 'c'}, Not('c'): {Not('a'), 'b'},} + assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}} + + + # Long deductions + I, P = D([('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e')]) + assert I == {'a': {'b', 'c', 'd', 'e'}, 'b': {'c', 'd', 'e'}, + 'c': {'d', 'e'}, 'd': {'e'}, Not('b'): {Not('a')}, + Not('c'): {Not('a'), Not('b')}, Not('d'): {Not('a'), Not('b'), + Not('c')}, Not('e'): {Not('a'), Not('b'), Not('c'), Not('d')}} + assert P == {'a': {'b', 'c', 'd', 'e'}, 'b': {'a', 'c', 'd', + 'e'}, 'c': {'a', 'b', 'd', 'e'}, 'd': {'a', 'b', 'c', 'e'}, + 'e': {'a', 'b', 'c', 'd'}} + + # something related to real-world + I, P = D([('rat', 'real'), ('int', 'rat')]) + + assert I == {'int': {'rat', 'real'}, 'rat': {'real'}, + Not('real'): {Not('rat'), Not('int')}, Not('rat'): {Not('int')}} + assert P == {'rat': {'int', 'real'}, 'real': {'int', 'rat'}, + 'int': {'rat', 'real'}} + + +# TODO move me to appropriate place +def test_apply_beta_to_alpha_route(): + APPLY = apply_beta_to_alpha_route + + # indicates empty alpha-chain with attached beta-rule #bidx + def Q(bidx): + return (set(), [bidx]) + + # x -> a &(a,b) -> x -- x -> a + A = {'x': {'a'}} + B = [(And('a', 'b'), 'x')] + assert APPLY(A, B) == {'x': ({'a'}, []), 'a': Q(0), 'b': Q(0)} + + # x -> a &(a,!x) -> b -- x -> a + A = {'x': {'a'}} + B = [(And('a', Not('x')), 'b')] + assert APPLY(A, B) == {'x': ({'a'}, []), Not('x'): Q(0), 'a': Q(0)} + + # x -> a b &(a,b) -> c -- x -> a b c + A = {'x': {'a', 'b'}} + B = [(And('a', 'b'), 'c')] + assert APPLY(A, B) == \ + {'x': ({'a', 'b', 'c'}, []), 'a': Q(0), 'b': Q(0)} + + # x -> a &(a,b) -> y -- x -> a [#0] + A = {'x': {'a'}} + B = [(And('a', 'b'), 'y')] + assert APPLY(A, B) == {'x': ({'a'}, [0]), 'a': Q(0), 'b': Q(0)} + + # x -> a b c &(a,b) -> c -- x -> a b c + A = {'x': {'a', 'b', 'c'}} + B = [(And('a', 'b'), 'c')] + assert APPLY(A, B) == \ + {'x': ({'a', 'b', 'c'}, []), 'a': Q(0), 'b': Q(0)} + + # x -> a b &(a,b,c) -> y -- x -> a b [#0] + A = {'x': {'a', 'b'}} + B = [(And('a', 'b', 'c'), 'y')] + assert APPLY(A, B) == \ + {'x': ({'a', 'b'}, [0]), 'a': Q(0), 'b': Q(0), 'c': Q(0)} + + # x -> a b &(a,b) -> c -- x -> a b c d + # c -> d c -> d + A = {'x': {'a', 'b'}, 'c': {'d'}} + B = [(And('a', 'b'), 'c')] + assert APPLY(A, B) == {'x': ({'a', 'b', 'c', 'd'}, []), + 'c': ({'d'}, []), 'a': Q(0), 'b': Q(0)} + + # x -> a b &(a,b) -> c -- x -> a b c d e + # c -> d &(c,d) -> e c -> d e + A = {'x': {'a', 'b'}, 'c': {'d'}} + B = [(And('a', 'b'), 'c'), (And('c', 'd'), 'e')] + assert APPLY(A, B) == {'x': ({'a', 'b', 'c', 'd', 'e'}, []), + 'c': ({'d', 'e'}, []), 'a': Q(0), 'b': Q(0), 'd': Q(1)} + + # x -> a b &(a,y) -> z -- x -> a b y z + # &(a,b) -> y + A = {'x': {'a', 'b'}} + B = [(And('a', 'y'), 'z'), (And('a', 'b'), 'y')] + assert APPLY(A, B) == {'x': ({'a', 'b', 'y', 'z'}, []), + 'a': (set(), [0, 1]), 'y': Q(0), 'b': Q(1)} + + # x -> a b &(a,!b) -> c -- x -> a b + A = {'x': {'a', 'b'}} + B = [(And('a', Not('b')), 'c')] + assert APPLY(A, B) == \ + {'x': ({'a', 'b'}, []), 'a': Q(0), Not('b'): Q(0)} + + # !x -> !a !b &(!a,b) -> c -- !x -> !a !b + A = {Not('x'): {Not('a'), Not('b')}} + B = [(And(Not('a'), 'b'), 'c')] + assert APPLY(A, B) == \ + {Not('x'): ({Not('a'), Not('b')}, []), Not('a'): Q(0), 'b': Q(0)} + + # x -> a b &(b,c) -> !a -- x -> a b + A = {'x': {'a', 'b'}} + B = [(And('b', 'c'), Not('a'))] + assert APPLY(A, B) == {'x': ({'a', 'b'}, []), 'b': Q(0), 'c': Q(0)} + + # x -> a b &(a, b) -> c -- x -> a b c p + # c -> p a + A = {'x': {'a', 'b'}, 'c': {'p', 'a'}} + B = [(And('a', 'b'), 'c')] + assert APPLY(A, B) == {'x': ({'a', 'b', 'c', 'p'}, []), + 'c': ({'p', 'a'}, []), 'a': Q(0), 'b': Q(0)} + + +def test_FactRules_parse(): + f = FactRules('a -> b') + assert f.prereq == {'b': {'a'}, 'a': {'b'}} + + f = FactRules('a -> !b') + assert f.prereq == {'b': {'a'}, 'a': {'b'}} + + f = FactRules('!a -> b') + assert f.prereq == {'b': {'a'}, 'a': {'b'}} + + f = FactRules('!a -> !b') + assert f.prereq == {'b': {'a'}, 'a': {'b'}} + + f = FactRules('!z == nz') + assert f.prereq == {'z': {'nz'}, 'nz': {'z'}} + + +def test_FactRules_parse2(): + raises(ValueError, lambda: FactRules('a -> !a')) + + +def test_FactRules_deduce(): + f = FactRules(['a -> b', 'b -> c', 'b -> d', 'c -> e']) + + def D(facts): + kb = FactKB(f) + kb.deduce_all_facts(facts) + return kb + + assert D({'a': T}) == {'a': T, 'b': T, 'c': T, 'd': T, 'e': T} + assert D({'b': T}) == { 'b': T, 'c': T, 'd': T, 'e': T} + assert D({'c': T}) == { 'c': T, 'e': T} + assert D({'d': T}) == { 'd': T } + assert D({'e': T}) == { 'e': T} + + assert D({'a': F}) == {'a': F } + assert D({'b': F}) == {'a': F, 'b': F } + assert D({'c': F}) == {'a': F, 'b': F, 'c': F } + assert D({'d': F}) == {'a': F, 'b': F, 'd': F } + + assert D({'a': U}) == {'a': U} # XXX ok? + + +def test_FactRules_deduce2(): + # pos/neg/zero, but the rules are not sufficient to derive all relations + f = FactRules(['pos -> !neg', 'pos -> !z']) + + def D(facts): + kb = FactKB(f) + kb.deduce_all_facts(facts) + return kb + + assert D({'pos': T}) == {'pos': T, 'neg': F, 'z': F} + assert D({'pos': F}) == {'pos': F } + assert D({'neg': T}) == {'pos': F, 'neg': T } + assert D({'neg': F}) == { 'neg': F } + assert D({'z': T}) == {'pos': F, 'z': T} + assert D({'z': F}) == { 'z': F} + + # pos/neg/zero. rules are sufficient to derive all relations + f = FactRules(['pos -> !neg', 'neg -> !pos', 'pos -> !z', 'neg -> !z']) + + assert D({'pos': T}) == {'pos': T, 'neg': F, 'z': F} + assert D({'pos': F}) == {'pos': F } + assert D({'neg': T}) == {'pos': F, 'neg': T, 'z': F} + assert D({'neg': F}) == { 'neg': F } + assert D({'z': T}) == {'pos': F, 'neg': F, 'z': T} + assert D({'z': F}) == { 'z': F} + + +def test_FactRules_deduce_multiple(): + # deduction that involves _several_ starting points + f = FactRules(['real == pos | npos']) + + def D(facts): + kb = FactKB(f) + kb.deduce_all_facts(facts) + return kb + + assert D({'real': T}) == {'real': T} + assert D({'real': F}) == {'real': F, 'pos': F, 'npos': F} + assert D({'pos': T}) == {'real': T, 'pos': T} + assert D({'npos': T}) == {'real': T, 'npos': T} + + # --- key tests below --- + assert D({'pos': F, 'npos': F}) == {'real': F, 'pos': F, 'npos': F} + assert D({'real': T, 'pos': F}) == {'real': T, 'pos': F, 'npos': T} + assert D({'real': T, 'npos': F}) == {'real': T, 'pos': T, 'npos': F} + + assert D({'pos': T, 'npos': F}) == {'real': T, 'pos': T, 'npos': F} + assert D({'pos': F, 'npos': T}) == {'real': T, 'pos': F, 'npos': T} + + +def test_FactRules_deduce_multiple2(): + f = FactRules(['real == neg | zero | pos']) + + def D(facts): + kb = FactKB(f) + kb.deduce_all_facts(facts) + return kb + + assert D({'real': T}) == {'real': T} + assert D({'real': F}) == {'real': F, 'neg': F, 'zero': F, 'pos': F} + assert D({'neg': T}) == {'real': T, 'neg': T} + assert D({'zero': T}) == {'real': T, 'zero': T} + assert D({'pos': T}) == {'real': T, 'pos': T} + + # --- key tests below --- + assert D({'neg': F, 'zero': F, 'pos': F}) == {'real': F, 'neg': F, + 'zero': F, 'pos': F} + assert D({'real': T, 'neg': F}) == {'real': T, 'neg': F} + assert D({'real': T, 'zero': F}) == {'real': T, 'zero': F} + assert D({'real': T, 'pos': F}) == {'real': T, 'pos': F} + + assert D({'real': T, 'zero': F, 'pos': F}) == {'real': T, + 'neg': T, 'zero': F, 'pos': F} + assert D({'real': T, 'neg': F, 'pos': F}) == {'real': T, + 'neg': F, 'zero': T, 'pos': F} + assert D({'real': T, 'neg': F, 'zero': F }) == {'real': T, + 'neg': F, 'zero': F, 'pos': T} + + assert D({'neg': T, 'zero': F, 'pos': F}) == {'real': T, 'neg': T, + 'zero': F, 'pos': F} + assert D({'neg': F, 'zero': T, 'pos': F}) == {'real': T, 'neg': F, + 'zero': T, 'pos': F} + assert D({'neg': F, 'zero': F, 'pos': T}) == {'real': T, 'neg': F, + 'zero': F, 'pos': T} + + +def test_FactRules_deduce_base(): + # deduction that starts from base + + f = FactRules(['real == neg | zero | pos', + 'neg -> real & !zero & !pos', + 'pos -> real & !zero & !neg']) + base = FactKB(f) + + base.deduce_all_facts({'real': T, 'neg': F}) + assert base == {'real': T, 'neg': F} + + base.deduce_all_facts({'zero': F}) + assert base == {'real': T, 'neg': F, 'zero': F, 'pos': T} + + +def test_FactRules_deduce_staticext(): + # verify that static beta-extensions deduction takes place + f = FactRules(['real == neg | zero | pos', + 'neg -> real & !zero & !pos', + 'pos -> real & !zero & !neg', + 'nneg == real & !neg', + 'npos == real & !pos']) + + assert ('npos', True) in f.full_implications[('neg', True)] + assert ('nneg', True) in f.full_implications[('pos', True)] + assert ('nneg', True) in f.full_implications[('zero', True)] + assert ('npos', True) in f.full_implications[('zero', True)] diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_function.py b/phivenv/Lib/site-packages/sympy/core/tests/test_function.py new file mode 100644 index 0000000000000000000000000000000000000000..a69c6b81b786ab0f0592367eaf402c2165a615dc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_function.py @@ -0,0 +1,1459 @@ +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic, _aresame +from sympy.core.cache import clear_cache +from sympy.core.containers import Dict, Tuple +from sympy.core.expr import Expr, unchanged +from sympy.core.function import (Subs, Function, diff, Lambda, expand, + nfloat, Derivative) +from sympy.core.numbers import E, Float, zoo, Rational, pi, I, oo, nan +from sympy.core.power import Pow +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols, Dummy, Symbol +from sympy.functions.elementary.complexes import im, re +from sympy.functions.elementary.exponential import log, exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import sin, cos, acos +from sympy.functions.special.error_functions import expint +from sympy.functions.special.gamma_functions import loggamma, polygamma +from sympy.matrices.dense import Matrix +from sympy.printing.str import sstr +from sympy.series.order import O +from sympy.tensor.indexed import Indexed +from sympy.core.function import (PoleError, _mexpand, arity, + BadSignatureError, BadArgumentsError) +from sympy.core.parameters import _exp_is_pow +from sympy.core.sympify import sympify, SympifyError +from sympy.matrices import MutableMatrix, ImmutableMatrix +from sympy.sets.sets import FiniteSet +from sympy.solvers.solveset import solveset +from sympy.tensor.array import NDimArray +from sympy.utilities.iterables import subsets, variations +from sympy.testing.pytest import XFAIL, raises, warns_deprecated_sympy, _both_exp_pow + +from sympy.abc import t, w, x, y, z +f, g, h = symbols('f g h', cls=Function) +_xi_1, _xi_2, _xi_3 = [Dummy() for i in range(3)] + +def test_f_expand_complex(): + x = Symbol('x', real=True) + + assert f(x).expand(complex=True) == I*im(f(x)) + re(f(x)) + assert exp(x).expand(complex=True) == exp(x) + assert exp(I*x).expand(complex=True) == cos(x) + I*sin(x) + assert exp(z).expand(complex=True) == cos(im(z))*exp(re(z)) + \ + I*sin(im(z))*exp(re(z)) + + +def test_bug1(): + e = sqrt(-log(w)) + assert e.subs(log(w), -x) == sqrt(x) + + e = sqrt(-5*log(w)) + assert e.subs(log(w), -x) == sqrt(5*x) + + +def test_general_function(): + nu = Function('nu') + + e = nu(x) + edx = e.diff(x) + edy = e.diff(y) + edxdx = e.diff(x).diff(x) + edxdy = e.diff(x).diff(y) + assert e == nu(x) + assert edx != nu(x) + assert edx == diff(nu(x), x) + assert edy == 0 + assert edxdx == diff(diff(nu(x), x), x) + assert edxdy == 0 + +def test_general_function_nullary(): + nu = Function('nu') + + e = nu() + edx = e.diff(x) + edxdx = e.diff(x).diff(x) + assert e == nu() + assert edx != nu() + assert edx == 0 + assert edxdx == 0 + + +def test_derivative_subs_bug(): + e = diff(g(x), x) + assert e.subs(g(x), f(x)) != e + assert e.subs(g(x), f(x)) == Derivative(f(x), x) + assert e.subs(g(x), -f(x)) == Derivative(-f(x), x) + + assert e.subs(x, y) == Derivative(g(y), y) + + +def test_derivative_subs_self_bug(): + d = diff(f(x), x) + + assert d.subs(d, y) == y + + +def test_derivative_linearity(): + assert diff(-f(x), x) == -diff(f(x), x) + assert diff(8*f(x), x) == 8*diff(f(x), x) + assert diff(8*f(x), x) != 7*diff(f(x), x) + assert diff(8*f(x)*x, x) == 8*f(x) + 8*x*diff(f(x), x) + assert diff(8*f(x)*y*x, x).expand() == 8*y*f(x) + 8*y*x*diff(f(x), x) + + +def test_derivative_evaluate(): + assert Derivative(sin(x), x) != diff(sin(x), x) + assert Derivative(sin(x), x).doit() == diff(sin(x), x) + + assert Derivative(Derivative(f(x), x), x) == diff(f(x), x, x) + assert Derivative(sin(x), x, 0) == sin(x) + assert Derivative(sin(x), (x, y), (x, -y)) == sin(x) + + +def test_diff_symbols(): + assert diff(f(x, y, z), x, y, z) == Derivative(f(x, y, z), x, y, z) + assert diff(f(x, y, z), x, x, x) == Derivative(f(x, y, z), x, x, x) == Derivative(f(x, y, z), (x, 3)) + assert diff(f(x, y, z), x, 3) == Derivative(f(x, y, z), x, 3) + + # issue 5028 + assert [diff(-z + x/y, sym) for sym in (z, x, y)] == [-1, 1/y, -x/y**2] + assert diff(f(x, y, z), x, y, z, 2) == Derivative(f(x, y, z), x, y, z, z) + assert diff(f(x, y, z), x, y, z, 2, evaluate=False) == \ + Derivative(f(x, y, z), x, y, z, z) + assert Derivative(f(x, y, z), x, y, z)._eval_derivative(z) == \ + Derivative(f(x, y, z), x, y, z, z) + assert Derivative(Derivative(f(x, y, z), x), y)._eval_derivative(z) == \ + Derivative(f(x, y, z), x, y, z) + + raises(TypeError, lambda: cos(x).diff((x, y)).variables) + assert cos(x).diff((x, y))._wrt_variables == [x] + + # issue 23222 + assert sympify("a*x+b").diff("x") == sympify("a") + +def test_Function(): + class myfunc(Function): + @classmethod + def eval(cls): # zero args + return + + assert myfunc.nargs == FiniteSet(0) + assert myfunc().nargs == FiniteSet(0) + raises(TypeError, lambda: myfunc(x).nargs) + + class myfunc(Function): + @classmethod + def eval(cls, x): # one arg + return + + assert myfunc.nargs == FiniteSet(1) + assert myfunc(x).nargs == FiniteSet(1) + raises(TypeError, lambda: myfunc(x, y).nargs) + + class myfunc(Function): + @classmethod + def eval(cls, *x): # star args + return + + assert myfunc.nargs == S.Naturals0 + assert myfunc(x).nargs == S.Naturals0 + + +def test_nargs(): + f = Function('f') + assert f.nargs == S.Naturals0 + assert f(1).nargs == S.Naturals0 + assert Function('f', nargs=2)(1, 2).nargs == FiniteSet(2) + assert sin.nargs == FiniteSet(1) + assert sin(2).nargs == FiniteSet(1) + assert log.nargs == FiniteSet(1, 2) + assert log(2).nargs == FiniteSet(1, 2) + assert Function('f', nargs=2).nargs == FiniteSet(2) + assert Function('f', nargs=0).nargs == FiniteSet(0) + assert Function('f', nargs=(0, 1)).nargs == FiniteSet(0, 1) + assert Function('f', nargs=None).nargs == S.Naturals0 + raises(ValueError, lambda: Function('f', nargs=())) + +def test_nargs_inheritance(): + class f1(Function): + nargs = 2 + class f2(f1): + pass + class f3(f2): + pass + class f4(f3): + nargs = 1,2 + class f5(f4): + pass + class f6(f5): + pass + class f7(f6): + nargs=None + class f8(f7): + pass + class f9(f8): + pass + class f10(f9): + nargs = 1 + class f11(f10): + pass + assert f1.nargs == FiniteSet(2) + assert f2.nargs == FiniteSet(2) + assert f3.nargs == FiniteSet(2) + assert f4.nargs == FiniteSet(1, 2) + assert f5.nargs == FiniteSet(1, 2) + assert f6.nargs == FiniteSet(1, 2) + assert f7.nargs == S.Naturals0 + assert f8.nargs == S.Naturals0 + assert f9.nargs == S.Naturals0 + assert f10.nargs == FiniteSet(1) + assert f11.nargs == FiniteSet(1) + +def test_arity(): + f = lambda x, y: 1 + assert arity(f) == 2 + def f(x, y, z=None): + pass + assert arity(f) == (2, 3) + assert arity(lambda *x: x) is None + assert arity(log) == (1, 2) + + +def test_Lambda(): + e = Lambda(x, x**2) + assert e(4) == 16 + assert e(x) == x**2 + assert e(y) == y**2 + + assert Lambda((), 42)() == 42 + assert unchanged(Lambda, (), 42) + assert Lambda((), 42) != Lambda((), 43) + assert Lambda((), f(x))() == f(x) + assert Lambda((), 42).nargs == FiniteSet(0) + + assert unchanged(Lambda, (x,), x**2) + assert Lambda(x, x**2) == Lambda((x,), x**2) + assert Lambda(x, x**2) != Lambda(x, x**2 + 1) + assert Lambda((x, y), x**y) != Lambda((y, x), y**x) + assert Lambda((x, y), x**y) != Lambda((x, y), y**x) + + assert Lambda((x, y), x**y)(x, y) == x**y + assert Lambda((x, y), x**y)(3, 3) == 3**3 + assert Lambda((x, y), x**y)(x, 3) == x**3 + assert Lambda((x, y), x**y)(3, y) == 3**y + assert Lambda(x, f(x))(x) == f(x) + assert Lambda(x, x**2)(e(x)) == x**4 + assert e(e(x)) == x**4 + + x1, x2 = (Indexed('x', i) for i in (1, 2)) + assert Lambda((x1, x2), x1 + x2)(x, y) == x + y + + assert Lambda((x, y), x + y).nargs == FiniteSet(2) + + p = x, y, z, t + assert Lambda(p, t*(x + y + z))(*p) == t * (x + y + z) + + eq = Lambda(x, 2*x) + Lambda(y, 2*y) + assert eq != 2*Lambda(x, 2*x) + assert eq.as_dummy() == 2*Lambda(x, 2*x).as_dummy() + assert Lambda(x, 2*x) not in [ Lambda(x, x) ] + raises(BadSignatureError, lambda: Lambda(1, x)) + assert Lambda(x, 1)(1) is S.One + + raises(BadSignatureError, lambda: Lambda((x, x), x + 2)) + raises(BadSignatureError, lambda: Lambda(((x, x), y), x)) + raises(BadSignatureError, lambda: Lambda(((y, x), x), x)) + raises(BadSignatureError, lambda: Lambda(((y, 1), 2), x)) + + with warns_deprecated_sympy(): + assert Lambda([x, y], x+y) == Lambda((x, y), x+y) + + flam = Lambda(((x, y),), x + y) + assert flam((2, 3)) == 5 + flam = Lambda(((x, y), z), x + y + z) + assert flam((2, 3), 1) == 6 + flam = Lambda((((x, y), z),), x + y + z) + assert flam(((2, 3), 1)) == 6 + raises(BadArgumentsError, lambda: flam(1, 2, 3)) + flam = Lambda( (x,), (x, x)) + assert flam(1,) == (1, 1) + assert flam((1,)) == ((1,), (1,)) + flam = Lambda( ((x,),), (x, x)) + raises(BadArgumentsError, lambda: flam(1)) + assert flam((1,)) == (1, 1) + + # Previously TypeError was raised so this is potentially needed for + # backwards compatibility. + assert issubclass(BadSignatureError, TypeError) + assert issubclass(BadArgumentsError, TypeError) + + # These are tested to see they don't raise: + hash(Lambda(x, 2*x)) + hash(Lambda(x, x)) # IdentityFunction subclass + + +def test_IdentityFunction(): + assert Lambda(x, x) is Lambda(y, y) is S.IdentityFunction + assert Lambda(x, 2*x) is not S.IdentityFunction + assert Lambda((x, y), x) is not S.IdentityFunction + + +def test_Lambda_symbols(): + assert Lambda(x, 2*x).free_symbols == set() + assert Lambda(x, x*y).free_symbols == {y} + assert Lambda((), 42).free_symbols == set() + assert Lambda((), x*y).free_symbols == {x,y} + + +def test_functionclas_symbols(): + assert f.free_symbols == set() + + +def test_Lambda_arguments(): + raises(TypeError, lambda: Lambda(x, 2*x)(x, y)) + raises(TypeError, lambda: Lambda((x, y), x + y)(x)) + raises(TypeError, lambda: Lambda((), 42)(x)) + + +def test_Lambda_equality(): + assert Lambda((x, y), 2*x) == Lambda((x, y), 2*x) + # these, of course, should never be equal + assert Lambda(x, 2*x) != Lambda((x, y), 2*x) + assert Lambda(x, 2*x) != 2*x + # But it is tempting to want expressions that differ only + # in bound symbols to compare the same. But this is not what + # Python's `==` is intended to do; two objects that compare + # as equal means that they are indistibguishable and cache to the + # same value. We wouldn't want to expression that are + # mathematically the same but written in different variables to be + # interchanged else what is the point of allowing for different + # variable names? + assert Lambda(x, 2*x) != Lambda(y, 2*y) + + +def test_Subs(): + assert Subs(1, (), ()) is S.One + # check null subs influence on hashing + assert Subs(x, y, z) != Subs(x, y, 1) + # neutral subs works + assert Subs(x, x, 1).subs(x, y).has(y) + # self mapping var/point + assert Subs(Derivative(f(x), (x, 2)), x, x).doit() == f(x).diff(x, x) + assert Subs(x, x, 0).has(x) # it's a structural answer + assert not Subs(x, x, 0).free_symbols + assert Subs(Subs(x + y, x, 2), y, 1) == Subs(x + y, (x, y), (2, 1)) + assert Subs(x, (x,), (0,)) == Subs(x, x, 0) + assert Subs(x, x, 0) == Subs(y, y, 0) + assert Subs(x, x, 0).subs(x, 1) == Subs(x, x, 0) + assert Subs(y, x, 0).subs(y, 1) == Subs(1, x, 0) + assert Subs(f(x), x, 0).doit() == f(0) + assert Subs(f(x**2), x**2, 0).doit() == f(0) + assert Subs(f(x, y, z), (x, y, z), (0, 1, 1)) != \ + Subs(f(x, y, z), (x, y, z), (0, 0, 1)) + assert Subs(x, y, 2).subs(x, y).doit() == 2 + assert Subs(f(x, y), (x, y, z), (0, 1, 1)) != \ + Subs(f(x, y) + z, (x, y, z), (0, 1, 0)) + assert Subs(f(x, y), (x, y), (0, 1)).doit() == f(0, 1) + assert Subs(Subs(f(x, y), x, 0), y, 1).doit() == f(0, 1) + raises(ValueError, lambda: Subs(f(x, y), (x, y), (0, 0, 1))) + raises(ValueError, lambda: Subs(f(x, y), (x, x, y), (0, 0, 1))) + + assert len(Subs(f(x, y), (x, y), (0, 1)).variables) == 2 + assert Subs(f(x, y), (x, y), (0, 1)).point == Tuple(0, 1) + + assert Subs(f(x), x, 0) == Subs(f(y), y, 0) + assert Subs(f(x, y), (x, y), (0, 1)) == Subs(f(x, y), (y, x), (1, 0)) + assert Subs(f(x)*y, (x, y), (0, 1)) == Subs(f(y)*x, (y, x), (0, 1)) + assert Subs(f(x)*y, (x, y), (1, 1)) == Subs(f(y)*x, (x, y), (1, 1)) + + assert Subs(f(x), x, 0).subs(x, 1).doit() == f(0) + assert Subs(f(x), x, y).subs(y, 0) == Subs(f(x), x, 0) + assert Subs(y*f(x), x, y).subs(y, 2) == Subs(2*f(x), x, 2) + assert (2 * Subs(f(x), x, 0)).subs(Subs(f(x), x, 0), y) == 2*y + + assert Subs(f(x), x, 0).free_symbols == set() + assert Subs(f(x, y), x, z).free_symbols == {y, z} + + assert Subs(f(x).diff(x), x, 0).doit(), Subs(f(x).diff(x), x, 0) + assert Subs(1 + f(x).diff(x), x, 0).doit(), 1 + Subs(f(x).diff(x), x, 0) + assert Subs(y*f(x, y).diff(x), (x, y), (0, 2)).doit() == \ + 2*Subs(Derivative(f(x, 2), x), x, 0) + assert Subs(y**2*f(x), x, 0).diff(y) == 2*y*f(0) + + e = Subs(y**2*f(x), x, y) + assert e.diff(y) == e.doit().diff(y) == y**2*Derivative(f(y), y) + 2*y*f(y) + + assert Subs(f(x), x, 0) + Subs(f(x), x, 0) == 2*Subs(f(x), x, 0) + e1 = Subs(z*f(x), x, 1) + e2 = Subs(z*f(y), y, 1) + assert e1 + e2 == 2*e1 + assert e1.__hash__() == e2.__hash__() + assert Subs(z*f(x + 1), x, 1) not in [ e1, e2 ] + assert Derivative(f(x), x).subs(x, g(x)) == Derivative(f(g(x)), g(x)) + assert Derivative(f(x), x).subs(x, x + y) == Subs(Derivative(f(x), x), + x, x + y) + assert Subs(f(x)*cos(y) + z, (x, y), (0, pi/3)).n(2) == \ + Subs(f(x)*cos(y) + z, (x, y), (0, pi/3)).evalf(2) == \ + z + Rational('1/2').n(2)*f(0) + + assert f(x).diff(x).subs(x, 0).subs(x, y) == f(x).diff(x).subs(x, 0) + assert (x*f(x).diff(x).subs(x, 0)).subs(x, y) == y*f(x).diff(x).subs(x, 0) + assert Subs(Derivative(g(x)**2, g(x), x), g(x), exp(x) + ).doit() == 2*exp(x) + assert Subs(Derivative(g(x)**2, g(x), x), g(x), exp(x) + ).doit(deep=False) == 2*Derivative(exp(x), x) + assert Derivative(f(x, g(x)), x).doit() == Derivative( + f(x, g(x)), g(x))*Derivative(g(x), x) + Subs(Derivative( + f(y, g(x)), y), y, x) + +def test_doitdoit(): + done = Derivative(f(x, g(x)), x, g(x)).doit() + assert done == done.doit() + + +@XFAIL +def test_Subs2(): + # this reflects a limitation of subs(), probably won't fix + assert Subs(f(x), x**2, x).doit() == f(sqrt(x)) + + +def test_expand_function(): + assert expand(x + y) == x + y + assert expand(x + y, complex=True) == I*im(x) + I*im(y) + re(x) + re(y) + assert expand((x + y)**11, modulus=11) == x**11 + y**11 + + +def test_function_comparable(): + assert sin(x).is_comparable is False + assert cos(x).is_comparable is False + + assert sin(Float('0.1')).is_comparable is True + assert cos(Float('0.1')).is_comparable is True + + assert sin(E).is_comparable is True + assert cos(E).is_comparable is True + + assert sin(Rational(1, 3)).is_comparable is True + assert cos(Rational(1, 3)).is_comparable is True + + +def test_function_comparable_infinities(): + assert sin(oo).is_comparable is False + assert sin(-oo).is_comparable is False + assert sin(zoo).is_comparable is False + assert sin(nan).is_comparable is False + + +def test_deriv1(): + # These all require derivatives evaluated at a point (issue 4719) to work. + # See issue 4624 + assert f(2*x).diff(x) == 2*Subs(Derivative(f(x), x), x, 2*x) + assert (f(x)**3).diff(x) == 3*f(x)**2*f(x).diff(x) + assert (f(2*x)**3).diff(x) == 6*f(2*x)**2*Subs( + Derivative(f(x), x), x, 2*x) + + assert f(2 + x).diff(x) == Subs(Derivative(f(x), x), x, x + 2) + assert f(2 + 3*x).diff(x) == 3*Subs( + Derivative(f(x), x), x, 3*x + 2) + assert f(3*sin(x)).diff(x) == 3*cos(x)*Subs( + Derivative(f(x), x), x, 3*sin(x)) + + # See issue 8510 + assert f(x, x + z).diff(x) == ( + Subs(Derivative(f(y, x + z), y), y, x) + + Subs(Derivative(f(x, y), y), y, x + z)) + assert f(x, x**2).diff(x) == ( + 2*x*Subs(Derivative(f(x, y), y), y, x**2) + + Subs(Derivative(f(y, x**2), y), y, x)) + # but Subs is not always necessary + assert f(x, g(y)).diff(g(y)) == Derivative(f(x, g(y)), g(y)) + + +def test_deriv2(): + assert (x**3).diff(x) == 3*x**2 + assert (x**3).diff(x, evaluate=False) != 3*x**2 + assert (x**3).diff(x, evaluate=False) == Derivative(x**3, x) + + assert diff(x**3, x) == 3*x**2 + assert diff(x**3, x, evaluate=False) != 3*x**2 + assert diff(x**3, x, evaluate=False) == Derivative(x**3, x) + + +def test_func_deriv(): + assert f(x).diff(x) == Derivative(f(x), x) + # issue 4534 + assert f(x, y).diff(x, y) - f(x, y).diff(y, x) == 0 + assert Derivative(f(x, y), x, y).args[1:] == ((x, 1), (y, 1)) + assert Derivative(f(x, y), y, x).args[1:] == ((y, 1), (x, 1)) + assert (Derivative(f(x, y), x, y) - Derivative(f(x, y), y, x)).doit() == 0 + + +def test_suppressed_evaluation(): + a = sin(0, evaluate=False) + assert a != 0 + assert a.func is sin + assert a.args == (0,) + + +def test_function_evalf(): + def eq(a, b, eps): + return abs(a - b) < eps + assert eq(sin(1).evalf(15), Float("0.841470984807897"), 1e-13) + assert eq( + sin(2).evalf(25), Float("0.9092974268256816953960199", 25), 1e-23) + assert eq(sin(1 + I).evalf( + 15), Float("1.29845758141598") + Float("0.634963914784736")*I, 1e-13) + assert eq(exp(1 + I).evalf(15), Float( + "1.46869393991588") + Float("2.28735528717884239")*I, 1e-13) + assert eq(exp(-0.5 + 1.5*I).evalf(15), Float( + "0.0429042815937374") + Float("0.605011292285002")*I, 1e-13) + assert eq(log(pi + sqrt(2)*I).evalf( + 15), Float("1.23699044022052") + Float("0.422985442737893")*I, 1e-13) + assert eq(cos(100).evalf(15), Float("0.86231887228768"), 1e-13) + + +def test_extensibility_eval(): + class MyFunc(Function): + @classmethod + def eval(cls, *args): + return (0, 0, 0) + assert MyFunc(0) == (0, 0, 0) + + +@_both_exp_pow +def test_function_non_commutative(): + x = Symbol('x', commutative=False) + assert f(x).is_commutative is False + assert sin(x).is_commutative is False + assert exp(x).is_commutative is False + assert log(x).is_commutative is False + assert f(x).is_complex is False + assert sin(x).is_complex is False + assert exp(x).is_complex is False + assert log(x).is_complex is False + + +def test_function_complex(): + x = Symbol('x', complex=True) + xzf = Symbol('x', complex=True, zero=False) + assert f(x).is_commutative is True + assert sin(x).is_commutative is True + assert exp(x).is_commutative is True + assert log(x).is_commutative is True + assert f(x).is_complex is None + assert sin(x).is_complex is True + assert exp(x).is_complex is True + assert log(x).is_complex is None + assert log(xzf).is_complex is True + + +def test_function__eval_nseries(): + n = Symbol('n') + + assert sin(x)._eval_nseries(x, 2, None) == x + O(x**2) + assert sin(x + 1)._eval_nseries(x, 2, None) == x*cos(1) + sin(1) + O(x**2) + assert sin(pi*(1 - x))._eval_nseries(x, 2, None) == pi*x + O(x**2) + assert acos(1 - x**2)._eval_nseries(x, 2, None) == sqrt(2)*sqrt(x**2) + O(x**2) + assert polygamma(n, x + 1)._eval_nseries(x, 2, None) == \ + polygamma(n, 1) + polygamma(n + 1, 1)*x + O(x**2) + raises(PoleError, lambda: sin(1/x)._eval_nseries(x, 2, None)) + assert acos(1 - x)._eval_nseries(x, 2, None) == sqrt(2)*sqrt(x) + sqrt(2)*x**(S(3)/2)/12 + O(x**2) + assert acos(1 + x)._eval_nseries(x, 2, None) == sqrt(2)*sqrt(-x) + sqrt(2)*(-x)**(S(3)/2)/12 + O(x**2) + assert loggamma(1/x)._eval_nseries(x, 0, None) == \ + log(x)/2 - log(x)/x - 1/x + O(1, x) + assert loggamma(log(1/x)).nseries(x, n=1, logx=y) == loggamma(-y) + + # issue 6725: + assert expint(Rational(3, 2), -x)._eval_nseries(x, 5, None) == \ + 2 - 2*x - x**2/3 - x**3/15 - x**4/84 - 2*I*sqrt(pi)*sqrt(x) + O(x**5) + assert sin(sqrt(x))._eval_nseries(x, 3, None) == \ + sqrt(x) - x**Rational(3, 2)/6 + x**Rational(5, 2)/120 + O(x**3) + + # issue 19065: + s1 = f(x,y).series(y, n=2) + assert {i.name for i in s1.atoms(Symbol)} == {'x', 'xi', 'y'} + xi = Symbol('xi') + s2 = f(xi, y).series(y, n=2) + assert {i.name for i in s2.atoms(Symbol)} == {'xi', 'xi0', 'y'} + +def test_doit(): + n = Symbol('n', integer=True) + f = Sum(2 * n * x, (n, 1, 3)) + d = Derivative(f, x) + assert d.doit() == 12 + assert d.doit(deep=False) == Sum(2*n, (n, 1, 3)) + + +def test_evalf_default(): + from sympy.functions.special.gamma_functions import polygamma + assert type(sin(4.0)) == Float + assert type(re(sin(I + 1.0))) == Float + assert type(im(sin(I + 1.0))) == Float + assert type(sin(4)) == sin + assert type(polygamma(2.0, 4.0)) == Float + assert type(sin(Rational(1, 4))) == sin + + +def test_issue_5399(): + args = [x, y, S(2), S.Half] + + def ok(a): + """Return True if the input args for diff are ok""" + if not a: + return False + if a[0].is_Symbol is False: + return False + s_at = [i for i in range(len(a)) if a[i].is_Symbol] + n_at = [i for i in range(len(a)) if not a[i].is_Symbol] + # every symbol is followed by symbol or int + # every number is followed by a symbol + return (all(a[i + 1].is_Symbol or a[i + 1].is_Integer + for i in s_at if i + 1 < len(a)) and + all(a[i + 1].is_Symbol + for i in n_at if i + 1 < len(a))) + eq = x**10*y**8 + for a in subsets(args): + for v in variations(a, len(a)): + if ok(v): + eq.diff(*v) # does not raise + else: + raises(ValueError, lambda: eq.diff(*v)) + + +def test_derivative_numerically(): + z0 = x._random() + assert abs(Derivative(sin(x), x).doit_numerically(z0) - cos(z0)) < 1e-15 + + +def test_fdiff_argument_index_error(): + from sympy.core.function import ArgumentIndexError + + class myfunc(Function): + nargs = 1 # define since there is no eval routine + + def fdiff(self, idx): + raise ArgumentIndexError + mf = myfunc(x) + assert mf.diff(x) == Derivative(mf, x) + raises(TypeError, lambda: myfunc(x, x)) + + +def test_deriv_wrt_function(): + x = f(t) + xd = diff(x, t) + xdd = diff(xd, t) + y = g(t) + yd = diff(y, t) + + assert diff(x, t) == xd + assert diff(2 * x + 4, t) == 2 * xd + assert diff(2 * x + 4 + y, t) == 2 * xd + yd + assert diff(2 * x + 4 + y * x, t) == 2 * xd + x * yd + xd * y + assert diff(2 * x + 4 + y * x, x) == 2 + y + assert (diff(4 * x**2 + 3 * x + x * y, t) == 3 * xd + x * yd + xd * y + + 8 * x * xd) + assert (diff(4 * x**2 + 3 * xd + x * y, t) == 3 * xdd + x * yd + xd * y + + 8 * x * xd) + assert diff(4 * x**2 + 3 * xd + x * y, xd) == 3 + assert diff(4 * x**2 + 3 * xd + x * y, xdd) == 0 + assert diff(sin(x), t) == xd * cos(x) + assert diff(exp(x), t) == xd * exp(x) + assert diff(sqrt(x), t) == xd / (2 * sqrt(x)) + + +def test_diff_wrt_value(): + assert Expr()._diff_wrt is False + assert x._diff_wrt is True + assert f(x)._diff_wrt is True + assert Derivative(f(x), x)._diff_wrt is True + assert Derivative(x**2, x)._diff_wrt is False + + +def test_diff_wrt(): + fx = f(x) + dfx = diff(f(x), x) + ddfx = diff(f(x), x, x) + + assert diff(sin(fx) + fx**2, fx) == cos(fx) + 2*fx + assert diff(sin(dfx) + dfx**2, dfx) == cos(dfx) + 2*dfx + assert diff(sin(ddfx) + ddfx**2, ddfx) == cos(ddfx) + 2*ddfx + assert diff(fx**2, dfx) == 0 + assert diff(fx**2, ddfx) == 0 + assert diff(dfx**2, fx) == 0 + assert diff(dfx**2, ddfx) == 0 + assert diff(ddfx**2, dfx) == 0 + + assert diff(fx*dfx*ddfx, fx) == dfx*ddfx + assert diff(fx*dfx*ddfx, dfx) == fx*ddfx + assert diff(fx*dfx*ddfx, ddfx) == fx*dfx + + assert diff(f(x), x).diff(f(x)) == 0 + assert (sin(f(x)) - cos(diff(f(x), x))).diff(f(x)) == cos(f(x)) + + assert diff(sin(fx), fx, x) == diff(sin(fx), x, fx) + + # Chain rule cases + assert f(g(x)).diff(x) == \ + Derivative(g(x), x)*Derivative(f(g(x)), g(x)) + assert diff(f(g(x), h(y)), x) == \ + Derivative(g(x), x)*Derivative(f(g(x), h(y)), g(x)) + assert diff(f(g(x), h(x)), x) == ( + Derivative(f(g(x), h(x)), g(x))*Derivative(g(x), x) + + Derivative(f(g(x), h(x)), h(x))*Derivative(h(x), x)) + assert f( + sin(x)).diff(x) == cos(x)*Subs(Derivative(f(x), x), x, sin(x)) + + assert diff(f(g(x)), g(x)) == Derivative(f(g(x)), g(x)) + + +def test_diff_wrt_func_subs(): + assert f(g(x)).diff(x).subs(g, Lambda(x, 2*x)).doit() == f(2*x).diff(x) + + +def test_subs_in_derivative(): + expr = sin(x*exp(y)) + u = Function('u') + v = Function('v') + assert Derivative(expr, y).subs(expr, y) == Derivative(y, y) + assert Derivative(expr, y).subs(y, x).doit() == \ + Derivative(expr, y).doit().subs(y, x) + assert Derivative(f(x, y), y).subs(y, x) == Subs(Derivative(f(x, y), y), y, x) + assert Derivative(f(x, y), y).subs(x, y) == Subs(Derivative(f(x, y), y), x, y) + assert Derivative(f(x, y), y).subs(y, g(x, y)) == Subs(Derivative(f(x, y), y), y, g(x, y)).doit() + assert Derivative(f(x, y), y).subs(x, g(x, y)) == Subs(Derivative(f(x, y), y), x, g(x, y)) + assert Derivative(f(x, y), g(y)).subs(x, g(x, y)) == Derivative(f(g(x, y), y), g(y)) + assert Derivative(f(u(x), h(y)), h(y)).subs(h(y), g(x, y)) == \ + Subs(Derivative(f(u(x), h(y)), h(y)), h(y), g(x, y)).doit() + assert Derivative(f(x, y), y).subs(y, z) == Derivative(f(x, z), z) + assert Derivative(f(x, y), y).subs(y, g(y)) == Derivative(f(x, g(y)), g(y)) + assert Derivative(f(g(x), h(y)), h(y)).subs(h(y), u(y)) == \ + Derivative(f(g(x), u(y)), u(y)) + assert Derivative(f(x, f(x, x)), f(x, x)).subs( + f, Lambda((x, y), x + y)) == Subs( + Derivative(z + x, z), z, 2*x) + assert Subs(Derivative(f(f(x)), x), f, cos).doit() == sin(x)*sin(cos(x)) + assert Subs(Derivative(f(f(x)), f(x)), f, cos).doit() == -sin(cos(x)) + # Issue 13791. No comparison (it's a long formula) but this used to raise an exception. + assert isinstance(v(x, y, u(x, y)).diff(y).diff(x).diff(y), Expr) + # This is also related to issues 13791 and 13795; issue 15190 + F = Lambda((x, y), exp(2*x + 3*y)) + abstract = f(x, f(x, x)).diff(x, 2) + concrete = F(x, F(x, x)).diff(x, 2) + assert (abstract.subs(f, F).doit() - concrete).simplify() == 0 + # don't introduce a new symbol if not necessary + assert x in f(x).diff(x).subs(x, 0).atoms() + # case (4) + assert Derivative(f(x,f(x,y)), x, y).subs(x, g(y) + ) == Subs(Derivative(f(x, f(x, y)), x, y), x, g(y)) + + assert Derivative(f(x, x), x).subs(x, 0 + ) == Subs(Derivative(f(x, x), x), x, 0) + # issue 15194 + assert Derivative(f(y, g(x)), (x, z)).subs(z, x + ) == Derivative(f(y, g(x)), (x, x)) + + df = f(x).diff(x) + assert df.subs(df, 1) is S.One + assert df.diff(df) is S.One + dxy = Derivative(f(x, y), x, y) + dyx = Derivative(f(x, y), y, x) + assert dxy.subs(Derivative(f(x, y), y, x), 1) is S.One + assert dxy.diff(dyx) is S.One + assert Derivative(f(x, y), x, 2, y, 3).subs( + dyx, g(x, y)) == Derivative(g(x, y), x, 1, y, 2) + assert Derivative(f(x, x - y), y).subs(x, x + y) == Subs( + Derivative(f(x, x - y), y), x, x + y) + + +def test_diff_wrt_not_allowed(): + # issue 7027 included + for wrt in ( + cos(x), re(x), x**2, x*y, 1 + x, + Derivative(cos(x), x), Derivative(f(f(x)), x)): + raises(ValueError, lambda: diff(f(x), wrt)) + # if we don't differentiate wrt then don't raise error + assert diff(exp(x*y), x*y, 0) == exp(x*y) + + +def test_diff_wrt_intlike(): + class Two: + def __int__(self): + return 2 + + assert cos(x).diff(x, Two()) == -cos(x) + + +def test_klein_gordon_lagrangian(): + m = Symbol('m') + phi = f(x, t) + + L = -(diff(phi, t)**2 - diff(phi, x)**2 - m**2*phi**2)/2 + eqna = Eq( + diff(L, phi) - diff(L, diff(phi, x), x) - diff(L, diff(phi, t), t), 0) + eqnb = Eq(diff(phi, t, t) - diff(phi, x, x) + m**2*phi, 0) + assert eqna == eqnb + + +def test_sho_lagrangian(): + m = Symbol('m') + k = Symbol('k') + x = f(t) + + L = m*diff(x, t)**2/2 - k*x**2/2 + eqna = Eq(diff(L, x), diff(L, diff(x, t), t)) + eqnb = Eq(-k*x, m*diff(x, t, t)) + assert eqna == eqnb + + assert diff(L, x, t) == diff(L, t, x) + assert diff(L, diff(x, t), t) == m*diff(x, t, 2) + assert diff(L, t, diff(x, t)) == -k*x + m*diff(x, t, 2) + + +def test_straight_line(): + F = f(x) + Fd = F.diff(x) + L = sqrt(1 + Fd**2) + assert diff(L, F) == 0 + assert diff(L, Fd) == Fd/sqrt(1 + Fd**2) + + +def test_sort_variable(): + vsort = Derivative._sort_variable_count + def vsort0(*v, reverse=False): + return [i[0] for i in vsort([(i, 0) for i in ( + reversed(v) if reverse else v)])] + + for R in range(2): + assert vsort0(y, x, reverse=R) == [x, y] + assert vsort0(f(x), x, reverse=R) == [x, f(x)] + assert vsort0(f(y), f(x), reverse=R) == [f(x), f(y)] + assert vsort0(g(x), f(y), reverse=R) == [f(y), g(x)] + assert vsort0(f(x, y), f(x), reverse=R) == [f(x), f(x, y)] + fx = f(x).diff(x) + assert vsort0(fx, y, reverse=R) == [y, fx] + fy = f(y).diff(y) + assert vsort0(fy, fx, reverse=R) == [fx, fy] + fxx = fx.diff(x) + assert vsort0(fxx, fx, reverse=R) == [fx, fxx] + assert vsort0(Basic(x), f(x), reverse=R) == [f(x), Basic(x)] + assert vsort0(Basic(y), Basic(x), reverse=R) == [Basic(x), Basic(y)] + assert vsort0(Basic(y, z), Basic(x), reverse=R) == [ + Basic(x), Basic(y, z)] + assert vsort0(fx, x, reverse=R) == [ + x, fx] if R else [fx, x] + assert vsort0(Basic(x), x, reverse=R) == [ + x, Basic(x)] if R else [Basic(x), x] + assert vsort0(Basic(f(x)), f(x), reverse=R) == [ + f(x), Basic(f(x))] if R else [Basic(f(x)), f(x)] + assert vsort0(Basic(x, z), Basic(x), reverse=R) == [ + Basic(x), Basic(x, z)] if R else [Basic(x, z), Basic(x)] + assert vsort([]) == [] + assert _aresame(vsort([(x, 1)]), [Tuple(x, 1)]) + assert vsort([(x, y), (x, z)]) == [(x, y + z)] + assert vsort([(y, 1), (x, 1 + y)]) == [(x, 1 + y), (y, 1)] + # coverage complete; legacy tests below + assert vsort([(x, 3), (y, 2), (z, 1)]) == [(x, 3), (y, 2), (z, 1)] + assert vsort([(h(x), 1), (g(x), 1), (f(x), 1)]) == [ + (f(x), 1), (g(x), 1), (h(x), 1)] + assert vsort([(z, 1), (y, 2), (x, 3), (h(x), 1), (g(x), 1), + (f(x), 1)]) == [(x, 3), (y, 2), (z, 1), (f(x), 1), (g(x), 1), + (h(x), 1)] + assert vsort([(x, 1), (f(x), 1), (y, 1), (f(y), 1)]) == [(x, 1), + (y, 1), (f(x), 1), (f(y), 1)] + assert vsort([(y, 1), (x, 2), (g(x), 1), (f(x), 1), (z, 1), + (h(x), 1), (y, 2), (x, 1)]) == [(x, 3), (y, 3), (z, 1), + (f(x), 1), (g(x), 1), (h(x), 1)] + assert vsort([(z, 1), (y, 1), (f(x), 1), (x, 1), (f(x), 1), + (g(x), 1)]) == [(x, 1), (y, 1), (z, 1), (f(x), 2), (g(x), 1)] + assert vsort([(z, 1), (y, 2), (f(x), 1), (x, 2), (f(x), 2), + (g(x), 1), (z, 2), (z, 1), (y, 1), (x, 1)]) == [(x, 3), (y, 3), + (z, 4), (f(x), 3), (g(x), 1)] + assert vsort(((y, 2), (x, 1), (y, 1), (x, 1))) == [(x, 2), (y, 3)] + assert isinstance(vsort([(x, 3), (y, 2), (z, 1)])[0], Tuple) + assert vsort([(x, 1), (f(x), 1), (x, 1)]) == [(x, 2), (f(x), 1)] + assert vsort([(y, 2), (x, 3), (z, 1)]) == [(x, 3), (y, 2), (z, 1)] + assert vsort([(h(y), 1), (g(x), 1), (f(x), 1)]) == [ + (f(x), 1), (g(x), 1), (h(y), 1)] + assert vsort([(x, 1), (y, 1), (x, 1)]) == [(x, 2), (y, 1)] + assert vsort([(f(x), 1), (f(y), 1), (f(x), 1)]) == [ + (f(x), 2), (f(y), 1)] + dfx = f(x).diff(x) + self = [(dfx, 1), (x, 1)] + assert vsort(self) == self + assert vsort([ + (dfx, 1), (y, 1), (f(x), 1), (x, 1), (f(y), 1), (x, 1)]) == [ + (y, 1), (f(x), 1), (f(y), 1), (dfx, 1), (x, 2)] + dfy = f(y).diff(y) + assert vsort([(dfy, 1), (dfx, 1)]) == [(dfx, 1), (dfy, 1)] + d2fx = dfx.diff(x) + assert vsort([(d2fx, 1), (dfx, 1)]) == [(dfx, 1), (d2fx, 1)] + + +def test_multiple_derivative(): + # Issue #15007 + assert f(x, y).diff(y, y, x, y, x + ) == Derivative(f(x, y), (x, 2), (y, 3)) + + +def test_unhandled(): + class MyExpr(Expr): + def _eval_derivative(self, s): + if not s.name.startswith('xi'): + return self + else: + return None + + eq = MyExpr(f(x), y, z) + assert diff(eq, x, y, f(x), z) == Derivative(eq, f(x)) + assert diff(eq, f(x), x) == Derivative(eq, f(x)) + assert f(x, y).diff(x,(y, z)) == Derivative(f(x, y), x, (y, z)) + assert f(x, y).diff(x,(y, 0)) == Derivative(f(x, y), x) + + +def test_nfloat(): + from sympy.core.basic import _aresame + from sympy.polys.rootoftools import rootof + + x = Symbol("x") + eq = x**Rational(4, 3) + 4*x**(S.One/3)/3 + assert _aresame(nfloat(eq), x**Rational(4, 3) + (4.0/3)*x**(S.One/3)) + assert _aresame(nfloat(eq, exponent=True), x**(4.0/3) + (4.0/3)*x**(1.0/3)) + eq = x**Rational(4, 3) + 4*x**(x/3)/3 + assert _aresame(nfloat(eq), x**Rational(4, 3) + (4.0/3)*x**(x/3)) + big = 12345678901234567890 + # specify precision to match value used in nfloat + Float_big = Float(big, 15) + assert _aresame(nfloat(big), Float_big) + assert _aresame(nfloat(big*x), Float_big*x) + assert _aresame(nfloat(x**big, exponent=True), x**Float_big) + assert nfloat(cos(x + sqrt(2))) == cos(x + nfloat(sqrt(2))) + + # issue 6342 + f = S('x*lamda + lamda**3*(x/2 + 1/2) + lamda**2 + 1/4') + assert not any(a.free_symbols for a in solveset(f.subs(x, -0.139))) + + # issue 6632 + assert nfloat(-100000*sqrt(2500000001) + 5000000001) == \ + 9.99999999800000e-11 + + # issue 7122 + eq = cos(3*x**4 + y)*rootof(x**5 + 3*x**3 + 1, 0) + assert str(nfloat(eq, exponent=False, n=1)) == '-0.7*cos(3.0*x**4 + y)' + + # issue 10933 + for ti in (dict, Dict): + d = ti({S.Half: S.Half}) + n = nfloat(d) + assert isinstance(n, ti) + assert _aresame(list(n.items()).pop(), (S.Half, Float(.5))) + for ti in (dict, Dict): + d = ti({S.Half: S.Half}) + n = nfloat(d, dkeys=True) + assert isinstance(n, ti) + assert _aresame(list(n.items()).pop(), (Float(.5), Float(.5))) + d = [S.Half] + n = nfloat(d) + assert type(n) is list + assert _aresame(n[0], Float(.5)) + assert _aresame(nfloat(Eq(x, S.Half)).rhs, Float(.5)) + assert _aresame(nfloat(S(True)), S(True)) + assert _aresame(nfloat(Tuple(S.Half))[0], Float(.5)) + assert nfloat(Eq((3 - I)**2/2 + I, 0)) == S.false + # pass along kwargs + assert nfloat([{S.Half: x}], dkeys=True) == [{Float(0.5): x}] + + # Issue 17706 + A = MutableMatrix([[1, 2], [3, 4]]) + B = MutableMatrix( + [[Float('1.0', precision=53), Float('2.0', precision=53)], + [Float('3.0', precision=53), Float('4.0', precision=53)]]) + assert _aresame(nfloat(A), B) + A = ImmutableMatrix([[1, 2], [3, 4]]) + B = ImmutableMatrix( + [[Float('1.0', precision=53), Float('2.0', precision=53)], + [Float('3.0', precision=53), Float('4.0', precision=53)]]) + assert _aresame(nfloat(A), B) + + # issue 22524 + f = Function('f') + assert not nfloat(f(2)).atoms(Float) + + +def test_issue_7068(): + from sympy.abc import a, b + f = Function('f') + y1 = Dummy('y') + y2 = Dummy('y') + func1 = f(a + y1 * b) + func2 = f(a + y2 * b) + func1_y = func1.diff(y1) + func2_y = func2.diff(y2) + assert func1_y != func2_y + z1 = Subs(f(a), a, y1) + z2 = Subs(f(a), a, y2) + assert z1 != z2 + + +def test_issue_7231(): + from sympy.abc import a + ans1 = f(x).series(x, a) + res = (f(a) + (-a + x)*Subs(Derivative(f(y), y), y, a) + + (-a + x)**2*Subs(Derivative(f(y), y, y), y, a)/2 + + (-a + x)**3*Subs(Derivative(f(y), y, y, y), + y, a)/6 + + (-a + x)**4*Subs(Derivative(f(y), y, y, y, y), + y, a)/24 + + (-a + x)**5*Subs(Derivative(f(y), y, y, y, y, y), + y, a)/120 + O((-a + x)**6, (x, a))) + assert res == ans1 + ans2 = f(x).series(x, a) + assert res == ans2 + + +def test_issue_7687(): + from sympy.core.function import Function + from sympy.abc import x + f = Function('f')(x) + ff = Function('f')(x) + match_with_cache = ff.matches(f) + assert isinstance(f, type(ff)) + clear_cache() + ff = Function('f')(x) + assert isinstance(f, type(ff)) + assert match_with_cache == ff.matches(f) + + +def test_issue_7688(): + from sympy.core.function import Function, UndefinedFunction + + f = Function('f') # actually an UndefinedFunction + clear_cache() + class A(UndefinedFunction): + pass + a = A('f') + assert isinstance(a, type(f)) + + +def test_mexpand(): + from sympy.abc import x + assert _mexpand(None) is None + assert _mexpand(1) is S.One + assert _mexpand(x*(x + 1)**2) == (x*(x + 1)**2).expand() + + +def test_issue_8469(): + # This should not take forever to run + N = 40 + def g(w, theta): + return 1/(1+exp(w-theta)) + + ws = symbols(['w%i'%i for i in range(N)]) + import functools + expr = functools.reduce(g, ws) + assert isinstance(expr, Pow) + + +def test_issue_12996(): + # foo=True imitates the sort of arguments that Derivative can get + # from Integral when it passes doit to the expression + assert Derivative(im(x), x).doit(foo=True) == Derivative(im(x), x) + + +def test_should_evalf(): + # This should not take forever to run (see #8506) + assert isinstance(sin((1.0 + 1.0*I)**10000 + 1), sin) + + +def test_Derivative_as_finite_difference(): + # Central 1st derivative at gridpoint + x, h = symbols('x h', real=True) + dfdx = f(x).diff(x) + assert (dfdx.as_finite_difference([x-2, x-1, x, x+1, x+2]) - + (S.One/12*(f(x-2)-f(x+2)) + Rational(2, 3)*(f(x+1)-f(x-1)))).simplify() == 0 + + # Central 1st derivative "half-way" + assert (dfdx.as_finite_difference() - + (f(x + S.Half)-f(x - S.Half))).simplify() == 0 + assert (dfdx.as_finite_difference(h) - + (f(x + h/S(2))-f(x - h/S(2)))/h).simplify() == 0 + assert (dfdx.as_finite_difference([x - 3*h, x-h, x+h, x + 3*h]) - + (S(9)/(8*2*h)*(f(x+h) - f(x-h)) + + S.One/(24*2*h)*(f(x - 3*h) - f(x + 3*h)))).simplify() == 0 + + # One sided 1st derivative at gridpoint + assert (dfdx.as_finite_difference([0, 1, 2], 0) - + (Rational(-3, 2)*f(0) + 2*f(1) - f(2)/2)).simplify() == 0 + assert (dfdx.as_finite_difference([x, x+h], x) - + (f(x+h) - f(x))/h).simplify() == 0 + assert (dfdx.as_finite_difference([x-h, x, x+h], x-h) - + (-S(3)/(2*h)*f(x-h) + 2/h*f(x) - + S.One/(2*h)*f(x+h))).simplify() == 0 + + # One sided 1st derivative "half-way" + assert (dfdx.as_finite_difference([x-h, x+h, x + 3*h, x + 5*h, x + 7*h]) + - 1/(2*h)*(-S(11)/(12)*f(x-h) + S(17)/(24)*f(x+h) + + Rational(3, 8)*f(x + 3*h) - Rational(5, 24)*f(x + 5*h) + + S.One/24*f(x + 7*h))).simplify() == 0 + + d2fdx2 = f(x).diff(x, 2) + # Central 2nd derivative at gridpoint + assert (d2fdx2.as_finite_difference([x-h, x, x+h]) - + h**-2 * (f(x-h) + f(x+h) - 2*f(x))).simplify() == 0 + + assert (d2fdx2.as_finite_difference([x - 2*h, x-h, x, x+h, x + 2*h]) - + h**-2 * (Rational(-1, 12)*(f(x - 2*h) + f(x + 2*h)) + + Rational(4, 3)*(f(x+h) + f(x-h)) - Rational(5, 2)*f(x))).simplify() == 0 + + # Central 2nd derivative "half-way" + assert (d2fdx2.as_finite_difference([x - 3*h, x-h, x+h, x + 3*h]) - + (2*h)**-2 * (S.Half*(f(x - 3*h) + f(x + 3*h)) - + S.Half*(f(x+h) + f(x-h)))).simplify() == 0 + + # One sided 2nd derivative at gridpoint + assert (d2fdx2.as_finite_difference([x, x+h, x + 2*h, x + 3*h]) - + h**-2 * (2*f(x) - 5*f(x+h) + + 4*f(x+2*h) - f(x+3*h))).simplify() == 0 + + # One sided 2nd derivative at "half-way" + assert (d2fdx2.as_finite_difference([x-h, x+h, x + 3*h, x + 5*h]) - + (2*h)**-2 * (Rational(3, 2)*f(x-h) - Rational(7, 2)*f(x+h) + Rational(5, 2)*f(x + 3*h) - + S.Half*f(x + 5*h))).simplify() == 0 + + d3fdx3 = f(x).diff(x, 3) + # Central 3rd derivative at gridpoint + assert (d3fdx3.as_finite_difference() - + (-f(x - Rational(3, 2)) + 3*f(x - S.Half) - + 3*f(x + S.Half) + f(x + Rational(3, 2)))).simplify() == 0 + + assert (d3fdx3.as_finite_difference( + [x - 3*h, x - 2*h, x-h, x, x+h, x + 2*h, x + 3*h]) - + h**-3 * (S.One/8*(f(x - 3*h) - f(x + 3*h)) - f(x - 2*h) + + f(x + 2*h) + Rational(13, 8)*(f(x-h) - f(x+h)))).simplify() == 0 + + # Central 3rd derivative at "half-way" + assert (d3fdx3.as_finite_difference([x - 3*h, x-h, x+h, x + 3*h]) - + (2*h)**-3 * (f(x + 3*h)-f(x - 3*h) + + 3*(f(x-h)-f(x+h)))).simplify() == 0 + + # One sided 3rd derivative at gridpoint + assert (d3fdx3.as_finite_difference([x, x+h, x + 2*h, x + 3*h]) - + h**-3 * (f(x + 3*h)-f(x) + 3*(f(x+h)-f(x + 2*h)))).simplify() == 0 + + # One sided 3rd derivative at "half-way" + assert (d3fdx3.as_finite_difference([x-h, x+h, x + 3*h, x + 5*h]) - + (2*h)**-3 * (f(x + 5*h)-f(x-h) + + 3*(f(x+h)-f(x + 3*h)))).simplify() == 0 + + # issue 11007 + y = Symbol('y', real=True) + d2fdxdy = f(x, y).diff(x, y) + + ref0 = Derivative(f(x + S.Half, y), y) - Derivative(f(x - S.Half, y), y) + assert (d2fdxdy.as_finite_difference(wrt=x) - ref0).simplify() == 0 + + half = S.Half + xm, xp, ym, yp = x-half, x+half, y-half, y+half + ref2 = f(xm, ym) + f(xp, yp) - f(xp, ym) - f(xm, yp) + assert (d2fdxdy.as_finite_difference() - ref2).simplify() == 0 + + +def test_issue_11159(): + # Tests Application._eval_subs + with _exp_is_pow(False): + expr1 = E + expr0 = expr1 * expr1 + expr1 = expr0.subs(expr1,expr0) + assert expr0 == expr1 + with _exp_is_pow(True): + expr1 = E + expr0 = expr1 * expr1 + expr2 = expr0.subs(expr1, expr0) + assert expr2 == E ** 4 + + +def test_issue_12005(): + e1 = Subs(Derivative(f(x), x), x, x) + assert e1.diff(x) == Derivative(f(x), x, x) + e2 = Subs(Derivative(f(x), x), x, x**2 + 1) + assert e2.diff(x) == 2*x*Subs(Derivative(f(x), x, x), x, x**2 + 1) + e3 = Subs(Derivative(f(x) + y**2 - y, y), y, y**2) + assert e3.diff(y) == 4*y + e4 = Subs(Derivative(f(x + y), y), y, (x**2)) + assert e4.diff(y) is S.Zero + e5 = Subs(Derivative(f(x), x), (y, z), (y, z)) + assert e5.diff(x) == Derivative(f(x), x, x) + assert f(g(x)).diff(g(x), g(x)) == Derivative(f(g(x)), g(x), g(x)) + + +def test_issue_13843(): + x = symbols('x') + f = Function('f') + m, n = symbols('m n', integer=True) + assert Derivative(Derivative(f(x), (x, m)), (x, n)) == Derivative(f(x), (x, m + n)) + assert Derivative(Derivative(f(x), (x, m+5)), (x, n+3)) == Derivative(f(x), (x, m + n + 8)) + + assert Derivative(f(x), (x, n)).doit() == Derivative(f(x), (x, n)) + + +def test_order_could_be_zero(): + x, y = symbols('x, y') + n = symbols('n', integer=True, nonnegative=True) + m = symbols('m', integer=True, positive=True) + assert diff(y, (x, n)) == Piecewise((y, Eq(n, 0)), (0, True)) + assert diff(y, (x, n + 1)) is S.Zero + assert diff(y, (x, m)) is S.Zero + + +def test_undefined_function_eq(): + f = Function('f') + f2 = Function('f') + g = Function('g') + f_real = Function('f', is_real=True) + + # This test may only be meaningful if the cache is turned off + assert f == f2 + assert hash(f) == hash(f2) + assert f == f + + assert f != g + + assert f != f_real + + +def test_function_assumptions(): + x = Symbol('x') + f = Function('f') + f_real = Function('f', real=True) + f_real1 = Function('f', real=1) + f_real_inherit = Function(Symbol('f', real=True)) + + assert f_real == f_real1 # assumptions are sanitized + assert f != f_real + assert f(x) != f_real(x) + + assert f(x).is_real is None + assert f_real(x).is_real is True + assert f_real_inherit(x).is_real is True and f_real_inherit.name == 'f' + + # Can also do it this way, but it won't be equal to f_real because of the + # way UndefinedFunction.__new__ works. Any non-recognized assumptions + # are just added literally as something which is used in the hash + f_real2 = Function('f', is_real=True) + assert f_real2(x).is_real is True + + +def test_undef_fcn_float_issue_6938(): + f = Function('ceil') + assert not f(0.3).is_number + f = Function('sin') + assert not f(0.3).is_number + assert not f(pi).evalf().is_number + x = Symbol('x') + assert not f(x).evalf(subs={x:1.2}).is_number + + +def test_undefined_function_eval(): + # Issue 15170. Make sure UndefinedFunction with eval defined works + # properly. + + fdiff = lambda self, argindex=1: cos(self.args[argindex - 1]) + eval = classmethod(lambda cls, t: None) + _imp_ = classmethod(lambda cls, t: sin(t)) + + temp = Function('temp', fdiff=fdiff, eval=eval, _imp_=_imp_) + + expr = temp(t) + assert sympify(expr) == expr + assert type(sympify(expr)).fdiff.__name__ == "" + assert expr.diff(t) == cos(t) + + +def test_issue_15241(): + F = f(x) + Fx = F.diff(x) + assert (F + x*Fx).diff(x, Fx) == 2 + assert (F + x*Fx).diff(Fx, x) == 1 + assert (x*F + x*Fx*F).diff(F, x) == x*Fx.diff(x) + Fx + 1 + assert (x*F + x*Fx*F).diff(x, F) == x*Fx.diff(x) + Fx + 1 + y = f(x) + G = f(y) + Gy = G.diff(y) + assert (G + y*Gy).diff(y, Gy) == 2 + assert (G + y*Gy).diff(Gy, y) == 1 + assert (y*G + y*Gy*G).diff(G, y) == y*Gy.diff(y) + Gy + 1 + assert (y*G + y*Gy*G).diff(y, G) == y*Gy.diff(y) + Gy + 1 + + +def test_issue_15226(): + assert Subs(Derivative(f(y), x, y), y, g(x)).doit() != 0 + + +def test_issue_7027(): + for wrt in (cos(x), re(x), Derivative(cos(x), x)): + raises(ValueError, lambda: diff(f(x), wrt)) + + +def test_derivative_quick_exit(): + assert f(x).diff(y) == 0 + assert f(x).diff(y, f(x)) == 0 + assert f(x).diff(x, f(y)) == 0 + assert f(f(x)).diff(x, f(x), f(y)) == 0 + assert f(f(x)).diff(x, f(x), y) == 0 + assert f(x).diff(g(x)) == 0 + assert f(x).diff(x, f(x).diff(x)) == 1 + df = f(x).diff(x) + assert f(x).diff(df) == 0 + dg = g(x).diff(x) + assert dg.diff(df).doit() == 0 + + +def test_issue_15084_13166(): + eq = f(x, g(x)) + assert eq.diff((g(x), y)) == Derivative(f(x, g(x)), (g(x), y)) + # issue 13166 + assert eq.diff(x, 2).doit() == ( + (Derivative(f(x, g(x)), (g(x), 2))*Derivative(g(x), x) + + Subs(Derivative(f(x, _xi_2), _xi_2, x), _xi_2, g(x)))*Derivative(g(x), + x) + Derivative(f(x, g(x)), g(x))*Derivative(g(x), (x, 2)) + + Derivative(g(x), x)*Subs(Derivative(f(_xi_1, g(x)), _xi_1, g(x)), + _xi_1, x) + Subs(Derivative(f(_xi_1, g(x)), (_xi_1, 2)), _xi_1, x)) + # issue 6681 + assert diff(f(x, t, g(x, t)), x).doit() == ( + Derivative(f(x, t, g(x, t)), g(x, t))*Derivative(g(x, t), x) + + Subs(Derivative(f(_xi_1, t, g(x, t)), _xi_1), _xi_1, x)) + # make sure the order doesn't matter when using diff + assert eq.diff(x, g(x)) == eq.diff(g(x), x) + + +def test_negative_counts(): + # issue 13873 + raises(ValueError, lambda: sin(x).diff(x, -1)) + + +def test_Derivative__new__(): + raises(TypeError, lambda: f(x).diff((x, 2), 0)) + assert f(x, y).diff([(x, y), 0]) == f(x, y) + assert f(x, y).diff([(x, y), 1]) == NDimArray([ + Derivative(f(x, y), x), Derivative(f(x, y), y)]) + assert f(x,y).diff(y, (x, z), y, x) == Derivative( + f(x, y), (x, z + 1), (y, 2)) + assert Matrix([x]).diff(x, 2) == Matrix([0]) # is_zero exit + + +def test_issue_14719_10150(): + class V(Expr): + _diff_wrt = True + is_scalar = False + assert V().diff(V()) == Derivative(V(), V()) + assert (2*V()).diff(V()) == 2*Derivative(V(), V()) + class X(Expr): + _diff_wrt = True + assert X().diff(X()) == 1 + assert (2*X()).diff(X()) == 2 + + +def test_noncommutative_issue_15131(): + x = Symbol('x', commutative=False) + t = Symbol('t', commutative=False) + fx = Function('Fx', commutative=False)(x) + ft = Function('Ft', commutative=False)(t) + A = Symbol('A', commutative=False) + eq = fx * A * ft + eqdt = eq.diff(t) + assert eqdt.args[-1] == ft.diff(t) + + +def test_Subs_Derivative(): + a = Derivative(f(g(x), h(x)), g(x), h(x),x) + b = Derivative(Derivative(f(g(x), h(x)), g(x), h(x)),x) + c = f(g(x), h(x)).diff(g(x), h(x), x) + d = f(g(x), h(x)).diff(g(x), h(x)).diff(x) + e = Derivative(f(g(x), h(x)), x) + eqs = (a, b, c, d, e) + subs = lambda arg: arg.subs(f, Lambda((x, y), exp(x + y)) + ).subs(g(x), 1/x).subs(h(x), x**3) + ans = 3*x**2*exp(1/x)*exp(x**3) - exp(1/x)*exp(x**3)/x**2 + assert all(subs(i).doit().expand() == ans for i in eqs) + assert all(subs(i.doit()).doit().expand() == ans for i in eqs) + +def test_issue_15360(): + f = Function('f') + assert f.name == 'f' + + +def test_issue_15947(): + assert f._diff_wrt is False + raises(TypeError, lambda: f(f)) + raises(TypeError, lambda: f(x).diff(f)) + + +def test_Derivative_free_symbols(): + f = Function('f') + n = Symbol('n', integer=True, positive=True) + assert diff(f(x), (x, n)).free_symbols == {n, x} + + +def test_issue_20683(): + x = Symbol('x') + y = Symbol('y') + z = Symbol('z') + y = Derivative(z, x).subs(x,0) + assert y.doit() == 0 + y = Derivative(8, x).subs(x,0) + assert y.doit() == 0 + + +def test_issue_10503(): + f = exp(x**3)*cos(x**6) + assert f.series(x, 0, 14) == 1 + x**3 + x**6/2 + x**9/6 - 11*x**12/24 + O(x**14) + + +def test_issue_17382(): + # copied from sympy/core/tests/test_evalf.py + def NS(e, n=15, **options): + return sstr(sympify(e).evalf(n, **options), full_prec=True) + + x = Symbol('x') + expr = solveset(2 * cos(x) * cos(2 * x) - 1, x, S.Reals) + expected = "Union(" \ + "ImageSet(Lambda(_n, 6.28318530717959*_n + 5.79812359592087), Integers), " \ + "ImageSet(Lambda(_n, 6.28318530717959*_n + 0.485061711258717), Integers))" + assert NS(expr) == expected + +def test_eval_sympified(): + # Check both arguments and return types from eval are sympified + + class F(Function): + @classmethod + def eval(cls, x): + assert x is S.One + return 1 + + assert F(1) is S.One + + # String arguments are not allowed + class F2(Function): + @classmethod + def eval(cls, x): + if x == 0: + return '1' + + raises(SympifyError, lambda: F2(0)) + F2(1) # Doesn't raise + + # TODO: Disable string inputs (https://github.com/sympy/sympy/issues/11003) + # raises(SympifyError, lambda: F2('2')) + +def test_eval_classmethod_check(): + with raises(TypeError): + class F(Function): + def eval(self, x): + pass + + +def test_issue_27163(): + # https://github.com/sympy/sympy/issues/27163 + raises(TypeError, lambda: Derivative(f, t)) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_kind.py b/phivenv/Lib/site-packages/sympy/core/tests/test_kind.py new file mode 100644 index 0000000000000000000000000000000000000000..cbfdffb9304b49488756752ca198fd4067087437 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_kind.py @@ -0,0 +1,57 @@ +from sympy.core.add import Add +from sympy.core.kind import NumberKind, UndefinedKind +from sympy.core.mul import Mul +from sympy.core.numbers import pi, zoo, I, AlgebraicNumber +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.integrals.integrals import Integral +from sympy.core.function import Derivative +from sympy.matrices import (Matrix, SparseMatrix, ImmutableMatrix, + ImmutableSparseMatrix, MatrixSymbol, MatrixKind, MatMul) + +comm_x = Symbol('x') +noncomm_x = Symbol('x', commutative=False) + +def test_NumberKind(): + assert S.One.kind is NumberKind + assert pi.kind is NumberKind + assert S.NaN.kind is NumberKind + assert zoo.kind is NumberKind + assert I.kind is NumberKind + assert AlgebraicNumber(1).kind is NumberKind + +def test_Add_kind(): + assert Add(2, 3, evaluate=False).kind is NumberKind + assert Add(2,comm_x).kind is NumberKind + assert Add(2,noncomm_x).kind is UndefinedKind + +def test_mul_kind(): + assert Mul(2,comm_x, evaluate=False).kind is NumberKind + assert Mul(2,3, evaluate=False).kind is NumberKind + assert Mul(noncomm_x,2, evaluate=False).kind is UndefinedKind + assert Mul(2,noncomm_x, evaluate=False).kind is UndefinedKind + +def test_Symbol_kind(): + assert comm_x.kind is NumberKind + assert noncomm_x.kind is UndefinedKind + +def test_Integral_kind(): + A = MatrixSymbol('A', 2,2) + assert Integral(comm_x, comm_x).kind is NumberKind + assert Integral(A, comm_x).kind is MatrixKind(NumberKind) + +def test_Derivative_kind(): + A = MatrixSymbol('A', 2,2) + assert Derivative(comm_x, comm_x).kind is NumberKind + assert Derivative(A, comm_x).kind is MatrixKind(NumberKind) + +def test_Matrix_kind(): + classes = (Matrix, SparseMatrix, ImmutableMatrix, ImmutableSparseMatrix) + for cls in classes: + m = cls.zeros(3, 2) + assert m.kind is MatrixKind(NumberKind) + +def test_MatMul_kind(): + M = Matrix([[1,2],[3,4]]) + assert MatMul(2, M).kind is MatrixKind(NumberKind) + assert MatMul(comm_x, M).kind is MatrixKind(NumberKind) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_logic.py b/phivenv/Lib/site-packages/sympy/core/tests/test_logic.py new file mode 100644 index 0000000000000000000000000000000000000000..df5647f32ea7c4e326eb4e3aec6a7b2987f32aee --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_logic.py @@ -0,0 +1,198 @@ +from sympy.core.logic import (fuzzy_not, Logic, And, Or, Not, fuzzy_and, + fuzzy_or, _fuzzy_group, _torf, fuzzy_nand, fuzzy_xor) +from sympy.testing.pytest import raises + +from itertools import product + +T = True +F = False +U = None + + + +def test_torf(): + v = [T, F, U] + for i in product(*[v]*3): + assert _torf(i) is (True if all(j for j in i) else + (False if all(j is False for j in i) else None)) + + +def test_fuzzy_group(): + v = [T, F, U] + for i in product(*[v]*3): + assert _fuzzy_group(i) is (None if None in i else + (True if all(j for j in i) else False)) + assert _fuzzy_group(i, quick_exit=True) is \ + (None if (i.count(False) > 1) else + (None if None in i else (True if all(j for j in i) else False))) + it = (True if (i == 0) else None for i in range(2)) + assert _torf(it) is None + it = (True if (i == 1) else None for i in range(2)) + assert _torf(it) is None + + +def test_fuzzy_not(): + assert fuzzy_not(T) == F + assert fuzzy_not(F) == T + assert fuzzy_not(U) == U + + +def test_fuzzy_and(): + assert fuzzy_and([T, T]) == T + assert fuzzy_and([T, F]) == F + assert fuzzy_and([T, U]) == U + assert fuzzy_and([F, F]) == F + assert fuzzy_and([F, U]) == F + assert fuzzy_and([U, U]) == U + assert [fuzzy_and([w]) for w in [U, T, F]] == [U, T, F] + assert fuzzy_and([T, F, U]) == F + assert fuzzy_and([]) == T + raises(TypeError, lambda: fuzzy_and()) + + +def test_fuzzy_or(): + assert fuzzy_or([T, T]) == T + assert fuzzy_or([T, F]) == T + assert fuzzy_or([T, U]) == T + assert fuzzy_or([F, F]) == F + assert fuzzy_or([F, U]) == U + assert fuzzy_or([U, U]) == U + assert [fuzzy_or([w]) for w in [U, T, F]] == [U, T, F] + assert fuzzy_or([T, F, U]) == T + assert fuzzy_or([]) == F + raises(TypeError, lambda: fuzzy_or()) + + +def test_logic_cmp(): + l1 = And('a', Not('b')) + l2 = And('a', Not('b')) + + assert hash(l1) == hash(l2) + assert (l1 == l2) == T + assert (l1 != l2) == F + + assert And('a', 'b', 'c') == And('b', 'a', 'c') + assert And('a', 'b', 'c') == And('c', 'b', 'a') + assert And('a', 'b', 'c') == And('c', 'a', 'b') + + assert Not('a') < Not('b') + assert (Not('b') < Not('a')) is False + assert (Not('a') < 2) is False + + +def test_logic_onearg(): + assert And() is True + assert Or() is False + + assert And(T) == T + assert And(F) == F + assert Or(T) == T + assert Or(F) == F + + assert And('a') == 'a' + assert Or('a') == 'a' + + +def test_logic_xnotx(): + assert And('a', Not('a')) == F + assert Or('a', Not('a')) == T + + +def test_logic_eval_TF(): + assert And(F, F) == F + assert And(F, T) == F + assert And(T, F) == F + assert And(T, T) == T + + assert Or(F, F) == F + assert Or(F, T) == T + assert Or(T, F) == T + assert Or(T, T) == T + + assert And('a', T) == 'a' + assert And('a', F) == F + assert Or('a', T) == T + assert Or('a', F) == 'a' + + +def test_logic_combine_args(): + assert And('a', 'b', 'a') == And('a', 'b') + assert Or('a', 'b', 'a') == Or('a', 'b') + + assert And(And('a', 'b'), And('c', 'd')) == And('a', 'b', 'c', 'd') + assert Or(Or('a', 'b'), Or('c', 'd')) == Or('a', 'b', 'c', 'd') + + assert Or('t', And('n', 'p', 'r'), And('n', 'r'), And('n', 'p', 'r'), 't', + And('n', 'r')) == Or('t', And('n', 'p', 'r'), And('n', 'r')) + + +def test_logic_expand(): + t = And(Or('a', 'b'), 'c') + assert t.expand() == Or(And('a', 'c'), And('b', 'c')) + + t = And(Or('a', Not('b')), 'b') + assert t.expand() == And('a', 'b') + + t = And(Or('a', 'b'), Or('c', 'd')) + assert t.expand() == \ + Or(And('a', 'c'), And('a', 'd'), And('b', 'c'), And('b', 'd')) + + +def test_logic_fromstring(): + S = Logic.fromstring + + assert S('a') == 'a' + assert S('!a') == Not('a') + assert S('a & b') == And('a', 'b') + assert S('a | b') == Or('a', 'b') + assert S('a | b & c') == And(Or('a', 'b'), 'c') + assert S('a & b | c') == Or(And('a', 'b'), 'c') + assert S('a & b & c') == And('a', 'b', 'c') + assert S('a | b | c') == Or('a', 'b', 'c') + + raises(ValueError, lambda: S('| a')) + raises(ValueError, lambda: S('& a')) + raises(ValueError, lambda: S('a | | b')) + raises(ValueError, lambda: S('a | & b')) + raises(ValueError, lambda: S('a & & b')) + raises(ValueError, lambda: S('a |')) + raises(ValueError, lambda: S('a|b')) + raises(ValueError, lambda: S('!')) + raises(ValueError, lambda: S('! a')) + raises(ValueError, lambda: S('!(a + 1)')) + raises(ValueError, lambda: S('')) + + +def test_logic_not(): + assert Not('a') != '!a' + assert Not('!a') != 'a' + assert Not(True) == False + assert Not(False) == True + + # NOTE: we may want to change default Not behaviour and put this + # functionality into some method. + assert Not(And('a', 'b')) == Or(Not('a'), Not('b')) + assert Not(Or('a', 'b')) == And(Not('a'), Not('b')) + + raises(ValueError, lambda: Not(1)) + + +def test_formatting(): + S = Logic.fromstring + raises(ValueError, lambda: S('a&b')) + raises(ValueError, lambda: S('a|b')) + raises(ValueError, lambda: S('! a')) + + +def test_fuzzy_xor(): + assert fuzzy_xor((None,)) is None + assert fuzzy_xor((None, True)) is None + assert fuzzy_xor((None, False)) is None + assert fuzzy_xor((True, False)) is True + assert fuzzy_xor((True, True)) is False + assert fuzzy_xor((True, True, False)) is False + assert fuzzy_xor((True, True, False, True)) is True + +def test_fuzzy_nand(): + for args in [(1, 0), (1, 1), (0, 0)]: + assert fuzzy_nand(args) == fuzzy_not(fuzzy_and(args)) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_match.py b/phivenv/Lib/site-packages/sympy/core/tests/test_match.py new file mode 100644 index 0000000000000000000000000000000000000000..b44012bebc4a5f3ec413c236dca1dec71da78cf5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_match.py @@ -0,0 +1,766 @@ +from sympy import abc +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Integer, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, Wild, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.hyper import meijerg +from sympy.polys.polytools import Poly +from sympy.simplify.radsimp import collect +from sympy.simplify.simplify import signsimp + +from sympy.testing.pytest import XFAIL + + +def test_symbol(): + x = Symbol('x') + a, b, c, p, q = map(Wild, 'abcpq') + + e = x + assert e.match(x) == {} + assert e.matches(x) == {} + assert e.match(a) == {a: x} + + e = Rational(5) + assert e.match(c) == {c: 5} + assert e.match(e) == {} + assert e.match(e + 1) is None + + +def test_add(): + x, y, a, b, c = map(Symbol, 'xyabc') + p, q, r = map(Wild, 'pqr') + + e = a + b + assert e.match(p + b) == {p: a} + assert e.match(p + a) == {p: b} + + e = 1 + b + assert e.match(p + b) == {p: 1} + + e = a + b + c + assert e.match(a + p + c) == {p: b} + assert e.match(b + p + c) == {p: a} + + e = a + b + c + x + assert e.match(a + p + x + c) == {p: b} + assert e.match(b + p + c + x) == {p: a} + assert e.match(b) is None + assert e.match(b + p) == {p: a + c + x} + assert e.match(a + p + c) == {p: b + x} + assert e.match(b + p + c) == {p: a + x} + + e = 4*x + 5 + assert e.match(4*x + p) == {p: 5} + assert e.match(3*x + p) == {p: x + 5} + assert e.match(p*x + 5) == {p: 4} + + +def test_power(): + x, y, a, b, c = map(Symbol, 'xyabc') + p, q, r = map(Wild, 'pqr') + + e = (x + y)**a + assert e.match(p**q) == {p: x + y, q: a} + assert e.match(p**p) is None + + e = (x + y)**(x + y) + assert e.match(p**p) == {p: x + y} + assert e.match(p**q) == {p: x + y, q: x + y} + + e = (2*x)**2 + assert e.match(p*q**r) == {p: 4, q: x, r: 2} + + e = Integer(1) + assert e.match(x**p) == {p: 0} + + +def test_match_exclude(): + x = Symbol('x') + y = Symbol('y') + p = Wild("p") + q = Wild("q") + r = Wild("r") + + e = Rational(6) + assert e.match(2*p) == {p: 3} + + e = 3/(4*x + 5) + assert e.match(3/(p*x + q)) == {p: 4, q: 5} + + e = 3/(4*x + 5) + assert e.match(p/(q*x + r)) == {p: 3, q: 4, r: 5} + + e = 2/(x + 1) + assert e.match(p/(q*x + r)) == {p: 2, q: 1, r: 1} + + e = 1/(x + 1) + assert e.match(p/(q*x + r)) == {p: 1, q: 1, r: 1} + + e = 4*x + 5 + assert e.match(p*x + q) == {p: 4, q: 5} + + e = 4*x + 5*y + 6 + assert e.match(p*x + q*y + r) == {p: 4, q: 5, r: 6} + + a = Wild('a', exclude=[x]) + + e = 3*x + assert e.match(p*x) == {p: 3} + assert e.match(a*x) == {a: 3} + + e = 3*x**2 + assert e.match(p*x) == {p: 3*x} + assert e.match(a*x) is None + + e = 3*x + 3 + 6/x + assert e.match(p*x**2 + p*x + 2*p) == {p: 3/x} + assert e.match(a*x**2 + a*x + 2*a) is None + + +def test_mul(): + x, y, a, b, c = map(Symbol, 'xyabc') + p, q = map(Wild, 'pq') + + e = 4*x + assert e.match(p*x) == {p: 4} + assert e.match(p*y) is None + assert e.match(e + p*y) == {p: 0} + + e = a*x*b*c + assert e.match(p*x) == {p: a*b*c} + assert e.match(c*p*x) == {p: a*b} + + e = (a + b)*(a + c) + assert e.match((p + b)*(p + c)) == {p: a} + + e = x + assert e.match(p*x) == {p: 1} + + e = exp(x) + assert e.match(x**p*exp(x*q)) == {p: 0, q: 1} + + e = I*Poly(x, x) + assert e.match(I*p) == {p: x} + + +def test_mul_noncommutative(): + x, y = symbols('x y') + A, B, C = symbols('A B C', commutative=False) + u, v = symbols('u v', cls=Wild) + w, z = symbols('w z', cls=Wild, commutative=False) + + assert (u*v).matches(x) in ({v: x, u: 1}, {u: x, v: 1}) + assert (u*v).matches(x*y) in ({v: y, u: x}, {u: y, v: x}) + assert (u*v).matches(A) is None + assert (u*v).matches(A*B) is None + assert (u*v).matches(x*A) is None + assert (u*v).matches(x*y*A) is None + assert (u*v).matches(x*A*B) is None + assert (u*v).matches(x*y*A*B) is None + + assert (v*w).matches(x) is None + assert (v*w).matches(x*y) is None + assert (v*w).matches(A) == {w: A, v: 1} + assert (v*w).matches(A*B) == {w: A*B, v: 1} + assert (v*w).matches(x*A) == {w: A, v: x} + assert (v*w).matches(x*y*A) == {w: A, v: x*y} + assert (v*w).matches(x*A*B) == {w: A*B, v: x} + assert (v*w).matches(x*y*A*B) == {w: A*B, v: x*y} + + assert (v*w).matches(-x) is None + assert (v*w).matches(-x*y) is None + assert (v*w).matches(-A) == {w: A, v: -1} + assert (v*w).matches(-A*B) == {w: A*B, v: -1} + assert (v*w).matches(-x*A) == {w: A, v: -x} + assert (v*w).matches(-x*y*A) == {w: A, v: -x*y} + assert (v*w).matches(-x*A*B) == {w: A*B, v: -x} + assert (v*w).matches(-x*y*A*B) == {w: A*B, v: -x*y} + + assert (w*z).matches(x) is None + assert (w*z).matches(x*y) is None + assert (w*z).matches(A) is None + assert (w*z).matches(A*B) == {w: A, z: B} + assert (w*z).matches(B*A) == {w: B, z: A} + assert (w*z).matches(A*B*C) in [{w: A, z: B*C}, {w: A*B, z: C}] + assert (w*z).matches(x*A) is None + assert (w*z).matches(x*y*A) is None + assert (w*z).matches(x*A*B) is None + assert (w*z).matches(x*y*A*B) is None + + assert (w*A).matches(A) is None + assert (A*w*B).matches(A*B) is None + + assert (u*w*z).matches(x) is None + assert (u*w*z).matches(x*y) is None + assert (u*w*z).matches(A) is None + assert (u*w*z).matches(A*B) == {u: 1, w: A, z: B} + assert (u*w*z).matches(B*A) == {u: 1, w: B, z: A} + assert (u*w*z).matches(x*A) is None + assert (u*w*z).matches(x*y*A) is None + assert (u*w*z).matches(x*A*B) == {u: x, w: A, z: B} + assert (u*w*z).matches(x*B*A) == {u: x, w: B, z: A} + assert (u*w*z).matches(x*y*A*B) == {u: x*y, w: A, z: B} + assert (u*w*z).matches(x*y*B*A) == {u: x*y, w: B, z: A} + + assert (u*A).matches(x*A) == {u: x} + assert (u*A).matches(x*A*B) is None + assert (u*B).matches(x*A) is None + assert (u*A*B).matches(x*A*B) == {u: x} + assert (u*A*B).matches(x*B*A) is None + assert (u*A*B).matches(x*A) is None + + assert (u*w*A).matches(x*A*B) is None + assert (u*w*B).matches(x*A*B) == {u: x, w: A} + + assert (u*v*A*B).matches(x*A*B) in [{u: x, v: 1}, {v: x, u: 1}] + assert (u*v*A*B).matches(x*B*A) is None + assert (u*v*A*B).matches(u*v*A*C) is None + + +def test_mul_noncommutative_mismatch(): + A, B, C = symbols('A B C', commutative=False) + w = symbols('w', cls=Wild, commutative=False) + + assert (w*B*w).matches(A*B*A) == {w: A} + assert (w*B*w).matches(A*C*B*A*C) == {w: A*C} + assert (w*B*w).matches(A*C*B*A*B) is None + assert (w*B*w).matches(A*B*C) is None + assert (w*w*C).matches(A*B*C) is None + + +def test_mul_noncommutative_pow(): + A, B, C = symbols('A B C', commutative=False) + w = symbols('w', cls=Wild, commutative=False) + + assert (A*B*w).matches(A*B**2) == {w: B} + assert (A*(B**2)*w*(B**3)).matches(A*B**8) == {w: B**3} + assert (A*B*w*C).matches(A*(B**4)*C) == {w: B**3} + + assert (A*B*(w**(-1))).matches(A*B*(C**(-1))) == {w: C} + assert (A*(B*w)**(-1)*C).matches(A*(B*C)**(-1)*C) == {w: C} + + assert ((w**2)*B*C).matches((A**2)*B*C) == {w: A} + assert ((w**2)*B*(w**3)).matches((A**2)*B*(A**3)) == {w: A} + assert ((w**2)*B*(w**4)).matches((A**2)*B*(A**2)) is None + +def test_complex(): + a, b, c = map(Symbol, 'abc') + x, y = map(Wild, 'xy') + + assert (1 + I).match(x + I) == {x: 1} + assert (a + I).match(x + I) == {x: a} + assert (2*I).match(x*I) == {x: 2} + assert (a*I).match(x*I) == {x: a} + assert (a*I).match(x*y) == {x: I, y: a} + assert (2*I).match(x*y) == {x: 2, y: I} + assert (a + b*I).match(x + y*I) == {x: a, y: b} + + +def test_functions(): + from sympy.core.function import WildFunction + x = Symbol('x') + g = WildFunction('g') + p = Wild('p') + q = Wild('q') + + f = cos(5*x) + notf = x + assert f.match(p*cos(q*x)) == {p: 1, q: 5} + assert f.match(p*g) == {p: 1, g: cos(5*x)} + assert notf.match(g) is None + + +@XFAIL +def test_functions_X1(): + from sympy.core.function import WildFunction + x = Symbol('x') + g = WildFunction('g') + p = Wild('p') + q = Wild('q') + + f = cos(5*x) + assert f.match(p*g(q*x)) == {p: 1, g: cos, q: 5} + + +def test_interface(): + x, y = map(Symbol, 'xy') + p, q = map(Wild, 'pq') + + assert (x + 1).match(p + 1) == {p: x} + assert (x*3).match(p*3) == {p: x} + assert (x**3).match(p**3) == {p: x} + assert (x*cos(y)).match(p*cos(q)) == {p: x, q: y} + + assert (x*y).match(p*q) in [{p:x, q:y}, {p:y, q:x}] + assert (x + y).match(p + q) in [{p:x, q:y}, {p:y, q:x}] + assert (x*y + 1).match(p*q) in [{p:1, q:1 + x*y}, {p:1 + x*y, q:1}] + + +def test_derivative1(): + x, y = map(Symbol, 'xy') + p, q = map(Wild, 'pq') + + f = Function('f', nargs=1) + fd = Derivative(f(x), x) + + assert fd.match(p) == {p: fd} + assert (fd + 1).match(p + 1) == {p: fd} + assert (fd).match(fd) == {} + assert (3*fd).match(p*fd) is not None + assert (3*fd - 1).match(p*fd + q) == {p: 3, q: -1} + + +def test_derivative_bug1(): + f = Function("f") + x = Symbol("x") + a = Wild("a", exclude=[f, x]) + b = Wild("b", exclude=[f]) + pattern = a * Derivative(f(x), x, x) + b + expr = Derivative(f(x), x) + x**2 + d1 = {b: x**2} + d2 = pattern.xreplace(d1).matches(expr, d1) + assert d2 is None + + +def test_derivative2(): + f = Function("f") + x = Symbol("x") + a = Wild("a", exclude=[f, x]) + b = Wild("b", exclude=[f]) + e = Derivative(f(x), x) + assert e.match(Derivative(f(x), x)) == {} + assert e.match(Derivative(f(x), x, x)) is None + e = Derivative(f(x), x, x) + assert e.match(Derivative(f(x), x)) is None + assert e.match(Derivative(f(x), x, x)) == {} + e = Derivative(f(x), x) + x**2 + assert e.match(a*Derivative(f(x), x) + b) == {a: 1, b: x**2} + assert e.match(a*Derivative(f(x), x, x) + b) is None + e = Derivative(f(x), x, x) + x**2 + assert e.match(a*Derivative(f(x), x) + b) is None + assert e.match(a*Derivative(f(x), x, x) + b) == {a: 1, b: x**2} + + +def test_match_deriv_bug1(): + n = Function('n') + l = Function('l') + + x = Symbol('x') + p = Wild('p') + + e = diff(l(x), x)/x - diff(diff(n(x), x), x)/2 - \ + diff(n(x), x)**2/4 + diff(n(x), x)*diff(l(x), x)/4 + e = e.subs(n(x), -l(x)).doit() + t = x*exp(-l(x)) + t2 = t.diff(x, x)/t + assert e.match( (p*t2).expand() ) == {p: Rational(-1, 2)} + + +def test_match_bug2(): + x, y = map(Symbol, 'xy') + p, q, r = map(Wild, 'pqr') + res = (x + y).match(p + q + r) + assert (p + q + r).subs(res) == x + y + + +def test_match_bug3(): + x, a, b = map(Symbol, 'xab') + p = Wild('p') + assert (b*x*exp(a*x)).match(x*exp(p*x)) is None + + +def test_match_bug4(): + x = Symbol('x') + p = Wild('p') + e = x + assert e.match(-p*x) == {p: -1} + + +def test_match_bug5(): + x = Symbol('x') + p = Wild('p') + e = -x + assert e.match(-p*x) == {p: 1} + + +def test_match_bug6(): + x = Symbol('x') + p = Wild('p') + e = x + assert e.match(3*p*x) == {p: Rational(1)/3} + + +def test_match_polynomial(): + x = Symbol('x') + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + c = Wild('c', exclude=[x]) + d = Wild('d', exclude=[x]) + + eq = 4*x**3 + 3*x**2 + 2*x + 1 + pattern = a*x**3 + b*x**2 + c*x + d + assert eq.match(pattern) == {a: 4, b: 3, c: 2, d: 1} + assert (eq - 3*x**2).match(pattern) == {a: 4, b: 0, c: 2, d: 1} + assert (x + sqrt(2) + 3).match(a + b*x + c*x**2) == \ + {b: 1, a: sqrt(2) + 3, c: 0} + + +def test_exclude(): + x, y, a = map(Symbol, 'xya') + p = Wild('p', exclude=[1, x]) + q = Wild('q') + r = Wild('r', exclude=[sin, y]) + + assert sin(x).match(r) is None + assert cos(y).match(r) is None + + e = 3*x**2 + y*x + a + assert e.match(p*x**2 + q*x + r) == {p: 3, q: y, r: a} + + e = x + 1 + assert e.match(x + p) is None + assert e.match(p + 1) is None + assert e.match(x + 1 + p) == {p: 0} + + e = cos(x) + 5*sin(y) + assert e.match(r) is None + assert e.match(cos(y) + r) is None + assert e.match(r + p*sin(q)) == {r: cos(x), p: 5, q: y} + + +def test_floats(): + a, b = map(Wild, 'ab') + + e = cos(0.12345, evaluate=False)**2 + r = e.match(a*cos(b)**2) + assert r == {a: 1, b: Float(0.12345)} + + +def test_Derivative_bug1(): + f = Function("f") + x = abc.x + a = Wild("a", exclude=[f(x)]) + b = Wild("b", exclude=[f(x)]) + eq = f(x).diff(x) + assert eq.match(a*Derivative(f(x), x) + b) == {a: 1, b: 0} + + +def test_match_wild_wild(): + p = Wild('p') + q = Wild('q') + r = Wild('r') + + assert p.match(q + r) in [ {q: p, r: 0}, {q: 0, r: p} ] + assert p.match(q*r) in [ {q: p, r: 1}, {q: 1, r: p} ] + + p = Wild('p') + q = Wild('q', exclude=[p]) + r = Wild('r') + + assert p.match(q + r) == {q: 0, r: p} + assert p.match(q*r) == {q: 1, r: p} + + p = Wild('p') + q = Wild('q', exclude=[p]) + r = Wild('r', exclude=[p]) + + assert p.match(q + r) is None + assert p.match(q*r) is None + + +def test__combine_inverse(): + x, y = symbols("x y") + assert Mul._combine_inverse(x*I*y, x*I) == y + assert Mul._combine_inverse(x*x**(1 + y), x**(1 + y)) == x + assert Mul._combine_inverse(x*I*y, y*I) == x + assert Mul._combine_inverse(oo*I*y, y*I) is oo + assert Mul._combine_inverse(oo*I*y, oo*I) == y + assert Mul._combine_inverse(oo*I*y, oo*I) == y + assert Mul._combine_inverse(oo*y, -oo) == -y + assert Mul._combine_inverse(-oo*y, oo) == -y + assert Mul._combine_inverse((1-exp(x/y)),(exp(x/y)-1)) == -1 + assert Add._combine_inverse(oo, oo) is S.Zero + assert Add._combine_inverse(oo*I, oo*I) is S.Zero + assert Add._combine_inverse(x*oo, x*oo) is S.Zero + assert Add._combine_inverse(-x*oo, -x*oo) is S.Zero + assert Add._combine_inverse((x - oo)*(x + oo), -oo) + + +def test_issue_3773(): + x = symbols('x') + z, phi, r = symbols('z phi r') + c, A, B, N = symbols('c A B N', cls=Wild) + l = Wild('l', exclude=(0,)) + + eq = z * sin(2*phi) * r**7 + matcher = c * sin(phi*N)**l * r**A * log(r)**B + + assert eq.match(matcher) == {c: z, l: 1, N: 2, A: 7, B: 0} + assert (-eq).match(matcher) == {c: -z, l: 1, N: 2, A: 7, B: 0} + assert (x*eq).match(matcher) == {c: x*z, l: 1, N: 2, A: 7, B: 0} + assert (-7*x*eq).match(matcher) == {c: -7*x*z, l: 1, N: 2, A: 7, B: 0} + + matcher = c*sin(phi*N)**l * r**A + + assert eq.match(matcher) == {c: z, l: 1, N: 2, A: 7} + assert (-eq).match(matcher) == {c: -z, l: 1, N: 2, A: 7} + assert (x*eq).match(matcher) == {c: x*z, l: 1, N: 2, A: 7} + assert (-7*x*eq).match(matcher) == {c: -7*x*z, l: 1, N: 2, A: 7} + + +def test_issue_3883(): + from sympy.abc import gamma, mu, x + f = (-gamma * (x - mu)**2 - log(gamma) + log(2*pi))/2 + a, b, c = symbols('a b c', cls=Wild, exclude=(gamma,)) + + assert f.match(a * log(gamma) + b * gamma + c) == \ + {a: Rational(-1, 2), b: -(-mu + x)**2/2, c: log(2*pi)/2} + assert f.expand().collect(gamma).match(a * log(gamma) + b * gamma + c) == \ + {a: Rational(-1, 2), b: (-(x - mu)**2/2).expand(), c: (log(2*pi)/2).expand()} + g1 = Wild('g1', exclude=[gamma]) + g2 = Wild('g2', exclude=[gamma]) + g3 = Wild('g3', exclude=[gamma]) + assert f.expand().match(g1 * log(gamma) + g2 * gamma + g3) == \ + {g3: log(2)/2 + log(pi)/2, g1: Rational(-1, 2), g2: -mu**2/2 + mu*x - x**2/2} + + +def test_issue_4418(): + x = Symbol('x') + a, b, c = symbols('a b c', cls=Wild, exclude=(x,)) + f, g = symbols('f g', cls=Function) + + eq = diff(g(x)*f(x).diff(x), x) + + assert eq.match( + g(x).diff(x)*f(x).diff(x) + g(x)*f(x).diff(x, x) + c) == {c: 0} + assert eq.match(a*g(x).diff( + x)*f(x).diff(x) + b*g(x)*f(x).diff(x, x) + c) == {a: 1, b: 1, c: 0} + + +def test_issue_4700(): + f = Function('f') + x = Symbol('x') + a, b = symbols('a b', cls=Wild, exclude=(f(x),)) + + p = a*f(x) + b + eq1 = sin(x) + eq2 = f(x) + sin(x) + eq3 = f(x) + x + sin(x) + eq4 = x + sin(x) + + assert eq1.match(p) == {a: 0, b: sin(x)} + assert eq2.match(p) == {a: 1, b: sin(x)} + assert eq3.match(p) == {a: 1, b: x + sin(x)} + assert eq4.match(p) == {a: 0, b: x + sin(x)} + + +def test_issue_5168(): + a, b, c = symbols('a b c', cls=Wild) + x = Symbol('x') + f = Function('f') + + assert x.match(a) == {a: x} + assert x.match(a*f(x)**c) == {a: x, c: 0} + assert x.match(a*b) == {a: 1, b: x} + assert x.match(a*b*f(x)**c) == {a: 1, b: x, c: 0} + + assert (-x).match(a) == {a: -x} + assert (-x).match(a*f(x)**c) == {a: -x, c: 0} + assert (-x).match(a*b) == {a: -1, b: x} + assert (-x).match(a*b*f(x)**c) == {a: -1, b: x, c: 0} + + assert (2*x).match(a) == {a: 2*x} + assert (2*x).match(a*f(x)**c) == {a: 2*x, c: 0} + assert (2*x).match(a*b) == {a: 2, b: x} + assert (2*x).match(a*b*f(x)**c) == {a: 2, b: x, c: 0} + + assert (-2*x).match(a) == {a: -2*x} + assert (-2*x).match(a*f(x)**c) == {a: -2*x, c: 0} + assert (-2*x).match(a*b) == {a: -2, b: x} + assert (-2*x).match(a*b*f(x)**c) == {a: -2, b: x, c: 0} + + +def test_issue_4559(): + x = Symbol('x') + e = Symbol('e') + w = Wild('w', exclude=[x]) + y = Wild('y') + + # this is as it should be + + assert (3/x).match(w/y) == {w: 3, y: x} + assert (3*x).match(w*y) == {w: 3, y: x} + assert (x/3).match(y/w) == {w: 3, y: x} + assert (3*x).match(y/w) == {w: S.One/3, y: x} + assert (3*x).match(y/w) == {w: Rational(1, 3), y: x} + + # these could be allowed to fail + + assert (x/3).match(w/y) == {w: S.One/3, y: 1/x} + assert (3*x).match(w/y) == {w: 3, y: 1/x} + assert (3/x).match(w*y) == {w: 3, y: 1/x} + + # Note that solve will give + # multiple roots but match only gives one: + # + # >>> solve(x**r-y**2,y) + # [-x**(r/2), x**(r/2)] + + r = Symbol('r', rational=True) + assert (x**r).match(y**2) == {y: x**(r/2)} + assert (x**e).match(y**2) == {y: sqrt(x**e)} + + # since (x**i = y) -> x = y**(1/i) where i is an integer + # the following should also be valid as long as y is not + # zero when i is negative. + + a = Wild('a') + + e = S.Zero + assert e.match(a) == {a: e} + assert e.match(1/a) is None + assert e.match(a**.3) is None + + e = S(3) + assert e.match(1/a) == {a: 1/e} + assert e.match(1/a**2) == {a: 1/sqrt(e)} + e = pi + assert e.match(1/a) == {a: 1/e} + assert e.match(1/a**2) == {a: 1/sqrt(e)} + assert (-e).match(sqrt(a)) is None + assert (-e).match(a**2) == {a: I*sqrt(pi)} + +# The pattern matcher doesn't know how to handle (x - a)**2 == (a - x)**2. To +# avoid ambiguity in actual applications, don't put a coefficient (including a +# minus sign) in front of a wild. +@XFAIL +def test_issue_4883(): + a = Wild('a') + x = Symbol('x') + + e = [i**2 for i in (x - 2, 2 - x)] + p = [i**2 for i in (x - a, a- x)] + for eq in e: + for pat in p: + assert eq.match(pat) == {a: 2} + + +def test_issue_4319(): + x, y = symbols('x y') + + p = -x*(S.One/8 - y) + ans = {S.Zero, y - S.One/8} + + def ok(pat): + assert set(p.match(pat).values()) == ans + + ok(Wild("coeff", exclude=[x])*x + Wild("rest")) + ok(Wild("w", exclude=[x])*x + Wild("rest")) + ok(Wild("coeff", exclude=[x])*x + Wild("rest")) + ok(Wild("w", exclude=[x])*x + Wild("rest")) + ok(Wild("e", exclude=[x])*x + Wild("rest")) + ok(Wild("ress", exclude=[x])*x + Wild("rest")) + ok(Wild("resu", exclude=[x])*x + Wild("rest")) + + +def test_issue_3778(): + p, c, q = symbols('p c q', cls=Wild) + x = Symbol('x') + + assert (sin(x)**2).match(sin(p)*sin(q)*c) == {q: x, c: 1, p: x} + assert (2*sin(x)).match(sin(p) + sin(q) + c) == {q: x, c: 0, p: x} + + +def test_issue_6103(): + x = Symbol('x') + a = Wild('a') + assert (-I*x*oo).match(I*a*oo) == {a: -x} + + +def test_issue_3539(): + a = Wild('a') + x = Symbol('x') + assert (x - 2).match(a - x) is None + assert (6/x).match(a*x) is None + assert (6/x**2).match(a/x) == {a: 6/x} + +def test_gh_issue_2711(): + x = Symbol('x') + f = meijerg(((), ()), ((0,), ()), x) + a = Wild('a') + b = Wild('b') + + assert f.find(a) == {(S.Zero,), ((), ()), ((S.Zero,), ()), x, S.Zero, + (), meijerg(((), ()), ((S.Zero,), ()), x)} + assert f.find(a + b) == \ + {meijerg(((), ()), ((S.Zero,), ()), x), x, S.Zero} + assert f.find(a**2) == {meijerg(((), ()), ((S.Zero,), ()), x), x} + + +def test_issue_17354(): + from sympy.core.symbol import (Wild, symbols) + x, y = symbols("x y", real=True) + a, b = symbols("a b", cls=Wild) + assert ((0 <= x).reversed | (y <= x)).match((1/a <= b) | (a <= b)) is None + + +def test_match_issue_17397(): + f = Function("f") + x = Symbol("x") + a3 = Wild('a3', exclude=[f(x), f(x).diff(x), f(x).diff(x, 2)]) + b3 = Wild('b3', exclude=[f(x), f(x).diff(x), f(x).diff(x, 2)]) + c3 = Wild('c3', exclude=[f(x), f(x).diff(x), f(x).diff(x, 2)]) + deq = a3*(f(x).diff(x, 2)) + b3*f(x).diff(x) + c3*f(x) + + eq = (x-2)**2*(f(x).diff(x, 2)) + (x-2)*(f(x).diff(x)) + ((x-2)**2 - 4)*f(x) + r = collect(eq, [f(x).diff(x, 2), f(x).diff(x), f(x)]).match(deq) + assert r == {a3: (x - 2)**2, c3: (x - 2)**2 - 4, b3: x - 2} + + eq =x*f(x) + x*Derivative(f(x), (x, 2)) - 4*f(x) + Derivative(f(x), x) \ + - 4*Derivative(f(x), (x, 2)) - 2*Derivative(f(x), x)/x + 4*Derivative(f(x), (x, 2))/x + r = collect(eq, [f(x).diff(x, 2), f(x).diff(x), f(x)]).match(deq) + assert r == {a3: x - 4 + 4/x, b3: 1 - 2/x, c3: x - 4} + + +def test_match_issue_21942(): + a, r, w = symbols('a, r, w', nonnegative=True) + p = symbols('p', positive=True) + g_ = Wild('g') + pattern = g_ ** (1 / (1 - p)) + eq = (a * r ** (1 - p) + w ** (1 - p) * (1 - a)) ** (1 / (1 - p)) + m = {g_: a * r ** (1 - p) + w ** (1 - p) * (1 - a)} + assert pattern.matches(eq) == m + assert (-pattern).matches(-eq) == m + assert pattern.matches(signsimp(eq)) is None + + +def test_match_terms(): + X, Y = map(Wild, "XY") + x, y, z = symbols('x y z') + assert (5*y - x).match(5*X - Y) == {X: y, Y: x} + # 15907 + assert (x + (y - 1)*z).match(x + X*z) == {X: y - 1} + # 20747 + assert (x - log(x/y)*(1-exp(x/y))).match(x - log(X/y)*(1-exp(x/y))) == {X: x} + + +def test_match_bound(): + V, W = map(Wild, "VW") + x, y = symbols('x y') + assert Sum(x, (x, 1, 2)).match(Sum(y, (y, 1, W))) is None + assert Sum(x, (x, 1, 2)).match(Sum(V, (V, 1, W))) == {W: 2, V:x} + assert Sum(x, (x, 1, 2)).match(Sum(V, (V, 1, 2))) == {V:x} + + +def test_issue_22462(): + x, f = symbols('x'), Function('f') + n, Q = symbols('n Q', cls=Wild) + pattern = -Q*f(x)**n + eq = 5*f(x)**2 + assert pattern.matches(eq) == {n: 2, Q: -5} diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_multidimensional.py b/phivenv/Lib/site-packages/sympy/core/tests/test_multidimensional.py new file mode 100644 index 0000000000000000000000000000000000000000..765c78adf8dbed2ead43721ca4ab9510dbeeb282 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_multidimensional.py @@ -0,0 +1,24 @@ +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.symbol import symbols +from sympy.functions.elementary.trigonometric import sin +from sympy.core.multidimensional import vectorize +x, y, z = symbols('x y z') +f, g, h = list(map(Function, 'fgh')) + + +def test_vectorize(): + @vectorize(0) + def vsin(x): + return sin(x) + + assert vsin([1, x, y]) == [sin(1), sin(x), sin(y)] + + @vectorize(0, 1) + def vdiff(f, y): + return diff(f, y) + + assert vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z]) == \ + [[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y), + Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x), + Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)], + [Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]] diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_noncommutative.py b/phivenv/Lib/site-packages/sympy/core/tests/test_noncommutative.py new file mode 100644 index 0000000000000000000000000000000000000000..b3d3a3cec2ef64aa500aad08b438c90cc8987581 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_noncommutative.py @@ -0,0 +1,140 @@ +"""Tests for noncommutative symbols and expressions.""" + +from sympy.core.function import expand +from sympy.core.numbers import I +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import (adjoint, conjugate, transpose) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.polys.polytools import (cancel, factor) +from sympy.simplify.combsimp import combsimp +from sympy.simplify.gammasimp import gammasimp +from sympy.simplify.radsimp import (collect, radsimp, rcollect) +from sympy.simplify.ratsimp import ratsimp +from sympy.simplify.simplify import (posify, simplify) +from sympy.simplify.trigsimp import trigsimp +from sympy.abc import x, y, z +from sympy.testing.pytest import XFAIL + +A, B, C = symbols("A B C", commutative=False) +X = symbols("X", commutative=False, hermitian=True) +Y = symbols("Y", commutative=False, antihermitian=True) + + +def test_adjoint(): + assert adjoint(A).is_commutative is False + assert adjoint(A*A) == adjoint(A)**2 + assert adjoint(A*B) == adjoint(B)*adjoint(A) + assert adjoint(A*B**2) == adjoint(B)**2*adjoint(A) + assert adjoint(A*B - B*A) == adjoint(B)*adjoint(A) - adjoint(A)*adjoint(B) + assert adjoint(A + I*B) == adjoint(A) - I*adjoint(B) + + assert adjoint(X) == X + assert adjoint(-I*X) == I*X + assert adjoint(Y) == -Y + assert adjoint(-I*Y) == -I*Y + + assert adjoint(X) == conjugate(transpose(X)) + assert adjoint(Y) == conjugate(transpose(Y)) + assert adjoint(X) == transpose(conjugate(X)) + assert adjoint(Y) == transpose(conjugate(Y)) + + +def test_cancel(): + assert cancel(A*B - B*A) == A*B - B*A + assert cancel(A*B*(x - 1)) == A*B*(x - 1) + assert cancel(A*B*(x**2 - 1)/(x + 1)) == A*B*(x - 1) + assert cancel(A*B*(x**2 - 1)/(x + 1) - B*A*(x - 1)) == A*B*(x - 1) + (1 - x)*B*A + + +@XFAIL +def test_collect(): + assert collect(A*B - B*A, A) == A*B - B*A + assert collect(A*B - B*A, B) == A*B - B*A + assert collect(A*B - B*A, x) == A*B - B*A + + +def test_combsimp(): + assert combsimp(A*B - B*A) == A*B - B*A + + +def test_gammasimp(): + assert gammasimp(A*B - B*A) == A*B - B*A + + +def test_conjugate(): + assert conjugate(A).is_commutative is False + assert (A*A).conjugate() == conjugate(A)**2 + assert (A*B).conjugate() == conjugate(A)*conjugate(B) + assert (A*B**2).conjugate() == conjugate(A)*conjugate(B)**2 + assert (A*B - B*A).conjugate() == \ + conjugate(A)*conjugate(B) - conjugate(B)*conjugate(A) + assert (A*B).conjugate() - (B*A).conjugate() == \ + conjugate(A)*conjugate(B) - conjugate(B)*conjugate(A) + assert (A + I*B).conjugate() == conjugate(A) - I*conjugate(B) + + +def test_expand(): + assert expand((A*B)**2) == A*B*A*B + assert expand(A*B - B*A) == A*B - B*A + assert expand((A*B/A)**2) == A*B*B/A + assert expand(B*A*(A + B)*B) == B*A**2*B + B*A*B**2 + assert expand(B*A*(A + C)*B) == B*A**2*B + B*A*C*B + + +def test_factor(): + assert factor(A*B - B*A) == A*B - B*A + + +def test_posify(): + assert posify(A)[0].is_commutative is False + for q in (A*B/A, (A*B/A)**2, (A*B)**2, A*B - B*A): + p = posify(q) + assert p[0].subs(p[1]) == q + + +def test_radsimp(): + assert radsimp(A*B - B*A) == A*B - B*A + + +@XFAIL +def test_ratsimp(): + assert ratsimp(A*B - B*A) == A*B - B*A + + +@XFAIL +def test_rcollect(): + assert rcollect(A*B - B*A, A) == A*B - B*A + assert rcollect(A*B - B*A, B) == A*B - B*A + assert rcollect(A*B - B*A, x) == A*B - B*A + + +def test_simplify(): + assert simplify(A*B - B*A) == A*B - B*A + + +def test_subs(): + assert (x*y*A).subs(x*y, z) == A*z + assert (x*A*B).subs(x*A, C) == C*B + assert (x*A*x*x).subs(x**2*A, C) == x*C + assert (x*A*x*B).subs(x**2*A, C) == C*B + assert (A**2*B**2).subs(A*B**2, C) == A*C + assert (A*A*A + A*B*A).subs(A*A*A, C) == C + A*B*A + + +def test_transpose(): + assert transpose(A).is_commutative is False + assert transpose(A*A) == transpose(A)**2 + assert transpose(A*B) == transpose(B)*transpose(A) + assert transpose(A*B**2) == transpose(B)**2*transpose(A) + assert transpose(A*B - B*A) == \ + transpose(B)*transpose(A) - transpose(A)*transpose(B) + assert transpose(A + I*B) == transpose(A) + I*transpose(B) + + assert transpose(X) == conjugate(X) + assert transpose(-I*X) == -I*conjugate(X) + assert transpose(Y) == -conjugate(Y) + assert transpose(-I*Y) == I*conjugate(Y) + + +def test_trigsimp(): + assert trigsimp(A*sin(x)**2 + A*cos(x)**2) == A diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_numbers.py b/phivenv/Lib/site-packages/sympy/core/tests/test_numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..10d14b6ac09fb0ab102c37cb99405440bd0bbffb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_numbers.py @@ -0,0 +1,2335 @@ +import numbers as nums +import decimal +from sympy.concrete.summations import Sum +from sympy.core import (EulerGamma, Catalan, TribonacciConstant, + GoldenRatio) +from sympy.core.containers import Tuple +from sympy.core.expr import unchanged +from sympy.core.logic import fuzzy_not +from sympy.core.mul import Mul +from sympy.core.numbers import (mpf_norm, seterr, + Integer, I, pi, comp, Rational, E, nan, + oo, AlgebraicNumber, Number, Float, zoo, equal_valued, + int_valued, all_close) +from sympy.core.intfunc import (igcd, igcdex, igcd2, igcd_lehmer, + ilcm, integer_nthroot, isqrt, integer_log, mod_inverse) +from sympy.core.power import Pow +from sympy.core.relational import Ge, Gt, Le, Lt +from sympy.core.singleton import S +from sympy.core.symbol import Dummy, Symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.integers import floor +from sympy.functions.combinatorial.numbers import fibonacci +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.miscellaneous import sqrt, cbrt +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.polys.domains.realfield import RealField +from sympy.printing.latex import latex +from sympy.printing.repr import srepr +from sympy.simplify import simplify +from sympy.polys.domains.groundtypes import PythonRational +from sympy.utilities.decorator import conserve_mpmath_dps +from sympy.utilities.iterables import permutations +from sympy.testing.pytest import (XFAIL, raises, _both_exp_pow, + warns_deprecated_sympy) +from sympy import Add + +from mpmath import mpf +import mpmath +from sympy.core import numbers +t = Symbol('t', real=False) + +_ninf = float(-oo) +_inf = float(oo) + + +def same_and_same_prec(a, b): + # stricter matching for Floats + return a == b and a._prec == b._prec + + +def test_seterr(): + seterr(divide=True) + raises(ValueError, lambda: S.Zero/S.Zero) + seterr(divide=False) + assert S.Zero / S.Zero is S.NaN + + +def test_mod(): + x = S.Half + y = Rational(3, 4) + z = Rational(5, 18043) + + assert x % x == 0 + assert x % y == S.Half + assert x % z == Rational(3, 36086) + assert y % x == Rational(1, 4) + assert y % y == 0 + assert y % z == Rational(9, 72172) + assert z % x == Rational(5, 18043) + assert z % y == Rational(5, 18043) + assert z % z == 0 + + a = Float(2.6) + + assert (a % .2) == 0.0 + assert (a % 2).round(15) == 0.6 + assert (a % 0.5).round(15) == 0.1 + + p = Symbol('p', infinite=True) + + assert oo % oo is nan + assert zoo % oo is nan + assert 5 % oo is nan + assert p % 5 is nan + + # In these two tests, if the precision of m does + # not match the precision of the ans, then it is + # likely that the change made now gives an answer + # with degraded accuracy. + r = Rational(500, 41) + f = Float('.36', 3) + m = r % f + ans = Float(r % Rational(f), 3) + assert m == ans and m._prec == ans._prec + f = Float('8.36', 3) + m = f % r + ans = Float(Rational(f) % r, 3) + assert m == ans and m._prec == ans._prec + + s = S.Zero + + assert s % float(1) == 0.0 + + # No rounding required since these numbers can be represented + # exactly. + assert Rational(3, 4) % Float(1.1) == 0.75 + assert Float(1.5) % Rational(5, 4) == 0.25 + assert Rational(5, 4).__rmod__(Float('1.5')) == 0.25 + assert Float('1.5').__rmod__(Float('2.75')) == Float('1.25') + assert 2.75 % Float('1.5') == Float('1.25') + + a = Integer(7) + b = Integer(4) + + assert type(a % b) == Integer + assert a % b == Integer(3) + assert Integer(1) % Rational(2, 3) == Rational(1, 3) + assert Rational(7, 5) % Integer(1) == Rational(2, 5) + assert Integer(2) % 1.5 == 0.5 + + assert Integer(3).__rmod__(Integer(10)) == Integer(1) + assert Integer(10) % 4 == Integer(2) + assert 15 % Integer(4) == Integer(3) + + +def test_divmod(): + x = Symbol("x") + assert divmod(S(12), S(8)) == Tuple(1, 4) + assert divmod(-S(12), S(8)) == Tuple(-2, 4) + assert divmod(S.Zero, S.One) == Tuple(0, 0) + raises(ZeroDivisionError, lambda: divmod(S.Zero, S.Zero)) + raises(ZeroDivisionError, lambda: divmod(S.One, S.Zero)) + assert divmod(S(12), 8) == Tuple(1, 4) + assert divmod(12, S(8)) == Tuple(1, 4) + assert S(1024)//x == 1024//x == floor(1024/x) + + assert divmod(S("2"), S("3/2")) == Tuple(S("1"), S("1/2")) + assert divmod(S("3/2"), S("2")) == Tuple(S("0"), S("3/2")) + assert divmod(S("2"), S("3.5")) == Tuple(S("0"), S("2.")) + assert divmod(S("3.5"), S("2")) == Tuple(S("1"), S("1.5")) + assert divmod(S("2"), S("1/3")) == Tuple(S("6"), S("0")) + assert divmod(S("1/3"), S("2")) == Tuple(S("0"), S("1/3")) + assert divmod(S("2"), S("1/10")) == Tuple(S("20"), S("0")) + assert divmod(S("2"), S(".1"))[0] == 19 + assert divmod(S("0.1"), S("2")) == Tuple(S("0"), S("0.1")) + assert divmod(S("2"), 2) == Tuple(S("1"), S("0")) + assert divmod(2, S("2")) == Tuple(S("1"), S("0")) + assert divmod(S("2"), 1.5) == Tuple(S("1"), S("0.5")) + assert divmod(1.5, S("2")) == Tuple(S("0"), S("1.5")) + assert divmod(0.3, S("2")) == Tuple(S("0"), S("0.3")) + assert divmod(S("3/2"), S("3.5")) == Tuple(S("0"), S(3/2)) + assert divmod(S("3.5"), S("3/2")) == Tuple(S("2"), S("0.5")) + assert divmod(S("3/2"), S("1/3")) == Tuple(S("4"), S("1/6")) + assert divmod(S("1/3"), S("3/2")) == Tuple(S("0"), S("1/3")) + assert divmod(S("3/2"), S("0.1"))[0] == 14 + assert divmod(S("0.1"), S("3/2")) == Tuple(S("0"), S("0.1")) + assert divmod(S("3/2"), 2) == Tuple(S("0"), S("3/2")) + assert divmod(2, S("3/2")) == Tuple(S("1"), S("1/2")) + assert divmod(S("3/2"), 1.5) == Tuple(S("1"), S("0.")) + assert divmod(1.5, S("3/2")) == Tuple(S("1"), S("0.")) + assert divmod(S("3/2"), 0.3) == Tuple(S("5"), S("0.")) + assert divmod(0.3, S("3/2")) == Tuple(S("0"), S("0.3")) + assert divmod(S("1/3"), S("3.5")) == (0, 1/3) + assert divmod(S("3.5"), S("0.1")) == Tuple(S("35"), S("0.")) + assert divmod(S("0.1"), S("3.5")) == Tuple(S("0"), S("0.1")) + assert divmod(S("3.5"), 2) == Tuple(S("1"), S("1.5")) + assert divmod(2, S("3.5")) == Tuple(S("0"), S("2.")) + assert divmod(S("3.5"), 1.5) == Tuple(S("2"), S("0.5")) + assert divmod(1.5, S("3.5")) == Tuple(S("0"), S("1.5")) + assert divmod(0.3, S("3.5")) == Tuple(S("0"), S("0.3")) + assert divmod(S("0.1"), S("1/3")) == Tuple(S("0"), S("0.1")) + assert divmod(S("1/3"), 2) == Tuple(S("0"), S("1/3")) + assert divmod(2, S("1/3")) == Tuple(S("6"), S("0")) + assert divmod(S("1/3"), 1.5) == (0, 1/3) + assert divmod(0.3, S("1/3")) == (0, 0.3) + assert divmod(S("0.1"), 2) == (0, 0.1) + assert divmod(2, S("0.1"))[0] == 19 + assert divmod(S("0.1"), 1.5) == (0, 0.1) + assert divmod(1.5, S("0.1")) == Tuple(S("15"), S("0.")) + assert divmod(S("0.1"), 0.3) == Tuple(S("0"), S("0.1")) + + assert str(divmod(S("2"), 0.3)) == '(6, 0.2)' + assert str(divmod(S("3.5"), S("1/3"))) == '(10, 0.166666666666667)' + assert str(divmod(S("3.5"), 0.3)) == '(11, 0.2)' + assert str(divmod(S("1/3"), S("0.1"))) == '(3, 0.0333333333333333)' + assert str(divmod(1.5, S("1/3"))) == '(4, 0.166666666666667)' + assert str(divmod(S("1/3"), 0.3)) == '(1, 0.0333333333333333)' + assert str(divmod(0.3, S("0.1"))) == '(2, 0.1)' + + assert divmod(-3, S(2)) == (-2, 1) + assert divmod(S(-3), S(2)) == (-2, 1) + assert divmod(S(-3), 2) == (-2, 1) + + assert divmod(oo, 1) == (S.NaN, S.NaN) + assert divmod(S.NaN, 1) == (S.NaN, S.NaN) + assert divmod(1, S.NaN) == (S.NaN, S.NaN) + ans = [(-1, oo), (-1, oo), (0, 0), (0, 1), (0, 2)] + OO = float('inf') + ANS = [tuple(map(float, i)) for i in ans] + assert [divmod(i, oo) for i in range(-2, 3)] == ans + ans = [(0, -2), (0, -1), (0, 0), (-1, -oo), (-1, -oo)] + ANS = [tuple(map(float, i)) for i in ans] + assert [divmod(i, -oo) for i in range(-2, 3)] == ans + assert [divmod(i, -OO) for i in range(-2, 3)] == ANS + + # sympy's divmod gives an Integer for the quotient rather than a float + dmod = lambda a, b: tuple([j if i else int(j) for i, j in enumerate(divmod(a, b))]) + for a in (4, 4., 4.25, 0, 0., -4, -4. -4.25): + for b in (2, 2., 2.5, -2, -2., -2.5): + assert divmod(S(a), S(b)) == dmod(a, b) + + +def test_igcd(): + assert igcd(0, 0) == 0 + assert igcd(0, 1) == 1 + assert igcd(1, 0) == 1 + assert igcd(0, 7) == 7 + assert igcd(7, 0) == 7 + assert igcd(7, 1) == 1 + assert igcd(1, 7) == 1 + assert igcd(-1, 0) == 1 + assert igcd(0, -1) == 1 + assert igcd(-1, -1) == 1 + assert igcd(-1, 7) == 1 + assert igcd(7, -1) == 1 + assert igcd(8, 2) == 2 + assert igcd(4, 8) == 4 + assert igcd(8, 16) == 8 + assert igcd(7, -3) == 1 + assert igcd(-7, 3) == 1 + assert igcd(-7, -3) == 1 + assert igcd(*[10, 20, 30]) == 10 + raises(TypeError, lambda: igcd()) + raises(TypeError, lambda: igcd(2)) + raises(ValueError, lambda: igcd(0, None)) + raises(ValueError, lambda: igcd(1, 2.2)) + for args in permutations((45.1, 1, 30)): + raises(ValueError, lambda: igcd(*args)) + for args in permutations((1, 2, None)): + raises(ValueError, lambda: igcd(*args)) + + +def test_igcd_lehmer(): + a, b = fibonacci(10001), fibonacci(10000) + # len(str(a)) == 2090 + # small divisors, long Euclidean sequence + assert igcd_lehmer(a, b) == 1 + c = fibonacci(100) + assert igcd_lehmer(a*c, b*c) == c + # big divisor + assert igcd_lehmer(a, 10**1000) == 1 + # swapping argument + assert igcd_lehmer(1, 2) == igcd_lehmer(2, 1) + + +def test_igcd2(): + # short loop + assert igcd2(2**100 - 1, 2**99 - 1) == 1 + # Lehmer's algorithm + a, b = int(fibonacci(10001)), int(fibonacci(10000)) + assert igcd2(a, b) == 1 + + +def test_ilcm(): + assert ilcm(0, 0) == 0 + assert ilcm(1, 0) == 0 + assert ilcm(0, 1) == 0 + assert ilcm(1, 1) == 1 + assert ilcm(2, 1) == 2 + assert ilcm(8, 2) == 8 + assert ilcm(8, 6) == 24 + assert ilcm(8, 7) == 56 + assert ilcm(*[10, 20, 30]) == 60 + raises(ValueError, lambda: ilcm(8.1, 7)) + raises(ValueError, lambda: ilcm(8, 7.1)) + raises(TypeError, lambda: ilcm(8)) + + +def test_igcdex(): + assert igcdex(2, 3) == (-1, 1, 1) + assert igcdex(10, 12) == (-1, 1, 2) + assert igcdex(100, 2004) == (-20, 1, 4) + assert igcdex(0, 0) == (0, 0, 0) + assert igcdex(1, 0) == (1, 0, 1) + + +def _strictly_equal(a, b): + return (a.p, a.q, type(a.p), type(a.q)) == \ + (b.p, b.q, type(b.p), type(b.q)) + + +def _test_rational_new(cls): + """ + Tests that are common between Integer and Rational. + """ + assert cls(0) is S.Zero + assert cls(1) is S.One + assert cls(-1) is S.NegativeOne + # These look odd, but are similar to int(): + assert cls('1') is S.One + assert cls('-1') is S.NegativeOne + + i = Integer(10) + assert _strictly_equal(i, cls('10')) + assert _strictly_equal(i, cls('10')) + assert _strictly_equal(i, cls(int(10))) + assert _strictly_equal(i, cls(i)) + + raises(TypeError, lambda: cls(Symbol('x'))) + + +def test_Integer_new(): + """ + Test for Integer constructor + """ + _test_rational_new(Integer) + + assert _strictly_equal(Integer(0.9), S.Zero) + assert _strictly_equal(Integer(10.5), Integer(10)) + raises(ValueError, lambda: Integer("10.5")) + assert Integer(Rational('1.' + '9'*20)) == 1 + + +def test_Rational_new(): + """" + Test for Rational constructor + """ + _test_rational_new(Rational) + + n1 = S.Half + assert n1 == Rational(Integer(1), 2) + assert n1 == Rational(Integer(1), Integer(2)) + assert n1 == Rational(1, Integer(2)) + assert n1 == Rational(S.Half) + assert 1 == Rational(n1, n1) + assert Rational(3, 2) == Rational(S.Half, Rational(1, 3)) + assert Rational(3, 1) == Rational(1, Rational(1, 3)) + n3_4 = Rational(3, 4) + assert Rational('3/4') == n3_4 + assert -Rational('-3/4') == n3_4 + assert Rational('.76').limit_denominator(4) == n3_4 + assert Rational(19, 25).limit_denominator(4) == n3_4 + assert Rational('19/25').limit_denominator(4) == n3_4 + assert Rational(1.0, 3) == Rational(1, 3) + assert Rational(1, 3.0) == Rational(1, 3) + assert Rational(Float(0.5)) == S.Half + assert Rational('1e2/1e-2') == Rational(10000) + assert Rational('1 234') == Rational(1234) + assert Rational('1/1 234') == Rational(1, 1234) + assert Rational(-1, 0) is S.ComplexInfinity + assert Rational(1, 0) is S.ComplexInfinity + # Make sure Rational doesn't lose precision on Floats + assert Rational(pi.evalf(100)).evalf(100) == pi.evalf(100) + raises(TypeError, lambda: Rational('3**3')) + raises(TypeError, lambda: Rational('1/2 + 2/3')) + + # handle fractions.Fraction instances + try: + import fractions + assert Rational(fractions.Fraction(1, 2)) == S.Half + except ImportError: + pass + + assert Rational(PythonRational(2, 6)) == Rational(1, 3) + + with warns_deprecated_sympy(): + assert Rational(2, 4, gcd=1).q == 4 + with warns_deprecated_sympy(): + n = Rational(2, -4, gcd=1) + assert n.q == 4 + assert n.p == -2 + + assert Rational.from_coprime_ints(3, 5) == Rational(3, 5) + + +def test_issue_24543(): + for p in ('1.5', 1.5, 2): + for q in ('1.5', 1.5, 2): + assert Rational(p, q).as_numer_denom() == Rational('%s/%s'%(p,q)).as_numer_denom() + + assert Rational('0.5', '100') == Rational(1, 200) + + +def test_Number_new(): + """" + Test for Number constructor + """ + # Expected behavior on numbers and strings + assert Number(1) is S.One + assert Number(2).__class__ is Integer + assert Number(-622).__class__ is Integer + assert Number(5, 3).__class__ is Rational + assert Number(5.3).__class__ is Float + assert Number('1') is S.One + assert Number('2').__class__ is Integer + assert Number('-622').__class__ is Integer + assert Number('5/3').__class__ is Rational + assert Number('5.3').__class__ is Float + raises(ValueError, lambda: Number('cos')) + raises(TypeError, lambda: Number(cos)) + a = Rational(3, 5) + assert Number(a) is a # Check idempotence on Numbers + u = ['inf', '-inf', 'nan', 'iNF', '+inf'] + v = [oo, -oo, nan, oo, oo] + for i, a in zip(u, v): + assert Number(i) is a, (i, Number(i), a) + + +def test_Number_cmp(): + n1 = Number(1) + n2 = Number(2) + n3 = Number(-3) + + assert n1 < n2 + assert n1 <= n2 + assert n3 < n1 + assert n2 > n3 + assert n2 >= n3 + + raises(TypeError, lambda: n1 < S.NaN) + raises(TypeError, lambda: n1 <= S.NaN) + raises(TypeError, lambda: n1 > S.NaN) + raises(TypeError, lambda: n1 >= S.NaN) + + +def test_Rational_cmp(): + n1 = Rational(1, 4) + n2 = Rational(1, 3) + n3 = Rational(2, 4) + n4 = Rational(2, -4) + n5 = Rational(0) + n6 = Rational(1) + n7 = Rational(3) + n8 = Rational(-3) + + assert n8 < n5 + assert n5 < n6 + assert n6 < n7 + assert n8 < n7 + assert n7 > n8 + assert (n1 + 1)**n2 < 2 + assert ((n1 + n6)/n7) < 1 + + assert n4 < n3 + assert n2 < n3 + assert n1 < n2 + assert n3 > n1 + assert not n3 < n1 + assert not (Rational(-1) > 0) + assert Rational(-1) < 0 + + raises(TypeError, lambda: n1 < S.NaN) + raises(TypeError, lambda: n1 <= S.NaN) + raises(TypeError, lambda: n1 > S.NaN) + raises(TypeError, lambda: n1 >= S.NaN) + + +def test_Float(): + def eq(a, b): + t = Float("1.0E-15") + return (-t < a - b < t) + + equal_pairs = [ + (0, 0.0), # This is just how Python works... + (0, S.Zero), + (0.0, Float(0)), + ] + unequal_pairs = [ + (0.0, S.Zero), + (0, Float(0)), + (S.Zero, Float(0)), + ] + for p1, p2 in equal_pairs: + assert (p1 == p2) is True + assert (p1 != p2) is False + assert (p2 == p1) is True + assert (p2 != p1) is False + for p1, p2 in unequal_pairs: + assert (p1 == p2) is False + assert (p1 != p2) is True + assert (p2 == p1) is False + assert (p2 != p1) is True + + assert S.Zero.is_zero + + a = Float(2) ** Float(3) + assert eq(a.evalf(), Float(8)) + assert eq((pi ** -1).evalf(), Float("0.31830988618379067")) + a = Float(2) ** Float(4) + assert eq(a.evalf(), Float(16)) + assert (S(.3) == S(.5)) is False + + mpf = (0, 5404319552844595, -52, 53) + x_str = Float((0, '13333333333333', -52, 53)) + x_0xstr = Float((0, '0x13333333333333', -52, 53)) + x2_str = Float((0, '26666666666666', -53, 54)) + x_hex = Float((0, int(0x13333333333333), -52, 53)) + x_dec = Float(mpf) + assert x_str == x_0xstr == x_hex == x_dec == Float(1.2) + # x2_str was entered slightly malformed in that the mantissa + # was even -- it should be odd and the even part should be + # included with the exponent, but this is resolved by normalization + # ONLY IF REQUIREMENTS of mpf_norm are met: the bitcount must + # be exact: double the mantissa ==> increase bc by 1 + assert Float(1.2)._mpf_ == mpf + assert x2_str._mpf_ == mpf + + assert Float((0, int(0), -123, -1)) is S.NaN + assert Float((0, int(0), -456, -2)) is S.Infinity + assert Float((1, int(0), -789, -3)) is S.NegativeInfinity + # if you don't give the full signature, it's not special + assert Float((0, int(0), -123)) == Float(0) + assert Float((0, int(0), -456)) == Float(0) + assert Float((1, int(0), -789)) == Float(0) + + raises(ValueError, lambda: Float((0, 7, 1, 3), '')) + + assert Float('0.0').is_finite is True + assert Float('0.0').is_negative is False + assert Float('0.0').is_positive is False + assert Float('0.0').is_infinite is False + assert Float('0.0').is_zero is True + + # rationality properties + # if the integer test fails then the use of intlike + # should be removed from gamma_functions.py + assert Float(1).is_integer is None + assert Float(1).is_rational is None + assert Float(1).is_irrational is None + assert sqrt(2).n(15).is_rational is None + assert sqrt(2).n(15).is_irrational is None + + # do not automatically evalf + def teq(a): + assert (a.evalf() == a) is False + assert (a.evalf() != a) is True + assert (a == a.evalf()) is False + assert (a != a.evalf()) is True + + teq(pi) + teq(2*pi) + teq(cos(0.1, evaluate=False)) + + # long integer + i = 12345678901234567890 + assert same_and_same_prec(Float(12, ''), Float('12', '')) + assert same_and_same_prec(Float(Integer(i), ''), Float(i, '')) + assert same_and_same_prec(Float(i, ''), Float(str(i), 20)) + assert same_and_same_prec(Float(str(i)), Float(i, '')) + assert same_and_same_prec(Float(i), Float(i, '')) + + # inexact floats (repeating binary = denom not multiple of 2) + # cannot have precision greater than 15 + assert Float(.125, 22)._prec == 76 + assert Float(2.0, 22)._prec == 76 + # only default prec is equal, even for exactly representable float + assert Float(.125, 22) != .125 + #assert Float(2.0, 22) == 2 + assert float(Float('.12500000000000001', '')) == .125 + raises(ValueError, lambda: Float(.12500000000000001, '')) + + # allow spaces + assert Float('123 456.123 456') == Float('123456.123456') + assert Integer('123 456') == Integer('123456') + assert Rational('123 456.123 456') == Rational('123456.123456') + assert Float(' .3e2') == Float('0.3e2') + # but treat them as strictly ass underscore between digits: only 1 + raises(ValueError, lambda: Float('1 2')) + + # allow underscore between digits + assert Float('1_23.4_56') == Float('123.456') + # assert Float('1_23.4_5_6', 12) == Float('123.456', 12) + # ...but not in all cases (per Py 3.6) + raises(ValueError, lambda: Float('1_')) + raises(ValueError, lambda: Float('1__2')) + raises(ValueError, lambda: Float('_1')) + raises(ValueError, lambda: Float('_inf')) + + # allow auto precision detection + assert Float('.1', '') == Float(.1, 1) + assert Float('.125', '') == Float(.125, 3) + assert Float('.100', '') == Float(.1, 3) + assert Float('2.0', '') == Float('2', 2) + + raises(ValueError, lambda: Float("12.3d-4", "")) + raises(ValueError, lambda: Float(12.3, "")) + raises(ValueError, lambda: Float('.')) + raises(ValueError, lambda: Float('-.')) + + zero = Float('0.0') + assert Float('-0') == zero + assert Float('.0') == zero + assert Float('-.0') == zero + assert Float('-0.0') == zero + assert Float(0.0) == zero + assert Float(0) == zero + assert Float(0, '') == Float('0', '') + assert Float(1) == Float(1.0) + assert Float(S.Zero) == zero + assert Float(S.One) == Float(1.0) + + assert Float(decimal.Decimal('0.1'), 3) == Float('.1', 3) + assert Float(decimal.Decimal('nan')) is S.NaN + assert Float(decimal.Decimal('Infinity')) is S.Infinity + assert Float(decimal.Decimal('-Infinity')) is S.NegativeInfinity + + assert '{:.3f}'.format(Float(4.236622)) == '4.237' + assert '{:.35f}'.format(Float(pi.n(40), 40)) == \ + '3.14159265358979323846264338327950288' + + # unicode + assert Float('0.73908513321516064100000000') == \ + Float('0.73908513321516064100000000') + assert Float('0.73908513321516064100000000', 28) == \ + Float('0.73908513321516064100000000', 28) + + # binary precision + # Decimal value 0.1 cannot be expressed precisely as a base 2 fraction + a = Float(S.One/10, dps=15) + b = Float(S.One/10, dps=16) + p = Float(S.One/10, precision=53) + q = Float(S.One/10, precision=54) + assert a._mpf_ == p._mpf_ + assert not a._mpf_ == q._mpf_ + assert not b._mpf_ == q._mpf_ + + # Precision specifying errors + raises(ValueError, lambda: Float("1.23", dps=3, precision=10)) + raises(ValueError, lambda: Float("1.23", dps="", precision=10)) + raises(ValueError, lambda: Float("1.23", dps=3, precision="")) + raises(ValueError, lambda: Float("1.23", dps="", precision="")) + + # from NumberSymbol + assert same_and_same_prec(Float(pi, 32), pi.evalf(32)) + assert same_and_same_prec(Float(Catalan), Catalan.evalf()) + + # oo and nan + u = ['inf', '-inf', 'nan', 'iNF', '+inf'] + v = [oo, -oo, nan, oo, oo] + for i, a in zip(u, v): + assert Float(i) is a + + +def test_zero_not_false(): + # https://github.com/sympy/sympy/issues/20796 + assert (S(0.0) == S.false) is False + assert (S.false == S(0.0)) is False + assert (S(0) == S.false) is False + assert (S.false == S(0)) is False + + +@conserve_mpmath_dps +def test_float_mpf(): + import mpmath + mpmath.mp.dps = 100 + mp_pi = mpmath.pi() + + assert Float(mp_pi, 100) == Float(mp_pi._mpf_, 100) == pi.evalf(100) + + mpmath.mp.dps = 15 + + assert Float(mp_pi, 100) == Float(mp_pi._mpf_, 100) == pi.evalf(100) + + +def test_Float_RealElement(): + repi = RealField(dps=100)(pi.evalf(100)) + # We still have to pass the precision because Float doesn't know what + # RealElement is, but make sure it keeps full precision from the result. + assert Float(repi, 100) == pi.evalf(100) + + +def test_Float_default_to_highprec_from_str(): + s = str(pi.evalf(128)) + assert same_and_same_prec(Float(s), Float(s, '')) + + +def test_Float_eval(): + a = Float(3.2) + assert (a**2).is_Float + + +def test_Float_issue_2107(): + a = Float(0.1, 10) + b = Float("0.1", 10) + + assert a - a == 0 + assert a + (-a) == 0 + assert S.Zero + a - a == 0 + assert S.Zero + a + (-a) == 0 + + assert b - b == 0 + assert b + (-b) == 0 + assert S.Zero + b - b == 0 + assert S.Zero + b + (-b) == 0 + + +def test_issue_14289(): + from sympy.polys.numberfields import to_number_field + + a = 1 - sqrt(2) + b = to_number_field(a) + assert b.as_expr() == a + assert b.minpoly(a).expand() == 0 + + +def test_Float_from_tuple(): + a = Float((0, '1L', 0, 1)) + b = Float((0, '1', 0, 1)) + assert a == b + + +def test_Infinity(): + assert oo != 1 + assert 1*oo is oo + assert 1 != oo + assert oo != -oo + assert oo != Symbol("x")**3 + assert oo + 1 is oo + assert 2 + oo is oo + assert 3*oo + 2 is oo + assert S.Half**oo == 0 + assert S.Half**(-oo) is oo + assert -oo*3 is -oo + assert oo + oo is oo + assert -oo + oo*(-5) is -oo + assert 1/oo == 0 + assert 1/(-oo) == 0 + assert 8/oo == 0 + assert oo % 2 is nan + assert 2 % oo is nan + assert oo/oo is nan + assert oo/-oo is nan + assert -oo/oo is nan + assert -oo/-oo is nan + assert oo - oo is nan + assert oo - -oo is oo + assert -oo - oo is -oo + assert -oo - -oo is nan + assert oo + -oo is nan + assert -oo + oo is nan + assert oo + oo is oo + assert -oo + oo is nan + assert oo + -oo is nan + assert -oo + -oo is -oo + assert oo*oo is oo + assert -oo*oo is -oo + assert oo*-oo is -oo + assert -oo*-oo is oo + assert oo/0 is oo + assert -oo/0 is -oo + assert 0/oo == 0 + assert 0/-oo == 0 + assert oo*0 is nan + assert -oo*0 is nan + assert 0*oo is nan + assert 0*-oo is nan + assert oo + 0 is oo + assert -oo + 0 is -oo + assert 0 + oo is oo + assert 0 + -oo is -oo + assert oo - 0 is oo + assert -oo - 0 is -oo + assert 0 - oo is -oo + assert 0 - -oo is oo + assert oo/2 is oo + assert -oo/2 is -oo + assert oo/-2 is -oo + assert -oo/-2 is oo + assert oo*2 is oo + assert -oo*2 is -oo + assert oo*-2 is -oo + assert 2/oo == 0 + assert 2/-oo == 0 + assert -2/oo == 0 + assert -2/-oo == 0 + assert 2*oo is oo + assert 2*-oo is -oo + assert -2*oo is -oo + assert -2*-oo is oo + assert 2 + oo is oo + assert 2 - oo is -oo + assert -2 + oo is oo + assert -2 - oo is -oo + assert 2 + -oo is -oo + assert 2 - -oo is oo + assert -2 + -oo is -oo + assert -2 - -oo is oo + assert S(2) + oo is oo + assert S(2) - oo is -oo + assert oo/I == -oo*I + assert -oo/I == oo*I + assert oo*float(1) == _inf and (oo*float(1)) is oo + assert -oo*float(1) == _ninf and (-oo*float(1)) is -oo + assert oo/float(1) == _inf and (oo/float(1)) is oo + assert -oo/float(1) == _ninf and (-oo/float(1)) is -oo + assert oo*float(-1) == _ninf and (oo*float(-1)) is -oo + assert -oo*float(-1) == _inf and (-oo*float(-1)) is oo + assert oo/float(-1) == _ninf and (oo/float(-1)) is -oo + assert -oo/float(-1) == _inf and (-oo/float(-1)) is oo + assert oo + float(1) == _inf and (oo + float(1)) is oo + assert -oo + float(1) == _ninf and (-oo + float(1)) is -oo + assert oo - float(1) == _inf and (oo - float(1)) is oo + assert -oo - float(1) == _ninf and (-oo - float(1)) is -oo + assert float(1)*oo == _inf and (float(1)*oo) is oo + assert float(1)*-oo == _ninf and (float(1)*-oo) is -oo + assert float(1)/oo == 0 + assert float(1)/-oo == 0 + assert float(-1)*oo == _ninf and (float(-1)*oo) is -oo + assert float(-1)*-oo == _inf and (float(-1)*-oo) is oo + assert float(-1)/oo == 0 + assert float(-1)/-oo == 0 + assert float(1) + oo is oo + assert float(1) + -oo is -oo + assert float(1) - oo is -oo + assert float(1) - -oo is oo + assert oo == float(oo) + assert (oo != float(oo)) is False + assert type(float(oo)) is float + assert -oo == float(-oo) + assert (-oo != float(-oo)) is False + assert type(float(-oo)) is float + + assert Float('nan') is nan + assert nan*1.0 is nan + assert -1.0*nan is nan + assert nan*oo is nan + assert nan*-oo is nan + assert nan/oo is nan + assert nan/-oo is nan + assert nan + oo is nan + assert nan + -oo is nan + assert nan - oo is nan + assert nan - -oo is nan + assert -oo * S.Zero is nan + + assert oo*nan is nan + assert -oo*nan is nan + assert oo/nan is nan + assert -oo/nan is nan + assert oo + nan is nan + assert -oo + nan is nan + assert oo - nan is nan + assert -oo - nan is nan + assert S.Zero * oo is nan + assert oo.is_Rational is False + assert isinstance(oo, Rational) is False + + assert S.One/oo == 0 + assert -S.One/oo == 0 + assert S.One/-oo == 0 + assert -S.One/-oo == 0 + assert S.One*oo is oo + assert -S.One*oo is -oo + assert S.One*-oo is -oo + assert -S.One*-oo is oo + assert S.One/nan is nan + assert S.One - -oo is oo + assert S.One + nan is nan + assert S.One - nan is nan + assert nan - S.One is nan + assert nan/S.One is nan + assert -oo - S.One is -oo + + +def test_Infinity_2(): + x = Symbol('x') + assert oo*x != oo + assert oo*(pi - 1) is oo + assert oo*(1 - pi) is -oo + + assert (-oo)*x != -oo + assert (-oo)*(pi - 1) is -oo + assert (-oo)*(1 - pi) is oo + + assert (-1)**S.NaN is S.NaN + assert oo - _inf is S.NaN + assert oo + _ninf is S.NaN + assert oo*0 is S.NaN + assert oo/_inf is S.NaN + assert oo/_ninf is S.NaN + assert oo**S.NaN is S.NaN + assert -oo + _inf is S.NaN + assert -oo - _ninf is S.NaN + assert -oo*S.NaN is S.NaN + assert -oo*0 is S.NaN + assert -oo/_inf is S.NaN + assert -oo/_ninf is S.NaN + assert -oo/S.NaN is S.NaN + assert abs(-oo) is oo + assert all((-oo)**i is S.NaN for i in (oo, -oo, S.NaN)) + assert (-oo)**3 is -oo + assert (-oo)**2 is oo + assert abs(S.ComplexInfinity) is oo + + +def test_Mul_Infinity_Zero(): + assert Float(0)*_inf is nan + assert Float(0)*_ninf is nan + assert Float(0)*_inf is nan + assert Float(0)*_ninf is nan + assert _inf*Float(0) is nan + assert _ninf*Float(0) is nan + assert _inf*Float(0) is nan + assert _ninf*Float(0) is nan + + +def test_Div_By_Zero(): + assert 1/S.Zero is zoo + assert 1/Float(0) is zoo + assert 0/S.Zero is nan + assert 0/Float(0) is nan + assert S.Zero/0 is nan + assert Float(0)/0 is nan + assert -1/S.Zero is zoo + assert -1/Float(0) is zoo + + +@_both_exp_pow +def test_Infinity_inequations(): + assert oo > pi + assert not (oo < pi) + assert exp(-3) < oo + + assert _inf > pi + assert not (_inf < pi) + assert exp(-3) < _inf + + raises(TypeError, lambda: oo < I) + raises(TypeError, lambda: oo <= I) + raises(TypeError, lambda: oo > I) + raises(TypeError, lambda: oo >= I) + raises(TypeError, lambda: -oo < I) + raises(TypeError, lambda: -oo <= I) + raises(TypeError, lambda: -oo > I) + raises(TypeError, lambda: -oo >= I) + + raises(TypeError, lambda: I < oo) + raises(TypeError, lambda: I <= oo) + raises(TypeError, lambda: I > oo) + raises(TypeError, lambda: I >= oo) + raises(TypeError, lambda: I < -oo) + raises(TypeError, lambda: I <= -oo) + raises(TypeError, lambda: I > -oo) + raises(TypeError, lambda: I >= -oo) + + assert oo > -oo and oo >= -oo + assert (oo < -oo) == False and (oo <= -oo) == False + assert -oo < oo and -oo <= oo + assert (-oo > oo) == False and (-oo >= oo) == False + + assert (oo < oo) == False # issue 7775 + assert (oo > oo) == False + assert (-oo > -oo) == False and (-oo < -oo) == False + assert oo >= oo and oo <= oo and -oo >= -oo and -oo <= -oo + assert (-oo < -_inf) == False + assert (oo > _inf) == False + assert -oo >= -_inf + assert oo <= _inf + + x = Symbol('x') + b = Symbol('b', finite=True, real=True) + assert (x < oo) == Lt(x, oo) # issue 7775 + assert b < oo and b > -oo and b <= oo and b >= -oo + assert oo > b and oo >= b and (oo < b) == False and (oo <= b) == False + assert (-oo > b) == False and (-oo >= b) == False and -oo < b and -oo <= b + assert (oo < x) == Lt(oo, x) and (oo > x) == Gt(oo, x) + assert (oo <= x) == Le(oo, x) and (oo >= x) == Ge(oo, x) + assert (-oo < x) == Lt(-oo, x) and (-oo > x) == Gt(-oo, x) + assert (-oo <= x) == Le(-oo, x) and (-oo >= x) == Ge(-oo, x) + + +def test_NaN(): + assert nan is nan + assert nan != 1 + assert 1*nan is nan + assert 1 != nan + assert -nan is nan + assert oo != Symbol("x")**3 + assert 2 + nan is nan + assert 3*nan + 2 is nan + assert -nan*3 is nan + assert nan + nan is nan + assert -nan + nan*(-5) is nan + assert 8/nan is nan + raises(TypeError, lambda: nan > 0) + raises(TypeError, lambda: nan < 0) + raises(TypeError, lambda: nan >= 0) + raises(TypeError, lambda: nan <= 0) + raises(TypeError, lambda: 0 < nan) + raises(TypeError, lambda: 0 > nan) + raises(TypeError, lambda: 0 <= nan) + raises(TypeError, lambda: 0 >= nan) + assert nan**0 == 1 # as per IEEE 754 + assert 1**nan is nan # IEEE 754 is not the best choice for symbolic work + # test Pow._eval_power's handling of NaN + assert Pow(nan, 0, evaluate=False)**2 == 1 + for n in (1, 1., S.One, S.NegativeOne, Float(1)): + assert n + nan is nan + assert n - nan is nan + assert nan + n is nan + assert nan - n is nan + assert n/nan is nan + assert nan/n is nan + + +def test_special_numbers(): + assert isinstance(S.NaN, Number) is True + assert isinstance(S.Infinity, Number) is True + assert isinstance(S.NegativeInfinity, Number) is True + + assert S.NaN.is_number is True + assert S.Infinity.is_number is True + assert S.NegativeInfinity.is_number is True + assert S.ComplexInfinity.is_number is True + + assert isinstance(S.NaN, Rational) is False + assert isinstance(S.Infinity, Rational) is False + assert isinstance(S.NegativeInfinity, Rational) is False + + assert S.NaN.is_rational is not True + assert S.Infinity.is_rational is not True + assert S.NegativeInfinity.is_rational is not True + + +def test_powers(): + assert integer_nthroot(1, 2) == (1, True) + assert integer_nthroot(1, 5) == (1, True) + assert integer_nthroot(2, 1) == (2, True) + assert integer_nthroot(2, 2) == (1, False) + assert integer_nthroot(2, 5) == (1, False) + assert integer_nthroot(4, 2) == (2, True) + assert integer_nthroot(123**25, 25) == (123, True) + assert integer_nthroot(123**25 + 1, 25) == (123, False) + assert integer_nthroot(123**25 - 1, 25) == (122, False) + assert integer_nthroot(1, 1) == (1, True) + assert integer_nthroot(0, 1) == (0, True) + assert integer_nthroot(0, 3) == (0, True) + assert integer_nthroot(10000, 1) == (10000, True) + assert integer_nthroot(4, 2) == (2, True) + assert integer_nthroot(16, 2) == (4, True) + assert integer_nthroot(26, 2) == (5, False) + assert integer_nthroot(1234567**7, 7) == (1234567, True) + assert integer_nthroot(1234567**7 + 1, 7) == (1234567, False) + assert integer_nthroot(1234567**7 - 1, 7) == (1234566, False) + b = 25**1000 + assert integer_nthroot(b, 1000) == (25, True) + assert integer_nthroot(b + 1, 1000) == (25, False) + assert integer_nthroot(b - 1, 1000) == (24, False) + c = 10**400 + c2 = c**2 + assert integer_nthroot(c2, 2) == (c, True) + assert integer_nthroot(c2 + 1, 2) == (c, False) + assert integer_nthroot(c2 - 1, 2) == (c - 1, False) + assert integer_nthroot(2, 10**10) == (1, False) + + p, r = integer_nthroot(int(factorial(10000)), 100) + assert p % (10**10) == 5322420655 + assert not r + + # Test that this is fast + assert integer_nthroot(2, 10**10) == (1, False) + + # output should be int if possible + assert type(integer_nthroot(2**61, 2)[0]) is int + + +def test_integer_nthroot_overflow(): + assert integer_nthroot(10**(50*50), 50) == (10**50, True) + assert integer_nthroot(10**100000, 10000) == (10**10, True) + + +def test_integer_log(): + raises(ValueError, lambda: integer_log(2, 1)) + raises(ValueError, lambda: integer_log(0, 2)) + raises(ValueError, lambda: integer_log(1.1, 2)) + raises(ValueError, lambda: integer_log(1, 2.2)) + + assert integer_log(1, 2) == (0, True) + assert integer_log(1, 3) == (0, True) + assert integer_log(2, 3) == (0, False) + assert integer_log(3, 3) == (1, True) + assert integer_log(3*2, 3) == (1, False) + assert integer_log(3**2, 3) == (2, True) + assert integer_log(3*4, 3) == (2, False) + assert integer_log(3**3, 3) == (3, True) + assert integer_log(27, 5) == (2, False) + assert integer_log(2, 3) == (0, False) + assert integer_log(-4, 2) == (2, False) + assert integer_log(-16, 4) == (0, False) + assert integer_log(-4, -2) == (2, False) + assert integer_log(4, -2) == (2, True) + assert integer_log(-8, -2) == (3, True) + assert integer_log(8, -2) == (3, False) + assert integer_log(-9, 3) == (0, False) + assert integer_log(-9, -3) == (2, False) + assert integer_log(9, -3) == (2, True) + assert integer_log(-27, -3) == (3, True) + assert integer_log(27, -3) == (3, False) + + +def test_isqrt(): + from math import sqrt as _sqrt + limit = 4503599761588223 + assert int(_sqrt(limit)) == integer_nthroot(limit, 2)[0] + assert int(_sqrt(limit + 1)) != integer_nthroot(limit + 1, 2)[0] + assert isqrt(limit + 1) == integer_nthroot(limit + 1, 2)[0] + assert isqrt(limit + S.Half) == integer_nthroot(limit, 2)[0] + assert isqrt(limit + 1 + S.Half) == integer_nthroot(limit + 1, 2)[0] + assert isqrt(limit + 2 + S.Half) == integer_nthroot(limit + 2, 2)[0] + + # Regression tests for https://github.com/sympy/sympy/issues/17034 + assert isqrt(4503599761588224) == 67108864 + assert isqrt(9999999999999999) == 99999999 + + # Other corner cases, especially involving non-integers. + raises(ValueError, lambda: isqrt(-1)) + raises(ValueError, lambda: isqrt(-10**1000)) + raises(ValueError, lambda: isqrt(Rational(-1, 2))) + + tiny = Rational(1, 10**1000) + raises(ValueError, lambda: isqrt(-tiny)) + assert isqrt(1-tiny) == 0 + assert isqrt(4503599761588224-tiny) == 67108864 + assert isqrt(10**100 - tiny) == 10**50 - 1 + + +def test_powers_Integer(): + """Test Integer._eval_power""" + # check infinity + assert S.One ** S.Infinity is S.NaN + assert S.NegativeOne** S.Infinity is S.NaN + assert S(2) ** S.Infinity is S.Infinity + assert S(-2)** S.Infinity == zoo + assert S(0) ** S.Infinity is S.Zero + + # check Nan + assert S.One ** S.NaN is S.NaN + assert S.NegativeOne ** S.NaN is S.NaN + + # check for exact roots + assert S.NegativeOne ** Rational(6, 5) == - (-1)**(S.One/5) + assert sqrt(S(4)) == 2 + assert sqrt(S(-4)) == I * 2 + assert S(16) ** Rational(1, 4) == 2 + assert S(-16) ** Rational(1, 4) == 2 * (-1)**Rational(1, 4) + assert S(9) ** Rational(3, 2) == 27 + assert S(-9) ** Rational(3, 2) == -27*I + assert S(27) ** Rational(2, 3) == 9 + assert S(-27) ** Rational(2, 3) == 9 * (S.NegativeOne ** Rational(2, 3)) + assert (-2) ** Rational(-2, 1) == Rational(1, 4) + + # not exact roots + assert sqrt(-3) == I*sqrt(3) + assert (3) ** (Rational(3, 2)) == 3 * sqrt(3) + assert (-3) ** (Rational(3, 2)) == - 3 * sqrt(-3) + assert (-3) ** (Rational(5, 2)) == 9 * I * sqrt(3) + assert (-3) ** (Rational(7, 2)) == - I * 27 * sqrt(3) + assert (2) ** (Rational(3, 2)) == 2 * sqrt(2) + assert (2) ** (Rational(-3, 2)) == sqrt(2) / 4 + assert (81) ** (Rational(2, 3)) == 9 * (S(3) ** (Rational(2, 3))) + assert (-81) ** (Rational(2, 3)) == 9 * (S(-3) ** (Rational(2, 3))) + assert (-3) ** Rational(-7, 3) == \ + -(-1)**Rational(2, 3)*3**Rational(2, 3)/27 + assert (-3) ** Rational(-2, 3) == \ + -(-1)**Rational(1, 3)*3**Rational(1, 3)/3 + + # join roots + assert sqrt(6) + sqrt(24) == 3*sqrt(6) + assert sqrt(2) * sqrt(3) == sqrt(6) + + # separate symbols & constansts + x = Symbol("x") + assert sqrt(49 * x) == 7 * sqrt(x) + assert sqrt((3 - sqrt(pi)) ** 2) == 3 - sqrt(pi) + + # check that it is fast for big numbers + assert (2**64 + 1) ** Rational(4, 3) + assert (2**64 + 1) ** Rational(17, 25) + + # negative rational power and negative base + assert (-3) ** Rational(-7, 3) == \ + -(-1)**Rational(2, 3)*3**Rational(2, 3)/27 + assert (-3) ** Rational(-2, 3) == \ + -(-1)**Rational(1, 3)*3**Rational(1, 3)/3 + assert (-2) ** Rational(-10, 3) == \ + (-1)**Rational(2, 3)*2**Rational(2, 3)/16 + assert abs(Pow(-2, Rational(-10, 3)).n() - + Pow(-2, Rational(-10, 3), evaluate=False).n()) < 1e-16 + + # negative base and rational power with some simplification + assert (-8) ** Rational(2, 5) == \ + 2*(-1)**Rational(2, 5)*2**Rational(1, 5) + assert (-4) ** Rational(9, 5) == \ + -8*(-1)**Rational(4, 5)*2**Rational(3, 5) + + assert S(1234).factors() == {617: 1, 2: 1} + assert Rational(2*3, 3*5*7).factors() == {2: 1, 5: -1, 7: -1} + + # test that eval_power factors numbers bigger than + # the current limit in factor_trial_division (2**15) + from sympy.ntheory.generate import nextprime + n = nextprime(2**15) + assert sqrt(n**2) == n + assert sqrt(n**3) == n*sqrt(n) + assert sqrt(4*n) == 2*sqrt(n) + + # check that factors of base with powers sharing gcd with power are removed + assert (2**4*3)**Rational(1, 6) == 2**Rational(2, 3)*3**Rational(1, 6) + assert (2**4*3)**Rational(5, 6) == 8*2**Rational(1, 3)*3**Rational(5, 6) + + # check that bases sharing a gcd are exptracted + assert 2**Rational(1, 3)*3**Rational(1, 4)*6**Rational(1, 5) == \ + 2**Rational(8, 15)*3**Rational(9, 20) + assert sqrt(8)*24**Rational(1, 3)*6**Rational(1, 5) == \ + 4*2**Rational(7, 10)*3**Rational(8, 15) + assert sqrt(8)*(-24)**Rational(1, 3)*(-6)**Rational(1, 5) == \ + 4*(-3)**Rational(8, 15)*2**Rational(7, 10) + assert 2**Rational(1, 3)*2**Rational(8, 9) == 2*2**Rational(2, 9) + assert 2**Rational(2, 3)*6**Rational(1, 3) == 2*3**Rational(1, 3) + assert 2**Rational(2, 3)*6**Rational(8, 9) == \ + 2*2**Rational(5, 9)*3**Rational(8, 9) + assert (-2)**Rational(2, S(3))*(-4)**Rational(1, S(3)) == -2*2**Rational(1, 3) + assert 3*Pow(3, 2, evaluate=False) == 3**3 + assert 3*Pow(3, Rational(-1, 3), evaluate=False) == 3**Rational(2, 3) + assert (-2)**Rational(1, 3)*(-3)**Rational(1, 4)*(-5)**Rational(5, 6) == \ + -(-1)**Rational(5, 12)*2**Rational(1, 3)*3**Rational(1, 4) * \ + 5**Rational(5, 6) + + assert Integer(-2)**Symbol('', even=True) == \ + Integer(2)**Symbol('', even=True) + assert (-1)**Float(.5) == 1.0*I + + +def test_powers_Rational(): + """Test Rational._eval_power""" + # check infinity + assert S.Half ** S.Infinity == 0 + assert Rational(3, 2) ** S.Infinity is S.Infinity + assert Rational(-1, 2) ** S.Infinity == 0 + assert Rational(-3, 2) ** S.Infinity == zoo + + # check Nan + assert Rational(3, 4) ** S.NaN is S.NaN + assert Rational(-2, 3) ** S.NaN is S.NaN + + # exact roots on numerator + assert sqrt(Rational(4, 3)) == 2 * sqrt(3) / 3 + assert Rational(4, 3) ** Rational(3, 2) == 8 * sqrt(3) / 9 + assert sqrt(Rational(-4, 3)) == I * 2 * sqrt(3) / 3 + assert Rational(-4, 3) ** Rational(3, 2) == - I * 8 * sqrt(3) / 9 + assert Rational(27, 2) ** Rational(1, 3) == 3 * (2 ** Rational(2, 3)) / 2 + assert Rational(5**3, 8**3) ** Rational(4, 3) == Rational(5**4, 8**4) + + # exact root on denominator + assert sqrt(Rational(1, 4)) == S.Half + assert sqrt(Rational(1, -4)) == I * S.Half + assert sqrt(Rational(3, 4)) == sqrt(3) / 2 + assert sqrt(Rational(3, -4)) == I * sqrt(3) / 2 + assert Rational(5, 27) ** Rational(1, 3) == (5 ** Rational(1, 3)) / 3 + + # not exact roots + assert sqrt(S.Half) == sqrt(2) / 2 + assert sqrt(Rational(-4, 7)) == I * sqrt(Rational(4, 7)) + assert Rational(-3, 2)**Rational(-7, 3) == \ + -4*(-1)**Rational(2, 3)*2**Rational(1, 3)*3**Rational(2, 3)/27 + assert Rational(-3, 2)**Rational(-2, 3) == \ + -(-1)**Rational(1, 3)*2**Rational(2, 3)*3**Rational(1, 3)/3 + assert Rational(-3, 2)**Rational(-10, 3) == \ + 8*(-1)**Rational(2, 3)*2**Rational(1, 3)*3**Rational(2, 3)/81 + assert abs(Pow(Rational(-2, 3), Rational(-7, 4)).n() - + Pow(Rational(-2, 3), Rational(-7, 4), evaluate=False).n()) < 1e-16 + + # negative integer power and negative rational base + assert Rational(-2, 3) ** Rational(-2, 1) == Rational(9, 4) + + a = Rational(1, 10) + assert a**Float(a, 2) == Float(a, 2)**Float(a, 2) + assert Rational(-2, 3)**Symbol('', even=True) == \ + Rational(2, 3)**Symbol('', even=True) + + +def test_powers_Float(): + assert str((S('-1/10')**S('3/10')).n()) == str(Float(-.1)**(.3)) + + +def test_lshift_Integer(): + assert Integer(0) << Integer(2) == Integer(0) + assert Integer(0) << 2 == Integer(0) + assert 0 << Integer(2) == Integer(0) + + assert Integer(0b11) << Integer(0) == Integer(0b11) + assert Integer(0b11) << 0 == Integer(0b11) + assert 0b11 << Integer(0) == Integer(0b11) + + assert Integer(0b11) << Integer(2) == Integer(0b11 << 2) + assert Integer(0b11) << 2 == Integer(0b11 << 2) + assert 0b11 << Integer(2) == Integer(0b11 << 2) + + assert Integer(-0b11) << Integer(2) == Integer(-0b11 << 2) + assert Integer(-0b11) << 2 == Integer(-0b11 << 2) + assert -0b11 << Integer(2) == Integer(-0b11 << 2) + + raises(TypeError, lambda: Integer(2) << 0.0) + raises(TypeError, lambda: 0.0 << Integer(2)) + raises(ValueError, lambda: Integer(1) << Integer(-1)) + + +def test_rshift_Integer(): + assert Integer(0) >> Integer(2) == Integer(0) + assert Integer(0) >> 2 == Integer(0) + assert 0 >> Integer(2) == Integer(0) + + assert Integer(0b11) >> Integer(0) == Integer(0b11) + assert Integer(0b11) >> 0 == Integer(0b11) + assert 0b11 >> Integer(0) == Integer(0b11) + + assert Integer(0b11) >> Integer(2) == Integer(0) + assert Integer(0b11) >> 2 == Integer(0) + assert 0b11 >> Integer(2) == Integer(0) + + assert Integer(-0b11) >> Integer(2) == Integer(-1) + assert Integer(-0b11) >> 2 == Integer(-1) + assert -0b11 >> Integer(2) == Integer(-1) + + assert Integer(0b1100) >> Integer(2) == Integer(0b1100 >> 2) + assert Integer(0b1100) >> 2 == Integer(0b1100 >> 2) + assert 0b1100 >> Integer(2) == Integer(0b1100 >> 2) + + assert Integer(-0b1100) >> Integer(2) == Integer(-0b1100 >> 2) + assert Integer(-0b1100) >> 2 == Integer(-0b1100 >> 2) + assert -0b1100 >> Integer(2) == Integer(-0b1100 >> 2) + + raises(TypeError, lambda: Integer(0b10) >> 0.0) + raises(TypeError, lambda: 0.0 >> Integer(2)) + raises(ValueError, lambda: Integer(1) >> Integer(-1)) + + +def test_and_Integer(): + assert Integer(0b01010101) & Integer(0b10101010) == Integer(0) + assert Integer(0b01010101) & 0b10101010 == Integer(0) + assert 0b01010101 & Integer(0b10101010) == Integer(0) + + assert Integer(0b01010101) & Integer(0b11011011) == Integer(0b01010001) + assert Integer(0b01010101) & 0b11011011 == Integer(0b01010001) + assert 0b01010101 & Integer(0b11011011) == Integer(0b01010001) + + assert -Integer(0b01010101) & Integer(0b11011011) == Integer(-0b01010101 & 0b11011011) + assert Integer(-0b01010101) & 0b11011011 == Integer(-0b01010101 & 0b11011011) + assert -0b01010101 & Integer(0b11011011) == Integer(-0b01010101 & 0b11011011) + + assert Integer(0b01010101) & -Integer(0b11011011) == Integer(0b01010101 & -0b11011011) + assert Integer(0b01010101) & -0b11011011 == Integer(0b01010101 & -0b11011011) + assert 0b01010101 & Integer(-0b11011011) == Integer(0b01010101 & -0b11011011) + + raises(TypeError, lambda: Integer(2) & 0.0) + raises(TypeError, lambda: 0.0 & Integer(2)) + + +def test_xor_Integer(): + assert Integer(0b01010101) ^ Integer(0b11111111) == Integer(0b10101010) + assert Integer(0b01010101) ^ 0b11111111 == Integer(0b10101010) + assert 0b01010101 ^ Integer(0b11111111) == Integer(0b10101010) + + assert Integer(0b01010101) ^ Integer(0b11011011) == Integer(0b10001110) + assert Integer(0b01010101) ^ 0b11011011 == Integer(0b10001110) + assert 0b01010101 ^ Integer(0b11011011) == Integer(0b10001110) + + assert -Integer(0b01010101) ^ Integer(0b11011011) == Integer(-0b01010101 ^ 0b11011011) + assert Integer(-0b01010101) ^ 0b11011011 == Integer(-0b01010101 ^ 0b11011011) + assert -0b01010101 ^ Integer(0b11011011) == Integer(-0b01010101 ^ 0b11011011) + + assert Integer(0b01010101) ^ -Integer(0b11011011) == Integer(0b01010101 ^ -0b11011011) + assert Integer(0b01010101) ^ -0b11011011 == Integer(0b01010101 ^ -0b11011011) + assert 0b01010101 ^ Integer(-0b11011011) == Integer(0b01010101 ^ -0b11011011) + + raises(TypeError, lambda: Integer(2) ^ 0.0) + raises(TypeError, lambda: 0.0 ^ Integer(2)) + + +def test_or_Integer(): + assert Integer(0b01010101) | Integer(0b10101010) == Integer(0b11111111) + assert Integer(0b01010101) | 0b10101010 == Integer(0b11111111) + assert 0b01010101 | Integer(0b10101010) == Integer(0b11111111) + + assert Integer(0b01010101) | Integer(0b11011011) == Integer(0b11011111) + assert Integer(0b01010101) | 0b11011011 == Integer(0b11011111) + assert 0b01010101 | Integer(0b11011011) == Integer(0b11011111) + + assert -Integer(0b01010101) | Integer(0b11011011) == Integer(-0b01010101 | 0b11011011) + assert Integer(-0b01010101) | 0b11011011 == Integer(-0b01010101 | 0b11011011) + assert -0b01010101 | Integer(0b11011011) == Integer(-0b01010101 | 0b11011011) + + assert Integer(0b01010101) | -Integer(0b11011011) == Integer(0b01010101 | -0b11011011) + assert Integer(0b01010101) | -0b11011011 == Integer(0b01010101 | -0b11011011) + assert 0b01010101 | Integer(-0b11011011) == Integer(0b01010101 | -0b11011011) + + raises(TypeError, lambda: Integer(2) | 0.0) + raises(TypeError, lambda: 0.0 | Integer(2)) + + +def test_invert_Integer(): + assert ~Integer(0b01010101) == Integer(-0b01010110) + assert ~Integer(0b01010101) == Integer(~0b01010101) + assert ~(~Integer(0b01010101)) == Integer(0b01010101) + + +def test_abs1(): + assert Rational(1, 6) != Rational(-1, 6) + assert abs(Rational(1, 6)) == abs(Rational(-1, 6)) + + +def test_accept_int(): + assert not Float(4) == 4 + assert Float(4) != 4 + assert Float(4) == 4.0 + + +def test_dont_accept_str(): + assert Float("0.2") != "0.2" + assert not (Float("0.2") == "0.2") + + +def test_int(): + a = Rational(5) + assert int(a) == 5 + a = Rational(9, 10) + assert int(a) == int(-a) == 0 + assert 1/(-1)**Rational(2, 3) == -(-1)**Rational(1, 3) + # issue 10368 + a = Rational(32442016954, 78058255275) + assert type(int(a)) is type(int(-a)) is int + + +def test_int_NumberSymbols(): + assert int(Catalan) == 0 + assert int(EulerGamma) == 0 + assert int(pi) == 3 + assert int(E) == 2 + assert int(GoldenRatio) == 1 + assert int(TribonacciConstant) == 1 + for i in [Catalan, E, EulerGamma, GoldenRatio, TribonacciConstant, pi]: + a, b = i.approximation_interval(Integer) + ia = int(i) + assert ia == a + assert isinstance(ia, int) + assert b == a + 1 + assert a.is_Integer and b.is_Integer + + +def test_real_bug(): + x = Symbol("x") + assert str(2.0*x*x) in ["(2.0*x)*x", "2.0*x**2", "2.00000000000000*x**2"] + assert str(2.1*x*x) != "(2.0*x)*x" + + +def test_bug_sqrt(): + assert ((sqrt(Rational(2)) + 1)*(sqrt(Rational(2)) - 1)).expand() == 1 + + +def test_pi_Pi(): + "Test that pi (instance) is imported, but Pi (class) is not" + from sympy import pi # noqa + with raises(ImportError): + from sympy import Pi # noqa + + +def test_no_len(): + # there should be no len for numbers + raises(TypeError, lambda: len(Rational(2))) + raises(TypeError, lambda: len(Rational(2, 3))) + raises(TypeError, lambda: len(Integer(2))) + + +def test_issue_3321(): + assert sqrt(Rational(1, 5)) == Rational(1, 5)**S.Half + assert 5 * sqrt(Rational(1, 5)) == sqrt(5) + + +def test_issue_3692(): + assert ((-1)**Rational(1, 6)).expand(complex=True) == I/2 + sqrt(3)/2 + assert ((-5)**Rational(1, 6)).expand(complex=True) == \ + 5**Rational(1, 6)*I/2 + 5**Rational(1, 6)*sqrt(3)/2 + assert ((-64)**Rational(1, 6)).expand(complex=True) == I + sqrt(3) + + +def test_issue_3423(): + x = Symbol("x") + assert sqrt(x - 1).as_base_exp() == (x - 1, S.Half) + assert sqrt(x - 1) != I*sqrt(1 - x) + + +def test_issue_3449(): + x = Symbol("x") + assert sqrt(x - 1).subs(x, 5) == 2 + + +def test_issue_13890(): + x = Symbol("x") + e = (-x/4 - S.One/12)**x - 1 + f = simplify(e) + a = Rational(9, 5) + assert abs(e.subs(x,a).evalf() - f.subs(x,a).evalf()) < 1e-15 + + +def test_Integer_factors(): + def F(i): + return Integer(i).factors() + + assert F(1) == {} + assert F(2) == {2: 1} + assert F(3) == {3: 1} + assert F(4) == {2: 2} + assert F(5) == {5: 1} + assert F(6) == {2: 1, 3: 1} + assert F(7) == {7: 1} + assert F(8) == {2: 3} + assert F(9) == {3: 2} + assert F(10) == {2: 1, 5: 1} + assert F(11) == {11: 1} + assert F(12) == {2: 2, 3: 1} + assert F(13) == {13: 1} + assert F(14) == {2: 1, 7: 1} + assert F(15) == {3: 1, 5: 1} + assert F(16) == {2: 4} + assert F(17) == {17: 1} + assert F(18) == {2: 1, 3: 2} + assert F(19) == {19: 1} + assert F(20) == {2: 2, 5: 1} + assert F(21) == {3: 1, 7: 1} + assert F(22) == {2: 1, 11: 1} + assert F(23) == {23: 1} + assert F(24) == {2: 3, 3: 1} + assert F(25) == {5: 2} + assert F(26) == {2: 1, 13: 1} + assert F(27) == {3: 3} + assert F(28) == {2: 2, 7: 1} + assert F(29) == {29: 1} + assert F(30) == {2: 1, 3: 1, 5: 1} + assert F(31) == {31: 1} + assert F(32) == {2: 5} + assert F(33) == {3: 1, 11: 1} + assert F(34) == {2: 1, 17: 1} + assert F(35) == {5: 1, 7: 1} + assert F(36) == {2: 2, 3: 2} + assert F(37) == {37: 1} + assert F(38) == {2: 1, 19: 1} + assert F(39) == {3: 1, 13: 1} + assert F(40) == {2: 3, 5: 1} + assert F(41) == {41: 1} + assert F(42) == {2: 1, 3: 1, 7: 1} + assert F(43) == {43: 1} + assert F(44) == {2: 2, 11: 1} + assert F(45) == {3: 2, 5: 1} + assert F(46) == {2: 1, 23: 1} + assert F(47) == {47: 1} + assert F(48) == {2: 4, 3: 1} + assert F(49) == {7: 2} + assert F(50) == {2: 1, 5: 2} + assert F(51) == {3: 1, 17: 1} + + +def test_Rational_factors(): + def F(p, q, visual=None): + return Rational(p, q).factors(visual=visual) + + assert F(2, 3) == {2: 1, 3: -1} + assert F(2, 9) == {2: 1, 3: -2} + assert F(2, 15) == {2: 1, 3: -1, 5: -1} + assert F(6, 10) == {3: 1, 5: -1} + + +def test_issue_4107(): + assert pi*(E + 10) + pi*(-E - 10) != 0 + assert pi*(E + 10**10) + pi*(-E - 10**10) != 0 + assert pi*(E + 10**20) + pi*(-E - 10**20) != 0 + assert pi*(E + 10**80) + pi*(-E - 10**80) != 0 + + assert (pi*(E + 10) + pi*(-E - 10)).expand() == 0 + assert (pi*(E + 10**10) + pi*(-E - 10**10)).expand() == 0 + assert (pi*(E + 10**20) + pi*(-E - 10**20)).expand() == 0 + assert (pi*(E + 10**80) + pi*(-E - 10**80)).expand() == 0 + + +def test_IntegerInteger(): + a = Integer(4) + b = Integer(a) + + assert a == b + + +def test_Rational_gcd_lcm_cofactors(): + assert Integer(4).gcd(2) == Integer(2) + assert Integer(4).lcm(2) == Integer(4) + assert Integer(4).gcd(Integer(2)) == Integer(2) + assert Integer(4).lcm(Integer(2)) == Integer(4) + a, b = 720**99911, 480**12342 + assert Integer(a).lcm(b) == a*b/Integer(a).gcd(b) + + assert Integer(4).gcd(3) == Integer(1) + assert Integer(4).lcm(3) == Integer(12) + assert Integer(4).gcd(Integer(3)) == Integer(1) + assert Integer(4).lcm(Integer(3)) == Integer(12) + + assert Rational(4, 3).gcd(2) == Rational(2, 3) + assert Rational(4, 3).lcm(2) == Integer(4) + assert Rational(4, 3).gcd(Integer(2)) == Rational(2, 3) + assert Rational(4, 3).lcm(Integer(2)) == Integer(4) + + assert Integer(4).gcd(Rational(2, 9)) == Rational(2, 9) + assert Integer(4).lcm(Rational(2, 9)) == Integer(4) + + assert Rational(4, 3).gcd(Rational(2, 9)) == Rational(2, 9) + assert Rational(4, 3).lcm(Rational(2, 9)) == Rational(4, 3) + assert Rational(4, 5).gcd(Rational(2, 9)) == Rational(2, 45) + assert Rational(4, 5).lcm(Rational(2, 9)) == Integer(4) + assert Rational(5, 9).lcm(Rational(3, 7)) == Rational(Integer(5).lcm(3),Integer(9).gcd(7)) + + assert Integer(4).cofactors(2) == (Integer(2), Integer(2), Integer(1)) + assert Integer(4).cofactors(Integer(2)) == \ + (Integer(2), Integer(2), Integer(1)) + + assert Integer(4).gcd(Float(2.0)) == Float(1.0) + assert Integer(4).lcm(Float(2.0)) == Float(8.0) + assert Integer(4).cofactors(Float(2.0)) == (Float(1.0), Float(4.0), Float(2.0)) + + assert S.Half.gcd(Float(2.0)) == Float(1.0) + assert S.Half.lcm(Float(2.0)) == Float(1.0) + assert S.Half.cofactors(Float(2.0)) == \ + (Float(1.0), Float(0.5), Float(2.0)) + + +def test_Float_gcd_lcm_cofactors(): + assert Float(2.0).gcd(Integer(4)) == Float(1.0) + assert Float(2.0).lcm(Integer(4)) == Float(8.0) + assert Float(2.0).cofactors(Integer(4)) == (Float(1.0), Float(2.0), Float(4.0)) + + assert Float(2.0).gcd(S.Half) == Float(1.0) + assert Float(2.0).lcm(S.Half) == Float(1.0) + assert Float(2.0).cofactors(S.Half) == \ + (Float(1.0), Float(2.0), Float(0.5)) + + +def test_issue_4611(): + assert abs(pi._evalf(50) - 3.14159265358979) < 1e-10 + assert abs(E._evalf(50) - 2.71828182845905) < 1e-10 + assert abs(Catalan._evalf(50) - 0.915965594177219) < 1e-10 + assert abs(EulerGamma._evalf(50) - 0.577215664901533) < 1e-10 + assert abs(GoldenRatio._evalf(50) - 1.61803398874989) < 1e-10 + assert abs(TribonacciConstant._evalf(50) - 1.83928675521416) < 1e-10 + + x = Symbol("x") + assert (pi + x).evalf() == pi.evalf() + x + assert (E + x).evalf() == E.evalf() + x + assert (Catalan + x).evalf() == Catalan.evalf() + x + assert (EulerGamma + x).evalf() == EulerGamma.evalf() + x + assert (GoldenRatio + x).evalf() == GoldenRatio.evalf() + x + assert (TribonacciConstant + x).evalf() == TribonacciConstant.evalf() + x + + +@conserve_mpmath_dps +def test_conversion_to_mpmath(): + assert mpmath.mpmathify(Integer(1)) == mpmath.mpf(1) + assert mpmath.mpmathify(S.Half) == mpmath.mpf(0.5) + assert mpmath.mpmathify(Float('1.23', 15)) == mpmath.mpf('1.23') + + assert mpmath.mpmathify(I) == mpmath.mpc(1j) + + assert mpmath.mpmathify(1 + 2*I) == mpmath.mpc(1 + 2j) + assert mpmath.mpmathify(1.0 + 2*I) == mpmath.mpc(1 + 2j) + assert mpmath.mpmathify(1 + 2.0*I) == mpmath.mpc(1 + 2j) + assert mpmath.mpmathify(1.0 + 2.0*I) == mpmath.mpc(1 + 2j) + assert mpmath.mpmathify(S.Half + S.Half*I) == mpmath.mpc(0.5 + 0.5j) + + assert mpmath.mpmathify(2*I) == mpmath.mpc(2j) + assert mpmath.mpmathify(2.0*I) == mpmath.mpc(2j) + assert mpmath.mpmathify(S.Half*I) == mpmath.mpc(0.5j) + + mpmath.mp.dps = 100 + assert mpmath.mpmathify(pi.evalf(100) + pi.evalf(100)*I) == mpmath.pi + mpmath.pi*mpmath.j + assert mpmath.mpmathify(pi.evalf(100)*I) == mpmath.pi*mpmath.j + + +def test_relational(): + # real + x = S(.1) + assert (x != cos) is True + assert (x == cos) is False + + # rational + x = Rational(1, 3) + assert (x != cos) is True + assert (x == cos) is False + + # integer defers to rational so these tests are omitted + + # number symbol + x = pi + assert (x != cos) is True + assert (x == cos) is False + + +def test_Integer_as_index(): + assert 'hello'[Integer(2):] == 'llo' + + +def test_Rational_int(): + assert int( Rational(7, 5)) == 1 + assert int( S.Half) == 0 + assert int(Rational(-1, 2)) == 0 + assert int(-Rational(7, 5)) == -1 + + +def test_zoo(): + b = Symbol('b', finite=True) + nz = Symbol('nz', nonzero=True) + p = Symbol('p', positive=True) + n = Symbol('n', negative=True) + im = Symbol('i', imaginary=True) + c = Symbol('c', complex=True) + pb = Symbol('pb', positive=True) + nb = Symbol('nb', negative=True) + imb = Symbol('ib', imaginary=True, finite=True) + for i in [I, S.Infinity, S.NegativeInfinity, S.Zero, S.One, S.Pi, S.Half, S(3), log(3), + b, nz, p, n, im, pb, nb, imb, c]: + if i.is_finite and (i.is_real or i.is_imaginary): + assert i + zoo is zoo + assert i - zoo is zoo + assert zoo + i is zoo + assert zoo - i is zoo + elif i.is_finite is not False: + assert (i + zoo).is_Add + assert (i - zoo).is_Add + assert (zoo + i).is_Add + assert (zoo - i).is_Add + else: + assert (i + zoo) is S.NaN + assert (i - zoo) is S.NaN + assert (zoo + i) is S.NaN + assert (zoo - i) is S.NaN + + if fuzzy_not(i.is_zero) and (i.is_extended_real or i.is_imaginary): + assert i*zoo is zoo + assert zoo*i is zoo + elif i.is_zero: + assert i*zoo is S.NaN + assert zoo*i is S.NaN + else: + assert (i*zoo).is_Mul + assert (zoo*i).is_Mul + + if fuzzy_not((1/i).is_zero) and (i.is_real or i.is_imaginary): + assert zoo/i is zoo + elif (1/i).is_zero: + assert zoo/i is S.NaN + elif i.is_zero: + assert zoo/i is zoo + else: + assert (zoo/i).is_Mul + + assert (I*oo).is_Mul # allow directed infinity + assert zoo + zoo is S.NaN + assert zoo * zoo is zoo + assert zoo - zoo is S.NaN + assert zoo/zoo is S.NaN + assert zoo**zoo is S.NaN + assert zoo**0 is S.One + assert zoo**2 is zoo + assert 1/zoo is S.Zero + + assert Mul.flatten([S.NegativeOne, oo, S(0)]) == ([S.NaN], [], None) + + +def test_issue_4122(): + x = Symbol('x', nonpositive=True) + assert oo + x is oo + x = Symbol('x', extended_nonpositive=True) + assert (oo + x).is_Add + x = Symbol('x', finite=True) + assert (oo + x).is_Add # x could be imaginary + x = Symbol('x', nonnegative=True) + assert oo + x is oo + x = Symbol('x', extended_nonnegative=True) + assert oo + x is oo + x = Symbol('x', finite=True, real=True) + assert oo + x is oo + + # similarly for negative infinity + x = Symbol('x', nonnegative=True) + assert -oo + x is -oo + x = Symbol('x', extended_nonnegative=True) + assert (-oo + x).is_Add + x = Symbol('x', finite=True) + assert (-oo + x).is_Add + x = Symbol('x', nonpositive=True) + assert -oo + x is -oo + x = Symbol('x', extended_nonpositive=True) + assert -oo + x is -oo + x = Symbol('x', finite=True, real=True) + assert -oo + x is -oo + + +def test_GoldenRatio_expand(): + assert GoldenRatio.expand(func=True) == S.Half + sqrt(5)/2 + + +def test_TribonacciConstant_expand(): + assert TribonacciConstant.expand(func=True) == \ + (1 + cbrt(19 - 3*sqrt(33)) + cbrt(19 + 3*sqrt(33))) / 3 + + +def test_as_content_primitive(): + assert S.Zero.as_content_primitive() == (1, 0) + assert S.Half.as_content_primitive() == (S.Half, 1) + assert (Rational(-1, 2)).as_content_primitive() == (S.Half, -1) + assert S(3).as_content_primitive() == (3, 1) + assert S(3.1).as_content_primitive() == (1, 3.1) + + +def test_hashing_sympy_integers(): + # Test for issue 5072 + assert {Integer(3)} == {int(3)} + assert hash(Integer(4)) == hash(int(4)) + + +def test_rounding_issue_4172(): + assert int((E**100).round()) == \ + 26881171418161354484126255515800135873611119 + assert int((pi**100).round()) == \ + 51878483143196131920862615246303013562686760680406 + assert int((Rational(1)/EulerGamma**100).round()) == \ + 734833795660954410469466 + + +@XFAIL +def test_mpmath_issues(): + from mpmath.libmp.libmpf import _normalize + import mpmath.libmp as mlib + rnd = mlib.round_nearest + mpf = (0, int(0), -123, -1, 53, rnd) # nan + assert _normalize(mpf, 53) != (0, int(0), 0, 0) + mpf = (0, int(0), -456, -2, 53, rnd) # +inf + assert _normalize(mpf, 53) != (0, int(0), 0, 0) + mpf = (1, int(0), -789, -3, 53, rnd) # -inf + assert _normalize(mpf, 53) != (0, int(0), 0, 0) + + from mpmath.libmp.libmpf import fnan + assert mlib.mpf_eq(fnan, fnan) + + +def test_Catalan_EulerGamma_prec(): + n = GoldenRatio + f = Float(n.n(), 5) + assert f._mpf_ == (0, int(212079), -17, 18) + assert f._prec == 20 + assert n._as_mpf_val(20) == f._mpf_ + + n = EulerGamma + f = Float(n.n(), 5) + assert f._mpf_ == (0, int(302627), -19, 19) + assert f._prec == 20 + assert n._as_mpf_val(20) == f._mpf_ + + +def test_Catalan_rewrite(): + k = Dummy('k', integer=True, nonnegative=True) + assert Catalan.rewrite(Sum).dummy_eq( + Sum((-1)**k/(2*k + 1)**2, (k, 0, oo))) + assert Catalan.rewrite() == Catalan + + +def test_bool_eq(): + assert 0 == False + assert S(0) == False + assert S(0) != S.false + assert 1 == True + assert S.One == True + assert S.One != S.true + + +def test_Float_eq(): + # Floats with different precision should not compare equal + assert Float(.5, 10) != Float(.5, 11) != Float(.5, 1) + # but floats that aren't exact in base-2 still + # don't compare the same because they have different + # underlying mpf values + assert Float(.12, 3) != Float(.12, 4) + assert Float(.12, 3) != .12 + assert 0.12 != Float(.12, 3) + assert Float('.12', 22) != .12 + # issue 11707 + # but Float/Rational -- except for 0 -- + # are exact so Rational(x) = Float(y) only if + # Rational(x) == Rational(Float(y)) + assert Float('1.1') != Rational(11, 10) + assert Rational(11, 10) != Float('1.1') + # coverage + assert not Float(3) == 2 + assert not Float(3) == Float(2) + assert not Float(3) == 3 + assert not Float(2**2) == S.Half + assert Float(2**2) == 4.0 + assert not Float(2**-2) == 1 + assert Float(2**-1) == 0.5 + assert not Float(2*3) == 3 + assert not Float(2*3) == 0.5 + assert Float(2*3) == 6.0 + assert not Float(2*3) == 6 + assert not Float(2*3) == 8 + assert not Float(.75) == Rational(3, 4) + assert Float(.75) == 0.75 + assert Float(5/18) == 5/18 + # 4473 + assert Float(2.) != 3 + assert not Float((0,1,-3)) == S.One/8 + assert Float((0,1,-3)) == 1/8 + assert Float((0,1,-3)) != S.One/9 + # 16196 + assert not 2 == Float(2) # unlike Python + assert t**2 != t**2.0 + + +def test_issue_6640(): + from mpmath.libmp.libmpf import finf, fninf + # fnan is not included because Float no longer returns fnan, + # but otherwise, the same sort of test could apply + assert Float(finf).is_zero is False + assert Float(fninf).is_zero is False + assert bool(Float(0)) is False + + +def test_issue_6349(): + assert Float('23.e3', '')._prec == 10 + assert Float('23e3', '')._prec == 20 + assert Float('23000', '')._prec == 20 + assert Float('-23000', '')._prec == 20 + + +def test_mpf_norm(): + assert mpf_norm((1, 0, 1, 0), 10) == mpf('0')._mpf_ + assert Float._new((1, 0, 1, 0), 10)._mpf_ == mpf('0')._mpf_ + + +def test_latex(): + assert latex(pi) == r"\pi" + assert latex(E) == r"e" + assert latex(GoldenRatio) == r"\phi" + assert latex(TribonacciConstant) == r"\text{TribonacciConstant}" + assert latex(EulerGamma) == r"\gamma" + assert latex(oo) == r"\infty" + assert latex(-oo) == r"-\infty" + assert latex(zoo) == r"\tilde{\infty}" + assert latex(nan) == r"\text{NaN}" + assert latex(I) == r"i" + + +def test_issue_7742(): + assert -oo % 1 is nan + + +def test_simplify_AlgebraicNumber(): + A = AlgebraicNumber + e = 3**(S.One/6)*(3 + (135 + 78*sqrt(3))**Rational(2, 3))/(45 + 26*sqrt(3))**(S.One/3) + assert simplify(A(e)) == A(12) # wester test_C20 + + e = (41 + 29*sqrt(2))**(S.One/5) + assert simplify(A(e)) == A(1 + sqrt(2)) # wester test_C21 + + e = (3 + 4*I)**Rational(3, 2) + assert simplify(A(e)) == A(2 + 11*I) # issue 4401 + + +def test_Float_idempotence(): + x = Float('1.23', '') + y = Float(x) + z = Float(x, 15) + assert same_and_same_prec(y, x) + assert not same_and_same_prec(z, x) + x = Float(10**20) + y = Float(x) + z = Float(x, 15) + assert same_and_same_prec(y, x) + assert not same_and_same_prec(z, x) + + +def test_comp1(): + # sqrt(2) = 1.414213 5623730950... + a = sqrt(2).n(7) + assert comp(a, 1.4142129) is False + assert comp(a, 1.4142130) + # ... + assert comp(a, 1.4142141) + assert comp(a, 1.4142142) is False + assert comp(sqrt(2).n(2), '1.4') + assert comp(sqrt(2).n(2), Float(1.4, 2), '') + assert comp(sqrt(2).n(2), 1.4, '') + assert comp(sqrt(2).n(2), Float(1.4, 3), '') is False + assert comp(sqrt(2) + sqrt(3)*I, 1.4 + 1.7*I, .1) + assert not comp(sqrt(2) + sqrt(3)*I, (1.5 + 1.7*I)*0.89, .1) + assert comp(sqrt(2) + sqrt(3)*I, (1.5 + 1.7*I)*0.90, .1) + assert comp(sqrt(2) + sqrt(3)*I, (1.5 + 1.7*I)*1.07, .1) + assert not comp(sqrt(2) + sqrt(3)*I, (1.5 + 1.7*I)*1.08, .1) + assert [(i, j) + for i in range(130, 150) + for j in range(170, 180) + if comp((sqrt(2)+ I*sqrt(3)).n(3), i/100. + I*j/100.)] == [ + (141, 173), (142, 173)] + raises(ValueError, lambda: comp(t, '1')) + raises(ValueError, lambda: comp(t, 1)) + assert comp(0, 0.0) + assert comp(.5, S.Half) + assert comp(2 + sqrt(2), 2.0 + sqrt(2)) + assert not comp(0, 1) + assert not comp(2, sqrt(2)) + assert not comp(2 + I, 2.0 + sqrt(2)) + assert not comp(2.0 + sqrt(2), 2 + I) + assert not comp(2.0 + sqrt(2), sqrt(3)) + assert comp(1/pi.n(4), 0.3183, 1e-5) + assert not comp(1/pi.n(4), 0.3183, 8e-6) + + +def test_issue_9491(): + assert oo**zoo is nan + + +def test_issue_10063(): + assert 2**Float(3) == Float(8) + + +def test_issue_10020(): + assert oo**I is S.NaN + assert oo**(1 + I) is S.ComplexInfinity + assert oo**(-1 + I) is S.Zero + assert (-oo)**I is S.NaN + assert (-oo)**(-1 + I) is S.Zero + assert oo**t == Pow(oo, t, evaluate=False) + assert (-oo)**t == Pow(-oo, t, evaluate=False) + + +def test_invert_numbers(): + assert S(2).invert(5) == 3 + assert S(2).invert(Rational(5, 2)) == S.Half + assert S(2).invert(5.) == S.Half + assert S(2).invert(S(5)) == 3 + assert S(2.).invert(5) == 0.5 + assert S(sqrt(2)).invert(5) == 1/sqrt(2) + assert S(sqrt(2)).invert(sqrt(3)) == 1/sqrt(2) + + +def test_mod_inverse(): + assert mod_inverse(3, 11) == 4 + assert mod_inverse(5, 11) == 9 + assert mod_inverse(21124921, 521512) == 7713 + assert mod_inverse(124215421, 5125) == 2981 + assert mod_inverse(214, 12515) == 1579 + assert mod_inverse(5823991, 3299) == 1442 + assert mod_inverse(123, 44) == 39 + assert mod_inverse(2, 5) == 3 + assert mod_inverse(-2, 5) == 2 + assert mod_inverse(2, -5) == -2 + assert mod_inverse(-2, -5) == -3 + assert mod_inverse(-3, -7) == -5 + x = Symbol('x') + assert S(2).invert(x) == S.Half + raises(TypeError, lambda: mod_inverse(2, x)) + raises(ValueError, lambda: mod_inverse(2, S.Half)) + raises(ValueError, lambda: mod_inverse(2, cos(1)**2 + sin(1)**2)) + + +def test_golden_ratio_rewrite_as_sqrt(): + assert GoldenRatio.rewrite(sqrt) == S.Half + sqrt(5)*S.Half + + +def test_tribonacci_constant_rewrite_as_sqrt(): + assert TribonacciConstant.rewrite(sqrt) == \ + (1 + cbrt(19 - 3*sqrt(33)) + cbrt(19 + 3*sqrt(33))) / 3 + + +def test_comparisons_with_unknown_type(): + class Foo: + """ + Class that is unaware of Basic, and relies on both classes returning + the NotImplemented singleton for equivalence to evaluate to False. + + """ + + ni, nf, nr = Integer(3), Float(1.0), Rational(1, 3) + foo = Foo() + + for n in ni, nf, nr, oo, -oo, zoo, nan: + assert n != foo + assert foo != n + assert not n == foo + assert not foo == n + raises(TypeError, lambda: n < foo) + raises(TypeError, lambda: foo > n) + raises(TypeError, lambda: n > foo) + raises(TypeError, lambda: foo < n) + raises(TypeError, lambda: n <= foo) + raises(TypeError, lambda: foo >= n) + raises(TypeError, lambda: n >= foo) + raises(TypeError, lambda: foo <= n) + + class Bar: + """ + Class that considers itself equal to any instance of Number except + infinities and nans, and relies on SymPy types returning the + NotImplemented singleton for symmetric equality relations. + + """ + def __eq__(self, other): + if other in (oo, -oo, zoo, nan): + return False + if isinstance(other, Number): + return True + return NotImplemented + + def __ne__(self, other): + return not self == other + + bar = Bar() + + for n in ni, nf, nr: + assert n == bar + assert bar == n + assert not n != bar + assert not bar != n + + for n in oo, -oo, zoo, nan: + assert n != bar + assert bar != n + assert not n == bar + assert not bar == n + + for n in ni, nf, nr, oo, -oo, zoo, nan: + raises(TypeError, lambda: n < bar) + raises(TypeError, lambda: bar > n) + raises(TypeError, lambda: n > bar) + raises(TypeError, lambda: bar < n) + raises(TypeError, lambda: n <= bar) + raises(TypeError, lambda: bar >= n) + raises(TypeError, lambda: n >= bar) + raises(TypeError, lambda: bar <= n) + + +def test_NumberSymbol_comparison(): + from sympy.core.tests.test_relational import rel_check + rpi = Rational('905502432259640373/288230376151711744') + fpi = Float(float(pi)) + assert rel_check(rpi, fpi) + + +def test_Integer_precision(): + # Make sure Integer inputs for keyword args work + assert Float('1.0', dps=Integer(15))._prec == 53 + assert Float('1.0', precision=Integer(15))._prec == 15 + assert type(Float('1.0', precision=Integer(15))._prec) == int + assert sympify(srepr(Float('1.0', precision=15))) == Float('1.0', precision=15) + + +def test_numpy_to_float(): + from sympy.testing.pytest import skip + from sympy.external import import_module + np = import_module('numpy') + if not np: + skip('numpy not installed. Abort numpy tests.') + + def check_prec_and_relerr(npval, ratval): + prec = np.finfo(npval).nmant + 1 + x = Float(npval) + assert x._prec == prec + y = Float(ratval, precision=prec) + assert abs((x - y)/y) < 2**(-(prec + 1)) + + check_prec_and_relerr(np.float16(2.0/3), Rational(2, 3)) + check_prec_and_relerr(np.float32(2.0/3), Rational(2, 3)) + check_prec_and_relerr(np.float64(2.0/3), Rational(2, 3)) + # extended precision, on some arch/compilers: + x = np.longdouble(2)/3 + check_prec_and_relerr(x, Rational(2, 3)) + y = Float(x, precision=10) + assert same_and_same_prec(y, Float(Rational(2, 3), precision=10)) + + raises(TypeError, lambda: Float(np.complex64(1+2j))) + raises(TypeError, lambda: Float(np.complex128(1+2j))) + + +def test_Integer_ceiling_floor(): + a = Integer(4) + + assert a.floor() == a + assert a.ceiling() == a + + +def test_ComplexInfinity(): + assert zoo.floor() is zoo + assert zoo.ceiling() is zoo + assert zoo**zoo is S.NaN + + +def test_Infinity_floor_ceiling_power(): + assert oo.floor() is oo + assert oo.ceiling() is oo + assert oo**S.NaN is S.NaN + assert oo**zoo is S.NaN + + +def test_One_power(): + assert S.One**12 is S.One + assert S.NegativeOne**S.NaN is S.NaN + + +def test_NegativeInfinity(): + assert (-oo).floor() is -oo + assert (-oo).ceiling() is -oo + assert (-oo)**11 is -oo + assert (-oo)**12 is oo + + +def test_issue_6133(): + raises(TypeError, lambda: (-oo < None)) + raises(TypeError, lambda: (S(-2) < None)) + raises(TypeError, lambda: (oo < None)) + raises(TypeError, lambda: (oo > None)) + raises(TypeError, lambda: (S(2) < None)) + + +def test_abc(): + x = numbers.Float(5) + assert(isinstance(x, nums.Number)) + assert(isinstance(x, numbers.Number)) + assert(isinstance(x, nums.Real)) + y = numbers.Rational(1, 3) + assert(isinstance(y, nums.Number)) + assert(y.numerator == 1) + assert(y.denominator == 3) + assert(isinstance(y, nums.Rational)) + z = numbers.Integer(3) + assert(isinstance(z, nums.Number)) + assert(isinstance(z, numbers.Number)) + assert(isinstance(z, nums.Rational)) + assert(isinstance(z, numbers.Rational)) + assert(isinstance(z, nums.Integral)) + + +def test_floordiv(): + assert S(2)//S.Half == 4 + + +def test_negation(): + assert -S.Zero is S.Zero + assert -Float(0) is not S.Zero and -Float(0) == 0.0 + + +def test_exponentiation_of_0(): + x = Symbol('x') + assert 0**-x == zoo**x + assert unchanged(Pow, 0, x) + x = Symbol('x', zero=True) + assert 0**-x == S.One + assert 0**x == S.One + + +def test_int_valued(): + x = Symbol('x') + assert int_valued(x) == False + assert int_valued(S.Half) == False + assert int_valued(S.One) == True + assert int_valued(Float(1)) == True + assert int_valued(Float(1.1)) == False + assert int_valued(pi) == False + + +def test_equal_valued(): + x = Symbol('x') + + equal_values = [ + [1, 1.0, S(1), S(1.0), S(1).n(5)], + [2, 2.0, S(2), S(2.0), S(2).n(5)], + [-1, -1.0, -S(1), -S(1.0), -S(1).n(5)], + [0.5, S(0.5), S(1)/2], + [-0.5, -S(0.5), -S(1)/2], + [0, 0.0, S(0), S(0.0), S(0).n()], + [pi], [pi.n()], # <-- not equal + [S(1)/10], [0.1, S(0.1)], # <-- not equal + [S(0.1).n(5)], + [oo], + [cos(x/2)], [cos(0.5*x)], # <-- no recursion + ] + + for m, values_m in enumerate(equal_values): + for value_i in values_m: + + # All values in same list equal + for value_j in values_m: + assert equal_valued(value_i, value_j) is True + + # Not equal to anything in any other list: + for n, values_n in enumerate(equal_values): + if n == m: + continue + for value_j in values_n: + assert equal_valued(value_i, value_j) is False + + +def test_all_close(): + x = Symbol('x') + y = Symbol('y') + z = Symbol('z') + assert all_close(2, 2) is True + assert all_close(2, 2.0000) is True + assert all_close(2, 2.0001) is False + assert all_close(1/3, 1/3.0001) is False + assert all_close(1/3, 1/3.0001, 1e-3, 1e-3) is True + assert all_close(1/3, Rational(1, 3)) is True + assert all_close(0.1*exp(0.2*x), exp(x/5)/10) is True + # The expressions should be structurally the same modulo identity: + assert all_close(1.4142135623730951, sqrt(2)) is False + assert all_close(1.4142135623730951, sqrt(2).evalf()) is True + assert all_close(x + 1e-20, x) is True + # We should be able to match terms of an Add/Mul in any order + assert all_close(Add(1, 2, evaluate=False), Add(2, 1, evaluate=False)) + # coverage + assert not all_close(2*x, 3*x) + assert all_close(2*x, 3*x, 1) + assert not all_close(2*x, 3*x, 0, 0.5) + assert all_close(2*x, 3*x, 0, 1) + assert not all_close(y*x, z*x) + assert all_close(2*x*exp(1.0*x), 2.0*x*exp(x)) + assert not all_close(2*x*exp(1.0*x), 2.0*x*exp(2.*x)) + assert all_close(x + 2.*y, 1.*x + 2*y) + assert all_close(x + exp(2.*x)*y, 1.*x + exp(2*x)*y) + assert not all_close(x + exp(2.*x)*y, 1.*x + 2*exp(2*x)*y) + assert not all_close(x + exp(2.*x)*y, 1.*x + exp(3*x)*y) + assert not all_close(x + 2.*y, 1.*x + 3*y) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_operations.py b/phivenv/Lib/site-packages/sympy/core/tests/test_operations.py new file mode 100644 index 0000000000000000000000000000000000000000..c60d691ef00ee9601ada04ef68e2db37794a81ad --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_operations.py @@ -0,0 +1,110 @@ +from sympy.core.expr import Expr +from sympy.core.numbers import Integer +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.core.operations import AssocOp, LatticeOp +from sympy.testing.pytest import raises +from sympy.core.sympify import SympifyError +from sympy.core.add import Add, add +from sympy.core.mul import Mul, mul + +# create the simplest possible Lattice class + + +class join(LatticeOp): + zero = Integer(0) + identity = Integer(1) + + +def test_lattice_simple(): + assert join(join(2, 3), 4) == join(2, join(3, 4)) + assert join(2, 3) == join(3, 2) + assert join(0, 2) == 0 + assert join(1, 2) == 2 + assert join(2, 2) == 2 + + assert join(join(2, 3), 4) == join(2, 3, 4) + assert join() == 1 + assert join(4) == 4 + assert join(1, 4, 2, 3, 1, 3, 2) == join(2, 3, 4) + + +def test_lattice_shortcircuit(): + raises(SympifyError, lambda: join(object)) + assert join(0, object) == 0 + + +def test_lattice_print(): + assert str(join(5, 4, 3, 2)) == 'join(2, 3, 4, 5)' + + +def test_lattice_make_args(): + assert join.make_args(join(2, 3, 4)) == {S(2), S(3), S(4)} + assert join.make_args(0) == {0} + assert list(join.make_args(0))[0] is S.Zero + assert Add.make_args(0)[0] is S.Zero + + +def test_issue_14025(): + a, b, c, d = symbols('a,b,c,d', commutative=False) + assert Mul(a, b, c).has(c*b) == False + assert Mul(a, b, c).has(b*c) == True + assert Mul(a, b, c, d).has(b*c*d) == True + + +def test_AssocOp_flatten(): + a, b, c, d = symbols('a,b,c,d') + + class MyAssoc(AssocOp): + identity = S.One + + assert MyAssoc(a, MyAssoc(b, c)).args == \ + MyAssoc(MyAssoc(a, b), c).args == \ + MyAssoc(MyAssoc(a, b, c)).args == \ + MyAssoc(a, b, c).args == \ + (a, b, c) + u = MyAssoc(b, c) + v = MyAssoc(u, d, evaluate=False) + assert v.args == (u, d) + # like Add, any unevaluated outer call will flatten inner args + assert MyAssoc(a, v).args == (a, b, c, d) + + +def test_add_dispatcher(): + + class NewBase(Expr): + @property + def _add_handler(self): + return NewAdd + class NewAdd(NewBase, Add): + pass + add.register_handlerclass((Add, NewAdd), NewAdd) + + a, b = Symbol('a'), NewBase() + + # Add called as fallback + assert add(1, 2) == Add(1, 2) + assert add(a, a) == Add(a, a) + + # selection by registered priority + assert add(a,b,a) == NewAdd(2*a, b) + + +def test_mul_dispatcher(): + + class NewBase(Expr): + @property + def _mul_handler(self): + return NewMul + class NewMul(NewBase, Mul): + pass + mul.register_handlerclass((Mul, NewMul), NewMul) + + a, b = Symbol('a'), NewBase() + + # Mul called as fallback + assert mul(1, 2) == Mul(1, 2) + assert mul(a, a) == Mul(a, a) + + # selection by registered priority + assert mul(a,b,a) == NewMul(a**2, b) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_parameters.py b/phivenv/Lib/site-packages/sympy/core/tests/test_parameters.py new file mode 100644 index 0000000000000000000000000000000000000000..21e03d717872a9a8165ceeebf7ef58e9842702c0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_parameters.py @@ -0,0 +1,126 @@ +from sympy.abc import x, y +from sympy.core.parameters import evaluate +from sympy.core import Mul, Add, Pow, S +from sympy.core.numbers import oo +from sympy.functions.elementary.miscellaneous import sqrt + +def test_add(): + with evaluate(False): + p = oo - oo + assert isinstance(p, Add) and p.args == (oo, -oo) + p = 5 - oo + assert isinstance(p, Add) and p.args == (-oo, 5) + p = oo - 5 + assert isinstance(p, Add) and p.args == (oo, -5) + p = oo + 5 + assert isinstance(p, Add) and p.args == (oo, 5) + p = 5 + oo + assert isinstance(p, Add) and p.args == (oo, 5) + p = -oo + 5 + assert isinstance(p, Add) and p.args == (-oo, 5) + p = -5 - oo + assert isinstance(p, Add) and p.args == (-oo, -5) + + with evaluate(False): + expr = x + x + assert isinstance(expr, Add) + assert expr.args == (x, x) + + with evaluate(True): + assert (x + x).args == (2, x) + + assert (x + x).args == (x, x) + + assert isinstance(x + x, Mul) + + with evaluate(False): + assert S.One + 1 == Add(1, 1) + assert 1 + S.One == Add(1, 1) + + assert S(4) - 3 == Add(4, -3) + assert -3 + S(4) == Add(4, -3) + + assert S(2) * 4 == Mul(2, 4) + assert 4 * S(2) == Mul(2, 4) + + assert S(6) / 3 == Mul(6, Pow(3, -1)) + assert S.One / 3 * 6 == Mul(S.One / 3, 6) + + assert 9 ** S(2) == Pow(9, 2) + assert S(2) ** 9 == Pow(2, 9) + + assert S(2) / 2 == Mul(2, Pow(2, -1)) + assert S.One / 2 * 2 == Mul(S.One / 2, 2) + + assert S(2) / 3 + 1 == Add(S(2) / 3, 1) + assert 1 + S(2) / 3 == Add(1, S(2) / 3) + + assert S(4) / 7 - 3 == Add(S(4) / 7, -3) + assert -3 + S(4) / 7 == Add(-3, S(4) / 7) + + assert S(2) / 4 * 4 == Mul(S(2) / 4, 4) + assert 4 * (S(2) / 4) == Mul(4, S(2) / 4) + + assert S(6) / 3 == Mul(6, Pow(3, -1)) + assert S.One / 3 * 6 == Mul(S.One / 3, 6) + + assert S.One / 3 + sqrt(3) == Add(S.One / 3, sqrt(3)) + assert sqrt(3) + S.One / 3 == Add(sqrt(3), S.One / 3) + + assert S.One / 2 * 10.333 == Mul(S.One / 2, 10.333) + assert 10.333 * (S.One / 2) == Mul(10.333, S.One / 2) + + assert sqrt(2) * sqrt(2) == Mul(sqrt(2), sqrt(2)) + + assert S.One / 2 + x == Add(S.One / 2, x) + assert x + S.One / 2 == Add(x, S.One / 2) + + assert S.One / x * x == Mul(S.One / x, x) + assert x * (S.One / x) == Mul(x, Pow(x, -1)) + + assert S.One / 3 == Pow(3, -1) + assert S.One / x == Pow(x, -1) + assert 1 / S(3) == Pow(3, -1) + assert 1 / x == Pow(x, -1) + +def test_nested(): + with evaluate(False): + expr = (x + x) + (y + y) + assert expr.args == ((x + x), (y + y)) + assert expr.args[0].args == (x, x) + +def test_reentrantcy(): + with evaluate(False): + expr = x + x + assert expr.args == (x, x) + with evaluate(True): + expr = x + x + assert expr.args == (2, x) + expr = x + x + assert expr.args == (x, x) + +def test_reusability(): + f = evaluate(False) + + with f: + expr = x + x + assert expr.args == (x, x) + + expr = x + x + assert expr.args == (2, x) + + with f: + expr = x + x + assert expr.args == (x, x) + + # Assure reentrancy with reusability + ctx = evaluate(False) + with ctx: + expr = x + x + assert expr.args == (x, x) + with ctx: + expr = x + x + assert expr.args == (x, x) + + expr = x + x + assert expr.args == (2, x) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_power.py b/phivenv/Lib/site-packages/sympy/core/tests/test_power.py new file mode 100644 index 0000000000000000000000000000000000000000..80ae48c525c20da6153deffbc9feadef81acf527 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_power.py @@ -0,0 +1,670 @@ +from sympy.core import ( + Basic, Rational, Symbol, S, Float, Integer, Mul, Number, Pow, + Expr, I, nan, pi, symbols, oo, zoo, N) +from sympy.core.parameters import global_parameters +from sympy.core.tests.test_evalf import NS +from sympy.core.function import expand_multinomial +from sympy.functions.elementary.miscellaneous import sqrt, cbrt +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.special.error_functions import erf +from sympy.functions.elementary.trigonometric import ( + sin, cos, tan, sec, csc, atan) +from sympy.functions.elementary.hyperbolic import cosh, sinh, tanh +from sympy.polys import Poly +from sympy.series.order import O +from sympy.sets import FiniteSet +from sympy.core.power import power +from sympy.core.intfunc import integer_nthroot +from sympy.testing.pytest import warns, _both_exp_pow +from sympy.utilities.exceptions import SymPyDeprecationWarning +from sympy.abc import a, b, c, x, y +from sympy.core.numbers import all_close + +def test_rational(): + a = Rational(1, 5) + + r = sqrt(5)/5 + assert sqrt(a) == r + assert 2*sqrt(a) == 2*r + + r = a*a**S.Half + assert a**Rational(3, 2) == r + assert 2*a**Rational(3, 2) == 2*r + + r = a**5*a**Rational(2, 3) + assert a**Rational(17, 3) == r + assert 2 * a**Rational(17, 3) == 2*r + + +def test_large_rational(): + e = (Rational(123712**12 - 1, 7) + Rational(1, 7))**Rational(1, 3) + assert e == 234232585392159195136 * (Rational(1, 7)**Rational(1, 3)) + + +def test_negative_real(): + def feq(a, b): + return abs(a - b) < 1E-10 + + assert feq(S.One / Float(-0.5), -Integer(2)) + + +def test_expand(): + assert (2**(-1 - x)).expand() == S.Half*2**(-x) + + +def test_issue_3449(): + #test if powers are simplified correctly + #see also issue 3995 + assert ((x**Rational(1, 3))**Rational(2)) == x**Rational(2, 3) + assert ( + (x**Rational(3))**Rational(2, 5)) == (x**Rational(3))**Rational(2, 5) + + a = Symbol('a', real=True) + b = Symbol('b', real=True) + assert (a**2)**b == (abs(a)**b)**2 + assert sqrt(1/a) != 1/sqrt(a) # e.g. for a = -1 + assert (a**3)**Rational(1, 3) != a + assert (x**a)**b != x**(a*b) # e.g. x = -1, a=2, b=1/2 + assert (x**.5)**b == x**(.5*b) + assert (x**.5)**.5 == x**.25 + assert (x**2.5)**.5 != x**1.25 # e.g. for x = 5*I + + k = Symbol('k', integer=True) + m = Symbol('m', integer=True) + assert (x**k)**m == x**(k*m) + assert Number(5)**Rational(2, 3) == Number(25)**Rational(1, 3) + + assert (x**.5)**2 == x**1.0 + assert (x**2)**k == (x**k)**2 == x**(2*k) + + a = Symbol('a', positive=True) + assert (a**3)**Rational(2, 5) == a**Rational(6, 5) + assert (a**2)**b == (a**b)**2 + assert (a**Rational(2, 3))**x == a**(x*Rational(2, 3)) != (a**x)**Rational(2, 3) + + +def test_issue_3866(): + assert --sqrt(sqrt(5) - 1) == sqrt(sqrt(5) - 1) + + +def test_negative_one(): + x = Symbol('x', complex=True) + y = Symbol('y', complex=True) + assert 1/x**y == x**(-y) + + +def test_issue_4362(): + neg = Symbol('neg', negative=True) + nonneg = Symbol('nonneg', nonnegative=True) + any = Symbol('any') + num, den = sqrt(1/neg).as_numer_denom() + assert num == sqrt(-1) + assert den == sqrt(-neg) + num, den = sqrt(1/nonneg).as_numer_denom() + assert num == 1 + assert den == sqrt(nonneg) + num, den = sqrt(1/any).as_numer_denom() + assert num == sqrt(1/any) + assert den == 1 + + def eqn(num, den, pow): + return (num/den)**pow + npos = 1 + nneg = -1 + dpos = 2 - sqrt(3) + dneg = 1 - sqrt(3) + assert dpos > 0 and dneg < 0 and npos > 0 and nneg < 0 + # pos or neg integer + eq = eqn(npos, dpos, 2) + assert eq.is_Pow and eq.as_numer_denom() == (1, dpos**2) + eq = eqn(npos, dneg, 2) + assert eq.is_Pow and eq.as_numer_denom() == (1, dneg**2) + eq = eqn(nneg, dpos, 2) + assert eq.is_Pow and eq.as_numer_denom() == (1, dpos**2) + eq = eqn(nneg, dneg, 2) + assert eq.is_Pow and eq.as_numer_denom() == (1, dneg**2) + eq = eqn(npos, dpos, -2) + assert eq.is_Pow and eq.as_numer_denom() == (dpos**2, 1) + eq = eqn(npos, dneg, -2) + assert eq.is_Pow and eq.as_numer_denom() == (dneg**2, 1) + eq = eqn(nneg, dpos, -2) + assert eq.is_Pow and eq.as_numer_denom() == (dpos**2, 1) + eq = eqn(nneg, dneg, -2) + assert eq.is_Pow and eq.as_numer_denom() == (dneg**2, 1) + # pos or neg rational + pow = S.Half + eq = eqn(npos, dpos, pow) + assert eq.is_Pow and eq.as_numer_denom() == (npos**pow, dpos**pow) + eq = eqn(npos, dneg, pow) + assert eq.is_Pow is False and eq.as_numer_denom() == ((-npos)**pow, (-dneg)**pow) + eq = eqn(nneg, dpos, pow) + assert not eq.is_Pow or eq.as_numer_denom() == (nneg**pow, dpos**pow) + eq = eqn(nneg, dneg, pow) + assert eq.is_Pow and eq.as_numer_denom() == ((-nneg)**pow, (-dneg)**pow) + eq = eqn(npos, dpos, -pow) + assert eq.is_Pow and eq.as_numer_denom() == (dpos**pow, npos**pow) + eq = eqn(npos, dneg, -pow) + assert eq.is_Pow is False and eq.as_numer_denom() == (-(-npos)**pow*(-dneg)**pow, npos) + eq = eqn(nneg, dpos, -pow) + assert not eq.is_Pow or eq.as_numer_denom() == (dpos**pow, nneg**pow) + eq = eqn(nneg, dneg, -pow) + assert eq.is_Pow and eq.as_numer_denom() == ((-dneg)**pow, (-nneg)**pow) + # unknown exponent + pow = 2*any + eq = eqn(npos, dpos, pow) + assert eq.is_Pow and eq.as_numer_denom() == (npos**pow, dpos**pow) + eq = eqn(npos, dneg, pow) + assert eq.is_Pow and eq.as_numer_denom() == ((-npos)**pow, (-dneg)**pow) + eq = eqn(nneg, dpos, pow) + assert eq.is_Pow and eq.as_numer_denom() == (nneg**pow, dpos**pow) + eq = eqn(nneg, dneg, pow) + assert eq.is_Pow and eq.as_numer_denom() == ((-nneg)**pow, (-dneg)**pow) + eq = eqn(npos, dpos, -pow) + assert eq.as_numer_denom() == (dpos**pow, npos**pow) + eq = eqn(npos, dneg, -pow) + assert eq.is_Pow and eq.as_numer_denom() == ((-dneg)**pow, (-npos)**pow) + eq = eqn(nneg, dpos, -pow) + assert eq.is_Pow and eq.as_numer_denom() == (dpos**pow, nneg**pow) + eq = eqn(nneg, dneg, -pow) + assert eq.is_Pow and eq.as_numer_denom() == ((-dneg)**pow, (-nneg)**pow) + + assert ((1/(1 + x/3))**(-S.One)).as_numer_denom() == (3 + x, 3) + notp = Symbol('notp', positive=False) # not positive does not imply real + b = ((1 + x/notp)**-2) + assert (b**(-y)).as_numer_denom() == (1, b**y) + assert (b**(-S.One)).as_numer_denom() == ((notp + x)**2, notp**2) + nonp = Symbol('nonp', nonpositive=True) + assert (((1 + x/nonp)**-2)**(-S.One)).as_numer_denom() == ((-nonp - + x)**2, nonp**2) + + n = Symbol('n', negative=True) + assert (x**n).as_numer_denom() == (1, x**-n) + assert sqrt(1/n).as_numer_denom() == (S.ImaginaryUnit, sqrt(-n)) + n = Symbol('0 or neg', nonpositive=True) + # if x and n are split up without negating each term and n is negative + # then the answer might be wrong; if n is 0 it won't matter since + # 1/oo and 1/zoo are both zero as is sqrt(0)/sqrt(-x) unless x is also + # zero (in which case the negative sign doesn't matter): + # 1/sqrt(1/-1) = -I but sqrt(-1)/sqrt(1) = I + assert (1/sqrt(x/n)).as_numer_denom() == (sqrt(-n), sqrt(-x)) + c = Symbol('c', complex=True) + e = sqrt(1/c) + assert e.as_numer_denom() == (e, 1) + i = Symbol('i', integer=True) + assert ((1 + x/y)**i).as_numer_denom() == ((x + y)**i, y**i) + + +def test_Pow_Expr_args(): + bases = [Basic(), Poly(x, x), FiniteSet(x)] + for base in bases: + # The cache can mess with the stacklevel test + with warns(SymPyDeprecationWarning, test_stacklevel=False): + Pow(base, S.One) + + +def test_Pow_signs(): + """Cf. issues 4595 and 5250""" + n = Symbol('n', even=True) + assert (3 - y)**2 != (y - 3)**2 + assert (3 - y)**n != (y - 3)**n + assert (-3 + y - x)**2 != (3 - y + x)**2 + assert (y - 3)**3 != -(3 - y)**3 + + +def test_power_with_noncommutative_mul_as_base(): + x = Symbol('x', commutative=False) + y = Symbol('y', commutative=False) + assert not (x*y)**3 == x**3*y**3 + assert (2*x*y)**3 == 8*(x*y)**3 + + +@_both_exp_pow +def test_power_rewrite_exp(): + assert (I**I).rewrite(exp) == exp(-pi/2) + + expr = (2 + 3*I)**(4 + 5*I) + assert expr.rewrite(exp) == exp((4 + 5*I)*(log(sqrt(13)) + I*atan(Rational(3, 2)))) + assert expr.rewrite(exp).expand() == \ + 169*exp(5*I*log(13)/2)*exp(4*I*atan(Rational(3, 2)))*exp(-5*atan(Rational(3, 2))) + + assert ((6 + 7*I)**5).rewrite(exp) == 7225*sqrt(85)*exp(5*I*atan(Rational(7, 6))) + + expr = 5**(6 + 7*I) + assert expr.rewrite(exp) == exp((6 + 7*I)*log(5)) + assert expr.rewrite(exp).expand() == 15625*exp(7*I*log(5)) + + assert Pow(123, 789, evaluate=False).rewrite(exp) == 123**789 + assert (1**I).rewrite(exp) == 1**I + assert (0**I).rewrite(exp) == 0**I + + expr = (-2)**(2 + 5*I) + assert expr.rewrite(exp) == exp((2 + 5*I)*(log(2) + I*pi)) + assert expr.rewrite(exp).expand() == 4*exp(-5*pi)*exp(5*I*log(2)) + + assert ((-2)**S(-5)).rewrite(exp) == (-2)**S(-5) + + x, y = symbols('x y') + assert (x**y).rewrite(exp) == exp(y*log(x)) + if global_parameters.exp_is_pow: + assert (7**x).rewrite(exp) == Pow(S.Exp1, x*log(7), evaluate=False) + else: + assert (7**x).rewrite(exp) == exp(x*log(7), evaluate=False) + assert ((2 + 3*I)**x).rewrite(exp) == exp(x*(log(sqrt(13)) + I*atan(Rational(3, 2)))) + assert (y**(5 + 6*I)).rewrite(exp) == exp(log(y)*(5 + 6*I)) + + assert all((1/func(x)).rewrite(exp) == 1/(func(x).rewrite(exp)) for func in + (sin, cos, tan, sec, csc, sinh, cosh, tanh)) + + +def test_zero(): + assert 0**x != 0 + assert 0**(2*x) == 0**x + assert 0**(1.0*x) == 0**x + assert 0**(2.0*x) == 0**x + assert (0**(2 - x)).as_base_exp() == (0, 2 - x) + assert 0**(x - 2) != S.Infinity**(2 - x) + assert 0**(2*x*y) == 0**(x*y) + assert 0**(-2*x*y) == S.ComplexInfinity**(x*y) + assert Float(0)**2 is not S.Zero + assert Float(0)**2 == 0.0 + assert Float(0)**-2 is zoo + assert Float(0)**oo is S.Zero + + #Test issue 19572 + assert 0 ** -oo is zoo + assert power(0, -oo) is zoo + assert Float(0)**-oo is zoo + +def test_pow_as_base_exp(): + assert (S.Infinity**(2 - x)).as_base_exp() == (S.Infinity, 2 - x) + assert (S.Infinity**(x - 2)).as_base_exp() == (S.Infinity, x - 2) + p = S.Half**x + assert p.base, p.exp == p.as_base_exp() == (S(2), -x) + p = (S(3)/2)**x + assert p.base, p.exp == p.as_base_exp() == (3*S.Half, x) + p = (S(2)/3)**x + assert p.as_base_exp() == (S(2)/3, x) + assert p.base, p.exp == (S(2)/3, x) + # issue 8344: + assert Pow(1, 2, evaluate=False).as_base_exp() == (S.One, S(2)) + + +def test_nseries(): + assert sqrt(I*x - 1)._eval_nseries(x, 4, None, 1) == I + x/2 + I*x**2/8 - x**3/16 + O(x**4) + assert sqrt(I*x - 1)._eval_nseries(x, 4, None, -1) == -I - x/2 - I*x**2/8 + x**3/16 + O(x**4) + assert cbrt(I*x - 1)._eval_nseries(x, 4, None, 1) == (-1)**(S(1)/3) - (-1)**(S(5)/6)*x/3 + \ + (-1)**(S(1)/3)*x**2/9 + 5*(-1)**(S(5)/6)*x**3/81 + O(x**4) + assert cbrt(I*x - 1)._eval_nseries(x, 4, None, -1) == -(-1)**(S(2)/3) - (-1)**(S(1)/6)*x/3 - \ + (-1)**(S(2)/3)*x**2/9 + 5*(-1)**(S(1)/6)*x**3/81 + O(x**4) + assert (1 / (exp(-1/x) + 1/x))._eval_nseries(x, 2, None) == x + O(x**2) + # test issue 23752 + assert sqrt(-I*x**2 + x - 3)._eval_nseries(x, 4, None, 1) == -sqrt(3)*I + sqrt(3)*I*x/6 - \ + sqrt(3)*I*x**2*(-S(1)/72 + I/6) - sqrt(3)*I*x**3*(-S(1)/432 + I/36) + O(x**4) + assert sqrt(-I*x**2 + x - 3)._eval_nseries(x, 4, None, -1) == -sqrt(3)*I + sqrt(3)*I*x/6 - \ + sqrt(3)*I*x**2*(-S(1)/72 + I/6) - sqrt(3)*I*x**3*(-S(1)/432 + I/36) + O(x**4) + assert cbrt(-I*x**2 + x - 3)._eval_nseries(x, 4, None, 1) == -(-1)**(S(2)/3)*3**(S(1)/3) + \ + (-1)**(S(2)/3)*3**(S(1)/3)*x/9 - (-1)**(S(2)/3)*3**(S(1)/3)*x**2*(-S(1)/81 + I/9) - \ + (-1)**(S(2)/3)*3**(S(1)/3)*x**3*(-S(5)/2187 + 2*I/81) + O(x**4) + assert cbrt(-I*x**2 + x - 3)._eval_nseries(x, 4, None, -1) == -(-1)**(S(2)/3)*3**(S(1)/3) + \ + (-1)**(S(2)/3)*3**(S(1)/3)*x/9 - (-1)**(S(2)/3)*3**(S(1)/3)*x**2*(-S(1)/81 + I/9) - \ + (-1)**(S(2)/3)*3**(S(1)/3)*x**3*(-S(5)/2187 + 2*I/81) + O(x**4) + + +def test_issue_6100_12942_4473(): + assert x**1.0 != x + assert x != x**1.0 + assert True != x**1.0 + assert x**1.0 is not True + assert x is not True + assert x*y != (x*y)**1.0 + # Pow != Symbol + assert (x**1.0)**1.0 != x + assert (x**1.0)**2.0 != x**2 + b = Expr() + assert Pow(b, 1.0, evaluate=False) != b + # if the following gets distributed as a Mul (x**1.0*y**1.0 then + # __eq__ methods could be added to Symbol and Pow to detect the + # power-of-1.0 case. + assert ((x*y)**1.0).func is Pow + + +def test_issue_6208(): + from sympy.functions.elementary.miscellaneous import root + assert sqrt(33**(I*9/10)) == -33**(I*9/20) + assert root((6*I)**(2*I), 3).as_base_exp()[1] == Rational(1, 3) # != 2*I/3 + assert root((6*I)**(I/3), 3).as_base_exp()[1] == I/9 + assert sqrt(exp(3*I)) == exp(3*I/2) + assert sqrt(-sqrt(3)*(1 + 2*I)) == sqrt(sqrt(3))*sqrt(-1 - 2*I) + assert sqrt(exp(5*I)) == -exp(5*I/2) + assert root(exp(5*I), 3).exp == Rational(1, 3) + + +def test_issue_6990(): + assert (sqrt(a + b*x + x**2)).series(x, 0, 3).removeO() == \ + sqrt(a)*x**2*(1/(2*a) - b**2/(8*a**2)) + sqrt(a) + b*x/(2*sqrt(a)) + + +def test_issue_6068(): + assert sqrt(sin(x)).series(x, 0, 7) == \ + sqrt(x) - x**Rational(5, 2)/12 + x**Rational(9, 2)/1440 - \ + x**Rational(13, 2)/24192 + O(x**7) + assert sqrt(sin(x)).series(x, 0, 9) == \ + sqrt(x) - x**Rational(5, 2)/12 + x**Rational(9, 2)/1440 - \ + x**Rational(13, 2)/24192 - 67*x**Rational(17, 2)/29030400 + O(x**9) + assert sqrt(sin(x**3)).series(x, 0, 19) == \ + x**Rational(3, 2) - x**Rational(15, 2)/12 + x**Rational(27, 2)/1440 + O(x**19) + assert sqrt(sin(x**3)).series(x, 0, 20) == \ + x**Rational(3, 2) - x**Rational(15, 2)/12 + x**Rational(27, 2)/1440 - \ + x**Rational(39, 2)/24192 + O(x**20) + + +def test_issue_6782(): + assert sqrt(sin(x**3)).series(x, 0, 7) == x**Rational(3, 2) + O(x**7) + assert sqrt(sin(x**4)).series(x, 0, 3) == x**2 + O(x**3) + + +def test_issue_6653(): + assert (1 / sqrt(1 + sin(x**2))).series(x, 0, 3) == 1 - x**2/2 + O(x**3) + + +def test_issue_6429(): + f = (c**2 + x)**(0.5) + assert f.series(x, x0=0, n=1) == (c**2)**0.5 + O(x) + assert f.taylor_term(0, x) == (c**2)**0.5 + assert f.taylor_term(1, x) == 0.5*x*(c**2)**(-0.5) + assert f.taylor_term(2, x) == -0.125*x**2*(c**2)**(-1.5) + + +def test_issue_7638(): + f = pi/log(sqrt(2)) + assert ((1 + I)**(I*f/2))**0.3 == (1 + I)**(0.15*I*f) + # if 1/3 -> 1.0/3 this should fail since it cannot be shown that the + # sign will be +/-1; for the previous "small arg" case, it didn't matter + # that this could not be proved + assert (1 + I)**(4*I*f) == ((1 + I)**(12*I*f))**Rational(1, 3) + + assert (((1 + I)**(I*(1 + 7*f)))**Rational(1, 3)).exp == Rational(1, 3) + r = symbols('r', real=True) + assert sqrt(r**2) == abs(r) + assert cbrt(r**3) != r + assert sqrt(Pow(2*I, 5*S.Half)) != (2*I)**Rational(5, 4) + p = symbols('p', positive=True) + assert cbrt(p**2) == p**Rational(2, 3) + assert NS(((0.2 + 0.7*I)**(0.7 + 1.0*I))**(0.5 - 0.1*I), 1) == '0.4 + 0.2*I' + assert sqrt(1/(1 + I)) == sqrt(1 - I)/sqrt(2) # or 1/sqrt(1 + I) + e = 1/(1 - sqrt(2)) + assert sqrt(e) == I/sqrt(-1 + sqrt(2)) + assert e**Rational(-1, 2) == -I*sqrt(-1 + sqrt(2)) + assert sqrt((cos(1)**2 + sin(1)**2 - 1)**(3 + I)).exp in [S.Half, + Rational(3, 2) + I/2] + assert sqrt(r**Rational(4, 3)) != r**Rational(2, 3) + assert sqrt((p + I)**Rational(4, 3)) == (p + I)**Rational(2, 3) + + for q in 1+I, 1-I: + assert sqrt(q**2) == q + for q in -1+I, -1-I: + assert sqrt(q**2) == -q + + assert sqrt((p + r*I)**2) != p + r*I + e = (1 + I/5) + assert sqrt(e**5) == e**(5*S.Half) + assert sqrt(e**6) == e**3 + assert sqrt((1 + I*r)**6) != (1 + I*r)**3 + + +def test_issue_8582(): + assert 1**oo is nan + assert 1**(-oo) is nan + assert 1**zoo is nan + assert 1**(oo + I) is nan + assert 1**(1 + I*oo) is nan + assert 1**(oo + I*oo) is nan + + +def test_issue_8650(): + n = Symbol('n', integer=True, nonnegative=True) + assert (n**n).is_positive is True + x = 5*n + 5 + assert (x**(5*(n + 1))).is_positive is True + + +def test_issue_13914(): + b = Symbol('b') + assert (-1)**zoo is nan + assert 2**zoo is nan + assert (S.Half)**(1 + zoo) is nan + assert I**(zoo + I) is nan + assert b**(I + zoo) is nan + + +def test_better_sqrt(): + n = Symbol('n', integer=True, nonnegative=True) + assert sqrt(3 + 4*I) == 2 + I + assert sqrt(3 - 4*I) == 2 - I + assert sqrt(-3 - 4*I) == 1 - 2*I + assert sqrt(-3 + 4*I) == 1 + 2*I + assert sqrt(32 + 24*I) == 6 + 2*I + assert sqrt(32 - 24*I) == 6 - 2*I + assert sqrt(-32 - 24*I) == 2 - 6*I + assert sqrt(-32 + 24*I) == 2 + 6*I + + # triple (3, 4, 5): + # parity of 3 matches parity of 5 and + # den, 4, is a square + assert sqrt((3 + 4*I)/4) == 1 + I/2 + # triple (8, 15, 17) + # parity of 8 doesn't match parity of 17 but + # den/2, 8/2, is a square + assert sqrt((8 + 15*I)/8) == (5 + 3*I)/4 + # handle the denominator + assert sqrt((3 - 4*I)/25) == (2 - I)/5 + assert sqrt((3 - 4*I)/26) == (2 - I)/sqrt(26) + # mul + # issue #12739 + assert sqrt((3 + 4*I)/(3 - 4*I)) == (3 + 4*I)/5 + assert sqrt(2/(3 + 4*I)) == sqrt(2)/5*(2 - I) + assert sqrt(n/(3 + 4*I)).subs(n, 2) == sqrt(2)/5*(2 - I) + assert sqrt(-2/(3 + 4*I)) == sqrt(2)/5*(1 + 2*I) + assert sqrt(-n/(3 + 4*I)).subs(n, 2) == sqrt(2)/5*(1 + 2*I) + # power + assert sqrt(1/(3 + I*4)) == (2 - I)/5 + assert sqrt(1/(3 - I)) == sqrt(10)*sqrt(3 + I)/10 + # symbolic + i = symbols('i', imaginary=True) + assert sqrt(3/i) == Mul(sqrt(3), 1/sqrt(i), evaluate=False) + # multiples of 1/2; don't make this too automatic + assert sqrt(3 + 4*I)**3 == (2 + I)**3 + assert Pow(3 + 4*I, Rational(3, 2)) == 2 + 11*I + assert Pow(6 + 8*I, Rational(3, 2)) == 2*sqrt(2)*(2 + 11*I) + n, d = (3 + 4*I), (3 - 4*I)**3 + a = n/d + assert a.args == (1/d, n) + eq = sqrt(a) + assert eq.args == (a, S.Half) + assert expand_multinomial(eq) == sqrt((-117 + 44*I)*(3 + 4*I))/125 + assert eq.expand() == (7 - 24*I)/125 + + # issue 12775 + # pos im part + assert sqrt(2*I) == (1 + I) + assert sqrt(2*9*I) == Mul(3, 1 + I, evaluate=False) + assert Pow(2*I, 3*S.Half) == (1 + I)**3 + # neg im part + assert sqrt(-I/2) == Mul(S.Half, 1 - I, evaluate=False) + # fractional im part + assert Pow(Rational(-9, 2)*I, Rational(3, 2)) == 27*(1 - I)**3/8 + + +def test_issue_2993(): + assert str((2.3*x - 4)**0.3) == '(2.3*x - 4)**0.3' + assert str((2.3*x + 4)**0.3) == '(2.3*x + 4)**0.3' + assert str((-2.3*x + 4)**0.3) == '(4 - 2.3*x)**0.3' + assert str((-2.3*x - 4)**0.3) == '(-2.3*x - 4)**0.3' + assert str((2.3*x - 2)**0.3) == '(2.3*x - 2)**0.3' + assert str((-2.3*x - 2)**0.3) == '(-2.3*x - 2)**0.3' + assert str((-2.3*x + 2)**0.3) == '(2 - 2.3*x)**0.3' + assert str((2.3*x + 2)**0.3) == '(2.3*x + 2)**0.3' + assert str((2.3*x - 4)**Rational(1, 3)) == '(2.3*x - 4)**(1/3)' + eq = (2.3*x + 4) + assert str(eq**2) == '(2.3*x + 4)**2' + assert (1/eq).args == (eq, -1) # don't change trivial power + # issue 17735 + q=.5*exp(x) - .5*exp(-x) + 0.1 + assert int((q**2).subs(x, 1)) == 1 + # issue 17756 + y = Symbol('y') + assert len(sqrt(x/(x + y)**2 + Float('0.008', 30)).subs(y, pi.n(25)).atoms(Float)) == 2 + # issue 17756 + a, b, c, d, e, f, g = symbols('a:g') + expr = sqrt(1 + a*(c**4 + g*d - 2*g*e - f*(-g + d))**2/ + (c**3*b**2*(d - 3*e + 2*f)**2))/2 + r = [ + (a, N('0.0170992456333788667034850458615', 30)), + (b, N('0.0966594956075474769169134801223', 30)), + (c, N('0.390911862903463913632151616184', 30)), + (d, N('0.152812084558656566271750185933', 30)), + (e, N('0.137562344465103337106561623432', 30)), + (f, N('0.174259178881496659302933610355', 30)), + (g, N('0.220745448491223779615401870086', 30))] + tru = expr.n(30, subs=dict(r)) + seq = expr.subs(r) + # although `tru` is the right way to evaluate + # expr with numerical values, `seq` will have + # significant loss of precision if extraction of + # the largest coefficient of a power's base's terms + # is done improperly + assert seq == tru + +def test_issue_17450(): + assert (erf(cosh(1)**7)**I).is_real is None + assert (erf(cosh(1)**7)**I).is_imaginary is False + assert (Pow(exp(1+sqrt(2)), ((1-sqrt(2))*I*pi), evaluate=False)).is_real is None + assert ((-10)**(10*I*pi/3)).is_real is False + assert ((-5)**(4*I*pi)).is_real is False + + +def test_issue_18190(): + assert sqrt(1 / tan(1 + I)) == 1 / sqrt(tan(1 + I)) + + +def test_issue_14815(): + x = Symbol('x', real=True) + assert sqrt(x).is_extended_negative is False + x = Symbol('x', real=False) + assert sqrt(x).is_extended_negative is None + x = Symbol('x', complex=True) + assert sqrt(x).is_extended_negative is False + x = Symbol('x', extended_real=True) + assert sqrt(x).is_extended_negative is False + assert sqrt(zoo, evaluate=False).is_extended_negative is False + assert sqrt(nan, evaluate=False).is_extended_negative is None + + +def test_issue_18509(): + x = Symbol('x', prime=True) + assert x**oo is oo + assert (1/x)**oo is S.Zero + assert (-1/x)**oo is S.Zero + assert (-x)**oo is zoo + assert (-oo)**(-1 + I) is S.Zero + assert (-oo)**(1 + I) is zoo + assert (oo)**(-1 + I) is S.Zero + assert (oo)**(1 + I) is zoo + + +def test_issue_18762(): + e, p = symbols('e p') + g0 = sqrt(1 + e**2 - 2*e*cos(p)) + assert len(g0.series(e, 1, 3).args) == 4 + + +def test_issue_21860(): + e = 3*2**Rational(66666666667,200000000000)*3**Rational(16666666667,50000000000)*x**Rational(66666666667, 200000000000) + ans = Mul(Rational(3, 2), + Pow(Integer(2), Rational(33333333333, 100000000000)), + Pow(Integer(3), Rational(26666666667, 40000000000))) + assert e.xreplace({x: Rational(3,8)}) == ans + + +def test_issue_21647(): + e = log((Integer(567)/500)**(811*(Integer(567)/500)**x/100)) + ans = log(Mul(Rational(64701150190720499096094005280169087619821081527, + 76293945312500000000000000000000000000000000000), + Pow(Integer(2), Rational(396204892125479941, 781250000000000000)), + Pow(Integer(3), Rational(385045107874520059, 390625000000000000)), + Pow(Integer(5), Rational(407364676376439823, 1562500000000000000)), + Pow(Integer(7), Rational(385045107874520059, 1562500000000000000)))) + assert e.xreplace({x: 6}) == ans + + +def test_issue_21762(): + e = (x**2 + 6)**(Integer(33333333333333333)/50000000000000000) + ans = Mul(Rational(5, 4), + Pow(Integer(2), Rational(16666666666666667, 25000000000000000)), + Pow(Integer(5), Rational(8333333333333333, 25000000000000000))) + assert e.xreplace({x: S.Half}) == ans + + +def test_issue_14704(): + a = 144**144 + x, xexact = integer_nthroot(a,a) + assert x == 1 and xexact is False + + +def test_rational_powers_larger_than_one(): + assert Rational(2, 3)**Rational(3, 2) == 2*sqrt(6)/9 + assert Rational(1, 6)**Rational(9, 4) == 6**Rational(3, 4)/216 + assert Rational(3, 7)**Rational(7, 3) == 9*3**Rational(1, 3)*7**Rational(2, 3)/343 + + +def test_power_dispatcher(): + + class NewBase(Expr): + pass + class NewPow(NewBase, Pow): + pass + a, b = Symbol('a'), NewBase() + + @power.register(Expr, NewBase) + @power.register(NewBase, Expr) + @power.register(NewBase, NewBase) + def _(a, b): + return NewPow(a, b) + + # Pow called as fallback + assert power(2, 3) == 8*S.One + assert power(a, 2) == Pow(a, 2) + assert power(a, a) == Pow(a, a) + + # NewPow called by dispatch + assert power(a, b) == NewPow(a, b) + assert power(b, a) == NewPow(b, a) + assert power(b, b) == NewPow(b, b) + + +def test_powers_of_I(): + assert [sqrt(I)**i for i in range(13)] == [ + 1, sqrt(I), I, sqrt(I)**3, -1, -sqrt(I), -I, -sqrt(I)**3, + 1, sqrt(I), I, sqrt(I)**3, -1] + assert sqrt(I)**(S(9)/2) == -I**(S(1)/4) + + +def test_issue_23918(): + b = S(2)/3 + assert (b**x).as_base_exp() == (b, x) + + +def test_issue_26546(): + x = Symbol('x', real=True) + assert x.is_extended_real is True + assert sqrt(x+I).is_extended_real is False + assert Pow(x+I, S.Half).is_extended_real is False + assert Pow(x+I, Rational(1,2)).is_extended_real is False + assert Pow(x+I, Rational(1,13)).is_extended_real is False + assert Pow(x+I, Rational(2,3)).is_extended_real is None + + +def test_issue_25165(): + e1 = (1/sqrt(( - x + 1)**2 + (x - 0.23)**4)).series(x, 0, 2) + e2 = 0.998603724830355 + 1.02004923189934*x + O(x**2) + assert all_close(e1, e2) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_priority.py b/phivenv/Lib/site-packages/sympy/core/tests/test_priority.py new file mode 100644 index 0000000000000000000000000000000000000000..276e653100f886243e07b866b699b8da53cdaf88 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_priority.py @@ -0,0 +1,145 @@ +from sympy.core.decorators import call_highest_priority +from sympy.core.expr import Expr +from sympy.core.mod import Mod +from sympy.core.numbers import Integer +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.integers import floor + + +class Higher(Integer): + ''' + Integer of value 1 and _op_priority 20 + + Operations handled by this class return 1 and reverse operations return 2 + ''' + + _op_priority = 20.0 + result: Expr = S.One + + def __new__(cls): + obj = Expr.__new__(cls) + obj.p = 1 + return obj + + @call_highest_priority('__rmul__') + def __mul__(self, other): + return self.result + + @call_highest_priority('__mul__') + def __rmul__(self, other): + return 2*self.result + + @call_highest_priority('__radd__') + def __add__(self, other): + return self.result + + @call_highest_priority('__add__') + def __radd__(self, other): + return 2*self.result + + @call_highest_priority('__rsub__') + def __sub__(self, other): + return self.result + + @call_highest_priority('__sub__') + def __rsub__(self, other): + return 2*self.result + + @call_highest_priority('__rpow__') + def __pow__(self, other): + return self.result + + @call_highest_priority('__pow__') + def __rpow__(self, other): + return 2*self.result + + @call_highest_priority('__rtruediv__') + def __truediv__(self, other): + return self.result + + @call_highest_priority('__truediv__') + def __rtruediv__(self, other): + return 2*self.result + + @call_highest_priority('__rmod__') + def __mod__(self, other): + return self.result + + @call_highest_priority('__mod__') + def __rmod__(self, other): + return 2*self.result + + @call_highest_priority('__rfloordiv__') + def __floordiv__(self, other): + return self.result + + @call_highest_priority('__floordiv__') + def __rfloordiv__(self, other): + return 2*self.result + + +class Lower(Higher): + ''' + Integer of value -1 and _op_priority 5 + + Operations handled by this class return -1 and reverse operations return -2 + ''' + + _op_priority = 5.0 + result: Expr = S.NegativeOne + + def __new__(cls): + obj = Expr.__new__(cls) + obj.p = -1 + return obj + + +x = Symbol('x') +h = Higher() +l = Lower() + + +def test_mul(): + assert h*l == h*x == 1 + assert l*h == x*h == 2 + assert x*l == l*x == -x + + +def test_add(): + assert h + l == h + x == 1 + assert l + h == x + h == 2 + assert x + l == l + x == x - 1 + + +def test_sub(): + assert h - l == h - x == 1 + assert l - h == x - h == 2 + assert x - l == -(l - x) == x + 1 + + +def test_pow(): + assert h**l == h**x == 1 + assert l**h == x**h == 2 + assert (x**l).args == (1/x).args and (x**l).is_Pow + assert (l**x).args == ((-1)**x).args and (l**x).is_Pow + + +def test_div(): + assert h/l == h/x == 1 + assert l/h == x/h == 2 + assert x/l == 1/(l/x) == -x + + +def test_mod(): + assert h%l == h%x == 1 + assert l%h == x%h == 2 + assert x%l == Mod(x, -1) + assert l%x == Mod(-1, x) + + +def test_floordiv(): + assert h//l == h//x == 1 + assert l//h == x//h == 2 + assert x//l == floor(-x) + assert l//x == floor(-1/x) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_random.py b/phivenv/Lib/site-packages/sympy/core/tests/test_random.py new file mode 100644 index 0000000000000000000000000000000000000000..01c677126285eb66349253368b94b3270fb97793 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_random.py @@ -0,0 +1,61 @@ +import random +from sympy.core.random import random as rand, seed, shuffle, _assumptions_shuffle +from sympy.core.symbol import Symbol, symbols +from sympy.functions.elementary.trigonometric import sin, acos +from sympy.abc import x + + +def test_random(): + random.seed(42) + a = random.random() + random.seed(42) + Symbol('z').is_finite + b = random.random() + assert a == b + + got = set() + for i in range(2): + random.seed(28) + m0, m1 = symbols('m_0 m_1', real=True) + _ = acos(-m0/m1) + got.add(random.uniform(0,1)) + assert len(got) == 1 + + random.seed(10) + y = 0 + for i in range(4): + y += sin(random.uniform(-10,10) * x) + random.seed(10) + z = 0 + for i in range(4): + z += sin(random.uniform(-10,10) * x) + assert y == z + + +def test_seed(): + assert rand() < 1 + seed(1) + a = rand() + b = rand() + seed(1) + c = rand() + d = rand() + assert a == c + if not c == d: + assert a != b + else: + assert a == b + + abc = 'abc' + first = list(abc) + second = list(abc) + third = list(abc) + + seed(123) + shuffle(first) + + seed(123) + shuffle(second) + _assumptions_shuffle(third) + + assert first == second == third diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_relational.py b/phivenv/Lib/site-packages/sympy/core/tests/test_relational.py new file mode 100644 index 0000000000000000000000000000000000000000..60c026fd5f5b8cee2e90e00582047cc7763bb8a4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_relational.py @@ -0,0 +1,1271 @@ +from sympy.core.logic import fuzzy_and +from sympy.core.sympify import _sympify +from sympy.multipledispatch import dispatch +from sympy.testing.pytest import XFAIL, raises +from sympy.assumptions.ask import Q +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.expr import Expr, unchanged +from sympy.core.function import Function +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Rational, nan, oo, pi, zoo) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import sign, Abs +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.integers import (ceiling, floor) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.logic.boolalg import (And, Implies, Not, Or, Xor) +from sympy.sets import Reals +from sympy.simplify.simplify import simplify +from sympy.simplify.trigsimp import trigsimp +from sympy.core.relational import (Relational, Equality, Unequality, + GreaterThan, LessThan, StrictGreaterThan, + StrictLessThan, Rel, Eq, Lt, Le, + Gt, Ge, Ne, is_le, is_gt, is_ge, is_lt, is_eq, is_neq) +from sympy.sets.sets import Interval, FiniteSet + +from itertools import combinations + +x, y, z, t = symbols('x,y,z,t') + + +def rel_check(a, b): + from sympy.testing.pytest import raises + assert a.is_number and b.is_number + for do in range(len({type(a), type(b)})): + if S.NaN in (a, b): + v = [(a == b), (a != b)] + assert len(set(v)) == 1 and v[0] == False + assert not (a != b) and not (a == b) + assert raises(TypeError, lambda: a < b) + assert raises(TypeError, lambda: a <= b) + assert raises(TypeError, lambda: a > b) + assert raises(TypeError, lambda: a >= b) + else: + E = [(a == b), (a != b)] + assert len(set(E)) == 2 + v = [ + (a < b), (a <= b), (a > b), (a >= b)] + i = [ + [True, True, False, False], + [False, True, False, True], # <-- i == 1 + [False, False, True, True]].index(v) + if i == 1: + assert E[0] or (a.is_Float != b.is_Float) # ugh + else: + assert E[1] + a, b = b, a + return True + + +def test_rel_ne(): + assert Relational(x, y, '!=') == Ne(x, y) + + # issue 6116 + p = Symbol('p', positive=True) + assert Ne(p, 0) is S.true + + +def test_rel_subs(): + e = Relational(x, y, '==') + e = e.subs(x, z) + + assert isinstance(e, Equality) + assert e.lhs == z + assert e.rhs == y + + e = Relational(x, y, '>=') + e = e.subs(x, z) + + assert isinstance(e, GreaterThan) + assert e.lhs == z + assert e.rhs == y + + e = Relational(x, y, '<=') + e = e.subs(x, z) + + assert isinstance(e, LessThan) + assert e.lhs == z + assert e.rhs == y + + e = Relational(x, y, '>') + e = e.subs(x, z) + + assert isinstance(e, StrictGreaterThan) + assert e.lhs == z + assert e.rhs == y + + e = Relational(x, y, '<') + e = e.subs(x, z) + + assert isinstance(e, StrictLessThan) + assert e.lhs == z + assert e.rhs == y + + e = Eq(x, 0) + assert e.subs(x, 0) is S.true + assert e.subs(x, 1) is S.false + + +def test_wrappers(): + e = x + x**2 + + res = Relational(y, e, '==') + assert Rel(y, x + x**2, '==') == res + assert Eq(y, x + x**2) == res + + res = Relational(y, e, '<') + assert Lt(y, x + x**2) == res + + res = Relational(y, e, '<=') + assert Le(y, x + x**2) == res + + res = Relational(y, e, '>') + assert Gt(y, x + x**2) == res + + res = Relational(y, e, '>=') + assert Ge(y, x + x**2) == res + + res = Relational(y, e, '!=') + assert Ne(y, x + x**2) == res + + +def test_Eq_Ne(): + + assert Eq(x, x) # issue 5719 + + # issue 6116 + p = Symbol('p', positive=True) + assert Eq(p, 0) is S.false + + # issue 13348; 19048 + # SymPy is strict about 0 and 1 not being + # interpreted as Booleans + assert Eq(True, 1) is S.false + assert Eq(False, 0) is S.false + assert Eq(~x, 0) is S.false + assert Eq(~x, 1) is S.false + assert Ne(True, 1) is S.true + assert Ne(False, 0) is S.true + assert Ne(~x, 0) is S.true + assert Ne(~x, 1) is S.true + + assert Eq((), 1) is S.false + assert Ne((), 1) is S.true + + +def test_as_poly(): + from sympy.polys.polytools import Poly + # Only Eq should have an as_poly method: + assert Eq(x, 1).as_poly() == Poly(x - 1, x, domain='ZZ') + raises(AttributeError, lambda: Ne(x, 1).as_poly()) + raises(AttributeError, lambda: Ge(x, 1).as_poly()) + raises(AttributeError, lambda: Gt(x, 1).as_poly()) + raises(AttributeError, lambda: Le(x, 1).as_poly()) + raises(AttributeError, lambda: Lt(x, 1).as_poly()) + + +def test_rel_Infinity(): + # NOTE: All of these are actually handled by sympy.core.Number, and do + # not create Relational objects. + assert (oo > oo) is S.false + assert (oo > -oo) is S.true + assert (oo > 1) is S.true + assert (oo < oo) is S.false + assert (oo < -oo) is S.false + assert (oo < 1) is S.false + assert (oo >= oo) is S.true + assert (oo >= -oo) is S.true + assert (oo >= 1) is S.true + assert (oo <= oo) is S.true + assert (oo <= -oo) is S.false + assert (oo <= 1) is S.false + assert (-oo > oo) is S.false + assert (-oo > -oo) is S.false + assert (-oo > 1) is S.false + assert (-oo < oo) is S.true + assert (-oo < -oo) is S.false + assert (-oo < 1) is S.true + assert (-oo >= oo) is S.false + assert (-oo >= -oo) is S.true + assert (-oo >= 1) is S.false + assert (-oo <= oo) is S.true + assert (-oo <= -oo) is S.true + assert (-oo <= 1) is S.true + + +def test_infinite_symbol_inequalities(): + x = Symbol('x', extended_positive=True, infinite=True) + y = Symbol('y', extended_positive=True, infinite=True) + z = Symbol('z', extended_negative=True, infinite=True) + w = Symbol('w', extended_negative=True, infinite=True) + + inf_set = (x, y, oo) + ninf_set = (z, w, -oo) + + for inf1 in inf_set: + assert (inf1 < 1) is S.false + assert (inf1 > 1) is S.true + assert (inf1 <= 1) is S.false + assert (inf1 >= 1) is S.true + + for inf2 in inf_set: + assert (inf1 < inf2) is S.false + assert (inf1 > inf2) is S.false + assert (inf1 <= inf2) is S.true + assert (inf1 >= inf2) is S.true + + for ninf1 in ninf_set: + assert (inf1 < ninf1) is S.false + assert (inf1 > ninf1) is S.true + assert (inf1 <= ninf1) is S.false + assert (inf1 >= ninf1) is S.true + assert (ninf1 < inf1) is S.true + assert (ninf1 > inf1) is S.false + assert (ninf1 <= inf1) is S.true + assert (ninf1 >= inf1) is S.false + + for ninf1 in ninf_set: + assert (ninf1 < 1) is S.true + assert (ninf1 > 1) is S.false + assert (ninf1 <= 1) is S.true + assert (ninf1 >= 1) is S.false + + for ninf2 in ninf_set: + assert (ninf1 < ninf2) is S.false + assert (ninf1 > ninf2) is S.false + assert (ninf1 <= ninf2) is S.true + assert (ninf1 >= ninf2) is S.true + + +def test_bool(): + assert Eq(0, 0) is S.true + assert Eq(1, 0) is S.false + assert Ne(0, 0) is S.false + assert Ne(1, 0) is S.true + assert Lt(0, 1) is S.true + assert Lt(1, 0) is S.false + assert Le(0, 1) is S.true + assert Le(1, 0) is S.false + assert Le(0, 0) is S.true + assert Gt(1, 0) is S.true + assert Gt(0, 1) is S.false + assert Ge(1, 0) is S.true + assert Ge(0, 1) is S.false + assert Ge(1, 1) is S.true + assert Eq(I, 2) is S.false + assert Ne(I, 2) is S.true + raises(TypeError, lambda: Gt(I, 2)) + raises(TypeError, lambda: Ge(I, 2)) + raises(TypeError, lambda: Lt(I, 2)) + raises(TypeError, lambda: Le(I, 2)) + a = Float('.000000000000000000001', '') + b = Float('.0000000000000000000001', '') + assert Eq(pi + a, pi + b) is S.false + + +def test_rich_cmp(): + assert (x < y) == Lt(x, y) + assert (x <= y) == Le(x, y) + assert (x > y) == Gt(x, y) + assert (x >= y) == Ge(x, y) + + +def test_doit(): + from sympy.core.symbol import Symbol + p = Symbol('p', positive=True) + n = Symbol('n', negative=True) + np = Symbol('np', nonpositive=True) + nn = Symbol('nn', nonnegative=True) + + assert Gt(p, 0).doit() is S.true + assert Gt(p, 1).doit() == Gt(p, 1) + assert Ge(p, 0).doit() is S.true + assert Le(p, 0).doit() is S.false + assert Lt(n, 0).doit() is S.true + assert Le(np, 0).doit() is S.true + assert Gt(nn, 0).doit() == Gt(nn, 0) + assert Lt(nn, 0).doit() is S.false + + assert Eq(x, 0).doit() == Eq(x, 0) + + +def test_new_relational(): + x = Symbol('x') + + assert Eq(x, 0) == Relational(x, 0) # None ==> Equality + assert Eq(x, 0) == Relational(x, 0, '==') + assert Eq(x, 0) == Relational(x, 0, 'eq') + assert Eq(x, 0) == Equality(x, 0) + + assert Eq(x, 0) != Relational(x, 1) # None ==> Equality + assert Eq(x, 0) != Relational(x, 1, '==') + assert Eq(x, 0) != Relational(x, 1, 'eq') + assert Eq(x, 0) != Equality(x, 1) + + assert Eq(x, -1) == Relational(x, -1) # None ==> Equality + assert Eq(x, -1) == Relational(x, -1, '==') + assert Eq(x, -1) == Relational(x, -1, 'eq') + assert Eq(x, -1) == Equality(x, -1) + assert Eq(x, -1) != Relational(x, 1) # None ==> Equality + assert Eq(x, -1) != Relational(x, 1, '==') + assert Eq(x, -1) != Relational(x, 1, 'eq') + assert Eq(x, -1) != Equality(x, 1) + + assert Ne(x, 0) == Relational(x, 0, '!=') + assert Ne(x, 0) == Relational(x, 0, '<>') + assert Ne(x, 0) == Relational(x, 0, 'ne') + assert Ne(x, 0) == Unequality(x, 0) + assert Ne(x, 0) != Relational(x, 1, '!=') + assert Ne(x, 0) != Relational(x, 1, '<>') + assert Ne(x, 0) != Relational(x, 1, 'ne') + assert Ne(x, 0) != Unequality(x, 1) + + assert Ge(x, 0) == Relational(x, 0, '>=') + assert Ge(x, 0) == Relational(x, 0, 'ge') + assert Ge(x, 0) == GreaterThan(x, 0) + assert Ge(x, 1) != Relational(x, 0, '>=') + assert Ge(x, 1) != Relational(x, 0, 'ge') + assert Ge(x, 1) != GreaterThan(x, 0) + assert (x >= 1) == Relational(x, 1, '>=') + assert (x >= 1) == Relational(x, 1, 'ge') + assert (x >= 1) == GreaterThan(x, 1) + assert (x >= 0) != Relational(x, 1, '>=') + assert (x >= 0) != Relational(x, 1, 'ge') + assert (x >= 0) != GreaterThan(x, 1) + + assert Le(x, 0) == Relational(x, 0, '<=') + assert Le(x, 0) == Relational(x, 0, 'le') + assert Le(x, 0) == LessThan(x, 0) + assert Le(x, 1) != Relational(x, 0, '<=') + assert Le(x, 1) != Relational(x, 0, 'le') + assert Le(x, 1) != LessThan(x, 0) + assert (x <= 1) == Relational(x, 1, '<=') + assert (x <= 1) == Relational(x, 1, 'le') + assert (x <= 1) == LessThan(x, 1) + assert (x <= 0) != Relational(x, 1, '<=') + assert (x <= 0) != Relational(x, 1, 'le') + assert (x <= 0) != LessThan(x, 1) + + assert Gt(x, 0) == Relational(x, 0, '>') + assert Gt(x, 0) == Relational(x, 0, 'gt') + assert Gt(x, 0) == StrictGreaterThan(x, 0) + assert Gt(x, 1) != Relational(x, 0, '>') + assert Gt(x, 1) != Relational(x, 0, 'gt') + assert Gt(x, 1) != StrictGreaterThan(x, 0) + assert (x > 1) == Relational(x, 1, '>') + assert (x > 1) == Relational(x, 1, 'gt') + assert (x > 1) == StrictGreaterThan(x, 1) + assert (x > 0) != Relational(x, 1, '>') + assert (x > 0) != Relational(x, 1, 'gt') + assert (x > 0) != StrictGreaterThan(x, 1) + + assert Lt(x, 0) == Relational(x, 0, '<') + assert Lt(x, 0) == Relational(x, 0, 'lt') + assert Lt(x, 0) == StrictLessThan(x, 0) + assert Lt(x, 1) != Relational(x, 0, '<') + assert Lt(x, 1) != Relational(x, 0, 'lt') + assert Lt(x, 1) != StrictLessThan(x, 0) + assert (x < 1) == Relational(x, 1, '<') + assert (x < 1) == Relational(x, 1, 'lt') + assert (x < 1) == StrictLessThan(x, 1) + assert (x < 0) != Relational(x, 1, '<') + assert (x < 0) != Relational(x, 1, 'lt') + assert (x < 0) != StrictLessThan(x, 1) + + # finally, some fuzz testing + from sympy.core.random import randint + for i in range(100): + while 1: + strtype, length = (chr, 65535) if randint(0, 1) else (chr, 255) + relation_type = strtype(randint(0, length)) + if randint(0, 1): + relation_type += strtype(randint(0, length)) + if relation_type not in ('==', 'eq', '!=', '<>', 'ne', '>=', 'ge', + '<=', 'le', '>', 'gt', '<', 'lt', ':=', + '+=', '-=', '*=', '/=', '%='): + break + + raises(ValueError, lambda: Relational(x, 1, relation_type)) + assert all(Relational(x, 0, op).rel_op == '==' for op in ('eq', '==')) + assert all(Relational(x, 0, op).rel_op == '!=' + for op in ('ne', '<>', '!=')) + assert all(Relational(x, 0, op).rel_op == '>' for op in ('gt', '>')) + assert all(Relational(x, 0, op).rel_op == '<' for op in ('lt', '<')) + assert all(Relational(x, 0, op).rel_op == '>=' for op in ('ge', '>=')) + assert all(Relational(x, 0, op).rel_op == '<=' for op in ('le', '<=')) + + +def test_relational_arithmetic(): + for cls in [Eq, Ne, Le, Lt, Ge, Gt]: + rel = cls(x, y) + raises(TypeError, lambda: 0+rel) + raises(TypeError, lambda: 1*rel) + raises(TypeError, lambda: 1**rel) + raises(TypeError, lambda: rel**1) + raises(TypeError, lambda: Add(0, rel)) + raises(TypeError, lambda: Mul(1, rel)) + raises(TypeError, lambda: Pow(1, rel)) + raises(TypeError, lambda: Pow(rel, 1)) + + +def test_relational_bool_output(): + # https://github.com/sympy/sympy/issues/5931 + raises(TypeError, lambda: bool(x > 3)) + raises(TypeError, lambda: bool(x >= 3)) + raises(TypeError, lambda: bool(x < 3)) + raises(TypeError, lambda: bool(x <= 3)) + raises(TypeError, lambda: bool(Eq(x, 3))) + raises(TypeError, lambda: bool(Ne(x, 3))) + + +def test_relational_logic_symbols(): + # See issue 6204 + assert (x < y) & (z < t) == And(x < y, z < t) + assert (x < y) | (z < t) == Or(x < y, z < t) + assert ~(x < y) == Not(x < y) + assert (x < y) >> (z < t) == Implies(x < y, z < t) + assert (x < y) << (z < t) == Implies(z < t, x < y) + assert (x < y) ^ (z < t) == Xor(x < y, z < t) + + assert isinstance((x < y) & (z < t), And) + assert isinstance((x < y) | (z < t), Or) + assert isinstance(~(x < y), GreaterThan) + assert isinstance((x < y) >> (z < t), Implies) + assert isinstance((x < y) << (z < t), Implies) + assert isinstance((x < y) ^ (z < t), (Or, Xor)) + + +def test_univariate_relational_as_set(): + assert (x > 0).as_set() == Interval(0, oo, True, True) + assert (x >= 0).as_set() == Interval(0, oo) + assert (x < 0).as_set() == Interval(-oo, 0, True, True) + assert (x <= 0).as_set() == Interval(-oo, 0) + assert Eq(x, 0).as_set() == FiniteSet(0) + assert Ne(x, 0).as_set() == Interval(-oo, 0, True, True) + \ + Interval(0, oo, True, True) + + assert (x**2 >= 4).as_set() == Interval(-oo, -2) + Interval(2, oo) + + +@XFAIL +def test_multivariate_relational_as_set(): + assert (x*y >= 0).as_set() == Interval(0, oo)*Interval(0, oo) + \ + Interval(-oo, 0)*Interval(-oo, 0) + + +def test_Not(): + assert Not(Equality(x, y)) == Unequality(x, y) + assert Not(Unequality(x, y)) == Equality(x, y) + assert Not(StrictGreaterThan(x, y)) == LessThan(x, y) + assert Not(StrictLessThan(x, y)) == GreaterThan(x, y) + assert Not(GreaterThan(x, y)) == StrictLessThan(x, y) + assert Not(LessThan(x, y)) == StrictGreaterThan(x, y) + + +def test_evaluate(): + assert str(Eq(x, x, evaluate=False)) == 'Eq(x, x)' + assert Eq(x, x, evaluate=False).doit() == S.true + assert str(Ne(x, x, evaluate=False)) == 'Ne(x, x)' + assert Ne(x, x, evaluate=False).doit() == S.false + + assert str(Ge(x, x, evaluate=False)) == 'x >= x' + assert str(Le(x, x, evaluate=False)) == 'x <= x' + assert str(Gt(x, x, evaluate=False)) == 'x > x' + assert str(Lt(x, x, evaluate=False)) == 'x < x' + + +def assert_all_ineq_raise_TypeError(a, b): + raises(TypeError, lambda: a > b) + raises(TypeError, lambda: a >= b) + raises(TypeError, lambda: a < b) + raises(TypeError, lambda: a <= b) + raises(TypeError, lambda: b > a) + raises(TypeError, lambda: b >= a) + raises(TypeError, lambda: b < a) + raises(TypeError, lambda: b <= a) + + +def assert_all_ineq_give_class_Inequality(a, b): + """All inequality operations on `a` and `b` result in class Inequality.""" + from sympy.core.relational import _Inequality as Inequality + assert isinstance(a > b, Inequality) + assert isinstance(a >= b, Inequality) + assert isinstance(a < b, Inequality) + assert isinstance(a <= b, Inequality) + assert isinstance(b > a, Inequality) + assert isinstance(b >= a, Inequality) + assert isinstance(b < a, Inequality) + assert isinstance(b <= a, Inequality) + + +def test_imaginary_compare_raises_TypeError(): + # See issue #5724 + assert_all_ineq_raise_TypeError(I, x) + + +def test_complex_compare_not_real(): + # two cases which are not real + y = Symbol('y', imaginary=True) + z = Symbol('z', complex=True, extended_real=False) + for w in (y, z): + assert_all_ineq_raise_TypeError(2, w) + # some cases which should remain un-evaluated + t = Symbol('t') + x = Symbol('x', real=True) + z = Symbol('z', complex=True) + for w in (x, z, t): + assert_all_ineq_give_class_Inequality(2, w) + + +def test_imaginary_and_inf_compare_raises_TypeError(): + # See pull request #7835 + y = Symbol('y', imaginary=True) + assert_all_ineq_raise_TypeError(oo, y) + assert_all_ineq_raise_TypeError(-oo, y) + + +def test_complex_pure_imag_not_ordered(): + raises(TypeError, lambda: 2*I < 3*I) + + # more generally + x = Symbol('x', real=True, nonzero=True) + y = Symbol('y', imaginary=True) + z = Symbol('z', complex=True) + assert_all_ineq_raise_TypeError(I, y) + + t = I*x # an imaginary number, should raise errors + assert_all_ineq_raise_TypeError(2, t) + + t = -I*y # a real number, so no errors + assert_all_ineq_give_class_Inequality(2, t) + + t = I*z # unknown, should be unevaluated + assert_all_ineq_give_class_Inequality(2, t) + + +def test_x_minus_y_not_same_as_x_lt_y(): + """ + A consequence of pull request #7792 is that `x - y < 0` and `x < y` + are not synonymous. + """ + x = I + 2 + y = I + 3 + raises(TypeError, lambda: x < y) + assert x - y < 0 + + ineq = Lt(x, y, evaluate=False) + raises(TypeError, lambda: ineq.doit()) + assert ineq.lhs - ineq.rhs < 0 + + t = Symbol('t', imaginary=True) + x = 2 + t + y = 3 + t + ineq = Lt(x, y, evaluate=False) + raises(TypeError, lambda: ineq.doit()) + assert ineq.lhs - ineq.rhs < 0 + + # this one should give error either way + x = I + 2 + y = 2*I + 3 + raises(TypeError, lambda: x < y) + raises(TypeError, lambda: x - y < 0) + + +def test_nan_equality_exceptions(): + # See issue #7774 + import random + assert Equality(nan, nan) is S.false + assert Unequality(nan, nan) is S.true + + # See issue #7773 + A = (x, S.Zero, S.One/3, pi, oo, -oo) + assert Equality(nan, random.choice(A)) is S.false + assert Equality(random.choice(A), nan) is S.false + assert Unequality(nan, random.choice(A)) is S.true + assert Unequality(random.choice(A), nan) is S.true + + +def test_nan_inequality_raise_errors(): + # See discussion in pull request #7776. We test inequalities with + # a set including examples of various classes. + for q in (x, S.Zero, S(10), S.One/3, pi, S(1.3), oo, -oo, nan): + assert_all_ineq_raise_TypeError(q, nan) + + +def test_nan_complex_inequalities(): + # Comparisons of NaN with non-real raise errors, we're not too + # fussy whether its the NaN error or complex error. + for r in (I, zoo, Symbol('z', imaginary=True)): + assert_all_ineq_raise_TypeError(r, nan) + + +def test_complex_infinity_inequalities(): + raises(TypeError, lambda: zoo > 0) + raises(TypeError, lambda: zoo >= 0) + raises(TypeError, lambda: zoo < 0) + raises(TypeError, lambda: zoo <= 0) + + +def test_inequalities_symbol_name_same(): + """Using the operator and functional forms should give same results.""" + # We test all combinations from a set + # FIXME: could replace with random selection after test passes + A = (x, y, S.Zero, S.One/3, pi, oo, -oo) + for a in A: + for b in A: + assert Gt(a, b) == (a > b) + assert Lt(a, b) == (a < b) + assert Ge(a, b) == (a >= b) + assert Le(a, b) == (a <= b) + + for b in (y, S.Zero, S.One/3, pi, oo, -oo): + assert Gt(x, b, evaluate=False) == (x > b) + assert Lt(x, b, evaluate=False) == (x < b) + assert Ge(x, b, evaluate=False) == (x >= b) + assert Le(x, b, evaluate=False) == (x <= b) + + for b in (y, S.Zero, S.One/3, pi, oo, -oo): + assert Gt(b, x, evaluate=False) == (b > x) + assert Lt(b, x, evaluate=False) == (b < x) + assert Ge(b, x, evaluate=False) == (b >= x) + assert Le(b, x, evaluate=False) == (b <= x) + + +def test_inequalities_symbol_name_same_complex(): + """Using the operator and functional forms should give same results. + With complex non-real numbers, both should raise errors. + """ + # FIXME: could replace with random selection after test passes + for a in (x, S.Zero, S.One/3, pi, oo, Rational(1, 3)): + raises(TypeError, lambda: Gt(a, I)) + raises(TypeError, lambda: a > I) + raises(TypeError, lambda: Lt(a, I)) + raises(TypeError, lambda: a < I) + raises(TypeError, lambda: Ge(a, I)) + raises(TypeError, lambda: a >= I) + raises(TypeError, lambda: Le(a, I)) + raises(TypeError, lambda: a <= I) + + +def test_inequalities_cant_sympify_other(): + # see issue 7833 + from operator import gt, lt, ge, le + + bar = "foo" + + for a in (x, S.Zero, S.One/3, pi, I, zoo, oo, -oo, nan, Rational(1, 3)): + for op in (lt, gt, le, ge): + raises(TypeError, lambda: op(a, bar)) + + +def test_ineq_avoid_wild_symbol_flip(): + # see issue #7951, we try to avoid this internally, e.g., by using + # __lt__ instead of "<". + from sympy.core.symbol import Wild + p = symbols('p', cls=Wild) + # x > p might flip, but Gt should not: + assert Gt(x, p) == Gt(x, p, evaluate=False) + # Previously failed as 'p > x': + e = Lt(x, y).subs({y: p}) + assert e == Lt(x, p, evaluate=False) + # Previously failed as 'p <= x': + e = Ge(x, p).doit() + assert e == Ge(x, p, evaluate=False) + + +def test_issue_8245(): + a = S("6506833320952669167898688709329/5070602400912917605986812821504") + assert rel_check(a, a.n(10)) + assert rel_check(a, a.n(20)) + assert rel_check(a, a.n()) + # prec of 31 is enough to fully capture a as mpf + assert Float(a, 31) == Float(str(a.p), '')/Float(str(a.q), '') + for i in range(31): + r = Rational(Float(a, i)) + f = Float(r) + assert (f < a) == (Rational(f) < a) + # test sign handling + assert (-f < -a) == (Rational(-f) < -a) + # test equivalence handling + isa = Float(a.p,'')/Float(a.q,'') + assert isa <= a + assert not isa < a + assert isa >= a + assert not isa > a + assert isa > 0 + + a = sqrt(2) + r = Rational(str(a.n(30))) + assert rel_check(a, r) + + a = sqrt(2) + r = Rational(str(a.n(29))) + assert rel_check(a, r) + + assert Eq(log(cos(2)**2 + sin(2)**2), 0) is S.true + + +def test_issue_8449(): + p = Symbol('p', nonnegative=True) + assert Lt(-oo, p) + assert Ge(-oo, p) is S.false + assert Gt(oo, -p) + assert Le(oo, -p) is S.false + + +def test_simplify_relational(): + assert simplify(x*(y + 1) - x*y - x + 1 < x) == (x > 1) + assert simplify(x*(y + 1) - x*y - x - 1 < x) == (x > -1) + assert simplify(x < x*(y + 1) - x*y - x + 1) == (x < 1) + q, r = symbols("q r") + assert (((-q + r) - (q - r)) <= 0).simplify() == (q >= r) + root2 = sqrt(2) + equation = ((root2 * (-q + r) - root2 * (q - r)) <= 0).simplify() + assert equation == (q >= r) + r = S.One < x + # canonical operations are not the same as simplification, + # so if there is no simplification, canonicalization will + # be done unless the measure forbids it + assert simplify(r) == r.canonical + assert simplify(r, ratio=0) != r.canonical + # this is not a random test; in _eval_simplify + # this will simplify to S.false and that is the + # reason for the 'if r.is_Relational' in Relational's + # _eval_simplify routine + assert simplify(-(2**(pi*Rational(3, 2)) + 6**pi)**(1/pi) + + 2*(2**(pi/2) + 3**pi)**(1/pi) < 0) is S.false + # canonical at least + assert Eq(y, x).simplify() == Eq(x, y) + assert Eq(x - 1, 0).simplify() == Eq(x, 1) + assert Eq(x - 1, x).simplify() == S.false + assert Eq(2*x - 1, x).simplify() == Eq(x, 1) + assert Eq(2*x, 4).simplify() == Eq(x, 2) + z = cos(1)**2 + sin(1)**2 - 1 # z.is_zero is None + assert Eq(z*x, 0).simplify() == S.true + + assert Ne(y, x).simplify() == Ne(x, y) + assert Ne(x - 1, 0).simplify() == Ne(x, 1) + assert Ne(x - 1, x).simplify() == S.true + assert Ne(2*x - 1, x).simplify() == Ne(x, 1) + assert Ne(2*x, 4).simplify() == Ne(x, 2) + assert Ne(z*x, 0).simplify() == S.false + + # No real-valued assumptions + assert Ge(y, x).simplify() == Le(x, y) + assert Ge(x - 1, 0).simplify() == Ge(x, 1) + assert Ge(x - 1, x).simplify() == S.false + assert Ge(2*x - 1, x).simplify() == Ge(x, 1) + assert Ge(2*x, 4).simplify() == Ge(x, 2) + assert Ge(z*x, 0).simplify() == S.true + assert Ge(x, -2).simplify() == Ge(x, -2) + assert Ge(-x, -2).simplify() == Le(x, 2) + assert Ge(x, 2).simplify() == Ge(x, 2) + assert Ge(-x, 2).simplify() == Le(x, -2) + + assert Le(y, x).simplify() == Ge(x, y) + assert Le(x - 1, 0).simplify() == Le(x, 1) + assert Le(x - 1, x).simplify() == S.true + assert Le(2*x - 1, x).simplify() == Le(x, 1) + assert Le(2*x, 4).simplify() == Le(x, 2) + assert Le(z*x, 0).simplify() == S.true + assert Le(x, -2).simplify() == Le(x, -2) + assert Le(-x, -2).simplify() == Ge(x, 2) + assert Le(x, 2).simplify() == Le(x, 2) + assert Le(-x, 2).simplify() == Ge(x, -2) + + assert Gt(y, x).simplify() == Lt(x, y) + assert Gt(x - 1, 0).simplify() == Gt(x, 1) + assert Gt(x - 1, x).simplify() == S.false + assert Gt(2*x - 1, x).simplify() == Gt(x, 1) + assert Gt(2*x, 4).simplify() == Gt(x, 2) + assert Gt(z*x, 0).simplify() == S.false + assert Gt(x, -2).simplify() == Gt(x, -2) + assert Gt(-x, -2).simplify() == Lt(x, 2) + assert Gt(x, 2).simplify() == Gt(x, 2) + assert Gt(-x, 2).simplify() == Lt(x, -2) + + assert Lt(y, x).simplify() == Gt(x, y) + assert Lt(x - 1, 0).simplify() == Lt(x, 1) + assert Lt(x - 1, x).simplify() == S.true + assert Lt(2*x - 1, x).simplify() == Lt(x, 1) + assert Lt(2*x, 4).simplify() == Lt(x, 2) + assert Lt(z*x, 0).simplify() == S.false + assert Lt(x, -2).simplify() == Lt(x, -2) + assert Lt(-x, -2).simplify() == Gt(x, 2) + assert Lt(x, 2).simplify() == Lt(x, 2) + assert Lt(-x, 2).simplify() == Gt(x, -2) + + # Test particular branches of _eval_simplify + m = exp(1) - exp_polar(1) + assert simplify(m*x > 1) is S.false + # These two test the same branch + assert simplify(m*x + 2*m*y > 1) is S.false + assert simplify(m*x + y > 1 + y) is S.false + + +def test_equals(): + w, x, y, z = symbols('w:z') + f = Function('f') + assert Eq(x, 1).equals(Eq(x*(y + 1) - x*y - x + 1, x)) + assert Eq(x, y).equals(x < y, True) == False + assert Eq(x, f(1)).equals(Eq(x, f(2)), True) == f(1) - f(2) + assert Eq(f(1), y).equals(Eq(f(2), y), True) == f(1) - f(2) + assert Eq(x, f(1)).equals(Eq(f(2), x), True) == f(1) - f(2) + assert Eq(f(1), x).equals(Eq(x, f(2)), True) == f(1) - f(2) + assert Eq(w, x).equals(Eq(y, z), True) == False + assert Eq(f(1), f(2)).equals(Eq(f(3), f(4)), True) == f(1) - f(3) + assert (x < y).equals(y > x, True) == True + assert (x < y).equals(y >= x, True) == False + assert (x < y).equals(z < y, True) == False + assert (x < y).equals(x < z, True) == False + assert (x < f(1)).equals(x < f(2), True) == f(1) - f(2) + assert (f(1) < x).equals(f(2) < x, True) == f(1) - f(2) + + +def test_reversed(): + assert (x < y).reversed == (y > x) + assert (x <= y).reversed == (y >= x) + assert Eq(x, y, evaluate=False).reversed == Eq(y, x, evaluate=False) + assert Ne(x, y, evaluate=False).reversed == Ne(y, x, evaluate=False) + assert (x >= y).reversed == (y <= x) + assert (x > y).reversed == (y < x) + + +def test_canonical(): + c = [i.canonical for i in ( + x + y < z, + x + 2 > 3, + x < 2, + S(2) > x, + x**2 > -x/y, + Gt(3, 2, evaluate=False) + )] + assert [i.canonical for i in c] == c + assert [i.reversed.canonical for i in c] == c + assert not any(i.lhs.is_Number and not i.rhs.is_Number for i in c) + + c = [i.reversed.func(i.rhs, i.lhs, evaluate=False).canonical for i in c] + assert [i.canonical for i in c] == c + assert [i.reversed.canonical for i in c] == c + assert not any(i.lhs.is_Number and not i.rhs.is_Number for i in c) + assert Eq(y < x, x > y).canonical is S.true + + +@XFAIL +def test_issue_8444_nonworkingtests(): + x = symbols('x', real=True) + assert (x <= oo) == (x >= -oo) == True + + x = symbols('x') + assert x >= floor(x) + assert (x < floor(x)) == False + assert x <= ceiling(x) + assert (x > ceiling(x)) == False + + +def test_issue_8444_workingtests(): + x = symbols('x') + assert Gt(x, floor(x)) == Gt(x, floor(x), evaluate=False) + assert Ge(x, floor(x)) == Ge(x, floor(x), evaluate=False) + assert Lt(x, ceiling(x)) == Lt(x, ceiling(x), evaluate=False) + assert Le(x, ceiling(x)) == Le(x, ceiling(x), evaluate=False) + i = symbols('i', integer=True) + assert (i > floor(i)) == False + assert (i < ceiling(i)) == False + + +def test_issue_10304(): + d = cos(1)**2 + sin(1)**2 - 1 + assert d.is_comparable is False # if this fails, find a new d + e = 1 + d*I + assert simplify(Eq(e, 0)) is S.false + + +def test_issue_18412(): + d = (Rational(1, 6) + z / 4 / y) + assert Eq(x, pi * y**3 * d).replace(y**3, z) == Eq(x, pi * z * d) + + +def test_issue_10401(): + x = symbols('x') + fin = symbols('inf', finite=True) + inf = symbols('inf', infinite=True) + inf2 = symbols('inf2', infinite=True) + infx = symbols('infx', infinite=True, extended_real=True) + # Used in the commented tests below: + #infx2 = symbols('infx2', infinite=True, extended_real=True) + infnx = symbols('inf~x', infinite=True, extended_real=False) + infnx2 = symbols('inf~x2', infinite=True, extended_real=False) + infp = symbols('infp', infinite=True, extended_positive=True) + infp1 = symbols('infp1', infinite=True, extended_positive=True) + infn = symbols('infn', infinite=True, extended_negative=True) + zero = symbols('z', zero=True) + nonzero = symbols('nz', zero=False, finite=True) + + assert Eq(1/(1/x + 1), 1).func is Eq + assert Eq(1/(1/x + 1), 1).subs(x, S.ComplexInfinity) is S.true + assert Eq(1/(1/fin + 1), 1) is S.false + + T, F = S.true, S.false + assert Eq(fin, inf) is F + assert Eq(inf, inf2) not in (T, F) and inf != inf2 + assert Eq(1 + inf, 2 + inf2) not in (T, F) and inf != inf2 + assert Eq(infp, infp1) is T + assert Eq(infp, infn) is F + assert Eq(1 + I*oo, I*oo) is F + assert Eq(I*oo, 1 + I*oo) is F + assert Eq(1 + I*oo, 2 + I*oo) is F + assert Eq(1 + I*oo, 2 + I*infx) is F + assert Eq(1 + I*oo, 2 + infx) is F + # FIXME: The test below fails because (-infx).is_extended_positive is True + # (should be None) + #assert Eq(1 + I*infx, 1 + I*infx2) not in (T, F) and infx != infx2 + # + assert Eq(zoo, sqrt(2) + I*oo) is F + assert Eq(zoo, oo) is F + r = Symbol('r', real=True) + i = Symbol('i', imaginary=True) + assert Eq(i*I, r) not in (T, F) + assert Eq(infx, infnx) is F + assert Eq(infnx, infnx2) not in (T, F) and infnx != infnx2 + assert Eq(zoo, oo) is F + assert Eq(inf/inf2, 0) is F + assert Eq(inf/fin, 0) is F + assert Eq(fin/inf, 0) is T + assert Eq(zero/nonzero, 0) is T and ((zero/nonzero) != 0) + # The commented out test below is incorrect because: + assert zoo == -zoo + assert Eq(zoo, -zoo) is T + assert Eq(oo, -oo) is F + assert Eq(inf, -inf) not in (T, F) + + assert Eq(fin/(fin + 1), 1) is S.false + + o = symbols('o', odd=True) + assert Eq(o, 2*o) is S.false + + p = symbols('p', positive=True) + assert Eq(p/(p - 1), 1) is F + + +def test_issue_10633(): + assert Eq(True, False) == False + assert Eq(False, True) == False + assert Eq(True, True) == True + assert Eq(False, False) == True + + +def test_issue_10927(): + x = symbols('x') + assert str(Eq(x, oo)) == 'Eq(x, oo)' + assert str(Eq(x, -oo)) == 'Eq(x, -oo)' + + +def test_issues_13081_12583_12534(): + # 13081 + r = Rational('905502432259640373/288230376151711744') + assert (r < pi) is S.false + assert (r > pi) is S.true + # 12583 + v = sqrt(2) + u = sqrt(v) + 2/sqrt(10 - 8/sqrt(2 - v) + 4*v*(1/sqrt(2 - v) - 1)) + assert (u >= 0) is S.true + # 12534; Rational vs NumberSymbol + # here are some precisions for which Rational forms + # at a lower and higher precision bracket the value of pi + # e.g. for p = 20: + # Rational(pi.n(p + 1)).n(25) = 3.14159265358979323846 2834 + # pi.n(25) = 3.14159265358979323846 2643 + # Rational(pi.n(p )).n(25) = 3.14159265358979323846 1987 + assert [p for p in range(20, 50) if + (Rational(pi.n(p)) < pi) and + (pi < Rational(pi.n(p + 1)))] == [20, 24, 27, 33, 37, 43, 48] + # pick one such precision and affirm that the reversed operation + # gives the opposite result, i.e. if x < y is true then x > y + # must be false + for i in (20, 21): + v = pi.n(i) + assert rel_check(Rational(v), pi) + assert rel_check(v, pi) + assert rel_check(pi.n(20), pi.n(21)) + # Float vs Rational + # the rational form is less than the floating representation + # at the same precision + assert [i for i in range(15, 50) if Rational(pi.n(i)) > pi.n(i)] == [] + # this should be the same if we reverse the relational + assert [i for i in range(15, 50) if pi.n(i) < Rational(pi.n(i))] == [] + +def test_issue_18188(): + from sympy.sets.conditionset import ConditionSet + result1 = Eq(x*cos(x) - 3*sin(x), 0) + assert result1.as_set() == ConditionSet(x, Eq(x*cos(x) - 3*sin(x), 0), Reals) + + result2 = Eq(x**2 + sqrt(x*2) + sin(x), 0) + assert result2.as_set() == ConditionSet(x, Eq(sqrt(2)*sqrt(x) + x**2 + sin(x), 0), Reals) + +def test_binary_symbols(): + ans = {x} + for f in Eq, Ne: + for t in S.true, S.false: + eq = f(x, S.true) + assert eq.binary_symbols == ans + assert eq.reversed.binary_symbols == ans + assert f(x, 1).binary_symbols == set() + + +def test_rel_args(): + # can't have Boolean args; this is automatic for True/False + # with Python 3 and we confirm that SymPy does the same + # for true/false + for op in ['<', '<=', '>', '>=']: + for b in (S.true, x < 1, And(x, y)): + for v in (0.1, 1, 2**32, t, S.One): + raises(TypeError, lambda: Relational(b, v, op)) + + +def test_nothing_happens_to_Eq_condition_during_simplify(): + # issue 25701 + r = symbols('r', real=True) + assert Eq(2*sign(r + 3)/(5*Abs(r + 3)**Rational(3, 5)), 0 + ).simplify() == Eq(Piecewise( + (0, Eq(r, -3)), ((r + 3)/(5*Abs((r + 3)**Rational(8, 5)))*2, True)), 0) + + +def test_issue_15847(): + a = Ne(x*(x + y), x**2 + x*y) + assert simplify(a) == False + + +def test_negated_property(): + eq = Eq(x, y) + assert eq.negated == Ne(x, y) + + eq = Ne(x, y) + assert eq.negated == Eq(x, y) + + eq = Ge(x + y, y - x) + assert eq.negated == Lt(x + y, y - x) + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(x, y).negated.negated == f(x, y) + + +def test_reversedsign_property(): + eq = Eq(x, y) + assert eq.reversedsign == Eq(-x, -y) + + eq = Ne(x, y) + assert eq.reversedsign == Ne(-x, -y) + + eq = Ge(x + y, y - x) + assert eq.reversedsign == Le(-x - y, x - y) + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(x, y).reversedsign.reversedsign == f(x, y) + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(-x, y).reversedsign.reversedsign == f(-x, y) + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(x, -y).reversedsign.reversedsign == f(x, -y) + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(-x, -y).reversedsign.reversedsign == f(-x, -y) + + +def test_reversed_reversedsign_property(): + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(x, y).reversed.reversedsign == f(x, y).reversedsign.reversed + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(-x, y).reversed.reversedsign == f(-x, y).reversedsign.reversed + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(x, -y).reversed.reversedsign == f(x, -y).reversedsign.reversed + + for f in (Eq, Ne, Ge, Gt, Le, Lt): + assert f(-x, -y).reversed.reversedsign == \ + f(-x, -y).reversedsign.reversed + + +def test_improved_canonical(): + def test_different_forms(listofforms): + for form1, form2 in combinations(listofforms, 2): + assert form1.canonical == form2.canonical + + def generate_forms(expr): + return [expr, expr.reversed, expr.reversedsign, + expr.reversed.reversedsign] + + test_different_forms(generate_forms(x > -y)) + test_different_forms(generate_forms(x >= -y)) + test_different_forms(generate_forms(Eq(x, -y))) + test_different_forms(generate_forms(Ne(x, -y))) + test_different_forms(generate_forms(pi < x)) + test_different_forms(generate_forms(pi - 5*y < -x + 2*y**2 - 7)) + + assert (pi >= x).canonical == (x <= pi) + + +def test_set_equality_canonical(): + a, b, c = symbols('a b c') + + A = Eq(FiniteSet(a, b, c), FiniteSet(1, 2, 3)) + B = Ne(FiniteSet(a, b, c), FiniteSet(4, 5, 6)) + + assert A.canonical == A.reversed + assert B.canonical == B.reversed + + +def test_trigsimp(): + # issue 16736 + s, c = sin(2*x), cos(2*x) + eq = Eq(s, c) + assert trigsimp(eq) == eq # no rearrangement of sides + # simplification of sides might result in + # an unevaluated Eq + changed = trigsimp(Eq(s + c, sqrt(2))) + assert isinstance(changed, Eq) + assert changed.subs(x, pi/8) is S.true + # or an evaluated one + assert trigsimp(Eq(cos(x)**2 + sin(x)**2, 1)) is S.true + + +def test_polynomial_relation_simplification(): + assert Ge(3*x*(x + 1) + 4, 3*x).simplify() in [Ge(x**2, -Rational(4,3)), Le(-x**2, Rational(4, 3))] + assert Le(-(3*x*(x + 1) + 4), -3*x).simplify() in [Ge(x**2, -Rational(4,3)), Le(-x**2, Rational(4, 3))] + assert ((x**2+3)*(x**2-1)+3*x >= 2*x**2).simplify() in [(x**4 + 3*x >= 3), (-x**4 - 3*x <= -3)] + + +def test_multivariate_linear_function_simplification(): + assert Ge(x + y, x - y).simplify() == Ge(y, 0) + assert Le(-x + y, -x - y).simplify() == Le(y, 0) + assert Eq(2*x + y, 2*x + y - 3).simplify() == False + assert (2*x + y > 2*x + y - 3).simplify() == True + assert (2*x + y < 2*x + y - 3).simplify() == False + assert (2*x + y < 2*x + y + 3).simplify() == True + a, b, c, d, e, f, g = symbols('a b c d e f g') + assert Lt(a + b + c + 2*d, 3*d - f + g). simplify() == Lt(a, -b - c + d - f + g) + + +def test_nonpolymonial_relations(): + assert Eq(cos(x), 0).simplify() == Eq(cos(x), 0) + +def test_18778(): + raises(TypeError, lambda: is_le(Basic(), Basic())) + raises(TypeError, lambda: is_gt(Basic(), Basic())) + raises(TypeError, lambda: is_ge(Basic(), Basic())) + raises(TypeError, lambda: is_lt(Basic(), Basic())) + +def test_EvalEq(): + """ + + This test exists to ensure backwards compatibility. + The method to use is _eval_is_eq + """ + from sympy.core.expr import Expr + + class PowTest(Expr): + def __new__(cls, base, exp): + return Basic.__new__(PowTest, _sympify(base), _sympify(exp)) + + def _eval_Eq(lhs, rhs): + if type(lhs) == PowTest and type(rhs) == PowTest: + return lhs.args[0] == rhs.args[0] and lhs.args[1] == rhs.args[1] + + assert is_eq(PowTest(3, 4), PowTest(3,4)) + assert is_eq(PowTest(3, 4), _sympify(4)) is None + assert is_neq(PowTest(3, 4), PowTest(3,7)) + + +def test_is_eq(): + # test assumptions + assert is_eq(x, y, Q.infinite(x) & Q.finite(y)) is False + assert is_eq(x, y, Q.infinite(x) & Q.infinite(y) & Q.extended_real(x) & ~Q.extended_real(y)) is False + assert is_eq(x, y, Q.infinite(x) & Q.infinite(y) & Q.extended_positive(x) & Q.extended_negative(y)) is False + + assert is_eq(x+I, y+I, Q.infinite(x) & Q.finite(y)) is False + assert is_eq(1+x*I, 1+y*I, Q.infinite(x) & Q.finite(y)) is False + + assert is_eq(x, S(0), assumptions=Q.zero(x)) + assert is_eq(x, S(0), assumptions=~Q.zero(x)) is False + assert is_eq(x, S(0), assumptions=Q.nonzero(x)) is False + assert is_neq(x, S(0), assumptions=Q.zero(x)) is False + assert is_neq(x, S(0), assumptions=~Q.zero(x)) + assert is_neq(x, S(0), assumptions=Q.nonzero(x)) + + # test registration + class PowTest(Expr): + def __new__(cls, base, exp): + return Basic.__new__(cls, _sympify(base), _sympify(exp)) + + @dispatch(PowTest, PowTest) + def _eval_is_eq(lhs, rhs): + if type(lhs) == PowTest and type(rhs) == PowTest: + return fuzzy_and([is_eq(lhs.args[0], rhs.args[0]), is_eq(lhs.args[1], rhs.args[1])]) + + assert is_eq(PowTest(3, 4), PowTest(3,4)) + assert is_eq(PowTest(3, 4), _sympify(4)) is None + assert is_neq(PowTest(3, 4), PowTest(3,7)) + + +def test_is_ge_le(): + # test assumptions + assert is_ge(x, S(0), Q.nonnegative(x)) is True + assert is_ge(x, S(0), Q.negative(x)) is False + + # test registration + class PowTest(Expr): + def __new__(cls, base, exp): + return Basic.__new__(cls, _sympify(base), _sympify(exp)) + + @dispatch(PowTest, PowTest) + def _eval_is_ge(lhs, rhs): + if type(lhs) == PowTest and type(rhs) == PowTest: + return fuzzy_and([is_ge(lhs.args[0], rhs.args[0]), is_ge(lhs.args[1], rhs.args[1])]) + + assert is_ge(PowTest(3, 9), PowTest(3,2)) + assert is_gt(PowTest(3, 9), PowTest(3,2)) + assert is_le(PowTest(3, 2), PowTest(3,9)) + assert is_lt(PowTest(3, 2), PowTest(3,9)) + + +def test_weak_strict(): + for func in (Eq, Ne): + eq = func(x, 1) + assert eq.strict == eq.weak == eq + eq = Gt(x, 1) + assert eq.weak == Ge(x, 1) + assert eq.strict == eq + eq = Lt(x, 1) + assert eq.weak == Le(x, 1) + assert eq.strict == eq + eq = Ge(x, 1) + assert eq.strict == Gt(x, 1) + assert eq.weak == eq + eq = Le(x, 1) + assert eq.strict == Lt(x, 1) + assert eq.weak == eq + + +def test_issue_23731(): + i = symbols('i', integer=True) + assert unchanged(Eq, i, 1.0) + assert unchanged(Eq, i/2, 0.5) + ni = symbols('ni', integer=False) + assert Eq(ni, 1) == False + assert unchanged(Eq, ni, .1) + assert Eq(ni, 1.0) == False + nr = symbols('nr', rational=False) + assert Eq(nr, .1) == False + + +def test_rewrite_Add(): + from sympy.testing.pytest import warns_deprecated_sympy + with warns_deprecated_sympy(): + assert Eq(x, y).rewrite(Add) == x - y diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_rules.py b/phivenv/Lib/site-packages/sympy/core/tests/test_rules.py new file mode 100644 index 0000000000000000000000000000000000000000..31cb88b52db21f39653033b4567526e992be99f0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_rules.py @@ -0,0 +1,14 @@ +from sympy.core.rules import Transform + +from sympy.testing.pytest import raises + + +def test_Transform(): + add1 = Transform(lambda x: x + 1, lambda x: x % 2 == 1) + assert add1[1] == 2 + assert (1 in add1) is True + assert add1.get(1) == 2 + + raises(KeyError, lambda: add1[2]) + assert (2 in add1) is False + assert add1.get(2) is None diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_singleton.py b/phivenv/Lib/site-packages/sympy/core/tests/test_singleton.py new file mode 100644 index 0000000000000000000000000000000000000000..893713f27d74b884391ad800d186eafe5337ab1c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_singleton.py @@ -0,0 +1,76 @@ +from sympy.core.basic import Basic +from sympy.core.numbers import Rational +from sympy.core.singleton import S, Singleton + +def test_Singleton(): + + class MySingleton(Basic, metaclass=Singleton): + pass + + MySingleton() # force instantiation + assert MySingleton() is not Basic() + assert MySingleton() is MySingleton() + assert S.MySingleton is MySingleton() + + class MySingleton_sub(MySingleton): + pass + + MySingleton_sub() + assert MySingleton_sub() is not MySingleton() + assert MySingleton_sub() is MySingleton_sub() + +def test_singleton_redefinition(): + class TestSingleton(Basic, metaclass=Singleton): + pass + + assert TestSingleton() is S.TestSingleton + + class TestSingleton(Basic, metaclass=Singleton): + pass + + assert TestSingleton() is S.TestSingleton + +def test_names_in_namespace(): + # Every singleton name should be accessible from the 'from sympy import *' + # namespace in addition to the S object. However, it does not need to be + # by the same name (e.g., oo instead of S.Infinity). + + # As a general rule, things should only be added to the singleton registry + # if they are used often enough that code can benefit either from the + # performance benefit of being able to use 'is' (this only matters in very + # tight loops), or from the memory savings of having exactly one instance + # (this matters for the numbers singletons, but very little else). The + # singleton registry is already a bit overpopulated, and things cannot be + # removed from it without breaking backwards compatibility. So if you got + # here by adding something new to the singletons, ask yourself if it + # really needs to be singletonized. Note that SymPy classes compare to one + # another just fine, so Class() == Class() will give True even if each + # Class() returns a new instance. Having unique instances is only + # necessary for the above noted performance gains. It should not be needed + # for any behavioral purposes. + + # If you determine that something really should be a singleton, it must be + # accessible to sympify() without using 'S' (hence this test). Also, its + # str printer should print a form that does not use S. This is because + # sympify() disables attribute lookups by default for safety purposes. + d = {} + exec('from sympy import *', d) + + for name in dir(S) + list(S._classes_to_install): + if name.startswith('_'): + continue + if name == 'register': + continue + if isinstance(getattr(S, name), Rational): + continue + if getattr(S, name).__module__.startswith('sympy.physics'): + continue + if name in ['MySingleton', 'MySingleton_sub', 'TestSingleton']: + # From the tests above + continue + if name == 'NegativeInfinity': + # Accessible by -oo + continue + + # Use is here to ensure it is the exact same object + assert any(getattr(S, name) is i for i in d.values()), name diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_sorting.py b/phivenv/Lib/site-packages/sympy/core/tests/test_sorting.py new file mode 100644 index 0000000000000000000000000000000000000000..a18dbfb624552cf2fa11bb7f3c3a9e865caeb0c4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_sorting.py @@ -0,0 +1,28 @@ +from sympy.core.sorting import default_sort_key, ordered +from sympy.testing.pytest import raises + +from sympy.abc import x + + +def test_default_sort_key(): + func = lambda x: x + assert sorted([func, x, func], key=default_sort_key) == [func, func, x] + + class C: + def __repr__(self): + return 'x.y' + func = C() + assert sorted([x, func], key=default_sort_key) == [func, x] + + +def test_ordered(): + # Issue 7210 - this had been failing with python2/3 problems + assert (list(ordered([{1:3, 2:4, 9:10}, {1:3}])) == \ + [{1: 3}, {1: 3, 2: 4, 9: 10}]) + # warnings should not be raised for identical items + l = [1, 1] + assert list(ordered(l, warn=True)) == l + l = [[1], [2], [1]] + assert list(ordered(l, warn=True)) == [[1], [1], [2]] + raises(ValueError, lambda: list(ordered(['a', 'ab'], keys=[lambda x: x[0]], + default=False, warn=True))) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_subs.py b/phivenv/Lib/site-packages/sympy/core/tests/test_subs.py new file mode 100644 index 0000000000000000000000000000000000000000..0803a4b1b5e93b8a35f43516ccef3ab9a16f08ec --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_subs.py @@ -0,0 +1,895 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.function import (Derivative, Function, Lambda, Subs) +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Integer, Rational, oo, pi, zoo) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, Wild, symbols) +from sympy.core.sympify import SympifyError +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (atan2, cos, cot, sin, tan) +from sympy.matrices.dense import (Matrix, zeros) +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.polys.polytools import factor +from sympy.polys.rootoftools import RootOf +from sympy.simplify.cse_main import cse +from sympy.simplify.simplify import nsimplify +from sympy.core.basic import _aresame +from sympy.testing.pytest import XFAIL, raises +from sympy.abc import a, x, y, z, t + + +def test_subs(): + n3 = Rational(3) + e = x + e = e.subs(x, n3) + assert e == Rational(3) + + e = 2*x + assert e == 2*x + e = e.subs(x, n3) + assert e == Rational(6) + + +def test_subs_Matrix(): + z = zeros(2) + z1 = ZeroMatrix(2, 2) + assert (x*y).subs({x:z, y:0}) in [z, z1] + assert (x*y).subs({y:z, x:0}) == 0 + assert (x*y).subs({y:z, x:0}, simultaneous=True) in [z, z1] + assert (x + y).subs({x: z, y: z}, simultaneous=True) in [z, z1] + assert (x + y).subs({x: z, y: z}) in [z, z1] + + # Issue #15528 + assert Mul(Matrix([[3]]), x).subs(x, 2.0) == Matrix([[6.0]]) + # Does not raise a TypeError, see comment on the MatAdd postprocessor + assert Add(Matrix([[3]]), x).subs(x, 2.0) == Add(Matrix([[3]]), 2.0) + + +def test_subs_AccumBounds(): + e = x + e = e.subs(x, AccumBounds(1, 3)) + assert e == AccumBounds(1, 3) + + e = 2*x + e = e.subs(x, AccumBounds(1, 3)) + assert e == AccumBounds(2, 6) + + e = x + x**2 + e = e.subs(x, AccumBounds(-1, 1)) + assert e == AccumBounds(-1, 2) + + +def test_trigonometric(): + n3 = Rational(3) + e = (sin(x)**2).diff(x) + assert e == 2*sin(x)*cos(x) + e = e.subs(x, n3) + assert e == 2*cos(n3)*sin(n3) + + e = (sin(x)**2).diff(x) + assert e == 2*sin(x)*cos(x) + e = e.subs(sin(x), cos(x)) + assert e == 2*cos(x)**2 + + assert exp(pi).subs(exp, sin) == 0 + assert cos(exp(pi)).subs(exp, sin) == 1 + + i = Symbol('i', integer=True) + zoo = S.ComplexInfinity + assert tan(x).subs(x, pi/2) is zoo + assert cot(x).subs(x, pi) is zoo + assert cot(i*x).subs(x, pi) is zoo + assert tan(i*x).subs(x, pi/2) == tan(i*pi/2) + assert tan(i*x).subs(x, pi/2).subs(i, 1) is zoo + o = Symbol('o', odd=True) + assert tan(o*x).subs(x, pi/2) == tan(o*pi/2) + + +def test_powers(): + assert sqrt(1 - sqrt(x)).subs(x, 4) == I + assert (sqrt(1 - x**2)**3).subs(x, 2) == - 3*I*sqrt(3) + assert (x**Rational(1, 3)).subs(x, 27) == 3 + assert (x**Rational(1, 3)).subs(x, -27) == 3*(-1)**Rational(1, 3) + assert ((-x)**Rational(1, 3)).subs(x, 27) == 3*(-1)**Rational(1, 3) + n = Symbol('n', negative=True) + assert (x**n).subs(x, 0) is S.ComplexInfinity + assert exp(-1).subs(S.Exp1, 0) is S.ComplexInfinity + assert (x**(4.0*y)).subs(x**(2.0*y), n) == n**2.0 + assert (2**(x + 2)).subs(2, 3) == 3**(x + 3) + + +def test_logexppow(): # no eval() + x = Symbol('x', real=True) + w = Symbol('w') + e = (3**(1 + x) + 2**(1 + x))/(3**x + 2**x) + assert e.subs(2**x, w) != e + assert e.subs(exp(x*log(Rational(2))), w) != e + + +def test_bug(): + x1 = Symbol('x1') + x2 = Symbol('x2') + y = x1*x2 + assert y.subs(x1, Float(3.0)) == Float(3.0)*x2 + + +def test_subbug1(): + # see that they don't fail + (x**x).subs(x, 1) + (x**x).subs(x, 1.0) + + +def test_subbug2(): + # Ensure this does not cause infinite recursion + assert Float(7.7).epsilon_eq(abs(x).subs(x, -7.7)) + + +def test_dict_set(): + a, b, c = map(Wild, 'abc') + + f = 3*cos(4*x) + r = f.match(a*cos(b*x)) + assert r == {a: 3, b: 4} + e = a/b*sin(b*x) + assert e.subs(r) == r[a]/r[b]*sin(r[b]*x) + assert e.subs(r) == 3*sin(4*x) / 4 + s = set(r.items()) + assert e.subs(s) == r[a]/r[b]*sin(r[b]*x) + assert e.subs(s) == 3*sin(4*x) / 4 + + assert e.subs(r) == r[a]/r[b]*sin(r[b]*x) + assert e.subs(r) == 3*sin(4*x) / 4 + assert x.subs(Dict((x, 1))) == 1 + + +def test_dict_ambigous(): # see issue 3566 + f = x*exp(x) + g = z*exp(z) + + df = {x: y, exp(x): y} + dg = {z: y, exp(z): y} + + assert f.subs(df) == y**2 + assert g.subs(dg) == y**2 + + # and this is how order can affect the result + assert f.subs(x, y).subs(exp(x), y) == y*exp(y) + assert f.subs(exp(x), y).subs(x, y) == y**2 + + # length of args and count_ops are the same so + # default_sort_key resolves ordering...if one + # doesn't want this result then an unordered + # sequence should not be used. + e = 1 + x*y + assert e.subs({x: y, y: 2}) == 5 + # here, there are no obviously clashing keys or values + # but the results depend on the order + assert exp(x/2 + y).subs({exp(y + 1): 2, x: 2}) == exp(y + 1) + + +def test_deriv_sub_bug3(): + f = Function('f') + pat = Derivative(f(x), x, x) + assert pat.subs(y, y**2) == Derivative(f(x), x, x) + assert pat.subs(y, y**2) != Derivative(f(x), x) + + +def test_equality_subs1(): + f = Function('f') + eq = Eq(f(x)**2, x) + res = Eq(Integer(16), x) + assert eq.subs(f(x), 4) == res + + +def test_equality_subs2(): + f = Function('f') + eq = Eq(f(x)**2, 16) + assert bool(eq.subs(f(x), 3)) is False + assert bool(eq.subs(f(x), 4)) is True + + +def test_issue_3742(): + e = sqrt(x)*exp(y) + assert e.subs(sqrt(x), 1) == exp(y) + + +def test_subs_dict1(): + assert (1 + x*y).subs(x, pi) == 1 + pi*y + assert (1 + x*y).subs({x: pi, y: 2}) == 1 + 2*pi + + c2, c3, q1p, q2p, c1, s1, s2, s3 = symbols('c2 c3 q1p q2p c1 s1 s2 s3') + test = (c2**2*q2p*c3 + c1**2*s2**2*q2p*c3 + s1**2*s2**2*q2p*c3 + - c1**2*q1p*c2*s3 - s1**2*q1p*c2*s3) + assert (test.subs({c1**2: 1 - s1**2, c2**2: 1 - s2**2, c3**3: 1 - s3**2}) + == c3*q2p*(1 - s2**2) + c3*q2p*s2**2*(1 - s1**2) + - c2*q1p*s3*(1 - s1**2) + c3*q2p*s1**2*s2**2 - c2*q1p*s3*s1**2) + + +def test_mul(): + x, y, z, a, b, c = symbols('x y z a b c') + A, B, C = symbols('A B C', commutative=0) + assert (x*y*z).subs(z*x, y) == y**2 + assert (z*x).subs(1/x, z) == 1 + assert (x*y/z).subs(1/z, a) == a*x*y + assert (x*y/z).subs(x/z, a) == a*y + assert (x*y/z).subs(y/z, a) == a*x + assert (x*y/z).subs(x/z, 1/a) == y/a + assert (x*y/z).subs(x, 1/a) == y/(z*a) + assert (2*x*y).subs(5*x*y, z) != z*Rational(2, 5) + assert (x*y*A).subs(x*y, a) == a*A + assert (x**2*y**(x*Rational(3, 2))).subs(x*y**(x/2), 2) == 4*y**(x/2) + assert (x*exp(x*2)).subs(x*exp(x), 2) == 2*exp(x) + assert ((x**(2*y))**3).subs(x**y, 2) == 64 + assert (x*A*B).subs(x*A, y) == y*B + assert (x*y*(1 + x)*(1 + x*y)).subs(x*y, 2) == 6*(1 + x) + assert ((1 + A*B)*A*B).subs(A*B, x*A*B) + assert (x*a/z).subs(x/z, A) == a*A + assert (x**3*A).subs(x**2*A, a) == a*x + assert (x**2*A*B).subs(x**2*B, a) == a*A + assert (x**2*A*B).subs(x**2*A, a) == a*B + assert (b*A**3/(a**3*c**3)).subs(a**4*c**3*A**3/b**4, z) == \ + b*A**3/(a**3*c**3) + assert (6*x).subs(2*x, y) == 3*y + assert (y*exp(x*Rational(3, 2))).subs(y*exp(x), 2) == 2*exp(x/2) + assert (y*exp(x*Rational(3, 2))).subs(y*exp(x), 2) == 2*exp(x/2) + assert (A**2*B*A**2*B*A**2).subs(A*B*A, C) == A*C**2*A + assert (x*A**3).subs(x*A, y) == y*A**2 + assert (x**2*A**3).subs(x*A, y) == y**2*A + assert (x*A**3).subs(x*A, B) == B*A**2 + assert (x*A*B*A*exp(x*A*B)).subs(x*A, B) == B**2*A*exp(B*B) + assert (x**2*A*B*A*exp(x*A*B)).subs(x*A, B) == B**3*exp(B**2) + assert (x**3*A*exp(x*A*B)*A*exp(x*A*B)).subs(x*A, B) == \ + x*B*exp(B**2)*B*exp(B**2) + assert (x*A*B*C*A*B).subs(x*A*B, C) == C**2*A*B + assert (-I*a*b).subs(a*b, 2) == -2*I + + # issue 6361 + assert (-8*I*a).subs(-2*a, 1) == 4*I + assert (-I*a).subs(-a, 1) == I + + # issue 6441 + assert (4*x**2).subs(2*x, y) == y**2 + assert (2*4*x**2).subs(2*x, y) == 2*y**2 + assert (-x**3/9).subs(-x/3, z) == -z**2*x + assert (-x**3/9).subs(x/3, z) == -z**2*x + assert (-2*x**3/9).subs(x/3, z) == -2*x*z**2 + assert (-2*x**3/9).subs(-x/3, z) == -2*x*z**2 + assert (-2*x**3/9).subs(-2*x, z) == z*x**2/9 + assert (-2*x**3/9).subs(2*x, z) == -z*x**2/9 + assert (2*(3*x/5/7)**2).subs(3*x/5, z) == 2*(Rational(1, 7))**2*z**2 + assert (4*x).subs(-2*x, z) == 4*x # try keep subs literal + + +def test_subs_simple(): + a = symbols('a', commutative=True) + x = symbols('x', commutative=False) + + assert (2*a).subs(1, 3) == 2*a + assert (2*a).subs(2, 3) == 3*a + assert (2*a).subs(a, 3) == 6 + assert sin(2).subs(1, 3) == sin(2) + assert sin(2).subs(2, 3) == sin(3) + assert sin(a).subs(a, 3) == sin(3) + + assert (2*x).subs(1, 3) == 2*x + assert (2*x).subs(2, 3) == 3*x + assert (2*x).subs(x, 3) == 6 + assert sin(x).subs(x, 3) == sin(3) + + +def test_subs_constants(): + a, b = symbols('a b', commutative=True) + x, y = symbols('x y', commutative=False) + + assert (a*b).subs(2*a, 1) == a*b + assert (1.5*a*b).subs(a, 1) == 1.5*b + assert (2*a*b).subs(2*a, 1) == b + assert (2*a*b).subs(4*a, 1) == 2*a*b + + assert (x*y).subs(2*x, 1) == x*y + assert (1.5*x*y).subs(x, 1) == 1.5*y + assert (2*x*y).subs(2*x, 1) == y + assert (2*x*y).subs(4*x, 1) == 2*x*y + + +def test_subs_commutative(): + a, b, c, d, K = symbols('a b c d K', commutative=True) + + assert (a*b).subs(a*b, K) == K + assert (a*b*a*b).subs(a*b, K) == K**2 + assert (a*a*b*b).subs(a*b, K) == K**2 + assert (a*b*c*d).subs(a*b*c, K) == d*K + assert (a*b**c).subs(a, K) == K*b**c + assert (a*b**c).subs(b, K) == a*K**c + assert (a*b**c).subs(c, K) == a*b**K + assert (a*b*c*b*a).subs(a*b, K) == c*K**2 + assert (a**3*b**2*a).subs(a*b, K) == a**2*K**2 + + +def test_subs_noncommutative(): + w, x, y, z, L = symbols('w x y z L', commutative=False) + alpha = symbols('alpha', commutative=True) + someint = symbols('someint', commutative=True, integer=True) + + assert (x*y).subs(x*y, L) == L + assert (w*y*x).subs(x*y, L) == w*y*x + assert (w*x*y*z).subs(x*y, L) == w*L*z + assert (x*y*x*y).subs(x*y, L) == L**2 + assert (x*x*y).subs(x*y, L) == x*L + assert (x*x*y*y).subs(x*y, L) == x*L*y + assert (w*x*y).subs(x*y*z, L) == w*x*y + assert (x*y**z).subs(x, L) == L*y**z + assert (x*y**z).subs(y, L) == x*L**z + assert (x*y**z).subs(z, L) == x*y**L + assert (w*x*y*z*x*y).subs(x*y*z, L) == w*L*x*y + assert (w*x*y*y*w*x*x*y*x*y*y*x*y).subs(x*y, L) == w*L*y*w*x*L**2*y*L + + # Check fractional power substitutions. It should not do + # substitutions that choose a value for noncommutative log, + # or inverses that don't already appear in the expressions. + assert (x*x*x).subs(x*x, L) == L*x + assert (x*x*x*y*x*x*x*x).subs(x*x, L) == L*x*y*L**2 + for p in range(1, 5): + for k in range(10): + assert (y * x**k).subs(x**p, L) == y * L**(k//p) * x**(k % p) + assert (x**Rational(3, 2)).subs(x**S.Half, L) == x**Rational(3, 2) + assert (x**S.Half).subs(x**S.Half, L) == L + assert (x**Rational(-1, 2)).subs(x**S.Half, L) == x**Rational(-1, 2) + assert (x**Rational(-1, 2)).subs(x**Rational(-1, 2), L) == L + + assert (x**(2*someint)).subs(x**someint, L) == L**2 + assert (x**(2*someint + 3)).subs(x**someint, L) == L**2*x**3 + assert (x**(3*someint + 3)).subs(x**someint, L) == L**3*x**3 + assert (x**(3*someint)).subs(x**(2*someint), L) == L * x**someint + assert (x**(4*someint)).subs(x**(2*someint), L) == L**2 + assert (x**(4*someint + 1)).subs(x**(2*someint), L) == L**2 * x + assert (x**(4*someint)).subs(x**(3*someint), L) == L * x**someint + assert (x**(4*someint + 1)).subs(x**(3*someint), L) == L * x**(someint + 1) + + assert (x**(2*alpha)).subs(x**alpha, L) == x**(2*alpha) + assert (x**(2*alpha + 2)).subs(x**2, L) == x**(2*alpha + 2) + assert ((2*z)**alpha).subs(z**alpha, y) == (2*z)**alpha + assert (x**(2*someint*alpha)).subs(x**someint, L) == x**(2*someint*alpha) + assert (x**(2*someint + alpha)).subs(x**someint, L) == x**(2*someint + alpha) + + # This could in principle be substituted, but is not currently + # because it requires recognizing that someint**2 is divisible by + # someint. + assert (x**(someint**2 + 3)).subs(x**someint, L) == x**(someint**2 + 3) + + # alpha**z := exp(log(alpha) z) is usually well-defined + assert (4**z).subs(2**z, y) == y**2 + + # Negative powers + assert (x**(-1)).subs(x**3, L) == x**(-1) + assert (x**(-2)).subs(x**3, L) == x**(-2) + assert (x**(-3)).subs(x**3, L) == L**(-1) + assert (x**(-4)).subs(x**3, L) == L**(-1) * x**(-1) + assert (x**(-5)).subs(x**3, L) == L**(-1) * x**(-2) + + assert (x**(-1)).subs(x**(-3), L) == x**(-1) + assert (x**(-2)).subs(x**(-3), L) == x**(-2) + assert (x**(-3)).subs(x**(-3), L) == L + assert (x**(-4)).subs(x**(-3), L) == L * x**(-1) + assert (x**(-5)).subs(x**(-3), L) == L * x**(-2) + + assert (x**1).subs(x**(-3), L) == x + assert (x**2).subs(x**(-3), L) == x**2 + assert (x**3).subs(x**(-3), L) == L**(-1) + assert (x**4).subs(x**(-3), L) == L**(-1) * x + assert (x**5).subs(x**(-3), L) == L**(-1) * x**2 + + +def test_subs_basic_funcs(): + a, b, c, d, K = symbols('a b c d K', commutative=True) + w, x, y, z, L = symbols('w x y z L', commutative=False) + + assert (x + y).subs(x + y, L) == L + assert (x - y).subs(x - y, L) == L + assert (x/y).subs(x, L) == L/y + assert (x**y).subs(x, L) == L**y + assert (x**y).subs(y, L) == x**L + assert ((a - c)/b).subs(b, K) == (a - c)/K + assert (exp(x*y - z)).subs(x*y, L) == exp(L - z) + assert (a*exp(x*y - w*z) + b*exp(x*y + w*z)).subs(z, 0) == \ + a*exp(x*y) + b*exp(x*y) + assert ((a - b)/(c*d - a*b)).subs(c*d - a*b, K) == (a - b)/K + assert (w*exp(a*b - c)*x*y/4).subs(x*y, L) == w*exp(a*b - c)*L/4 + + +def test_subs_wild(): + R, S, T, U = symbols('R S T U', cls=Wild) + + assert (R*S).subs(R*S, T) == T + assert (S*R).subs(R*S, T) == T + assert (R + S).subs(R + S, T) == T + assert (R**S).subs(R, T) == T**S + assert (R**S).subs(S, T) == R**T + assert (R*S**T).subs(R, U) == U*S**T + assert (R*S**T).subs(S, U) == R*U**T + assert (R*S**T).subs(T, U) == R*S**U + + +def test_subs_mixed(): + a, b, c, d, K = symbols('a b c d K', commutative=True) + w, x, y, z, L = symbols('w x y z L', commutative=False) + R, S, T, U = symbols('R S T U', cls=Wild) + + assert (a*x*y).subs(x*y, L) == a*L + assert (a*b*x*y*x).subs(x*y, L) == a*b*L*x + assert (R*x*y*exp(x*y)).subs(x*y, L) == R*L*exp(L) + assert (a*x*y*y*x - x*y*z*exp(a*b)).subs(x*y, L) == a*L*y*x - L*z*exp(a*b) + e = c*y*x*y*x**(R*S - a*b) - T*(a*R*b*S) + assert e.subs(x*y, L).subs(a*b, K).subs(R*S, U) == \ + c*y*L*x**(U - K) - T*(U*K) + + +def test_division(): + a, b, c = symbols('a b c', commutative=True) + x, y, z = symbols('x y z', commutative=True) + + assert (1/a).subs(a, c) == 1/c + assert (1/a**2).subs(a, c) == 1/c**2 + assert (1/a**2).subs(a, -2) == Rational(1, 4) + assert (-(1/a**2)).subs(a, -2) == Rational(-1, 4) + + assert (1/x).subs(x, z) == 1/z + assert (1/x**2).subs(x, z) == 1/z**2 + assert (1/x**2).subs(x, -2) == Rational(1, 4) + assert (-(1/x**2)).subs(x, -2) == Rational(-1, 4) + + #issue 5360 + assert (1/x).subs(x, 0) == 1/S.Zero + + +def test_add(): + a, b, c, d, x, y, t = symbols('a b c d x y t') + + assert (a**2 - b - c).subs(a**2 - b, d) in [d - c, a**2 - b - c] + assert (a**2 - c).subs(a**2 - c, d) == d + assert (a**2 - b - c).subs(a**2 - c, d) in [d - b, a**2 - b - c] + assert (a**2 - x - c).subs(a**2 - c, d) in [d - x, a**2 - x - c] + assert (a**2 - b - sqrt(a)).subs(a**2 - sqrt(a), c) == c - b + assert (a + b + exp(a + b)).subs(a + b, c) == c + exp(c) + assert (c + b + exp(c + b)).subs(c + b, a) == a + exp(a) + assert (a + b + c + d).subs(b + c, x) == a + d + x + assert (a + b + c + d).subs(-b - c, x) == a + d - x + assert ((x + 1)*y).subs(x + 1, t) == t*y + assert ((-x - 1)*y).subs(x + 1, t) == -t*y + assert ((x - 1)*y).subs(x + 1, t) == y*(t - 2) + assert ((-x + 1)*y).subs(x + 1, t) == y*(-t + 2) + + # this should work every time: + e = a**2 - b - c + assert e.subs(Add(*e.args[:2]), d) == d + e.args[2] + assert e.subs(a**2 - c, d) == d - b + + # the fallback should recognize when a change has + # been made; while .1 == Rational(1, 10) they are not the same + # and the change should be made + assert (0.1 + a).subs(0.1, Rational(1, 10)) == Rational(1, 10) + a + + e = (-x*(-y + 1) - y*(y - 1)) + ans = (-x*(x) - y*(-x)).expand() + assert e.subs(-y + 1, x) == ans + + #Test issue 18747 + assert (exp(x) + cos(x)).subs(x, oo) == oo + assert Add(*[AccumBounds(-1, 1), oo]) == oo + assert Add(*[oo, AccumBounds(-1, 1)]) == oo + + +def test_subs_issue_4009(): + assert (I*Symbol('a')).subs(1, 2) == I*Symbol('a') + + +def test_functions_subs(): + f, g = symbols('f g', cls=Function) + l = Lambda((x, y), sin(x) + y) + assert (g(y, x) + cos(x)).subs(g, l) == sin(y) + x + cos(x) + assert (f(x)**2).subs(f, sin) == sin(x)**2 + assert (f(x, y)).subs(f, log) == log(x, y) + assert (f(x, y)).subs(f, sin) == f(x, y) + assert (sin(x) + atan2(x, y)).subs([[atan2, f], [sin, g]]) == \ + f(x, y) + g(x) + assert (g(f(x + y, x))).subs([[f, l], [g, exp]]) == exp(x + sin(x + y)) + + +def test_derivative_subs(): + f = Function('f') + g = Function('g') + assert Derivative(f(x), x).subs(f(x), y) != 0 + # need xreplace to put the function back, see #13803 + assert Derivative(f(x), x).subs(f(x), y).xreplace({y: f(x)}) == \ + Derivative(f(x), x) + # issues 5085, 5037 + assert cse(Derivative(f(x), x) + f(x))[1][0].has(Derivative) + assert cse(Derivative(f(x, y), x) + + Derivative(f(x, y), y))[1][0].has(Derivative) + eq = Derivative(g(x), g(x)) + assert eq.subs(g, f) == Derivative(f(x), f(x)) + assert eq.subs(g(x), f(x)) == Derivative(f(x), f(x)) + assert eq.subs(g, cos) == Subs(Derivative(y, y), y, cos(x)) + + +def test_derivative_subs2(): + f_func, g_func = symbols('f g', cls=Function) + f, g = f_func(x, y, z), g_func(x, y, z) + assert Derivative(f, x, y).subs(Derivative(f, x, y), g) == g + assert Derivative(f, y, x).subs(Derivative(f, x, y), g) == g + assert Derivative(f, x, y).subs(Derivative(f, x), g) == Derivative(g, y) + assert Derivative(f, x, y).subs(Derivative(f, y), g) == Derivative(g, x) + assert (Derivative(f, x, y, z).subs( + Derivative(f, x, z), g) == Derivative(g, y)) + assert (Derivative(f, x, y, z).subs( + Derivative(f, z, y), g) == Derivative(g, x)) + assert (Derivative(f, x, y, z).subs( + Derivative(f, z, y, x), g) == g) + + # Issue 9135 + assert (Derivative(f, x, x, y).subs( + Derivative(f, y, y), g) == Derivative(f, x, x, y)) + assert (Derivative(f, x, y, y, z).subs( + Derivative(f, x, y, y, y), g) == Derivative(f, x, y, y, z)) + + assert Derivative(f, x, y).subs(Derivative(f_func(x), x, y), g) == Derivative(f, x, y) + + +def test_derivative_subs3(): + dex = Derivative(exp(x), x) + assert Derivative(dex, x).subs(dex, exp(x)) == dex + assert dex.subs(exp(x), dex) == Derivative(exp(x), x, x) + + +def test_issue_5284(): + A, B = symbols('A B', commutative=False) + assert (x*A).subs(x**2*A, B) == x*A + assert (A**2).subs(A**3, B) == A**2 + assert (A**6).subs(A**3, B) == B**2 + + +def test_subs_iter(): + assert x.subs(reversed([[x, y]])) == y + it = iter([[x, y]]) + assert x.subs(it) == y + assert x.subs(Tuple((x, y))) == y + + +def test_subs_dict(): + a, b, c, d, e = symbols('a b c d e') + + assert (2*x + y + z).subs({"x": 1, "y": 2}) == 4 + z + + l = [(sin(x), 2), (x, 1)] + assert (sin(x)).subs(l) == \ + (sin(x)).subs(dict(l)) == 2 + assert sin(x).subs(reversed(l)) == sin(1) + + expr = sin(2*x) + sqrt(sin(2*x))*cos(2*x)*sin(exp(x)*x) + reps = {sin(2*x): c, + sqrt(sin(2*x)): a, + cos(2*x): b, + exp(x): e, + x: d,} + assert expr.subs(reps) == c + a*b*sin(d*e) + + l = [(x, 3), (y, x**2)] + assert (x + y).subs(l) == 3 + x**2 + assert (x + y).subs(reversed(l)) == 12 + + # If changes are made to convert lists into dictionaries and do + # a dictionary-lookup replacement, these tests will help to catch + # some logical errors that might occur + l = [(y, z + 2), (1 + z, 5), (z, 2)] + assert (y - 1 + 3*x).subs(l) == 5 + 3*x + l = [(y, z + 2), (z, 3)] + assert (y - 2).subs(l) == 3 + + +def test_no_arith_subs_on_floats(): + assert (x + 3).subs(x + 3, a) == a + assert (x + 3).subs(x + 2, a) == a + 1 + + assert (x + y + 3).subs(x + 3, a) == a + y + assert (x + y + 3).subs(x + 2, a) == a + y + 1 + + assert (x + 3.0).subs(x + 3.0, a) == a + assert (x + 3.0).subs(x + 2.0, a) == x + 3.0 + + assert (x + y + 3.0).subs(x + 3.0, a) == a + y + assert (x + y + 3.0).subs(x + 2.0, a) == x + y + 3.0 + + +def test_issue_5651(): + a, b, c, K = symbols('a b c K', commutative=True) + assert (a/(b*c)).subs(b*c, K) == a/K + assert (a/(b**2*c**3)).subs(b*c, K) == a/(c*K**2) + assert (1/(x*y)).subs(x*y, 2) == S.Half + assert ((1 + x*y)/(x*y)).subs(x*y, 1) == 2 + assert (x*y*z).subs(x*y, 2) == 2*z + assert ((1 + x*y)/(x*y)/z).subs(x*y, 1) == 2/z + + +def test_issue_6075(): + assert Tuple(1, True).subs(1, 2) == Tuple(2, True) + + +def test_issue_6079(): + # since x + 2.0 == x + 2 we can't do a simple equality test + assert _aresame((x + 2.0).subs(2, 3), x + 2.0) + assert _aresame((x + 2.0).subs(2.0, 3), x + 3) + assert not _aresame(x + 2, x + 2.0) + assert not _aresame(Basic(cos(x), S(1)), Basic(cos(x), S(1.))) + assert _aresame(cos, cos) + assert not _aresame(1, S.One) + assert not _aresame(x, symbols('x', positive=True)) + + +def test_issue_4680(): + N = Symbol('N') + assert N.subs({"N": 3}) == 3 + + +def test_issue_6158(): + assert (x - 1).subs(1, y) == x - y + assert (x - 1).subs(-1, y) == x + y + assert (x - oo).subs(oo, y) == x - y + assert (x - oo).subs(-oo, y) == x + y + + +def test_Function_subs(): + f, g, h, i = symbols('f g h i', cls=Function) + p = Piecewise((g(f(x, y)), x < -1), (g(x), x <= 1)) + assert p.subs(g, h) == Piecewise((h(f(x, y)), x < -1), (h(x), x <= 1)) + assert (f(y) + g(x)).subs({f: h, g: i}) == i(x) + h(y) + + +def test_simultaneous_subs(): + reps = {x: 0, y: 0} + assert (x/y).subs(reps) != (y/x).subs(reps) + assert (x/y).subs(reps, simultaneous=True) == \ + (y/x).subs(reps, simultaneous=True) + reps = reps.items() + assert (x/y).subs(reps) != (y/x).subs(reps) + assert (x/y).subs(reps, simultaneous=True) == \ + (y/x).subs(reps, simultaneous=True) + assert Derivative(x, y, z).subs(reps, simultaneous=True) == \ + Subs(Derivative(0, y, z), y, 0) + + +def test_issue_6419_6421(): + assert (1/(1 + x/y)).subs(x/y, x) == 1/(1 + x) + assert (-2*I).subs(2*I, x) == -x + assert (-I*x).subs(I*x, x) == -x + assert (-3*I*y**4).subs(3*I*y**2, x) == -x*y**2 + + +def test_issue_6559(): + assert (-12*x + y).subs(-x, 1) == 12 + y + # though this involves cse it generated a failure in Mul._eval_subs + x0, x1 = symbols('x0 x1') + e = -log(-12*sqrt(2) + 17)/24 - log(-2*sqrt(2) + 3)/12 + sqrt(2)/3 + # XXX modify cse so x1 is eliminated and x0 = -sqrt(2)? + assert cse(e) == ( + [(x0, sqrt(2))], [x0/3 - log(-12*x0 + 17)/24 - log(-2*x0 + 3)/12]) + + +def test_issue_5261(): + x = symbols('x', real=True) + e = I*x + assert exp(e).subs(exp(x), y) == y**I + assert (2**e).subs(2**x, y) == y**I + eq = (-2)**e + assert eq.subs((-2)**x, y) == eq + + +def test_issue_6923(): + assert (-2*x*sqrt(2)).subs(2*x, y) == -sqrt(2)*y + + +def test_2arg_hack(): + N = Symbol('N', commutative=False) + ans = Mul(2, y + 1, evaluate=False) + assert (2*x*(y + 1)).subs(x, 1, hack2=True) == ans + assert (2*(y + 1 + N)).subs(N, 0, hack2=True) == ans + + +@XFAIL +def test_mul2(): + """When this fails, remove things labelled "2-arg hack" + 1) remove special handling in the fallback of subs that + was added in the same commit as this test + 2) remove the special handling in Mul.flatten + """ + assert (2*(x + 1)).is_Mul + + +def test_noncommutative_subs(): + x,y = symbols('x,y', commutative=False) + assert (x*y*x).subs([(x, x*y), (y, x)], simultaneous=True) == (x*y*x**2*y) + + +def test_issue_2877(): + f = Float(2.0) + assert (x + f).subs({f: 2}) == x + 2 + + def r(a, b, c): + return factor(a*x**2 + b*x + c) + e = r(5.0/6, 10, 5) + assert nsimplify(e) == 5*x**2/6 + 10*x + 5 + + +def test_issue_5910(): + t = Symbol('t') + assert (1/(1 - t)).subs(t, 1) is zoo + n = t + d = t - 1 + assert (n/d).subs(t, 1) is zoo + assert (-n/-d).subs(t, 1) is zoo + + +def test_issue_5217(): + s = Symbol('s') + z = (1 - 2*x*x) + w = (1 + 2*x*x) + q = 2*x*x*2*y*y + sub = {2*x*x: s} + assert w.subs(sub) == 1 + s + assert z.subs(sub) == 1 - s + assert q == 4*x**2*y**2 + assert q.subs(sub) == 2*y**2*s + + +def test_issue_10829(): + assert (4**x).subs(2**x, y) == y**2 + assert (9**x).subs(3**x, y) == y**2 + + +def test_pow_eval_subs_no_cache(): + # Tests pull request 9376 is working + from sympy.core.cache import clear_cache + + s = 1/sqrt(x**2) + # This bug only appeared when the cache was turned off. + # We need to approximate running this test without the cache. + # This creates approximately the same situation. + clear_cache() + + # This used to fail with a wrong result. + # It incorrectly returned 1/sqrt(x**2) before this pull request. + result = s.subs(sqrt(x**2), y) + assert result == 1/y + + +def test_RootOf_issue_10092(): + x = Symbol('x', real=True) + eq = x**3 - 17*x**2 + 81*x - 118 + r = RootOf(eq, 0) + assert (x < r).subs(x, r) is S.false + + +def test_issue_8886(): + from sympy.physics.mechanics import ReferenceFrame as R + # if something can't be sympified we assume that it + # doesn't play well with SymPy and disallow the + # substitution + v = R('A').x + raises(SympifyError, lambda: x.subs(x, v)) + raises(SympifyError, lambda: v.subs(v, x)) + assert v.__eq__(x) is False + + +def test_issue_12657(): + # treat -oo like the atom that it is + reps = [(-oo, 1), (oo, 2)] + assert (x < -oo).subs(reps) == (x < 1) + assert (x < -oo).subs(list(reversed(reps))) == (x < 1) + reps = [(-oo, 2), (oo, 1)] + assert (x < oo).subs(reps) == (x < 1) + assert (x < oo).subs(list(reversed(reps))) == (x < 1) + + +def test_recurse_Application_args(): + F = Lambda((x, y), exp(2*x + 3*y)) + f = Function('f') + A = f(x, f(x, x)) + C = F(x, F(x, x)) + assert A.subs(f, F) == A.replace(f, F) == C + + +def test_Subs_subs(): + assert Subs(x*y, x, x).subs(x, y) == Subs(x*y, x, y) + assert Subs(x*y, x, x + 1).subs(x, y) == \ + Subs(x*y, x, y + 1) + assert Subs(x*y, y, x + 1).subs(x, y) == \ + Subs(y**2, y, y + 1) + a = Subs(x*y*z, (y, x, z), (x + 1, x + z, x)) + b = Subs(x*y*z, (y, x, z), (x + 1, y + z, y)) + assert a.subs(x, y) == b and \ + a.doit().subs(x, y) == a.subs(x, y).doit() + f = Function('f') + g = Function('g') + assert Subs(2*f(x, y) + g(x), f(x, y), 1).subs(y, 2) == Subs( + 2*f(x, y) + g(x), (f(x, y), y), (1, 2)) + + +def test_issue_13333(): + eq = 1/x + assert eq.subs({"x": '1/2'}) == 2 + assert eq.subs({"x": '(1/2)'}) == 2 + + +def test_issue_15234(): + x, y = symbols('x y', real=True) + p = 6*x**5 + x**4 - 4*x**3 + 4*x**2 - 2*x + 3 + p_subbed = 6*x**5 - 4*x**3 - 2*x + y**4 + 4*y**2 + 3 + assert p.subs([(x**i, y**i) for i in [2, 4]]) == p_subbed + x, y = symbols('x y', complex=True) + p = 6*x**5 + x**4 - 4*x**3 + 4*x**2 - 2*x + 3 + p_subbed = 6*x**5 - 4*x**3 - 2*x + y**4 + 4*y**2 + 3 + assert p.subs([(x**i, y**i) for i in [2, 4]]) == p_subbed + + +def test_issue_6976(): + x, y = symbols('x y') + assert (sqrt(x)**3 + sqrt(x) + x + x**2).subs(sqrt(x), y) == \ + y**4 + y**3 + y**2 + y + assert (x**4 + x**3 + x**2 + x + sqrt(x)).subs(x**2, y) == \ + sqrt(x) + x**3 + x + y**2 + y + assert x.subs(x**3, y) == x + assert x.subs(x**Rational(1, 3), y) == y**3 + + # More substitutions are possible with nonnegative symbols + x, y = symbols('x y', nonnegative=True) + assert (x**4 + x**3 + x**2 + x + sqrt(x)).subs(x**2, y) == \ + y**Rational(1, 4) + y**Rational(3, 2) + sqrt(y) + y**2 + y + assert x.subs(x**3, y) == y**Rational(1, 3) + + +def test_issue_11746(): + assert (1/x).subs(x**2, 1) == 1/x + assert (1/(x**3)).subs(x**2, 1) == x**(-3) + assert (1/(x**4)).subs(x**2, 1) == 1 + assert (1/(x**3)).subs(x**4, 1) == x**(-3) + assert (1/(y**5)).subs(x**5, 1) == y**(-5) + + +def test_issue_17823(): + from sympy.physics.mechanics import dynamicsymbols + q1, q2 = dynamicsymbols('q1, q2') + expr = q1.diff().diff()**2*q1 + q1.diff()*q2.diff() + reps={q1: a, q1.diff(): a*x*y, q1.diff().diff(): z} + assert expr.subs(reps) == a*x*y*Derivative(q2, t) + a*z**2 + + +def test_issue_19326(): + x, y = [i(t) for i in map(Function, 'xy')] + assert (x*y).subs({x: 1 + x, y: x}) == (1 + x)*x + + +def test_issue_19558(): + e = (7*x*cos(x) - 12*log(x)**3)*(-log(x)**4 + 2*sin(x) + 1)**2/ \ + (2*(x*cos(x) - 2*log(x)**3)*(3*log(x)**4 - 7*sin(x) + 3)**2) + + assert e.subs(x, oo) == AccumBounds(-oo, oo) + assert (sin(x) + cos(x)).subs(x, oo) == AccumBounds(-2, 2) + + +def test_issue_22033(): + xr = Symbol('xr', real=True) + e = (1/xr) + assert e.subs(xr**2, y) == e + + +def test_guard_against_indeterminate_evaluation(): + eq = x**y + assert eq.subs([(x, 1), (y, oo)]) == 1 # because 1**y == 1 + assert eq.subs([(y, oo), (x, 1)]) is S.NaN + assert eq.subs({x: 1, y: oo}) is S.NaN + assert eq.subs([(x, 1), (y, oo)], simultaneous=True) is S.NaN diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_symbol.py b/phivenv/Lib/site-packages/sympy/core/tests/test_symbol.py new file mode 100644 index 0000000000000000000000000000000000000000..acf27700825c4822456207afe95108480505ce2c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_symbol.py @@ -0,0 +1,421 @@ +import threading + +from sympy.core.function import Function, UndefinedFunction +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import (GreaterThan, LessThan, StrictGreaterThan, StrictLessThan) +from sympy.core.symbol import (Dummy, Symbol, Wild, symbols) +from sympy.core.sympify import sympify # can't import as S yet +from sympy.core.symbol import uniquely_named_symbol, _symbol, Str + +from sympy.testing.pytest import raises, skip_under_pyodide +from sympy.core.symbol import disambiguate + + +def test_Str(): + a1 = Str('a') + a2 = Str('a') + b = Str('b') + assert a1 == a2 != b + raises(TypeError, lambda: Str()) + + +def test_Symbol(): + a = Symbol("a") + x1 = Symbol("x") + x2 = Symbol("x") + xdummy1 = Dummy("x") + xdummy2 = Dummy("x") + + assert a != x1 + assert a != x2 + assert x1 == x2 + assert x1 != xdummy1 + assert xdummy1 != xdummy2 + + assert Symbol("x") == Symbol("x") + assert Dummy("x") != Dummy("x") + d = symbols('d', cls=Dummy) + assert isinstance(d, Dummy) + c, d = symbols('c,d', cls=Dummy) + assert isinstance(c, Dummy) + assert isinstance(d, Dummy) + raises(TypeError, lambda: Symbol()) + + +def test_Dummy(): + assert Dummy() != Dummy() + + +def test_Dummy_force_dummy_index(): + raises(AssertionError, lambda: Dummy(dummy_index=1)) + assert Dummy('d', dummy_index=2) == Dummy('d', dummy_index=2) + assert Dummy('d1', dummy_index=2) != Dummy('d2', dummy_index=2) + d1 = Dummy('d', dummy_index=3) + d2 = Dummy('d') + # might fail if d1 were created with dummy_index >= 10**6 + assert d1 != d2 + d3 = Dummy('d', dummy_index=3) + assert d1 == d3 + assert Dummy()._count == Dummy('d', dummy_index=3)._count + + +def test_lt_gt(): + S = sympify + x, y = Symbol('x'), Symbol('y') + + assert (x >= y) == GreaterThan(x, y) + assert (x >= 0) == GreaterThan(x, 0) + assert (x <= y) == LessThan(x, y) + assert (x <= 0) == LessThan(x, 0) + + assert (0 <= x) == GreaterThan(x, 0) + assert (0 >= x) == LessThan(x, 0) + assert (S(0) >= x) == GreaterThan(0, x) + assert (S(0) <= x) == LessThan(0, x) + + assert (x > y) == StrictGreaterThan(x, y) + assert (x > 0) == StrictGreaterThan(x, 0) + assert (x < y) == StrictLessThan(x, y) + assert (x < 0) == StrictLessThan(x, 0) + + assert (0 < x) == StrictGreaterThan(x, 0) + assert (0 > x) == StrictLessThan(x, 0) + assert (S(0) > x) == StrictGreaterThan(0, x) + assert (S(0) < x) == StrictLessThan(0, x) + + e = x**2 + 4*x + 1 + assert (e >= 0) == GreaterThan(e, 0) + assert (0 <= e) == GreaterThan(e, 0) + assert (e > 0) == StrictGreaterThan(e, 0) + assert (0 < e) == StrictGreaterThan(e, 0) + + assert (e <= 0) == LessThan(e, 0) + assert (0 >= e) == LessThan(e, 0) + assert (e < 0) == StrictLessThan(e, 0) + assert (0 > e) == StrictLessThan(e, 0) + + assert (S(0) >= e) == GreaterThan(0, e) + assert (S(0) <= e) == LessThan(0, e) + assert (S(0) < e) == StrictLessThan(0, e) + assert (S(0) > e) == StrictGreaterThan(0, e) + + +def test_no_len(): + # there should be no len for numbers + x = Symbol('x') + raises(TypeError, lambda: len(x)) + + +def test_ineq_unequal(): + S = sympify + x, y, z = symbols('x,y,z') + + e = ( + S(-1) >= x, S(-1) >= y, S(-1) >= z, + S(-1) > x, S(-1) > y, S(-1) > z, + S(-1) <= x, S(-1) <= y, S(-1) <= z, + S(-1) < x, S(-1) < y, S(-1) < z, + S(0) >= x, S(0) >= y, S(0) >= z, + S(0) > x, S(0) > y, S(0) > z, + S(0) <= x, S(0) <= y, S(0) <= z, + S(0) < x, S(0) < y, S(0) < z, + S('3/7') >= x, S('3/7') >= y, S('3/7') >= z, + S('3/7') > x, S('3/7') > y, S('3/7') > z, + S('3/7') <= x, S('3/7') <= y, S('3/7') <= z, + S('3/7') < x, S('3/7') < y, S('3/7') < z, + S(1.5) >= x, S(1.5) >= y, S(1.5) >= z, + S(1.5) > x, S(1.5) > y, S(1.5) > z, + S(1.5) <= x, S(1.5) <= y, S(1.5) <= z, + S(1.5) < x, S(1.5) < y, S(1.5) < z, + S(2) >= x, S(2) >= y, S(2) >= z, + S(2) > x, S(2) > y, S(2) > z, + S(2) <= x, S(2) <= y, S(2) <= z, + S(2) < x, S(2) < y, S(2) < z, + x >= -1, y >= -1, z >= -1, + x > -1, y > -1, z > -1, + x <= -1, y <= -1, z <= -1, + x < -1, y < -1, z < -1, + x >= 0, y >= 0, z >= 0, + x > 0, y > 0, z > 0, + x <= 0, y <= 0, z <= 0, + x < 0, y < 0, z < 0, + x >= 1.5, y >= 1.5, z >= 1.5, + x > 1.5, y > 1.5, z > 1.5, + x <= 1.5, y <= 1.5, z <= 1.5, + x < 1.5, y < 1.5, z < 1.5, + x >= 2, y >= 2, z >= 2, + x > 2, y > 2, z > 2, + x <= 2, y <= 2, z <= 2, + x < 2, y < 2, z < 2, + + x >= y, x >= z, y >= x, y >= z, z >= x, z >= y, + x > y, x > z, y > x, y > z, z > x, z > y, + x <= y, x <= z, y <= x, y <= z, z <= x, z <= y, + x < y, x < z, y < x, y < z, z < x, z < y, + + x - pi >= y + z, y - pi >= x + z, z - pi >= x + y, + x - pi > y + z, y - pi > x + z, z - pi > x + y, + x - pi <= y + z, y - pi <= x + z, z - pi <= x + y, + x - pi < y + z, y - pi < x + z, z - pi < x + y, + True, False + ) + + left_e = e[:-1] + for i, e1 in enumerate( left_e ): + for e2 in e[i + 1:]: + assert e1 != e2 + + +def test_Wild_properties(): + S = sympify + # these tests only include Atoms + x = Symbol("x") + y = Symbol("y") + p = Symbol("p", positive=True) + k = Symbol("k", integer=True) + n = Symbol("n", integer=True, positive=True) + + given_patterns = [ x, y, p, k, -k, n, -n, S(-3), S(3), + pi, Rational(3, 2), I ] + + integerp = lambda k: k.is_integer + positivep = lambda k: k.is_positive + symbolp = lambda k: k.is_Symbol + realp = lambda k: k.is_extended_real + + S = Wild("S", properties=[symbolp]) + R = Wild("R", properties=[realp]) + Y = Wild("Y", exclude=[x, p, k, n]) + P = Wild("P", properties=[positivep]) + K = Wild("K", properties=[integerp]) + N = Wild("N", properties=[positivep, integerp]) + + given_wildcards = [ S, R, Y, P, K, N ] + + goodmatch = { + S: (x, y, p, k, n), + R: (p, k, -k, n, -n, -3, 3, pi, Rational(3, 2)), + Y: (y, -3, 3, pi, Rational(3, 2), I ), + P: (p, n, 3, pi, Rational(3, 2)), + K: (k, -k, n, -n, -3, 3), + N: (n, 3)} + + for A in given_wildcards: + for pat in given_patterns: + d = pat.match(A) + if pat in goodmatch[A]: + assert d[A] in goodmatch[A] + else: + assert d is None + + +def test_symbols(): + x = Symbol('x') + y = Symbol('y') + z = Symbol('z') + + assert symbols('x') == x + assert symbols('x ') == x + assert symbols(' x ') == x + assert symbols('x,') == (x,) + assert symbols('x, ') == (x,) + assert symbols('x ,') == (x,) + + assert symbols('x , y') == (x, y) + + assert symbols('x,y,z') == (x, y, z) + assert symbols('x y z') == (x, y, z) + + assert symbols('x,y,z,') == (x, y, z) + assert symbols('x y z ') == (x, y, z) + + xyz = Symbol('xyz') + abc = Symbol('abc') + + assert symbols('xyz') == xyz + assert symbols('xyz,') == (xyz,) + assert symbols('xyz,abc') == (xyz, abc) + + assert symbols(('xyz',)) == (xyz,) + assert symbols(('xyz,',)) == ((xyz,),) + assert symbols(('x,y,z,',)) == ((x, y, z),) + assert symbols(('xyz', 'abc')) == (xyz, abc) + assert symbols(('xyz,abc',)) == ((xyz, abc),) + assert symbols(('xyz,abc', 'x,y,z')) == ((xyz, abc), (x, y, z)) + + assert symbols(('x', 'y', 'z')) == (x, y, z) + assert symbols(['x', 'y', 'z']) == [x, y, z] + assert symbols({'x', 'y', 'z'}) == {x, y, z} + + raises(ValueError, lambda: symbols('')) + raises(ValueError, lambda: symbols(',')) + raises(ValueError, lambda: symbols('x,,y,,z')) + raises(ValueError, lambda: symbols(('x', '', 'y', '', 'z'))) + + a, b = symbols('x,y', real=True) + assert a.is_real and b.is_real + + x0 = Symbol('x0') + x1 = Symbol('x1') + x2 = Symbol('x2') + + y0 = Symbol('y0') + y1 = Symbol('y1') + + assert symbols('x0:0') == () + assert symbols('x0:1') == (x0,) + assert symbols('x0:2') == (x0, x1) + assert symbols('x0:3') == (x0, x1, x2) + + assert symbols('x:0') == () + assert symbols('x:1') == (x0,) + assert symbols('x:2') == (x0, x1) + assert symbols('x:3') == (x0, x1, x2) + + assert symbols('x1:1') == () + assert symbols('x1:2') == (x1,) + assert symbols('x1:3') == (x1, x2) + + assert symbols('x1:3,x,y,z') == (x1, x2, x, y, z) + + assert symbols('x:3,y:2') == (x0, x1, x2, y0, y1) + assert symbols(('x:3', 'y:2')) == ((x0, x1, x2), (y0, y1)) + + a = Symbol('a') + b = Symbol('b') + c = Symbol('c') + d = Symbol('d') + + assert symbols('x:z') == (x, y, z) + assert symbols('a:d,x:z') == (a, b, c, d, x, y, z) + assert symbols(('a:d', 'x:z')) == ((a, b, c, d), (x, y, z)) + + aa = Symbol('aa') + ab = Symbol('ab') + ac = Symbol('ac') + ad = Symbol('ad') + + assert symbols('aa:d') == (aa, ab, ac, ad) + assert symbols('aa:d,x:z') == (aa, ab, ac, ad, x, y, z) + assert symbols(('aa:d','x:z')) == ((aa, ab, ac, ad), (x, y, z)) + + assert type(symbols(('q:2', 'u:2'), cls=Function)[0][0]) == UndefinedFunction # issue 23532 + + # issue 6675 + def sym(s): + return str(symbols(s)) + assert sym('a0:4') == '(a0, a1, a2, a3)' + assert sym('a2:4,b1:3') == '(a2, a3, b1, b2)' + assert sym('a1(2:4)') == '(a12, a13)' + assert sym('a0:2.0:2') == '(a0.0, a0.1, a1.0, a1.1)' + assert sym('aa:cz') == '(aaz, abz, acz)' + assert sym('aa:c0:2') == '(aa0, aa1, ab0, ab1, ac0, ac1)' + assert sym('aa:ba:b') == '(aaa, aab, aba, abb)' + assert sym('a:3b') == '(a0b, a1b, a2b)' + assert sym('a-1:3b') == '(a-1b, a-2b)' + assert sym(r'a:2\,:2' + chr(0)) == '(a0,0%s, a0,1%s, a1,0%s, a1,1%s)' % ( + (chr(0),)*4) + assert sym('x(:a:3)') == '(x(a0), x(a1), x(a2))' + assert sym('x(:c):1') == '(xa0, xb0, xc0)' + assert sym('x((:a)):3') == '(x(a)0, x(a)1, x(a)2)' + assert sym('x(:a:3') == '(x(a0, x(a1, x(a2)' + assert sym(':2') == '(0, 1)' + assert sym(':b') == '(a, b)' + assert sym(':b:2') == '(a0, a1, b0, b1)' + assert sym(':2:2') == '(00, 01, 10, 11)' + assert sym(':b:b') == '(aa, ab, ba, bb)' + + raises(ValueError, lambda: symbols(':')) + raises(ValueError, lambda: symbols('a:')) + raises(ValueError, lambda: symbols('::')) + raises(ValueError, lambda: symbols('a::')) + raises(ValueError, lambda: symbols(':a:')) + raises(ValueError, lambda: symbols('::a')) + + +def test_symbols_become_functions_issue_3539(): + from sympy.abc import alpha, phi, beta, t + raises(TypeError, lambda: beta(2)) + raises(TypeError, lambda: beta(2.5)) + raises(TypeError, lambda: phi(2.5)) + raises(TypeError, lambda: alpha(2.5)) + raises(TypeError, lambda: phi(t)) + + +def test_unicode(): + xu = Symbol('x') + x = Symbol('x') + assert x == xu + + raises(TypeError, lambda: Symbol(1)) + + +def test_uniquely_named_symbol_and_Symbol(): + F = uniquely_named_symbol + x = Symbol('x') + assert F(x) == x + assert F('x') == x + assert str(F('x', x)) == 'x0' + assert str(F('x', (x + 1, 1/x))) == 'x0' + _x = Symbol('x', real=True) + assert F(('x', _x)) == _x + assert F((x, _x)) == _x + assert F('x', real=True).is_real + y = Symbol('y') + assert F(('x', y), real=True).is_real + r = Symbol('x', real=True) + assert F(('x', r)).is_real + assert F(('x', r), real=False).is_real + assert F('x1', Symbol('x1'), + compare=lambda i: str(i).rstrip('1')).name == 'x0' + assert F('x1', Symbol('x1'), + modify=lambda i: i + '_').name == 'x1_' + assert _symbol(x, _x) == x + + +def test_disambiguate(): + x, y, y_1, _x, x_1, x_2 = symbols('x y y_1 _x x_1 x_2') + t1 = Dummy('y'), _x, Dummy('x'), Dummy('x') + t2 = Dummy('x'), Dummy('x') + t3 = Dummy('x'), Dummy('y') + t4 = x, Dummy('x') + t5 = Symbol('x', integer=True), x, Symbol('x_1') + + assert disambiguate(*t1) == (y, x_2, x, x_1) + assert disambiguate(*t2) == (x, x_1) + assert disambiguate(*t3) == (x, y) + assert disambiguate(*t4) == (x_1, x) + assert disambiguate(*t5) == (t5[0], x_2, x_1) + assert disambiguate(*t5)[0] != x # assumptions are retained + + t6 = _x, Dummy('x')/y + t7 = y*Dummy('y'), y + + assert disambiguate(*t6) == (x_1, x/y) + assert disambiguate(*t7) == (y*y_1, y_1) + assert disambiguate(Dummy('x_1'), Dummy('x_1') + ) == (x_1, Symbol('x_1_1')) + + +@skip_under_pyodide("Cannot create threads under pyodide.") +def test_issue_gh_16734(): + # https://github.com/sympy/sympy/issues/16734 + + syms = list(symbols('x, y')) + + def thread1(): + for n in range(1000): + syms[0], syms[1] = symbols(f'x{n}, y{n}') + syms[0].is_positive # Check an assumption in this thread. + syms[0] = None + + def thread2(): + while syms[0] is not None: + # Compare the symbol in this thread. + result = (syms[0] == syms[1]) # noqa + + # Previously this would be very likely to raise an exception: + thread = threading.Thread(target=thread1) + thread.start() + thread2() + thread.join() diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_sympify.py b/phivenv/Lib/site-packages/sympy/core/tests/test_sympify.py new file mode 100644 index 0000000000000000000000000000000000000000..40be30c25d5826ceadde6d176c160a0090967659 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_sympify.py @@ -0,0 +1,892 @@ +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.function import (Function, Lambda) +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Integer, Rational, pi, oo) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.logic.boolalg import (false, Or, true, Xor) +from sympy.matrices.dense import Matrix +from sympy.parsing.sympy_parser import null +from sympy.polys.polytools import Poly +from sympy.printing.repr import srepr +from sympy.sets.fancysets import Range +from sympy.sets.sets import Interval +from sympy.abc import x, y +from sympy.core.sympify import (sympify, _sympify, SympifyError, kernS, + CantSympify, converter) +from sympy.core.decorators import _sympifyit +from sympy.external import import_module +from sympy.testing.pytest import raises, XFAIL, skip +from sympy.utilities.decorator import conserve_mpmath_dps +from sympy.geometry import Point, Line +from sympy.functions.combinatorial.factorials import factorial, factorial2 +from sympy.abc import _clash, _clash1, _clash2 +from sympy.external.gmpy import gmpy as _gmpy, flint as _flint +from sympy.sets import FiniteSet, EmptySet +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray + +import mpmath +from collections import defaultdict, OrderedDict + + +numpy = import_module('numpy') + + +def test_issue_3538(): + v = sympify("exp(x)") + assert v == exp(x) + assert type(v) == type(exp(x)) + assert str(type(v)) == str(type(exp(x))) + + +def test_sympify1(): + assert sympify("x") == Symbol("x") + assert sympify(" x") == Symbol("x") + assert sympify(" x ") == Symbol("x") + # issue 4877 + assert sympify('--.5') == 0.5 + assert sympify('-1/2') == -S.Half + assert sympify('-+--.5') == -0.5 + assert sympify('-.[3]') == Rational(-1, 3) + assert sympify('.[3]') == Rational(1, 3) + assert sympify('+.[3]') == Rational(1, 3) + assert sympify('+0.[3]*10**-2') == Rational(1, 300) + assert sympify('.[052631578947368421]') == Rational(1, 19) + assert sympify('.0[526315789473684210]') == Rational(1, 19) + assert sympify('.034[56]') == Rational(1711, 49500) + # options to make reals into rationals + assert sympify('1.22[345]', rational=True) == \ + 1 + Rational(22, 100) + Rational(345, 99900) + assert sympify('2/2.6', rational=True) == Rational(10, 13) + assert sympify('2.6/2', rational=True) == Rational(13, 10) + assert sympify('2.6e2/17', rational=True) == Rational(260, 17) + assert sympify('2.6e+2/17', rational=True) == Rational(260, 17) + assert sympify('2.6e-2/17', rational=True) == Rational(26, 17000) + assert sympify('2.1+3/4', rational=True) == \ + Rational(21, 10) + Rational(3, 4) + assert sympify('2.234456', rational=True) == Rational(279307, 125000) + assert sympify('2.234456e23', rational=True) == 223445600000000000000000 + assert sympify('2.234456e-23', rational=True) == \ + Rational(279307, 12500000000000000000000000000) + assert sympify('-2.234456e-23', rational=True) == \ + Rational(-279307, 12500000000000000000000000000) + assert sympify('12345678901/17', rational=True) == \ + Rational(12345678901, 17) + assert sympify('1/.3 + x', rational=True) == Rational(10, 3) + x + # make sure longs in fractions work + assert sympify('222222222222/11111111111') == \ + Rational(222222222222, 11111111111) + # ... even if they come from repetend notation + assert sympify('1/.2[123456789012]') == Rational(333333333333, 70781892967) + # ... or from high precision reals + assert sympify('.1234567890123456', rational=True) == \ + Rational(19290123283179, 156250000000000) + + +def test_sympify_Fraction(): + try: + import fractions + except ImportError: + pass + else: + value = sympify(fractions.Fraction(101, 127)) + assert value == Rational(101, 127) and type(value) is Rational + + +def test_sympify_gmpy(): + if _gmpy is not None: + import gmpy2 + + value = sympify(gmpy2.mpz(1000001)) + assert value == Integer(1000001) and type(value) is Integer + + value = sympify(gmpy2.mpq(101, 127)) + assert value == Rational(101, 127) and type(value) is Rational + + +def test_sympify_flint(): + if _flint is not None: + import flint + + value = sympify(flint.fmpz(1000001)) + assert value == Integer(1000001) and type(value) is Integer + + value = sympify(flint.fmpq(101, 127)) + assert value == Rational(101, 127) and type(value) is Rational + + +@conserve_mpmath_dps +def test_sympify_mpmath(): + value = sympify(mpmath.mpf(1.0)) + assert value == Float(1.0) and type(value) is Float + + mpmath.mp.dps = 12 + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159265359"), Float("1e-12")) == True + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159265359"), Float("1e-13")) == False + + mpmath.mp.dps = 6 + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159"), Float("1e-5")) == True + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159"), Float("1e-6")) == False + + mpmath.mp.dps = 15 + assert sympify(mpmath.mpc(1.0 + 2.0j)) == Float(1.0) + Float(2.0)*I + + +def test_sympify2(): + class A: + def _sympy_(self): + return Symbol("x")**3 + + a = A() + + assert _sympify(a) == x**3 + assert sympify(a) == x**3 + assert a == x**3 + + +def test_sympify3(): + assert sympify("x**3") == x**3 + assert sympify("x^3") == x**3 + assert sympify("1/2") == Integer(1)/2 + + raises(SympifyError, lambda: _sympify('x**3')) + raises(SympifyError, lambda: _sympify('1/2')) + + +def test_sympify_keywords(): + raises(SympifyError, lambda: sympify('if')) + raises(SympifyError, lambda: sympify('for')) + raises(SympifyError, lambda: sympify('while')) + raises(SympifyError, lambda: sympify('lambda')) + + +def test_sympify_float(): + assert sympify("1e-64") != 0 + assert sympify("1e-20000") != 0 + + +def test_sympify_bool(): + assert sympify(True) is true + assert sympify(False) is false + + +def test_sympify_iterables(): + ans = [Rational(3, 10), Rational(1, 5)] + assert sympify(['.3', '.2'], rational=True) == ans + assert sympify({"x": 0, "y": 1}) == {x: 0, y: 1} + assert sympify(['1', '2', ['3', '4']]) == [S(1), S(2), [S(3), S(4)]] + + +@XFAIL +def test_issue_16772(): + # because there is a converter for tuple, the + # args are only sympified without the flags being passed + # along; list, on the other hand, is not converted + # with a converter so its args are traversed later + ans = [Rational(3, 10), Rational(1, 5)] + assert sympify(('.3', '.2'), rational=True) == Tuple(*ans) + + +def test_issue_16859(): + class no(float, CantSympify): + pass + raises(SympifyError, lambda: sympify(no(1.2))) + + +def test_sympify4(): + class A: + def _sympy_(self): + return Symbol("x") + + a = A() + + assert _sympify(a)**3 == x**3 + assert sympify(a)**3 == x**3 + assert a == x + + +def test_sympify_text(): + assert sympify('some') == Symbol('some') + assert sympify('core') == Symbol('core') + + assert sympify('True') is True + assert sympify('False') is False + + assert sympify('Poly') == Poly + assert sympify('sin') == sin + + +def test_sympify_function(): + assert sympify('factor(x**2-1, x)') == -(1 - x)*(x + 1) + assert sympify('sin(pi/2)*cos(pi)') == -Integer(1) + + +def test_sympify_poly(): + p = Poly(x**2 + x + 1, x) + + assert _sympify(p) is p + assert sympify(p) is p + + +def test_sympify_factorial(): + assert sympify('x!') == factorial(x) + assert sympify('(x+1)!') == factorial(x + 1) + assert sympify('(1 + y*(x + 1))!') == factorial(1 + y*(x + 1)) + assert sympify('(1 + y*(x + 1)!)^2') == (1 + y*factorial(x + 1))**2 + assert sympify('y*x!') == y*factorial(x) + assert sympify('x!!') == factorial2(x) + assert sympify('(x+1)!!') == factorial2(x + 1) + assert sympify('(1 + y*(x + 1))!!') == factorial2(1 + y*(x + 1)) + assert sympify('(1 + y*(x + 1)!!)^2') == (1 + y*factorial2(x + 1))**2 + assert sympify('y*x!!') == y*factorial2(x) + assert sympify('factorial2(x)!') == factorial(factorial2(x)) + + raises(SympifyError, lambda: sympify("+!!")) + raises(SympifyError, lambda: sympify(")!!")) + raises(SympifyError, lambda: sympify("!")) + raises(SympifyError, lambda: sympify("(!)")) + raises(SympifyError, lambda: sympify("x!!!")) + + +def test_issue_3595(): + assert sympify("a_") == Symbol("a_") + assert sympify("_a") == Symbol("_a") + + +def test_lambda(): + x = Symbol('x') + assert sympify('lambda: 1') == Lambda((), 1) + assert sympify('lambda x: x') == Lambda(x, x) + assert sympify('lambda x: 2*x') == Lambda(x, 2*x) + assert sympify('lambda x, y: 2*x+y') == Lambda((x, y), 2*x + y) + + +def test_lambda_raises(): + raises(SympifyError, lambda: sympify("lambda *args: args")) # args argument error + raises(SympifyError, lambda: sympify("lambda **kwargs: kwargs[0]")) # kwargs argument error + raises(SympifyError, lambda: sympify("lambda x = 1: x")) # Keyword argument error + with raises(SympifyError): + _sympify('lambda: 1') + + +def test_sympify_raises(): + raises(SympifyError, lambda: sympify("fx)")) + + class A: + def __str__(self): + return 'x' + + raises(SympifyError, lambda: sympify(A())) + + +def test__sympify(): + x = Symbol('x') + f = Function('f') + + # positive _sympify + assert _sympify(x) is x + assert _sympify(1) == Integer(1) + assert _sympify(0.5) == Float("0.5") + assert _sympify(1 + 1j) == 1.0 + I*1.0 + + # Function f is not Basic and can't sympify to Basic. We allow it to pass + # with sympify but not with _sympify. + # https://github.com/sympy/sympy/issues/20124 + assert sympify(f) is f + raises(SympifyError, lambda: _sympify(f)) + + class A: + def _sympy_(self): + return Integer(5) + + a = A() + assert _sympify(a) == Integer(5) + + # negative _sympify + raises(SympifyError, lambda: _sympify('1')) + raises(SympifyError, lambda: _sympify([1, 2, 3])) + + +def test_sympifyit(): + x = Symbol('x') + y = Symbol('y') + + @_sympifyit('b', NotImplemented) + def add(a, b): + return a + b + + assert add(x, 1) == x + 1 + assert add(x, 0.5) == x + Float('0.5') + assert add(x, y) == x + y + + assert add(x, '1') == NotImplemented + + @_sympifyit('b') + def add_raises(a, b): + return a + b + + assert add_raises(x, 1) == x + 1 + assert add_raises(x, 0.5) == x + Float('0.5') + assert add_raises(x, y) == x + y + + raises(SympifyError, lambda: add_raises(x, '1')) + + +def test_int_float(): + class F1_1: + def __float__(self): + return 1.1 + + class F1_1b: + """ + This class is still a float, even though it also implements __int__(). + """ + def __float__(self): + return 1.1 + + def __int__(self): + return 1 + + class F1_1c: + """ + This class is still a float, because it implements _sympy_() + """ + def __float__(self): + return 1.1 + + def __int__(self): + return 1 + + def _sympy_(self): + return Float(1.1) + + class I5: + def __int__(self): + return 5 + + class I5b: + """ + This class implements both __int__() and __float__(), so it will be + treated as Float in SymPy. One could change this behavior, by using + float(a) == int(a), but deciding that integer-valued floats represent + exact numbers is arbitrary and often not correct, so we do not do it. + If, in the future, we decide to do it anyway, the tests for I5b need to + be changed. + """ + def __float__(self): + return 5.0 + + def __int__(self): + return 5 + + class I5c: + """ + This class implements both __int__() and __float__(), but also + a _sympy_() method, so it will be Integer. + """ + def __float__(self): + return 5.0 + + def __int__(self): + return 5 + + def _sympy_(self): + return Integer(5) + + i5 = I5() + i5b = I5b() + i5c = I5c() + f1_1 = F1_1() + f1_1b = F1_1b() + f1_1c = F1_1c() + assert sympify(i5) == 5 + assert isinstance(sympify(i5), Integer) + assert sympify(i5b) == 5.0 + assert isinstance(sympify(i5b), Float) + assert sympify(i5c) == 5 + assert isinstance(sympify(i5c), Integer) + assert abs(sympify(f1_1) - 1.1) < 1e-5 + assert abs(sympify(f1_1b) - 1.1) < 1e-5 + assert abs(sympify(f1_1c) - 1.1) < 1e-5 + + assert _sympify(i5) == 5 + assert isinstance(_sympify(i5), Integer) + assert _sympify(i5b) == 5.0 + assert isinstance(_sympify(i5b), Float) + assert _sympify(i5c) == 5 + assert isinstance(_sympify(i5c), Integer) + assert abs(_sympify(f1_1) - 1.1) < 1e-5 + assert abs(_sympify(f1_1b) - 1.1) < 1e-5 + assert abs(_sympify(f1_1c) - 1.1) < 1e-5 + + +def test_evaluate_false(): + cases = { + '2 + 3': Add(2, 3, evaluate=False), + '2**2 / 3': Mul(Pow(2, 2, evaluate=False), Pow(3, -1, evaluate=False), evaluate=False), + '2 + 3 * 5': Add(2, Mul(3, 5, evaluate=False), evaluate=False), + '2 - 3 * 5': Add(2, Mul(-1, Mul(3, 5,evaluate=False), evaluate=False), evaluate=False), + '1 / 3': Mul(1, Pow(3, -1, evaluate=False), evaluate=False), + 'True | False': Or(True, False, evaluate=False), + '1 + 2 + 3 + 5*3 + integrate(x)': Add(1, 2, 3, Mul(5, 3, evaluate=False), x**2/2, evaluate=False), + '2 * 4 * 6 + 8': Add(Mul(2, 4, 6, evaluate=False), 8, evaluate=False), + '2 - 8 / 4': Add(2, Mul(-1, Mul(8, Pow(4, -1, evaluate=False), evaluate=False), evaluate=False), evaluate=False), + '2 - 2**2': Add(2, Mul(-1, Pow(2, 2, evaluate=False), evaluate=False), evaluate=False), + } + for case, result in cases.items(): + assert sympify(case, evaluate=False) == result + + +def test_issue_4133(): + a = sympify('Integer(4)') + + assert a == Integer(4) + assert a.is_Integer + + +def test_issue_3982(): + a = [3, 2.0] + assert sympify(a) == [Integer(3), Float(2.0)] + assert sympify(tuple(a)) == Tuple(Integer(3), Float(2.0)) + assert sympify(set(a)) == FiniteSet(Integer(3), Float(2.0)) + + +def test_S_sympify(): + assert S(1)/2 == sympify(1)/2 == S.Half + assert (-2)**(S(1)/2) == sqrt(2)*I + + +def test_issue_4788(): + assert srepr(S(1.0 + 0J)) == srepr(S(1.0)) == srepr(Float(1.0)) + + +def test_issue_4798_None(): + assert S(None) is None + + +def test_issue_3218(): + assert sympify("x+\ny") == x + y + +def test_issue_19399(): + if not numpy: + skip("numpy not installed.") + + a = numpy.array(Rational(1, 2)) + b = Rational(1, 3) + assert (a * b, type(a * b)) == (b * a, type(b * a)) + + +def test_issue_4988_builtins(): + C = Symbol('C') + vars = {'C': C} + exp1 = sympify('C') + assert exp1 == C # Make sure it did not get mixed up with sympy.C + + exp2 = sympify('C', vars) + assert exp2 == C # Make sure it did not get mixed up with sympy.C + + +def test_geometry(): + p = sympify(Point(0, 1)) + assert p == Point(0, 1) and isinstance(p, Point) + L = sympify(Line(p, (1, 0))) + assert L == Line((0, 1), (1, 0)) and isinstance(L, Line) + + +def test_kernS(): + s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))' + # when 1497 is fixed, this no longer should pass: the expression + # should be unchanged + assert -1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) == -1 + # sympification should not allow the constant to enter a Mul + # or else the structure can change dramatically + ss = kernS(s) + assert ss != -1 and ss.simplify() == -1 + s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))'.replace( + 'x', '_kern') + ss = kernS(s) + assert ss != -1 and ss.simplify() == -1 + # issue 6687 + assert (kernS('Interval(-1,-2 - 4*(-3))') + == Interval(-1, Add(-2, Mul(12, 1, evaluate=False), evaluate=False))) + assert kernS('_kern') == Symbol('_kern') + assert kernS('E**-(x)') == exp(-x) + e = 2*(x + y)*y + assert kernS(['2*(x + y)*y', ('2*(x + y)*y',)]) == [e, (e,)] + assert kernS('-(2*sin(x)**2 + 2*sin(x)*cos(x))*y/2') == \ + -y*(2*sin(x)**2 + 2*sin(x)*cos(x))/2 + # issue 15132 + assert kernS('(1 - x)/(1 - x*(1-y))') == kernS('(1-x)/(1-(1-y)*x)') + assert kernS('(1-2**-(4+1)*(1-y)*x)') == (1 - x*(1 - y)/32) + assert kernS('(1-2**(4+1)*(1-y)*x)') == (1 - 32*x*(1 - y)) + assert kernS('(1-2.*(1-y)*x)') == 1 - 2.*x*(1 - y) + one = kernS('x - (x - 1)') + assert one != 1 and one.expand() == 1 + assert kernS("(2*x)/(x-1)") == 2*x/(x-1) + + +def test_issue_6540_6552(): + assert S('[[1/3,2], (2/5,)]') == [[Rational(1, 3), 2], (Rational(2, 5),)] + assert S('[[2/6,2], (2/4,)]') == [[Rational(1, 3), 2], (S.Half,)] + assert S('[[[2*(1)]]]') == [[[2]]] + assert S('Matrix([2*(1)])') == Matrix([2]) + + +def test_issue_6046(): + assert str(S("Q & C", locals=_clash1)) == 'C & Q' + assert str(S('pi(x)', locals=_clash2)) == 'pi(x)' + locals = {} + exec("from sympy.abc import Q, C", locals) + assert str(S('C&Q', locals)) == 'C & Q' + # clash can act as Symbol or Function + assert str(S('pi(C, Q)', locals=_clash)) == 'pi(C, Q)' + assert len(S('pi + x', locals=_clash2).free_symbols) == 2 + # but not both + raises(TypeError, lambda: S('pi + pi(x)', locals=_clash2)) + assert all(set(i.values()) == {null} for i in ( + _clash, _clash1, _clash2)) + + +def test_issue_8821_highprec_from_str(): + s = str(pi.evalf(128)) + p = sympify(s) + assert Abs(sin(p)) < 1e-127 + + +def test_issue_10295(): + if not numpy: + skip("numpy not installed.") + + A = numpy.array([[1, 3, -1], + [0, 1, 7]]) + sA = S(A) + assert sA.shape == (2, 3) + for (ri, ci), val in numpy.ndenumerate(A): + assert sA[ri, ci] == val + + B = numpy.array([-7, x, 3*y**2]) + sB = S(B) + assert sB.shape == (3,) + assert B[0] == sB[0] == -7 + assert B[1] == sB[1] == x + assert B[2] == sB[2] == 3*y**2 + + C = numpy.arange(0, 24) + C.resize(2,3,4) + sC = S(C) + assert sC[0, 0, 0].is_integer + assert sC[0, 0, 0] == 0 + + a1 = numpy.array([1, 2, 3]) + a2 = numpy.array(list(range(24))) + a2.resize(2, 4, 3) + assert sympify(a1) == ImmutableDenseNDimArray([1, 2, 3]) + assert sympify(a2) == ImmutableDenseNDimArray(list(range(24)), (2, 4, 3)) + + +def test_Range(): + # Only works in Python 3 where range returns a range type + assert sympify(range(10)) == Range(10) + assert _sympify(range(10)) == Range(10) + + +def test_sympify_set(): + n = Symbol('n') + assert sympify({n}) == FiniteSet(n) + assert sympify(set()) == EmptySet + + +def test_sympify_numpy(): + if not numpy: + skip('numpy not installed. Abort numpy tests.') + np = numpy + + def equal(x, y): + return x == y and type(x) == type(y) + + assert sympify(np.bool_(1)) is S(True) + try: + assert equal( + sympify(np.int_(1234567891234567891)), S(1234567891234567891)) + assert equal( + sympify(np.intp(1234567891234567891)), S(1234567891234567891)) + except OverflowError: + # May fail on 32-bit systems: Python int too large to convert to C long + pass + assert equal(sympify(np.intc(1234567891)), S(1234567891)) + assert equal(sympify(np.int8(-123)), S(-123)) + assert equal(sympify(np.int16(-12345)), S(-12345)) + assert equal(sympify(np.int32(-1234567891)), S(-1234567891)) + assert equal( + sympify(np.int64(-1234567891234567891)), S(-1234567891234567891)) + assert equal(sympify(np.uint8(123)), S(123)) + assert equal(sympify(np.uint16(12345)), S(12345)) + assert equal(sympify(np.uint32(1234567891)), S(1234567891)) + assert equal( + sympify(np.uint64(1234567891234567891)), S(1234567891234567891)) + assert equal(sympify(np.float32(1.123456)), Float(1.123456, precision=24)) + assert equal(sympify(np.float64(1.1234567891234)), + Float(1.1234567891234, precision=53)) + + # The exact precision of np.longdouble, npfloat128 and other extended + # precision dtypes is platform dependent. + ldprec = np.finfo(np.longdouble(1)).nmant + 1 + assert equal(sympify(np.longdouble(1.123456789)), + Float(1.123456789, precision=ldprec)) + + assert equal(sympify(np.complex64(1 + 2j)), S(1.0 + 2.0*I)) + assert equal(sympify(np.complex128(1 + 2j)), S(1.0 + 2.0*I)) + + lcprec = np.finfo(np.clongdouble(1)).nmant + 1 + assert equal(sympify(np.clongdouble(1 + 2j)), + Float(1.0, precision=lcprec) + Float(2.0, precision=lcprec)*I) + + #float96 does not exist on all platforms + if hasattr(np, 'float96'): + f96prec = np.finfo(np.float96(1)).nmant + 1 + assert equal(sympify(np.float96(1.123456789)), + Float(1.123456789, precision=f96prec)) + + #float128 does not exist on all platforms + if hasattr(np, 'float128'): + f128prec = np.finfo(np.float128(1)).nmant + 1 + assert equal(sympify(np.float128(1.123456789123)), + Float(1.123456789123, precision=f128prec)) + + +@XFAIL +def test_sympify_rational_numbers_set(): + ans = [Rational(3, 10), Rational(1, 5)] + assert sympify({'.3', '.2'}, rational=True) == FiniteSet(*ans) + + +def test_sympify_mro(): + """Tests the resolution order for classes that implement _sympy_""" + class a: + def _sympy_(self): + return Integer(1) + class b(a): + def _sympy_(self): + return Integer(2) + class c(a): + pass + + assert sympify(a()) == Integer(1) + assert sympify(b()) == Integer(2) + assert sympify(c()) == Integer(1) + + +def test_sympify_converter(): + """Tests the resolution order for classes in converter""" + class a: + pass + class b(a): + pass + class c(a): + pass + + converter[a] = lambda x: Integer(1) + converter[b] = lambda x: Integer(2) + + assert sympify(a()) == Integer(1) + assert sympify(b()) == Integer(2) + assert sympify(c()) == Integer(1) + + class MyInteger(Integer): + pass + + if int in converter: + int_converter = converter[int] + else: + int_converter = None + + try: + converter[int] = MyInteger + assert sympify(1) == MyInteger(1) + finally: + if int_converter is None: + del converter[int] + else: + converter[int] = int_converter + + +def test_issue_13924(): + if not numpy: + skip("numpy not installed.") + + a = sympify(numpy.array([1])) + assert isinstance(a, ImmutableDenseNDimArray) + assert a[0] == 1 + + +def test_numpy_sympify_args(): + # Issue 15098. Make sure sympify args work with numpy types (like numpy.str_) + if not numpy: + skip("numpy not installed.") + + a = sympify(numpy.str_('a')) + assert type(a) is Symbol + assert a == Symbol('a') + + class CustomSymbol(Symbol): + pass + + a = sympify(numpy.str_('a'), {"Symbol": CustomSymbol}) + assert isinstance(a, CustomSymbol) + + a = sympify(numpy.str_('x^y')) + assert a == x**y + a = sympify(numpy.str_('x^y'), convert_xor=False) + assert a == Xor(x, y) + + raises(SympifyError, lambda: sympify(numpy.str_('x'), strict=True)) + + a = sympify(numpy.str_('1.1')) + assert isinstance(a, Float) + assert a == 1.1 + + a = sympify(numpy.str_('1.1'), rational=True) + assert isinstance(a, Rational) + assert a == Rational(11, 10) + + a = sympify(numpy.str_('x + x')) + assert isinstance(a, Mul) + assert a == 2*x + + a = sympify(numpy.str_('x + x'), evaluate=False) + assert isinstance(a, Add) + assert a == Add(x, x, evaluate=False) + + +def test_issue_5939(): + a = Symbol('a') + b = Symbol('b') + assert sympify('''a+\nb''') == a + b + + +def test_issue_16759(): + d = sympify({.5: 1}) + assert S.Half not in d + assert Float(.5) in d + assert d[.5] is S.One + d = sympify(OrderedDict({.5: 1})) + assert S.Half not in d + assert Float(.5) in d + assert d[.5] is S.One + d = sympify(defaultdict(int, {.5: 1})) + assert S.Half not in d + assert Float(.5) in d + assert d[.5] is S.One + + +def test_issue_17811(): + a = Function('a') + assert sympify('a(x)*5', evaluate=False) == Mul(a(x), 5, evaluate=False) + + +def test_issue_8439(): + assert sympify(float('inf')) == oo + assert x + float('inf') == x + oo + assert S(float('inf')) == oo + + +def test_issue_14706(): + if not numpy: + skip("numpy not installed.") + + z1 = numpy.zeros((1, 1), dtype=numpy.float64) + z2 = numpy.zeros((2, 2), dtype=numpy.float64) + z3 = numpy.zeros((), dtype=numpy.float64) + + y1 = numpy.ones((1, 1), dtype=numpy.float64) + y2 = numpy.ones((2, 2), dtype=numpy.float64) + y3 = numpy.ones((), dtype=numpy.float64) + + assert numpy.all(x + z1 == numpy.full((1, 1), x)) + assert numpy.all(x + z2 == numpy.full((2, 2), x)) + assert numpy.all(z1 + x == numpy.full((1, 1), x)) + assert numpy.all(z2 + x == numpy.full((2, 2), x)) + for z in [z3, + numpy.int64(0), + numpy.float64(0), + numpy.complex64(0)]: + assert x + z == x + assert z + x == x + assert isinstance(x + z, Symbol) + assert isinstance(z + x, Symbol) + + # If these tests fail, then it means that numpy has finally + # fixed the issue of scalar conversion for rank>0 arrays + # which is mentioned in numpy/numpy#10404. In that case, + # some changes have to be made in sympify.py. + # Note: For future reference, for anyone who takes up this + # issue when numpy has finally fixed their side of the problem, + # the changes for this temporary fix were introduced in PR 18651 + assert numpy.all(x + y1 == numpy.full((1, 1), x + 1.0)) + assert numpy.all(x + y2 == numpy.full((2, 2), x + 1.0)) + assert numpy.all(y1 + x == numpy.full((1, 1), x + 1.0)) + assert numpy.all(y2 + x == numpy.full((2, 2), x + 1.0)) + for y_ in [y3, + numpy.int64(1), + numpy.float64(1), + numpy.complex64(1)]: + assert x + y_ == y_ + x + assert isinstance(x + y_, Add) + assert isinstance(y_ + x, Add) + + assert x + numpy.array(x) == 2 * x + assert x + numpy.array([x]) == numpy.array([2*x], dtype=object) + + assert sympify(numpy.array([1])) == ImmutableDenseNDimArray([1], 1) + assert sympify(numpy.array([[[1]]])) == ImmutableDenseNDimArray([1], (1, 1, 1)) + assert sympify(z1) == ImmutableDenseNDimArray([0.0], (1, 1)) + assert sympify(z2) == ImmutableDenseNDimArray([0.0, 0.0, 0.0, 0.0], (2, 2)) + assert sympify(z3) == ImmutableDenseNDimArray([0.0], ()) + assert sympify(z3, strict=True) == 0.0 + + raises(SympifyError, lambda: sympify(numpy.array([1]), strict=True)) + raises(SympifyError, lambda: sympify(z1, strict=True)) + raises(SympifyError, lambda: sympify(z2, strict=True)) + + +def test_issue_21536(): + #test to check evaluate=False in case of iterable input + u = sympify("x+3*x+2", evaluate=False) + v = sympify("2*x+4*x+2+4", evaluate=False) + + assert u.is_Add and set(u.args) == {x, 3*x, 2} + assert v.is_Add and set(v.args) == {2*x, 4*x, 2, 4} + assert sympify(["x+3*x+2", "2*x+4*x+2+4"], evaluate=False) == [u, v] + + #test to check evaluate=True in case of iterable input + u = sympify("x+3*x+2", evaluate=True) + v = sympify("2*x+4*x+2+4", evaluate=True) + + assert u.is_Add and set(u.args) == {4*x, 2} + assert v.is_Add and set(v.args) == {6*x, 6} + assert sympify(["x+3*x+2", "2*x+4*x+2+4"], evaluate=True) == [u, v] + + #test to check evaluate with no input in case of iterable input + u = sympify("x+3*x+2") + v = sympify("2*x+4*x+2+4") + + assert u.is_Add and set(u.args) == {4*x, 2} + assert v.is_Add and set(v.args) == {6*x, 6} + assert sympify(["x+3*x+2", "2*x+4*x+2+4"]) == [u, v] + +def test_issue_27284(): + if not numpy: + skip("numpy not installed.") + + assert Float(numpy.float32(float('inf'))) == S.Infinity + assert Float(numpy.float32(float('-inf'))) == S.NegativeInfinity diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_traversal.py b/phivenv/Lib/site-packages/sympy/core/tests/test_traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..8bf067283eaba5d4a073a73feb07aac199055a7f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_traversal.py @@ -0,0 +1,119 @@ +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import symbols +from sympy.core.singleton import S +from sympy.core.function import expand, Function +from sympy.core.numbers import I +from sympy.integrals.integrals import Integral +from sympy.polys.polytools import factor +from sympy.core.traversal import preorder_traversal, use, postorder_traversal, iterargs, iterfreeargs +from sympy.functions.elementary.piecewise import ExprCondPair, Piecewise +from sympy.testing.pytest import warns_deprecated_sympy +from sympy.utilities.iterables import capture + +b1 = Basic() +b2 = Basic(b1) +b3 = Basic(b2) +b21 = Basic(b2, b1) + + +def test_preorder_traversal(): + expr = Basic(b21, b3) + assert list( + preorder_traversal(expr)) == [expr, b21, b2, b1, b1, b3, b2, b1] + assert list(preorder_traversal(('abc', ('d', 'ef')))) == [ + ('abc', ('d', 'ef')), 'abc', ('d', 'ef'), 'd', 'ef'] + + result = [] + pt = preorder_traversal(expr) + for i in pt: + result.append(i) + if i == b2: + pt.skip() + assert result == [expr, b21, b2, b1, b3, b2] + + w, x, y, z = symbols('w:z') + expr = z + w*(x + y) + assert list(preorder_traversal([expr], keys=default_sort_key)) == \ + [[w*(x + y) + z], w*(x + y) + z, z, w*(x + y), w, x + y, x, y] + assert list(preorder_traversal((x + y)*z, keys=True)) == \ + [z*(x + y), z, x + y, x, y] + + +def test_use(): + x, y = symbols('x y') + + assert use(0, expand) == 0 + + f = (x + y)**2*x + 1 + + assert use(f, expand, level=0) == x**3 + 2*x**2*y + x*y**2 + + 1 + assert use(f, expand, level=1) == x**3 + 2*x**2*y + x*y**2 + + 1 + assert use(f, expand, level=2) == 1 + x*(2*x*y + x**2 + y**2) + assert use(f, expand, level=3) == (x + y)**2*x + 1 + + f = (x**2 + 1)**2 - 1 + kwargs = {'gaussian': True} + + assert use(f, factor, level=0, kwargs=kwargs) == x**2*(x**2 + 2) + assert use(f, factor, level=1, kwargs=kwargs) == (x + I)**2*(x - I)**2 - 1 + assert use(f, factor, level=2, kwargs=kwargs) == (x + I)**2*(x - I)**2 - 1 + assert use(f, factor, level=3, kwargs=kwargs) == (x**2 + 1)**2 - 1 + + +def test_postorder_traversal(): + x, y, z, w = symbols('x y z w') + expr = z + w*(x + y) + expected = [z, w, x, y, x + y, w*(x + y), w*(x + y) + z] + assert list(postorder_traversal(expr, keys=default_sort_key)) == expected + assert list(postorder_traversal(expr, keys=True)) == expected + + expr = Piecewise((x, x < 1), (x**2, True)) + expected = [ + x, 1, x, x < 1, ExprCondPair(x, x < 1), + 2, x, x**2, S.true, + ExprCondPair(x**2, True), Piecewise((x, x < 1), (x**2, True)) + ] + assert list(postorder_traversal(expr, keys=default_sort_key)) == expected + assert list(postorder_traversal( + [expr], keys=default_sort_key)) == expected + [[expr]] + + assert list(postorder_traversal(Integral(x**2, (x, 0, 1)), + keys=default_sort_key)) == [ + 2, x, x**2, 0, 1, x, Tuple(x, 0, 1), + Integral(x**2, Tuple(x, 0, 1)) + ] + assert list(postorder_traversal(('abc', ('d', 'ef')))) == [ + 'abc', 'd', 'ef', ('d', 'ef'), ('abc', ('d', 'ef'))] + + +def test_iterargs(): + f = Function('f') + x = symbols('x') + assert list(iterfreeargs(Integral(f(x), (f(x), 1)))) == [ + Integral(f(x), (f(x), 1)), 1] + assert list(iterargs(Integral(f(x), (f(x), 1)))) == [ + Integral(f(x), (f(x), 1)), f(x), (f(x), 1), x, f(x), 1, x] + +def test_deprecated_imports(): + x = symbols('x') + + with warns_deprecated_sympy(): + from sympy.core.basic import preorder_traversal + preorder_traversal(x) + with warns_deprecated_sympy(): + from sympy.simplify.simplify import bottom_up + bottom_up(x, lambda x: x) + with warns_deprecated_sympy(): + from sympy.simplify.simplify import walk + walk(x, lambda x: x) + with warns_deprecated_sympy(): + from sympy.simplify.traversaltools import use + use(x, lambda x: x) + with warns_deprecated_sympy(): + from sympy.utilities.iterables import postorder_traversal + postorder_traversal(x) + with warns_deprecated_sympy(): + from sympy.utilities.iterables import interactive_traversal + capture(lambda: interactive_traversal(x)) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_truediv.py b/phivenv/Lib/site-packages/sympy/core/tests/test_truediv.py new file mode 100644 index 0000000000000000000000000000000000000000..1fcf9e1ab754d05a3b47e7ec0c2be5ea9929da02 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_truediv.py @@ -0,0 +1,54 @@ +#this module tests that SymPy works with true division turned on + +from sympy.core.numbers import (Float, Rational) +from sympy.core.symbol import Symbol + + +def test_truediv(): + assert 1/2 != 0 + assert Rational(1)/2 != 0 + + +def dotest(s): + x = Symbol("x") + y = Symbol("y") + l = [ + Rational(2), + Float("1.3"), + x, + y, + pow(x, y)*y, + 5, + 5.5 + ] + for x in l: + for y in l: + s(x, y) + return True + + +def test_basic(): + def s(a, b): + x = a + x = +a + x = -a + x = a + b + x = a - b + x = a*b + x = a/b + x = a**b + del x + assert dotest(s) + + +def test_ibasic(): + def s(a, b): + x = a + x += b + x = a + x -= b + x = a + x *= b + x = a + x /= b + assert dotest(s) diff --git a/phivenv/Lib/site-packages/sympy/core/tests/test_var.py b/phivenv/Lib/site-packages/sympy/core/tests/test_var.py new file mode 100644 index 0000000000000000000000000000000000000000..a02709464c9878082fecaf70fa47067ac8838ac6 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/tests/test_var.py @@ -0,0 +1,62 @@ +from sympy.core.function import (Function, FunctionClass) +from sympy.core.symbol import (Symbol, var) +from sympy.testing.pytest import raises + +def test_var(): + ns = {"var": var, "raises": raises} + eval("var('a')", ns) + assert ns["a"] == Symbol("a") + + eval("var('b bb cc zz _x')", ns) + assert ns["b"] == Symbol("b") + assert ns["bb"] == Symbol("bb") + assert ns["cc"] == Symbol("cc") + assert ns["zz"] == Symbol("zz") + assert ns["_x"] == Symbol("_x") + + v = eval("var(['d', 'e', 'fg'])", ns) + assert ns['d'] == Symbol('d') + assert ns['e'] == Symbol('e') + assert ns['fg'] == Symbol('fg') + +# check return value + assert v != ['d', 'e', 'fg'] + assert v == [Symbol('d'), Symbol('e'), Symbol('fg')] + + +def test_var_return(): + ns = {"var": var, "raises": raises} + "raises(ValueError, lambda: var(''))" + v2 = eval("var('q')", ns) + v3 = eval("var('q p')", ns) + + assert v2 == Symbol('q') + assert v3 == (Symbol('q'), Symbol('p')) + + +def test_var_accepts_comma(): + ns = {"var": var} + v1 = eval("var('x y z')", ns) + v2 = eval("var('x,y,z')", ns) + v3 = eval("var('x,y z')", ns) + + assert v1 == v2 + assert v1 == v3 + + +def test_var_keywords(): + ns = {"var": var} + eval("var('x y', real=True)", ns) + assert ns['x'].is_real and ns['y'].is_real + + +def test_var_cls(): + ns = {"var": var, "Function": Function} + eval("var('f', cls=Function)", ns) + + assert isinstance(ns['f'], FunctionClass) + + eval("var('g,h', cls=Function)", ns) + + assert isinstance(ns['g'], FunctionClass) + assert isinstance(ns['h'], FunctionClass) diff --git a/phivenv/Lib/site-packages/sympy/core/trace.py b/phivenv/Lib/site-packages/sympy/core/trace.py new file mode 100644 index 0000000000000000000000000000000000000000..58326ce1fdd5038f0b5805afe7c453314a22cb6a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/trace.py @@ -0,0 +1,12 @@ +from sympy.utilities.exceptions import sympy_deprecation_warning + +sympy_deprecation_warning( + """ + sympy.core.trace is deprecated. Use sympy.physics.quantum.trace + instead. + """, + deprecated_since_version="1.10", + active_deprecations_target="sympy-core-trace-deprecated", +) + +from sympy.physics.quantum.trace import Tr # noqa:F401 diff --git a/phivenv/Lib/site-packages/sympy/core/traversal.py b/phivenv/Lib/site-packages/sympy/core/traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..e4e000ef44bf636b9adc700964a7ee4c2372a019 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/core/traversal.py @@ -0,0 +1,304 @@ +from __future__ import annotations + +from typing import Iterator + +from .basic import Basic +from .sorting import ordered +from .sympify import sympify +from sympy.utilities.iterables import iterable + + + +def iterargs(expr): + """Yield the args of a Basic object in a breadth-first traversal. + Depth-traversal stops if `arg.args` is either empty or is not + an iterable. + + Examples + ======== + + >>> from sympy import Integral, Function + >>> from sympy.abc import x + >>> f = Function('f') + >>> from sympy.core.traversal import iterargs + >>> list(iterargs(Integral(f(x), (f(x), 1)))) + [Integral(f(x), (f(x), 1)), f(x), (f(x), 1), x, f(x), 1, x] + + See Also + ======== + iterfreeargs, preorder_traversal + """ + args = [expr] + for i in args: + yield i + args.extend(i.args) + + +def iterfreeargs(expr, _first=True): + """Yield the args of a Basic object in a breadth-first traversal. + Depth-traversal stops if `arg.args` is either empty or is not + an iterable. The bound objects of an expression will be returned + as canonical variables. + + Examples + ======== + + >>> from sympy import Integral, Function + >>> from sympy.abc import x + >>> f = Function('f') + >>> from sympy.core.traversal import iterfreeargs + >>> list(iterfreeargs(Integral(f(x), (f(x), 1)))) + [Integral(f(x), (f(x), 1)), 1] + + See Also + ======== + iterargs, preorder_traversal + """ + args = [expr] + for i in args: + yield i + if _first and hasattr(i, 'bound_symbols'): + void = i.canonical_variables.values() + for i in iterfreeargs(i.as_dummy(), _first=False): + if not i.has(*void): + yield i + args.extend(i.args) + + +class preorder_traversal: + """ + Do a pre-order traversal of a tree. + + This iterator recursively yields nodes that it has visited in a pre-order + fashion. That is, it yields the current node then descends through the + tree breadth-first to yield all of a node's children's pre-order + traversal. + + + For an expression, the order of the traversal depends on the order of + .args, which in many cases can be arbitrary. + + Parameters + ========== + node : SymPy expression + The expression to traverse. + keys : (default None) sort key(s) + The key(s) used to sort args of Basic objects. When None, args of Basic + objects are processed in arbitrary order. If key is defined, it will + be passed along to ordered() as the only key(s) to use to sort the + arguments; if ``key`` is simply True then the default keys of ordered + will be used. + + Yields + ====== + subtree : SymPy expression + All of the subtrees in the tree. + + Examples + ======== + + >>> from sympy import preorder_traversal, symbols + >>> x, y, z = symbols('x y z') + + The nodes are returned in the order that they are encountered unless key + is given; simply passing key=True will guarantee that the traversal is + unique. + + >>> list(preorder_traversal((x + y)*z, keys=None)) # doctest: +SKIP + [z*(x + y), z, x + y, y, x] + >>> list(preorder_traversal((x + y)*z, keys=True)) + [z*(x + y), z, x + y, x, y] + + """ + def __init__(self, node, keys=None): + self._skip_flag = False + self._pt = self._preorder_traversal(node, keys) + + def _preorder_traversal(self, node, keys): + yield node + if self._skip_flag: + self._skip_flag = False + return + if isinstance(node, Basic): + if not keys and hasattr(node, '_argset'): + # LatticeOp keeps args as a set. We should use this if we + # don't care about the order, to prevent unnecessary sorting. + args = node._argset + else: + args = node.args + if keys: + if keys != True: + args = ordered(args, keys, default=False) + else: + args = ordered(args) + for arg in args: + yield from self._preorder_traversal(arg, keys) + elif iterable(node): + for item in node: + yield from self._preorder_traversal(item, keys) + + def skip(self): + """ + Skip yielding current node's (last yielded node's) subtrees. + + Examples + ======== + + >>> from sympy import preorder_traversal, symbols + >>> x, y, z = symbols('x y z') + >>> pt = preorder_traversal((x + y*z)*z) + >>> for i in pt: + ... print(i) + ... if i == x + y*z: + ... pt.skip() + z*(x + y*z) + z + x + y*z + """ + self._skip_flag = True + + def __next__(self): + return next(self._pt) + + def __iter__(self) -> Iterator[Basic]: + return self + + +def use(expr, func, level=0, args=(), kwargs={}): + """ + Use ``func`` to transform ``expr`` at the given level. + + Examples + ======== + + >>> from sympy import use, expand + >>> from sympy.abc import x, y + + >>> f = (x + y)**2*x + 1 + + >>> use(f, expand, level=2) + x*(x**2 + 2*x*y + y**2) + 1 + >>> expand(f) + x**3 + 2*x**2*y + x*y**2 + 1 + + """ + def _use(expr, level): + if not level: + return func(expr, *args, **kwargs) + else: + if expr.is_Atom: + return expr + else: + level -= 1 + _args = [_use(arg, level) for arg in expr.args] + return expr.__class__(*_args) + + return _use(sympify(expr), level) + + +def walk(e, *target): + """Iterate through the args that are the given types (target) and + return a list of the args that were traversed; arguments + that are not of the specified types are not traversed. + + Examples + ======== + + >>> from sympy.core.traversal import walk + >>> from sympy import Min, Max + >>> from sympy.abc import x, y, z + >>> list(walk(Min(x, Max(y, Min(1, z))), Min)) + [Min(x, Max(y, Min(1, z)))] + >>> list(walk(Min(x, Max(y, Min(1, z))), Min, Max)) + [Min(x, Max(y, Min(1, z))), Max(y, Min(1, z)), Min(1, z)] + + See Also + ======== + + bottom_up + """ + if isinstance(e, target): + yield e + for i in e.args: + yield from walk(i, *target) + + +def bottom_up(rv, F, atoms=False, nonbasic=False): + """Apply ``F`` to all expressions in an expression tree from the + bottom up. If ``atoms`` is True, apply ``F`` even if there are no args; + if ``nonbasic`` is True, try to apply ``F`` to non-Basic objects. + """ + args = getattr(rv, 'args', None) + if args is not None: + if args: + args = tuple([bottom_up(a, F, atoms, nonbasic) for a in args]) + if args != rv.args: + rv = rv.func(*args) + rv = F(rv) + elif atoms: + rv = F(rv) + else: + if nonbasic: + try: + rv = F(rv) + except TypeError: + pass + + return rv + + +def postorder_traversal(node, keys=None): + """ + Do a postorder traversal of a tree. + + This generator recursively yields nodes that it has visited in a postorder + fashion. That is, it descends through the tree depth-first to yield all of + a node's children's postorder traversal before yielding the node itself. + + Parameters + ========== + + node : SymPy expression + The expression to traverse. + keys : (default None) sort key(s) + The key(s) used to sort args of Basic objects. When None, args of Basic + objects are processed in arbitrary order. If key is defined, it will + be passed along to ordered() as the only key(s) to use to sort the + arguments; if ``key`` is simply True then the default keys of + ``ordered`` will be used (node count and default_sort_key). + + Yields + ====== + subtree : SymPy expression + All of the subtrees in the tree. + + Examples + ======== + + >>> from sympy import postorder_traversal + >>> from sympy.abc import w, x, y, z + + The nodes are returned in the order that they are encountered unless key + is given; simply passing key=True will guarantee that the traversal is + unique. + + >>> list(postorder_traversal(w + (x + y)*z)) # doctest: +SKIP + [z, y, x, x + y, z*(x + y), w, w + z*(x + y)] + >>> list(postorder_traversal(w + (x + y)*z, keys=True)) + [w, z, x, y, x + y, z*(x + y), w + z*(x + y)] + + + """ + if isinstance(node, Basic): + args = node.args + if keys: + if keys != True: + args = ordered(args, keys, default=False) + else: + args = ordered(args) + for arg in args: + yield from postorder_traversal(arg, keys) + elif iterable(node): + for item in node: + yield from postorder_traversal(item, keys) + yield node diff --git a/phivenv/Lib/site-packages/sympy/crypto/__init__.py b/phivenv/Lib/site-packages/sympy/crypto/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2b27b4b036e5f2ed93a1ea88cd7d7144eb5615d4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/crypto/__init__.py @@ -0,0 +1,35 @@ +from sympy.crypto.crypto import (cycle_list, + encipher_shift, encipher_affine, encipher_substitution, + check_and_join, encipher_vigenere, decipher_vigenere, bifid5_square, + bifid6_square, encipher_hill, decipher_hill, + encipher_bifid5, encipher_bifid6, decipher_bifid5, + decipher_bifid6, encipher_kid_rsa, decipher_kid_rsa, + kid_rsa_private_key, kid_rsa_public_key, decipher_rsa, rsa_private_key, + rsa_public_key, encipher_rsa, lfsr_connection_polynomial, + lfsr_autocorrelation, lfsr_sequence, encode_morse, decode_morse, + elgamal_private_key, elgamal_public_key, decipher_elgamal, + encipher_elgamal, dh_private_key, dh_public_key, dh_shared_key, + padded_key, encipher_bifid, decipher_bifid, bifid_square, bifid5, + bifid6, bifid10, decipher_gm, encipher_gm, gm_public_key, + gm_private_key, bg_private_key, bg_public_key, encipher_bg, decipher_bg, + encipher_rot13, decipher_rot13, encipher_atbash, decipher_atbash, + encipher_railfence, decipher_railfence) + +__all__ = [ + 'cycle_list', 'encipher_shift', 'encipher_affine', + 'encipher_substitution', 'check_and_join', 'encipher_vigenere', + 'decipher_vigenere', 'bifid5_square', 'bifid6_square', 'encipher_hill', + 'decipher_hill', 'encipher_bifid5', 'encipher_bifid6', 'decipher_bifid5', + 'decipher_bifid6', 'encipher_kid_rsa', 'decipher_kid_rsa', + 'kid_rsa_private_key', 'kid_rsa_public_key', 'decipher_rsa', + 'rsa_private_key', 'rsa_public_key', 'encipher_rsa', + 'lfsr_connection_polynomial', 'lfsr_autocorrelation', 'lfsr_sequence', + 'encode_morse', 'decode_morse', 'elgamal_private_key', + 'elgamal_public_key', 'decipher_elgamal', 'encipher_elgamal', + 'dh_private_key', 'dh_public_key', 'dh_shared_key', 'padded_key', + 'encipher_bifid', 'decipher_bifid', 'bifid_square', 'bifid5', 'bifid6', + 'bifid10', 'decipher_gm', 'encipher_gm', 'gm_public_key', + 'gm_private_key', 'bg_private_key', 'bg_public_key', 'encipher_bg', + 'decipher_bg', 'encipher_rot13', 'decipher_rot13', 'encipher_atbash', + 'decipher_atbash', 'encipher_railfence', 'decipher_railfence', +] diff --git a/phivenv/Lib/site-packages/sympy/crypto/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/crypto/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de47eb2d7c600e2fcffd53f9b1b8d9eab62191ef Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/crypto/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/crypto/__pycache__/crypto.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/crypto/__pycache__/crypto.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1f806297e5388e79cad6615b22d34ddc1346772 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/crypto/__pycache__/crypto.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/crypto/crypto.py b/phivenv/Lib/site-packages/sympy/crypto/crypto.py new file mode 100644 index 0000000000000000000000000000000000000000..2c298e4ac08616dbe7d607a9d56d33b7fe9d5e2d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/crypto/crypto.py @@ -0,0 +1,3368 @@ +""" +This file contains some classical ciphers and routines +implementing a linear-feedback shift register (LFSR) +and the Diffie-Hellman key exchange. + +.. warning:: + + This module is intended for educational purposes only. Do not use the + functions in this module for real cryptographic applications. If you wish + to encrypt real data, we recommend using something like the `cryptography + `_ module. + +""" + +from string import whitespace, ascii_uppercase as uppercase, printable +from functools import reduce +import string +import warnings + +from itertools import cycle + +from sympy.external.gmpy import GROUND_TYPES +from sympy.core import Symbol +from sympy.core.numbers import Rational +from sympy.core.random import _randrange, _randint +from sympy.external.gmpy import gcd, invert +from sympy.functions.combinatorial.numbers import (totient as _euler, + reduced_totient as _carmichael) +from sympy.matrices import Matrix +from sympy.ntheory import isprime, primitive_root, factorint +from sympy.ntheory.generate import nextprime +from sympy.ntheory.modular import crt +from sympy.polys.domains import FF +from sympy.polys.polytools import Poly +from sympy.utilities.misc import as_int, filldedent, translate +from sympy.utilities.iterables import uniq, multiset +from sympy.utilities.decorator import doctest_depends_on + + +if GROUND_TYPES == 'flint': + __doctest_skip__ = ['lfsr_sequence'] + + +class NonInvertibleCipherWarning(RuntimeWarning): + """A warning raised if the cipher is not invertible.""" + def __init__(self, msg): + self.fullMessage = msg + + def __str__(self): + return '\n\t' + self.fullMessage + + def warn(self, stacklevel=3): + warnings.warn(self, stacklevel=stacklevel) + + +def AZ(s=None): + """Return the letters of ``s`` in uppercase. In case more than + one string is passed, each of them will be processed and a list + of upper case strings will be returned. + + Examples + ======== + + >>> from sympy.crypto.crypto import AZ + >>> AZ('Hello, world!') + 'HELLOWORLD' + >>> AZ('Hello, world!'.split()) + ['HELLO', 'WORLD'] + + See Also + ======== + + check_and_join + + """ + if not s: + return uppercase + t = isinstance(s, str) + if t: + s = [s] + rv = [check_and_join(i.upper().split(), uppercase, filter=True) + for i in s] + if t: + return rv[0] + return rv + +bifid5 = AZ().replace('J', '') +bifid6 = AZ() + string.digits +bifid10 = printable + + +def padded_key(key, symbols): + """Return a string of the distinct characters of ``symbols`` with + those of ``key`` appearing first. A ValueError is raised if + a) there are duplicate characters in ``symbols`` or + b) there are characters in ``key`` that are not in ``symbols``. + + Examples + ======== + + >>> from sympy.crypto.crypto import padded_key + >>> padded_key('PUPPY', 'OPQRSTUVWXY') + 'PUYOQRSTVWX' + >>> padded_key('RSA', 'ARTIST') + Traceback (most recent call last): + ... + ValueError: duplicate characters in symbols: T + + """ + syms = list(uniq(symbols)) + if len(syms) != len(symbols): + extra = ''.join(sorted({ + i for i in symbols if symbols.count(i) > 1})) + raise ValueError('duplicate characters in symbols: %s' % extra) + extra = set(key) - set(syms) + if extra: + raise ValueError( + 'characters in key but not symbols: %s' % ''.join( + sorted(extra))) + key0 = ''.join(list(uniq(key))) + # remove from syms characters in key0 + return key0 + translate(''.join(syms), None, key0) + + +def check_and_join(phrase, symbols=None, filter=None): + """ + Joins characters of ``phrase`` and if ``symbols`` is given, raises + an error if any character in ``phrase`` is not in ``symbols``. + + Parameters + ========== + + phrase + String or list of strings to be returned as a string. + + symbols + Iterable of characters allowed in ``phrase``. + + If ``symbols`` is ``None``, no checking is performed. + + Examples + ======== + + >>> from sympy.crypto.crypto import check_and_join + >>> check_and_join('a phrase') + 'a phrase' + >>> check_and_join('a phrase'.upper().split()) + 'APHRASE' + >>> check_and_join('a phrase!'.upper().split(), 'ARE', filter=True) + 'ARAE' + >>> check_and_join('a phrase!'.upper().split(), 'ARE') + Traceback (most recent call last): + ... + ValueError: characters in phrase but not symbols: "!HPS" + + """ + rv = ''.join(''.join(phrase)) + if symbols is not None: + symbols = check_and_join(symbols) + missing = ''.join(sorted(set(rv) - set(symbols))) + if missing: + if not filter: + raise ValueError( + 'characters in phrase but not symbols: "%s"' % missing) + rv = translate(rv, None, missing) + return rv + + +def _prep(msg, key, alp, default=None): + if not alp: + if not default: + alp = AZ() + msg = AZ(msg) + key = AZ(key) + else: + alp = default + else: + alp = ''.join(alp) + key = check_and_join(key, alp, filter=True) + msg = check_and_join(msg, alp, filter=True) + return msg, key, alp + + +def cycle_list(k, n): + """ + Returns the elements of the list ``range(n)`` shifted to the + left by ``k`` (so the list starts with ``k`` (mod ``n``)). + + Examples + ======== + + >>> from sympy.crypto.crypto import cycle_list + >>> cycle_list(3, 10) + [3, 4, 5, 6, 7, 8, 9, 0, 1, 2] + + """ + k = k % n + return list(range(k, n)) + list(range(k)) + + +######## shift cipher examples ############ + + +def encipher_shift(msg, key, symbols=None): + """ + Performs shift cipher encryption on plaintext msg, and returns the + ciphertext. + + Parameters + ========== + + key : int + The secret key. + + msg : str + Plaintext of upper-case letters. + + Returns + ======= + + str + Ciphertext of upper-case letters. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_shift, decipher_shift + >>> msg = "GONAVYBEATARMY" + >>> ct = encipher_shift(msg, 1); ct + 'HPOBWZCFBUBSNZ' + + To decipher the shifted text, change the sign of the key: + + >>> encipher_shift(ct, -1) + 'GONAVYBEATARMY' + + There is also a convenience function that does this with the + original key: + + >>> decipher_shift(ct, 1) + 'GONAVYBEATARMY' + + Notes + ===== + + ALGORITHM: + + STEPS: + 0. Number the letters of the alphabet from 0, ..., N + 1. Compute from the string ``msg`` a list ``L1`` of + corresponding integers. + 2. Compute from the list ``L1`` a new list ``L2``, given by + adding ``(k mod 26)`` to each element in ``L1``. + 3. Compute from the list ``L2`` a string ``ct`` of + corresponding letters. + + The shift cipher is also called the Caesar cipher, after + Julius Caesar, who, according to Suetonius, used it with a + shift of three to protect messages of military significance. + Caesar's nephew Augustus reportedly used a similar cipher, but + with a right shift of 1. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Caesar_cipher + .. [2] https://mathworld.wolfram.com/CaesarsMethod.html + + See Also + ======== + + decipher_shift + + """ + msg, _, A = _prep(msg, '', symbols) + shift = len(A) - key % len(A) + key = A[shift:] + A[:shift] + return translate(msg, key, A) + + +def decipher_shift(msg, key, symbols=None): + """ + Return the text by shifting the characters of ``msg`` to the + left by the amount given by ``key``. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_shift, decipher_shift + >>> msg = "GONAVYBEATARMY" + >>> ct = encipher_shift(msg, 1); ct + 'HPOBWZCFBUBSNZ' + + To decipher the shifted text, change the sign of the key: + + >>> encipher_shift(ct, -1) + 'GONAVYBEATARMY' + + Or use this function with the original key: + + >>> decipher_shift(ct, 1) + 'GONAVYBEATARMY' + + """ + return encipher_shift(msg, -key, symbols) + +def encipher_rot13(msg, symbols=None): + """ + Performs the ROT13 encryption on a given plaintext ``msg``. + + Explanation + =========== + + ROT13 is a substitution cipher which substitutes each letter + in the plaintext message for the letter furthest away from it + in the English alphabet. + + Equivalently, it is just a Caeser (shift) cipher with a shift + key of 13 (midway point of the alphabet). + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/ROT13 + + See Also + ======== + + decipher_rot13 + encipher_shift + + """ + return encipher_shift(msg, 13, symbols) + +def decipher_rot13(msg, symbols=None): + """ + Performs the ROT13 decryption on a given plaintext ``msg``. + + Explanation + ============ + + ``decipher_rot13`` is equivalent to ``encipher_rot13`` as both + ``decipher_shift`` with a key of 13 and ``encipher_shift`` key with a + key of 13 will return the same results. Nonetheless, + ``decipher_rot13`` has nonetheless been explicitly defined here for + consistency. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_rot13, decipher_rot13 + >>> msg = 'GONAVYBEATARMY' + >>> ciphertext = encipher_rot13(msg);ciphertext + 'TBANILORNGNEZL' + >>> decipher_rot13(ciphertext) + 'GONAVYBEATARMY' + >>> encipher_rot13(msg) == decipher_rot13(msg) + True + >>> msg == decipher_rot13(ciphertext) + True + + """ + return decipher_shift(msg, 13, symbols) + +######## affine cipher examples ############ + + +def encipher_affine(msg, key, symbols=None, _inverse=False): + r""" + Performs the affine cipher encryption on plaintext ``msg``, and + returns the ciphertext. + + Explanation + =========== + + Encryption is based on the map `x \rightarrow ax+b` (mod `N`) + where ``N`` is the number of characters in the alphabet. + Decryption is based on the map `x \rightarrow cx+d` (mod `N`), + where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`). + In particular, for the map to be invertible, we need + `\mathrm{gcd}(a, N) = 1` and an error will be raised if this is + not true. + + Parameters + ========== + + msg : str + Characters that appear in ``symbols``. + + a, b : int, int + A pair integers, with ``gcd(a, N) = 1`` (the secret key). + + symbols + String of characters (default = uppercase letters). + + When no symbols are given, ``msg`` is converted to upper case + letters and all other characters are ignored. + + Returns + ======= + + ct + String of characters (the ciphertext message) + + Notes + ===== + + ALGORITHM: + + STEPS: + 0. Number the letters of the alphabet from 0, ..., N + 1. Compute from the string ``msg`` a list ``L1`` of + corresponding integers. + 2. Compute from the list ``L1`` a new list ``L2``, given by + replacing ``x`` by ``a*x + b (mod N)``, for each element + ``x`` in ``L1``. + 3. Compute from the list ``L2`` a string ``ct`` of + corresponding letters. + + This is a straightforward generalization of the shift cipher with + the added complexity of requiring 2 characters to be deciphered in + order to recover the key. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Affine_cipher + + See Also + ======== + + decipher_affine + + """ + msg, _, A = _prep(msg, '', symbols) + N = len(A) + a, b = key + assert gcd(a, N) == 1 + if _inverse: + c = invert(a, N) + d = -b*c + a, b = c, d + B = ''.join([A[(a*i + b) % N] for i in range(N)]) + return translate(msg, A, B) + + +def decipher_affine(msg, key, symbols=None): + r""" + Return the deciphered text that was made from the mapping, + `x \rightarrow ax+b` (mod `N`), where ``N`` is the + number of characters in the alphabet. Deciphering is done by + reciphering with a new key: `x \rightarrow cx+d` (mod `N`), + where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`). + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_affine, decipher_affine + >>> msg = "GO NAVY BEAT ARMY" + >>> key = (3, 1) + >>> encipher_affine(msg, key) + 'TROBMVENBGBALV' + >>> decipher_affine(_, key) + 'GONAVYBEATARMY' + + See Also + ======== + + encipher_affine + + """ + return encipher_affine(msg, key, symbols, _inverse=True) + + +def encipher_atbash(msg, symbols=None): + r""" + Enciphers a given ``msg`` into its Atbash ciphertext and returns it. + + Explanation + =========== + + Atbash is a substitution cipher originally used to encrypt the Hebrew + alphabet. Atbash works on the principle of mapping each alphabet to its + reverse / counterpart (i.e. a would map to z, b to y etc.) + + Atbash is functionally equivalent to the affine cipher with ``a = 25`` + and ``b = 25`` + + See Also + ======== + + decipher_atbash + + """ + return encipher_affine(msg, (25, 25), symbols) + + +def decipher_atbash(msg, symbols=None): + r""" + Deciphers a given ``msg`` using Atbash cipher and returns it. + + Explanation + =========== + + ``decipher_atbash`` is functionally equivalent to ``encipher_atbash``. + However, it has still been added as a separate function to maintain + consistency. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_atbash, decipher_atbash + >>> msg = 'GONAVYBEATARMY' + >>> encipher_atbash(msg) + 'TLMZEBYVZGZINB' + >>> decipher_atbash(msg) + 'TLMZEBYVZGZINB' + >>> encipher_atbash(msg) == decipher_atbash(msg) + True + >>> msg == encipher_atbash(encipher_atbash(msg)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Atbash + + See Also + ======== + + encipher_atbash + + """ + return decipher_affine(msg, (25, 25), symbols) + +#################### substitution cipher ########################### + + +def encipher_substitution(msg, old, new=None): + r""" + Returns the ciphertext obtained by replacing each character that + appears in ``old`` with the corresponding character in ``new``. + If ``old`` is a mapping, then new is ignored and the replacements + defined by ``old`` are used. + + Explanation + =========== + + This is a more general than the affine cipher in that the key can + only be recovered by determining the mapping for each symbol. + Though in practice, once a few symbols are recognized the mappings + for other characters can be quickly guessed. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_substitution, AZ + >>> old = 'OEYAG' + >>> new = '034^6' + >>> msg = AZ("go navy! beat army!") + >>> ct = encipher_substitution(msg, old, new); ct + '60N^V4B3^T^RM4' + + To decrypt a substitution, reverse the last two arguments: + + >>> encipher_substitution(ct, new, old) + 'GONAVYBEATARMY' + + In the special case where ``old`` and ``new`` are a permutation of + order 2 (representing a transposition of characters) their order + is immaterial: + + >>> old = 'NAVY' + >>> new = 'ANYV' + >>> encipher = lambda x: encipher_substitution(x, old, new) + >>> encipher('NAVY') + 'ANYV' + >>> encipher(_) + 'NAVY' + + The substitution cipher, in general, is a method + whereby "units" (not necessarily single characters) of plaintext + are replaced with ciphertext according to a regular system. + + >>> ords = dict(zip('abc', ['\\%i' % ord(i) for i in 'abc'])) + >>> print(encipher_substitution('abc', ords)) + \97\98\99 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Substitution_cipher + + """ + return translate(msg, old, new) + + +###################################################################### +#################### Vigenere cipher examples ######################## +###################################################################### + +def encipher_vigenere(msg, key, symbols=None): + """ + Performs the Vigenere cipher encryption on plaintext ``msg``, and + returns the ciphertext. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_vigenere, AZ + >>> key = "encrypt" + >>> msg = "meet me on monday" + >>> encipher_vigenere(msg, key) + 'QRGKKTHRZQEBPR' + + Section 1 of the Kryptos sculpture at the CIA headquarters + uses this cipher and also changes the order of the + alphabet [2]_. Here is the first line of that section of + the sculpture: + + >>> from sympy.crypto.crypto import decipher_vigenere, padded_key + >>> alp = padded_key('KRYPTOS', AZ()) + >>> key = 'PALIMPSEST' + >>> msg = 'EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJ' + >>> decipher_vigenere(msg, key, alp) + 'BETWEENSUBTLESHADINGANDTHEABSENC' + + Explanation + =========== + + The Vigenere cipher is named after Blaise de Vigenere, a sixteenth + century diplomat and cryptographer, by a historical accident. + Vigenere actually invented a different and more complicated cipher. + The so-called *Vigenere cipher* was actually invented + by Giovan Batista Belaso in 1553. + + This cipher was used in the 1800's, for example, during the American + Civil War. The Confederacy used a brass cipher disk to implement the + Vigenere cipher (now on display in the NSA Museum in Fort + Meade) [1]_. + + The Vigenere cipher is a generalization of the shift cipher. + Whereas the shift cipher shifts each letter by the same amount + (that amount being the key of the shift cipher) the Vigenere + cipher shifts a letter by an amount determined by the key (which is + a word or phrase known only to the sender and receiver). + + For example, if the key was a single letter, such as "C", then the + so-called Vigenere cipher is actually a shift cipher with a + shift of `2` (since "C" is the 2nd letter of the alphabet, if + you start counting at `0`). If the key was a word with two + letters, such as "CA", then the so-called Vigenere cipher will + shift letters in even positions by `2` and letters in odd positions + are left alone (shifted by `0`, since "A" is the 0th letter, if + you start counting at `0`). + + + ALGORITHM: + + INPUT: + + ``msg``: string of characters that appear in ``symbols`` + (the plaintext) + + ``key``: a string of characters that appear in ``symbols`` + (the secret key) + + ``symbols``: a string of letters defining the alphabet + + + OUTPUT: + + ``ct``: string of characters (the ciphertext message) + + STEPS: + 0. Number the letters of the alphabet from 0, ..., N + 1. Compute from the string ``key`` a list ``L1`` of + corresponding integers. Let ``n1 = len(L1)``. + 2. Compute from the string ``msg`` a list ``L2`` of + corresponding integers. Let ``n2 = len(L2)``. + 3. Break ``L2`` up sequentially into sublists of size + ``n1``; the last sublist may be smaller than ``n1`` + 4. For each of these sublists ``L`` of ``L2``, compute a + new list ``C`` given by ``C[i] = L[i] + L1[i] (mod N)`` + to the ``i``-th element in the sublist, for each ``i``. + 5. Assemble these lists ``C`` by concatenation into a new + list of length ``n2``. + 6. Compute from the new list a string ``ct`` of + corresponding letters. + + Once it is known that the key is, say, `n` characters long, + frequency analysis can be applied to every `n`-th letter of + the ciphertext to determine the plaintext. This method is + called *Kasiski examination* (although it was first discovered + by Babbage). If they key is as long as the message and is + comprised of randomly selected characters -- a one-time pad -- the + message is theoretically unbreakable. + + The cipher Vigenere actually discovered is an "auto-key" cipher + described as follows. + + ALGORITHM: + + INPUT: + + ``key``: a string of letters (the secret key) + + ``msg``: string of letters (the plaintext message) + + OUTPUT: + + ``ct``: string of upper-case letters (the ciphertext message) + + STEPS: + 0. Number the letters of the alphabet from 0, ..., N + 1. Compute from the string ``msg`` a list ``L2`` of + corresponding integers. Let ``n2 = len(L2)``. + 2. Let ``n1`` be the length of the key. Append to the + string ``key`` the first ``n2 - n1`` characters of + the plaintext message. Compute from this string (also of + length ``n2``) a list ``L1`` of integers corresponding + to the letter numbers in the first step. + 3. Compute a new list ``C`` given by + ``C[i] = L1[i] + L2[i] (mod N)``. + 4. Compute from the new list a string ``ct`` of letters + corresponding to the new integers. + + To decipher the auto-key ciphertext, the key is used to decipher + the first ``n1`` characters and then those characters become the + key to decipher the next ``n1`` characters, etc...: + + >>> m = AZ('go navy, beat army! yes you can'); m + 'GONAVYBEATARMYYESYOUCAN' + >>> key = AZ('gold bug'); n1 = len(key); n2 = len(m) + >>> auto_key = key + m[:n2 - n1]; auto_key + 'GOLDBUGGONAVYBEATARMYYE' + >>> ct = encipher_vigenere(m, auto_key); ct + 'MCYDWSHKOGAMKZCELYFGAYR' + >>> n1 = len(key) + >>> pt = [] + >>> while ct: + ... part, ct = ct[:n1], ct[n1:] + ... pt.append(decipher_vigenere(part, key)) + ... key = pt[-1] + ... + >>> ''.join(pt) == m + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Vigenere_cipher + .. [2] https://web.archive.org/web/20071116100808/https://filebox.vt.edu/users/batman/kryptos.html + (short URL: https://goo.gl/ijr22d) + + """ + msg, key, A = _prep(msg, key, symbols) + map = {c: i for i, c in enumerate(A)} + key = [map[c] for c in key] + N = len(map) + k = len(key) + rv = [] + for i, m in enumerate(msg): + rv.append(A[(map[m] + key[i % k]) % N]) + rv = ''.join(rv) + return rv + + +def decipher_vigenere(msg, key, symbols=None): + """ + Decode using the Vigenere cipher. + + Examples + ======== + + >>> from sympy.crypto.crypto import decipher_vigenere + >>> key = "encrypt" + >>> ct = "QRGK kt HRZQE BPR" + >>> decipher_vigenere(ct, key) + 'MEETMEONMONDAY' + + """ + msg, key, A = _prep(msg, key, symbols) + map = {c: i for i, c in enumerate(A)} + N = len(A) # normally, 26 + K = [map[c] for c in key] + n = len(K) + C = [map[c] for c in msg] + rv = ''.join([A[(-K[i % n] + c) % N] for i, c in enumerate(C)]) + return rv + + +#################### Hill cipher ######################## + + +def encipher_hill(msg, key, symbols=None, pad="Q"): + r""" + Return the Hill cipher encryption of ``msg``. + + Explanation + =========== + + The Hill cipher [1]_, invented by Lester S. Hill in the 1920's [2]_, + was the first polygraphic cipher in which it was practical + (though barely) to operate on more than three symbols at once. + The following discussion assumes an elementary knowledge of + matrices. + + First, each letter is first encoded as a number starting with 0. + Suppose your message `msg` consists of `n` capital letters, with no + spaces. This may be regarded an `n`-tuple M of elements of + `Z_{26}` (if the letters are those of the English alphabet). A key + in the Hill cipher is a `k x k` matrix `K`, all of whose entries + are in `Z_{26}`, such that the matrix `K` is invertible (i.e., the + linear transformation `K: Z_{N}^k \rightarrow Z_{N}^k` + is one-to-one). + + + Parameters + ========== + + msg + Plaintext message of `n` upper-case letters. + + key + A `k \times k` invertible matrix `K`, all of whose entries are + in `Z_{26}` (or whatever number of symbols are being used). + + pad + Character (default "Q") to use to make length of text be a + multiple of ``k``. + + Returns + ======= + + ct + Ciphertext of upper-case letters. + + Notes + ===== + + ALGORITHM: + + STEPS: + 0. Number the letters of the alphabet from 0, ..., N + 1. Compute from the string ``msg`` a list ``L`` of + corresponding integers. Let ``n = len(L)``. + 2. Break the list ``L`` up into ``t = ceiling(n/k)`` + sublists ``L_1``, ..., ``L_t`` of size ``k`` (with + the last list "padded" to ensure its size is + ``k``). + 3. Compute new list ``C_1``, ..., ``C_t`` given by + ``C[i] = K*L_i`` (arithmetic is done mod N), for each + ``i``. + 4. Concatenate these into a list ``C = C_1 + ... + C_t``. + 5. Compute from ``C`` a string ``ct`` of corresponding + letters. This has length ``k*t``. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hill_cipher + .. [2] Lester S. Hill, Cryptography in an Algebraic Alphabet, + The American Mathematical Monthly Vol.36, June-July 1929, + pp.306-312. + + See Also + ======== + + decipher_hill + + """ + assert key.is_square + assert len(pad) == 1 + msg, pad, A = _prep(msg, pad, symbols) + map = {c: i for i, c in enumerate(A)} + P = [map[c] for c in msg] + N = len(A) + k = key.cols + n = len(P) + m, r = divmod(n, k) + if r: + P = P + [map[pad]]*(k - r) + m += 1 + rv = ''.join([A[c % N] for j in range(m) for c in + list(key*Matrix(k, 1, [P[i] + for i in range(k*j, k*(j + 1))]))]) + return rv + + +def decipher_hill(msg, key, symbols=None): + """ + Deciphering is the same as enciphering but using the inverse of the + key matrix. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_hill, decipher_hill + >>> from sympy import Matrix + + >>> key = Matrix([[1, 2], [3, 5]]) + >>> encipher_hill("meet me on monday", key) + 'UEQDUEODOCTCWQ' + >>> decipher_hill(_, key) + 'MEETMEONMONDAY' + + When the length of the plaintext (stripped of invalid characters) + is not a multiple of the key dimension, extra characters will + appear at the end of the enciphered and deciphered text. In order to + decipher the text, those characters must be included in the text to + be deciphered. In the following, the key has a dimension of 4 but + the text is 2 short of being a multiple of 4 so two characters will + be added. + + >>> key = Matrix([[1, 1, 1, 2], [0, 1, 1, 0], + ... [2, 2, 3, 4], [1, 1, 0, 1]]) + >>> msg = "ST" + >>> encipher_hill(msg, key) + 'HJEB' + >>> decipher_hill(_, key) + 'STQQ' + >>> encipher_hill(msg, key, pad="Z") + 'ISPK' + >>> decipher_hill(_, key) + 'STZZ' + + If the last two characters of the ciphertext were ignored in + either case, the wrong plaintext would be recovered: + + >>> decipher_hill("HD", key) + 'ORMV' + >>> decipher_hill("IS", key) + 'UIKY' + + See Also + ======== + + encipher_hill + + """ + assert key.is_square + msg, _, A = _prep(msg, '', symbols) + map = {c: i for i, c in enumerate(A)} + C = [map[c] for c in msg] + N = len(A) + k = key.cols + n = len(C) + m, r = divmod(n, k) + if r: + C = C + [0]*(k - r) + m += 1 + key_inv = key.inv_mod(N) + rv = ''.join([A[p % N] for j in range(m) for p in + list(key_inv*Matrix( + k, 1, [C[i] for i in range(k*j, k*(j + 1))]))]) + return rv + + +#################### Bifid cipher ######################## + + +def encipher_bifid(msg, key, symbols=None): + r""" + Performs the Bifid cipher encryption on plaintext ``msg``, and + returns the ciphertext. + + This is the version of the Bifid cipher that uses an `n \times n` + Polybius square. + + Parameters + ========== + + msg + Plaintext string. + + key + Short string for key. + + Duplicate characters are ignored and then it is padded with the + characters in ``symbols`` that were not in the short key. + + symbols + `n \times n` characters defining the alphabet. + + (default is string.printable) + + Returns + ======= + + ciphertext + Ciphertext using Bifid5 cipher without spaces. + + See Also + ======== + + decipher_bifid, encipher_bifid5, encipher_bifid6 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bifid_cipher + + """ + msg, key, A = _prep(msg, key, symbols, bifid10) + long_key = ''.join(uniq(key)) or A + + n = len(A)**.5 + if n != int(n): + raise ValueError( + 'Length of alphabet (%s) is not a square number.' % len(A)) + N = int(n) + if len(long_key) < N**2: + long_key = list(long_key) + [x for x in A if x not in long_key] + + # the fractionalization + row_col = {ch: divmod(i, N) for i, ch in enumerate(long_key)} + r, c = zip(*[row_col[x] for x in msg]) + rc = r + c + ch = {i: ch for ch, i in row_col.items()} + rv = ''.join(ch[i] for i in zip(rc[::2], rc[1::2])) + return rv + + +def decipher_bifid(msg, key, symbols=None): + r""" + Performs the Bifid cipher decryption on ciphertext ``msg``, and + returns the plaintext. + + This is the version of the Bifid cipher that uses the `n \times n` + Polybius square. + + Parameters + ========== + + msg + Ciphertext string. + + key + Short string for key. + + Duplicate characters are ignored and then it is padded with the + characters in symbols that were not in the short key. + + symbols + `n \times n` characters defining the alphabet. + + (default=string.printable, a `10 \times 10` matrix) + + Returns + ======= + + deciphered + Deciphered text. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... encipher_bifid, decipher_bifid, AZ) + + Do an encryption using the bifid5 alphabet: + + >>> alp = AZ().replace('J', '') + >>> ct = AZ("meet me on monday!") + >>> key = AZ("gold bug") + >>> encipher_bifid(ct, key, alp) + 'IEILHHFSTSFQYE' + + When entering the text or ciphertext, spaces are ignored so it + can be formatted as desired. Re-entering the ciphertext from the + preceding, putting 4 characters per line and padding with an extra + J, does not cause problems for the deciphering: + + >>> decipher_bifid(''' + ... IEILH + ... HFSTS + ... FQYEJ''', key, alp) + 'MEETMEONMONDAY' + + When no alphabet is given, all 100 printable characters will be + used: + + >>> key = '' + >>> encipher_bifid('hello world!', key) + 'bmtwmg-bIo*w' + >>> decipher_bifid(_, key) + 'hello world!' + + If the key is changed, a different encryption is obtained: + + >>> key = 'gold bug' + >>> encipher_bifid('hello world!', 'gold_bug') + 'hg2sfuei7t}w' + + And if the key used to decrypt the message is not exact, the + original text will not be perfectly obtained: + + >>> decipher_bifid(_, 'gold pug') + 'heldo~wor6d!' + + """ + msg, _, A = _prep(msg, '', symbols, bifid10) + long_key = ''.join(uniq(key)) or A + + n = len(A)**.5 + if n != int(n): + raise ValueError( + 'Length of alphabet (%s) is not a square number.' % len(A)) + N = int(n) + if len(long_key) < N**2: + long_key = list(long_key) + [x for x in A if x not in long_key] + + # the reverse fractionalization + row_col = { + ch: divmod(i, N) for i, ch in enumerate(long_key)} + rc = [i for c in msg for i in row_col[c]] + n = len(msg) + rc = zip(*(rc[:n], rc[n:])) + ch = {i: ch for ch, i in row_col.items()} + rv = ''.join(ch[i] for i in rc) + return rv + + +def bifid_square(key): + """Return characters of ``key`` arranged in a square. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... bifid_square, AZ, padded_key, bifid5) + >>> bifid_square(AZ().replace('J', '')) + Matrix([ + [A, B, C, D, E], + [F, G, H, I, K], + [L, M, N, O, P], + [Q, R, S, T, U], + [V, W, X, Y, Z]]) + + >>> bifid_square(padded_key(AZ('gold bug!'), bifid5)) + Matrix([ + [G, O, L, D, B], + [U, A, C, E, F], + [H, I, K, M, N], + [P, Q, R, S, T], + [V, W, X, Y, Z]]) + + See Also + ======== + + padded_key + + """ + A = ''.join(uniq(''.join(key))) + n = len(A)**.5 + if n != int(n): + raise ValueError( + 'Length of alphabet (%s) is not a square number.' % len(A)) + n = int(n) + f = lambda i, j: Symbol(A[n*i + j]) + rv = Matrix(n, n, f) + return rv + + +def encipher_bifid5(msg, key): + r""" + Performs the Bifid cipher encryption on plaintext ``msg``, and + returns the ciphertext. + + Explanation + =========== + + This is the version of the Bifid cipher that uses the `5 \times 5` + Polybius square. The letter "J" is ignored so it must be replaced + with something else (traditionally an "I") before encryption. + + ALGORITHM: (5x5 case) + + STEPS: + 0. Create the `5 \times 5` Polybius square ``S`` associated + to ``key`` as follows: + + a) moving from left-to-right, top-to-bottom, + place the letters of the key into a `5 \times 5` + matrix, + b) if the key has less than 25 letters, add the + letters of the alphabet not in the key until the + `5 \times 5` square is filled. + + 1. Create a list ``P`` of pairs of numbers which are the + coordinates in the Polybius square of the letters in + ``msg``. + 2. Let ``L1`` be the list of all first coordinates of ``P`` + (length of ``L1 = n``), let ``L2`` be the list of all + second coordinates of ``P`` (so the length of ``L2`` + is also ``n``). + 3. Let ``L`` be the concatenation of ``L1`` and ``L2`` + (length ``L = 2*n``), except that consecutive numbers + are paired ``(L[2*i], L[2*i + 1])``. You can regard + ``L`` as a list of pairs of length ``n``. + 4. Let ``C`` be the list of all letters which are of the + form ``S[i, j]``, for all ``(i, j)`` in ``L``. As a + string, this is the ciphertext of ``msg``. + + Parameters + ========== + + msg : str + Plaintext string. + + Converted to upper case and filtered of anything but all letters + except J. + + key + Short string for key; non-alphabetic letters, J and duplicated + characters are ignored and then, if the length is less than 25 + characters, it is padded with other letters of the alphabet + (in alphabetical order). + + Returns + ======= + + ct + Ciphertext (all caps, no spaces). + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... encipher_bifid5, decipher_bifid5) + + "J" will be omitted unless it is replaced with something else: + + >>> round_trip = lambda m, k: \ + ... decipher_bifid5(encipher_bifid5(m, k), k) + >>> key = 'a' + >>> msg = "JOSIE" + >>> round_trip(msg, key) + 'OSIE' + >>> round_trip(msg.replace("J", "I"), key) + 'IOSIE' + >>> j = "QIQ" + >>> round_trip(msg.replace("J", j), key).replace(j, "J") + 'JOSIE' + + + Notes + ===== + + The Bifid cipher was invented around 1901 by Felix Delastelle. + It is a *fractional substitution* cipher, where letters are + replaced by pairs of symbols from a smaller alphabet. The + cipher uses a `5 \times 5` square filled with some ordering of the + alphabet, except that "J" is replaced with "I" (this is a so-called + Polybius square; there is a `6 \times 6` analog if you add back in + "J" and also append onto the usual 26 letter alphabet, the digits + 0, 1, ..., 9). + According to Helen Gaines' book *Cryptanalysis*, this type of cipher + was used in the field by the German Army during World War I. + + See Also + ======== + + decipher_bifid5, encipher_bifid + + """ + msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5) + key = padded_key(key, bifid5) + return encipher_bifid(msg, '', key) + + +def decipher_bifid5(msg, key): + r""" + Return the Bifid cipher decryption of ``msg``. + + Explanation + =========== + + This is the version of the Bifid cipher that uses the `5 \times 5` + Polybius square; the letter "J" is ignored unless a ``key`` of + length 25 is used. + + Parameters + ========== + + msg + Ciphertext string. + + key + Short string for key; duplicated characters are ignored and if + the length is less then 25 characters, it will be padded with + other letters from the alphabet omitting "J". + Non-alphabetic characters are ignored. + + Returns + ======= + + plaintext + Plaintext from Bifid5 cipher (all caps, no spaces). + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_bifid5, decipher_bifid5 + >>> key = "gold bug" + >>> encipher_bifid5('meet me on friday', key) + 'IEILEHFSTSFXEE' + >>> encipher_bifid5('meet me on monday', key) + 'IEILHHFSTSFQYE' + >>> decipher_bifid5(_, key) + 'MEETMEONMONDAY' + + """ + msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5) + key = padded_key(key, bifid5) + return decipher_bifid(msg, '', key) + + +def bifid5_square(key=None): + r""" + 5x5 Polybius square. + + Produce the Polybius square for the `5 \times 5` Bifid cipher. + + Examples + ======== + + >>> from sympy.crypto.crypto import bifid5_square + >>> bifid5_square("gold bug") + Matrix([ + [G, O, L, D, B], + [U, A, C, E, F], + [H, I, K, M, N], + [P, Q, R, S, T], + [V, W, X, Y, Z]]) + + """ + if not key: + key = bifid5 + else: + _, key, _ = _prep('', key.upper(), None, bifid5) + key = padded_key(key, bifid5) + return bifid_square(key) + + +def encipher_bifid6(msg, key): + r""" + Performs the Bifid cipher encryption on plaintext ``msg``, and + returns the ciphertext. + + This is the version of the Bifid cipher that uses the `6 \times 6` + Polybius square. + + Parameters + ========== + + msg + Plaintext string (digits okay). + + key + Short string for key (digits okay). + + If ``key`` is less than 36 characters long, the square will be + filled with letters A through Z and digits 0 through 9. + + Returns + ======= + + ciphertext + Ciphertext from Bifid cipher (all caps, no spaces). + + See Also + ======== + + decipher_bifid6, encipher_bifid + + """ + msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6) + key = padded_key(key, bifid6) + return encipher_bifid(msg, '', key) + + +def decipher_bifid6(msg, key): + r""" + Performs the Bifid cipher decryption on ciphertext ``msg``, and + returns the plaintext. + + This is the version of the Bifid cipher that uses the `6 \times 6` + Polybius square. + + Parameters + ========== + + msg + Ciphertext string (digits okay); converted to upper case + + key + Short string for key (digits okay). + + If ``key`` is less than 36 characters long, the square will be + filled with letters A through Z and digits 0 through 9. + All letters are converted to uppercase. + + Returns + ======= + + plaintext + Plaintext from Bifid cipher (all caps, no spaces). + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_bifid6, decipher_bifid6 + >>> key = "gold bug" + >>> encipher_bifid6('meet me on monday at 8am', key) + 'KFKLJJHF5MMMKTFRGPL' + >>> decipher_bifid6(_, key) + 'MEETMEONMONDAYAT8AM' + + """ + msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6) + key = padded_key(key, bifid6) + return decipher_bifid(msg, '', key) + + +def bifid6_square(key=None): + r""" + 6x6 Polybius square. + + Produces the Polybius square for the `6 \times 6` Bifid cipher. + Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "9". + + Examples + ======== + + >>> from sympy.crypto.crypto import bifid6_square + >>> key = "gold bug" + >>> bifid6_square(key) + Matrix([ + [G, O, L, D, B, U], + [A, C, E, F, H, I], + [J, K, M, N, P, Q], + [R, S, T, V, W, X], + [Y, Z, 0, 1, 2, 3], + [4, 5, 6, 7, 8, 9]]) + + """ + if not key: + key = bifid6 + else: + _, key, _ = _prep('', key.upper(), None, bifid6) + key = padded_key(key, bifid6) + return bifid_square(key) + + +#################### RSA ############################# + +def _decipher_rsa_crt(i, d, factors): + """Decipher RSA using chinese remainder theorem from the information + of the relatively-prime factors of the modulus. + + Parameters + ========== + + i : integer + Ciphertext + + d : integer + The exponent component. + + factors : list of relatively-prime integers + The integers given must be coprime and the product must equal + the modulus component of the original RSA key. + + Examples + ======== + + How to decrypt RSA with CRT: + + >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key + >>> primes = [61, 53] + >>> e = 17 + >>> args = primes + [e] + >>> puk = rsa_public_key(*args) + >>> prk = rsa_private_key(*args) + + >>> from sympy.crypto.crypto import encipher_rsa, _decipher_rsa_crt + >>> msg = 65 + >>> crt_primes = primes + >>> encrypted = encipher_rsa(msg, puk) + >>> decrypted = _decipher_rsa_crt(encrypted, prk[1], primes) + >>> decrypted + 65 + """ + moduluses = [pow(i, d, p) for p in factors] + + result = crt(factors, moduluses) + if not result: + raise ValueError("CRT failed") + return result[0] + + +def _rsa_key(*args, public=True, private=True, totient='Euler', index=None, multipower=None): + r"""A private subroutine to generate RSA key + + Parameters + ========== + + public, private : bool, optional + Flag to generate either a public key, a private key. + + totient : 'Euler' or 'Carmichael' + Different notation used for totient. + + multipower : bool, optional + Flag to bypass warning for multipower RSA. + """ + + if len(args) < 2: + return False + + if totient not in ('Euler', 'Carmichael'): + raise ValueError( + "The argument totient={} should either be " \ + "'Euler', 'Carmichalel'." \ + .format(totient)) + + if totient == 'Euler': + _totient = _euler + else: + _totient = _carmichael + + if index is not None: + index = as_int(index) + if totient != 'Carmichael': + raise ValueError( + "Setting the 'index' keyword argument requires totient" + "notation to be specified as 'Carmichael'.") + + primes, e = args[:-1], args[-1] + + if not all(isprime(p) for p in primes): + new_primes = [] + for i in primes: + new_primes.extend(factorint(i, multiple=True)) + primes = new_primes + + n = reduce(lambda i, j: i*j, primes) + + tally = multiset(primes) + if all(v == 1 for v in tally.values()): + phi = int(_totient(tally)) + + else: + if not multipower: + NonInvertibleCipherWarning( + 'Non-distinctive primes found in the factors {}. ' + 'The cipher may not be decryptable for some numbers ' + 'in the complete residue system Z[{}], but the cipher ' + 'can still be valid if you restrict the domain to be ' + 'the reduced residue system Z*[{}]. You can pass ' + 'the flag multipower=True if you want to suppress this ' + 'warning.' + .format(primes, n, n) + # stacklevel=4 because most users will call a function that + # calls this function + ).warn(stacklevel=4) + phi = int(_totient(tally)) + + if gcd(e, phi) == 1: + if public and not private: + if isinstance(index, int): + e = e % phi + e += index * phi + return n, e + + if private and not public: + d = invert(e, phi) + if isinstance(index, int): + d += index * phi + return n, d + + return False + + +def rsa_public_key(*args, **kwargs): + r"""Return the RSA *public key* pair, `(n, e)` + + Parameters + ========== + + args : naturals + If specified as `p, q, e` where `p` and `q` are distinct primes + and `e` is a desired public exponent of the RSA, `n = p q` and + `e` will be verified against the totient + `\phi(n)` (Euler totient) or `\lambda(n)` (Carmichael totient) + to be `\gcd(e, \phi(n)) = 1` or `\gcd(e, \lambda(n)) = 1`. + + If specified as `p_1, p_2, \dots, p_n, e` where + `p_1, p_2, \dots, p_n` are specified as primes, + and `e` is specified as a desired public exponent of the RSA, + it will be able to form a multi-prime RSA, which is a more + generalized form of the popular 2-prime RSA. + + It can also be possible to form a single-prime RSA by specifying + the argument as `p, e`, which can be considered a trivial case + of a multiprime RSA. + + Furthermore, it can be possible to form a multi-power RSA by + specifying two or more pairs of the primes to be same. + However, unlike the two-distinct prime RSA or multi-prime + RSA, not every numbers in the complete residue system + (`\mathbb{Z}_n`) will be decryptable since the mapping + `\mathbb{Z}_{n} \rightarrow \mathbb{Z}_{n}` + will not be bijective. + (Only except for the trivial case when + `e = 1` + or more generally, + + .. math:: + e \in \left \{ 1 + k \lambda(n) + \mid k \in \mathbb{Z} \land k \geq 0 \right \} + + when RSA reduces to the identity.) + However, the RSA can still be decryptable for the numbers in the + reduced residue system (`\mathbb{Z}_n^{\times}`), since the + mapping + `\mathbb{Z}_{n}^{\times} \rightarrow \mathbb{Z}_{n}^{\times}` + can still be bijective. + + If you pass a non-prime integer to the arguments + `p_1, p_2, \dots, p_n`, the particular number will be + prime-factored and it will become either a multi-prime RSA or a + multi-power RSA in its canonical form, depending on whether the + product equals its radical or not. + `p_1 p_2 \dots p_n = \text{rad}(p_1 p_2 \dots p_n)` + + totient : bool, optional + If ``'Euler'``, it uses Euler's totient `\phi(n)` which is + :meth:`sympy.functions.combinatorial.numbers.totient` in SymPy. + + If ``'Carmichael'``, it uses Carmichael's totient `\lambda(n)` + which is :meth:`sympy.functions.combinatorial.numbers.reduced_totient` in SymPy. + + Unlike private key generation, this is a trivial keyword for + public key generation because + `\gcd(e, \phi(n)) = 1 \iff \gcd(e, \lambda(n)) = 1`. + + index : nonnegative integer, optional + Returns an arbitrary solution of a RSA public key at the index + specified at `0, 1, 2, \dots`. This parameter needs to be + specified along with ``totient='Carmichael'``. + + Similarly to the non-uniquenss of a RSA private key as described + in the ``index`` parameter documentation in + :meth:`rsa_private_key`, RSA public key is also not unique and + there is an infinite number of RSA public exponents which + can behave in the same manner. + + From any given RSA public exponent `e`, there are can be an + another RSA public exponent `e + k \lambda(n)` where `k` is an + integer, `\lambda` is a Carmichael's totient function. + + However, considering only the positive cases, there can be + a principal solution of a RSA public exponent `e_0` in + `0 < e_0 < \lambda(n)`, and all the other solutions + can be canonicalzed in a form of `e_0 + k \lambda(n)`. + + ``index`` specifies the `k` notation to yield any possible value + an RSA public key can have. + + An example of computing any arbitrary RSA public key: + + >>> from sympy.crypto.crypto import rsa_public_key + >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=0) + (3233, 17) + >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=1) + (3233, 797) + >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=2) + (3233, 1577) + + multipower : bool, optional + Any pair of non-distinct primes found in the RSA specification + will restrict the domain of the cryptosystem, as noted in the + explanation of the parameter ``args``. + + SymPy RSA key generator may give a warning before dispatching it + as a multi-power RSA, however, you can disable the warning if + you pass ``True`` to this keyword. + + Returns + ======= + + (n, e) : int, int + `n` is a product of any arbitrary number of primes given as + the argument. + + `e` is relatively prime (coprime) to the Euler totient + `\phi(n)`. + + False + Returned if less than two arguments are given, or `e` is + not relatively prime to the modulus. + + Examples + ======== + + >>> from sympy.crypto.crypto import rsa_public_key + + A public key of a two-prime RSA: + + >>> p, q, e = 3, 5, 7 + >>> rsa_public_key(p, q, e) + (15, 7) + >>> rsa_public_key(p, q, 30) + False + + A public key of a multiprime RSA: + + >>> primes = [2, 3, 5, 7, 11, 13] + >>> e = 7 + >>> args = primes + [e] + >>> rsa_public_key(*args) + (30030, 7) + + Notes + ===== + + Although the RSA can be generalized over any modulus `n`, using + two large primes had became the most popular specification because a + product of two large primes is usually the hardest to factor + relatively to the digits of `n` can have. + + However, it may need further understanding of the time complexities + of each prime-factoring algorithms to verify the claim. + + See Also + ======== + + rsa_private_key + encipher_rsa + decipher_rsa + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29 + + .. [2] https://cacr.uwaterloo.ca/techreports/2006/cacr2006-16.pdf + + .. [3] https://link.springer.com/content/pdf/10.1007/BFb0055738.pdf + + .. [4] https://www.itiis.org/digital-library/manuscript/1381 + """ + return _rsa_key(*args, public=True, private=False, **kwargs) + + +def rsa_private_key(*args, **kwargs): + r"""Return the RSA *private key* pair, `(n, d)` + + Parameters + ========== + + args : naturals + The keyword is identical to the ``args`` in + :meth:`rsa_public_key`. + + totient : bool, optional + If ``'Euler'``, it uses Euler's totient convention `\phi(n)` + which is :meth:`sympy.functions.combinatorial.numbers.totient` in SymPy. + + If ``'Carmichael'``, it uses Carmichael's totient convention + `\lambda(n)` which is + :meth:`sympy.functions.combinatorial.numbers.reduced_totient` in SymPy. + + There can be some output differences for private key generation + as examples below. + + Example using Euler's totient: + + >>> from sympy.crypto.crypto import rsa_private_key + >>> rsa_private_key(61, 53, 17, totient='Euler') + (3233, 2753) + + Example using Carmichael's totient: + + >>> from sympy.crypto.crypto import rsa_private_key + >>> rsa_private_key(61, 53, 17, totient='Carmichael') + (3233, 413) + + index : nonnegative integer, optional + Returns an arbitrary solution of a RSA private key at the index + specified at `0, 1, 2, \dots`. This parameter needs to be + specified along with ``totient='Carmichael'``. + + RSA private exponent is a non-unique solution of + `e d \mod \lambda(n) = 1` and it is possible in any form of + `d + k \lambda(n)`, where `d` is an another + already-computed private exponent, and `\lambda` is a + Carmichael's totient function, and `k` is any integer. + + However, considering only the positive cases, there can be + a principal solution of a RSA private exponent `d_0` in + `0 < d_0 < \lambda(n)`, and all the other solutions + can be canonicalzed in a form of `d_0 + k \lambda(n)`. + + ``index`` specifies the `k` notation to yield any possible value + an RSA private key can have. + + An example of computing any arbitrary RSA private key: + + >>> from sympy.crypto.crypto import rsa_private_key + >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=0) + (3233, 413) + >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=1) + (3233, 1193) + >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=2) + (3233, 1973) + + multipower : bool, optional + The keyword is identical to the ``multipower`` in + :meth:`rsa_public_key`. + + Returns + ======= + + (n, d) : int, int + `n` is a product of any arbitrary number of primes given as + the argument. + + `d` is the inverse of `e` (mod `\phi(n)`) where `e` is the + exponent given, and `\phi` is a Euler totient. + + False + Returned if less than two arguments are given, or `e` is + not relatively prime to the totient of the modulus. + + Examples + ======== + + >>> from sympy.crypto.crypto import rsa_private_key + + A private key of a two-prime RSA: + + >>> p, q, e = 3, 5, 7 + >>> rsa_private_key(p, q, e) + (15, 7) + >>> rsa_private_key(p, q, 30) + False + + A private key of a multiprime RSA: + + >>> primes = [2, 3, 5, 7, 11, 13] + >>> e = 7 + >>> args = primes + [e] + >>> rsa_private_key(*args) + (30030, 823) + + See Also + ======== + + rsa_public_key + encipher_rsa + decipher_rsa + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29 + + .. [2] https://cacr.uwaterloo.ca/techreports/2006/cacr2006-16.pdf + + .. [3] https://link.springer.com/content/pdf/10.1007/BFb0055738.pdf + + .. [4] https://www.itiis.org/digital-library/manuscript/1381 + """ + return _rsa_key(*args, public=False, private=True, **kwargs) + + +def _encipher_decipher_rsa(i, key, factors=None): + n, d = key + if not factors: + return pow(i, d, n) + + def _is_coprime_set(l): + is_coprime_set = True + for i in range(len(l)): + for j in range(i+1, len(l)): + if gcd(l[i], l[j]) != 1: + is_coprime_set = False + break + return is_coprime_set + + prod = reduce(lambda i, j: i*j, factors) + if prod == n and _is_coprime_set(factors): + return _decipher_rsa_crt(i, d, factors) + return _encipher_decipher_rsa(i, key, factors=None) + + +def encipher_rsa(i, key, factors=None): + r"""Encrypt the plaintext with RSA. + + Parameters + ========== + + i : integer + The plaintext to be encrypted for. + + key : (n, e) where n, e are integers + `n` is the modulus of the key and `e` is the exponent of the + key. The encryption is computed by `i^e \bmod n`. + + The key can either be a public key or a private key, however, + the message encrypted by a public key can only be decrypted by + a private key, and vice versa, as RSA is an asymmetric + cryptography system. + + factors : list of coprime integers + This is identical to the keyword ``factors`` in + :meth:`decipher_rsa`. + + Notes + ===== + + Some specifications may make the RSA not cryptographically + meaningful. + + For example, `0`, `1` will remain always same after taking any + number of exponentiation, thus, should be avoided. + + Furthermore, if `i^e < n`, `i` may easily be figured out by taking + `e` th root. + + And also, specifying the exponent as `1` or in more generalized form + as `1 + k \lambda(n)` where `k` is an nonnegative integer, + `\lambda` is a carmichael totient, the RSA becomes an identity + mapping. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_rsa + >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key + + Public Key Encryption: + + >>> p, q, e = 3, 5, 7 + >>> puk = rsa_public_key(p, q, e) + >>> msg = 12 + >>> encipher_rsa(msg, puk) + 3 + + Private Key Encryption: + + >>> p, q, e = 3, 5, 7 + >>> prk = rsa_private_key(p, q, e) + >>> msg = 12 + >>> encipher_rsa(msg, prk) + 3 + + Encryption using chinese remainder theorem: + + >>> encipher_rsa(msg, prk, factors=[p, q]) + 3 + """ + return _encipher_decipher_rsa(i, key, factors=factors) + + +def decipher_rsa(i, key, factors=None): + r"""Decrypt the ciphertext with RSA. + + Parameters + ========== + + i : integer + The ciphertext to be decrypted for. + + key : (n, d) where n, d are integers + `n` is the modulus of the key and `d` is the exponent of the + key. The decryption is computed by `i^d \bmod n`. + + The key can either be a public key or a private key, however, + the message encrypted by a public key can only be decrypted by + a private key, and vice versa, as RSA is an asymmetric + cryptography system. + + factors : list of coprime integers + As the modulus `n` created from RSA key generation is composed + of arbitrary prime factors + `n = {p_1}^{k_1}{p_2}^{k_2}\dots{p_n}^{k_n}` where + `p_1, p_2, \dots, p_n` are distinct primes and + `k_1, k_2, \dots, k_n` are positive integers, chinese remainder + theorem can be used to compute `i^d \bmod n` from the + fragmented modulo operations like + + .. math:: + i^d \bmod {p_1}^{k_1}, i^d \bmod {p_2}^{k_2}, \dots, + i^d \bmod {p_n}^{k_n} + + or like + + .. math:: + i^d \bmod {p_1}^{k_1}{p_2}^{k_2}, + i^d \bmod {p_3}^{k_3}, \dots , + i^d \bmod {p_n}^{k_n} + + as long as every moduli does not share any common divisor each + other. + + The raw primes used in generating the RSA key pair can be a good + option. + + Note that the speed advantage of using this is only viable for + very large cases (Like 2048-bit RSA keys) since the + overhead of using pure Python implementation of + :meth:`sympy.ntheory.modular.crt` may overcompensate the + theoretical speed advantage. + + Notes + ===== + + See the ``Notes`` section in the documentation of + :meth:`encipher_rsa` + + Examples + ======== + + >>> from sympy.crypto.crypto import decipher_rsa, encipher_rsa + >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key + + Public Key Encryption and Decryption: + + >>> p, q, e = 3, 5, 7 + >>> prk = rsa_private_key(p, q, e) + >>> puk = rsa_public_key(p, q, e) + >>> msg = 12 + >>> new_msg = encipher_rsa(msg, prk) + >>> new_msg + 3 + >>> decipher_rsa(new_msg, puk) + 12 + + Private Key Encryption and Decryption: + + >>> p, q, e = 3, 5, 7 + >>> prk = rsa_private_key(p, q, e) + >>> puk = rsa_public_key(p, q, e) + >>> msg = 12 + >>> new_msg = encipher_rsa(msg, puk) + >>> new_msg + 3 + >>> decipher_rsa(new_msg, prk) + 12 + + Decryption using chinese remainder theorem: + + >>> decipher_rsa(new_msg, prk, factors=[p, q]) + 12 + + See Also + ======== + + encipher_rsa + """ + return _encipher_decipher_rsa(i, key, factors=factors) + + +#################### kid krypto (kid RSA) ############################# + + +def kid_rsa_public_key(a, b, A, B): + r""" + Kid RSA is a version of RSA useful to teach grade school children + since it does not involve exponentiation. + + Explanation + =========== + + Alice wants to talk to Bob. Bob generates keys as follows. + Key generation: + + * Select positive integers `a, b, A, B` at random. + * Compute `M = a b - 1`, `e = A M + a`, `d = B M + b`, + `n = (e d - 1)//M`. + * The *public key* is `(n, e)`. Bob sends these to Alice. + * The *private key* is `(n, d)`, which Bob keeps secret. + + Encryption: If `p` is the plaintext message then the + ciphertext is `c = p e \pmod n`. + + Decryption: If `c` is the ciphertext message then the + plaintext is `p = c d \pmod n`. + + Examples + ======== + + >>> from sympy.crypto.crypto import kid_rsa_public_key + >>> a, b, A, B = 3, 4, 5, 6 + >>> kid_rsa_public_key(a, b, A, B) + (369, 58) + + """ + M = a*b - 1 + e = A*M + a + d = B*M + b + n = (e*d - 1)//M + return n, e + + +def kid_rsa_private_key(a, b, A, B): + """ + Compute `M = a b - 1`, `e = A M + a`, `d = B M + b`, + `n = (e d - 1) / M`. The *private key* is `d`, which Bob + keeps secret. + + Examples + ======== + + >>> from sympy.crypto.crypto import kid_rsa_private_key + >>> a, b, A, B = 3, 4, 5, 6 + >>> kid_rsa_private_key(a, b, A, B) + (369, 70) + + """ + M = a*b - 1 + e = A*M + a + d = B*M + b + n = (e*d - 1)//M + return n, d + + +def encipher_kid_rsa(msg, key): + """ + Here ``msg`` is the plaintext and ``key`` is the public key. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... encipher_kid_rsa, kid_rsa_public_key) + >>> msg = 200 + >>> a, b, A, B = 3, 4, 5, 6 + >>> key = kid_rsa_public_key(a, b, A, B) + >>> encipher_kid_rsa(msg, key) + 161 + + """ + n, e = key + return (msg*e) % n + + +def decipher_kid_rsa(msg, key): + """ + Here ``msg`` is the plaintext and ``key`` is the private key. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... kid_rsa_public_key, kid_rsa_private_key, + ... decipher_kid_rsa, encipher_kid_rsa) + >>> a, b, A, B = 3, 4, 5, 6 + >>> d = kid_rsa_private_key(a, b, A, B) + >>> msg = 200 + >>> pub = kid_rsa_public_key(a, b, A, B) + >>> pri = kid_rsa_private_key(a, b, A, B) + >>> ct = encipher_kid_rsa(msg, pub) + >>> decipher_kid_rsa(ct, pri) + 200 + + """ + n, d = key + return (msg*d) % n + + +#################### Morse Code ###################################### + +morse_char = { + ".-": "A", "-...": "B", + "-.-.": "C", "-..": "D", + ".": "E", "..-.": "F", + "--.": "G", "....": "H", + "..": "I", ".---": "J", + "-.-": "K", ".-..": "L", + "--": "M", "-.": "N", + "---": "O", ".--.": "P", + "--.-": "Q", ".-.": "R", + "...": "S", "-": "T", + "..-": "U", "...-": "V", + ".--": "W", "-..-": "X", + "-.--": "Y", "--..": "Z", + "-----": "0", ".----": "1", + "..---": "2", "...--": "3", + "....-": "4", ".....": "5", + "-....": "6", "--...": "7", + "---..": "8", "----.": "9", + ".-.-.-": ".", "--..--": ",", + "---...": ":", "-.-.-.": ";", + "..--..": "?", "-....-": "-", + "..--.-": "_", "-.--.": "(", + "-.--.-": ")", ".----.": "'", + "-...-": "=", ".-.-.": "+", + "-..-.": "/", ".--.-.": "@", + "...-..-": "$", "-.-.--": "!"} +char_morse = {v: k for k, v in morse_char.items()} + + +def encode_morse(msg, sep='|', mapping=None): + """ + Encodes a plaintext into popular Morse Code with letters + separated by ``sep`` and words by a double ``sep``. + + Examples + ======== + + >>> from sympy.crypto.crypto import encode_morse + >>> msg = 'ATTACK RIGHT FLANK' + >>> encode_morse(msg) + '.-|-|-|.-|-.-.|-.-||.-.|..|--.|....|-||..-.|.-..|.-|-.|-.-' + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Morse_code + + """ + + mapping = mapping or char_morse + assert sep not in mapping + word_sep = 2*sep + mapping[" "] = word_sep + suffix = msg and msg[-1] in whitespace + + # normalize whitespace + msg = (' ' if word_sep else '').join(msg.split()) + # omit unmapped chars + chars = set(''.join(msg.split())) + ok = set(mapping.keys()) + msg = translate(msg, None, ''.join(chars - ok)) + + morsestring = [] + words = msg.split() + for word in words: + morseword = [] + for letter in word: + morseletter = mapping[letter] + morseword.append(morseletter) + + word = sep.join(morseword) + morsestring.append(word) + + return word_sep.join(morsestring) + (word_sep if suffix else '') + + +def decode_morse(msg, sep='|', mapping=None): + """ + Decodes a Morse Code with letters separated by ``sep`` + (default is '|') and words by `word_sep` (default is '||) + into plaintext. + + Examples + ======== + + >>> from sympy.crypto.crypto import decode_morse + >>> mc = '--|---|...-|.||.|.-|...|-' + >>> decode_morse(mc) + 'MOVE EAST' + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Morse_code + + """ + + mapping = mapping or morse_char + word_sep = 2*sep + characterstring = [] + words = msg.strip(word_sep).split(word_sep) + for word in words: + letters = word.split(sep) + chars = [mapping[c] for c in letters] + word = ''.join(chars) + characterstring.append(word) + rv = " ".join(characterstring) + return rv + + +#################### LFSRs ########################################## + + +@doctest_depends_on(ground_types=['python', 'gmpy']) +def lfsr_sequence(key, fill, n): + r""" + This function creates an LFSR sequence. + + Parameters + ========== + + key : list + A list of finite field elements, `[c_0, c_1, \ldots, c_k].` + + fill : list + The list of the initial terms of the LFSR sequence, + `[x_0, x_1, \ldots, x_k].` + + n + Number of terms of the sequence that the function returns. + + Returns + ======= + + L + The LFSR sequence defined by + `x_{n+1} = c_k x_n + \ldots + c_0 x_{n-k}`, for + `n \leq k`. + + Notes + ===== + + S. Golomb [G]_ gives a list of three statistical properties a + sequence of numbers `a = \{a_n\}_{n=1}^\infty`, + `a_n \in \{0,1\}`, should display to be considered + "random". Define the autocorrelation of `a` to be + + .. math:: + + C(k) = C(k,a) = \lim_{N\rightarrow \infty} {1\over N}\sum_{n=1}^N (-1)^{a_n + a_{n+k}}. + + In the case where `a` is periodic with period + `P` then this reduces to + + .. math:: + + C(k) = {1\over P}\sum_{n=1}^P (-1)^{a_n + a_{n+k}}. + + Assume `a` is periodic with period `P`. + + - balance: + + .. math:: + + \left|\sum_{n=1}^P(-1)^{a_n}\right| \leq 1. + + - low autocorrelation: + + .. math:: + + C(k) = \left\{ \begin{array}{cc} 1,& k = 0,\\ \epsilon, & k \ne 0. \end{array} \right. + + (For sequences satisfying these first two properties, it is known + that `\epsilon = -1/P` must hold.) + + - proportional runs property: In each period, half the runs have + length `1`, one-fourth have length `2`, etc. + Moreover, there are as many runs of `1`'s as there are of + `0`'s. + + Examples + ======== + + >>> from sympy.crypto.crypto import lfsr_sequence + >>> from sympy.polys.domains import FF + >>> F = FF(2) + >>> fill = [F(1), F(1), F(0), F(1)] + >>> key = [F(1), F(0), F(0), F(1)] + >>> lfsr_sequence(key, fill, 10) + [1 mod 2, 1 mod 2, 0 mod 2, 1 mod 2, 0 mod 2, + 1 mod 2, 1 mod 2, 0 mod 2, 0 mod 2, 1 mod 2] + + References + ========== + + .. [G] Solomon Golomb, Shift register sequences, Aegean Park Press, + Laguna Hills, Ca, 1967 + + """ + if not isinstance(key, list): + raise TypeError("key must be a list") + if not isinstance(fill, list): + raise TypeError("fill must be a list") + p = key[0].modulus() + F = FF(p) + s = fill + k = len(fill) + L = [] + for i in range(n): + s0 = s[:] + L.append(s[0]) + s = s[1:k] + x = sum(int(key[i]*s0[i]) for i in range(k)) + s.append(F(x)) + return L # use [int(x) for x in L] for int version + + +def lfsr_autocorrelation(L, P, k): + """ + This function computes the LFSR autocorrelation function. + + Parameters + ========== + + L + A periodic sequence of elements of `GF(2)`. + L must have length larger than P. + + P + The period of L. + + k : int + An integer `k` (`0 < k < P`). + + Returns + ======= + + autocorrelation + The k-th value of the autocorrelation of the LFSR L. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... lfsr_sequence, lfsr_autocorrelation) + >>> from sympy.polys.domains import FF + >>> F = FF(2) + >>> fill = [F(1), F(1), F(0), F(1)] + >>> key = [F(1), F(0), F(0), F(1)] + >>> s = lfsr_sequence(key, fill, 20) + >>> lfsr_autocorrelation(s, 15, 7) + -1/15 + >>> lfsr_autocorrelation(s, 15, 0) + 1 + + """ + if not isinstance(L, list): + raise TypeError("L (=%s) must be a list" % L) + P = int(P) + k = int(k) + L0 = L[:P] # slices makes a copy + L1 = L0 + L0[:k] + L2 = [(-1)**(int(L1[i]) + int(L1[i + k])) for i in range(P)] + tot = sum(L2) + return Rational(tot, P) + + +def lfsr_connection_polynomial(s): + """ + This function computes the LFSR connection polynomial. + + Parameters + ========== + + s + A sequence of elements of even length, with entries in a finite + field. + + Returns + ======= + + C(x) + The connection polynomial of a minimal LFSR yielding s. + + This implements the algorithm in section 3 of J. L. Massey's + article [M]_. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... lfsr_sequence, lfsr_connection_polynomial) + >>> from sympy.polys.domains import FF + >>> F = FF(2) + >>> fill = [F(1), F(1), F(0), F(1)] + >>> key = [F(1), F(0), F(0), F(1)] + >>> s = lfsr_sequence(key, fill, 20) + >>> lfsr_connection_polynomial(s) + x**4 + x + 1 + >>> fill = [F(1), F(0), F(0), F(1)] + >>> key = [F(1), F(1), F(0), F(1)] + >>> s = lfsr_sequence(key, fill, 20) + >>> lfsr_connection_polynomial(s) + x**3 + 1 + >>> fill = [F(1), F(0), F(1)] + >>> key = [F(1), F(1), F(0)] + >>> s = lfsr_sequence(key, fill, 20) + >>> lfsr_connection_polynomial(s) + x**3 + x**2 + 1 + >>> fill = [F(1), F(0), F(1)] + >>> key = [F(1), F(0), F(1)] + >>> s = lfsr_sequence(key, fill, 20) + >>> lfsr_connection_polynomial(s) + x**3 + x + 1 + + References + ========== + + .. [M] James L. Massey, "Shift-Register Synthesis and BCH Decoding." + IEEE Trans. on Information Theory, vol. 15(1), pp. 122-127, + Jan 1969. + + """ + # Initialization: + p = s[0].modulus() + x = Symbol("x") + C = 1*x**0 + B = 1*x**0 + m = 1 + b = 1*x**0 + L = 0 + N = 0 + while N < len(s): + if L > 0: + dC = Poly(C).degree() + r = min(L + 1, dC + 1) + coeffsC = [C.subs(x, 0)] + [C.coeff(x**i) + for i in range(1, dC + 1)] + d = (int(s[N]) + sum(coeffsC[i]*int(s[N - i]) + for i in range(1, r))) % p + if L == 0: + d = int(s[N])*x**0 + if d == 0: + m += 1 + N += 1 + if d > 0: + if 2*L > N: + C = (C - d*((b**(p - 2)) % p)*x**m*B).expand() + m += 1 + N += 1 + else: + T = C + C = (C - d*((b**(p - 2)) % p)*x**m*B).expand() + L = N + 1 - L + m = 1 + b = d + B = T + N += 1 + dC = Poly(C).degree() + coeffsC = [C.subs(x, 0)] + [C.coeff(x**i) for i in range(1, dC + 1)] + return sum(coeffsC[i] % p*x**i for i in range(dC + 1) + if coeffsC[i] is not None) + + +#################### ElGamal ############################# + + +def elgamal_private_key(digit=10, seed=None): + r""" + Return three number tuple as private key. + + Explanation + =========== + + Elgamal encryption is based on the mathematical problem + called the Discrete Logarithm Problem (DLP). For example, + + `a^{b} \equiv c \pmod p` + + In general, if ``a`` and ``b`` are known, ``ct`` is easily + calculated. If ``b`` is unknown, it is hard to use + ``a`` and ``ct`` to get ``b``. + + Parameters + ========== + + digit : int + Minimum number of binary digits for key. + + Returns + ======= + + tuple : (p, r, d) + p = prime number. + + r = primitive root. + + d = random number. + + Notes + ===== + + For testing purposes, the ``seed`` parameter may be set to control + the output of this routine. See sympy.core.random._randrange. + + Examples + ======== + + >>> from sympy.crypto.crypto import elgamal_private_key + >>> from sympy.ntheory import is_primitive_root, isprime + >>> a, b, _ = elgamal_private_key() + >>> isprime(a) + True + >>> is_primitive_root(b, a) + True + + """ + randrange = _randrange(seed) + p = nextprime(2**digit) + return p, primitive_root(p), randrange(2, p) + + +def elgamal_public_key(key): + r""" + Return three number tuple as public key. + + Parameters + ========== + + key : (p, r, e) + Tuple generated by ``elgamal_private_key``. + + Returns + ======= + + tuple : (p, r, e) + `e = r**d \bmod p` + + `d` is a random number in private key. + + Examples + ======== + + >>> from sympy.crypto.crypto import elgamal_public_key + >>> elgamal_public_key((1031, 14, 636)) + (1031, 14, 212) + + """ + p, r, e = key + return p, r, pow(r, e, p) + + +def encipher_elgamal(i, key, seed=None): + r""" + Encrypt message with public key. + + Explanation + =========== + + ``i`` is a plaintext message expressed as an integer. + ``key`` is public key (p, r, e). In order to encrypt + a message, a random number ``a`` in ``range(2, p)`` + is generated and the encrypted message is returned as + `c_{1}` and `c_{2}` where: + + `c_{1} \equiv r^{a} \pmod p` + + `c_{2} \equiv m e^{a} \pmod p` + + Parameters + ========== + + msg + int of encoded message. + + key + Public key. + + Returns + ======= + + tuple : (c1, c2) + Encipher into two number. + + Notes + ===== + + For testing purposes, the ``seed`` parameter may be set to control + the output of this routine. See sympy.core.random._randrange. + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_elgamal, elgamal_private_key, elgamal_public_key + >>> pri = elgamal_private_key(5, seed=[3]); pri + (37, 2, 3) + >>> pub = elgamal_public_key(pri); pub + (37, 2, 8) + >>> msg = 36 + >>> encipher_elgamal(msg, pub, seed=[3]) + (8, 6) + + """ + p, r, e = key + if i < 0 or i >= p: + raise ValueError( + 'Message (%s) should be in range(%s)' % (i, p)) + randrange = _randrange(seed) + a = randrange(2, p) + return pow(r, a, p), i*pow(e, a, p) % p + + +def decipher_elgamal(msg, key): + r""" + Decrypt message with private key. + + `msg = (c_{1}, c_{2})` + + `key = (p, r, d)` + + According to extended Eucliden theorem, + `u c_{1}^{d} + p n = 1` + + `u \equiv 1/{{c_{1}}^d} \pmod p` + + `u c_{2} \equiv \frac{1}{c_{1}^d} c_{2} \equiv \frac{1}{r^{ad}} c_{2} \pmod p` + + `\frac{1}{r^{ad}} m e^a \equiv \frac{1}{r^{ad}} m {r^{d a}} \equiv m \pmod p` + + Examples + ======== + + >>> from sympy.crypto.crypto import decipher_elgamal + >>> from sympy.crypto.crypto import encipher_elgamal + >>> from sympy.crypto.crypto import elgamal_private_key + >>> from sympy.crypto.crypto import elgamal_public_key + + >>> pri = elgamal_private_key(5, seed=[3]) + >>> pub = elgamal_public_key(pri); pub + (37, 2, 8) + >>> msg = 17 + >>> decipher_elgamal(encipher_elgamal(msg, pub), pri) == msg + True + + """ + p, _, d = key + c1, c2 = msg + u = pow(c1, -d, p) + return u * c2 % p + + +################ Diffie-Hellman Key Exchange ######################### + +def dh_private_key(digit=10, seed=None): + r""" + Return three integer tuple as private key. + + Explanation + =========== + + Diffie-Hellman key exchange is based on the mathematical problem + called the Discrete Logarithm Problem (see ElGamal). + + Diffie-Hellman key exchange is divided into the following steps: + + * Alice and Bob agree on a base that consist of a prime ``p`` + and a primitive root of ``p`` called ``g`` + * Alice choses a number ``a`` and Bob choses a number ``b`` where + ``a`` and ``b`` are random numbers in range `[2, p)`. These are + their private keys. + * Alice then publicly sends Bob `g^{a} \pmod p` while Bob sends + Alice `g^{b} \pmod p` + * They both raise the received value to their secretly chosen + number (``a`` or ``b``) and now have both as their shared key + `g^{ab} \pmod p` + + Parameters + ========== + + digit + Minimum number of binary digits required in key. + + Returns + ======= + + tuple : (p, g, a) + p = prime number. + + g = primitive root of p. + + a = random number from 2 through p - 1. + + Notes + ===== + + For testing purposes, the ``seed`` parameter may be set to control + the output of this routine. See sympy.core.random._randrange. + + Examples + ======== + + >>> from sympy.crypto.crypto import dh_private_key + >>> from sympy.ntheory import isprime, is_primitive_root + >>> p, g, _ = dh_private_key() + >>> isprime(p) + True + >>> is_primitive_root(g, p) + True + >>> p, g, _ = dh_private_key(5) + >>> isprime(p) + True + >>> is_primitive_root(g, p) + True + + """ + p = nextprime(2**digit) + g = primitive_root(p) + randrange = _randrange(seed) + a = randrange(2, p) + return p, g, a + + +def dh_public_key(key): + r""" + Return three number tuple as public key. + + This is the tuple that Alice sends to Bob. + + Parameters + ========== + + key : (p, g, a) + A tuple generated by ``dh_private_key``. + + Returns + ======= + + tuple : int, int, int + A tuple of `(p, g, g^a \mod p)` with `p`, `g` and `a` given as + parameters.s + + Examples + ======== + + >>> from sympy.crypto.crypto import dh_private_key, dh_public_key + >>> p, g, a = dh_private_key(); + >>> _p, _g, x = dh_public_key((p, g, a)) + >>> p == _p and g == _g + True + >>> x == pow(g, a, p) + True + + """ + p, g, a = key + return p, g, pow(g, a, p) + + +def dh_shared_key(key, b): + """ + Return an integer that is the shared key. + + This is what Bob and Alice can both calculate using the public + keys they received from each other and their private keys. + + Parameters + ========== + + key : (p, g, x) + Tuple `(p, g, x)` generated by ``dh_public_key``. + + b + Random number in the range of `2` to `p - 1` + (Chosen by second key exchange member (Bob)). + + Returns + ======= + + int + A shared key. + + Examples + ======== + + >>> from sympy.crypto.crypto import ( + ... dh_private_key, dh_public_key, dh_shared_key) + >>> prk = dh_private_key(); + >>> p, g, x = dh_public_key(prk); + >>> sk = dh_shared_key((p, g, x), 1000) + >>> sk == pow(x, 1000, p) + True + + """ + p, _, x = key + if 1 >= b or b >= p: + raise ValueError(filldedent(''' + Value of b should be greater 1 and less + than prime %s.''' % p)) + + return pow(x, b, p) + + +################ Goldwasser-Micali Encryption ######################### + + +def _legendre(a, p): + """ + Returns the legendre symbol of a and p + assuming that p is a prime. + + i.e. 1 if a is a quadratic residue mod p + -1 if a is not a quadratic residue mod p + 0 if a is divisible by p + + Parameters + ========== + + a : int + The number to test. + + p : prime + The prime to test ``a`` against. + + Returns + ======= + + int + Legendre symbol (a / p). + + """ + sig = pow(a, (p - 1)//2, p) + if sig == 1: + return 1 + elif sig == 0: + return 0 + else: + return -1 + + +def _random_coprime_stream(n, seed=None): + randrange = _randrange(seed) + while True: + y = randrange(n) + if gcd(y, n) == 1: + yield y + + +def gm_private_key(p, q, a=None): + r""" + Check if ``p`` and ``q`` can be used as private keys for + the Goldwasser-Micali encryption. The method works + roughly as follows. + + Explanation + =========== + + #. Pick two large primes $p$ and $q$. + #. Call their product $N$. + #. Given a message as an integer $i$, write $i$ in its bit representation $b_0, \dots, b_n$. + #. For each $k$, + + if $b_k = 0$: + let $a_k$ be a random square + (quadratic residue) modulo $p q$ + such that ``jacobi_symbol(a, p*q) = 1`` + if $b_k = 1$: + let $a_k$ be a random non-square + (non-quadratic residue) modulo $p q$ + such that ``jacobi_symbol(a, p*q) = 1`` + + returns $\left[a_1, a_2, \dots\right]$ + + $b_k$ can be recovered by checking whether or not + $a_k$ is a residue. And from the $b_k$'s, the message + can be reconstructed. + + The idea is that, while ``jacobi_symbol(a, p*q)`` + can be easily computed (and when it is equal to $-1$ will + tell you that $a$ is not a square mod $p q$), quadratic + residuosity modulo a composite number is hard to compute + without knowing its factorization. + + Moreover, approximately half the numbers coprime to $p q$ have + :func:`~.jacobi_symbol` equal to $1$ . And among those, approximately half + are residues and approximately half are not. This maximizes the + entropy of the code. + + Parameters + ========== + + p, q, a + Initialization variables. + + Returns + ======= + + tuple : (p, q) + The input value ``p`` and ``q``. + + Raises + ====== + + ValueError + If ``p`` and ``q`` are not distinct odd primes. + + """ + if p == q: + raise ValueError("expected distinct primes, " + "got two copies of %i" % p) + elif not isprime(p) or not isprime(q): + raise ValueError("first two arguments must be prime, " + "got %i of %i" % (p, q)) + elif p == 2 or q == 2: + raise ValueError("first two arguments must not be even, " + "got %i of %i" % (p, q)) + return p, q + + +def gm_public_key(p, q, a=None, seed=None): + """ + Compute public keys for ``p`` and ``q``. + Note that in Goldwasser-Micali Encryption, + public keys are randomly selected. + + Parameters + ========== + + p, q, a : int, int, int + Initialization variables. + + Returns + ======= + + tuple : (a, N) + ``a`` is the input ``a`` if it is not ``None`` otherwise + some random integer coprime to ``p`` and ``q``. + + ``N`` is the product of ``p`` and ``q``. + + """ + + p, q = gm_private_key(p, q) + N = p * q + + if a is None: + randrange = _randrange(seed) + while True: + a = randrange(N) + if _legendre(a, p) == _legendre(a, q) == -1: + break + else: + if _legendre(a, p) != -1 or _legendre(a, q) != -1: + return False + return (a, N) + + +def encipher_gm(i, key, seed=None): + """ + Encrypt integer 'i' using public_key 'key' + Note that gm uses random encryption. + + Parameters + ========== + + i : int + The message to encrypt. + + key : (a, N) + The public key. + + Returns + ======= + + list : list of int + The randomized encrypted message. + + """ + if i < 0: + raise ValueError( + "message must be a non-negative " + "integer: got %d instead" % i) + a, N = key + bits = [] + while i > 0: + bits.append(i % 2) + i //= 2 + + gen = _random_coprime_stream(N, seed) + rev = reversed(bits) + encode = lambda b: next(gen)**2*pow(a, b) % N + return [ encode(b) for b in rev ] + + + +def decipher_gm(message, key): + """ + Decrypt message 'message' using public_key 'key'. + + Parameters + ========== + + message : list of int + The randomized encrypted message. + + key : (p, q) + The private key. + + Returns + ======= + + int + The encrypted message. + + """ + p, q = key + res = lambda m, p: _legendre(m, p) > 0 + bits = [res(m, p) * res(m, q) for m in message] + m = 0 + for b in bits: + m <<= 1 + m += not b + return m + + + +########### RailFence Cipher ############# + +def encipher_railfence(message,rails): + """ + Performs Railfence Encryption on plaintext and returns ciphertext + + Examples + ======== + + >>> from sympy.crypto.crypto import encipher_railfence + >>> message = "hello world" + >>> encipher_railfence(message,3) + 'horel ollwd' + + Parameters + ========== + + message : string, the message to encrypt. + rails : int, the number of rails. + + Returns + ======= + + The Encrypted string message. + + References + ========== + .. [1] https://en.wikipedia.org/wiki/Rail_fence_cipher + + """ + r = list(range(rails)) + p = cycle(r + r[-2:0:-1]) + return ''.join(sorted(message, key=lambda i: next(p))) + + +def decipher_railfence(ciphertext,rails): + """ + Decrypt the message using the given rails + + Examples + ======== + + >>> from sympy.crypto.crypto import decipher_railfence + >>> decipher_railfence("horel ollwd",3) + 'hello world' + + Parameters + ========== + + message : string, the message to encrypt. + rails : int, the number of rails. + + Returns + ======= + + The Decrypted string message. + + """ + r = list(range(rails)) + p = cycle(r + r[-2:0:-1]) + + idx = sorted(range(len(ciphertext)), key=lambda i: next(p)) + res = [''] * len(ciphertext) + for i, c in zip(idx, ciphertext): + res[i] = c + return ''.join(res) + + +################ Blum-Goldwasser cryptosystem ######################### + +def bg_private_key(p, q): + """ + Check if p and q can be used as private keys for + the Blum-Goldwasser cryptosystem. + + Explanation + =========== + + The three necessary checks for p and q to pass + so that they can be used as private keys: + + 1. p and q must both be prime + 2. p and q must be distinct + 3. p and q must be congruent to 3 mod 4 + + Parameters + ========== + + p, q + The keys to be checked. + + Returns + ======= + + p, q + Input values. + + Raises + ====== + + ValueError + If p and q do not pass the above conditions. + + """ + + if not isprime(p) or not isprime(q): + raise ValueError("the two arguments must be prime, " + "got %i and %i" %(p, q)) + elif p == q: + raise ValueError("the two arguments must be distinct, " + "got two copies of %i. " %p) + elif (p - 3) % 4 != 0 or (q - 3) % 4 != 0: + raise ValueError("the two arguments must be congruent to 3 mod 4, " + "got %i and %i" %(p, q)) + return p, q + +def bg_public_key(p, q): + """ + Calculates public keys from private keys. + + Explanation + =========== + + The function first checks the validity of + private keys passed as arguments and + then returns their product. + + Parameters + ========== + + p, q + The private keys. + + Returns + ======= + + N + The public key. + + """ + p, q = bg_private_key(p, q) + N = p * q + return N + +def encipher_bg(i, key, seed=None): + """ + Encrypts the message using public key and seed. + + Explanation + =========== + + ALGORITHM: + 1. Encodes i as a string of L bits, m. + 2. Select a random element r, where 1 < r < key, and computes + x = r^2 mod key. + 3. Use BBS pseudo-random number generator to generate L random bits, b, + using the initial seed as x. + 4. Encrypted message, c_i = m_i XOR b_i, 1 <= i <= L. + 5. x_L = x^(2^L) mod key. + 6. Return (c, x_L) + + Parameters + ========== + + i + Message, a non-negative integer + + key + The public key + + Returns + ======= + + Tuple + (encrypted_message, x_L) + + Raises + ====== + + ValueError + If i is negative. + + """ + + if i < 0: + raise ValueError( + "message must be a non-negative " + "integer: got %d instead" % i) + + enc_msg = [] + while i > 0: + enc_msg.append(i % 2) + i //= 2 + enc_msg.reverse() + L = len(enc_msg) + + r = _randint(seed)(2, key - 1) + x = r**2 % key + x_L = pow(int(x), int(2**L), int(key)) + + rand_bits = [] + for _ in range(L): + rand_bits.append(x % 2) + x = x**2 % key + + encrypt_msg = [m ^ b for (m, b) in zip(enc_msg, rand_bits)] + + return (encrypt_msg, x_L) + +def decipher_bg(message, key): + """ + Decrypts the message using private keys. + + Explanation + =========== + + ALGORITHM: + 1. Let, c be the encrypted message, y the second number received, + and p and q be the private keys. + 2. Compute, r_p = y^((p+1)/4 ^ L) mod p and + r_q = y^((q+1)/4 ^ L) mod q. + 3. Compute x_0 = (q(q^-1 mod p)r_p + p(p^-1 mod q)r_q) mod N. + 4. From, recompute the bits using the BBS generator, as in the + encryption algorithm. + 5. Compute original message by XORing c and b. + + Parameters + ========== + + message + Tuple of encrypted message and a non-negative integer. + + key + Tuple of private keys. + + Returns + ======= + + orig_msg + The original message + + """ + + p, q = key + encrypt_msg, y = message + public_key = p * q + L = len(encrypt_msg) + p_t = ((p + 1)/4)**L + q_t = ((q + 1)/4)**L + r_p = pow(int(y), int(p_t), int(p)) + r_q = pow(int(y), int(q_t), int(q)) + + x = (q * invert(q, p) * r_p + p * invert(p, q) * r_q) % public_key + + orig_bits = [] + for _ in range(L): + orig_bits.append(x % 2) + x = x**2 % public_key + + orig_msg = 0 + for (m, b) in zip(encrypt_msg, orig_bits): + orig_msg = orig_msg * 2 + orig_msg += (m ^ b) + + return orig_msg diff --git a/phivenv/Lib/site-packages/sympy/crypto/tests/__init__.py b/phivenv/Lib/site-packages/sympy/crypto/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/crypto/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/crypto/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66788553db93f819084f59353ec4d8a1010062b7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/crypto/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/crypto/tests/__pycache__/test_crypto.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/crypto/tests/__pycache__/test_crypto.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93fdc8f94379e965ae842d0ec9bff4dba80998eb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/crypto/tests/__pycache__/test_crypto.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/crypto/tests/test_crypto.py b/phivenv/Lib/site-packages/sympy/crypto/tests/test_crypto.py new file mode 100644 index 0000000000000000000000000000000000000000..c671138f9a61325f6e65cc7cafddc7cd46f19229 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/crypto/tests/test_crypto.py @@ -0,0 +1,562 @@ +from sympy.core import symbols +from sympy.crypto.crypto import (cycle_list, + encipher_shift, encipher_affine, encipher_substitution, + check_and_join, encipher_vigenere, decipher_vigenere, + encipher_hill, decipher_hill, encipher_bifid5, encipher_bifid6, + bifid5_square, bifid6_square, bifid5, bifid6, + decipher_bifid5, decipher_bifid6, encipher_kid_rsa, + decipher_kid_rsa, kid_rsa_private_key, kid_rsa_public_key, + decipher_rsa, rsa_private_key, rsa_public_key, encipher_rsa, + lfsr_connection_polynomial, lfsr_autocorrelation, lfsr_sequence, + encode_morse, decode_morse, elgamal_private_key, elgamal_public_key, + encipher_elgamal, decipher_elgamal, dh_private_key, dh_public_key, + dh_shared_key, decipher_shift, decipher_affine, encipher_bifid, + decipher_bifid, bifid_square, padded_key, uniq, decipher_gm, + encipher_gm, gm_public_key, gm_private_key, encipher_bg, decipher_bg, + bg_private_key, bg_public_key, encipher_rot13, decipher_rot13, + encipher_atbash, decipher_atbash, NonInvertibleCipherWarning, + encipher_railfence, decipher_railfence) +from sympy.external.gmpy import gcd +from sympy.matrices import Matrix +from sympy.ntheory import isprime, is_primitive_root +from sympy.polys.domains import FF + +from sympy.testing.pytest import raises, warns + +from sympy.core.random import randrange + +def test_encipher_railfence(): + assert encipher_railfence("hello world",2) == "hlowrdel ol" + assert encipher_railfence("hello world",3) == "horel ollwd" + assert encipher_railfence("hello world",4) == "hwe olordll" + +def test_decipher_railfence(): + assert decipher_railfence("hlowrdel ol",2) == "hello world" + assert decipher_railfence("horel ollwd",3) == "hello world" + assert decipher_railfence("hwe olordll",4) == "hello world" + + +def test_cycle_list(): + assert cycle_list(3, 4) == [3, 0, 1, 2] + assert cycle_list(-1, 4) == [3, 0, 1, 2] + assert cycle_list(1, 4) == [1, 2, 3, 0] + + +def test_encipher_shift(): + assert encipher_shift("ABC", 0) == "ABC" + assert encipher_shift("ABC", 1) == "BCD" + assert encipher_shift("ABC", -1) == "ZAB" + assert decipher_shift("ZAB", -1) == "ABC" + +def test_encipher_rot13(): + assert encipher_rot13("ABC") == "NOP" + assert encipher_rot13("NOP") == "ABC" + assert decipher_rot13("ABC") == "NOP" + assert decipher_rot13("NOP") == "ABC" + + +def test_encipher_affine(): + assert encipher_affine("ABC", (1, 0)) == "ABC" + assert encipher_affine("ABC", (1, 1)) == "BCD" + assert encipher_affine("ABC", (-1, 0)) == "AZY" + assert encipher_affine("ABC", (-1, 1), symbols="ABCD") == "BAD" + assert encipher_affine("123", (-1, 1), symbols="1234") == "214" + assert encipher_affine("ABC", (3, 16)) == "QTW" + assert decipher_affine("QTW", (3, 16)) == "ABC" + +def test_encipher_atbash(): + assert encipher_atbash("ABC") == "ZYX" + assert encipher_atbash("ZYX") == "ABC" + assert decipher_atbash("ABC") == "ZYX" + assert decipher_atbash("ZYX") == "ABC" + +def test_encipher_substitution(): + assert encipher_substitution("ABC", "BAC", "ABC") == "BAC" + assert encipher_substitution("123", "1243", "1234") == "124" + + +def test_check_and_join(): + assert check_and_join("abc") == "abc" + assert check_and_join(uniq("aaabc")) == "abc" + assert check_and_join("ab c".split()) == "abc" + assert check_and_join("abc", "a", filter=True) == "a" + raises(ValueError, lambda: check_and_join('ab', 'a')) + + +def test_encipher_vigenere(): + assert encipher_vigenere("ABC", "ABC") == "ACE" + assert encipher_vigenere("ABC", "ABC", symbols="ABCD") == "ACA" + assert encipher_vigenere("ABC", "AB", symbols="ABCD") == "ACC" + assert encipher_vigenere("AB", "ABC", symbols="ABCD") == "AC" + assert encipher_vigenere("A", "ABC", symbols="ABCD") == "A" + + +def test_decipher_vigenere(): + assert decipher_vigenere("ABC", "ABC") == "AAA" + assert decipher_vigenere("ABC", "ABC", symbols="ABCD") == "AAA" + assert decipher_vigenere("ABC", "AB", symbols="ABCD") == "AAC" + assert decipher_vigenere("AB", "ABC", symbols="ABCD") == "AA" + assert decipher_vigenere("A", "ABC", symbols="ABCD") == "A" + + +def test_encipher_hill(): + A = Matrix(2, 2, [1, 2, 3, 5]) + assert encipher_hill("ABCD", A) == "CFIV" + A = Matrix(2, 2, [1, 0, 0, 1]) + assert encipher_hill("ABCD", A) == "ABCD" + assert encipher_hill("ABCD", A, symbols="ABCD") == "ABCD" + A = Matrix(2, 2, [1, 2, 3, 5]) + assert encipher_hill("ABCD", A, symbols="ABCD") == "CBAB" + assert encipher_hill("AB", A, symbols="ABCD") == "CB" + # message length, n, does not need to be a multiple of k; + # it is padded + assert encipher_hill("ABA", A) == "CFGC" + assert encipher_hill("ABA", A, pad="Z") == "CFYV" + + +def test_decipher_hill(): + A = Matrix(2, 2, [1, 2, 3, 5]) + assert decipher_hill("CFIV", A) == "ABCD" + A = Matrix(2, 2, [1, 0, 0, 1]) + assert decipher_hill("ABCD", A) == "ABCD" + assert decipher_hill("ABCD", A, symbols="ABCD") == "ABCD" + A = Matrix(2, 2, [1, 2, 3, 5]) + assert decipher_hill("CBAB", A, symbols="ABCD") == "ABCD" + assert decipher_hill("CB", A, symbols="ABCD") == "AB" + # n does not need to be a multiple of k + assert decipher_hill("CFA", A) == "ABAA" + + +def test_encipher_bifid5(): + assert encipher_bifid5("AB", "AB") == "AB" + assert encipher_bifid5("AB", "CD") == "CO" + assert encipher_bifid5("ab", "c") == "CH" + assert encipher_bifid5("a bc", "b") == "BAC" + + +def test_bifid5_square(): + A = bifid5 + f = lambda i, j: symbols(A[5*i + j]) + M = Matrix(5, 5, f) + assert bifid5_square("") == M + + +def test_decipher_bifid5(): + assert decipher_bifid5("AB", "AB") == "AB" + assert decipher_bifid5("CO", "CD") == "AB" + assert decipher_bifid5("ch", "c") == "AB" + assert decipher_bifid5("b ac", "b") == "ABC" + + +def test_encipher_bifid6(): + assert encipher_bifid6("AB", "AB") == "AB" + assert encipher_bifid6("AB", "CD") == "CP" + assert encipher_bifid6("ab", "c") == "CI" + assert encipher_bifid6("a bc", "b") == "BAC" + + +def test_decipher_bifid6(): + assert decipher_bifid6("AB", "AB") == "AB" + assert decipher_bifid6("CP", "CD") == "AB" + assert decipher_bifid6("ci", "c") == "AB" + assert decipher_bifid6("b ac", "b") == "ABC" + + +def test_bifid6_square(): + A = bifid6 + f = lambda i, j: symbols(A[6*i + j]) + M = Matrix(6, 6, f) + assert bifid6_square("") == M + + +def test_rsa_public_key(): + assert rsa_public_key(2, 3, 1) == (6, 1) + assert rsa_public_key(5, 3, 3) == (15, 3) + + with warns(NonInvertibleCipherWarning): + assert rsa_public_key(2, 2, 1) == (4, 1) + assert rsa_public_key(8, 8, 8) is False + + +def test_rsa_private_key(): + assert rsa_private_key(2, 3, 1) == (6, 1) + assert rsa_private_key(5, 3, 3) == (15, 3) + assert rsa_private_key(23,29,5) == (667,493) + + with warns(NonInvertibleCipherWarning): + assert rsa_private_key(2, 2, 1) == (4, 1) + assert rsa_private_key(8, 8, 8) is False + + +def test_rsa_large_key(): + # Sample from + # http://www.herongyang.com/Cryptography/JCE-Public-Key-RSA-Private-Public-Key-Pair-Sample.html + p = int('101565610013301240713207239558950144682174355406589305284428666'\ + '903702505233009') + q = int('894687191887545488935455605955948413812376003053143521429242133'\ + '12069293984003') + e = int('65537') + d = int('893650581832704239530398858744759129594796235440844479456143566'\ + '6999402846577625762582824202269399672579058991442587406384754958587'\ + '400493169361356902030209') + assert rsa_public_key(p, q, e) == (p*q, e) + assert rsa_private_key(p, q, e) == (p*q, d) + + +def test_encipher_rsa(): + puk = rsa_public_key(2, 3, 1) + assert encipher_rsa(2, puk) == 2 + puk = rsa_public_key(5, 3, 3) + assert encipher_rsa(2, puk) == 8 + + with warns(NonInvertibleCipherWarning): + puk = rsa_public_key(2, 2, 1) + assert encipher_rsa(2, puk) == 2 + + +def test_decipher_rsa(): + prk = rsa_private_key(2, 3, 1) + assert decipher_rsa(2, prk) == 2 + prk = rsa_private_key(5, 3, 3) + assert decipher_rsa(8, prk) == 2 + + with warns(NonInvertibleCipherWarning): + prk = rsa_private_key(2, 2, 1) + assert decipher_rsa(2, prk) == 2 + + +def test_mutltiprime_rsa_full_example(): + # Test example from + # https://iopscience.iop.org/article/10.1088/1742-6596/995/1/012030 + puk = rsa_public_key(2, 3, 5, 7, 11, 13, 7) + prk = rsa_private_key(2, 3, 5, 7, 11, 13, 7) + assert puk == (30030, 7) + assert prk == (30030, 823) + + msg = 10 + encrypted = encipher_rsa(2 * msg - 15, puk) + assert encrypted == 18065 + decrypted = (decipher_rsa(encrypted, prk) + 15) / 2 + assert decrypted == msg + + # Test example from + # https://www.scirp.org/pdf/JCC_2018032215502008.pdf + puk1 = rsa_public_key(53, 41, 43, 47, 41) + prk1 = rsa_private_key(53, 41, 43, 47, 41) + puk2 = rsa_public_key(53, 41, 43, 47, 97) + prk2 = rsa_private_key(53, 41, 43, 47, 97) + + assert puk1 == (4391633, 41) + assert prk1 == (4391633, 294041) + assert puk2 == (4391633, 97) + assert prk2 == (4391633, 455713) + + msg = 12321 + encrypted = encipher_rsa(encipher_rsa(msg, puk1), puk2) + assert encrypted == 1081588 + decrypted = decipher_rsa(decipher_rsa(encrypted, prk2), prk1) + assert decrypted == msg + + +def test_rsa_crt_extreme(): + p = int( + '10177157607154245068023861503693082120906487143725062283406501' \ + '54082258226204046999838297167140821364638180697194879500245557' \ + '65445186962893346463841419427008800341257468600224049986260471' \ + '92257248163014468841725476918639415726709736077813632961290911' \ + '0256421232977833028677441206049309220354796014376698325101693') + + q = int( + '28752342353095132872290181526607275886182793241660805077850801' \ + '75689512797754286972952273553128181861830576836289738668745250' \ + '34028199691128870676414118458442900035778874482624765513861643' \ + '27966696316822188398336199002306588703902894100476186823849595' \ + '103239410527279605442148285816149368667083114802852804976893') + + r = int( + '17698229259868825776879500736350186838850961935956310134378261' \ + '89771862186717463067541369694816245225291921138038800171125596' \ + '07315449521981157084370187887650624061033066022458512942411841' \ + '18747893789972315277160085086164119879536041875335384844820566' \ + '0287479617671726408053319619892052000850883994343378882717849') + + s = int( + '68925428438585431029269182233502611027091755064643742383515623' \ + '64321310582896893395529367074942808353187138794422745718419645' \ + '28291231865157212604266903677599180789896916456120289112752835' \ + '98502265889669730331688206825220074713977607415178738015831030' \ + '364290585369150502819743827343552098197095520550865360159439' + ) + + t = int( + '69035483433453632820551311892368908779778144568711455301541094' \ + '31487047642322695357696860925747923189635033183069823820910521' \ + '71172909106797748883261493224162414050106920442445896819806600' \ + '15448444826108008217972129130625571421904893252804729877353352' \ + '739420480574842850202181462656251626522910618936534699566291' + ) + + e = 65537 + puk = rsa_public_key(p, q, r, s, t, e) + prk = rsa_private_key(p, q, r, s, t, e) + + plaintext = 1000 + ciphertext_1 = encipher_rsa(plaintext, puk) + ciphertext_2 = encipher_rsa(plaintext, puk, [p, q, r, s, t]) + assert ciphertext_1 == ciphertext_2 + assert decipher_rsa(ciphertext_1, prk) == \ + decipher_rsa(ciphertext_1, prk, [p, q, r, s, t]) + + +def test_rsa_exhaustive(): + p, q = 61, 53 + e = 17 + puk = rsa_public_key(p, q, e, totient='Carmichael') + prk = rsa_private_key(p, q, e, totient='Carmichael') + + for msg in range(puk[0]): + encrypted = encipher_rsa(msg, puk) + decrypted = decipher_rsa(encrypted, prk) + try: + assert decrypted == msg + except AssertionError: + raise AssertionError( + "The RSA is not correctly decrypted " \ + "(Original : {}, Encrypted : {}, Decrypted : {})" \ + .format(msg, encrypted, decrypted) + ) + + +def test_rsa_multiprime_exhanstive(): + primes = [3, 5, 7, 11] + e = 7 + args = primes + [e] + puk = rsa_public_key(*args, totient='Carmichael') + prk = rsa_private_key(*args, totient='Carmichael') + n = puk[0] + + for msg in range(n): + encrypted = encipher_rsa(msg, puk) + decrypted = decipher_rsa(encrypted, prk) + try: + assert decrypted == msg + except AssertionError: + raise AssertionError( + "The RSA is not correctly decrypted " \ + "(Original : {}, Encrypted : {}, Decrypted : {})" \ + .format(msg, encrypted, decrypted) + ) + + +def test_rsa_multipower_exhanstive(): + primes = [5, 5, 7] + e = 7 + args = primes + [e] + puk = rsa_public_key(*args, multipower=True) + prk = rsa_private_key(*args, multipower=True) + n = puk[0] + + for msg in range(n): + if gcd(msg, n) != 1: + continue + + encrypted = encipher_rsa(msg, puk) + decrypted = decipher_rsa(encrypted, prk) + try: + assert decrypted == msg + except AssertionError: + raise AssertionError( + "The RSA is not correctly decrypted " \ + "(Original : {}, Encrypted : {}, Decrypted : {})" \ + .format(msg, encrypted, decrypted) + ) + + +def test_kid_rsa_public_key(): + assert kid_rsa_public_key(1, 2, 1, 1) == (5, 2) + assert kid_rsa_public_key(1, 2, 2, 1) == (8, 3) + assert kid_rsa_public_key(1, 2, 1, 2) == (7, 2) + + +def test_kid_rsa_private_key(): + assert kid_rsa_private_key(1, 2, 1, 1) == (5, 3) + assert kid_rsa_private_key(1, 2, 2, 1) == (8, 3) + assert kid_rsa_private_key(1, 2, 1, 2) == (7, 4) + + +def test_encipher_kid_rsa(): + assert encipher_kid_rsa(1, (5, 2)) == 2 + assert encipher_kid_rsa(1, (8, 3)) == 3 + assert encipher_kid_rsa(1, (7, 2)) == 2 + + +def test_decipher_kid_rsa(): + assert decipher_kid_rsa(2, (5, 3)) == 1 + assert decipher_kid_rsa(3, (8, 3)) == 1 + assert decipher_kid_rsa(2, (7, 4)) == 1 + + +def test_encode_morse(): + assert encode_morse('ABC') == '.-|-...|-.-.' + assert encode_morse('SMS ') == '...|--|...||' + assert encode_morse('SMS\n') == '...|--|...||' + assert encode_morse('') == '' + assert encode_morse(' ') == '||' + assert encode_morse(' ', sep='`') == '``' + assert encode_morse(' ', sep='``') == '````' + assert encode_morse('!@#$%^&*()_+') == '-.-.--|.--.-.|...-..-|-.--.|-.--.-|..--.-|.-.-.' + assert encode_morse('12345') == '.----|..---|...--|....-|.....' + assert encode_morse('67890') == '-....|--...|---..|----.|-----' + + +def test_decode_morse(): + assert decode_morse('-.-|.|-.--') == 'KEY' + assert decode_morse('.-.|..-|-.||') == 'RUN' + raises(KeyError, lambda: decode_morse('.....----')) + + +def test_lfsr_sequence(): + raises(TypeError, lambda: lfsr_sequence(1, [1], 1)) + raises(TypeError, lambda: lfsr_sequence([1], 1, 1)) + F = FF(2) + assert lfsr_sequence([F(1)], [F(1)], 2) == [F(1), F(1)] + assert lfsr_sequence([F(0)], [F(1)], 2) == [F(1), F(0)] + F = FF(3) + assert lfsr_sequence([F(1)], [F(1)], 2) == [F(1), F(1)] + assert lfsr_sequence([F(0)], [F(2)], 2) == [F(2), F(0)] + assert lfsr_sequence([F(1)], [F(2)], 2) == [F(2), F(2)] + + +def test_lfsr_autocorrelation(): + raises(TypeError, lambda: lfsr_autocorrelation(1, 2, 3)) + F = FF(2) + s = lfsr_sequence([F(1), F(0)], [F(0), F(1)], 5) + assert lfsr_autocorrelation(s, 2, 0) == 1 + assert lfsr_autocorrelation(s, 2, 1) == -1 + + +def test_lfsr_connection_polynomial(): + F = FF(2) + x = symbols("x") + s = lfsr_sequence([F(1), F(0)], [F(0), F(1)], 5) + assert lfsr_connection_polynomial(s) == x**2 + 1 + s = lfsr_sequence([F(1), F(1)], [F(0), F(1)], 5) + assert lfsr_connection_polynomial(s) == x**2 + x + 1 + + +def test_elgamal_private_key(): + a, b, _ = elgamal_private_key(digit=100) + assert isprime(a) + assert is_primitive_root(b, a) + assert len(bin(a)) >= 102 + + +def test_elgamal(): + dk = elgamal_private_key(5) + ek = elgamal_public_key(dk) + P = ek[0] + assert P - 1 == decipher_elgamal(encipher_elgamal(P - 1, ek), dk) + raises(ValueError, lambda: encipher_elgamal(P, dk)) + raises(ValueError, lambda: encipher_elgamal(-1, dk)) + + +def test_dh_private_key(): + p, g, _ = dh_private_key(digit = 100) + assert isprime(p) + assert is_primitive_root(g, p) + assert len(bin(p)) >= 102 + + +def test_dh_public_key(): + p1, g1, a = dh_private_key(digit = 100) + p2, g2, ga = dh_public_key((p1, g1, a)) + assert p1 == p2 + assert g1 == g2 + assert ga == pow(g1, a, p1) + + +def test_dh_shared_key(): + prk = dh_private_key(digit = 100) + p, _, ga = dh_public_key(prk) + b = randrange(2, p) + sk = dh_shared_key((p, _, ga), b) + assert sk == pow(ga, b, p) + raises(ValueError, lambda: dh_shared_key((1031, 14, 565), 2000)) + + +def test_padded_key(): + assert padded_key('b', 'ab') == 'ba' + raises(ValueError, lambda: padded_key('ab', 'ace')) + raises(ValueError, lambda: padded_key('ab', 'abba')) + + +def test_bifid(): + raises(ValueError, lambda: encipher_bifid('abc', 'b', 'abcde')) + assert encipher_bifid('abc', 'b', 'abcd') == 'bdb' + raises(ValueError, lambda: decipher_bifid('bdb', 'b', 'abcde')) + assert encipher_bifid('bdb', 'b', 'abcd') == 'abc' + raises(ValueError, lambda: bifid_square('abcde')) + assert bifid5_square("B") == \ + bifid5_square('BACDEFGHIKLMNOPQRSTUVWXYZ') + assert bifid6_square('B0') == \ + bifid6_square('B0ACDEFGHIJKLMNOPQRSTUVWXYZ123456789') + + +def test_encipher_decipher_gm(): + ps = [131, 137, 139, 149, 151, 157, 163, 167, + 173, 179, 181, 191, 193, 197, 199] + qs = [89, 97, 101, 103, 107, 109, 113, 127, + 131, 137, 139, 149, 151, 157, 47] + messages = [ + 0, 32855, 34303, 14805, 1280, 75859, 38368, + 724, 60356, 51675, 76697, 61854, 18661, + ] + for p, q in zip(ps, qs): + pri = gm_private_key(p, q) + for msg in messages: + pub = gm_public_key(p, q) + enc = encipher_gm(msg, pub) + dec = decipher_gm(enc, pri) + assert dec == msg + + +def test_gm_private_key(): + raises(ValueError, lambda: gm_public_key(13, 15)) + raises(ValueError, lambda: gm_public_key(0, 0)) + raises(ValueError, lambda: gm_public_key(0, 5)) + assert 17, 19 == gm_public_key(17, 19) + + +def test_gm_public_key(): + assert 323 == gm_public_key(17, 19)[1] + assert 15 == gm_public_key(3, 5)[1] + raises(ValueError, lambda: gm_public_key(15, 19)) + +def test_encipher_decipher_bg(): + ps = [67, 7, 71, 103, 11, 43, 107, 47, + 79, 19, 83, 23, 59, 127, 31] + qs = qs = [7, 71, 103, 11, 43, 107, 47, + 79, 19, 83, 23, 59, 127, 31, 67] + messages = [ + 0, 328, 343, 148, 1280, 758, 383, + 724, 603, 516, 766, 618, 186, + ] + + for p, q in zip(ps, qs): + pri = bg_private_key(p, q) + for msg in messages: + pub = bg_public_key(p, q) + enc = encipher_bg(msg, pub) + dec = decipher_bg(enc, pri) + assert dec == msg + +def test_bg_private_key(): + raises(ValueError, lambda: bg_private_key(8, 16)) + raises(ValueError, lambda: bg_private_key(8, 8)) + raises(ValueError, lambda: bg_private_key(13, 17)) + assert 23, 31 == bg_private_key(23, 31) + +def test_bg_public_key(): + assert 5293 == bg_public_key(67, 79) + assert 713 == bg_public_key(23, 31) + raises(ValueError, lambda: bg_private_key(13, 17)) diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/__init__.py b/phivenv/Lib/site-packages/sympy/diffgeom/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8846a99510601c9675103e21ef5a0a1e839fdd11 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/__init__.py @@ -0,0 +1,19 @@ +from .diffgeom import ( + BaseCovarDerivativeOp, BaseScalarField, BaseVectorField, Commutator, + contravariant_order, CoordSystem, CoordinateSymbol, + CovarDerivativeOp, covariant_order, Differential, intcurve_diffequ, + intcurve_series, LieDerivative, Manifold, metric_to_Christoffel_1st, + metric_to_Christoffel_2nd, metric_to_Ricci_components, + metric_to_Riemann_components, Patch, Point, TensorProduct, twoform_to_matrix, + vectors_in_basis, WedgeProduct, +) + +__all__ = [ + 'BaseCovarDerivativeOp', 'BaseScalarField', 'BaseVectorField', 'Commutator', + 'contravariant_order', 'CoordSystem', 'CoordinateSymbol', + 'CovarDerivativeOp', 'covariant_order', 'Differential', 'intcurve_diffequ', + 'intcurve_series', 'LieDerivative', 'Manifold', 'metric_to_Christoffel_1st', + 'metric_to_Christoffel_2nd', 'metric_to_Ricci_components', + 'metric_to_Riemann_components', 'Patch', 'Point', 'TensorProduct', + 'twoform_to_matrix', 'vectors_in_basis', 'WedgeProduct', +] diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30990f2774d16ca145853991e081562bd92aad83 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/diffgeom.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/diffgeom.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd58827bc16679e51b3440bfbe471879189cb046 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/diffgeom.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/rn.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/rn.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e777a4eef348da2b955f925a5dac1d97045f8742 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/__pycache__/rn.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/diffgeom.py b/phivenv/Lib/site-packages/sympy/diffgeom/diffgeom.py new file mode 100644 index 0000000000000000000000000000000000000000..a95f83122d6de0b7015b9a3ad0573cbfd97a7ef3 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/diffgeom.py @@ -0,0 +1,2270 @@ +from __future__ import annotations +from typing import Any + +from functools import reduce +from itertools import permutations + +from sympy.combinatorics import Permutation +from sympy.core import ( + Basic, Expr, Function, diff, + Pow, Mul, Add, Lambda, S, Tuple, Dict +) +from sympy.core.cache import cacheit + +from sympy.core.symbol import Symbol, Dummy +from sympy.core.symbol import Str +from sympy.core.sympify import _sympify +from sympy.functions import factorial +from sympy.matrices import ImmutableDenseMatrix as Matrix +from sympy.solvers import solve + +from sympy.utilities.exceptions import (sympy_deprecation_warning, + SymPyDeprecationWarning, + ignore_warnings) + + +# TODO you are a bit excessive in the use of Dummies +# TODO dummy point, literal field +# TODO too often one needs to call doit or simplify on the output, check the +# tests and find out why +from sympy.tensor.array import ImmutableDenseNDimArray + + +class Manifold(Basic): + """ + A mathematical manifold. + + Explanation + =========== + + A manifold is a topological space that locally resembles + Euclidean space near each point [1]. + This class does not provide any means to study the topological + characteristics of the manifold that it represents, though. + + Parameters + ========== + + name : str + The name of the manifold. + + dim : int + The dimension of the manifold. + + Examples + ======== + + >>> from sympy.diffgeom import Manifold + >>> m = Manifold('M', 2) + >>> m + M + >>> m.dim + 2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Manifold + """ + + def __new__(cls, name, dim, **kwargs): + if not isinstance(name, Str): + name = Str(name) + dim = _sympify(dim) + obj = super().__new__(cls, name, dim) + + obj.patches = _deprecated_list( + """ + Manifold.patches is deprecated. The Manifold object is now + immutable. Instead use a separate list to keep track of the + patches. + """, []) + return obj + + @property + def name(self): + return self.args[0] + + @property + def dim(self): + return self.args[1] + + +class Patch(Basic): + """ + A patch on a manifold. + + Explanation + =========== + + Coordinate patch, or patch in short, is a simply-connected open set around + a point in the manifold [1]. On a manifold one can have many patches that + do not always include the whole manifold. On these patches coordinate + charts can be defined that permit the parameterization of any point on the + patch in terms of a tuple of real numbers (the coordinates). + + This class does not provide any means to study the topological + characteristics of the patch that it represents. + + Parameters + ========== + + name : str + The name of the patch. + + manifold : Manifold + The manifold on which the patch is defined. + + Examples + ======== + + >>> from sympy.diffgeom import Manifold, Patch + >>> m = Manifold('M', 2) + >>> p = Patch('P', m) + >>> p + P + >>> p.dim + 2 + + References + ========== + + .. [1] G. Sussman, J. Wisdom, W. Farr, Functional Differential Geometry + (2013) + + """ + def __new__(cls, name, manifold, **kwargs): + if not isinstance(name, Str): + name = Str(name) + obj = super().__new__(cls, name, manifold) + + obj.manifold.patches.append(obj) # deprecated + obj.coord_systems = _deprecated_list( + """ + Patch.coord_systms is deprecated. The Patch class is now + immutable. Instead use a separate list to keep track of coordinate + systems. + """, []) + return obj + + @property + def name(self): + return self.args[0] + + @property + def manifold(self): + return self.args[1] + + @property + def dim(self): + return self.manifold.dim + + +class CoordSystem(Basic): + """ + A coordinate system defined on the patch. + + Explanation + =========== + + Coordinate system is a system that uses one or more coordinates to uniquely + determine the position of the points or other geometric elements on a + manifold [1]. + + By passing ``Symbols`` to *symbols* parameter, user can define the name and + assumptions of coordinate symbols of the coordinate system. If not passed, + these symbols are generated automatically and are assumed to be real valued. + + By passing *relations* parameter, user can define the transform relations of + coordinate systems. Inverse transformation and indirect transformation can + be found automatically. If this parameter is not passed, coordinate + transformation cannot be done. + + Parameters + ========== + + name : str + The name of the coordinate system. + + patch : Patch + The patch where the coordinate system is defined. + + symbols : list of Symbols, optional + Defines the names and assumptions of coordinate symbols. + + relations : dict, optional + Key is a tuple of two strings, who are the names of the systems where + the coordinates transform from and transform to. + Value is a tuple of the symbols before transformation and a tuple of + the expressions after transformation. + + Examples + ======== + + We define two-dimensional Cartesian coordinate system and polar coordinate + system. + + >>> from sympy import symbols, pi, sqrt, atan2, cos, sin + >>> from sympy.diffgeom import Manifold, Patch, CoordSystem + >>> m = Manifold('M', 2) + >>> p = Patch('P', m) + >>> x, y = symbols('x y', real=True) + >>> r, theta = symbols('r theta', nonnegative=True) + >>> relation_dict = { + ... ('Car2D', 'Pol'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))], + ... ('Pol', 'Car2D'): [(r, theta), (r*cos(theta), r*sin(theta))] + ... } + >>> Car2D = CoordSystem('Car2D', p, (x, y), relation_dict) + >>> Pol = CoordSystem('Pol', p, (r, theta), relation_dict) + + ``symbols`` property returns ``CoordinateSymbol`` instances. These symbols + are not same with the symbols used to construct the coordinate system. + + >>> Car2D + Car2D + >>> Car2D.dim + 2 + >>> Car2D.symbols + (x, y) + >>> _[0].func + + + ``transformation()`` method returns the transformation function from + one coordinate system to another. ``transform()`` method returns the + transformed coordinates. + + >>> Car2D.transformation(Pol) + Lambda((x, y), Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]])) + >>> Car2D.transform(Pol) + Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]]) + >>> Car2D.transform(Pol, [1, 2]) + Matrix([ + [sqrt(5)], + [atan(2)]]) + + ``jacobian()`` method returns the Jacobian matrix of coordinate + transformation between two systems. ``jacobian_determinant()`` method + returns the Jacobian determinant of coordinate transformation between two + systems. + + >>> Pol.jacobian(Car2D) + Matrix([ + [cos(theta), -r*sin(theta)], + [sin(theta), r*cos(theta)]]) + >>> Pol.jacobian(Car2D, [1, pi/2]) + Matrix([ + [0, -1], + [1, 0]]) + >>> Car2D.jacobian_determinant(Pol) + 1/sqrt(x**2 + y**2) + >>> Car2D.jacobian_determinant(Pol, [1,0]) + 1 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Coordinate_system + + """ + def __new__(cls, name, patch, symbols=None, relations={}, **kwargs): + if not isinstance(name, Str): + name = Str(name) + + # canonicallize the symbols + if symbols is None: + names = kwargs.get('names', None) + if names is None: + symbols = Tuple( + *[Symbol('%s_%s' % (name.name, i), real=True) + for i in range(patch.dim)] + ) + else: + sympy_deprecation_warning( + f""" +The 'names' argument to CoordSystem is deprecated. Use 'symbols' instead. That +is, replace + + CoordSystem(..., names={names}) + +with + + CoordSystem(..., symbols=[{', '.join(["Symbol(" + repr(n) + ", real=True)" for n in names])}]) + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + symbols = Tuple( + *[Symbol(n, real=True) for n in names] + ) + else: + syms = [] + for s in symbols: + if isinstance(s, Symbol): + syms.append(Symbol(s.name, **s._assumptions.generator)) + elif isinstance(s, str): + sympy_deprecation_warning( + f""" + +Passing a string as the coordinate symbol name to CoordSystem is deprecated. +Pass a Symbol with the appropriate name and assumptions instead. + +That is, replace {s} with Symbol({s!r}, real=True). + """, + + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + syms.append(Symbol(s, real=True)) + symbols = Tuple(*syms) + + # canonicallize the relations + rel_temp = {} + for k,v in relations.items(): + s1, s2 = k + if not isinstance(s1, Str): + s1 = Str(s1) + if not isinstance(s2, Str): + s2 = Str(s2) + key = Tuple(s1, s2) + + # Old version used Lambda as a value. + if isinstance(v, Lambda): + v = (tuple(v.signature), tuple(v.expr)) + else: + v = (tuple(v[0]), tuple(v[1])) + rel_temp[key] = v + relations = Dict(rel_temp) + + # construct the object + obj = super().__new__(cls, name, patch, symbols, relations) + + # Add deprecated attributes + obj.transforms = _deprecated_dict( + """ + CoordSystem.transforms is deprecated. The CoordSystem class is now + immutable. Use the 'relations' keyword argument to the + CoordSystems() constructor to specify relations. + """, {}) + obj._names = [str(n) for n in symbols] + obj.patch.coord_systems.append(obj) # deprecated + obj._dummies = [Dummy(str(n)) for n in symbols] # deprecated + obj._dummy = Dummy() + + return obj + + @property + def name(self): + return self.args[0] + + @property + def patch(self): + return self.args[1] + + @property + def manifold(self): + return self.patch.manifold + + @property + def symbols(self): + return tuple(CoordinateSymbol(self, i, **s._assumptions.generator) + for i,s in enumerate(self.args[2])) + + @property + def relations(self): + return self.args[3] + + @property + def dim(self): + return self.patch.dim + + ########################################################################## + # Finding transformation relation + ########################################################################## + + def transformation(self, sys): + """ + Return coordinate transformation function from *self* to *sys*. + + Parameters + ========== + + sys : CoordSystem + + Returns + ======= + + sympy.Lambda + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_r.transformation(R2_p) + Lambda((x, y), Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]])) + + """ + signature = self.args[2] + + key = Tuple(self.name, sys.name) + if self == sys: + expr = Matrix(self.symbols) + elif key in self.relations: + expr = Matrix(self.relations[key][1]) + elif key[::-1] in self.relations: + expr = Matrix(self._inverse_transformation(sys, self)) + else: + expr = Matrix(self._indirect_transformation(self, sys)) + return Lambda(signature, expr) + + @staticmethod + def _solve_inverse(sym1, sym2, exprs, sys1_name, sys2_name): + ret = solve( + [t[0] - t[1] for t in zip(sym2, exprs)], + list(sym1), dict=True) + + if len(ret) == 0: + temp = "Cannot solve inverse relation from {} to {}." + raise NotImplementedError(temp.format(sys1_name, sys2_name)) + elif len(ret) > 1: + temp = "Obtained multiple inverse relation from {} to {}." + raise ValueError(temp.format(sys1_name, sys2_name)) + + return ret[0] + + @classmethod + def _inverse_transformation(cls, sys1, sys2): + # Find the transformation relation from sys2 to sys1 + forward = sys1.transform(sys2) + inv_results = cls._solve_inverse(sys1.symbols, sys2.symbols, forward, + sys1.name, sys2.name) + signature = tuple(sys1.symbols) + return [inv_results[s] for s in signature] + + @classmethod + @cacheit + def _indirect_transformation(cls, sys1, sys2): + # Find the transformation relation between two indirectly connected + # coordinate systems + rel = sys1.relations + path = cls._dijkstra(sys1, sys2) + + transforms = [] + for s1, s2 in zip(path, path[1:]): + if (s1, s2) in rel: + transforms.append(rel[(s1, s2)]) + else: + sym2, inv_exprs = rel[(s2, s1)] + sym1 = tuple(Dummy() for i in sym2) + ret = cls._solve_inverse(sym2, sym1, inv_exprs, s2, s1) + ret = tuple(ret[s] for s in sym2) + transforms.append((sym1, ret)) + syms = sys1.args[2] + exprs = syms + for newsyms, newexprs in transforms: + exprs = tuple(e.subs(zip(newsyms, exprs)) for e in newexprs) + return exprs + + @staticmethod + def _dijkstra(sys1, sys2): + # Use Dijkstra algorithm to find the shortest path between two indirectly-connected + # coordinate systems + # return value is the list of the names of the systems. + relations = sys1.relations + graph = {} + for s1, s2 in relations.keys(): + if s1 not in graph: + graph[s1] = {s2} + else: + graph[s1].add(s2) + if s2 not in graph: + graph[s2] = {s1} + else: + graph[s2].add(s1) + + path_dict = {sys:[0, [], 0] for sys in graph} # minimum distance, path, times of visited + + def visit(sys): + path_dict[sys][2] = 1 + for newsys in graph[sys]: + distance = path_dict[sys][0] + 1 + if path_dict[newsys][0] >= distance or not path_dict[newsys][1]: + path_dict[newsys][0] = distance + path_dict[newsys][1] = list(path_dict[sys][1]) + path_dict[newsys][1].append(sys) + + visit(sys1.name) + + while True: + min_distance = max(path_dict.values(), key=lambda x:x[0])[0] + newsys = None + for sys, lst in path_dict.items(): + if 0 < lst[0] <= min_distance and not lst[2]: + min_distance = lst[0] + newsys = sys + if newsys is None: + break + visit(newsys) + + result = path_dict[sys2.name][1] + result.append(sys2.name) + + if result == [sys2.name]: + raise KeyError("Two coordinate systems are not connected.") + return result + + def connect_to(self, to_sys, from_coords, to_exprs, inverse=True, fill_in_gaps=False): + sympy_deprecation_warning( + """ + The CoordSystem.connect_to() method is deprecated. Instead, + generate a new instance of CoordSystem with the 'relations' + keyword argument (CoordSystem classes are now immutable). + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + + from_coords, to_exprs = dummyfy(from_coords, to_exprs) + self.transforms[to_sys] = Matrix(from_coords), Matrix(to_exprs) + + if inverse: + to_sys.transforms[self] = self._inv_transf(from_coords, to_exprs) + + if fill_in_gaps: + self._fill_gaps_in_transformations() + + @staticmethod + def _inv_transf(from_coords, to_exprs): + # Will be removed when connect_to is removed + inv_from = [i.as_dummy() for i in from_coords] + inv_to = solve( + [t[0] - t[1] for t in zip(inv_from, to_exprs)], + list(from_coords), dict=True)[0] + inv_to = [inv_to[fc] for fc in from_coords] + return Matrix(inv_from), Matrix(inv_to) + + @staticmethod + def _fill_gaps_in_transformations(): + # Will be removed when connect_to is removed + raise NotImplementedError + + ########################################################################## + # Coordinate transformations + ########################################################################## + + def transform(self, sys, coordinates=None): + """ + Return the result of coordinate transformation from *self* to *sys*. + If coordinates are not given, coordinate symbols of *self* are used. + + Parameters + ========== + + sys : CoordSystem + + coordinates : Any iterable, optional. + + Returns + ======= + + sympy.ImmutableDenseMatrix containing CoordinateSymbol + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_r.transform(R2_p) + Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]]) + >>> R2_r.transform(R2_p, [0, 1]) + Matrix([ + [ 1], + [pi/2]]) + + """ + if coordinates is None: + coordinates = self.symbols + if self != sys: + transf = self.transformation(sys) + coordinates = transf(*coordinates) + else: + coordinates = Matrix(coordinates) + return coordinates + + def coord_tuple_transform_to(self, to_sys, coords): + """Transform ``coords`` to coord system ``to_sys``.""" + sympy_deprecation_warning( + """ + The CoordSystem.coord_tuple_transform_to() method is deprecated. + Use the CoordSystem.transform() method instead. + """, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + ) + + coords = Matrix(coords) + if self != to_sys: + with ignore_warnings(SymPyDeprecationWarning): + transf = self.transforms[to_sys] + coords = transf[1].subs(list(zip(transf[0], coords))) + return coords + + def jacobian(self, sys, coordinates=None): + """ + Return the jacobian matrix of a transformation on given coordinates. + If coordinates are not given, coordinate symbols of *self* are used. + + Parameters + ========== + + sys : CoordSystem + + coordinates : Any iterable, optional. + + Returns + ======= + + sympy.ImmutableDenseMatrix + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_p.jacobian(R2_r) + Matrix([ + [cos(theta), -rho*sin(theta)], + [sin(theta), rho*cos(theta)]]) + >>> R2_p.jacobian(R2_r, [1, 0]) + Matrix([ + [1, 0], + [0, 1]]) + + """ + result = self.transform(sys).jacobian(self.symbols) + if coordinates is not None: + result = result.subs(list(zip(self.symbols, coordinates))) + return result + jacobian_matrix = jacobian + + def jacobian_determinant(self, sys, coordinates=None): + """ + Return the jacobian determinant of a transformation on given + coordinates. If coordinates are not given, coordinate symbols of *self* + are used. + + Parameters + ========== + + sys : CoordSystem + + coordinates : Any iterable, optional. + + Returns + ======= + + sympy.Expr + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> R2_r.jacobian_determinant(R2_p) + 1/sqrt(x**2 + y**2) + >>> R2_r.jacobian_determinant(R2_p, [1, 0]) + 1 + + """ + return self.jacobian(sys, coordinates).det() + + + ########################################################################## + # Points + ########################################################################## + + def point(self, coords): + """Create a ``Point`` with coordinates given in this coord system.""" + return Point(self, coords) + + def point_to_coords(self, point): + """Calculate the coordinates of a point in this coord system.""" + return point.coords(self) + + ########################################################################## + # Base fields. + ########################################################################## + + def base_scalar(self, coord_index): + """Return ``BaseScalarField`` that takes a point and returns one of the coordinates.""" + return BaseScalarField(self, coord_index) + coord_function = base_scalar + + def base_scalars(self): + """Returns a list of all coordinate functions. + For more details see the ``base_scalar`` method of this class.""" + return [self.base_scalar(i) for i in range(self.dim)] + coord_functions = base_scalars + + def base_vector(self, coord_index): + """Return a basis vector field. + The basis vector field for this coordinate system. It is also an + operator on scalar fields.""" + return BaseVectorField(self, coord_index) + + def base_vectors(self): + """Returns a list of all base vectors. + For more details see the ``base_vector`` method of this class.""" + return [self.base_vector(i) for i in range(self.dim)] + + def base_oneform(self, coord_index): + """Return a basis 1-form field. + The basis one-form field for this coordinate system. It is also an + operator on vector fields.""" + return Differential(self.coord_function(coord_index)) + + def base_oneforms(self): + """Returns a list of all base oneforms. + For more details see the ``base_oneform`` method of this class.""" + return [self.base_oneform(i) for i in range(self.dim)] + + +class CoordinateSymbol(Symbol): + """A symbol which denotes an abstract value of i-th coordinate of + the coordinate system with given context. + + Explanation + =========== + + Each coordinates in coordinate system are represented by unique symbol, + such as x, y, z in Cartesian coordinate system. + + You may not construct this class directly. Instead, use `symbols` method + of CoordSystem. + + Parameters + ========== + + coord_sys : CoordSystem + + index : integer + + Examples + ======== + + >>> from sympy import symbols, Lambda, Matrix, sqrt, atan2, cos, sin + >>> from sympy.diffgeom import Manifold, Patch, CoordSystem + >>> m = Manifold('M', 2) + >>> p = Patch('P', m) + >>> x, y = symbols('x y', real=True) + >>> r, theta = symbols('r theta', nonnegative=True) + >>> relation_dict = { + ... ('Car2D', 'Pol'): Lambda((x, y), Matrix([sqrt(x**2 + y**2), atan2(y, x)])), + ... ('Pol', 'Car2D'): Lambda((r, theta), Matrix([r*cos(theta), r*sin(theta)])) + ... } + >>> Car2D = CoordSystem('Car2D', p, [x, y], relation_dict) + >>> Pol = CoordSystem('Pol', p, [r, theta], relation_dict) + >>> x, y = Car2D.symbols + + ``CoordinateSymbol`` contains its coordinate symbol and index. + + >>> x.name + 'x' + >>> x.coord_sys == Car2D + True + >>> x.index + 0 + >>> x.is_real + True + + You can transform ``CoordinateSymbol`` into other coordinate system using + ``rewrite()`` method. + + >>> x.rewrite(Pol) + r*cos(theta) + >>> sqrt(x**2 + y**2).rewrite(Pol).simplify() + r + + """ + def __new__(cls, coord_sys, index, **assumptions): + name = coord_sys.args[2][index].name + obj = super().__new__(cls, name, **assumptions) + obj.coord_sys = coord_sys + obj.index = index + return obj + + def __getnewargs__(self): + return (self.coord_sys, self.index) + + def _hashable_content(self): + return ( + self.coord_sys, self.index + ) + tuple(sorted(self.assumptions0.items())) + + def _eval_rewrite(self, rule, args, **hints): + if isinstance(rule, CoordSystem): + return rule.transform(self.coord_sys)[self.index] + return super()._eval_rewrite(rule, args, **hints) + + +class Point(Basic): + """Point defined in a coordinate system. + + Explanation + =========== + + Mathematically, point is defined in the manifold and does not have any coordinates + by itself. Coordinate system is what imbues the coordinates to the point by coordinate + chart. However, due to the difficulty of realizing such logic, you must supply + a coordinate system and coordinates to define a Point here. + + The usage of this object after its definition is independent of the + coordinate system that was used in order to define it, however due to + limitations in the simplification routines you can arrive at complicated + expressions if you use inappropriate coordinate systems. + + Parameters + ========== + + coord_sys : CoordSystem + + coords : list + The coordinates of the point. + + Examples + ======== + + >>> from sympy import pi + >>> from sympy.diffgeom import Point + >>> from sympy.diffgeom.rn import R2, R2_r, R2_p + >>> rho, theta = R2_p.symbols + + >>> p = Point(R2_p, [rho, 3*pi/4]) + + >>> p.manifold == R2 + True + + >>> p.coords() + Matrix([ + [ rho], + [3*pi/4]]) + >>> p.coords(R2_r) + Matrix([ + [-sqrt(2)*rho/2], + [ sqrt(2)*rho/2]]) + + """ + + def __new__(cls, coord_sys, coords, **kwargs): + coords = Matrix(coords) + obj = super().__new__(cls, coord_sys, coords) + obj._coord_sys = coord_sys + obj._coords = coords + return obj + + @property + def patch(self): + return self._coord_sys.patch + + @property + def manifold(self): + return self._coord_sys.manifold + + @property + def dim(self): + return self.manifold.dim + + def coords(self, sys=None): + """ + Coordinates of the point in given coordinate system. If coordinate system + is not passed, it returns the coordinates in the coordinate system in which + the point was defined. + """ + if sys is None: + return self._coords + else: + return self._coord_sys.transform(sys, self._coords) + + @property + def free_symbols(self): + return self._coords.free_symbols + + +class BaseScalarField(Expr): + """Base scalar field over a manifold for a given coordinate system. + + Explanation + =========== + + A scalar field takes a point as an argument and returns a scalar. + A base scalar field of a coordinate system takes a point and returns one of + the coordinates of that point in the coordinate system in question. + + To define a scalar field you need to choose the coordinate system and the + index of the coordinate. + + The use of the scalar field after its definition is independent of the + coordinate system in which it was defined, however due to limitations in + the simplification routines you may arrive at more complicated + expression if you use unappropriate coordinate systems. + You can build complicated scalar fields by just building up SymPy + expressions containing ``BaseScalarField`` instances. + + Parameters + ========== + + coord_sys : CoordSystem + + index : integer + + Examples + ======== + + >>> from sympy import Function, pi + >>> from sympy.diffgeom import BaseScalarField + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> rho, _ = R2_p.symbols + >>> point = R2_p.point([rho, 0]) + >>> fx, fy = R2_r.base_scalars() + >>> ftheta = BaseScalarField(R2_r, 1) + + >>> fx(point) + rho + >>> fy(point) + 0 + + >>> (fx**2+fy**2).rcall(point) + rho**2 + + >>> g = Function('g') + >>> fg = g(ftheta-pi) + >>> fg.rcall(point) + g(-pi) + + """ + + is_commutative = True + + def __new__(cls, coord_sys, index, **kwargs): + index = _sympify(index) + obj = super().__new__(cls, coord_sys, index) + obj._coord_sys = coord_sys + obj._index = index + return obj + + @property + def coord_sys(self): + return self.args[0] + + @property + def index(self): + return self.args[1] + + @property + def patch(self): + return self.coord_sys.patch + + @property + def manifold(self): + return self.coord_sys.manifold + + @property + def dim(self): + return self.manifold.dim + + def __call__(self, *args): + """Evaluating the field at a point or doing nothing. + If the argument is a ``Point`` instance, the field is evaluated at that + point. The field is returned itself if the argument is any other + object. It is so in order to have working recursive calling mechanics + for all fields (check the ``__call__`` method of ``Expr``). + """ + point = args[0] + if len(args) != 1 or not isinstance(point, Point): + return self + coords = point.coords(self._coord_sys) + # XXX Calling doit is necessary with all the Subs expressions + # XXX Calling simplify is necessary with all the trig expressions + return simplify(coords[self._index]).doit() + + # XXX Workaround for limitations on the content of args + free_symbols: set[Any] = set() + + +class BaseVectorField(Expr): + r"""Base vector field over a manifold for a given coordinate system. + + Explanation + =========== + + A vector field is an operator taking a scalar field and returning a + directional derivative (which is also a scalar field). + A base vector field is the same type of operator, however the derivation is + specifically done with respect to a chosen coordinate. + + To define a base vector field you need to choose the coordinate system and + the index of the coordinate. + + The use of the vector field after its definition is independent of the + coordinate system in which it was defined, however due to limitations in the + simplification routines you may arrive at more complicated expression if you + use unappropriate coordinate systems. + + Parameters + ========== + coord_sys : CoordSystem + + index : integer + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.diffgeom.rn import R2_p, R2_r + >>> from sympy.diffgeom import BaseVectorField + >>> from sympy import pprint + + >>> x, y = R2_r.symbols + >>> rho, theta = R2_p.symbols + >>> fx, fy = R2_r.base_scalars() + >>> point_p = R2_p.point([rho, theta]) + >>> point_r = R2_r.point([x, y]) + + >>> g = Function('g') + >>> s_field = g(fx, fy) + + >>> v = BaseVectorField(R2_r, 1) + >>> pprint(v(s_field)) + / d \| + |---(g(x, xi))|| + \dxi /|xi=y + >>> pprint(v(s_field).rcall(point_r).doit()) + d + --(g(x, y)) + dy + >>> pprint(v(s_field).rcall(point_p)) + / d \| + |---(g(rho*cos(theta), xi))|| + \dxi /|xi=rho*sin(theta) + + """ + + is_commutative = False + + def __new__(cls, coord_sys, index, **kwargs): + index = _sympify(index) + obj = super().__new__(cls, coord_sys, index) + obj._coord_sys = coord_sys + obj._index = index + return obj + + @property + def coord_sys(self): + return self.args[0] + + @property + def index(self): + return self.args[1] + + @property + def patch(self): + return self.coord_sys.patch + + @property + def manifold(self): + return self.coord_sys.manifold + + @property + def dim(self): + return self.manifold.dim + + def __call__(self, scalar_field): + """Apply on a scalar field. + The action of a vector field on a scalar field is a directional + differentiation. + If the argument is not a scalar field an error is raised. + """ + if covariant_order(scalar_field) or contravariant_order(scalar_field): + raise ValueError('Only scalar fields can be supplied as arguments to vector fields.') + + if scalar_field is None: + return self + + base_scalars = list(scalar_field.atoms(BaseScalarField)) + + # First step: e_x(x+r**2) -> e_x(x) + 2*r*e_x(r) + d_var = self._coord_sys._dummy + # TODO: you need a real dummy function for the next line + d_funcs = [Function('_#_%s' % i)(d_var) for i, + b in enumerate(base_scalars)] + d_result = scalar_field.subs(list(zip(base_scalars, d_funcs))) + d_result = d_result.diff(d_var) + + # Second step: e_x(x) -> 1 and e_x(r) -> cos(atan2(x, y)) + coords = self._coord_sys.symbols + d_funcs_deriv = [f.diff(d_var) for f in d_funcs] + d_funcs_deriv_sub = [] + for b in base_scalars: + jac = self._coord_sys.jacobian(b._coord_sys, coords) + d_funcs_deriv_sub.append(jac[b._index, self._index]) + d_result = d_result.subs(list(zip(d_funcs_deriv, d_funcs_deriv_sub))) + + # Remove the dummies + result = d_result.subs(list(zip(d_funcs, base_scalars))) + result = result.subs(list(zip(coords, self._coord_sys.coord_functions()))) + return result.doit() + + +def _find_coords(expr): + # Finds CoordinateSystems existing in expr + fields = expr.atoms(BaseScalarField, BaseVectorField) + return {f._coord_sys for f in fields} + + +class Commutator(Expr): + r"""Commutator of two vector fields. + + Explanation + =========== + + The commutator of two vector fields `v_1` and `v_2` is defined as the + vector field `[v_1, v_2]` that evaluated on each scalar field `f` is equal + to `v_1(v_2(f)) - v_2(v_1(f))`. + + Examples + ======== + + + >>> from sympy.diffgeom.rn import R2_p, R2_r + >>> from sympy.diffgeom import Commutator + >>> from sympy import simplify + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> e_r = R2_p.base_vector(0) + + >>> c_xy = Commutator(e_x, e_y) + >>> c_xr = Commutator(e_x, e_r) + >>> c_xy + 0 + + Unfortunately, the current code is not able to compute everything: + + >>> c_xr + Commutator(e_x, e_rho) + >>> simplify(c_xr(fy**2)) + -2*cos(theta)*y**2/(x**2 + y**2) + + """ + def __new__(cls, v1, v2): + if (covariant_order(v1) or contravariant_order(v1) != 1 + or covariant_order(v2) or contravariant_order(v2) != 1): + raise ValueError( + 'Only commutators of vector fields are supported.') + if v1 == v2: + return S.Zero + coord_sys = set().union(*[_find_coords(v) for v in (v1, v2)]) + if len(coord_sys) == 1: + # Only one coordinate systems is used, hence it is easy enough to + # actually evaluate the commutator. + if all(isinstance(v, BaseVectorField) for v in (v1, v2)): + return S.Zero + bases_1, bases_2 = [list(v.atoms(BaseVectorField)) + for v in (v1, v2)] + coeffs_1 = [v1.expand().coeff(b) for b in bases_1] + coeffs_2 = [v2.expand().coeff(b) for b in bases_2] + res = 0 + for c1, b1 in zip(coeffs_1, bases_1): + for c2, b2 in zip(coeffs_2, bases_2): + res += c1*b1(c2)*b2 - c2*b2(c1)*b1 + return res + else: + obj = super().__new__(cls, v1, v2) + obj._v1 = v1 # deprecated assignment + obj._v2 = v2 # deprecated assignment + return obj + + @property + def v1(self): + return self.args[0] + + @property + def v2(self): + return self.args[1] + + def __call__(self, scalar_field): + """Apply on a scalar field. + If the argument is not a scalar field an error is raised. + """ + return self.v1(self.v2(scalar_field)) - self.v2(self.v1(scalar_field)) + + +class Differential(Expr): + r"""Return the differential (exterior derivative) of a form field. + + Explanation + =========== + + The differential of a form (i.e. the exterior derivative) has a complicated + definition in the general case. + The differential `df` of the 0-form `f` is defined for any vector field `v` + as `df(v) = v(f)`. + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import Differential + >>> from sympy import pprint + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> g = Function('g') + >>> s_field = g(fx, fy) + >>> dg = Differential(s_field) + + >>> dg + d(g(x, y)) + >>> pprint(dg(e_x)) + / d \| + |---(g(xi, y))|| + \dxi /|xi=x + >>> pprint(dg(e_y)) + / d \| + |---(g(x, xi))|| + \dxi /|xi=y + + Applying the exterior derivative operator twice always results in: + + >>> Differential(dg) + 0 + """ + + is_commutative = False + + def __new__(cls, form_field): + if contravariant_order(form_field): + raise ValueError( + 'A vector field was supplied as an argument to Differential.') + if isinstance(form_field, Differential): + return S.Zero + else: + obj = super().__new__(cls, form_field) + obj._form_field = form_field # deprecated assignment + return obj + + @property + def form_field(self): + return self.args[0] + + def __call__(self, *vector_fields): + """Apply on a list of vector_fields. + + Explanation + =========== + + If the number of vector fields supplied is not equal to 1 + the order of + the form field inside the differential the result is undefined. + + For 1-forms (i.e. differentials of scalar fields) the evaluation is + done as `df(v)=v(f)`. However if `v` is ``None`` instead of a vector + field, the differential is returned unchanged. This is done in order to + permit partial contractions for higher forms. + + In the general case the evaluation is done by applying the form field + inside the differential on a list with one less elements than the number + of elements in the original list. Lowering the number of vector fields + is achieved through replacing each pair of fields by their + commutator. + + If the arguments are not vectors or ``None``s an error is raised. + """ + if any((contravariant_order(a) != 1 or covariant_order(a)) and a is not None + for a in vector_fields): + raise ValueError('The arguments supplied to Differential should be vector fields or Nones.') + k = len(vector_fields) + if k == 1: + if vector_fields[0]: + return vector_fields[0].rcall(self._form_field) + return self + else: + # For higher form it is more complicated: + # Invariant formula: + # https://en.wikipedia.org/wiki/Exterior_derivative#Invariant_formula + # df(v1, ... vn) = +/- vi(f(v1..no i..vn)) + # +/- f([vi,vj],v1..no i, no j..vn) + f = self._form_field + v = vector_fields + ret = 0 + for i in range(k): + t = v[i].rcall(f.rcall(*v[:i] + v[i + 1:])) + ret += (-1)**i*t + for j in range(i + 1, k): + c = Commutator(v[i], v[j]) + if c: # TODO this is ugly - the Commutator can be Zero and + # this causes the next line to fail + t = f.rcall(*(c,) + v[:i] + v[i + 1:j] + v[j + 1:]) + ret += (-1)**(i + j)*t + return ret + + +class TensorProduct(Expr): + """Tensor product of forms. + + Explanation + =========== + + The tensor product permits the creation of multilinear functionals (i.e. + higher order tensors) out of lower order fields (e.g. 1-forms and vector + fields). However, the higher tensors thus created lack the interesting + features provided by the other type of product, the wedge product, namely + they are not antisymmetric and hence are not form fields. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import TensorProduct + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> TensorProduct(dx, dy)(e_x, e_y) + 1 + >>> TensorProduct(dx, dy)(e_y, e_x) + 0 + >>> TensorProduct(dx, fx*dy)(fx*e_x, e_y) + x**2 + >>> TensorProduct(e_x, e_y)(fx**2, fy**2) + 4*x*y + >>> TensorProduct(e_y, dx)(fy) + dx + + You can nest tensor products. + + >>> tp1 = TensorProduct(dx, dy) + >>> TensorProduct(tp1, dx)(e_x, e_y, e_x) + 1 + + You can make partial contraction for instance when 'raising an index'. + Putting ``None`` in the second argument of ``rcall`` means that the + respective position in the tensor product is left as it is. + + >>> TP = TensorProduct + >>> metric = TP(dx, dx) + 3*TP(dy, dy) + >>> metric.rcall(e_y, None) + 3*dy + + Or automatically pad the args with ``None`` without specifying them. + + >>> metric.rcall(e_y) + 3*dy + + """ + def __new__(cls, *args): + scalar = Mul(*[m for m in args if covariant_order(m) + contravariant_order(m) == 0]) + multifields = [m for m in args if covariant_order(m) + contravariant_order(m)] + if multifields: + if len(multifields) == 1: + return scalar*multifields[0] + return scalar*super().__new__(cls, *multifields) + else: + return scalar + + def __call__(self, *fields): + """Apply on a list of fields. + + If the number of input fields supplied is not equal to the order of + the tensor product field, the list of arguments is padded with ``None``'s. + + The list of arguments is divided in sublists depending on the order of + the forms inside the tensor product. The sublists are provided as + arguments to these forms and the resulting expressions are given to the + constructor of ``TensorProduct``. + + """ + tot_order = covariant_order(self) + contravariant_order(self) + tot_args = len(fields) + if tot_args != tot_order: + fields = list(fields) + [None]*(tot_order - tot_args) + orders = [covariant_order(f) + contravariant_order(f) for f in self._args] + indices = [sum(orders[:i + 1]) for i in range(len(orders) - 1)] + fields = [fields[i:j] for i, j in zip([0] + indices, indices + [None])] + multipliers = [t[0].rcall(*t[1]) for t in zip(self._args, fields)] + return TensorProduct(*multipliers) + + +class WedgeProduct(TensorProduct): + """Wedge product of forms. + + Explanation + =========== + + In the context of integration only completely antisymmetric forms make + sense. The wedge product permits the creation of such forms. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import WedgeProduct + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> WedgeProduct(dx, dy)(e_x, e_y) + 1 + >>> WedgeProduct(dx, dy)(e_y, e_x) + -1 + >>> WedgeProduct(dx, fx*dy)(fx*e_x, e_y) + x**2 + >>> WedgeProduct(e_x, e_y)(fy, None) + -e_x + + You can nest wedge products. + + >>> wp1 = WedgeProduct(dx, dy) + >>> WedgeProduct(wp1, dx)(e_x, e_y, e_x) + 0 + + """ + # TODO the calculation of signatures is slow + # TODO you do not need all these permutations (neither the prefactor) + def __call__(self, *fields): + """Apply on a list of vector_fields. + The expression is rewritten internally in terms of tensor products and evaluated.""" + orders = (covariant_order(e) + contravariant_order(e) for e in self.args) + mul = 1/Mul(*(factorial(o) for o in orders)) + perms = permutations(fields) + perms_par = (Permutation( + p).signature() for p in permutations(range(len(fields)))) + tensor_prod = TensorProduct(*self.args) + return mul*Add(*[tensor_prod(*p[0])*p[1] for p in zip(perms, perms_par)]) + + +class LieDerivative(Expr): + """Lie derivative with respect to a vector field. + + Explanation + =========== + + The transport operator that defines the Lie derivative is the pushforward of + the field to be derived along the integral curve of the field with respect + to which one derives. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r, R2_p + >>> from sympy.diffgeom import (LieDerivative, TensorProduct) + + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> e_rho, e_theta = R2_p.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> LieDerivative(e_x, fy) + 0 + >>> LieDerivative(e_x, fx) + 1 + >>> LieDerivative(e_x, e_x) + 0 + + The Lie derivative of a tensor field by another tensor field is equal to + their commutator: + + >>> LieDerivative(e_x, e_rho) + Commutator(e_x, e_rho) + >>> LieDerivative(e_x + e_y, fx) + 1 + + >>> tp = TensorProduct(dx, dy) + >>> LieDerivative(e_x, tp) + LieDerivative(e_x, TensorProduct(dx, dy)) + >>> LieDerivative(e_x, tp) + LieDerivative(e_x, TensorProduct(dx, dy)) + + """ + def __new__(cls, v_field, expr): + expr_form_ord = covariant_order(expr) + if contravariant_order(v_field) != 1 or covariant_order(v_field): + raise ValueError('Lie derivatives are defined only with respect to' + ' vector fields. The supplied argument was not a ' + 'vector field.') + if expr_form_ord > 0: + obj = super().__new__(cls, v_field, expr) + # deprecated assignments + obj._v_field = v_field + obj._expr = expr + return obj + if expr.atoms(BaseVectorField): + return Commutator(v_field, expr) + else: + return v_field.rcall(expr) + + @property + def v_field(self): + return self.args[0] + + @property + def expr(self): + return self.args[1] + + def __call__(self, *args): + v = self.v_field + expr = self.expr + lead_term = v(expr(*args)) + rest = Add(*[Mul(*args[:i] + (Commutator(v, args[i]),) + args[i + 1:]) + for i in range(len(args))]) + return lead_term - rest + + +class BaseCovarDerivativeOp(Expr): + """Covariant derivative operator with respect to a base vector. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import BaseCovarDerivativeOp + >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct + + >>> TP = TensorProduct + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + + >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy)) + >>> ch + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> cvd = BaseCovarDerivativeOp(R2_r, 0, ch) + >>> cvd(fx) + 1 + >>> cvd(fx*e_x) + e_x + """ + + def __new__(cls, coord_sys, index, christoffel): + index = _sympify(index) + christoffel = ImmutableDenseNDimArray(christoffel) + obj = super().__new__(cls, coord_sys, index, christoffel) + # deprecated assignments + obj._coord_sys = coord_sys + obj._index = index + obj._christoffel = christoffel + return obj + + @property + def coord_sys(self): + return self.args[0] + + @property + def index(self): + return self.args[1] + + @property + def christoffel(self): + return self.args[2] + + def __call__(self, field): + """Apply on a scalar field. + + The action of a vector field on a scalar field is a directional + differentiation. + If the argument is not a scalar field the behaviour is undefined. + """ + if covariant_order(field) != 0: + raise NotImplementedError() + + field = vectors_in_basis(field, self._coord_sys) + + wrt_vector = self._coord_sys.base_vector(self._index) + wrt_scalar = self._coord_sys.coord_function(self._index) + vectors = list(field.atoms(BaseVectorField)) + + # First step: replace all vectors with something susceptible to + # derivation and do the derivation + # TODO: you need a real dummy function for the next line + d_funcs = [Function('_#_%s' % i)(wrt_scalar) for i, + b in enumerate(vectors)] + d_result = field.subs(list(zip(vectors, d_funcs))) + d_result = wrt_vector(d_result) + + # Second step: backsubstitute the vectors in + d_result = d_result.subs(list(zip(d_funcs, vectors))) + + # Third step: evaluate the derivatives of the vectors + derivs = [] + for v in vectors: + d = Add(*[(self._christoffel[k, wrt_vector._index, v._index] + *v._coord_sys.base_vector(k)) + for k in range(v._coord_sys.dim)]) + derivs.append(d) + to_subs = [wrt_vector(d) for d in d_funcs] + # XXX: This substitution can fail when there are Dummy symbols and the + # cache is disabled: https://github.com/sympy/sympy/issues/17794 + result = d_result.subs(list(zip(to_subs, derivs))) + + # Remove the dummies + result = result.subs(list(zip(d_funcs, vectors))) + return result.doit() + + +class CovarDerivativeOp(Expr): + """Covariant derivative operator. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2_r + >>> from sympy.diffgeom import CovarDerivativeOp + >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct + >>> TP = TensorProduct + >>> fx, fy = R2_r.base_scalars() + >>> e_x, e_y = R2_r.base_vectors() + >>> dx, dy = R2_r.base_oneforms() + >>> ch = metric_to_Christoffel_2nd(TP(dx, dx) + TP(dy, dy)) + + >>> ch + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> cvd = CovarDerivativeOp(fx*e_x, ch) + >>> cvd(fx) + x + >>> cvd(fx*e_x) + x*e_x + + """ + + def __new__(cls, wrt, christoffel): + if len({v._coord_sys for v in wrt.atoms(BaseVectorField)}) > 1: + raise NotImplementedError() + if contravariant_order(wrt) != 1 or covariant_order(wrt): + raise ValueError('Covariant derivatives are defined only with ' + 'respect to vector fields. The supplied argument ' + 'was not a vector field.') + christoffel = ImmutableDenseNDimArray(christoffel) + obj = super().__new__(cls, wrt, christoffel) + # deprecated assignments + obj._wrt = wrt + obj._christoffel = christoffel + return obj + + @property + def wrt(self): + return self.args[0] + + @property + def christoffel(self): + return self.args[1] + + def __call__(self, field): + vectors = list(self._wrt.atoms(BaseVectorField)) + base_ops = [BaseCovarDerivativeOp(v._coord_sys, v._index, self._christoffel) + for v in vectors] + return self._wrt.subs(list(zip(vectors, base_ops))).rcall(field) + + +############################################################################### +# Integral curves on vector fields +############################################################################### +def intcurve_series(vector_field, param, start_point, n=6, coord_sys=None, coeffs=False): + r"""Return the series expansion for an integral curve of the field. + + Explanation + =========== + + Integral curve is a function `\gamma` taking a parameter in `R` to a point + in the manifold. It verifies the equation: + + `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)` + + where the given ``vector_field`` is denoted as `V`. This holds for any + value `t` for the parameter and any scalar field `f`. + + This equation can also be decomposed of a basis of coordinate functions + `V(f_i)\big(\gamma(t)\big) = \frac{d}{dt}f_i\big(\gamma(t)\big) \quad \forall i` + + This function returns a series expansion of `\gamma(t)` in terms of the + coordinate system ``coord_sys``. The equations and expansions are necessarily + done in coordinate-system-dependent way as there is no other way to + represent movement between points on the manifold (i.e. there is no such + thing as a difference of points for a general manifold). + + Parameters + ========== + vector_field + the vector field for which an integral curve will be given + + param + the argument of the function `\gamma` from R to the curve + + start_point + the point which corresponds to `\gamma(0)` + + n + the order to which to expand + + coord_sys + the coordinate system in which to expand + coeffs (default False) - if True return a list of elements of the expansion + + Examples + ======== + + Use the predefined R2 manifold: + + >>> from sympy.abc import t, x, y + >>> from sympy.diffgeom.rn import R2_p, R2_r + >>> from sympy.diffgeom import intcurve_series + + Specify a starting point and a vector field: + + >>> start_point = R2_r.point([x, y]) + >>> vector_field = R2_r.e_x + + Calculate the series: + + >>> intcurve_series(vector_field, t, start_point, n=3) + Matrix([ + [t + x], + [ y]]) + + Or get the elements of the expansion in a list: + + >>> series = intcurve_series(vector_field, t, start_point, n=3, coeffs=True) + >>> series[0] + Matrix([ + [x], + [y]]) + >>> series[1] + Matrix([ + [t], + [0]]) + >>> series[2] + Matrix([ + [0], + [0]]) + + The series in the polar coordinate system: + + >>> series = intcurve_series(vector_field, t, start_point, + ... n=3, coord_sys=R2_p, coeffs=True) + >>> series[0] + Matrix([ + [sqrt(x**2 + y**2)], + [ atan2(y, x)]]) + >>> series[1] + Matrix([ + [t*x/sqrt(x**2 + y**2)], + [ -t*y/(x**2 + y**2)]]) + >>> series[2] + Matrix([ + [t**2*(-x**2/(x**2 + y**2)**(3/2) + 1/sqrt(x**2 + y**2))/2], + [ t**2*x*y/(x**2 + y**2)**2]]) + + See Also + ======== + + intcurve_diffequ + + """ + if contravariant_order(vector_field) != 1 or covariant_order(vector_field): + raise ValueError('The supplied field was not a vector field.') + + def iter_vfield(scalar_field, i): + """Return ``vector_field`` called `i` times on ``scalar_field``.""" + return reduce(lambda s, v: v.rcall(s), [vector_field, ]*i, scalar_field) + + def taylor_terms_per_coord(coord_function): + """Return the series for one of the coordinates.""" + return [param**i*iter_vfield(coord_function, i).rcall(start_point)/factorial(i) + for i in range(n)] + coord_sys = coord_sys if coord_sys else start_point._coord_sys + coord_functions = coord_sys.coord_functions() + taylor_terms = [taylor_terms_per_coord(f) for f in coord_functions] + if coeffs: + return [Matrix(t) for t in zip(*taylor_terms)] + else: + return Matrix([sum(c) for c in taylor_terms]) + + +def intcurve_diffequ(vector_field, param, start_point, coord_sys=None): + r"""Return the differential equation for an integral curve of the field. + + Explanation + =========== + + Integral curve is a function `\gamma` taking a parameter in `R` to a point + in the manifold. It verifies the equation: + + `V(f)\big(\gamma(t)\big) = \frac{d}{dt}f\big(\gamma(t)\big)` + + where the given ``vector_field`` is denoted as `V`. This holds for any + value `t` for the parameter and any scalar field `f`. + + This function returns the differential equation of `\gamma(t)` in terms of the + coordinate system ``coord_sys``. The equations and expansions are necessarily + done in coordinate-system-dependent way as there is no other way to + represent movement between points on the manifold (i.e. there is no such + thing as a difference of points for a general manifold). + + Parameters + ========== + + vector_field + the vector field for which an integral curve will be given + + param + the argument of the function `\gamma` from R to the curve + + start_point + the point which corresponds to `\gamma(0)` + + coord_sys + the coordinate system in which to give the equations + + Returns + ======= + + a tuple of (equations, initial conditions) + + Examples + ======== + + Use the predefined R2 manifold: + + >>> from sympy.abc import t + >>> from sympy.diffgeom.rn import R2, R2_p, R2_r + >>> from sympy.diffgeom import intcurve_diffequ + + Specify a starting point and a vector field: + + >>> start_point = R2_r.point([0, 1]) + >>> vector_field = -R2.y*R2.e_x + R2.x*R2.e_y + + Get the equation: + + >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point) + >>> equations + [f_1(t) + Derivative(f_0(t), t), -f_0(t) + Derivative(f_1(t), t)] + >>> init_cond + [f_0(0), f_1(0) - 1] + + The series in the polar coordinate system: + + >>> equations, init_cond = intcurve_diffequ(vector_field, t, start_point, R2_p) + >>> equations + [Derivative(f_0(t), t), Derivative(f_1(t), t) - 1] + >>> init_cond + [f_0(0) - 1, f_1(0) - pi/2] + + See Also + ======== + + intcurve_series + + """ + if contravariant_order(vector_field) != 1 or covariant_order(vector_field): + raise ValueError('The supplied field was not a vector field.') + coord_sys = coord_sys if coord_sys else start_point._coord_sys + gammas = [Function('f_%d' % i)(param) for i in range( + start_point._coord_sys.dim)] + arbitrary_p = Point(coord_sys, gammas) + coord_functions = coord_sys.coord_functions() + equations = [simplify(diff(cf.rcall(arbitrary_p), param) - vector_field.rcall(cf).rcall(arbitrary_p)) + for cf in coord_functions] + init_cond = [simplify(cf.rcall(arbitrary_p).subs(param, 0) - cf.rcall(start_point)) + for cf in coord_functions] + return equations, init_cond + + +############################################################################### +# Helpers +############################################################################### +def dummyfy(args, exprs): + # TODO Is this a good idea? + d_args = Matrix([s.as_dummy() for s in args]) + reps = dict(zip(args, d_args)) + d_exprs = Matrix([_sympify(expr).subs(reps) for expr in exprs]) + return d_args, d_exprs + +############################################################################### +# Helpers +############################################################################### +def contravariant_order(expr, _strict=False): + """Return the contravariant order of an expression. + + Examples + ======== + + >>> from sympy.diffgeom import contravariant_order + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.abc import a + + >>> contravariant_order(a) + 0 + >>> contravariant_order(a*R2.x + 2) + 0 + >>> contravariant_order(a*R2.x*R2.e_y + R2.e_x) + 1 + + """ + # TODO move some of this to class methods. + # TODO rewrite using the .as_blah_blah methods + if isinstance(expr, Add): + orders = [contravariant_order(e) for e in expr.args] + if len(set(orders)) != 1: + raise ValueError('Misformed expression containing contravariant fields of varying order.') + return orders[0] + elif isinstance(expr, Mul): + orders = [contravariant_order(e) for e in expr.args] + not_zero = [o for o in orders if o != 0] + if len(not_zero) > 1: + raise ValueError('Misformed expression containing multiplication between vectors.') + return 0 if not not_zero else not_zero[0] + elif isinstance(expr, Pow): + if covariant_order(expr.base) or covariant_order(expr.exp): + raise ValueError( + 'Misformed expression containing a power of a vector.') + return 0 + elif isinstance(expr, BaseVectorField): + return 1 + elif isinstance(expr, TensorProduct): + return sum(contravariant_order(a) for a in expr.args) + elif not _strict or expr.atoms(BaseScalarField): + return 0 + else: # If it does not contain anything related to the diffgeom module and it is _strict + return -1 + + +def covariant_order(expr, _strict=False): + """Return the covariant order of an expression. + + Examples + ======== + + >>> from sympy.diffgeom import covariant_order + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.abc import a + + >>> covariant_order(a) + 0 + >>> covariant_order(a*R2.x + 2) + 0 + >>> covariant_order(a*R2.x*R2.dy + R2.dx) + 1 + + """ + # TODO move some of this to class methods. + # TODO rewrite using the .as_blah_blah methods + if isinstance(expr, Add): + orders = [covariant_order(e) for e in expr.args] + if len(set(orders)) != 1: + raise ValueError('Misformed expression containing form fields of varying order.') + return orders[0] + elif isinstance(expr, Mul): + orders = [covariant_order(e) for e in expr.args] + not_zero = [o for o in orders if o != 0] + if len(not_zero) > 1: + raise ValueError('Misformed expression containing multiplication between forms.') + return 0 if not not_zero else not_zero[0] + elif isinstance(expr, Pow): + if covariant_order(expr.base) or covariant_order(expr.exp): + raise ValueError( + 'Misformed expression containing a power of a form.') + return 0 + elif isinstance(expr, Differential): + return covariant_order(*expr.args) + 1 + elif isinstance(expr, TensorProduct): + return sum(covariant_order(a) for a in expr.args) + elif not _strict or expr.atoms(BaseScalarField): + return 0 + else: # If it does not contain anything related to the diffgeom module and it is _strict + return -1 + + +############################################################################### +# Coordinate transformation functions +############################################################################### +def vectors_in_basis(expr, to_sys): + """Transform all base vectors in base vectors of a specified coord basis. + While the new base vectors are in the new coordinate system basis, any + coefficients are kept in the old system. + + Examples + ======== + + >>> from sympy.diffgeom import vectors_in_basis + >>> from sympy.diffgeom.rn import R2_r, R2_p + + >>> vectors_in_basis(R2_r.e_x, R2_p) + -y*e_theta/(x**2 + y**2) + x*e_rho/sqrt(x**2 + y**2) + >>> vectors_in_basis(R2_p.e_r, R2_r) + sin(theta)*e_y + cos(theta)*e_x + + """ + vectors = list(expr.atoms(BaseVectorField)) + new_vectors = [] + for v in vectors: + cs = v._coord_sys + jac = cs.jacobian(to_sys, cs.coord_functions()) + new = (jac.T*Matrix(to_sys.base_vectors()))[v._index] + new_vectors.append(new) + return expr.subs(list(zip(vectors, new_vectors))) + + +############################################################################### +# Coordinate-dependent functions +############################################################################### +def twoform_to_matrix(expr): + """Return the matrix representing the twoform. + + For the twoform `w` return the matrix `M` such that `M[i,j]=w(e_i, e_j)`, + where `e_i` is the i-th base vector field for the coordinate system in + which the expression of `w` is given. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import twoform_to_matrix, TensorProduct + >>> TP = TensorProduct + + >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + Matrix([ + [1, 0], + [0, 1]]) + >>> twoform_to_matrix(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + Matrix([ + [x, 0], + [0, 1]]) + >>> twoform_to_matrix(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy) - TP(R2.dx, R2.dy)/2) + Matrix([ + [ 1, 0], + [-1/2, 1]]) + + """ + if covariant_order(expr) != 2 or contravariant_order(expr): + raise ValueError('The input expression is not a two-form.') + coord_sys = _find_coords(expr) + if len(coord_sys) != 1: + raise ValueError('The input expression concerns more than one ' + 'coordinate systems, hence there is no unambiguous ' + 'way to choose a coordinate system for the matrix.') + coord_sys = coord_sys.pop() + vectors = coord_sys.base_vectors() + expr = expr.expand() + matrix_content = [[expr.rcall(v1, v2) for v1 in vectors] + for v2 in vectors] + return Matrix(matrix_content) + + +def metric_to_Christoffel_1st(expr): + """Return the nested list of Christoffel symbols for the given metric. + This returns the Christoffel symbol of first kind that represents the + Levi-Civita connection for the given metric. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Christoffel_1st, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Christoffel_1st(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> metric_to_Christoffel_1st(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[1/2, 0], [0, 0]], [[0, 0], [0, 0]]] + + """ + matrix = twoform_to_matrix(expr) + if not matrix.is_symmetric(): + raise ValueError( + 'The two-form representing the metric is not symmetric.') + coord_sys = _find_coords(expr).pop() + deriv_matrices = [matrix.applyfunc(d) for d in coord_sys.base_vectors()] + indices = list(range(coord_sys.dim)) + christoffel = [[[(deriv_matrices[k][i, j] + deriv_matrices[j][i, k] - deriv_matrices[i][j, k])/2 + for k in indices] + for j in indices] + for i in indices] + return ImmutableDenseNDimArray(christoffel) + + +def metric_to_Christoffel_2nd(expr): + """Return the nested list of Christoffel symbols for the given metric. + This returns the Christoffel symbol of second kind that represents the + Levi-Civita connection for the given metric. + + Examples + ======== + + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Christoffel_2nd, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Christoffel_2nd(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[0, 0], [0, 0]], [[0, 0], [0, 0]]] + >>> metric_to_Christoffel_2nd(R2.x*TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[1/(2*x), 0], [0, 0]], [[0, 0], [0, 0]]] + + """ + ch_1st = metric_to_Christoffel_1st(expr) + coord_sys = _find_coords(expr).pop() + indices = list(range(coord_sys.dim)) + # XXX workaround, inverting a matrix does not work if it contains non + # symbols + #matrix = twoform_to_matrix(expr).inv() + matrix = twoform_to_matrix(expr) + s_fields = set() + for e in matrix: + s_fields.update(e.atoms(BaseScalarField)) + s_fields = list(s_fields) + dums = coord_sys.symbols + matrix = matrix.subs(list(zip(s_fields, dums))).inv().subs(list(zip(dums, s_fields))) + # XXX end of workaround + christoffel = [[[Add(*[matrix[i, l]*ch_1st[l, j, k] for l in indices]) + for k in indices] + for j in indices] + for i in indices] + return ImmutableDenseNDimArray(christoffel) + + +def metric_to_Riemann_components(expr): + """Return the components of the Riemann tensor expressed in a given basis. + + Given a metric it calculates the components of the Riemann tensor in the + canonical basis of the coordinate system in which the metric expression is + given. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Riemann_components, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Riemann_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[[[0, 0], [0, 0]], [[0, 0], [0, 0]]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]] + >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \ + R2.r**2*TP(R2.dtheta, R2.dtheta) + >>> non_trivial_metric + exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta) + >>> riemann = metric_to_Riemann_components(non_trivial_metric) + >>> riemann[0, :, :, :] + [[[0, 0], [0, 0]], [[0, exp(-2*rho)*rho], [-exp(-2*rho)*rho, 0]]] + >>> riemann[1, :, :, :] + [[[0, -1/rho], [1/rho, 0]], [[0, 0], [0, 0]]] + + """ + ch_2nd = metric_to_Christoffel_2nd(expr) + coord_sys = _find_coords(expr).pop() + indices = list(range(coord_sys.dim)) + deriv_ch = [[[[d(ch_2nd[i, j, k]) + for d in coord_sys.base_vectors()] + for k in indices] + for j in indices] + for i in indices] + riemann_a = [[[[deriv_ch[rho][sig][nu][mu] - deriv_ch[rho][sig][mu][nu] + for nu in indices] + for mu in indices] + for sig in indices] + for rho in indices] + riemann_b = [[[[Add(*[ch_2nd[rho, l, mu]*ch_2nd[l, sig, nu] - ch_2nd[rho, l, nu]*ch_2nd[l, sig, mu] for l in indices]) + for nu in indices] + for mu in indices] + for sig in indices] + for rho in indices] + riemann = [[[[riemann_a[rho][sig][mu][nu] + riemann_b[rho][sig][mu][nu] + for nu in indices] + for mu in indices] + for sig in indices] + for rho in indices] + return ImmutableDenseNDimArray(riemann) + + +def metric_to_Ricci_components(expr): + + """Return the components of the Ricci tensor expressed in a given basis. + + Given a metric it calculates the components of the Ricci tensor in the + canonical basis of the coordinate system in which the metric expression is + given. + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.diffgeom.rn import R2 + >>> from sympy.diffgeom import metric_to_Ricci_components, TensorProduct + >>> TP = TensorProduct + + >>> metric_to_Ricci_components(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + [[0, 0], [0, 0]] + >>> non_trivial_metric = exp(2*R2.r)*TP(R2.dr, R2.dr) + \ + R2.r**2*TP(R2.dtheta, R2.dtheta) + >>> non_trivial_metric + exp(2*rho)*TensorProduct(drho, drho) + rho**2*TensorProduct(dtheta, dtheta) + >>> metric_to_Ricci_components(non_trivial_metric) + [[1/rho, 0], [0, exp(-2*rho)*rho]] + + """ + riemann = metric_to_Riemann_components(expr) + coord_sys = _find_coords(expr).pop() + indices = list(range(coord_sys.dim)) + ricci = [[Add(*[riemann[k, i, k, j] for k in indices]) + for j in indices] + for i in indices] + return ImmutableDenseNDimArray(ricci) + +############################################################################### +# Classes for deprecation +############################################################################### + +class _deprecated_container: + # This class gives deprecation warning. + # When deprecated features are completely deleted, this should be removed as well. + # See https://github.com/sympy/sympy/pull/19368 + def __init__(self, message, data): + super().__init__(data) + self.message = message + + def warn(self): + sympy_deprecation_warning( + self.message, + deprecated_since_version="1.7", + active_deprecations_target="deprecated-diffgeom-mutable", + stacklevel=4 + ) + + def __iter__(self): + self.warn() + return super().__iter__() + + def __getitem__(self, key): + self.warn() + return super().__getitem__(key) + + def __contains__(self, key): + self.warn() + return super().__contains__(key) + + +class _deprecated_list(_deprecated_container, list): + pass + + +class _deprecated_dict(_deprecated_container, dict): + pass + + +# Import at end to avoid cyclic imports +from sympy.simplify.simplify import simplify diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/rn.py b/phivenv/Lib/site-packages/sympy/diffgeom/rn.py new file mode 100644 index 0000000000000000000000000000000000000000..897c7e82bc804d260612f79c820af92632f3b281 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/rn.py @@ -0,0 +1,143 @@ +"""Predefined R^n manifolds together with common coord. systems. + +Coordinate systems are predefined as well as the transformation laws between +them. + +Coordinate functions can be accessed as attributes of the manifold (eg `R2.x`), +as attributes of the coordinate systems (eg `R2_r.x` and `R2_p.theta`), or by +using the usual `coord_sys.coord_function(index, name)` interface. +""" + +from typing import Any +import warnings + +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, atan2, cos, sin) +from .diffgeom import Manifold, Patch, CoordSystem + +__all__ = [ + 'R2', 'R2_origin', 'relations_2d', 'R2_r', 'R2_p', + 'R3', 'R3_origin', 'relations_3d', 'R3_r', 'R3_c', 'R3_s' +] + +############################################################################### +# R2 +############################################################################### +R2: Any = Manifold('R^2', 2) + +R2_origin: Any = Patch('origin', R2) + +x, y = symbols('x y', real=True) +r, theta = symbols('rho theta', nonnegative=True) + +relations_2d = { + ('rectangular', 'polar'): [(x, y), (sqrt(x**2 + y**2), atan2(y, x))], + ('polar', 'rectangular'): [(r, theta), (r*cos(theta), r*sin(theta))], +} + +R2_r: Any = CoordSystem('rectangular', R2_origin, (x, y), relations_2d) +R2_p: Any = CoordSystem('polar', R2_origin, (r, theta), relations_2d) + +# support deprecated feature +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x, y, r, theta = symbols('x y r theta', cls=Dummy) + R2_r.connect_to(R2_p, [x, y], + [sqrt(x**2 + y**2), atan2(y, x)], + inverse=False, fill_in_gaps=False) + R2_p.connect_to(R2_r, [r, theta], + [r*cos(theta), r*sin(theta)], + inverse=False, fill_in_gaps=False) + +# Defining the basis coordinate functions and adding shortcuts for them to the +# manifold and the patch. +R2.x, R2.y = R2_origin.x, R2_origin.y = R2_r.x, R2_r.y = R2_r.coord_functions() +R2.r, R2.theta = R2_origin.r, R2_origin.theta = R2_p.r, R2_p.theta = R2_p.coord_functions() + +# Defining the basis vector fields and adding shortcuts for them to the +# manifold and the patch. +R2.e_x, R2.e_y = R2_origin.e_x, R2_origin.e_y = R2_r.e_x, R2_r.e_y = R2_r.base_vectors() +R2.e_r, R2.e_theta = R2_origin.e_r, R2_origin.e_theta = R2_p.e_r, R2_p.e_theta = R2_p.base_vectors() + +# Defining the basis oneform fields and adding shortcuts for them to the +# manifold and the patch. +R2.dx, R2.dy = R2_origin.dx, R2_origin.dy = R2_r.dx, R2_r.dy = R2_r.base_oneforms() +R2.dr, R2.dtheta = R2_origin.dr, R2_origin.dtheta = R2_p.dr, R2_p.dtheta = R2_p.base_oneforms() + +############################################################################### +# R3 +############################################################################### +R3: Any = Manifold('R^3', 3) + +R3_origin: Any = Patch('origin', R3) + +x, y, z = symbols('x y z', real=True) +rho, psi, r, theta, phi = symbols('rho psi r theta phi', nonnegative=True) + +relations_3d = { + ('rectangular', 'cylindrical'): [(x, y, z), + (sqrt(x**2 + y**2), atan2(y, x), z)], + ('cylindrical', 'rectangular'): [(rho, psi, z), + (rho*cos(psi), rho*sin(psi), z)], + ('rectangular', 'spherical'): [(x, y, z), + (sqrt(x**2 + y**2 + z**2), + acos(z/sqrt(x**2 + y**2 + z**2)), + atan2(y, x))], + ('spherical', 'rectangular'): [(r, theta, phi), + (r*sin(theta)*cos(phi), + r*sin(theta)*sin(phi), + r*cos(theta))], + ('cylindrical', 'spherical'): [(rho, psi, z), + (sqrt(rho**2 + z**2), + acos(z/sqrt(rho**2 + z**2)), + psi)], + ('spherical', 'cylindrical'): [(r, theta, phi), + (r*sin(theta), phi, r*cos(theta))], +} + +R3_r: Any = CoordSystem('rectangular', R3_origin, (x, y, z), relations_3d) +R3_c: Any = CoordSystem('cylindrical', R3_origin, (rho, psi, z), relations_3d) +R3_s: Any = CoordSystem('spherical', R3_origin, (r, theta, phi), relations_3d) + +# support deprecated feature +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x, y, z, rho, psi, r, theta, phi = symbols('x y z rho psi r theta phi', cls=Dummy) + R3_r.connect_to(R3_c, [x, y, z], + [sqrt(x**2 + y**2), atan2(y, x), z], + inverse=False, fill_in_gaps=False) + R3_c.connect_to(R3_r, [rho, psi, z], + [rho*cos(psi), rho*sin(psi), z], + inverse=False, fill_in_gaps=False) + ## rectangular <-> spherical + R3_r.connect_to(R3_s, [x, y, z], + [sqrt(x**2 + y**2 + z**2), acos(z/ + sqrt(x**2 + y**2 + z**2)), atan2(y, x)], + inverse=False, fill_in_gaps=False) + R3_s.connect_to(R3_r, [r, theta, phi], + [r*sin(theta)*cos(phi), r*sin( + theta)*sin(phi), r*cos(theta)], + inverse=False, fill_in_gaps=False) + ## cylindrical <-> spherical + R3_c.connect_to(R3_s, [rho, psi, z], + [sqrt(rho**2 + z**2), acos(z/sqrt(rho**2 + z**2)), psi], + inverse=False, fill_in_gaps=False) + R3_s.connect_to(R3_c, [r, theta, phi], + [r*sin(theta), phi, r*cos(theta)], + inverse=False, fill_in_gaps=False) + +# Defining the basis coordinate functions. +R3_r.x, R3_r.y, R3_r.z = R3_r.coord_functions() +R3_c.rho, R3_c.psi, R3_c.z = R3_c.coord_functions() +R3_s.r, R3_s.theta, R3_s.phi = R3_s.coord_functions() + +# Defining the basis vector fields. +R3_r.e_x, R3_r.e_y, R3_r.e_z = R3_r.base_vectors() +R3_c.e_rho, R3_c.e_psi, R3_c.e_z = R3_c.base_vectors() +R3_s.e_r, R3_s.e_theta, R3_s.e_phi = R3_s.base_vectors() + +# Defining the basis oneform fields. +R3_r.dx, R3_r.dy, R3_r.dz = R3_r.base_oneforms() +R3_c.drho, R3_c.dpsi, R3_c.dz = R3_c.base_oneforms() +R3_s.dr, R3_s.dtheta, R3_s.dphi = R3_s.base_oneforms() diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/__init__.py b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c87f12116670e50b7be1212672b4a2fa90d9828 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_class_structure.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_class_structure.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25659d25e90f8022143298fedf0cbeb8d736ae0b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_class_structure.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_diffgeom.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_diffgeom.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9f8154dfcf3a3966ba794d69ef26ca41ac841a6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_diffgeom.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_function_diffgeom_book.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_function_diffgeom_book.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc0c219d446df62f063c6b5f7045d8655849057d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_function_diffgeom_book.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_hyperbolic_space.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_hyperbolic_space.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a52579d441c8fa6e214f6c502b7849f716070463 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/diffgeom/tests/__pycache__/test_hyperbolic_space.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_class_structure.py b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_class_structure.py new file mode 100644 index 0000000000000000000000000000000000000000..c649fd9fcb9acdf1f410a021966c6e0fee62cc2b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_class_structure.py @@ -0,0 +1,33 @@ +from sympy.diffgeom import Manifold, Patch, CoordSystem, Point +from sympy.core.function import Function +from sympy.core.symbol import symbols +from sympy.testing.pytest import warns_deprecated_sympy + +m = Manifold('m', 2) +p = Patch('p', m) +a, b = symbols('a b') +cs = CoordSystem('cs', p, [a, b]) +x, y = symbols('x y') +f = Function('f') +s1, s2 = cs.coord_functions() +v1, v2 = cs.base_vectors() +f1, f2 = cs.base_oneforms() + +def test_point(): + point = Point(cs, [x, y]) + assert point != Point(cs, [2, y]) + #TODO assert point.subs(x, 2) == Point(cs, [2, y]) + #TODO assert point.free_symbols == set([x, y]) + +def test_subs(): + assert s1.subs(s1, s2) == s2 + assert v1.subs(v1, v2) == v2 + assert f1.subs(f1, f2) == f2 + assert (x*f(s1) + y).subs(s1, s2) == x*f(s2) + y + assert (f(s1)*v1).subs(v1, v2) == f(s1)*v2 + assert (y*f(s1)*f1).subs(f1, f2) == y*f(s1)*f2 + +def test_deprecated(): + with warns_deprecated_sympy(): + cs_wname = CoordSystem('cs', p, ['a', 'b']) + assert cs_wname == cs_wname.func(*cs_wname.args) diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_diffgeom.py b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_diffgeom.py new file mode 100644 index 0000000000000000000000000000000000000000..7c3c9265785896b8f4ffa3a2b41816ca90579758 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_diffgeom.py @@ -0,0 +1,342 @@ +from sympy.core import Lambda, Symbol, symbols +from sympy.diffgeom.rn import R2, R2_p, R2_r, R3_r, R3_c, R3_s, R2_origin +from sympy.diffgeom import (Manifold, Patch, CoordSystem, Commutator, Differential, TensorProduct, + WedgeProduct, BaseCovarDerivativeOp, CovarDerivativeOp, LieDerivative, + covariant_order, contravariant_order, twoform_to_matrix, metric_to_Christoffel_1st, + metric_to_Christoffel_2nd, metric_to_Riemann_components, + metric_to_Ricci_components, intcurve_diffequ, intcurve_series) +from sympy.simplify import trigsimp, simplify +from sympy.functions import sqrt, atan2, sin +from sympy.matrices import Matrix +from sympy.testing.pytest import raises, nocache_fail +from sympy.testing.pytest import warns_deprecated_sympy + +TP = TensorProduct + + +def test_coordsys_transform(): + # test inverse transforms + p, q, r, s = symbols('p q r s') + rel = {('first', 'second'): [(p, q), (q, -p)]} + R2_pq = CoordSystem('first', R2_origin, [p, q], rel) + R2_rs = CoordSystem('second', R2_origin, [r, s], rel) + r, s = R2_rs.symbols + assert R2_rs.transform(R2_pq) == Matrix([[-s], [r]]) + + # inverse transform impossible case + a, b = symbols('a b', positive=True) + rel = {('first', 'second'): [(a,), (-a,)]} + R2_a = CoordSystem('first', R2_origin, [a], rel) + R2_b = CoordSystem('second', R2_origin, [b], rel) + # This transformation is uninvertible because there is no positive a, b satisfying a = -b + with raises(NotImplementedError): + R2_b.transform(R2_a) + + # inverse transform ambiguous case + c, d = symbols('c d') + rel = {('first', 'second'): [(c,), (c**2,)]} + R2_c = CoordSystem('first', R2_origin, [c], rel) + R2_d = CoordSystem('second', R2_origin, [d], rel) + # The transform method should throw if it finds multiple inverses for a coordinate transformation. + with raises(ValueError): + R2_d.transform(R2_c) + + # test indirect transformation + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + rel = {('C1', 'C2'): [(a, b), (2*a, 3*b)], + ('C2', 'C3'): [(c, d), (3*c, 2*d)]} + C1 = CoordSystem('C1', R2_origin, (a, b), rel) + C2 = CoordSystem('C2', R2_origin, (c, d), rel) + C3 = CoordSystem('C3', R2_origin, (e, f), rel) + a, b = C1.symbols + c, d = C2.symbols + e, f = C3.symbols + assert C2.transform(C1) == Matrix([c/2, d/3]) + assert C1.transform(C3) == Matrix([6*a, 6*b]) + assert C3.transform(C1) == Matrix([e/6, f/6]) + assert C3.transform(C2) == Matrix([e/3, f/2]) + + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + rel = {('C1', 'C2'): [(a, b), (2*a, 3*b + 1)], + ('C3', 'C2'): [(e, f), (-e - 2, 2*f)]} + C1 = CoordSystem('C1', R2_origin, (a, b), rel) + C2 = CoordSystem('C2', R2_origin, (c, d), rel) + C3 = CoordSystem('C3', R2_origin, (e, f), rel) + a, b = C1.symbols + c, d = C2.symbols + e, f = C3.symbols + assert C2.transform(C1) == Matrix([c/2, (d - 1)/3]) + assert C1.transform(C3) == Matrix([-2*a - 2, (3*b + 1)/2]) + assert C3.transform(C1) == Matrix([-e/2 - 1, (2*f - 1)/3]) + assert C3.transform(C2) == Matrix([-e - 2, 2*f]) + + # old signature uses Lambda + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + rel = {('C1', 'C2'): Lambda((a, b), (2*a, 3*b + 1)), + ('C3', 'C2'): Lambda((e, f), (-e - 2, 2*f))} + C1 = CoordSystem('C1', R2_origin, (a, b), rel) + C2 = CoordSystem('C2', R2_origin, (c, d), rel) + C3 = CoordSystem('C3', R2_origin, (e, f), rel) + a, b = C1.symbols + c, d = C2.symbols + e, f = C3.symbols + assert C2.transform(C1) == Matrix([c/2, (d - 1)/3]) + assert C1.transform(C3) == Matrix([-2*a - 2, (3*b + 1)/2]) + assert C3.transform(C1) == Matrix([-e/2 - 1, (2*f - 1)/3]) + assert C3.transform(C2) == Matrix([-e - 2, 2*f]) + + +def test_R2(): + x0, y0, r0, theta0 = symbols('x0, y0, r0, theta0', real=True) + point_r = R2_r.point([x0, y0]) + point_p = R2_p.point([r0, theta0]) + + # r**2 = x**2 + y**2 + assert (R2.r**2 - R2.x**2 - R2.y**2).rcall(point_r) == 0 + assert trigsimp( (R2.r**2 - R2.x**2 - R2.y**2).rcall(point_p) ) == 0 + assert trigsimp(R2.e_r(R2.x**2 + R2.y**2).rcall(point_p).doit()) == 2*r0 + + # polar->rect->polar == Id + a, b = symbols('a b', positive=True) + m = Matrix([[a], [b]]) + + #TODO assert m == R2_r.transform(R2_p, R2_p.transform(R2_r, [a, b])).applyfunc(simplify) + assert m == R2_p.transform(R2_r, R2_r.transform(R2_p, m)).applyfunc(simplify) + + # deprecated method + with warns_deprecated_sympy(): + assert m == R2_p.coord_tuple_transform_to( + R2_r, R2_r.coord_tuple_transform_to(R2_p, m)).applyfunc(simplify) + + +def test_R3(): + a, b, c = symbols('a b c', positive=True) + m = Matrix([[a], [b], [c]]) + + assert m == R3_c.transform(R3_r, R3_r.transform(R3_c, m)).applyfunc(simplify) + #TODO assert m == R3_r.transform(R3_c, R3_c.transform(R3_r, m)).applyfunc(simplify) + assert m == R3_s.transform( + R3_r, R3_r.transform(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_r.transform(R3_s, R3_s.transform(R3_r, m)).applyfunc(simplify) + assert m == R3_s.transform( + R3_c, R3_c.transform(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_c.transform(R3_s, R3_s.transform(R3_c, m)).applyfunc(simplify) + + with warns_deprecated_sympy(): + assert m == R3_c.coord_tuple_transform_to( + R3_r, R3_r.coord_tuple_transform_to(R3_c, m)).applyfunc(simplify) + #TODO assert m == R3_r.coord_tuple_transform_to(R3_c, R3_c.coord_tuple_transform_to(R3_r, m)).applyfunc(simplify) + assert m == R3_s.coord_tuple_transform_to( + R3_r, R3_r.coord_tuple_transform_to(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_r.coord_tuple_transform_to(R3_s, R3_s.coord_tuple_transform_to(R3_r, m)).applyfunc(simplify) + assert m == R3_s.coord_tuple_transform_to( + R3_c, R3_c.coord_tuple_transform_to(R3_s, m)).applyfunc(simplify) + #TODO assert m == R3_c.coord_tuple_transform_to(R3_s, R3_s.coord_tuple_transform_to(R3_c, m)).applyfunc(simplify) + + +def test_CoordinateSymbol(): + x, y = R2_r.symbols + r, theta = R2_p.symbols + assert y.rewrite(R2_p) == r*sin(theta) + + +def test_point(): + x, y = symbols('x, y') + p = R2_r.point([x, y]) + assert p.free_symbols == {x, y} + assert p.coords(R2_r) == p.coords() == Matrix([x, y]) + assert p.coords(R2_p) == Matrix([sqrt(x**2 + y**2), atan2(y, x)]) + + +def test_commutator(): + assert Commutator(R2.e_x, R2.e_y) == 0 + assert Commutator(R2.x*R2.e_x, R2.x*R2.e_x) == 0 + assert Commutator(R2.x*R2.e_x, R2.x*R2.e_y) == R2.x*R2.e_y + c = Commutator(R2.e_x, R2.e_r) + assert c(R2.x) == R2.y*(R2.x**2 + R2.y**2)**(-1)*sin(R2.theta) + + +def test_differential(): + xdy = R2.x*R2.dy + dxdy = Differential(xdy) + assert xdy.rcall(None) == xdy + assert dxdy(R2.e_x, R2.e_y) == 1 + assert dxdy(R2.e_x, R2.x*R2.e_y) == R2.x + assert Differential(dxdy) == 0 + + +def test_products(): + assert TensorProduct( + R2.dx, R2.dy)(R2.e_x, R2.e_y) == R2.dx(R2.e_x)*R2.dy(R2.e_y) == 1 + assert TensorProduct(R2.dx, R2.dy)(None, R2.e_y) == R2.dx + assert TensorProduct(R2.dx, R2.dy)(R2.e_x, None) == R2.dy + assert TensorProduct(R2.dx, R2.dy)(R2.e_x) == R2.dy + assert TensorProduct(R2.x, R2.dx) == R2.x*R2.dx + assert TensorProduct( + R2.e_x, R2.e_y)(R2.x, R2.y) == R2.e_x(R2.x) * R2.e_y(R2.y) == 1 + assert TensorProduct(R2.e_x, R2.e_y)(None, R2.y) == R2.e_x + assert TensorProduct(R2.e_x, R2.e_y)(R2.x, None) == R2.e_y + assert TensorProduct(R2.e_x, R2.e_y)(R2.x) == R2.e_y + assert TensorProduct(R2.x, R2.e_x) == R2.x * R2.e_x + assert TensorProduct( + R2.dx, R2.e_y)(R2.e_x, R2.y) == R2.dx(R2.e_x) * R2.e_y(R2.y) == 1 + assert TensorProduct(R2.dx, R2.e_y)(None, R2.y) == R2.dx + assert TensorProduct(R2.dx, R2.e_y)(R2.e_x, None) == R2.e_y + assert TensorProduct(R2.dx, R2.e_y)(R2.e_x) == R2.e_y + assert TensorProduct(R2.x, R2.e_x) == R2.x * R2.e_x + assert TensorProduct( + R2.e_x, R2.dy)(R2.x, R2.e_y) == R2.e_x(R2.x) * R2.dy(R2.e_y) == 1 + assert TensorProduct(R2.e_x, R2.dy)(None, R2.e_y) == R2.e_x + assert TensorProduct(R2.e_x, R2.dy)(R2.x, None) == R2.dy + assert TensorProduct(R2.e_x, R2.dy)(R2.x) == R2.dy + assert TensorProduct(R2.e_y,R2.e_x)(R2.x**2 + R2.y**2,R2.x**2 + R2.y**2) == 4*R2.x*R2.y + + assert WedgeProduct(R2.dx, R2.dy)(R2.e_x, R2.e_y) == 1 + assert WedgeProduct(R2.e_x, R2.e_y)(R2.x, R2.y) == 1 + + +def test_lie_derivative(): + assert LieDerivative(R2.e_x, R2.y) == R2.e_x(R2.y) == 0 + assert LieDerivative(R2.e_x, R2.x) == R2.e_x(R2.x) == 1 + assert LieDerivative(R2.e_x, R2.e_x) == Commutator(R2.e_x, R2.e_x) == 0 + assert LieDerivative(R2.e_x, R2.e_r) == Commutator(R2.e_x, R2.e_r) + assert LieDerivative(R2.e_x + R2.e_y, R2.x) == 1 + assert LieDerivative( + R2.e_x, TensorProduct(R2.dx, R2.dy))(R2.e_x, R2.e_y) == 0 + + +@nocache_fail +def test_covar_deriv(): + ch = metric_to_Christoffel_2nd(TP(R2.dx, R2.dx) + TP(R2.dy, R2.dy)) + cvd = BaseCovarDerivativeOp(R2_r, 0, ch) + assert cvd(R2.x) == 1 + # This line fails if the cache is disabled: + assert cvd(R2.x*R2.e_x) == R2.e_x + cvd = CovarDerivativeOp(R2.x*R2.e_x, ch) + assert cvd(R2.x) == R2.x + assert cvd(R2.x*R2.e_x) == R2.x*R2.e_x + + +def test_intcurve_diffequ(): + t = symbols('t') + start_point = R2_r.point([1, 0]) + vector_field = -R2.y*R2.e_x + R2.x*R2.e_y + equations, init_cond = intcurve_diffequ(vector_field, t, start_point) + assert str(equations) == '[f_1(t) + Derivative(f_0(t), t), -f_0(t) + Derivative(f_1(t), t)]' + assert str(init_cond) == '[f_0(0) - 1, f_1(0)]' + equations, init_cond = intcurve_diffequ(vector_field, t, start_point, R2_p) + assert str( + equations) == '[Derivative(f_0(t), t), Derivative(f_1(t), t) - 1]' + assert str(init_cond) == '[f_0(0) - 1, f_1(0)]' + + +def test_helpers_and_coordinate_dependent(): + one_form = R2.dr + R2.dx + two_form = Differential(R2.x*R2.dr + R2.r*R2.dx) + three_form = Differential( + R2.y*two_form) + Differential(R2.x*Differential(R2.r*R2.dr)) + metric = TensorProduct(R2.dx, R2.dx) + TensorProduct(R2.dy, R2.dy) + metric_ambig = TensorProduct(R2.dx, R2.dx) + TensorProduct(R2.dr, R2.dr) + misform_a = TensorProduct(R2.dr, R2.dr) + R2.dr + misform_b = R2.dr**4 + misform_c = R2.dx*R2.dy + twoform_not_sym = TensorProduct(R2.dx, R2.dx) + TensorProduct(R2.dx, R2.dy) + twoform_not_TP = WedgeProduct(R2.dx, R2.dy) + + one_vector = R2.e_x + R2.e_y + two_vector = TensorProduct(R2.e_x, R2.e_y) + three_vector = TensorProduct(R2.e_x, R2.e_y, R2.e_x) + two_wp = WedgeProduct(R2.e_x,R2.e_y) + + assert covariant_order(one_form) == 1 + assert covariant_order(two_form) == 2 + assert covariant_order(three_form) == 3 + assert covariant_order(two_form + metric) == 2 + assert covariant_order(two_form + metric_ambig) == 2 + assert covariant_order(two_form + twoform_not_sym) == 2 + assert covariant_order(two_form + twoform_not_TP) == 2 + + assert contravariant_order(one_vector) == 1 + assert contravariant_order(two_vector) == 2 + assert contravariant_order(three_vector) == 3 + assert contravariant_order(two_vector + two_wp) == 2 + + raises(ValueError, lambda: covariant_order(misform_a)) + raises(ValueError, lambda: covariant_order(misform_b)) + raises(ValueError, lambda: covariant_order(misform_c)) + + assert twoform_to_matrix(metric) == Matrix([[1, 0], [0, 1]]) + assert twoform_to_matrix(twoform_not_sym) == Matrix([[1, 0], [1, 0]]) + assert twoform_to_matrix(twoform_not_TP) == Matrix([[0, -1], [1, 0]]) + + raises(ValueError, lambda: twoform_to_matrix(one_form)) + raises(ValueError, lambda: twoform_to_matrix(three_form)) + raises(ValueError, lambda: twoform_to_matrix(metric_ambig)) + + raises(ValueError, lambda: metric_to_Christoffel_1st(twoform_not_sym)) + raises(ValueError, lambda: metric_to_Christoffel_2nd(twoform_not_sym)) + raises(ValueError, lambda: metric_to_Riemann_components(twoform_not_sym)) + raises(ValueError, lambda: metric_to_Ricci_components(twoform_not_sym)) + + +def test_correct_arguments(): + raises(ValueError, lambda: R2.e_x(R2.e_x)) + raises(ValueError, lambda: R2.e_x(R2.dx)) + + raises(ValueError, lambda: Commutator(R2.e_x, R2.x)) + raises(ValueError, lambda: Commutator(R2.dx, R2.e_x)) + + raises(ValueError, lambda: Differential(Differential(R2.e_x))) + + raises(ValueError, lambda: R2.dx(R2.x)) + + raises(ValueError, lambda: LieDerivative(R2.dx, R2.dx)) + raises(ValueError, lambda: LieDerivative(R2.x, R2.dx)) + + raises(ValueError, lambda: CovarDerivativeOp(R2.dx, [])) + raises(ValueError, lambda: CovarDerivativeOp(R2.x, [])) + + a = Symbol('a') + raises(ValueError, lambda: intcurve_series(R2.dx, a, R2_r.point([1, 2]))) + raises(ValueError, lambda: intcurve_series(R2.x, a, R2_r.point([1, 2]))) + + raises(ValueError, lambda: intcurve_diffequ(R2.dx, a, R2_r.point([1, 2]))) + raises(ValueError, lambda: intcurve_diffequ(R2.x, a, R2_r.point([1, 2]))) + + raises(ValueError, lambda: contravariant_order(R2.e_x + R2.dx)) + raises(ValueError, lambda: covariant_order(R2.e_x + R2.dx)) + + raises(ValueError, lambda: contravariant_order(R2.e_x*R2.e_y)) + raises(ValueError, lambda: covariant_order(R2.dx*R2.dy)) + +def test_simplify(): + x, y = R2_r.coord_functions() + dx, dy = R2_r.base_oneforms() + ex, ey = R2_r.base_vectors() + assert simplify(x) == x + assert simplify(x*y) == x*y + assert simplify(dx*dy) == dx*dy + assert simplify(ex*ey) == ex*ey + assert ((1-x)*dx)/(1-x)**2 == dx/(1-x) + + +def test_issue_17917(): + X = R2.x*R2.e_x - R2.y*R2.e_y + Y = (R2.x**2 + R2.y**2)*R2.e_x - R2.x*R2.y*R2.e_y + assert LieDerivative(X, Y).expand() == ( + R2.x**2*R2.e_x - 3*R2.y**2*R2.e_x - R2.x*R2.y*R2.e_y) + +def test_deprecations(): + m = Manifold('M', 2) + p = Patch('P', m) + with warns_deprecated_sympy(): + CoordSystem('Car2d', p, names=['x', 'y']) + + with warns_deprecated_sympy(): + c = CoordSystem('Car2d', p, ['x', 'y']) + + with warns_deprecated_sympy(): + list(m.patches) + + with warns_deprecated_sympy(): + list(c.transforms) diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_function_diffgeom_book.py b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_function_diffgeom_book.py new file mode 100644 index 0000000000000000000000000000000000000000..44d9623bc34ab73c7d575d9d9fd5b6d84f8e4a94 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_function_diffgeom_book.py @@ -0,0 +1,145 @@ +from sympy.diffgeom.rn import R2, R2_p, R2_r, R3_r +from sympy.diffgeom import intcurve_series, Differential, WedgeProduct +from sympy.core import symbols, Function, Derivative +from sympy.simplify import trigsimp, simplify +from sympy.functions import sqrt, atan2, sin, cos +from sympy.matrices import Matrix + +# Most of the functionality is covered in the +# test_functional_diffgeom_ch* tests which are based on the +# example from the paper of Sussman and Wisdom. +# If they do not cover something, additional tests are added in other test +# functions. + +# From "Functional Differential Geometry" as of 2011 +# by Sussman and Wisdom. + + +def test_functional_diffgeom_ch2(): + x0, y0, r0, theta0 = symbols('x0, y0, r0, theta0', real=True) + x, y = symbols('x, y', real=True) + f = Function('f') + + assert (R2_p.point_to_coords(R2_r.point([x0, y0])) == + Matrix([sqrt(x0**2 + y0**2), atan2(y0, x0)])) + assert (R2_r.point_to_coords(R2_p.point([r0, theta0])) == + Matrix([r0*cos(theta0), r0*sin(theta0)])) + + assert R2_p.jacobian(R2_r, [r0, theta0]) == Matrix( + [[cos(theta0), -r0*sin(theta0)], [sin(theta0), r0*cos(theta0)]]) + + field = f(R2.x, R2.y) + p1_in_rect = R2_r.point([x0, y0]) + p1_in_polar = R2_p.point([sqrt(x0**2 + y0**2), atan2(y0, x0)]) + assert field.rcall(p1_in_rect) == f(x0, y0) + assert field.rcall(p1_in_polar) == f(x0, y0) + + p_r = R2_r.point([x0, y0]) + p_p = R2_p.point([r0, theta0]) + assert R2.x(p_r) == x0 + assert R2.x(p_p) == r0*cos(theta0) + assert R2.r(p_p) == r0 + assert R2.r(p_r) == sqrt(x0**2 + y0**2) + assert R2.theta(p_r) == atan2(y0, x0) + + h = R2.x*R2.r**2 + R2.y**3 + assert h.rcall(p_r) == x0*(x0**2 + y0**2) + y0**3 + assert h.rcall(p_p) == r0**3*sin(theta0)**3 + r0**3*cos(theta0) + + +def test_functional_diffgeom_ch3(): + x0, y0 = symbols('x0, y0', real=True) + x, y, t = symbols('x, y, t', real=True) + f = Function('f') + b1 = Function('b1') + b2 = Function('b2') + p_r = R2_r.point([x0, y0]) + + s_field = f(R2.x, R2.y) + v_field = b1(R2.x)*R2.e_x + b2(R2.y)*R2.e_y + assert v_field.rcall(s_field).rcall(p_r).doit() == b1( + x0)*Derivative(f(x0, y0), x0) + b2(y0)*Derivative(f(x0, y0), y0) + + assert R2.e_x(R2.r**2).rcall(p_r) == 2*x0 + v = R2.e_x + 2*R2.e_y + s = R2.r**2 + 3*R2.x + assert v.rcall(s).rcall(p_r).doit() == 2*x0 + 4*y0 + 3 + + circ = -R2.y*R2.e_x + R2.x*R2.e_y + series = intcurve_series(circ, t, R2_r.point([1, 0]), coeffs=True) + series_x, series_y = zip(*series) + assert all( + term == cos(t).taylor_term(i, t) for i, term in enumerate(series_x)) + assert all( + term == sin(t).taylor_term(i, t) for i, term in enumerate(series_y)) + + +def test_functional_diffgeom_ch4(): + x0, y0, theta0 = symbols('x0, y0, theta0', real=True) + x, y, r, theta = symbols('x, y, r, theta', real=True) + r0 = symbols('r0', positive=True) + f = Function('f') + b1 = Function('b1') + b2 = Function('b2') + p_r = R2_r.point([x0, y0]) + p_p = R2_p.point([r0, theta0]) + + f_field = b1(R2.x, R2.y)*R2.dx + b2(R2.x, R2.y)*R2.dy + assert f_field.rcall(R2.e_x).rcall(p_r) == b1(x0, y0) + assert f_field.rcall(R2.e_y).rcall(p_r) == b2(x0, y0) + + s_field_r = f(R2.x, R2.y) + df = Differential(s_field_r) + assert df(R2.e_x).rcall(p_r).doit() == Derivative(f(x0, y0), x0) + assert df(R2.e_y).rcall(p_r).doit() == Derivative(f(x0, y0), y0) + + s_field_p = f(R2.r, R2.theta) + df = Differential(s_field_p) + assert trigsimp(df(R2.e_x).rcall(p_p).doit()) == ( + cos(theta0)*Derivative(f(r0, theta0), r0) - + sin(theta0)*Derivative(f(r0, theta0), theta0)/r0) + assert trigsimp(df(R2.e_y).rcall(p_p).doit()) == ( + sin(theta0)*Derivative(f(r0, theta0), r0) + + cos(theta0)*Derivative(f(r0, theta0), theta0)/r0) + + assert R2.dx(R2.e_x).rcall(p_r) == 1 + assert R2.dx(R2.e_x) == 1 + assert R2.dx(R2.e_y).rcall(p_r) == 0 + assert R2.dx(R2.e_y) == 0 + + circ = -R2.y*R2.e_x + R2.x*R2.e_y + assert R2.dx(circ).rcall(p_r).doit() == -y0 + assert R2.dy(circ).rcall(p_r) == x0 + assert R2.dr(circ).rcall(p_r) == 0 + assert simplify(R2.dtheta(circ).rcall(p_r)) == 1 + + assert (circ - R2.e_theta).rcall(s_field_r).rcall(p_r) == 0 + + +def test_functional_diffgeom_ch6(): + u0, u1, u2, v0, v1, v2, w0, w1, w2 = symbols('u0:3, v0:3, w0:3', real=True) + + u = u0*R2.e_x + u1*R2.e_y + v = v0*R2.e_x + v1*R2.e_y + wp = WedgeProduct(R2.dx, R2.dy) + assert wp(u, v) == u0*v1 - u1*v0 + + u = u0*R3_r.e_x + u1*R3_r.e_y + u2*R3_r.e_z + v = v0*R3_r.e_x + v1*R3_r.e_y + v2*R3_r.e_z + w = w0*R3_r.e_x + w1*R3_r.e_y + w2*R3_r.e_z + wp = WedgeProduct(R3_r.dx, R3_r.dy, R3_r.dz) + assert wp( + u, v, w) == Matrix(3, 3, [u0, u1, u2, v0, v1, v2, w0, w1, w2]).det() + + a, b, c = symbols('a, b, c', cls=Function) + a_f = a(R3_r.x, R3_r.y, R3_r.z) + b_f = b(R3_r.x, R3_r.y, R3_r.z) + c_f = c(R3_r.x, R3_r.y, R3_r.z) + theta = a_f*R3_r.dx + b_f*R3_r.dy + c_f*R3_r.dz + dtheta = Differential(theta) + da = Differential(a_f) + db = Differential(b_f) + dc = Differential(c_f) + expr = dtheta - WedgeProduct( + da, R3_r.dx) - WedgeProduct(db, R3_r.dy) - WedgeProduct(dc, R3_r.dz) + assert expr.rcall(R3_r.e_x, R3_r.e_y) == 0 diff --git a/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_hyperbolic_space.py b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_hyperbolic_space.py new file mode 100644 index 0000000000000000000000000000000000000000..48ddc7f8065f2b69bcd8eca4726a21c5901514ec --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/diffgeom/tests/test_hyperbolic_space.py @@ -0,0 +1,91 @@ +r''' +unit test describing the hyperbolic half-plane with the Poincare metric. This +is a basic model of hyperbolic geometry on the (positive) half-space + +{(x,y) \in R^2 | y > 0} + +with the Riemannian metric + +ds^2 = (dx^2 + dy^2)/y^2 + +It has constant negative scalar curvature = -2 + +https://en.wikipedia.org/wiki/Poincare_half-plane_model +''' +from sympy.matrices.dense import diag +from sympy.diffgeom import (twoform_to_matrix, + metric_to_Christoffel_1st, metric_to_Christoffel_2nd, + metric_to_Riemann_components, metric_to_Ricci_components) +import sympy.diffgeom.rn +from sympy.tensor.array import ImmutableDenseNDimArray + + +def test_H2(): + TP = sympy.diffgeom.TensorProduct + R2 = sympy.diffgeom.rn.R2 + y = R2.y + dy = R2.dy + dx = R2.dx + g = (TP(dx, dx) + TP(dy, dy))*y**(-2) + automat = twoform_to_matrix(g) + mat = diag(y**(-2), y**(-2)) + assert mat == automat + + gamma1 = metric_to_Christoffel_1st(g) + assert gamma1[0, 0, 0] == 0 + assert gamma1[0, 0, 1] == -y**(-3) + assert gamma1[0, 1, 0] == -y**(-3) + assert gamma1[0, 1, 1] == 0 + + assert gamma1[1, 1, 1] == -y**(-3) + assert gamma1[1, 1, 0] == 0 + assert gamma1[1, 0, 1] == 0 + assert gamma1[1, 0, 0] == y**(-3) + + gamma2 = metric_to_Christoffel_2nd(g) + assert gamma2[0, 0, 0] == 0 + assert gamma2[0, 0, 1] == -y**(-1) + assert gamma2[0, 1, 0] == -y**(-1) + assert gamma2[0, 1, 1] == 0 + + assert gamma2[1, 1, 1] == -y**(-1) + assert gamma2[1, 1, 0] == 0 + assert gamma2[1, 0, 1] == 0 + assert gamma2[1, 0, 0] == y**(-1) + + Rm = metric_to_Riemann_components(g) + assert Rm[0, 0, 0, 0] == 0 + assert Rm[0, 0, 0, 1] == 0 + assert Rm[0, 0, 1, 0] == 0 + assert Rm[0, 0, 1, 1] == 0 + + assert Rm[0, 1, 0, 0] == 0 + assert Rm[0, 1, 0, 1] == -y**(-2) + assert Rm[0, 1, 1, 0] == y**(-2) + assert Rm[0, 1, 1, 1] == 0 + + assert Rm[1, 0, 0, 0] == 0 + assert Rm[1, 0, 0, 1] == y**(-2) + assert Rm[1, 0, 1, 0] == -y**(-2) + assert Rm[1, 0, 1, 1] == 0 + + assert Rm[1, 1, 0, 0] == 0 + assert Rm[1, 1, 0, 1] == 0 + assert Rm[1, 1, 1, 0] == 0 + assert Rm[1, 1, 1, 1] == 0 + + Ric = metric_to_Ricci_components(g) + assert Ric[0, 0] == -y**(-2) + assert Ric[0, 1] == 0 + assert Ric[1, 0] == 0 + assert Ric[0, 0] == -y**(-2) + + assert Ric == ImmutableDenseNDimArray([-y**(-2), 0, 0, -y**(-2)], (2, 2)) + + ## scalar curvature is -2 + #TODO - it would be nice to have index contraction built-in + R = (Ric[0, 0] + Ric[1, 1])*y**2 + assert R == -2 + + ## Gauss curvature is -1 + assert R/2 == -1 diff --git a/phivenv/Lib/site-packages/sympy/discrete/__init__.py b/phivenv/Lib/site-packages/sympy/discrete/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..968c4caa0d4562b71285f414bfb70f43d0b35111 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/__init__.py @@ -0,0 +1,20 @@ +"""This module contains functions which operate on discrete sequences. + +Transforms - ``fft``, ``ifft``, ``ntt``, ``intt``, ``fwht``, ``ifwht``, + ``mobius_transform``, ``inverse_mobius_transform`` + +Convolutions - ``convolution``, ``convolution_fft``, ``convolution_ntt``, + ``convolution_fwht``, ``convolution_subset``, + ``covering_product``, ``intersecting_product`` +""" + +from .transforms import (fft, ifft, ntt, intt, fwht, ifwht, + mobius_transform, inverse_mobius_transform) +from .convolutions import convolution, covering_product, intersecting_product + +__all__ = [ + 'fft', 'ifft', 'ntt', 'intt', 'fwht', 'ifwht', 'mobius_transform', + 'inverse_mobius_transform', + + 'convolution', 'covering_product', 'intersecting_product', +] diff --git a/phivenv/Lib/site-packages/sympy/discrete/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec6c16cd2aed258c10d9383adf5c0bb0d9db50fc Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/__pycache__/convolutions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/convolutions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..079bf7a0facef0838f28d433160325a140be07ff Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/convolutions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/__pycache__/recurrences.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/recurrences.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14fc5d5ec80297e7668229cc46d0431d35aac17d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/recurrences.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/__pycache__/transforms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/transforms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd438e14940f641bce59be58bc25a6214f25d491 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/__pycache__/transforms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/convolutions.py b/phivenv/Lib/site-packages/sympy/discrete/convolutions.py new file mode 100644 index 0000000000000000000000000000000000000000..ac9a3dbbb26b8b117ea1ee99cf7ebabbd21322cc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/convolutions.py @@ -0,0 +1,597 @@ +""" +Convolution (using **FFT**, **NTT**, **FWHT**), Subset Convolution, +Covering Product, Intersecting Product +""" + +from sympy.core import S, sympify, Rational +from sympy.core.function import expand_mul +from sympy.discrete.transforms import ( + fft, ifft, ntt, intt, fwht, ifwht, + mobius_transform, inverse_mobius_transform) +from sympy.external.gmpy import MPZ, lcm +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import as_int + + +def convolution(a, b, cycle=0, dps=None, prime=None, dyadic=None, subset=None): + """ + Performs convolution by determining the type of desired + convolution using hints. + + Exactly one of ``dps``, ``prime``, ``dyadic``, ``subset`` arguments + should be specified explicitly for identifying the type of convolution, + and the argument ``cycle`` can be specified optionally. + + For the default arguments, linear convolution is performed using **FFT**. + + Parameters + ========== + + a, b : iterables + The sequences for which convolution is performed. + cycle : Integer + Specifies the length for doing cyclic convolution. + dps : Integer + Specifies the number of decimal digits for precision for + performing **FFT** on the sequence. + prime : Integer + Prime modulus of the form `(m 2^k + 1)` to be used for + performing **NTT** on the sequence. + dyadic : bool + Identifies the convolution type as dyadic (*bitwise-XOR*) + convolution, which is performed using **FWHT**. + subset : bool + Identifies the convolution type as subset convolution. + + Examples + ======== + + >>> from sympy import convolution, symbols, S, I + >>> u, v, w, x, y, z = symbols('u v w x y z') + + >>> convolution([1 + 2*I, 4 + 3*I], [S(5)/4, 6], dps=3) + [1.25 + 2.5*I, 11.0 + 15.8*I, 24.0 + 18.0*I] + >>> convolution([1, 2, 3], [4, 5, 6], cycle=3) + [31, 31, 28] + + >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1) + [1283, 19351, 14219] + >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1, cycle=2) + [15502, 19351] + + >>> convolution([u, v], [x, y, z], dyadic=True) + [u*x + v*y, u*y + v*x, u*z, v*z] + >>> convolution([u, v], [x, y, z], dyadic=True, cycle=2) + [u*x + u*z + v*y, u*y + v*x + v*z] + + >>> convolution([u, v, w], [x, y, z], subset=True) + [u*x, u*y + v*x, u*z + w*x, v*z + w*y] + >>> convolution([u, v, w], [x, y, z], subset=True, cycle=3) + [u*x + v*z + w*y, u*y + v*x, u*z + w*x] + + """ + + c = as_int(cycle) + if c < 0: + raise ValueError("The length for cyclic convolution " + "must be non-negative") + + dyadic = True if dyadic else None + subset = True if subset else None + if sum(x is not None for x in (prime, dps, dyadic, subset)) > 1: + raise TypeError("Ambiguity in determining the type of convolution") + + if prime is not None: + ls = convolution_ntt(a, b, prime=prime) + return ls if not c else [sum(ls[i::c]) % prime for i in range(c)] + + if dyadic: + ls = convolution_fwht(a, b) + elif subset: + ls = convolution_subset(a, b) + else: + def loop(a): + dens = [] + for i in a: + if isinstance(i, Rational) and i.q - 1: + dens.append(i.q) + elif not isinstance(i, int): + return + if dens: + l = lcm(*dens) + return [i*l if type(i) is int else i.p*(l//i.q) for i in a], l + # no lcm of den to deal with + return a, 1 + ls = None + da = loop(a) + if da is not None: + db = loop(b) + if db is not None: + (ia, ma), (ib, mb) = da, db + den = ma*mb + ls = convolution_int(ia, ib) + if den != 1: + ls = [Rational(i, den) for i in ls] + if ls is None: + ls = convolution_fft(a, b, dps) + + return ls if not c else [sum(ls[i::c]) for i in range(c)] + + +#----------------------------------------------------------------------------# +# # +# Convolution for Complex domain # +# # +#----------------------------------------------------------------------------# + +def convolution_fft(a, b, dps=None): + """ + Performs linear convolution using Fast Fourier Transform. + + Parameters + ========== + + a, b : iterables + The sequences for which convolution is performed. + dps : Integer + Specifies the number of decimal digits for precision. + + Examples + ======== + + >>> from sympy import S, I + >>> from sympy.discrete.convolutions import convolution_fft + + >>> convolution_fft([2, 3], [4, 5]) + [8, 22, 15] + >>> convolution_fft([2, 5], [6, 7, 3]) + [12, 44, 41, 15] + >>> convolution_fft([1 + 2*I, 4 + 3*I], [S(5)/4, 6]) + [5/4 + 5*I/2, 11 + 63*I/4, 24 + 18*I] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Convolution_theorem + .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29 + + """ + + a, b = a[:], b[:] + n = m = len(a) + len(b) - 1 # convolution size + + if n > 0 and n&(n - 1): # not a power of 2 + n = 2**n.bit_length() + + # padding with zeros + a += [S.Zero]*(n - len(a)) + b += [S.Zero]*(n - len(b)) + + a, b = fft(a, dps), fft(b, dps) + a = [expand_mul(x*y) for x, y in zip(a, b)] + a = ifft(a, dps)[:m] + + return a + + +#----------------------------------------------------------------------------# +# # +# Convolution for GF(p) # +# # +#----------------------------------------------------------------------------# + +def convolution_ntt(a, b, prime): + """ + Performs linear convolution using Number Theoretic Transform. + + Parameters + ========== + + a, b : iterables + The sequences for which convolution is performed. + prime : Integer + Prime modulus of the form `(m 2^k + 1)` to be used for performing + **NTT** on the sequence. + + Examples + ======== + + >>> from sympy.discrete.convolutions import convolution_ntt + >>> convolution_ntt([2, 3], [4, 5], prime=19*2**10 + 1) + [8, 22, 15] + >>> convolution_ntt([2, 5], [6, 7, 3], prime=19*2**10 + 1) + [12, 44, 41, 15] + >>> convolution_ntt([333, 555], [222, 666], prime=19*2**10 + 1) + [15555, 14219, 19404] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Convolution_theorem + .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29 + + """ + + a, b, p = a[:], b[:], as_int(prime) + n = m = len(a) + len(b) - 1 # convolution size + + if n > 0 and n&(n - 1): # not a power of 2 + n = 2**n.bit_length() + + # padding with zeros + a += [0]*(n - len(a)) + b += [0]*(n - len(b)) + + a, b = ntt(a, p), ntt(b, p) + a = [x*y % p for x, y in zip(a, b)] + a = intt(a, p)[:m] + + return a + + +#----------------------------------------------------------------------------# +# # +# Convolution for 2**n-group # +# # +#----------------------------------------------------------------------------# + +def convolution_fwht(a, b): + """ + Performs dyadic (*bitwise-XOR*) convolution using Fast Walsh Hadamard + Transform. + + The convolution is automatically padded to the right with zeros, as the + *radix-2 FWHT* requires the number of sample points to be a power of 2. + + Parameters + ========== + + a, b : iterables + The sequences for which convolution is performed. + + Examples + ======== + + >>> from sympy import symbols, S, I + >>> from sympy.discrete.convolutions import convolution_fwht + + >>> u, v, x, y = symbols('u v x y') + >>> convolution_fwht([u, v], [x, y]) + [u*x + v*y, u*y + v*x] + + >>> convolution_fwht([2, 3], [4, 5]) + [23, 22] + >>> convolution_fwht([2, 5 + 4*I, 7], [6*I, 7, 3 + 4*I]) + [56 + 68*I, -10 + 30*I, 6 + 50*I, 48 + 32*I] + + >>> convolution_fwht([S(33)/7, S(55)/6, S(7)/4], [S(2)/3, 5]) + [2057/42, 1870/63, 7/6, 35/4] + + References + ========== + + .. [1] https://www.radioeng.cz/fulltexts/2002/02_03_40_42.pdf + .. [2] https://en.wikipedia.org/wiki/Hadamard_transform + + """ + + if not a or not b: + return [] + + a, b = a[:], b[:] + n = max(len(a), len(b)) + + if n&(n - 1): # not a power of 2 + n = 2**n.bit_length() + + # padding with zeros + a += [S.Zero]*(n - len(a)) + b += [S.Zero]*(n - len(b)) + + a, b = fwht(a), fwht(b) + a = [expand_mul(x*y) for x, y in zip(a, b)] + a = ifwht(a) + + return a + + +#----------------------------------------------------------------------------# +# # +# Subset Convolution # +# # +#----------------------------------------------------------------------------# + +def convolution_subset(a, b): + """ + Performs Subset Convolution of given sequences. + + The indices of each argument, considered as bit strings, correspond to + subsets of a finite set. + + The sequence is automatically padded to the right with zeros, as the + definition of subset based on bitmasks (indices) requires the size of + sequence to be a power of 2. + + Parameters + ========== + + a, b : iterables + The sequences for which convolution is performed. + + Examples + ======== + + >>> from sympy import symbols, S + >>> from sympy.discrete.convolutions import convolution_subset + >>> u, v, x, y, z = symbols('u v x y z') + + >>> convolution_subset([u, v], [x, y]) + [u*x, u*y + v*x] + >>> convolution_subset([u, v, x], [y, z]) + [u*y, u*z + v*y, x*y, x*z] + + >>> convolution_subset([1, S(2)/3], [3, 4]) + [3, 6] + >>> convolution_subset([1, 3, S(5)/7], [7]) + [7, 21, 5, 0] + + References + ========== + + .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf + + """ + + if not a or not b: + return [] + + if not iterable(a) or not iterable(b): + raise TypeError("Expected a sequence of coefficients for convolution") + + a = [sympify(arg) for arg in a] + b = [sympify(arg) for arg in b] + n = max(len(a), len(b)) + + if n&(n - 1): # not a power of 2 + n = 2**n.bit_length() + + # padding with zeros + a += [S.Zero]*(n - len(a)) + b += [S.Zero]*(n - len(b)) + + c = [S.Zero]*n + + for mask in range(n): + smask = mask + while smask > 0: + c[mask] += expand_mul(a[smask] * b[mask^smask]) + smask = (smask - 1)&mask + + c[mask] += expand_mul(a[smask] * b[mask^smask]) + + return c + + +#----------------------------------------------------------------------------# +# # +# Covering Product # +# # +#----------------------------------------------------------------------------# + +def covering_product(a, b): + """ + Returns the covering product of given sequences. + + The indices of each argument, considered as bit strings, correspond to + subsets of a finite set. + + The covering product of given sequences is a sequence which contains + the sum of products of the elements of the given sequences grouped by + the *bitwise-OR* of the corresponding indices. + + The sequence is automatically padded to the right with zeros, as the + definition of subset based on bitmasks (indices) requires the size of + sequence to be a power of 2. + + Parameters + ========== + + a, b : iterables + The sequences for which covering product is to be obtained. + + Examples + ======== + + >>> from sympy import symbols, S, I, covering_product + >>> u, v, x, y, z = symbols('u v x y z') + + >>> covering_product([u, v], [x, y]) + [u*x, u*y + v*x + v*y] + >>> covering_product([u, v, x], [y, z]) + [u*y, u*z + v*y + v*z, x*y, x*z] + + >>> covering_product([1, S(2)/3], [3, 4 + 5*I]) + [3, 26/3 + 25*I/3] + >>> covering_product([1, 3, S(5)/7], [7, 8]) + [7, 53, 5, 40/7] + + References + ========== + + .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf + + """ + + if not a or not b: + return [] + + a, b = a[:], b[:] + n = max(len(a), len(b)) + + if n&(n - 1): # not a power of 2 + n = 2**n.bit_length() + + # padding with zeros + a += [S.Zero]*(n - len(a)) + b += [S.Zero]*(n - len(b)) + + a, b = mobius_transform(a), mobius_transform(b) + a = [expand_mul(x*y) for x, y in zip(a, b)] + a = inverse_mobius_transform(a) + + return a + + +#----------------------------------------------------------------------------# +# # +# Intersecting Product # +# # +#----------------------------------------------------------------------------# + +def intersecting_product(a, b): + """ + Returns the intersecting product of given sequences. + + The indices of each argument, considered as bit strings, correspond to + subsets of a finite set. + + The intersecting product of given sequences is the sequence which + contains the sum of products of the elements of the given sequences + grouped by the *bitwise-AND* of the corresponding indices. + + The sequence is automatically padded to the right with zeros, as the + definition of subset based on bitmasks (indices) requires the size of + sequence to be a power of 2. + + Parameters + ========== + + a, b : iterables + The sequences for which intersecting product is to be obtained. + + Examples + ======== + + >>> from sympy import symbols, S, I, intersecting_product + >>> u, v, x, y, z = symbols('u v x y z') + + >>> intersecting_product([u, v], [x, y]) + [u*x + u*y + v*x, v*y] + >>> intersecting_product([u, v, x], [y, z]) + [u*y + u*z + v*y + x*y + x*z, v*z, 0, 0] + + >>> intersecting_product([1, S(2)/3], [3, 4 + 5*I]) + [9 + 5*I, 8/3 + 10*I/3] + >>> intersecting_product([1, 3, S(5)/7], [7, 8]) + [327/7, 24, 0, 0] + + References + ========== + + .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf + + """ + + if not a or not b: + return [] + + a, b = a[:], b[:] + n = max(len(a), len(b)) + + if n&(n - 1): # not a power of 2 + n = 2**n.bit_length() + + # padding with zeros + a += [S.Zero]*(n - len(a)) + b += [S.Zero]*(n - len(b)) + + a, b = mobius_transform(a, subset=False), mobius_transform(b, subset=False) + a = [expand_mul(x*y) for x, y in zip(a, b)] + a = inverse_mobius_transform(a, subset=False) + + return a + + +#----------------------------------------------------------------------------# +# # +# Integer Convolutions # +# # +#----------------------------------------------------------------------------# + +def convolution_int(a, b): + """Return the convolution of two sequences as a list. + + The iterables must consist solely of integers. + + Parameters + ========== + + a, b : Sequence + The sequences for which convolution is performed. + + Explanation + =========== + + This function performs the convolution of ``a`` and ``b`` by packing + each into a single integer, multiplying them together, and then + unpacking the result from the product. The intuition behind this is + that if we evaluate some polynomial [1]: + + .. math :: + 1156x^6 + 3808x^5 + 8440x^4 + 14856x^3 + 16164x^2 + 14040x + 8100 + + at say $x = 10^5$ we obtain $1156038080844014856161641404008100$. + Note we can read of the coefficients for each term every five digits. + If the $x$ we chose to evaluate at is large enough, the same will hold + for the product. + + The idea now is since big integer multiplication in libraries such + as GMP is highly optimised, this will be reasonably fast. + + Examples + ======== + + >>> from sympy.discrete.convolutions import convolution_int + + >>> convolution_int([2, 3], [4, 5]) + [8, 22, 15] + >>> convolution_int([1, 1, -1], [1, 1]) + [1, 2, 0, -1] + + References + ========== + + .. [1] Fateman, Richard J. + Can you save time in multiplying polynomials by encoding them as integers? + University of California, Berkeley, California (2004). + https://people.eecs.berkeley.edu/~fateman/papers/polysbyGMP.pdf + """ + # An upper bound on the largest coefficient in p(x)q(x) is given by (1 + min(dp, dq))N(p)N(q) + # where dp = deg(p), dq = deg(q), N(f) denotes the coefficient of largest modulus in f [1] + B = max(abs(c) for c in a)*max(abs(c) for c in b)*(1 + min(len(a) - 1, len(b) - 1)) + x, power = MPZ(1), 0 + while x <= (2*B): # multiply by two for negative coefficients, see [1] + x <<= 1 + power += 1 + + def to_integer(poly): + n, mul = MPZ(0), 0 + for c in reversed(poly): + if c and not mul: mul = -1 if c < 0 else 1 + n <<= power + n += mul*int(c) + return mul, n + + # Perform packing and multiplication + (a_mul, a_packed), (b_mul, b_packed) = to_integer(a), to_integer(b) + result = a_packed * b_packed + + # Perform unpacking + mul = a_mul * b_mul + mask, half, borrow, poly = x - 1, x >> 1, 0, [] + while result or borrow: + coeff = (result & mask) + borrow + result >>= power + borrow = coeff >= half + poly.append(mul * int(coeff if coeff < half else coeff - x)) + return poly or [0] diff --git a/phivenv/Lib/site-packages/sympy/discrete/recurrences.py b/phivenv/Lib/site-packages/sympy/discrete/recurrences.py new file mode 100644 index 0000000000000000000000000000000000000000..0b0ed80d304161cf9ca298321aedc094c8cae1b3 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/recurrences.py @@ -0,0 +1,166 @@ +""" +Recurrences +""" + +from sympy.core import S, sympify +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import as_int + + +def linrec(coeffs, init, n): + r""" + Evaluation of univariate linear recurrences of homogeneous type + having coefficients independent of the recurrence variable. + + Parameters + ========== + + coeffs : iterable + Coefficients of the recurrence + init : iterable + Initial values of the recurrence + n : Integer + Point of evaluation for the recurrence + + Notes + ===== + + Let `y(n)` be the recurrence of given type, ``c`` be the sequence + of coefficients, ``b`` be the sequence of initial/base values of the + recurrence and ``k`` (equal to ``len(c)``) be the order of recurrence. + Then, + + .. math :: y(n) = \begin{cases} b_n & 0 \le n < k \\ + c_0 y(n-1) + c_1 y(n-2) + \cdots + c_{k-1} y(n-k) & n \ge k + \end{cases} + + Let `x_0, x_1, \ldots, x_n` be a sequence and consider the transformation + that maps each polynomial `f(x)` to `T(f(x))` where each power `x^i` is + replaced by the corresponding value `x_i`. The sequence is then a solution + of the recurrence if and only if `T(x^i p(x)) = 0` for each `i \ge 0` where + `p(x) = x^k - c_0 x^(k-1) - \cdots - c_{k-1}` is the characteristic + polynomial. + + Then `T(f(x)p(x)) = 0` for each polynomial `f(x)` (as it is a linear + combination of powers `x^i`). Now, if `x^n` is congruent to + `g(x) = a_0 x^0 + a_1 x^1 + \cdots + a_{k-1} x^{k-1}` modulo `p(x)`, then + `T(x^n) = x_n` is equal to + `T(g(x)) = a_0 x_0 + a_1 x_1 + \cdots + a_{k-1} x_{k-1}`. + + Computation of `x^n`, + given `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}` + is performed using exponentiation by squaring (refer to [1_]) with + an additional reduction step performed to retain only first `k` powers + of `x` in the representation of `x^n`. + + Examples + ======== + + >>> from sympy.discrete.recurrences import linrec + >>> from sympy.abc import x, y, z + + >>> linrec(coeffs=[1, 1], init=[0, 1], n=10) + 55 + + >>> linrec(coeffs=[1, 1], init=[x, y], n=10) + 34*x + 55*y + + >>> linrec(coeffs=[x, y], init=[0, 1], n=5) + x**2*y + x*(x**3 + 2*x*y) + y**2 + + >>> linrec(coeffs=[1, 2, 3, 0, 0, 4], init=[x, y, z], n=16) + 13576*x + 5676*y + 2356*z + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Exponentiation_by_squaring + .. [2] https://en.wikipedia.org/w/index.php?title=Modular_exponentiation§ion=6#Matrices + + See Also + ======== + + sympy.polys.agca.extensions.ExtensionElement.__pow__ + + """ + + if not coeffs: + return S.Zero + + if not iterable(coeffs): + raise TypeError("Expected a sequence of coefficients for" + " the recurrence") + + if not iterable(init): + raise TypeError("Expected a sequence of values for the initialization" + " of the recurrence") + + n = as_int(n) + if n < 0: + raise ValueError("Point of evaluation of recurrence must be a " + "non-negative integer") + + c = [sympify(arg) for arg in coeffs] + b = [sympify(arg) for arg in init] + k = len(c) + + if len(b) > k: + raise TypeError("Count of initial values should not exceed the " + "order of the recurrence") + else: + b += [S.Zero]*(k - len(b)) # remaining initial values default to zero + + if n < k: + return b[n] + terms = [u*v for u, v in zip(linrec_coeffs(c, n), b)] + return sum(terms[:-1], terms[-1]) + + +def linrec_coeffs(c, n): + r""" + Compute the coefficients of n'th term in linear recursion + sequence defined by c. + + `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`. + + It computes the coefficients by using binary exponentiation. + This function is used by `linrec` and `_eval_pow_by_cayley`. + + Parameters + ========== + + c = coefficients of the divisor polynomial + n = exponent of x, so dividend is x^n + + """ + + k = len(c) + + def _square_and_reduce(u, offset): + # squares `(u_0 + u_1 x + u_2 x^2 + \cdots + u_{k-1} x^k)` (and + # multiplies by `x` if offset is 1) and reduces the above result of + # length upto `2k` to `k` using the characteristic equation of the + # recurrence given by, `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}` + + w = [S.Zero]*(2*len(u) - 1 + offset) + for i, p in enumerate(u): + for j, q in enumerate(u): + w[offset + i + j] += p*q + + for j in range(len(w) - 1, k - 1, -1): + for i in range(k): + w[j - i - 1] += w[j]*c[i] + + return w[:k] + + def _final_coeffs(n): + # computes the final coefficient list - `cf` corresponding to the + # point at which recurrence is to be evalauted - `n`, such that, + # `y(n) = cf_0 y(k-1) + cf_1 y(k-2) + \cdots + cf_{k-1} y(0)` + + if n < k: + return [S.Zero]*n + [S.One] + [S.Zero]*(k - n - 1) + else: + return _square_and_reduce(_final_coeffs(n // 2), n % 2) + + return _final_coeffs(n) diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/__init__.py b/phivenv/Lib/site-packages/sympy/discrete/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf907478e2cc742ddba5f8119aaed36aef987ae0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_convolutions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_convolutions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee88aa94c2c13129bdf7981251a1178cf048cc90 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_convolutions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_recurrences.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_recurrences.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c235eb0dac5ab011516c8f8da5c2bdf69a2381f0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_recurrences.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_transforms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_transforms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da655c19a35446bc2f9fc4bfe4a8155e0c6de27a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/discrete/tests/__pycache__/test_transforms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/test_convolutions.py b/phivenv/Lib/site-packages/sympy/discrete/tests/test_convolutions.py new file mode 100644 index 0000000000000000000000000000000000000000..96e5fc801ac63f95c01eb18d48143ae3a1ac6222 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/tests/test_convolutions.py @@ -0,0 +1,392 @@ +from sympy.core.numbers import (E, Rational, pi) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.core import S, symbols, I +from sympy.discrete.convolutions import ( + convolution, convolution_fft, convolution_ntt, convolution_fwht, + convolution_subset, covering_product, intersecting_product, + convolution_int) +from sympy.testing.pytest import raises +from sympy.abc import x, y + +def test_convolution(): + # fft + a = [1, Rational(5, 3), sqrt(3), Rational(7, 5)] + b = [9, 5, 5, 4, 3, 2] + c = [3, 5, 3, 7, 8] + d = [1422, 6572, 3213, 5552] + e = [-1, Rational(5, 3), Rational(7, 5)] + + assert convolution(a, b) == convolution_fft(a, b) + assert convolution(a, b, dps=9) == convolution_fft(a, b, dps=9) + assert convolution(a, d, dps=7) == convolution_fft(d, a, dps=7) + assert convolution(a, d[1:], dps=3) == convolution_fft(d[1:], a, dps=3) + + # prime moduli of the form (m*2**k + 1), sequence length + # should be a divisor of 2**k + p = 7*17*2**23 + 1 + q = 19*2**10 + 1 + + # ntt + assert convolution(d, b, prime=q) == convolution_ntt(b, d, prime=q) + assert convolution(c, b, prime=p) == convolution_ntt(b, c, prime=p) + assert convolution(d, c, prime=p) == convolution_ntt(c, d, prime=p) + raises(TypeError, lambda: convolution(b, d, dps=5, prime=q)) + raises(TypeError, lambda: convolution(b, d, dps=6, prime=q)) + + # fwht + assert convolution(a, b, dyadic=True) == convolution_fwht(a, b) + assert convolution(a, b, dyadic=False) == convolution(a, b) + raises(TypeError, lambda: convolution(b, d, dps=2, dyadic=True)) + raises(TypeError, lambda: convolution(b, d, prime=p, dyadic=True)) + raises(TypeError, lambda: convolution(a, b, dps=2, dyadic=True)) + raises(TypeError, lambda: convolution(b, c, prime=p, dyadic=True)) + + # subset + assert convolution(a, b, subset=True) == convolution_subset(a, b) == \ + convolution(a, b, subset=True, dyadic=False) == \ + convolution(a, b, subset=True) + assert convolution(a, b, subset=False) == convolution(a, b) + raises(TypeError, lambda: convolution(a, b, subset=True, dyadic=True)) + raises(TypeError, lambda: convolution(c, d, subset=True, dps=6)) + raises(TypeError, lambda: convolution(a, c, subset=True, prime=q)) + + # integer + assert convolution([0], [0]) == convolution_int([0], [0]) + assert convolution(b, c) == convolution_int(b, c) + + # rational + assert convolution([Rational(1,2)], [Rational(1,2)]) == [Rational(1, 4)] + assert convolution(b, e) == [-9, 10, Rational(239, 15), Rational(34, 3), + Rational(32, 3), Rational(43, 5), Rational(113, 15), + Rational(14, 5)] + + +def test_cyclic_convolution(): + # fft + a = [1, Rational(5, 3), sqrt(3), Rational(7, 5)] + b = [9, 5, 5, 4, 3, 2] + + assert convolution([1, 2, 3], [4, 5, 6], cycle=0) == \ + convolution([1, 2, 3], [4, 5, 6], cycle=5) == \ + convolution([1, 2, 3], [4, 5, 6]) + + assert convolution([1, 2, 3], [4, 5, 6], cycle=3) == [31, 31, 28] + + a = [Rational(1, 3), Rational(7, 3), Rational(5, 9), Rational(2, 7), Rational(5, 8)] + b = [Rational(3, 5), Rational(4, 7), Rational(7, 8), Rational(8, 9)] + + assert convolution(a, b, cycle=0) == \ + convolution(a, b, cycle=len(a) + len(b) - 1) + + assert convolution(a, b, cycle=4) == [Rational(87277, 26460), Rational(30521, 11340), + Rational(11125, 4032), Rational(3653, 1080)] + + assert convolution(a, b, cycle=6) == [Rational(20177, 20160), Rational(676, 315), Rational(47, 24), + Rational(3053, 1080), Rational(16397, 5292), Rational(2497, 2268)] + + assert convolution(a, b, cycle=9) == \ + convolution(a, b, cycle=0) + [S.Zero] + + # ntt + a = [2313, 5323532, S(3232), 42142, 42242421] + b = [S(33456), 56757, 45754, 432423] + + assert convolution(a, b, prime=19*2**10 + 1, cycle=0) == \ + convolution(a, b, prime=19*2**10 + 1, cycle=8) == \ + convolution(a, b, prime=19*2**10 + 1) + + assert convolution(a, b, prime=19*2**10 + 1, cycle=5) == [96, 17146, 2664, + 15534, 3517] + + assert convolution(a, b, prime=19*2**10 + 1, cycle=7) == [4643, 3458, 1260, + 15534, 3517, 16314, 13688] + + assert convolution(a, b, prime=19*2**10 + 1, cycle=9) == \ + convolution(a, b, prime=19*2**10 + 1) + [0] + + # fwht + u, v, w, x, y = symbols('u v w x y') + p, q, r, s, t = symbols('p q r s t') + c = [u, v, w, x, y] + d = [p, q, r, s, t] + + assert convolution(a, b, dyadic=True, cycle=3) == \ + [2499522285783, 19861417974796, 4702176579021] + + assert convolution(a, b, dyadic=True, cycle=5) == [2718149225143, + 2114320852171, 20571217906407, 246166418903, 1413262436976] + + assert convolution(c, d, dyadic=True, cycle=4) == \ + [p*u + p*y + q*v + r*w + s*x + t*u + t*y, + p*v + q*u + q*y + r*x + s*w + t*v, + p*w + q*x + r*u + r*y + s*v + t*w, + p*x + q*w + r*v + s*u + s*y + t*x] + + assert convolution(c, d, dyadic=True, cycle=6) == \ + [p*u + q*v + r*w + r*y + s*x + t*w + t*y, + p*v + q*u + r*x + s*w + s*y + t*x, + p*w + q*x + r*u + s*v, + p*x + q*w + r*v + s*u, + p*y + t*u, + q*y + t*v] + + # subset + assert convolution(a, b, subset=True, cycle=7) == [18266671799811, + 178235365533, 213958794, 246166418903, 1413262436976, + 2397553088697, 1932759730434] + + assert convolution(a[1:], b, subset=True, cycle=4) == \ + [178104086592, 302255835516, 244982785880, 3717819845434] + + assert convolution(a, b[:-1], subset=True, cycle=6) == [1932837114162, + 178235365533, 213958794, 245166224504, 1413262436976, 2397553088697] + + assert convolution(c, d, subset=True, cycle=3) == \ + [p*u + p*x + q*w + r*v + r*y + s*u + t*w, + p*v + p*y + q*u + s*y + t*u + t*x, + p*w + q*y + r*u + t*v] + + assert convolution(c, d, subset=True, cycle=5) == \ + [p*u + q*y + t*v, + p*v + q*u + r*y + t*w, + p*w + r*u + s*y + t*x, + p*x + q*w + r*v + s*u, + p*y + t*u] + + raises(ValueError, lambda: convolution([1, 2, 3], [4, 5, 6], cycle=-1)) + + +def test_convolution_fft(): + assert all(convolution_fft([], x, dps=y) == [] for x in ([], [1]) for y in (None, 3)) + assert convolution_fft([1, 2, 3], [4, 5, 6]) == [4, 13, 28, 27, 18] + assert convolution_fft([1], [5, 6, 7]) == [5, 6, 7] + assert convolution_fft([1, 3], [5, 6, 7]) == [5, 21, 25, 21] + + assert convolution_fft([1 + 2*I], [2 + 3*I]) == [-4 + 7*I] + assert convolution_fft([1 + 2*I, 3 + 4*I, 5 + 3*I/5], [Rational(2, 5) + 4*I/7]) == \ + [Rational(-26, 35) + I*48/35, Rational(-38, 35) + I*116/35, Rational(58, 35) + I*542/175] + + assert convolution_fft([Rational(3, 4), Rational(5, 6)], [Rational(7, 8), Rational(1, 3), Rational(2, 5)]) == \ + [Rational(21, 32), Rational(47, 48), Rational(26, 45), Rational(1, 3)] + + assert convolution_fft([Rational(1, 9), Rational(2, 3), Rational(3, 5)], [Rational(2, 5), Rational(3, 7), Rational(4, 9)]) == \ + [Rational(2, 45), Rational(11, 35), Rational(8152, 14175), Rational(523, 945), Rational(4, 15)] + + assert convolution_fft([pi, E, sqrt(2)], [sqrt(3), 1/pi, 1/E]) == \ + [sqrt(3)*pi, 1 + sqrt(3)*E, E/pi + pi*exp(-1) + sqrt(6), + sqrt(2)/pi + 1, sqrt(2)*exp(-1)] + + assert convolution_fft([2321, 33123], [5321, 6321, 71323]) == \ + [12350041, 190918524, 374911166, 2362431729] + + assert convolution_fft([312313, 31278232], [32139631, 319631]) == \ + [10037624576503, 1005370659728895, 9997492572392] + + raises(TypeError, lambda: convolution_fft(x, y)) + raises(ValueError, lambda: convolution_fft([x, y], [y, x])) + + +def test_convolution_ntt(): + # prime moduli of the form (m*2**k + 1), sequence length + # should be a divisor of 2**k + p = 7*17*2**23 + 1 + q = 19*2**10 + 1 + r = 2*500000003 + 1 # only for sequences of length 1 or 2 + # s = 2*3*5*7 # composite modulus + + assert all(convolution_ntt([], x, prime=y) == [] for x in ([], [1]) for y in (p, q, r)) + assert convolution_ntt([2], [3], r) == [6] + assert convolution_ntt([2, 3], [4], r) == [8, 12] + + assert convolution_ntt([32121, 42144, 4214, 4241], [32132, 3232, 87242], p) == [33867619, + 459741727, 79180879, 831885249, 381344700, 369993322] + assert convolution_ntt([121913, 3171831, 31888131, 12], [17882, 21292, 29921, 312], q) == \ + [8158, 3065, 3682, 7090, 1239, 2232, 3744] + + assert convolution_ntt([12, 19, 21, 98, 67], [2, 6, 7, 8, 9], p) == \ + convolution_ntt([12, 19, 21, 98, 67], [2, 6, 7, 8, 9], q) + assert convolution_ntt([12, 19, 21, 98, 67], [21, 76, 17, 78, 69], p) == \ + convolution_ntt([12, 19, 21, 98, 67], [21, 76, 17, 78, 69], q) + + raises(ValueError, lambda: convolution_ntt([2, 3], [4, 5], r)) + raises(ValueError, lambda: convolution_ntt([x, y], [y, x], q)) + raises(TypeError, lambda: convolution_ntt(x, y, p)) + + +def test_convolution_fwht(): + assert convolution_fwht([], []) == [] + assert convolution_fwht([], [1]) == [] + assert convolution_fwht([1, 2, 3], [4, 5, 6]) == [32, 13, 18, 27] + + assert convolution_fwht([Rational(5, 7), Rational(6, 8), Rational(7, 3)], [2, 4, Rational(6, 7)]) == \ + [Rational(45, 7), Rational(61, 14), Rational(776, 147), Rational(419, 42)] + + a = [1, Rational(5, 3), sqrt(3), Rational(7, 5), 4 + 5*I] + b = [94, 51, 53, 45, 31, 27, 13] + c = [3 + 4*I, 5 + 7*I, 3, Rational(7, 6), 8] + + assert convolution_fwht(a, b) == [53*sqrt(3) + 366 + 155*I, + 45*sqrt(3) + Rational(5848, 15) + 135*I, + 94*sqrt(3) + Rational(1257, 5) + 65*I, + 51*sqrt(3) + Rational(3974, 15), + 13*sqrt(3) + 452 + 470*I, + Rational(4513, 15) + 255*I, + 31*sqrt(3) + Rational(1314, 5) + 265*I, + 27*sqrt(3) + Rational(3676, 15) + 225*I] + + assert convolution_fwht(b, c) == [Rational(1993, 2) + 733*I, Rational(6215, 6) + 862*I, + Rational(1659, 2) + 527*I, Rational(1988, 3) + 551*I, 1019 + 313*I, Rational(3955, 6) + 325*I, + Rational(1175, 2) + 52*I, Rational(3253, 6) + 91*I] + + assert convolution_fwht(a[3:], c) == [Rational(-54, 5) + I*293/5, -1 + I*204/5, + Rational(133, 15) + I*35/6, Rational(409, 30) + 15*I, Rational(56, 5), 32 + 40*I, 0, 0] + + u, v, w, x, y, z = symbols('u v w x y z') + + assert convolution_fwht([u, v], [x, y]) == [u*x + v*y, u*y + v*x] + + assert convolution_fwht([u, v, w], [x, y]) == \ + [u*x + v*y, u*y + v*x, w*x, w*y] + + assert convolution_fwht([u, v, w], [x, y, z]) == \ + [u*x + v*y + w*z, u*y + v*x, u*z + w*x, v*z + w*y] + + raises(TypeError, lambda: convolution_fwht(x, y)) + raises(TypeError, lambda: convolution_fwht(x*y, u + v)) + + +def test_convolution_subset(): + assert convolution_subset([], []) == [] + assert convolution_subset([], [Rational(1, 3)]) == [] + assert convolution_subset([6 + I*3/7], [Rational(2, 3)]) == [4 + I*2/7] + + a = [1, Rational(5, 3), sqrt(3), 4 + 5*I] + b = [64, 71, 55, 47, 33, 29, 15] + c = [3 + I*2/3, 5 + 7*I, 7, Rational(7, 5), 9] + + assert convolution_subset(a, b) == [64, Rational(533, 3), 55 + 64*sqrt(3), + 71*sqrt(3) + Rational(1184, 3) + 320*I, 33, 84, + 15 + 33*sqrt(3), 29*sqrt(3) + 157 + 165*I] + + assert convolution_subset(b, c) == [192 + I*128/3, 533 + I*1486/3, + 613 + I*110/3, Rational(5013, 5) + I*1249/3, + 675 + 22*I, 891 + I*751/3, + 771 + 10*I, Rational(3736, 5) + 105*I] + + assert convolution_subset(a, c) == convolution_subset(c, a) + assert convolution_subset(a[:2], b) == \ + [64, Rational(533, 3), 55, Rational(416, 3), 33, 84, 15, 25] + + assert convolution_subset(a[:2], c) == \ + [3 + I*2/3, 10 + I*73/9, 7, Rational(196, 15), 9, 15, 0, 0] + + u, v, w, x, y, z = symbols('u v w x y z') + + assert convolution_subset([u, v, w], [x, y]) == [u*x, u*y + v*x, w*x, w*y] + assert convolution_subset([u, v, w, x], [y, z]) == \ + [u*y, u*z + v*y, w*y, w*z + x*y] + + assert convolution_subset([u, v], [x, y, z]) == \ + convolution_subset([x, y, z], [u, v]) + + raises(TypeError, lambda: convolution_subset(x, z)) + raises(TypeError, lambda: convolution_subset(Rational(7, 3), u)) + + +def test_covering_product(): + assert covering_product([], []) == [] + assert covering_product([], [Rational(1, 3)]) == [] + assert covering_product([6 + I*3/7], [Rational(2, 3)]) == [4 + I*2/7] + + a = [1, Rational(5, 8), sqrt(7), 4 + 9*I] + b = [66, 81, 95, 49, 37, 89, 17] + c = [3 + I*2/3, 51 + 72*I, 7, Rational(7, 15), 91] + + assert covering_product(a, b) == [66, Rational(1383, 8), 95 + 161*sqrt(7), + 130*sqrt(7) + 1303 + 2619*I, 37, + Rational(671, 4), 17 + 54*sqrt(7), + 89*sqrt(7) + Rational(4661, 8) + 1287*I] + + assert covering_product(b, c) == [198 + 44*I, 7740 + 10638*I, + 1412 + I*190/3, Rational(42684, 5) + I*31202/3, + 9484 + I*74/3, 22163 + I*27394/3, + 10621 + I*34/3, Rational(90236, 15) + 1224*I] + + assert covering_product(a, c) == covering_product(c, a) + assert covering_product(b, c[:-1]) == [198 + 44*I, 7740 + 10638*I, + 1412 + I*190/3, Rational(42684, 5) + I*31202/3, + 111 + I*74/3, 6693 + I*27394/3, + 429 + I*34/3, Rational(23351, 15) + 1224*I] + + assert covering_product(a, c[:-1]) == [3 + I*2/3, + Rational(339, 4) + I*1409/12, 7 + 10*sqrt(7) + 2*sqrt(7)*I/3, + -403 + 772*sqrt(7)/15 + 72*sqrt(7)*I + I*12658/15] + + u, v, w, x, y, z = symbols('u v w x y z') + + assert covering_product([u, v, w], [x, y]) == \ + [u*x, u*y + v*x + v*y, w*x, w*y] + + assert covering_product([u, v, w, x], [y, z]) == \ + [u*y, u*z + v*y + v*z, w*y, w*z + x*y + x*z] + + assert covering_product([u, v], [x, y, z]) == \ + covering_product([x, y, z], [u, v]) + + raises(TypeError, lambda: covering_product(x, z)) + raises(TypeError, lambda: covering_product(Rational(7, 3), u)) + + +def test_intersecting_product(): + assert intersecting_product([], []) == [] + assert intersecting_product([], [Rational(1, 3)]) == [] + assert intersecting_product([6 + I*3/7], [Rational(2, 3)]) == [4 + I*2/7] + + a = [1, sqrt(5), Rational(3, 8) + 5*I, 4 + 7*I] + b = [67, 51, 65, 48, 36, 79, 27] + c = [3 + I*2/5, 5 + 9*I, 7, Rational(7, 19), 13] + + assert intersecting_product(a, b) == [195*sqrt(5) + Rational(6979, 8) + 1886*I, + 178*sqrt(5) + 520 + 910*I, Rational(841, 2) + 1344*I, + 192 + 336*I, 0, 0, 0, 0] + + assert intersecting_product(b, c) == [Rational(128553, 19) + I*9521/5, + Rational(17820, 19) + 1602*I, Rational(19264, 19), Rational(336, 19), 1846, 0, 0, 0] + + assert intersecting_product(a, c) == intersecting_product(c, a) + assert intersecting_product(b[1:], c[:-1]) == [Rational(64788, 19) + I*8622/5, + Rational(12804, 19) + 1152*I, Rational(11508, 19), Rational(252, 19), 0, 0, 0, 0] + + assert intersecting_product(a, c[:-2]) == \ + [Rational(-99, 5) + 10*sqrt(5) + 2*sqrt(5)*I/5 + I*3021/40, + -43 + 5*sqrt(5) + 9*sqrt(5)*I + 71*I, Rational(245, 8) + 84*I, 0] + + u, v, w, x, y, z = symbols('u v w x y z') + + assert intersecting_product([u, v, w], [x, y]) == \ + [u*x + u*y + v*x + w*x + w*y, v*y, 0, 0] + + assert intersecting_product([u, v, w, x], [y, z]) == \ + [u*y + u*z + v*y + w*y + w*z + x*y, v*z + x*z, 0, 0] + + assert intersecting_product([u, v], [x, y, z]) == \ + intersecting_product([x, y, z], [u, v]) + + raises(TypeError, lambda: intersecting_product(x, z)) + raises(TypeError, lambda: intersecting_product(u, Rational(8, 3))) + + +def test_convolution_int(): + assert convolution_int([1], [1]) == [1] + assert convolution_int([1, 1], [0]) == [0] + assert convolution_int([1, 2, 3], [4, 5, 6]) == [4, 13, 28, 27, 18] + assert convolution_int([1], [5, 6, 7]) == [5, 6, 7] + assert convolution_int([1, 3], [5, 6, 7]) == [5, 21, 25, 21] + assert convolution_int([10, -5, 1, 3], [-5, 6, 7]) == [-50, 85, 35, -44, 25, 21] + assert convolution_int([0, 1, 0, -1], [1, 0, -1, 0]) == [0, 1, 0, -2, 0, 1] + assert convolution_int( + [-341, -5, 1, 3, -71, -99, 43, 87], + [5, 6, 7, 12, 345, 21, -78, -7, -89] + ) == [-1705, -2071, -2412, -4106, -118035, -9774, 25998, 2981, 5509, + -34317, 19228, 38870, 5485, 1724, -4436, -7743] diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/test_recurrences.py b/phivenv/Lib/site-packages/sympy/discrete/tests/test_recurrences.py new file mode 100644 index 0000000000000000000000000000000000000000..2c2186ca525b6680350a03edbe44ca88f8f95c3c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/tests/test_recurrences.py @@ -0,0 +1,59 @@ +from sympy.core.numbers import Rational +from sympy.functions.combinatorial.numbers import fibonacci +from sympy.core import S, symbols +from sympy.testing.pytest import raises +from sympy.discrete.recurrences import linrec + +def test_linrec(): + assert linrec(coeffs=[1, 1], init=[1, 1], n=20) == 10946 + assert linrec(coeffs=[1, 2, 3, 4, 5], init=[1, 1, 0, 2], n=10) == 1040 + assert linrec(coeffs=[0, 0, 11, 13], init=[23, 27], n=25) == 59628567384 + assert linrec(coeffs=[0, 0, 1, 1, 2], init=[1, 5, 3], n=15) == 165 + assert linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4], n=70) == \ + 56889923441670659718376223533331214868804815612050381493741233489928913241 + assert linrec(coeffs=[0]*55 + [1, 1, 2, 3], init=[0]*50 + [1, 2, 3], n=4000) == \ + 702633573874937994980598979769135096432444135301118916539 + + assert linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4], n=10**4) + assert linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4], n=10**5) + + assert all(linrec(coeffs=[1, 1], init=[0, 1], n=n) == fibonacci(n) + for n in range(95, 115)) + + assert all(linrec(coeffs=[1, 1], init=[1, 1], n=n) == fibonacci(n + 1) + for n in range(595, 615)) + + a = [S.Half, Rational(3, 4), Rational(5, 6), 7, Rational(8, 9), Rational(3, 5)] + b = [1, 2, 8, Rational(5, 7), Rational(3, 7), Rational(2, 9), 6] + x, y, z = symbols('x y z') + + assert linrec(coeffs=a[:5], init=b[:4], n=80) == \ + Rational(1726244235456268979436592226626304376013002142588105090705187189, + 1960143456748895967474334873705475211264) + + assert linrec(coeffs=a[:4], init=b[:4], n=50) == \ + Rational(368949940033050147080268092104304441, 504857282956046106624) + + assert linrec(coeffs=a[3:], init=b[:3], n=35) == \ + Rational(97409272177295731943657945116791049305244422833125109, + 814315512679031689453125) + + assert linrec(coeffs=[0]*60 + [Rational(2, 3), Rational(4, 5)], init=b, n=3000) == \ + Rational(26777668739896791448594650497024, 48084516708184142230517578125) + + raises(TypeError, lambda: linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4, 5], n=1)) + raises(TypeError, lambda: linrec(coeffs=a[:4], init=b[:5], n=10000)) + raises(ValueError, lambda: linrec(coeffs=a[:4], init=b[:4], n=-10000)) + raises(TypeError, lambda: linrec(x, b, n=10000)) + raises(TypeError, lambda: linrec(a, y, n=10000)) + + assert linrec(coeffs=[x, y, z], init=[1, 1, 1], n=4) == \ + x**2 + x*y + x*z + y + z + assert linrec(coeffs=[1, 2, 1], init=[x, y, z], n=20) == \ + 269542*x + 664575*y + 578949*z + assert linrec(coeffs=[0, 3, 1, 2], init=[x, y], n=30) == \ + 58516436*x + 56372788*y + assert linrec(coeffs=[0]*50 + [1, 2, 3], init=[x, y, z], n=1000) == \ + 11477135884896*x + 25999077948732*y + 41975630244216*z + assert linrec(coeffs=[], init=[1, 1], n=20) == 0 + assert linrec(coeffs=[x, y, z], init=[1, 2, 3], n=2) == 3 diff --git a/phivenv/Lib/site-packages/sympy/discrete/tests/test_transforms.py b/phivenv/Lib/site-packages/sympy/discrete/tests/test_transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..385514be4cdec2f19cf3a750bdbe0f4f6e21cc6e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/tests/test_transforms.py @@ -0,0 +1,154 @@ +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.core import S, Symbol, symbols, I, Rational +from sympy.discrete import (fft, ifft, ntt, intt, fwht, ifwht, + mobius_transform, inverse_mobius_transform) +from sympy.testing.pytest import raises + + +def test_fft_ifft(): + assert all(tf(ls) == ls for tf in (fft, ifft) + for ls in ([], [Rational(5, 3)])) + + ls = list(range(6)) + fls = [15, -7*sqrt(2)/2 - 4 - sqrt(2)*I/2 + 2*I, 2 + 3*I, + -4 + 7*sqrt(2)/2 - 2*I - sqrt(2)*I/2, -3, + -4 + 7*sqrt(2)/2 + sqrt(2)*I/2 + 2*I, + 2 - 3*I, -7*sqrt(2)/2 - 4 - 2*I + sqrt(2)*I/2] + + assert fft(ls) == fls + assert ifft(fls) == ls + [S.Zero]*2 + + ls = [1 + 2*I, 3 + 4*I, 5 + 6*I] + ifls = [Rational(9, 4) + 3*I, I*Rational(-7, 4), Rational(3, 4) + I, -2 - I/4] + + assert ifft(ls) == ifls + assert fft(ifls) == ls + [S.Zero] + + x = Symbol('x', real=True) + raises(TypeError, lambda: fft(x)) + raises(ValueError, lambda: ifft([x, 2*x, 3*x**2, 4*x**3])) + + +def test_ntt_intt(): + # prime moduli of the form (m*2**k + 1), sequence length + # should be a divisor of 2**k + p = 7*17*2**23 + 1 + q = 2*500000003 + 1 # only for sequences of length 1 or 2 + r = 2*3*5*7 # composite modulus + + assert all(tf(ls, p) == ls for tf in (ntt, intt) + for ls in ([], [5])) + + ls = list(range(6)) + nls = [15, 801133602, 738493201, 334102277, 998244350, 849020224, + 259751156, 12232587] + + assert ntt(ls, p) == nls + assert intt(nls, p) == ls + [0]*2 + + ls = [1 + 2*I, 3 + 4*I, 5 + 6*I] + x = Symbol('x', integer=True) + + raises(TypeError, lambda: ntt(x, p)) + raises(ValueError, lambda: intt([x, 2*x, 3*x**2, 4*x**3], p)) + raises(ValueError, lambda: intt(ls, p)) + raises(ValueError, lambda: ntt([1.2, 2.1, 3.5], p)) + raises(ValueError, lambda: ntt([3, 5, 6], q)) + raises(ValueError, lambda: ntt([4, 5, 7], r)) + raises(ValueError, lambda: ntt([1.0, 2.0, 3.0], p)) + + +def test_fwht_ifwht(): + assert all(tf(ls) == ls for tf in (fwht, ifwht) \ + for ls in ([], [Rational(7, 4)])) + + ls = [213, 321, 43235, 5325, 312, 53] + fls = [49459, 38061, -47661, -37759, 48729, 37543, -48391, -38277] + + assert fwht(ls) == fls + assert ifwht(fls) == ls + [S.Zero]*2 + + ls = [S.Half + 2*I, Rational(3, 7) + 4*I, Rational(5, 6) + 6*I, Rational(7, 3), Rational(9, 4)] + ifls = [Rational(533, 672) + I*3/2, Rational(23, 224) + I/2, Rational(1, 672), Rational(107, 224) - I, + Rational(155, 672) + I*3/2, Rational(-103, 224) + I/2, Rational(-377, 672), Rational(-19, 224) - I] + + assert ifwht(ls) == ifls + assert fwht(ifls) == ls + [S.Zero]*3 + + x, y = symbols('x y') + + raises(TypeError, lambda: fwht(x)) + + ls = [x, 2*x, 3*x**2, 4*x**3] + ifls = [x**3 + 3*x**2/4 + x*Rational(3, 4), + -x**3 + 3*x**2/4 - x/4, + -x**3 - 3*x**2/4 + x*Rational(3, 4), + x**3 - 3*x**2/4 - x/4] + + assert ifwht(ls) == ifls + assert fwht(ifls) == ls + + ls = [x, y, x**2, y**2, x*y] + fls = [x**2 + x*y + x + y**2 + y, + x**2 + x*y + x - y**2 - y, + -x**2 + x*y + x - y**2 + y, + -x**2 + x*y + x + y**2 - y, + x**2 - x*y + x + y**2 + y, + x**2 - x*y + x - y**2 - y, + -x**2 - x*y + x - y**2 + y, + -x**2 - x*y + x + y**2 - y] + + assert fwht(ls) == fls + assert ifwht(fls) == ls + [S.Zero]*3 + + ls = list(range(6)) + + assert fwht(ls) == [x*8 for x in ifwht(ls)] + + +def test_mobius_transform(): + assert all(tf(ls, subset=subset) == ls + for ls in ([], [Rational(7, 4)]) for subset in (True, False) + for tf in (mobius_transform, inverse_mobius_transform)) + + w, x, y, z = symbols('w x y z') + + assert mobius_transform([x, y]) == [x, x + y] + assert inverse_mobius_transform([x, x + y]) == [x, y] + assert mobius_transform([x, y], subset=False) == [x + y, y] + assert inverse_mobius_transform([x + y, y], subset=False) == [x, y] + + assert mobius_transform([w, x, y, z]) == [w, w + x, w + y, w + x + y + z] + assert inverse_mobius_transform([w, w + x, w + y, w + x + y + z]) == \ + [w, x, y, z] + assert mobius_transform([w, x, y, z], subset=False) == \ + [w + x + y + z, x + z, y + z, z] + assert inverse_mobius_transform([w + x + y + z, x + z, y + z, z], subset=False) == \ + [w, x, y, z] + + ls = [Rational(2, 3), Rational(6, 7), Rational(5, 8), 9, Rational(5, 3) + 7*I] + mls = [Rational(2, 3), Rational(32, 21), Rational(31, 24), Rational(1873, 168), + Rational(7, 3) + 7*I, Rational(67, 21) + 7*I, Rational(71, 24) + 7*I, + Rational(2153, 168) + 7*I] + + assert mobius_transform(ls) == mls + assert inverse_mobius_transform(mls) == ls + [S.Zero]*3 + + mls = [Rational(2153, 168) + 7*I, Rational(69, 7), Rational(77, 8), 9, Rational(5, 3) + 7*I, 0, 0, 0] + + assert mobius_transform(ls, subset=False) == mls + assert inverse_mobius_transform(mls, subset=False) == ls + [S.Zero]*3 + + ls = ls[:-1] + mls = [Rational(2, 3), Rational(32, 21), Rational(31, 24), Rational(1873, 168)] + + assert mobius_transform(ls) == mls + assert inverse_mobius_transform(mls) == ls + + mls = [Rational(1873, 168), Rational(69, 7), Rational(77, 8), 9] + + assert mobius_transform(ls, subset=False) == mls + assert inverse_mobius_transform(mls, subset=False) == ls + + raises(TypeError, lambda: mobius_transform(x, subset=True)) + raises(TypeError, lambda: inverse_mobius_transform(y, subset=False)) diff --git a/phivenv/Lib/site-packages/sympy/discrete/transforms.py b/phivenv/Lib/site-packages/sympy/discrete/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..cb3550837021a4cf99e38c6b15f89ce8bb69b25a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/discrete/transforms.py @@ -0,0 +1,425 @@ +""" +Discrete Fourier Transform, Number Theoretic Transform, +Walsh Hadamard Transform, Mobius Transform +""" + +from sympy.core import S, Symbol, sympify +from sympy.core.function import expand_mul +from sympy.core.numbers import pi, I +from sympy.functions.elementary.trigonometric import sin, cos +from sympy.ntheory import isprime, primitive_root +from sympy.utilities.iterables import ibin, iterable +from sympy.utilities.misc import as_int + + +#----------------------------------------------------------------------------# +# # +# Discrete Fourier Transform # +# # +#----------------------------------------------------------------------------# + +def _fourier_transform(seq, dps, inverse=False): + """Utility function for the Discrete Fourier Transform""" + + if not iterable(seq): + raise TypeError("Expected a sequence of numeric coefficients " + "for Fourier Transform") + + a = [sympify(arg) for arg in seq] + if any(x.has(Symbol) for x in a): + raise ValueError("Expected non-symbolic coefficients") + + n = len(a) + if n < 2: + return a + + b = n.bit_length() - 1 + if n&(n - 1): # not a power of 2 + b += 1 + n = 2**b + + a += [S.Zero]*(n - len(a)) + for i in range(1, n): + j = int(ibin(i, b, str=True)[::-1], 2) + if i < j: + a[i], a[j] = a[j], a[i] + + ang = -2*pi/n if inverse else 2*pi/n + + if dps is not None: + ang = ang.evalf(dps + 2) + + w = [cos(ang*i) + I*sin(ang*i) for i in range(n // 2)] + + h = 2 + while h <= n: + hf, ut = h // 2, n // h + for i in range(0, n, h): + for j in range(hf): + u, v = a[i + j], expand_mul(a[i + j + hf]*w[ut * j]) + a[i + j], a[i + j + hf] = u + v, u - v + h *= 2 + + if inverse: + a = [(x/n).evalf(dps) for x in a] if dps is not None \ + else [x/n for x in a] + + return a + + +def fft(seq, dps=None): + r""" + Performs the Discrete Fourier Transform (**DFT**) in the complex domain. + + The sequence is automatically padded to the right with zeros, as the + *radix-2 FFT* requires the number of sample points to be a power of 2. + + This method should be used with default arguments only for short sequences + as the complexity of expressions increases with the size of the sequence. + + Parameters + ========== + + seq : iterable + The sequence on which **DFT** is to be applied. + dps : Integer + Specifies the number of decimal digits for precision. + + Examples + ======== + + >>> from sympy import fft, ifft + + >>> fft([1, 2, 3, 4]) + [10, -2 - 2*I, -2, -2 + 2*I] + >>> ifft(_) + [1, 2, 3, 4] + + >>> ifft([1, 2, 3, 4]) + [5/2, -1/2 + I/2, -1/2, -1/2 - I/2] + >>> fft(_) + [1, 2, 3, 4] + + >>> ifft([1, 7, 3, 4], dps=15) + [3.75, -0.5 - 0.75*I, -1.75, -0.5 + 0.75*I] + >>> fft(_) + [1.0, 7.0, 3.0, 4.0] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm + .. [2] https://mathworld.wolfram.com/FastFourierTransform.html + + """ + + return _fourier_transform(seq, dps=dps) + + +def ifft(seq, dps=None): + return _fourier_transform(seq, dps=dps, inverse=True) + +ifft.__doc__ = fft.__doc__ + + +#----------------------------------------------------------------------------# +# # +# Number Theoretic Transform # +# # +#----------------------------------------------------------------------------# + +def _number_theoretic_transform(seq, prime, inverse=False): + """Utility function for the Number Theoretic Transform""" + + if not iterable(seq): + raise TypeError("Expected a sequence of integer coefficients " + "for Number Theoretic Transform") + + p = as_int(prime) + if not isprime(p): + raise ValueError("Expected prime modulus for " + "Number Theoretic Transform") + + a = [as_int(x) % p for x in seq] + + n = len(a) + if n < 1: + return a + + b = n.bit_length() - 1 + if n&(n - 1): + b += 1 + n = 2**b + + if (p - 1) % n: + raise ValueError("Expected prime modulus of the form (m*2**k + 1)") + + a += [0]*(n - len(a)) + for i in range(1, n): + j = int(ibin(i, b, str=True)[::-1], 2) + if i < j: + a[i], a[j] = a[j], a[i] + + pr = primitive_root(p) + + rt = pow(pr, (p - 1) // n, p) + if inverse: + rt = pow(rt, p - 2, p) + + w = [1]*(n // 2) + for i in range(1, n // 2): + w[i] = w[i - 1]*rt % p + + h = 2 + while h <= n: + hf, ut = h // 2, n // h + for i in range(0, n, h): + for j in range(hf): + u, v = a[i + j], a[i + j + hf]*w[ut * j] + a[i + j], a[i + j + hf] = (u + v) % p, (u - v) % p + h *= 2 + + if inverse: + rv = pow(n, p - 2, p) + a = [x*rv % p for x in a] + + return a + + +def ntt(seq, prime): + r""" + Performs the Number Theoretic Transform (**NTT**), which specializes the + Discrete Fourier Transform (**DFT**) over quotient ring `Z/pZ` for prime + `p` instead of complex numbers `C`. + + The sequence is automatically padded to the right with zeros, as the + *radix-2 NTT* requires the number of sample points to be a power of 2. + + Parameters + ========== + + seq : iterable + The sequence on which **DFT** is to be applied. + prime : Integer + Prime modulus of the form `(m 2^k + 1)` to be used for performing + **NTT** on the sequence. + + Examples + ======== + + >>> from sympy import ntt, intt + >>> ntt([1, 2, 3, 4], prime=3*2**8 + 1) + [10, 643, 767, 122] + >>> intt(_, 3*2**8 + 1) + [1, 2, 3, 4] + >>> intt([1, 2, 3, 4], prime=3*2**8 + 1) + [387, 415, 384, 353] + >>> ntt(_, prime=3*2**8 + 1) + [1, 2, 3, 4] + + References + ========== + + .. [1] http://www.apfloat.org/ntt.html + .. [2] https://mathworld.wolfram.com/NumberTheoreticTransform.html + .. [3] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29 + + """ + + return _number_theoretic_transform(seq, prime=prime) + + +def intt(seq, prime): + return _number_theoretic_transform(seq, prime=prime, inverse=True) + +intt.__doc__ = ntt.__doc__ + + +#----------------------------------------------------------------------------# +# # +# Walsh Hadamard Transform # +# # +#----------------------------------------------------------------------------# + +def _walsh_hadamard_transform(seq, inverse=False): + """Utility function for the Walsh Hadamard Transform""" + + if not iterable(seq): + raise TypeError("Expected a sequence of coefficients " + "for Walsh Hadamard Transform") + + a = [sympify(arg) for arg in seq] + n = len(a) + if n < 2: + return a + + if n&(n - 1): + n = 2**n.bit_length() + + a += [S.Zero]*(n - len(a)) + h = 2 + while h <= n: + hf = h // 2 + for i in range(0, n, h): + for j in range(hf): + u, v = a[i + j], a[i + j + hf] + a[i + j], a[i + j + hf] = u + v, u - v + h *= 2 + + if inverse: + a = [x/n for x in a] + + return a + + +def fwht(seq): + r""" + Performs the Walsh Hadamard Transform (**WHT**), and uses Hadamard + ordering for the sequence. + + The sequence is automatically padded to the right with zeros, as the + *radix-2 FWHT* requires the number of sample points to be a power of 2. + + Parameters + ========== + + seq : iterable + The sequence on which WHT is to be applied. + + Examples + ======== + + >>> from sympy import fwht, ifwht + >>> fwht([4, 2, 2, 0, 0, 2, -2, 0]) + [8, 0, 8, 0, 8, 8, 0, 0] + >>> ifwht(_) + [4, 2, 2, 0, 0, 2, -2, 0] + + >>> ifwht([19, -1, 11, -9, -7, 13, -15, 5]) + [2, 0, 4, 0, 3, 10, 0, 0] + >>> fwht(_) + [19, -1, 11, -9, -7, 13, -15, 5] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hadamard_transform + .. [2] https://en.wikipedia.org/wiki/Fast_Walsh%E2%80%93Hadamard_transform + + """ + + return _walsh_hadamard_transform(seq) + + +def ifwht(seq): + return _walsh_hadamard_transform(seq, inverse=True) + +ifwht.__doc__ = fwht.__doc__ + + +#----------------------------------------------------------------------------# +# # +# Mobius Transform for Subset Lattice # +# # +#----------------------------------------------------------------------------# + +def _mobius_transform(seq, sgn, subset): + r"""Utility function for performing Mobius Transform using + Yate's Dynamic Programming method""" + + if not iterable(seq): + raise TypeError("Expected a sequence of coefficients") + + a = [sympify(arg) for arg in seq] + + n = len(a) + if n < 2: + return a + + if n&(n - 1): + n = 2**n.bit_length() + + a += [S.Zero]*(n - len(a)) + + if subset: + i = 1 + while i < n: + for j in range(n): + if j & i: + a[j] += sgn*a[j ^ i] + i *= 2 + + else: + i = 1 + while i < n: + for j in range(n): + if j & i: + continue + a[j] += sgn*a[j ^ i] + i *= 2 + + return a + + +def mobius_transform(seq, subset=True): + r""" + Performs the Mobius Transform for subset lattice with indices of + sequence as bitmasks. + + The indices of each argument, considered as bit strings, correspond + to subsets of a finite set. + + The sequence is automatically padded to the right with zeros, as the + definition of subset/superset based on bitmasks (indices) requires + the size of sequence to be a power of 2. + + Parameters + ========== + + seq : iterable + The sequence on which Mobius Transform is to be applied. + subset : bool + Specifies if Mobius Transform is applied by enumerating subsets + or supersets of the given set. + + Examples + ======== + + >>> from sympy import symbols + >>> from sympy import mobius_transform, inverse_mobius_transform + >>> x, y, z = symbols('x y z') + + >>> mobius_transform([x, y, z]) + [x, x + y, x + z, x + y + z] + >>> inverse_mobius_transform(_) + [x, y, z, 0] + + >>> mobius_transform([x, y, z], subset=False) + [x + y + z, y, z, 0] + >>> inverse_mobius_transform(_, subset=False) + [x, y, z, 0] + + >>> mobius_transform([1, 2, 3, 4]) + [1, 3, 4, 10] + >>> inverse_mobius_transform(_) + [1, 2, 3, 4] + >>> mobius_transform([1, 2, 3, 4], subset=False) + [10, 6, 7, 4] + >>> inverse_mobius_transform(_, subset=False) + [1, 2, 3, 4] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/M%C3%B6bius_inversion_formula + .. [2] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf + .. [3] https://arxiv.org/pdf/1211.0189.pdf + + """ + + return _mobius_transform(seq, sgn=+1, subset=subset) + +def inverse_mobius_transform(seq, subset=True): + return _mobius_transform(seq, sgn=-1, subset=subset) + +inverse_mobius_transform.__doc__ = mobius_transform.__doc__ diff --git a/phivenv/Lib/site-packages/sympy/external/__init__.py b/phivenv/Lib/site-packages/sympy/external/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..549b4b96cdce0ee4d31960e89cb9dc26af0e105d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/__init__.py @@ -0,0 +1,20 @@ +""" +Unified place for determining if external dependencies are installed or not. + +You should import all external modules using the import_module() function. + +For example + +>>> from sympy.external import import_module +>>> numpy = import_module('numpy') + +If the resulting library is not installed, or if the installed version +is less than a given minimum version, the function will return None. +Otherwise, it will return the library. See the docstring of +import_module() for more information. + +""" + +from sympy.external.importtools import import_module + +__all__ = ['import_module'] diff --git a/phivenv/Lib/site-packages/sympy/external/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98cd04ea5d195c7d4d17bcbf022d0df8d7805449 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/__pycache__/gmpy.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/__pycache__/gmpy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60f77261cd32bc08f2d56a8a1e9c753ba38e78b4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/__pycache__/gmpy.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/__pycache__/importtools.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/__pycache__/importtools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03b7c119e34d71670b95e30b7d1da4922efa5ea6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/__pycache__/importtools.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/__pycache__/ntheory.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/__pycache__/ntheory.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95d8bd0024ba2b80de3be3be18c7e421cea8d679 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/__pycache__/ntheory.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/__pycache__/pythonmpq.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/__pycache__/pythonmpq.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9600f143b52fb734580d6a66f7a8b78f250132e9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/__pycache__/pythonmpq.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/gmpy.py b/phivenv/Lib/site-packages/sympy/external/gmpy.py new file mode 100644 index 0000000000000000000000000000000000000000..d26942864bf4786e72198d3640d488857b3313f4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/gmpy.py @@ -0,0 +1,342 @@ +from __future__ import annotations +import os +from ctypes import c_long, sizeof +from functools import reduce +from typing import Type +from warnings import warn + +from sympy.external import import_module + +from .pythonmpq import PythonMPQ + +from .ntheory import ( + bit_scan1 as python_bit_scan1, + bit_scan0 as python_bit_scan0, + remove as python_remove, + factorial as python_factorial, + sqrt as python_sqrt, + sqrtrem as python_sqrtrem, + gcd as python_gcd, + lcm as python_lcm, + gcdext as python_gcdext, + is_square as python_is_square, + invert as python_invert, + legendre as python_legendre, + jacobi as python_jacobi, + kronecker as python_kronecker, + iroot as python_iroot, + is_fermat_prp as python_is_fermat_prp, + is_euler_prp as python_is_euler_prp, + is_strong_prp as python_is_strong_prp, + is_fibonacci_prp as python_is_fibonacci_prp, + is_lucas_prp as python_is_lucas_prp, + is_selfridge_prp as python_is_selfridge_prp, + is_strong_lucas_prp as python_is_strong_lucas_prp, + is_strong_selfridge_prp as python_is_strong_selfridge_prp, + is_bpsw_prp as python_is_bpsw_prp, + is_strong_bpsw_prp as python_is_strong_bpsw_prp, +) + + +__all__ = [ + # GROUND_TYPES is either 'gmpy' or 'python' depending on which is used. If + # gmpy is installed then it will be used unless the environment variable + # SYMPY_GROUND_TYPES is set to something other than 'auto', 'gmpy', or + # 'gmpy2'. + 'GROUND_TYPES', + + # If HAS_GMPY is 0, no supported version of gmpy is available. Otherwise, + # HAS_GMPY will be 2 for gmpy2 if GROUND_TYPES is 'gmpy'. It used to be + # possible for HAS_GMPY to be 1 for gmpy but gmpy is no longer supported. + 'HAS_GMPY', + + # SYMPY_INTS is a tuple containing the base types for valid integer types. + # This is either (int,) or (int, type(mpz(0))) depending on GROUND_TYPES. + 'SYMPY_INTS', + + # MPQ is either gmpy.mpq or the Python equivalent from + # sympy.external.pythonmpq + 'MPQ', + + # MPZ is either gmpy.mpz or int. + 'MPZ', + + 'bit_scan1', + 'bit_scan0', + 'remove', + 'factorial', + 'sqrt', + 'is_square', + 'sqrtrem', + 'gcd', + 'lcm', + 'gcdext', + 'invert', + 'legendre', + 'jacobi', + 'kronecker', + 'iroot', + 'is_fermat_prp', + 'is_euler_prp', + 'is_strong_prp', + 'is_fibonacci_prp', + 'is_lucas_prp', + 'is_selfridge_prp', + 'is_strong_lucas_prp', + 'is_strong_selfridge_prp', + 'is_bpsw_prp', + 'is_strong_bpsw_prp', +] + + +# +# Tested python-flint version. Future versions might work but we will only use +# them if explicitly requested by SYMPY_GROUND_TYPES=flint. +# +_PYTHON_FLINT_VERSION_NEEDED = ["0.6", "0.7", "0.8", "0.9", "0.10"] + + +def _flint_version_okay(flint_version): + major, minor = flint_version.split('.')[:2] + flint_ver = f'{major}.{minor}' + return flint_ver in _PYTHON_FLINT_VERSION_NEEDED + +# +# We will only use gmpy2 >= 2.0.0 +# +_GMPY2_MIN_VERSION = '2.0.0' + + +def _get_flint(sympy_ground_types): + if sympy_ground_types not in ('auto', 'flint'): + return None + + try: + import flint + # Earlier versions of python-flint may not have __version__. + from flint import __version__ as _flint_version + except ImportError: + if sympy_ground_types == 'flint': + warn("SYMPY_GROUND_TYPES was set to flint but python-flint is not " + "installed. Falling back to other ground types.") + return None + + if _flint_version_okay(_flint_version): + return flint + elif sympy_ground_types == 'auto': + return None + else: + warn(f"Using python-flint {_flint_version} because SYMPY_GROUND_TYPES " + f"is set to flint but this version of SymPy is only tested " + f"with python-flint versions {_PYTHON_FLINT_VERSION_NEEDED}.") + return flint + + +def _get_gmpy2(sympy_ground_types): + if sympy_ground_types not in ('auto', 'gmpy', 'gmpy2'): + return None + + gmpy = import_module('gmpy2', min_module_version=_GMPY2_MIN_VERSION, + module_version_attr='version', module_version_attr_call_args=()) + + if sympy_ground_types != 'auto' and gmpy is None: + warn("gmpy2 library is not installed, switching to 'python' ground types") + + return gmpy + + +# +# SYMPY_GROUND_TYPES can be flint, gmpy, gmpy2, python or auto (default) +# +_SYMPY_GROUND_TYPES = os.environ.get('SYMPY_GROUND_TYPES', 'auto').lower() +_flint = None +_gmpy = None + +# +# First handle auto-detection of flint/gmpy2. We will prefer flint if available +# or otherwise gmpy2 if available and then lastly the python types. +# +if _SYMPY_GROUND_TYPES in ('auto', 'flint'): + _flint = _get_flint(_SYMPY_GROUND_TYPES) + if _flint is not None: + _SYMPY_GROUND_TYPES = 'flint' + else: + _SYMPY_GROUND_TYPES = 'auto' + +if _SYMPY_GROUND_TYPES in ('auto', 'gmpy', 'gmpy2'): + _gmpy = _get_gmpy2(_SYMPY_GROUND_TYPES) + if _gmpy is not None: + _SYMPY_GROUND_TYPES = 'gmpy' + else: + _SYMPY_GROUND_TYPES = 'python' + +if _SYMPY_GROUND_TYPES not in ('flint', 'gmpy', 'python'): + warn("SYMPY_GROUND_TYPES environment variable unrecognised. " + "Should be 'auto', 'flint', 'gmpy', 'gmpy2' or 'python'.") + _SYMPY_GROUND_TYPES = 'python' + +# +# At this point _SYMPY_GROUND_TYPES is either flint, gmpy or python. The blocks +# below define the values exported by this module in each case. +# + +# +# In gmpy2 and flint, there are functions that take a long (or unsigned long) +# argument. That is, it is not possible to input a value larger than that. +# +LONG_MAX = (1 << (8*sizeof(c_long) - 1)) - 1 + +# +# Type checkers are confused by what SYMPY_INTS is. There may be a better type +# hint for this like Type[Integral] or something. +# +SYMPY_INTS: tuple[Type, ...] + +if _SYMPY_GROUND_TYPES == 'gmpy': + + assert _gmpy is not None + + flint = None + gmpy = _gmpy + + HAS_GMPY = 2 + GROUND_TYPES = 'gmpy' + SYMPY_INTS = (int, type(gmpy.mpz(0))) + MPZ = gmpy.mpz + MPQ = gmpy.mpq + + bit_scan1 = gmpy.bit_scan1 + bit_scan0 = gmpy.bit_scan0 + remove = gmpy.remove + factorial = gmpy.fac + sqrt = gmpy.isqrt + is_square = gmpy.is_square + sqrtrem = gmpy.isqrt_rem + gcd = gmpy.gcd + lcm = gmpy.lcm + gcdext = gmpy.gcdext + invert = gmpy.invert + legendre = gmpy.legendre + jacobi = gmpy.jacobi + kronecker = gmpy.kronecker + + def iroot(x, n): + # In the latest gmpy2, the threshold for n is ULONG_MAX, + # but adjust to the older one. + if n <= LONG_MAX: + return gmpy.iroot(x, n) + return python_iroot(x, n) + + is_fermat_prp = gmpy.is_fermat_prp + is_euler_prp = gmpy.is_euler_prp + is_strong_prp = gmpy.is_strong_prp + is_fibonacci_prp = gmpy.is_fibonacci_prp + is_lucas_prp = gmpy.is_lucas_prp + is_selfridge_prp = gmpy.is_selfridge_prp + is_strong_lucas_prp = gmpy.is_strong_lucas_prp + is_strong_selfridge_prp = gmpy.is_strong_selfridge_prp + is_bpsw_prp = gmpy.is_bpsw_prp + is_strong_bpsw_prp = gmpy.is_strong_bpsw_prp + +elif _SYMPY_GROUND_TYPES == 'flint': + + assert _flint is not None + + flint = _flint + gmpy = None + + HAS_GMPY = 0 + GROUND_TYPES = 'flint' + SYMPY_INTS = (int, flint.fmpz) # type: ignore + MPZ = flint.fmpz # type: ignore + MPQ = flint.fmpq # type: ignore + + bit_scan1 = python_bit_scan1 + bit_scan0 = python_bit_scan0 + remove = python_remove + factorial = python_factorial + + def sqrt(x): + return flint.fmpz(x).isqrt() + + def is_square(x): + if x < 0: + return False + return flint.fmpz(x).sqrtrem()[1] == 0 + + def sqrtrem(x): + return flint.fmpz(x).sqrtrem() + + def gcd(*args): + return reduce(flint.fmpz.gcd, args, flint.fmpz(0)) + + def lcm(*args): + return reduce(flint.fmpz.lcm, args, flint.fmpz(1)) + + gcdext = python_gcdext + invert = python_invert + legendre = python_legendre + + def jacobi(x, y): + if y <= 0 or not y % 2: + raise ValueError("y should be an odd positive integer") + return flint.fmpz(x).jacobi(y) + + kronecker = python_kronecker + + def iroot(x, n): + if n <= LONG_MAX: + y = flint.fmpz(x).root(n) + return y, y**n == x + return python_iroot(x, n) + + is_fermat_prp = python_is_fermat_prp + is_euler_prp = python_is_euler_prp + is_strong_prp = python_is_strong_prp + is_fibonacci_prp = python_is_fibonacci_prp + is_lucas_prp = python_is_lucas_prp + is_selfridge_prp = python_is_selfridge_prp + is_strong_lucas_prp = python_is_strong_lucas_prp + is_strong_selfridge_prp = python_is_strong_selfridge_prp + is_bpsw_prp = python_is_bpsw_prp + is_strong_bpsw_prp = python_is_strong_bpsw_prp + +elif _SYMPY_GROUND_TYPES == 'python': + + flint = None + gmpy = None + + HAS_GMPY = 0 + GROUND_TYPES = 'python' + SYMPY_INTS = (int,) + MPZ = int + MPQ = PythonMPQ + + bit_scan1 = python_bit_scan1 + bit_scan0 = python_bit_scan0 + remove = python_remove + factorial = python_factorial + sqrt = python_sqrt + is_square = python_is_square + sqrtrem = python_sqrtrem + gcd = python_gcd + lcm = python_lcm + gcdext = python_gcdext + invert = python_invert + legendre = python_legendre + jacobi = python_jacobi + kronecker = python_kronecker + iroot = python_iroot + is_fermat_prp = python_is_fermat_prp + is_euler_prp = python_is_euler_prp + is_strong_prp = python_is_strong_prp + is_fibonacci_prp = python_is_fibonacci_prp + is_lucas_prp = python_is_lucas_prp + is_selfridge_prp = python_is_selfridge_prp + is_strong_lucas_prp = python_is_strong_lucas_prp + is_strong_selfridge_prp = python_is_strong_selfridge_prp + is_bpsw_prp = python_is_bpsw_prp + is_strong_bpsw_prp = python_is_strong_bpsw_prp + +else: + assert False diff --git a/phivenv/Lib/site-packages/sympy/external/importtools.py b/phivenv/Lib/site-packages/sympy/external/importtools.py new file mode 100644 index 0000000000000000000000000000000000000000..5008b3dd4634d3cee10744a0a92b1204051f07cc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/importtools.py @@ -0,0 +1,187 @@ +"""Tools to assist importing optional external modules.""" + +import sys +import re + +# Override these in the module to change the default warning behavior. +# For example, you might set both to False before running the tests so that +# warnings are not printed to the console, or set both to True for debugging. + +WARN_NOT_INSTALLED = None # Default is False +WARN_OLD_VERSION = None # Default is True + + +def __sympy_debug(): + # helper function from sympy/__init__.py + # We don't just import SYMPY_DEBUG from that file because we don't want to + # import all of SymPy just to use this module. + import os + debug_str = os.getenv('SYMPY_DEBUG', 'False') + if debug_str in ('True', 'False'): + return eval(debug_str) + else: + raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" % + debug_str) + +if __sympy_debug(): + WARN_OLD_VERSION = True + WARN_NOT_INSTALLED = True + + +_component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + +def version_tuple(vstring): + # Parse a version string to a tuple e.g. '1.2' -> (1, 2) + # Simplified from distutils.version.LooseVersion which was deprecated in + # Python 3.10. + components = [] + for x in _component_re.split(vstring): + if x and x != '.': + try: + x = int(x) + except ValueError: + pass + components.append(x) + return tuple(components) + + +def import_module(module, min_module_version=None, min_python_version=None, + warn_not_installed=None, warn_old_version=None, + module_version_attr='__version__', module_version_attr_call_args=None, + import_kwargs={}, catch=()): + """ + Import and return a module if it is installed. + + If the module is not installed, it returns None. + + A minimum version for the module can be given as the keyword argument + min_module_version. This should be comparable against the module version. + By default, module.__version__ is used to get the module version. To + override this, set the module_version_attr keyword argument. If the + attribute of the module to get the version should be called (e.g., + module.version()), then set module_version_attr_call_args to the args such + that module.module_version_attr(*module_version_attr_call_args) returns the + module's version. + + If the module version is less than min_module_version using the Python < + comparison, None will be returned, even if the module is installed. You can + use this to keep from importing an incompatible older version of a module. + + You can also specify a minimum Python version by using the + min_python_version keyword argument. This should be comparable against + sys.version_info. + + If the keyword argument warn_not_installed is set to True, the function will + emit a UserWarning when the module is not installed. + + If the keyword argument warn_old_version is set to True, the function will + emit a UserWarning when the library is installed, but cannot be imported + because of the min_module_version or min_python_version options. + + Note that because of the way warnings are handled, a warning will be + emitted for each module only once. You can change the default warning + behavior by overriding the values of WARN_NOT_INSTALLED and WARN_OLD_VERSION + in sympy.external.importtools. By default, WARN_NOT_INSTALLED is False and + WARN_OLD_VERSION is True. + + This function uses __import__() to import the module. To pass additional + options to __import__(), use the import_kwargs keyword argument. For + example, to import a submodule A.B, you must pass a nonempty fromlist option + to __import__. See the docstring of __import__(). + + This catches ImportError to determine if the module is not installed. To + catch additional errors, pass them as a tuple to the catch keyword + argument. + + Examples + ======== + + >>> from sympy.external import import_module + + >>> numpy = import_module('numpy') + + >>> numpy = import_module('numpy', min_python_version=(2, 7), + ... warn_old_version=False) + + >>> numpy = import_module('numpy', min_module_version='1.5', + ... warn_old_version=False) # numpy.__version__ is a string + + >>> # gmpy does not have __version__, but it does have gmpy.version() + + >>> gmpy = import_module('gmpy', min_module_version='1.14', + ... module_version_attr='version', module_version_attr_call_args=(), + ... warn_old_version=False) + + >>> # To import a submodule, you must pass a nonempty fromlist to + >>> # __import__(). The values do not matter. + >>> p3 = import_module('mpl_toolkits.mplot3d', + ... import_kwargs={'fromlist':['something']}) + + >>> # matplotlib.pyplot can raise RuntimeError when the display cannot be opened + >>> matplotlib = import_module('matplotlib', + ... import_kwargs={'fromlist':['pyplot']}, catch=(RuntimeError,)) + + """ + # keyword argument overrides default, and global variable overrides + # keyword argument. + warn_old_version = (WARN_OLD_VERSION if WARN_OLD_VERSION is not None + else warn_old_version or True) + warn_not_installed = (WARN_NOT_INSTALLED if WARN_NOT_INSTALLED is not None + else warn_not_installed or False) + + import warnings + + # Check Python first so we don't waste time importing a module we can't use + if min_python_version: + if sys.version_info < min_python_version: + if warn_old_version: + warnings.warn("Python version is too old to use %s " + "(%s or newer required)" % ( + module, '.'.join(map(str, min_python_version))), + UserWarning, stacklevel=2) + return + + try: + mod = __import__(module, **import_kwargs) + + ## there's something funny about imports with matplotlib and py3k. doing + ## from matplotlib import collections + ## gives python's stdlib collections module. explicitly re-importing + ## the module fixes this. + from_list = import_kwargs.get('fromlist', ()) + for submod in from_list: + if submod == 'collections' and mod.__name__ == 'matplotlib': + __import__(module + '.' + submod) + except ImportError: + if warn_not_installed: + warnings.warn("%s module is not installed" % module, UserWarning, + stacklevel=2) + return + except catch as e: + if warn_not_installed: + warnings.warn( + "%s module could not be used (%s)" % (module, repr(e)), + stacklevel=2) + return + + if min_module_version: + modversion = getattr(mod, module_version_attr) + if module_version_attr_call_args is not None: + modversion = modversion(*module_version_attr_call_args) + if version_tuple(modversion) < version_tuple(min_module_version): + if warn_old_version: + # Attempt to create a pretty string version of the version + if isinstance(min_module_version, str): + verstr = min_module_version + elif isinstance(min_module_version, (tuple, list)): + verstr = '.'.join(map(str, min_module_version)) + else: + # Either don't know what this is. Hopefully + # it's something that has a nice str version, like an int. + verstr = str(min_module_version) + warnings.warn("%s version is too old to use " + "(%s or newer required)" % (module, verstr), + UserWarning, stacklevel=2) + return + + return mod diff --git a/phivenv/Lib/site-packages/sympy/external/ntheory.py b/phivenv/Lib/site-packages/sympy/external/ntheory.py new file mode 100644 index 0000000000000000000000000000000000000000..a0c9bf813cf02b311f9a12ee7fbc4932ed551f3b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/ntheory.py @@ -0,0 +1,618 @@ +# sympy.external.ntheory +# +# This module provides pure Python implementations of some number theory +# functions that are alternately used from gmpy2 if it is installed. + +import math + +import mpmath.libmp as mlib + + +_small_trailing = [0] * 256 +for j in range(1, 8): + _small_trailing[1 << j :: 1 << (j + 1)] = [j] * (1 << (7 - j)) + + +def bit_scan1(x, n=0): + if not x: + return + x = abs(x >> n) + low_byte = x & 0xFF + if low_byte: + return _small_trailing[low_byte] + n + + t = 8 + n + x >>= 8 + # 2**m is quick for z up through 2**30 + z = x.bit_length() - 1 + if x == 1 << z: + return z + t + + if z < 300: + # fixed 8-byte reduction + while not x & 0xFF: + x >>= 8 + t += 8 + else: + # binary reduction important when there might be a large + # number of trailing 0s + p = z >> 1 + while not x & 0xFF: + while x & ((1 << p) - 1): + p >>= 1 + x >>= p + t += p + return t + _small_trailing[x & 0xFF] + + +def bit_scan0(x, n=0): + return bit_scan1(x + (1 << n), n) + + +def remove(x, f): + if f < 2: + raise ValueError("factor must be > 1") + if x == 0: + return 0, 0 + if f == 2: + b = bit_scan1(x) + return x >> b, b + m = 0 + y, rem = divmod(x, f) + while not rem: + x = y + m += 1 + if m > 5: + pow_list = [f**2] + while pow_list: + _f = pow_list[-1] + y, rem = divmod(x, _f) + if not rem: + m += 1 << len(pow_list) + x = y + pow_list.append(_f**2) + else: + pow_list.pop() + y, rem = divmod(x, f) + return x, m + + +def factorial(x): + """Return x!.""" + return int(mlib.ifac(int(x))) + + +def sqrt(x): + """Integer square root of x.""" + return int(mlib.isqrt(int(x))) + + +def sqrtrem(x): + """Integer square root of x and remainder.""" + s, r = mlib.sqrtrem(int(x)) + return (int(s), int(r)) + + +gcd = math.gcd +lcm = math.lcm + + +def _sign(n): + if n < 0: + return -1, -n + return 1, n + + +def gcdext(a, b): + if not a or not b: + g = abs(a) or abs(b) + if not g: + return (0, 0, 0) + return (g, a // g, b // g) + + x_sign, a = _sign(a) + y_sign, b = _sign(b) + x, r = 1, 0 + y, s = 0, 1 + + while b: + q, c = divmod(a, b) + a, b = b, c + x, r = r, x - q*r + y, s = s, y - q*s + + return (a, x * x_sign, y * y_sign) + + +def is_square(x): + """Return True if x is a square number.""" + if x < 0: + return False + + # Note that the possible values of y**2 % n for a given n are limited. + # For example, when n=4, y**2 % n can only take 0 or 1. + # In other words, if x % 4 is 2 or 3, then x is not a square number. + # Mathematically, it determines if it belongs to the set {y**2 % n}, + # but implementationally, it can be realized as a logical conjunction + # with an n-bit integer. + # see https://mersenneforum.org/showpost.php?p=110896 + # def magic(n): + # s = {y**2 % n for y in range(n)} + # s = set(range(n)) - s + # return sum(1 << bit for bit in s) + # >>> print(hex(magic(128))) + # 0xfdfdfdedfdfdfdecfdfdfdedfdfcfdec + # >>> print(hex(magic(99))) + # 0x5f6f9ffb6fb7ddfcb75befdec + # >>> print(hex(magic(91))) + # 0x6fd1bfcfed5f3679d3ebdec + # >>> print(hex(magic(85))) + # 0xdef9ae771ffe3b9d67dec + if 0xfdfdfdedfdfdfdecfdfdfdedfdfcfdec & (1 << (x & 127)): + return False # e.g. 2, 3 + m = x % 765765 # 765765 = 99 * 91 * 85 + if 0x5f6f9ffb6fb7ddfcb75befdec & (1 << (m % 99)): + return False # e.g. 17, 68 + if 0x6fd1bfcfed5f3679d3ebdec & (1 << (m % 91)): + return False # e.g. 97, 388 + if 0xdef9ae771ffe3b9d67dec & (1 << (m % 85)): + return False # e.g. 793, 1408 + return mlib.sqrtrem(int(x))[1] == 0 + + +def invert(x, m): + """Modular inverse of x modulo m. + + Returns y such that x*y == 1 mod m. + + Uses ``math.pow`` but reproduces the behaviour of ``gmpy2.invert`` + which raises ZeroDivisionError if no inverse exists. + """ + try: + return pow(x, -1, m) + except ValueError: + raise ZeroDivisionError("invert() no inverse exists") + + +def legendre(x, y): + """Legendre symbol (x / y). + + Following the implementation of gmpy2, + the error is raised only when y is an even number. + """ + if y <= 0 or not y % 2: + raise ValueError("y should be an odd prime") + x %= y + if not x: + return 0 + if pow(x, (y - 1) // 2, y) == 1: + return 1 + return -1 + + +def jacobi(x, y): + """Jacobi symbol (x / y).""" + if y <= 0 or not y % 2: + raise ValueError("y should be an odd positive integer") + x %= y + if not x: + return int(y == 1) + if y == 1 or x == 1: + return 1 + if gcd(x, y) != 1: + return 0 + j = 1 + while x != 0: + while x % 2 == 0 and x > 0: + x >>= 1 + if y % 8 in [3, 5]: + j = -j + x, y = y, x + if x % 4 == y % 4 == 3: + j = -j + x %= y + return j + + +def kronecker(x, y): + """Kronecker symbol (x / y).""" + if gcd(x, y) != 1: + return 0 + if y == 0: + return 1 + sign = -1 if y < 0 and x < 0 else 1 + y = abs(y) + s = bit_scan1(y) + y >>= s + if s % 2 and x % 8 in [3, 5]: + sign = -sign + return sign * jacobi(x, y) + + +def iroot(y, n): + if y < 0: + raise ValueError("y must be nonnegative") + if n < 1: + raise ValueError("n must be positive") + if y in (0, 1): + return y, True + if n == 1: + return y, True + if n == 2: + x, rem = mlib.sqrtrem(y) + return int(x), not rem + if n >= y.bit_length(): + return 1, False + # Get initial estimate for Newton's method. Care must be taken to + # avoid overflow + try: + guess = int(y**(1./n) + 0.5) + except OverflowError: + exp = math.log2(y)/n + if exp > 53: + shift = int(exp - 53) + guess = int(2.0**(exp - shift) + 1) << shift + else: + guess = int(2.0**exp) + if guess > 2**50: + # Newton iteration + xprev, x = -1, guess + while 1: + t = x**(n - 1) + xprev, x = x, ((n - 1)*x + y//t)//n + if abs(x - xprev) < 2: + break + else: + x = guess + # Compensate + t = x**n + while t < y: + x += 1 + t = x**n + while t > y: + x -= 1 + t = x**n + return x, t == y + + +def is_fermat_prp(n, a): + if a < 2: + raise ValueError("is_fermat_prp() requires 'a' greater than or equal to 2") + if n < 1: + raise ValueError("is_fermat_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + a %= n + if gcd(n, a) != 1: + raise ValueError("is_fermat_prp() requires gcd(n,a) == 1") + return pow(a, n - 1, n) == 1 + + +def is_euler_prp(n, a): + if a < 2: + raise ValueError("is_euler_prp() requires 'a' greater than or equal to 2") + if n < 1: + raise ValueError("is_euler_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + a %= n + if gcd(n, a) != 1: + raise ValueError("is_euler_prp() requires gcd(n,a) == 1") + return pow(a, n >> 1, n) == jacobi(a, n) % n + + +def _is_strong_prp(n, a): + s = bit_scan1(n - 1) + a = pow(a, n >> s, n) + if a == 1 or a == n - 1: + return True + for _ in range(s - 1): + a = pow(a, 2, n) + if a == n - 1: + return True + if a == 1: + return False + return False + + +def is_strong_prp(n, a): + if a < 2: + raise ValueError("is_strong_prp() requires 'a' greater than or equal to 2") + if n < 1: + raise ValueError("is_strong_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + a %= n + if gcd(n, a) != 1: + raise ValueError("is_strong_prp() requires gcd(n,a) == 1") + return _is_strong_prp(n, a) + + +def _lucas_sequence(n, P, Q, k): + r"""Return the modular Lucas sequence (U_k, V_k, Q_k). + + Explanation + =========== + + Given a Lucas sequence defined by P, Q, returns the kth values for + U and V, along with Q^k, all modulo n. This is intended for use with + possibly very large values of n and k, where the combinatorial functions + would be completely unusable. + + .. math :: + U_k = \begin{cases} + 0 & \text{if } k = 0\\ + 1 & \text{if } k = 1\\ + PU_{k-1} - QU_{k-2} & \text{if } k > 1 + \end{cases}\\ + V_k = \begin{cases} + 2 & \text{if } k = 0\\ + P & \text{if } k = 1\\ + PV_{k-1} - QV_{k-2} & \text{if } k > 1 + \end{cases} + + The modular Lucas sequences are used in numerous places in number theory, + especially in the Lucas compositeness tests and the various n + 1 proofs. + + Parameters + ========== + + n : int + n is an odd number greater than or equal to 3 + P : int + Q : int + D determined by D = P**2 - 4*Q is non-zero + k : int + k is a nonnegative integer + + Returns + ======= + + U, V, Qk : (int, int, int) + `(U_k \bmod{n}, V_k \bmod{n}, Q^k \bmod{n})` + + Examples + ======== + + >>> from sympy.external.ntheory import _lucas_sequence + >>> N = 10**2000 + 4561 + >>> sol = U, V, Qk = _lucas_sequence(N, 3, 1, N//2); sol + (0, 2, 1) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lucas_sequence + + """ + if k == 0: + return (0, 2, 1) + D = P**2 - 4*Q + U = 1 + V = P + Qk = Q % n + if Q == 1: + # Optimization for extra strong tests. + for b in bin(k)[3:]: + U = (U*V) % n + V = (V*V - 2) % n + if b == "1": + U, V = U*P + V, V*P + U*D + if U & 1: + U += n + if V & 1: + V += n + U, V = U >> 1, V >> 1 + elif P == 1 and Q == -1: + # Small optimization for 50% of Selfridge parameters. + for b in bin(k)[3:]: + U = (U*V) % n + if Qk == 1: + V = (V*V - 2) % n + else: + V = (V*V + 2) % n + Qk = 1 + if b == "1": + # new_U = (U + V) // 2 + # new_V = (5*U + V) // 2 = 2*U + new_U + U, V = U + V, U << 1 + if U & 1: + U += n + U >>= 1 + V += U + Qk = -1 + Qk %= n + elif P == 1: + for b in bin(k)[3:]: + U = (U*V) % n + V = (V*V - 2*Qk) % n + Qk *= Qk + if b == "1": + # new_U = (U + V) // 2 + # new_V = new_U - 2*Q*U + U, V = U + V, (Q*U) << 1 + if U & 1: + U += n + U >>= 1 + V = U - V + Qk *= Q + Qk %= n + else: + # The general case with any P and Q. + for b in bin(k)[3:]: + U = (U*V) % n + V = (V*V - 2*Qk) % n + Qk *= Qk + if b == "1": + U, V = U*P + V, V*P + U*D + if U & 1: + U += n + if V & 1: + V += n + U, V = U >> 1, V >> 1 + Qk *= Q + Qk %= n + return (U % n, V % n, Qk) + + +def is_fibonacci_prp(n, p, q): + d = p**2 - 4*q + if d == 0 or p <= 0 or q not in [1, -1]: + raise ValueError("invalid values for p,q in is_fibonacci_prp()") + if n < 1: + raise ValueError("is_fibonacci_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + return _lucas_sequence(n, p, q, n)[1] == p % n + + +def is_lucas_prp(n, p, q): + d = p**2 - 4*q + if d == 0: + raise ValueError("invalid values for p,q in is_lucas_prp()") + if n < 1: + raise ValueError("is_lucas_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + if gcd(n, q*d) not in [1, n]: + raise ValueError("is_lucas_prp() requires gcd(n,2*q*D) == 1") + return _lucas_sequence(n, p, q, n - jacobi(d, n))[0] == 0 + + +def _is_selfridge_prp(n): + """Lucas compositeness test with the Selfridge parameters for n. + + Explanation + =========== + + The Lucas compositeness test checks whether n is a prime number. + The test can be run with arbitrary parameters ``P`` and ``Q``, which also change the performance of the test. + So, which parameters are most effective for running the Lucas compositeness test? + As an algorithm for determining ``P`` and ``Q``, Selfridge proposed method A [1]_ page 1401 + (Since two methods were proposed, referred to simply as A and B in the paper, + we will refer to one of them as "method A"). + + method A fixes ``P = 1``. Then, ``D`` defined by ``D = P**2 - 4Q`` is varied from 5, -7, 9, -11, 13, and so on, + with the first ``D`` being ``jacobi(D, n) == -1``. Once ``D`` is determined, + ``Q`` is determined to be ``(P**2 - D)//4``. + + References + ========== + + .. [1] Robert Baillie, Samuel S. Wagstaff, Lucas Pseudoprimes, + Math. Comp. Vol 35, Number 152 (1980), pp. 1391-1417, + https://doi.org/10.1090%2FS0025-5718-1980-0583518-6 + http://mpqs.free.fr/LucasPseudoprimes.pdf + + """ + for D in range(5, 1_000_000, 2): + if D & 2: # if D % 4 == 3 + D = -D + j = jacobi(D, n) + if j == -1: + return _lucas_sequence(n, 1, (1-D) // 4, n + 1)[0] == 0 + if j == 0 and D % n: + return False + # When j == -1 is hard to find, suspect a square number + if D == 13 and is_square(n): + return False + raise ValueError("appropriate value for D cannot be found in is_selfridge_prp()") + + +def is_selfridge_prp(n): + if n < 1: + raise ValueError("is_selfridge_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + return _is_selfridge_prp(n) + + +def is_strong_lucas_prp(n, p, q): + D = p**2 - 4*q + if D == 0: + raise ValueError("invalid values for p,q in is_strong_lucas_prp()") + if n < 1: + raise ValueError("is_selfridge_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + if gcd(n, q*D) not in [1, n]: + raise ValueError("is_strong_lucas_prp() requires gcd(n,2*q*D) == 1") + j = jacobi(D, n) + s = bit_scan1(n - j) + U, V, Qk = _lucas_sequence(n, p, q, (n - j) >> s) + if U == 0 or V == 0: + return True + for _ in range(s - 1): + V = (V*V - 2*Qk) % n + if V == 0: + return True + Qk = pow(Qk, 2, n) + return False + + +def _is_strong_selfridge_prp(n): + for D in range(5, 1_000_000, 2): + if D & 2: # if D % 4 == 3 + D = -D + j = jacobi(D, n) + if j == -1: + s = bit_scan1(n + 1) + U, V, Qk = _lucas_sequence(n, 1, (1-D) // 4, (n + 1) >> s) + if U == 0 or V == 0: + return True + for _ in range(s - 1): + V = (V*V - 2*Qk) % n + if V == 0: + return True + Qk = pow(Qk, 2, n) + return False + if j == 0 and D % n: + return False + # When j == -1 is hard to find, suspect a square number + if D == 13 and is_square(n): + return False + raise ValueError("appropriate value for D cannot be found in is_strong_selfridge_prp()") + + +def is_strong_selfridge_prp(n): + if n < 1: + raise ValueError("is_strong_selfridge_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + return _is_strong_selfridge_prp(n) + + +def is_bpsw_prp(n): + if n < 1: + raise ValueError("is_bpsw_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + return _is_strong_prp(n, 2) and _is_selfridge_prp(n) + + +def is_strong_bpsw_prp(n): + if n < 1: + raise ValueError("is_strong_bpsw_prp() requires 'n' be greater than 0") + if n == 1: + return False + if n % 2 == 0: + return n == 2 + return _is_strong_prp(n, 2) and _is_strong_selfridge_prp(n) diff --git a/phivenv/Lib/site-packages/sympy/external/pythonmpq.py b/phivenv/Lib/site-packages/sympy/external/pythonmpq.py new file mode 100644 index 0000000000000000000000000000000000000000..4f2d102974e04e139c00a39057976b5a5bf90776 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/pythonmpq.py @@ -0,0 +1,341 @@ +""" +PythonMPQ: Rational number type based on Python integers. + +This class is intended as a pure Python fallback for when gmpy2 is not +installed. If gmpy2 is installed then its mpq type will be used instead. The +mpq type is around 20x faster. We could just use the stdlib Fraction class +here but that is slower: + + from fractions import Fraction + from sympy.external.pythonmpq import PythonMPQ + nums = range(1000) + dens = range(5, 1005) + rats = [Fraction(n, d) for n, d in zip(nums, dens)] + sum(rats) # <--- 24 milliseconds + rats = [PythonMPQ(n, d) for n, d in zip(nums, dens)] + sum(rats) # <--- 7 milliseconds + +Both mpq and Fraction have some awkward features like the behaviour of +division with // and %: + + >>> from fractions import Fraction + >>> Fraction(2, 3) % Fraction(1, 4) + 1/6 + +For the QQ domain we do not want this behaviour because there should be no +remainder when dividing rational numbers. SymPy does not make use of this +aspect of mpq when gmpy2 is installed. Since this class is a fallback for that +case we do not bother implementing e.g. __mod__ so that we can be sure we +are not using it when gmpy2 is installed either. +""" + +from __future__ import annotations +import operator +from math import gcd +from decimal import Decimal +from fractions import Fraction +import sys +from typing import Type + + +# Used for __hash__ +_PyHASH_MODULUS = sys.hash_info.modulus +_PyHASH_INF = sys.hash_info.inf + + +class PythonMPQ: + """Rational number implementation that is intended to be compatible with + gmpy2's mpq. + + Also slightly faster than fractions.Fraction. + + PythonMPQ should be treated as immutable although no effort is made to + prevent mutation (since that might slow down calculations). + """ + __slots__ = ('numerator', 'denominator') + + def __new__(cls, numerator, denominator=None): + """Construct PythonMPQ with gcd computation and checks""" + if denominator is not None: + # + # PythonMPQ(n, d): require n and d to be int and d != 0 + # + if isinstance(numerator, int) and isinstance(denominator, int): + # This is the slow part: + divisor = gcd(numerator, denominator) + numerator //= divisor + denominator //= divisor + return cls._new_check(numerator, denominator) + else: + # + # PythonMPQ(q) + # + # Here q can be PythonMPQ, int, Decimal, float, Fraction or str + # + if isinstance(numerator, int): + return cls._new(numerator, 1) + elif isinstance(numerator, PythonMPQ): + return cls._new(numerator.numerator, numerator.denominator) + + # Let Fraction handle Decimal/float conversion and str parsing + if isinstance(numerator, (Decimal, float, str)): + numerator = Fraction(numerator) + if isinstance(numerator, Fraction): + return cls._new(numerator.numerator, numerator.denominator) + # + # Reject everything else. This is more strict than mpq which allows + # things like mpq(Fraction, Fraction) or mpq(Decimal, any). The mpq + # behaviour is somewhat inconsistent so we choose to accept only a + # more strict subset of what mpq allows. + # + raise TypeError("PythonMPQ() requires numeric or string argument") + + @classmethod + def _new_check(cls, numerator, denominator): + """Construct PythonMPQ, check divide by zero and canonicalize signs""" + if not denominator: + raise ZeroDivisionError(f'Zero divisor {numerator}/{denominator}') + elif denominator < 0: + numerator = -numerator + denominator = -denominator + return cls._new(numerator, denominator) + + @classmethod + def _new(cls, numerator, denominator): + """Construct PythonMPQ efficiently (no checks)""" + obj = super().__new__(cls) + obj.numerator = numerator + obj.denominator = denominator + return obj + + def __int__(self): + """Convert to int (truncates towards zero)""" + p, q = self.numerator, self.denominator + if p < 0: + return -(-p//q) + return p//q + + def __float__(self): + """Convert to float (approximately)""" + return self.numerator / self.denominator + + def __bool__(self): + """True/False if nonzero/zero""" + return bool(self.numerator) + + def __eq__(self, other): + """Compare equal with PythonMPQ, int, float, Decimal or Fraction""" + if isinstance(other, PythonMPQ): + return (self.numerator == other.numerator + and self.denominator == other.denominator) + elif isinstance(other, self._compatible_types): + return self.__eq__(PythonMPQ(other)) + else: + return NotImplemented + + def __hash__(self): + """hash - same as mpq/Fraction""" + try: + dinv = pow(self.denominator, -1, _PyHASH_MODULUS) + except ValueError: + hash_ = _PyHASH_INF + else: + hash_ = hash(hash(abs(self.numerator)) * dinv) + result = hash_ if self.numerator >= 0 else -hash_ + return -2 if result == -1 else result + + def __reduce__(self): + """Deconstruct for pickling""" + return type(self), (self.numerator, self.denominator) + + def __str__(self): + """Convert to string""" + if self.denominator != 1: + return f"{self.numerator}/{self.denominator}" + else: + return f"{self.numerator}" + + def __repr__(self): + """Convert to string""" + return f"MPQ({self.numerator},{self.denominator})" + + def _cmp(self, other, op): + """Helper for lt/le/gt/ge""" + if not isinstance(other, self._compatible_types): + return NotImplemented + lhs = self.numerator * other.denominator + rhs = other.numerator * self.denominator + return op(lhs, rhs) + + def __lt__(self, other): + """self < other""" + return self._cmp(other, operator.lt) + + def __le__(self, other): + """self <= other""" + return self._cmp(other, operator.le) + + def __gt__(self, other): + """self > other""" + return self._cmp(other, operator.gt) + + def __ge__(self, other): + """self >= other""" + return self._cmp(other, operator.ge) + + def __abs__(self): + """abs(q)""" + return self._new(abs(self.numerator), self.denominator) + + def __pos__(self): + """+q""" + return self + + def __neg__(self): + """-q""" + return self._new(-self.numerator, self.denominator) + + def __add__(self, other): + """q1 + q2""" + if isinstance(other, PythonMPQ): + # + # This is much faster than the naive method used in the stdlib + # fractions module. Not sure where this method comes from + # though... + # + # Compare timings for something like: + # nums = range(1000) + # rats = [PythonMPQ(n, d) for n, d in zip(nums[:-5], nums[5:])] + # sum(rats) # <-- time this + # + ap, aq = self.numerator, self.denominator + bp, bq = other.numerator, other.denominator + g = gcd(aq, bq) + if g == 1: + p = ap*bq + aq*bp + q = bq*aq + else: + q1, q2 = aq//g, bq//g + p, q = ap*q2 + bp*q1, q1*q2 + g2 = gcd(p, g) + p, q = (p // g2), q * (g // g2) + + elif isinstance(other, int): + p = self.numerator + self.denominator * other + q = self.denominator + else: + return NotImplemented + + return self._new(p, q) + + def __radd__(self, other): + """z1 + q2""" + if isinstance(other, int): + p = self.numerator + self.denominator * other + q = self.denominator + return self._new(p, q) + else: + return NotImplemented + + def __sub__(self ,other): + """q1 - q2""" + if isinstance(other, PythonMPQ): + ap, aq = self.numerator, self.denominator + bp, bq = other.numerator, other.denominator + g = gcd(aq, bq) + if g == 1: + p = ap*bq - aq*bp + q = bq*aq + else: + q1, q2 = aq//g, bq//g + p, q = ap*q2 - bp*q1, q1*q2 + g2 = gcd(p, g) + p, q = (p // g2), q * (g // g2) + elif isinstance(other, int): + p = self.numerator - self.denominator*other + q = self.denominator + else: + return NotImplemented + + return self._new(p, q) + + def __rsub__(self, other): + """z1 - q2""" + if isinstance(other, int): + p = self.denominator * other - self.numerator + q = self.denominator + return self._new(p, q) + else: + return NotImplemented + + def __mul__(self, other): + """q1 * q2""" + if isinstance(other, PythonMPQ): + ap, aq = self.numerator, self.denominator + bp, bq = other.numerator, other.denominator + x1 = gcd(ap, bq) + x2 = gcd(bp, aq) + p, q = ((ap//x1)*(bp//x2), (aq//x2)*(bq//x1)) + elif isinstance(other, int): + x = gcd(other, self.denominator) + p = self.numerator*(other//x) + q = self.denominator//x + else: + return NotImplemented + + return self._new(p, q) + + def __rmul__(self, other): + """z1 * q2""" + if isinstance(other, int): + x = gcd(self.denominator, other) + p = self.numerator*(other//x) + q = self.denominator//x + return self._new(p, q) + else: + return NotImplemented + + def __pow__(self, exp): + """q ** z""" + p, q = self.numerator, self.denominator + + if exp < 0: + p, q, exp = q, p, -exp + + return self._new_check(p**exp, q**exp) + + def __truediv__(self, other): + """q1 / q2""" + if isinstance(other, PythonMPQ): + ap, aq = self.numerator, self.denominator + bp, bq = other.numerator, other.denominator + x1 = gcd(ap, bp) + x2 = gcd(bq, aq) + p, q = ((ap//x1)*(bq//x2), (aq//x2)*(bp//x1)) + elif isinstance(other, int): + x = gcd(other, self.numerator) + p = self.numerator//x + q = self.denominator*(other//x) + else: + return NotImplemented + + return self._new_check(p, q) + + def __rtruediv__(self, other): + """z / q""" + if isinstance(other, int): + x = gcd(self.numerator, other) + p = self.denominator*(other//x) + q = self.numerator//x + return self._new_check(p, q) + else: + return NotImplemented + + _compatible_types: tuple[Type, ...] = () + +# +# These are the types that PythonMPQ will interoperate with for operations +# and comparisons such as ==, + etc. We define this down here so that we can +# include PythonMPQ in the list as well. +# +PythonMPQ._compatible_types = (PythonMPQ, int, Decimal, Fraction) diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__init__.py b/phivenv/Lib/site-packages/sympy/external/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa638559c732a40893b3495beb7db0f0e6d24f0d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_autowrap.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_autowrap.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4694ca7343fb7bef000f07e868f24024df269538 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_autowrap.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_codegen.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_codegen.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf6165b5a546e48b2cb5863366d577223bbe2b42 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_codegen.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_gmpy.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_gmpy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3be32fea09b632f45af129646aea173881435f1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_gmpy.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_importtools.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_importtools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c545340ce0e1903aea2a315f26e4d6f2d3d3e3a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_importtools.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_ntheory.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_ntheory.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e3e0b3e19712d6973ed1fc57b2a72138f5e2d04 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_ntheory.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_numpy.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_numpy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4bc85777fe4d3a77e15037fff99faec80579c5e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_numpy.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_pythonmpq.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_pythonmpq.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09a679dd6ba23a72deff310e385d0222e845f8fd Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_pythonmpq.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_scipy.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_scipy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..688f8936d03868b6657111b66374bf6bd5598997 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/external/tests/__pycache__/test_scipy.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_autowrap.py b/phivenv/Lib/site-packages/sympy/external/tests/test_autowrap.py new file mode 100644 index 0000000000000000000000000000000000000000..d469b552995b7625f786f3296089e41f42da75cb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_autowrap.py @@ -0,0 +1,313 @@ +import sympy +import tempfile +import os +from pathlib import Path +from sympy.core.mod import Mod +from sympy.core.relational import Eq +from sympy.core.symbol import symbols +from sympy.external import import_module +from sympy.tensor import IndexedBase, Idx +from sympy.utilities.autowrap import autowrap, ufuncify, CodeWrapError +from sympy.testing.pytest import skip + +numpy = import_module('numpy', min_module_version='1.6.1') +Cython = import_module('Cython', min_module_version='0.15.1') +f2py = import_module('numpy.f2py', import_kwargs={'fromlist': ['f2py']}) + +f2pyworks = False +if f2py: + try: + autowrap(symbols('x'), 'f95', 'f2py') + except (CodeWrapError, ImportError, OSError): + f2pyworks = False + else: + f2pyworks = True + +a, b, c = symbols('a b c') +n, m, d = symbols('n m d', integer=True) +A, B, C = symbols('A B C', cls=IndexedBase) +i = Idx('i', m) +j = Idx('j', n) +k = Idx('k', d) + + +def has_module(module): + """ + Return True if module exists, otherwise run skip(). + + module should be a string. + """ + # To give a string of the module name to skip(), this function takes a + # string. So we don't waste time running import_module() more than once, + # just map the three modules tested here in this dict. + modnames = {'numpy': numpy, 'Cython': Cython, 'f2py': f2py} + + if modnames[module]: + if module == 'f2py' and not f2pyworks: + skip("Couldn't run f2py.") + return True + skip("Couldn't import %s." % module) + +# +# test runners used by several language-backend combinations +# + +def runtest_autowrap_twice(language, backend): + f = autowrap((((a + b)/c)**5).expand(), language, backend) + g = autowrap((((a + b)/c)**4).expand(), language, backend) + + # check that autowrap updates the module name. Else, g gives the same as f + assert f(1, -2, 1) == -1.0 + assert g(1, -2, 1) == 1.0 + + +def runtest_autowrap_trace(language, backend): + has_module('numpy') + trace = autowrap(A[i, i], language, backend) + assert trace(numpy.eye(100)) == 100 + + +def runtest_autowrap_matrix_vector(language, backend): + has_module('numpy') + x, y = symbols('x y', cls=IndexedBase) + expr = Eq(y[i], A[i, j]*x[j]) + mv = autowrap(expr, language, backend) + + # compare with numpy's dot product + M = numpy.random.rand(10, 20) + x = numpy.random.rand(20) + y = numpy.dot(M, x) + assert numpy.sum(numpy.abs(y - mv(M, x))) < 1e-13 + + +def runtest_autowrap_matrix_matrix(language, backend): + has_module('numpy') + expr = Eq(C[i, j], A[i, k]*B[k, j]) + matmat = autowrap(expr, language, backend) + + # compare with numpy's dot product + M1 = numpy.random.rand(10, 20) + M2 = numpy.random.rand(20, 15) + M3 = numpy.dot(M1, M2) + assert numpy.sum(numpy.abs(M3 - matmat(M1, M2))) < 1e-13 + + +def runtest_ufuncify(language, backend): + has_module('numpy') + a, b, c = symbols('a b c') + fabc = ufuncify([a, b, c], a*b + c, backend=backend) + facb = ufuncify([a, c, b], a*b + c, backend=backend) + grid = numpy.linspace(-2, 2, 50) + b = numpy.linspace(-5, 4, 50) + c = numpy.linspace(-1, 1, 50) + expected = grid*b + c + numpy.testing.assert_allclose(fabc(grid, b, c), expected) + numpy.testing.assert_allclose(facb(grid, c, b), expected) + + +def runtest_issue_10274(language, backend): + expr = (a - b + c)**(13) + tmp = tempfile.mkdtemp() + f = autowrap(expr, language, backend, tempdir=tmp, + helpers=('helper', a - b + c, (a, b, c))) + assert f(1, 1, 1) == 1 + + for file in os.listdir(tmp): + if not (file.startswith("wrapped_code_") and file.endswith(".c")): + continue + + with open(tmp + '/' + file) as fil: + lines = fil.readlines() + assert lines[0] == "/******************************************************************************\n" + assert "Code generated with SymPy " + sympy.__version__ in lines[1] + assert lines[2:] == [ + " * *\n", + " * See http://www.sympy.org/ for more information. *\n", + " * *\n", + " * This file is part of 'autowrap' *\n", + " ******************************************************************************/\n", + "#include " + '"' + file[:-1]+ 'h"' + "\n", + "#include \n", + "\n", + "double helper(double a, double b, double c) {\n", + "\n", + " double helper_result;\n", + " helper_result = a - b + c;\n", + " return helper_result;\n", + "\n", + "}\n", + "\n", + "double autofunc(double a, double b, double c) {\n", + "\n", + " double autofunc_result;\n", + " autofunc_result = pow(helper(a, b, c), 13);\n", + " return autofunc_result;\n", + "\n", + "}\n", + ] + + +def runtest_issue_15337(language, backend): + has_module('numpy') + # NOTE : autowrap was originally designed to only accept an iterable for + # the kwarg "helpers", but in issue 10274 the user mistakenly thought that + # if there was only a single helper it did not need to be passed via an + # iterable that wrapped the helper tuple. There were no tests for this + # behavior so when the code was changed to accept a single tuple it broke + # the original behavior. These tests below ensure that both now work. + a, b, c, d, e = symbols('a, b, c, d, e') + expr = (a - b + c - d + e)**13 + exp_res = (1. - 2. + 3. - 4. + 5.)**13 + + f = autowrap(expr, language, backend, args=(a, b, c, d, e), + helpers=('f1', a - b + c, (a, b, c))) + numpy.testing.assert_allclose(f(1, 2, 3, 4, 5), exp_res) + + f = autowrap(expr, language, backend, args=(a, b, c, d, e), + helpers=(('f1', a - b, (a, b)), ('f2', c - d, (c, d)))) + numpy.testing.assert_allclose(f(1, 2, 3, 4, 5), exp_res) + + +def test_issue_15230(): + has_module('f2py') + + x, y = symbols('x, y') + expr = Mod(x, 3.0) - Mod(y, -2.0) + f = autowrap(expr, args=[x, y], language='F95') + exp_res = float(expr.xreplace({x: 3.5, y: 2.7}).evalf()) + assert abs(f(3.5, 2.7) - exp_res) < 1e-14 + + x, y = symbols('x, y', integer=True) + expr = Mod(x, 3) - Mod(y, -2) + f = autowrap(expr, args=[x, y], language='F95') + assert f(3, 2) == expr.xreplace({x: 3, y: 2}) + +# +# tests of language-backend combinations +# + +# f2py + + +def test_wrap_twice_f95_f2py(): + has_module('f2py') + runtest_autowrap_twice('f95', 'f2py') + + +def test_autowrap_trace_f95_f2py(): + has_module('f2py') + runtest_autowrap_trace('f95', 'f2py') + + +def test_autowrap_matrix_vector_f95_f2py(): + has_module('f2py') + runtest_autowrap_matrix_vector('f95', 'f2py') + + +def test_autowrap_matrix_matrix_f95_f2py(): + has_module('f2py') + runtest_autowrap_matrix_matrix('f95', 'f2py') + + +def test_ufuncify_f95_f2py(): + has_module('f2py') + runtest_ufuncify('f95', 'f2py') + + +def test_issue_15337_f95_f2py(): + has_module('f2py') + runtest_issue_15337('f95', 'f2py') + +# Cython + + +def test_wrap_twice_c_cython(): + has_module('Cython') + runtest_autowrap_twice('C', 'cython') + + +def test_autowrap_trace_C_Cython(): + has_module('Cython') + runtest_autowrap_trace('C99', 'cython') + + +def test_autowrap_matrix_vector_C_cython(): + has_module('Cython') + runtest_autowrap_matrix_vector('C99', 'cython') + + +def test_autowrap_matrix_matrix_C_cython(): + has_module('Cython') + runtest_autowrap_matrix_matrix('C99', 'cython') + + +def test_ufuncify_C_Cython(): + has_module('Cython') + runtest_ufuncify('C99', 'cython') + + +def test_issue_10274_C_cython(): + has_module('Cython') + runtest_issue_10274('C89', 'cython') + + +def test_issue_15337_C_cython(): + has_module('Cython') + runtest_issue_15337('C89', 'cython') + + +def test_autowrap_custom_printer(): + has_module('Cython') + + from sympy.core.numbers import pi + from sympy.utilities.codegen import C99CodeGen + from sympy.printing.c import C99CodePrinter + + class PiPrinter(C99CodePrinter): + def _print_Pi(self, expr): + return "S_PI" + + printer = PiPrinter() + gen = C99CodeGen(printer=printer) + gen.preprocessor_statements.append('#include "shortpi.h"') + + expr = pi * a + + expected = ( + '#include "%s"\n' + '#include \n' + '#include "shortpi.h"\n' + '\n' + 'double autofunc(double a) {\n' + '\n' + ' double autofunc_result;\n' + ' autofunc_result = S_PI*a;\n' + ' return autofunc_result;\n' + '\n' + '}\n' + ) + + tmpdir = tempfile.mkdtemp() + # write a trivial header file to use in the generated code + Path(os.path.join(tmpdir, 'shortpi.h')).write_text('#define S_PI 3.14') + + func = autowrap(expr, backend='cython', tempdir=tmpdir, code_gen=gen) + + assert func(4.2) == 3.14 * 4.2 + + # check that the generated code is correct + for filename in os.listdir(tmpdir): + if filename.startswith('wrapped_code') and filename.endswith('.c'): + with open(os.path.join(tmpdir, filename)) as f: + lines = f.readlines() + expected = expected % filename.replace('.c', '.h') + assert ''.join(lines[7:]) == expected + + +# Numpy + +def test_ufuncify_numpy(): + # This test doesn't use Cython, but if Cython works, then there is a valid + # C compiler, which is needed. + has_module('Cython') + runtest_ufuncify('C99', 'numpy') diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_codegen.py b/phivenv/Lib/site-packages/sympy/external/tests/test_codegen.py new file mode 100644 index 0000000000000000000000000000000000000000..8a4fe28300b86fb0b38d98fcf2fcbbe514cf720f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_codegen.py @@ -0,0 +1,375 @@ +# This tests the compilation and execution of the source code generated with +# utilities.codegen. The compilation takes place in a temporary directory that +# is removed after the test. By default the test directory is always removed, +# but this behavior can be changed by setting the environment variable +# SYMPY_TEST_CLEAN_TEMP to: +# export SYMPY_TEST_CLEAN_TEMP=always : the default behavior. +# export SYMPY_TEST_CLEAN_TEMP=success : only remove the directories of working tests. +# export SYMPY_TEST_CLEAN_TEMP=never : never remove the directories with the test code. +# When a directory is not removed, the necessary information is printed on +# screen to find the files that belong to the (failed) tests. If a test does +# not fail, py.test captures all the output and you will not see the directories +# corresponding to the successful tests. Use the --nocapture option to see all +# the output. + +# All tests below have a counterpart in utilities/test/test_codegen.py. In the +# latter file, the resulting code is compared with predefined strings, without +# compilation or execution. + +# All the generated Fortran code should conform with the Fortran 95 standard, +# and all the generated C code should be ANSI C, which facilitates the +# incorporation in various projects. The tests below assume that the binary cc +# is somewhere in the path and that it can compile ANSI C code. + +from sympy.abc import x, y, z +from sympy.testing.pytest import IS_WASM, skip +from sympy.utilities.codegen import codegen, make_routine, get_code_generator +import sys +import os +import tempfile +import subprocess +from pathlib import Path + + +# templates for the main program that will test the generated code. + +main_template = {} +main_template['F95'] = """ +program main + include "codegen.h" + integer :: result; + result = 0 + + %(statements)s + + call exit(result) +end program +""" + +main_template['C89'] = """ +#include "codegen.h" +#include +#include + +int main() { + int result = 0; + + %(statements)s + + return result; +} +""" +main_template['C99'] = main_template['C89'] +# templates for the numerical tests + +numerical_test_template = {} +numerical_test_template['C89'] = """ + if (fabs(%(call)s)>%(threshold)s) { + printf("Numerical validation failed: %(call)s=%%e threshold=%(threshold)s\\n", %(call)s); + result = -1; + } +""" +numerical_test_template['C99'] = numerical_test_template['C89'] + +numerical_test_template['F95'] = """ + if (abs(%(call)s)>%(threshold)s) then + write(6,"('Numerical validation failed:')") + write(6,"('%(call)s=',e15.5,'threshold=',e15.5)") %(call)s, %(threshold)s + result = -1; + end if +""" +# command sequences for supported compilers + +compile_commands = {} +compile_commands['cc'] = [ + "cc -c codegen.c -o codegen.o", + "cc -c main.c -o main.o", + "cc main.o codegen.o -lm -o test.exe" +] + +compile_commands['gfortran'] = [ + "gfortran -c codegen.f90 -o codegen.o", + "gfortran -ffree-line-length-none -c main.f90 -o main.o", + "gfortran main.o codegen.o -o test.exe" +] + +compile_commands['g95'] = [ + "g95 -c codegen.f90 -o codegen.o", + "g95 -ffree-line-length-huge -c main.f90 -o main.o", + "g95 main.o codegen.o -o test.exe" +] + +compile_commands['ifort'] = [ + "ifort -c codegen.f90 -o codegen.o", + "ifort -c main.f90 -o main.o", + "ifort main.o codegen.o -o test.exe" +] + +combinations_lang_compiler = [ + ('C89', 'cc'), + ('C99', 'cc'), + ('F95', 'ifort'), + ('F95', 'gfortran'), + ('F95', 'g95') +] + +def try_run(commands): + """Run a series of commands and only return True if all ran fine.""" + if IS_WASM: + return False + with open(os.devnull, 'w') as null: + for command in commands: + retcode = subprocess.call(command, stdout=null, shell=True, + stderr=subprocess.STDOUT) + if retcode != 0: + return False + return True + + +def run_test(label, routines, numerical_tests, language, commands, friendly=True): + """A driver for the codegen tests. + + This driver assumes that a compiler ifort is present in the PATH and that + ifort is (at least) a Fortran 90 compiler. The generated code is written in + a temporary directory, together with a main program that validates the + generated code. The test passes when the compilation and the validation + run correctly. + """ + + # Check input arguments before touching the file system + language = language.upper() + assert language in main_template + assert language in numerical_test_template + + # Check that environment variable makes sense + clean = os.getenv('SYMPY_TEST_CLEAN_TEMP', 'always').lower() + if clean not in ('always', 'success', 'never'): + raise ValueError("SYMPY_TEST_CLEAN_TEMP must be one of the following: 'always', 'success' or 'never'.") + + # Do all the magic to compile, run and validate the test code + # 1) prepare the temporary working directory, switch to that dir + work = tempfile.mkdtemp("_sympy_%s_test" % language, "%s_" % label) + oldwork = os.getcwd() + os.chdir(work) + + # 2) write the generated code + if friendly: + # interpret the routines as a name_expr list and call the friendly + # function codegen + codegen(routines, language, "codegen", to_files=True) + else: + code_gen = get_code_generator(language, "codegen") + code_gen.write(routines, "codegen", to_files=True) + + # 3) write a simple main program that links to the generated code, and that + # includes the numerical tests + test_strings = [] + for fn_name, args, expected, threshold in numerical_tests: + call_string = "%s(%s)-(%s)" % ( + fn_name, ",".join(str(arg) for arg in args), expected) + if language == "F95": + call_string = fortranize_double_constants(call_string) + threshold = fortranize_double_constants(str(threshold)) + test_strings.append(numerical_test_template[language] % { + "call": call_string, + "threshold": threshold, + }) + + if language == "F95": + f_name = "main.f90" + elif language.startswith("C"): + f_name = "main.c" + else: + raise NotImplementedError( + "FIXME: filename extension unknown for language: %s" % language) + + Path(f_name).write_text( + main_template[language] % {'statements': "".join(test_strings)}) + + # 4) Compile and link + compiled = try_run(commands) + + # 5) Run if compiled + if compiled: + executed = try_run(["./test.exe"]) + else: + executed = False + + # 6) Clean up stuff + if clean == 'always' or (clean == 'success' and compiled and executed): + def safe_remove(filename): + if os.path.isfile(filename): + os.remove(filename) + safe_remove("codegen.f90") + safe_remove("codegen.c") + safe_remove("codegen.h") + safe_remove("codegen.o") + safe_remove("main.f90") + safe_remove("main.c") + safe_remove("main.o") + safe_remove("test.exe") + os.chdir(oldwork) + os.rmdir(work) + else: + print("TEST NOT REMOVED: %s" % work, file=sys.stderr) + os.chdir(oldwork) + + # 7) Do the assertions in the end + assert compiled, "failed to compile %s code with:\n%s" % ( + language, "\n".join(commands)) + assert executed, "failed to execute %s code from:\n%s" % ( + language, "\n".join(commands)) + + +def fortranize_double_constants(code_string): + """ + Replaces every literal float with literal doubles + """ + import re + pattern_exp = re.compile(r'\d+(\.)?\d*[eE]-?\d+') + pattern_float = re.compile(r'\d+\.\d*(?!\d*d)') + + def subs_exp(matchobj): + return re.sub('[eE]', 'd', matchobj.group(0)) + + def subs_float(matchobj): + return "%sd0" % matchobj.group(0) + + code_string = pattern_exp.sub(subs_exp, code_string) + code_string = pattern_float.sub(subs_float, code_string) + + return code_string + + +def is_feasible(language, commands): + # This test should always work, otherwise the compiler is not present. + routine = make_routine("test", x) + numerical_tests = [ + ("test", ( 1.0,), 1.0, 1e-15), + ("test", (-1.0,), -1.0, 1e-15), + ] + try: + run_test("is_feasible", [routine], numerical_tests, language, commands, + friendly=False) + return True + except AssertionError: + return False + +valid_lang_commands = [] +invalid_lang_compilers = [] +for lang, compiler in combinations_lang_compiler: + commands = compile_commands[compiler] + if is_feasible(lang, commands): + valid_lang_commands.append((lang, commands)) + else: + invalid_lang_compilers.append((lang, compiler)) + +# We test all language-compiler combinations, just to report what is skipped + +def test_C89_cc(): + if ("C89", 'cc') in invalid_lang_compilers: + skip("`cc' command didn't work as expected (C89)") + + +def test_C99_cc(): + if ("C99", 'cc') in invalid_lang_compilers: + skip("`cc' command didn't work as expected (C99)") + + +def test_F95_ifort(): + if ("F95", 'ifort') in invalid_lang_compilers: + skip("`ifort' command didn't work as expected") + + +def test_F95_gfortran(): + if ("F95", 'gfortran') in invalid_lang_compilers: + skip("`gfortran' command didn't work as expected") + + +def test_F95_g95(): + if ("F95", 'g95') in invalid_lang_compilers: + skip("`g95' command didn't work as expected") + +# Here comes the actual tests + + +def test_basic_codegen(): + numerical_tests = [ + ("test", (1.0, 6.0, 3.0), 21.0, 1e-15), + ("test", (-1.0, 2.0, -2.5), -2.5, 1e-15), + ] + name_expr = [("test", (x + y)*z)] + for lang, commands in valid_lang_commands: + run_test("basic_codegen", name_expr, numerical_tests, lang, commands) + + +def test_intrinsic_math1_codegen(): + # not included: log10 + from sympy.core.evalf import N + from sympy.functions import ln + from sympy.functions.elementary.exponential import log + from sympy.functions.elementary.hyperbolic import (cosh, sinh, tanh) + from sympy.functions.elementary.integers import (ceiling, floor) + from sympy.functions.elementary.miscellaneous import sqrt + from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, tan) + name_expr = [ + ("test_fabs", abs(x)), + ("test_acos", acos(x)), + ("test_asin", asin(x)), + ("test_atan", atan(x)), + ("test_cos", cos(x)), + ("test_cosh", cosh(x)), + ("test_log", log(x)), + ("test_ln", ln(x)), + ("test_sin", sin(x)), + ("test_sinh", sinh(x)), + ("test_sqrt", sqrt(x)), + ("test_tan", tan(x)), + ("test_tanh", tanh(x)), + ] + numerical_tests = [] + for name, expr in name_expr: + for xval in 0.2, 0.5, 0.8: + expected = N(expr.subs(x, xval)) + numerical_tests.append((name, (xval,), expected, 1e-14)) + for lang, commands in valid_lang_commands: + if lang.startswith("C"): + name_expr_C = [("test_floor", floor(x)), ("test_ceil", ceiling(x))] + else: + name_expr_C = [] + run_test("intrinsic_math1", name_expr + name_expr_C, + numerical_tests, lang, commands) + + +def test_instrinsic_math2_codegen(): + # not included: frexp, ldexp, modf, fmod + from sympy.core.evalf import N + from sympy.functions.elementary.trigonometric import atan2 + name_expr = [ + ("test_atan2", atan2(x, y)), + ("test_pow", x**y), + ] + numerical_tests = [] + for name, expr in name_expr: + for xval, yval in (0.2, 1.3), (0.5, -0.2), (0.8, 0.8): + expected = N(expr.subs(x, xval).subs(y, yval)) + numerical_tests.append((name, (xval, yval), expected, 1e-14)) + for lang, commands in valid_lang_commands: + run_test("intrinsic_math2", name_expr, numerical_tests, lang, commands) + + +def test_complicated_codegen(): + from sympy.core.evalf import N + from sympy.functions.elementary.trigonometric import (cos, sin, tan) + name_expr = [ + ("test1", ((sin(x) + cos(y) + tan(z))**7).expand()), + ("test2", cos(cos(cos(cos(cos(cos(cos(cos(x + y + z))))))))), + ] + numerical_tests = [] + for name, expr in name_expr: + for xval, yval, zval in (0.2, 1.3, -0.3), (0.5, -0.2, 0.0), (0.8, 2.1, 0.8): + expected = N(expr.subs(x, xval).subs(y, yval).subs(z, zval)) + numerical_tests.append((name, (xval, yval, zval), expected, 1e-12)) + for lang, commands in valid_lang_commands: + run_test( + "complicated_codegen", name_expr, numerical_tests, lang, commands) diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_gmpy.py b/phivenv/Lib/site-packages/sympy/external/tests/test_gmpy.py new file mode 100644 index 0000000000000000000000000000000000000000..d88f9da0c6c26c15f529ce485fff5b72342170ea --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_gmpy.py @@ -0,0 +1,12 @@ +from sympy.external.gmpy import LONG_MAX, iroot +from sympy.testing.pytest import raises + + +def test_iroot(): + assert iroot(2, LONG_MAX) == (1, False) + assert iroot(2, LONG_MAX + 1) == (1, False) + for x in range(3): + assert iroot(x, 1) == (x, True) + raises(ValueError, lambda: iroot(-1, 1)) + raises(ValueError, lambda: iroot(0, 0)) + raises(ValueError, lambda: iroot(0, -1)) diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_importtools.py b/phivenv/Lib/site-packages/sympy/external/tests/test_importtools.py new file mode 100644 index 0000000000000000000000000000000000000000..0b954070c179282ed2bcf5735d802c5f22a3a261 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_importtools.py @@ -0,0 +1,40 @@ +from sympy.external import import_module +from sympy.testing.pytest import warns + +# fixes issue that arose in addressing issue 6533 +def test_no_stdlib_collections(): + ''' + make sure we get the right collections when it is not part of a + larger list + ''' + import collections + matplotlib = import_module('matplotlib', + import_kwargs={'fromlist': ['cm', 'collections']}, + min_module_version='1.1.0', catch=(RuntimeError,)) + if matplotlib: + assert collections != matplotlib.collections + +def test_no_stdlib_collections2(): + ''' + make sure we get the right collections when it is not part of a + larger list + ''' + import collections + matplotlib = import_module('matplotlib', + import_kwargs={'fromlist': ['collections']}, + min_module_version='1.1.0', catch=(RuntimeError,)) + if matplotlib: + assert collections != matplotlib.collections + +def test_no_stdlib_collections3(): + '''make sure we get the right collections with no catch''' + import collections + matplotlib = import_module('matplotlib', + import_kwargs={'fromlist': ['cm', 'collections']}, + min_module_version='1.1.0') + if matplotlib: + assert collections != matplotlib.collections + +def test_min_module_version_python3_basestring_error(): + with warns(UserWarning): + import_module('mpmath', min_module_version='1000.0.1') diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_ntheory.py b/phivenv/Lib/site-packages/sympy/external/tests/test_ntheory.py new file mode 100644 index 0000000000000000000000000000000000000000..00824481ad27aa9071ea5801fb3bde75cacbc3c8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_ntheory.py @@ -0,0 +1,307 @@ +from itertools import permutations + +from sympy.external.ntheory import (bit_scan1, remove, bit_scan0, is_fermat_prp, + is_euler_prp, is_strong_prp, gcdext, _lucas_sequence, + is_fibonacci_prp, is_lucas_prp, is_selfridge_prp, + is_strong_lucas_prp, is_strong_selfridge_prp, + is_bpsw_prp, is_strong_bpsw_prp) +from sympy.testing.pytest import raises + + +def test_bit_scan1(): + assert bit_scan1(0) is None + assert bit_scan1(1) == 0 + assert bit_scan1(-1) == 0 + assert bit_scan1(2) == 1 + assert bit_scan1(7) == 0 + assert bit_scan1(-7) == 0 + for i in range(100): + assert bit_scan1(1 << i) == i + assert bit_scan1((1 << i) * 31337) == i + for i in range(500): + n = (1 << 500) + (1 << i) + assert bit_scan1(n) == i + assert bit_scan1(1 << 1000001) == 1000001 + assert bit_scan1((1 << 273956)*7**37) == 273956 + # issue 12709 + for i in range(1, 10): + big = 1 << i + assert bit_scan1(-big) == bit_scan1(big) + + +def test_bit_scan0(): + assert bit_scan0(-1) is None + assert bit_scan0(0) == 0 + assert bit_scan0(1) == 1 + assert bit_scan0(-2) == 0 + + +def test_remove(): + raises(ValueError, lambda: remove(1, 1)) + assert remove(0, 3) == (0, 0) + for f in range(2, 10): + for y in range(2, 1000): + for z in [1, 17, 101, 1009]: + assert remove(z*f**y, f) == (z, y) + + +def test_gcdext(): + assert gcdext(0, 0) == (0, 0, 0) + assert gcdext(3, 0) == (3, 1, 0) + assert gcdext(0, 4) == (4, 0, 1) + + for n in range(1, 10): + assert gcdext(n, 1) == gcdext(-n, 1) == (1, 0, 1) + assert gcdext(n, -1) == gcdext(-n, -1) == (1, 0, -1) + assert gcdext(n, n) == gcdext(-n, n) == (n, 0, 1) + assert gcdext(n, -n) == gcdext(-n, -n) == (n, 0, -1) + + for n in range(2, 10): + assert gcdext(1, n) == gcdext(1, -n) == (1, 1, 0) + assert gcdext(-1, n) == gcdext(-1, -n) == (1, -1, 0) + + for a, b in permutations([2**5, 3, 5, 7**2, 11], 2): + g, x, y = gcdext(a, b) + assert g == a*x + b*y == 1 + + +def test_is_fermat_prp(): + # invalid input + raises(ValueError, lambda: is_fermat_prp(0, 10)) + raises(ValueError, lambda: is_fermat_prp(5, 1)) + + # n = 1 + assert not is_fermat_prp(1, 3) + + # n is prime + assert is_fermat_prp(2, 4) + assert is_fermat_prp(3, 2) + assert is_fermat_prp(11, 3) + assert is_fermat_prp(2**31-1, 5) + + # A001567 + pseudorpime = [341, 561, 645, 1105, 1387, 1729, 1905, 2047, + 2465, 2701, 2821, 3277, 4033, 4369, 4371, 4681] + for n in pseudorpime: + assert is_fermat_prp(n, 2) + + # A020136 + pseudorpime = [15, 85, 91, 341, 435, 451, 561, 645, 703, 1105, + 1247, 1271, 1387, 1581, 1695, 1729, 1891, 1905] + for n in pseudorpime: + assert is_fermat_prp(n, 4) + + +def test_is_euler_prp(): + # invalid input + raises(ValueError, lambda: is_euler_prp(0, 10)) + raises(ValueError, lambda: is_euler_prp(5, 1)) + + # n = 1 + assert not is_euler_prp(1, 3) + + # n is prime + assert is_euler_prp(2, 4) + assert is_euler_prp(3, 2) + assert is_euler_prp(11, 3) + assert is_euler_prp(2**31-1, 5) + + # A047713 + pseudorpime = [561, 1105, 1729, 1905, 2047, 2465, 3277, 4033, + 4681, 6601, 8321, 8481, 10585, 12801, 15841] + for n in pseudorpime: + assert is_euler_prp(n, 2) + + # A048950 + pseudorpime = [121, 703, 1729, 1891, 2821, 3281, 7381, 8401, + 8911, 10585, 12403, 15457, 15841, 16531, 18721] + for n in pseudorpime: + assert is_euler_prp(n, 3) + + +def test_is_strong_prp(): + # invalid input + raises(ValueError, lambda: is_strong_prp(0, 10)) + raises(ValueError, lambda: is_strong_prp(5, 1)) + + # n = 1 + assert not is_strong_prp(1, 3) + + # n is prime + assert is_strong_prp(2, 4) + assert is_strong_prp(3, 2) + assert is_strong_prp(11, 3) + assert is_strong_prp(2**31-1, 5) + + # A001262 + pseudorpime = [2047, 3277, 4033, 4681, 8321, 15841, 29341, + 42799, 49141, 52633, 65281, 74665, 80581] + for n in pseudorpime: + assert is_strong_prp(n, 2) + + # A020229 + pseudorpime = [121, 703, 1891, 3281, 8401, 8911, 10585, 12403, + 16531, 18721, 19345, 23521, 31621, 44287, 47197] + for n in pseudorpime: + assert is_strong_prp(n, 3) + + +def test_lucas_sequence(): + def lucas_u(P, Q, length): + array = [0] * length + array[1] = 1 + for k in range(2, length): + array[k] = P * array[k - 1] - Q * array[k - 2] + return array + + def lucas_v(P, Q, length): + array = [0] * length + array[0] = 2 + array[1] = P + for k in range(2, length): + array[k] = P * array[k - 1] - Q * array[k - 2] + return array + + length = 20 + for P in range(-10, 10): + for Q in range(-10, 10): + D = P**2 - 4*Q + if D == 0: + continue + us = lucas_u(P, Q, length) + vs = lucas_v(P, Q, length) + for n in range(3, 100, 2): + for k in range(length): + U, V, Qk = _lucas_sequence(n, P, Q, k) + assert U == us[k] % n + assert V == vs[k] % n + assert pow(Q, k, n) == Qk + + +def test_is_fibonacci_prp(): + # invalid input + raises(ValueError, lambda: is_fibonacci_prp(3, 2, 1)) + raises(ValueError, lambda: is_fibonacci_prp(3, -5, 1)) + raises(ValueError, lambda: is_fibonacci_prp(3, 5, 2)) + raises(ValueError, lambda: is_fibonacci_prp(0, 5, -1)) + + # n = 1 + assert not is_fibonacci_prp(1, 3, 1) + + # n is prime + assert is_fibonacci_prp(2, 5, 1) + assert is_fibonacci_prp(3, 6, -1) + assert is_fibonacci_prp(11, 7, 1) + assert is_fibonacci_prp(2**31-1, 8, -1) + + # A005845 + pseudorpime = [705, 2465, 2737, 3745, 4181, 5777, 6721, + 10877, 13201, 15251, 24465, 29281, 34561] + for n in pseudorpime: + assert is_fibonacci_prp(n, 1, -1) + + +def test_is_lucas_prp(): + # invalid input + raises(ValueError, lambda: is_lucas_prp(3, 2, 1)) + raises(ValueError, lambda: is_lucas_prp(0, 5, -1)) + raises(ValueError, lambda: is_lucas_prp(15, 3, 1)) + + # n = 1 + assert not is_lucas_prp(1, 3, 1) + + # n is prime + assert is_lucas_prp(2, 5, 2) + assert is_lucas_prp(3, 6, -1) + assert is_lucas_prp(11, 7, 5) + assert is_lucas_prp(2**31-1, 8, -3) + + # A081264 + pseudorpime = [323, 377, 1891, 3827, 4181, 5777, 6601, 6721, + 8149, 10877, 11663, 13201, 13981, 15251, 17119] + for n in pseudorpime: + assert is_lucas_prp(n, 1, -1) + + +def test_is_selfridge_prp(): + # invalid input + raises(ValueError, lambda: is_selfridge_prp(0)) + + # n = 1 + assert not is_selfridge_prp(1) + + # n is prime + assert is_selfridge_prp(2) + assert is_selfridge_prp(3) + assert is_selfridge_prp(11) + assert is_selfridge_prp(2**31-1) + + # A217120 + pseudorpime = [323, 377, 1159, 1829, 3827, 5459, 5777, 9071, + 9179, 10877, 11419, 11663, 13919, 14839, 16109] + for n in pseudorpime: + assert is_selfridge_prp(n) + + +def test_is_strong_lucas_prp(): + # invalid input + raises(ValueError, lambda: is_strong_lucas_prp(3, 2, 1)) + raises(ValueError, lambda: is_strong_lucas_prp(0, 5, -1)) + raises(ValueError, lambda: is_strong_lucas_prp(15, 3, 1)) + + # n = 1 + assert not is_strong_lucas_prp(1, 3, 1) + + # n is prime + assert is_strong_lucas_prp(2, 5, 2) + assert is_strong_lucas_prp(3, 6, -1) + assert is_strong_lucas_prp(11, 7, 5) + assert is_strong_lucas_prp(2**31-1, 8, -3) + + +def test_is_strong_selfridge_prp(): + # invalid input + raises(ValueError, lambda: is_strong_selfridge_prp(0)) + + # n = 1 + assert not is_strong_selfridge_prp(1) + + # n is prime + assert is_strong_selfridge_prp(2) + assert is_strong_selfridge_prp(3) + assert is_strong_selfridge_prp(11) + assert is_strong_selfridge_prp(2**31-1) + + # A217255 + pseudorpime = [5459, 5777, 10877, 16109, 18971, 22499, 24569, + 25199, 40309, 58519, 75077, 97439, 100127, 113573] + for n in pseudorpime: + assert is_strong_selfridge_prp(n) + + +def test_is_bpsw_prp(): + # invalid input + raises(ValueError, lambda: is_bpsw_prp(0)) + + # n = 1 + assert not is_bpsw_prp(1) + + # n is prime + assert is_bpsw_prp(2) + assert is_bpsw_prp(3) + assert is_bpsw_prp(11) + assert is_bpsw_prp(2**31-1) + + +def test_is_strong_bpsw_prp(): + # invalid input + raises(ValueError, lambda: is_strong_bpsw_prp(0)) + + # n = 1 + assert not is_strong_bpsw_prp(1) + + # n is prime + assert is_strong_bpsw_prp(2) + assert is_strong_bpsw_prp(3) + assert is_strong_bpsw_prp(11) + assert is_strong_bpsw_prp(2**31-1) diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_numpy.py b/phivenv/Lib/site-packages/sympy/external/tests/test_numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..cd456d0d6cc49138c29d7ab28ee02694448d578f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_numpy.py @@ -0,0 +1,335 @@ +# This testfile tests SymPy <-> NumPy compatibility + +# Don't test any SymPy features here. Just pure interaction with NumPy. +# Always write regular SymPy tests for anything, that can be tested in pure +# Python (without numpy). Here we test everything, that a user may need when +# using SymPy with NumPy +from sympy.external.importtools import version_tuple +from sympy.external import import_module + +numpy = import_module('numpy') +if numpy: + array, matrix, ndarray = numpy.array, numpy.matrix, numpy.ndarray +else: + #bin/test will not execute any tests now + disabled = True + + +from sympy.core.numbers import (Float, Integer, Rational) +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.trigonometric import sin +from sympy.matrices.dense import (Matrix, list2numpy, matrix2numpy, symarray) +from sympy.utilities.lambdify import lambdify +import sympy + +import mpmath +from sympy.abc import x, y, z +from sympy.utilities.decorator import conserve_mpmath_dps +from sympy.utilities.exceptions import ignore_warnings +from sympy.testing.pytest import raises + + +# first, systematically check, that all operations are implemented and don't +# raise an exception + + +def test_systematic_basic(): + def s(sympy_object, numpy_array): + _ = [sympy_object + numpy_array, + numpy_array + sympy_object, + sympy_object - numpy_array, + numpy_array - sympy_object, + sympy_object * numpy_array, + numpy_array * sympy_object, + sympy_object / numpy_array, + numpy_array / sympy_object, + sympy_object ** numpy_array, + numpy_array ** sympy_object] + x = Symbol("x") + y = Symbol("y") + sympy_objs = [ + Rational(2, 3), + Float("1.3"), + x, + y, + pow(x, y)*y, + Integer(5), + Float(5.5), + ] + numpy_objs = [ + array([1]), + array([3, 8, -1]), + array([x, x**2, Rational(5)]), + array([x/y*sin(y), 5, Rational(5)]), + ] + for x in sympy_objs: + for y in numpy_objs: + s(x, y) + + +# now some random tests, that test particular problems and that also +# check that the results of the operations are correct + +def test_basics(): + one = Rational(1) + zero = Rational(0) + assert array(1) == array(one) + assert array([one]) == array([one]) + assert array([x]) == array([x]) + assert array(x) == array(Symbol("x")) + assert array(one + x) == array(1 + x) + + X = array([one, zero, zero]) + assert (X == array([one, zero, zero])).all() + assert (X == array([one, 0, 0])).all() + + +def test_arrays(): + one = Rational(1) + zero = Rational(0) + X = array([one, zero, zero]) + Y = one*X + X = array([Symbol("a") + Rational(1, 2)]) + Y = X + X + assert Y == array([1 + 2*Symbol("a")]) + Y = Y + 1 + assert Y == array([2 + 2*Symbol("a")]) + Y = X - X + assert Y == array([0]) + + +def test_conversion1(): + a = list2numpy([x**2, x]) + #looks like an array? + assert isinstance(a, ndarray) + assert a[0] == x**2 + assert a[1] == x + assert len(a) == 2 + #yes, it's the array + + +def test_conversion2(): + a = 2*list2numpy([x**2, x]) + b = list2numpy([2*x**2, 2*x]) + assert (a == b).all() + + one = Rational(1) + zero = Rational(0) + X = list2numpy([one, zero, zero]) + Y = one*X + X = list2numpy([Symbol("a") + Rational(1, 2)]) + Y = X + X + assert Y == array([1 + 2*Symbol("a")]) + Y = Y + 1 + assert Y == array([2 + 2*Symbol("a")]) + Y = X - X + assert Y == array([0]) + + +def test_list2numpy(): + assert (array([x**2, x]) == list2numpy([x**2, x])).all() + + +def test_Matrix1(): + m = Matrix([[x, x**2], [5, 2/x]]) + assert (array(m.subs(x, 2)) == array([[2, 4], [5, 1]])).all() + m = Matrix([[sin(x), x**2], [5, 2/x]]) + assert (array(m.subs(x, 2)) == array([[sin(2), 4], [5, 1]])).all() + + +def test_Matrix2(): + m = Matrix([[x, x**2], [5, 2/x]]) + with ignore_warnings(PendingDeprecationWarning): + assert (matrix(m.subs(x, 2)) == matrix([[2, 4], [5, 1]])).all() + m = Matrix([[sin(x), x**2], [5, 2/x]]) + with ignore_warnings(PendingDeprecationWarning): + assert (matrix(m.subs(x, 2)) == matrix([[sin(2), 4], [5, 1]])).all() + + +def test_Matrix3(): + a = array([[2, 4], [5, 1]]) + assert Matrix(a) == Matrix([[2, 4], [5, 1]]) + assert Matrix(a) != Matrix([[2, 4], [5, 2]]) + a = array([[sin(2), 4], [5, 1]]) + assert Matrix(a) == Matrix([[sin(2), 4], [5, 1]]) + assert Matrix(a) != Matrix([[sin(0), 4], [5, 1]]) + + +def test_Matrix4(): + with ignore_warnings(PendingDeprecationWarning): + a = matrix([[2, 4], [5, 1]]) + assert Matrix(a) == Matrix([[2, 4], [5, 1]]) + assert Matrix(a) != Matrix([[2, 4], [5, 2]]) + with ignore_warnings(PendingDeprecationWarning): + a = matrix([[sin(2), 4], [5, 1]]) + assert Matrix(a) == Matrix([[sin(2), 4], [5, 1]]) + assert Matrix(a) != Matrix([[sin(0), 4], [5, 1]]) + + +def test_Matrix_sum(): + M = Matrix([[1, 2, 3], [x, y, x], [2*y, -50, z*x]]) + with ignore_warnings(PendingDeprecationWarning): + m = matrix([[2, 3, 4], [x, 5, 6], [x, y, z**2]]) + assert M + m == Matrix([[3, 5, 7], [2*x, y + 5, x + 6], [2*y + x, y - 50, z*x + z**2]]) + assert m + M == Matrix([[3, 5, 7], [2*x, y + 5, x + 6], [2*y + x, y - 50, z*x + z**2]]) + assert M + m == M.add(m) + + +def test_Matrix_mul(): + M = Matrix([[1, 2, 3], [x, y, x]]) + with ignore_warnings(PendingDeprecationWarning): + m = matrix([[2, 4], [x, 6], [x, z**2]]) + assert M*m == Matrix([ + [ 2 + 5*x, 16 + 3*z**2], + [2*x + x*y + x**2, 4*x + 6*y + x*z**2], + ]) + + assert m*M == Matrix([ + [ 2 + 4*x, 4 + 4*y, 6 + 4*x], + [ 7*x, 2*x + 6*y, 9*x], + [x + x*z**2, 2*x + y*z**2, 3*x + x*z**2], + ]) + a = array([2]) + assert a[0] * M == 2 * M + assert M * a[0] == 2 * M + + +def test_Matrix_array(): + class matarray: + def __array__(self, dtype=object, copy=None): + if copy is not None and not copy: + raise TypeError("Cannot implement copy=False when converting Matrix to ndarray") + from numpy import array + return array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + matarr = matarray() + assert Matrix(matarr) == Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + + +def test_matrix2numpy(): + a = matrix2numpy(Matrix([[1, x**2], [3*sin(x), 0]])) + assert isinstance(a, ndarray) + assert a.shape == (2, 2) + assert a[0, 0] == 1 + assert a[0, 1] == x**2 + assert a[1, 0] == 3*sin(x) + assert a[1, 1] == 0 + + +def test_matrix2numpy_conversion(): + a = Matrix([[1, 2, sin(x)], [x**2, x, Rational(1, 2)]]) + b = array([[1, 2, sin(x)], [x**2, x, Rational(1, 2)]]) + assert (matrix2numpy(a) == b).all() + assert matrix2numpy(a).dtype == numpy.dtype('object') + + c = matrix2numpy(Matrix([[1, 2], [10, 20]]), dtype='int8') + d = matrix2numpy(Matrix([[1, 2], [10, 20]]), dtype='float64') + assert c.dtype == numpy.dtype('int8') + assert d.dtype == numpy.dtype('float64') + + +def test_issue_3728(): + assert (Rational(1, 2)*array([2*x, 0]) == array([x, 0])).all() + assert (Rational(1, 2) + array( + [2*x, 0]) == array([2*x + Rational(1, 2), Rational(1, 2)])).all() + assert (Float("0.5")*array([2*x, 0]) == array([Float("1.0")*x, 0])).all() + assert (Float("0.5") + array( + [2*x, 0]) == array([2*x + Float("0.5"), Float("0.5")])).all() + + +@conserve_mpmath_dps +def test_lambdify(): + mpmath.mp.dps = 16 + sin02 = mpmath.mpf("0.198669330795061215459412627") + f = lambdify(x, sin(x), "numpy") + prec = 1e-15 + assert -prec < f(0.2) - sin02 < prec + + # if this succeeds, it can't be a numpy function + + if version_tuple(numpy.__version__) >= version_tuple('1.17'): + with raises(TypeError): + f(x) + else: + with raises(AttributeError): + f(x) + + +def test_lambdify_matrix(): + f = lambdify(x, Matrix([[x, 2*x], [1, 2]]), [{'ImmutableMatrix': numpy.array}, "numpy"]) + assert (f(1) == array([[1, 2], [1, 2]])).all() + + +def test_lambdify_matrix_multi_input(): + M = sympy.Matrix([[x**2, x*y, x*z], + [y*x, y**2, y*z], + [z*x, z*y, z**2]]) + f = lambdify((x, y, z), M, [{'ImmutableMatrix': numpy.array}, "numpy"]) + + xh, yh, zh = 1.0, 2.0, 3.0 + expected = array([[xh**2, xh*yh, xh*zh], + [yh*xh, yh**2, yh*zh], + [zh*xh, zh*yh, zh**2]]) + actual = f(xh, yh, zh) + assert numpy.allclose(actual, expected) + + +def test_lambdify_matrix_vec_input(): + X = sympy.DeferredVector('X') + M = Matrix([ + [X[0]**2, X[0]*X[1], X[0]*X[2]], + [X[1]*X[0], X[1]**2, X[1]*X[2]], + [X[2]*X[0], X[2]*X[1], X[2]**2]]) + f = lambdify(X, M, [{'ImmutableMatrix': numpy.array}, "numpy"]) + + Xh = array([1.0, 2.0, 3.0]) + expected = array([[Xh[0]**2, Xh[0]*Xh[1], Xh[0]*Xh[2]], + [Xh[1]*Xh[0], Xh[1]**2, Xh[1]*Xh[2]], + [Xh[2]*Xh[0], Xh[2]*Xh[1], Xh[2]**2]]) + actual = f(Xh) + assert numpy.allclose(actual, expected) + + +def test_lambdify_transl(): + from sympy.utilities.lambdify import NUMPY_TRANSLATIONS + for sym, mat in NUMPY_TRANSLATIONS.items(): + assert sym in sympy.__dict__ + assert mat in numpy.__dict__ + + +def test_symarray(): + """Test creation of numpy arrays of SymPy symbols.""" + + import numpy as np + import numpy.testing as npt + + syms = symbols('_0,_1,_2') + s1 = symarray("", 3) + s2 = symarray("", 3) + npt.assert_array_equal(s1, np.array(syms, dtype=object)) + assert s1[0] == s2[0] + + a = symarray('a', 3) + b = symarray('b', 3) + assert not(a[0] == b[0]) + + asyms = symbols('a_0,a_1,a_2') + npt.assert_array_equal(a, np.array(asyms, dtype=object)) + + # Multidimensional checks + a2d = symarray('a', (2, 3)) + assert a2d.shape == (2, 3) + a00, a12 = symbols('a_0_0,a_1_2') + assert a2d[0, 0] == a00 + assert a2d[1, 2] == a12 + + a3d = symarray('a', (2, 3, 2)) + assert a3d.shape == (2, 3, 2) + a000, a120, a121 = symbols('a_0_0_0,a_1_2_0,a_1_2_1') + assert a3d[0, 0, 0] == a000 + assert a3d[1, 2, 0] == a120 + assert a3d[1, 2, 1] == a121 + + +def test_vectorize(): + assert (numpy.vectorize( + sin)([1, 2, 3]) == numpy.array([sin(1), sin(2), sin(3)])).all() diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_pythonmpq.py b/phivenv/Lib/site-packages/sympy/external/tests/test_pythonmpq.py new file mode 100644 index 0000000000000000000000000000000000000000..137cfdf5c858544f0811ae666f000cfb368787a0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_pythonmpq.py @@ -0,0 +1,176 @@ +""" +test_pythonmpq.py + +Test the PythonMPQ class for consistency with gmpy2's mpq type. If gmpy2 is +installed run the same tests for both. +""" +from fractions import Fraction +from decimal import Decimal +import pickle +from typing import Callable, List, Tuple, Type + +from sympy.testing.pytest import raises + +from sympy.external.pythonmpq import PythonMPQ + +# +# If gmpy2 is installed then run the tests for both mpq and PythonMPQ. +# That should ensure consistency between the implementation here and mpq. +# +rational_types: List[Tuple[Callable, Type, Callable, Type]] +rational_types = [(PythonMPQ, PythonMPQ, int, int)] +try: + from gmpy2 import mpq, mpz + rational_types.append((mpq, type(mpq(1)), mpz, type(mpz(1)))) +except ImportError: + pass + + +def test_PythonMPQ(): + # + # Test PythonMPQ and also mpq if gmpy/gmpy2 is installed. + # + for Q, TQ, Z, TZ in rational_types: + + def check_Q(q): + assert isinstance(q, TQ) + assert isinstance(q.numerator, TZ) + assert isinstance(q.denominator, TZ) + return q.numerator, q.denominator + + # Check construction from different types + assert check_Q(Q(3)) == (3, 1) + assert check_Q(Q(3, 5)) == (3, 5) + assert check_Q(Q(Q(3, 5))) == (3, 5) + assert check_Q(Q(0.5)) == (1, 2) + assert check_Q(Q('0.5')) == (1, 2) + assert check_Q(Q(Fraction(3, 5))) == (3, 5) + + # https://github.com/aleaxit/gmpy/issues/327 + if Q is PythonMPQ: + assert check_Q(Q(Decimal('0.6'))) == (3, 5) + + # Invalid types + raises(TypeError, lambda: Q([])) + raises(TypeError, lambda: Q([], [])) + + # Check normalisation of signs + assert check_Q(Q(2, 3)) == (2, 3) + assert check_Q(Q(-2, 3)) == (-2, 3) + assert check_Q(Q(2, -3)) == (-2, 3) + assert check_Q(Q(-2, -3)) == (2, 3) + + # Check gcd calculation + assert check_Q(Q(12, 8)) == (3, 2) + + # __int__/__float__ + assert int(Q(5, 3)) == 1 + assert int(Q(-5, 3)) == -1 + assert float(Q(5, 2)) == 2.5 + assert float(Q(-5, 2)) == -2.5 + + # __str__/__repr__ + assert str(Q(2, 1)) == "2" + assert str(Q(1, 2)) == "1/2" + if Q is PythonMPQ: + assert repr(Q(2, 1)) == "MPQ(2,1)" + assert repr(Q(1, 2)) == "MPQ(1,2)" + else: + assert repr(Q(2, 1)) == "mpq(2,1)" + assert repr(Q(1, 2)) == "mpq(1,2)" + + # __bool__ + assert bool(Q(1, 2)) is True + assert bool(Q(0)) is False + + # __eq__/__ne__ + assert (Q(2, 3) == Q(2, 3)) is True + assert (Q(2, 3) == Q(2, 5)) is False + assert (Q(2, 3) != Q(2, 3)) is False + assert (Q(2, 3) != Q(2, 5)) is True + + # __hash__ + assert hash(Q(3, 5)) == hash(Fraction(3, 5)) + + # __reduce__ + q = Q(2, 3) + assert pickle.loads(pickle.dumps(q)) == q + + # __ge__/__gt__/__le__/__lt__ + assert (Q(1, 3) < Q(2, 3)) is True + assert (Q(2, 3) < Q(2, 3)) is False + assert (Q(2, 3) < Q(1, 3)) is False + assert (Q(-2, 3) < Q(1, 3)) is True + assert (Q(1, 3) < Q(-2, 3)) is False + + assert (Q(1, 3) <= Q(2, 3)) is True + assert (Q(2, 3) <= Q(2, 3)) is True + assert (Q(2, 3) <= Q(1, 3)) is False + assert (Q(-2, 3) <= Q(1, 3)) is True + assert (Q(1, 3) <= Q(-2, 3)) is False + + assert (Q(1, 3) > Q(2, 3)) is False + assert (Q(2, 3) > Q(2, 3)) is False + assert (Q(2, 3) > Q(1, 3)) is True + assert (Q(-2, 3) > Q(1, 3)) is False + assert (Q(1, 3) > Q(-2, 3)) is True + + assert (Q(1, 3) >= Q(2, 3)) is False + assert (Q(2, 3) >= Q(2, 3)) is True + assert (Q(2, 3) >= Q(1, 3)) is True + assert (Q(-2, 3) >= Q(1, 3)) is False + assert (Q(1, 3) >= Q(-2, 3)) is True + + # __abs__/__pos__/__neg__ + assert abs(Q(2, 3)) == abs(Q(-2, 3)) == Q(2, 3) + assert +Q(2, 3) == Q(2, 3) + assert -Q(2, 3) == Q(-2, 3) + + # __add__/__radd__ + assert Q(2, 3) + Q(5, 7) == Q(29, 21) + assert Q(2, 3) + 1 == Q(5, 3) + assert 1 + Q(2, 3) == Q(5, 3) + raises(TypeError, lambda: [] + Q(1)) + raises(TypeError, lambda: Q(1) + []) + + # __sub__/__rsub__ + assert Q(2, 3) - Q(5, 7) == Q(-1, 21) + assert Q(2, 3) - 1 == Q(-1, 3) + assert 1 - Q(2, 3) == Q(1, 3) + raises(TypeError, lambda: [] - Q(1)) + raises(TypeError, lambda: Q(1) - []) + + # __mul__/__rmul__ + assert Q(2, 3) * Q(5, 7) == Q(10, 21) + assert Q(2, 3) * 1 == Q(2, 3) + assert 1 * Q(2, 3) == Q(2, 3) + raises(TypeError, lambda: [] * Q(1)) + raises(TypeError, lambda: Q(1) * []) + + # __pow__/__rpow__ + assert Q(2, 3) ** 2 == Q(4, 9) + assert Q(2, 3) ** 1 == Q(2, 3) + assert Q(-2, 3) ** 2 == Q(4, 9) + assert Q(-2, 3) ** -1 == Q(-3, 2) + if Q is PythonMPQ: + raises(TypeError, lambda: 1 ** Q(2, 3)) + raises(TypeError, lambda: Q(1, 4) ** Q(1, 2)) + raises(TypeError, lambda: [] ** Q(1)) + raises(TypeError, lambda: Q(1) ** []) + + # __div__/__rdiv__ + assert Q(2, 3) / Q(5, 7) == Q(14, 15) + assert Q(2, 3) / 1 == Q(2, 3) + assert 1 / Q(2, 3) == Q(3, 2) + raises(TypeError, lambda: [] / Q(1)) + raises(TypeError, lambda: Q(1) / []) + raises(ZeroDivisionError, lambda: Q(1, 2) / Q(0)) + + # __divmod__ + if Q is PythonMPQ: + raises(TypeError, lambda: Q(2, 3) // Q(1, 3)) + raises(TypeError, lambda: Q(2, 3) % Q(1, 3)) + raises(TypeError, lambda: 1 // Q(1, 3)) + raises(TypeError, lambda: 1 % Q(1, 3)) + raises(TypeError, lambda: Q(2, 3) // 1) + raises(TypeError, lambda: Q(2, 3) % 1) diff --git a/phivenv/Lib/site-packages/sympy/external/tests/test_scipy.py b/phivenv/Lib/site-packages/sympy/external/tests/test_scipy.py new file mode 100644 index 0000000000000000000000000000000000000000..3746d1a311eb68bb1af16e18ab152c7236b42bb5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/external/tests/test_scipy.py @@ -0,0 +1,35 @@ +# This testfile tests SymPy <-> SciPy compatibility + +# Don't test any SymPy features here. Just pure interaction with SciPy. +# Always write regular SymPy tests for anything, that can be tested in pure +# Python (without scipy). Here we test everything, that a user may need when +# using SymPy with SciPy + +from sympy.external import import_module + +scipy = import_module('scipy') +if not scipy: + #bin/test will not execute any tests now + disabled = True + +from sympy.functions.special.bessel import jn_zeros + + +def eq(a, b, tol=1e-6): + for x, y in zip(a, b): + if not (abs(x - y) < tol): + return False + return True + + +def test_jn_zeros(): + assert eq(jn_zeros(0, 4, method="scipy"), + [3.141592, 6.283185, 9.424777, 12.566370]) + assert eq(jn_zeros(1, 4, method="scipy"), + [4.493409, 7.725251, 10.904121, 14.066193]) + assert eq(jn_zeros(2, 4, method="scipy"), + [5.763459, 9.095011, 12.322940, 15.514603]) + assert eq(jn_zeros(3, 4, method="scipy"), + [6.987932, 10.417118, 13.698023, 16.923621]) + assert eq(jn_zeros(4, 4, method="scipy"), + [8.182561, 11.704907, 15.039664, 18.301255]) diff --git a/phivenv/Lib/site-packages/sympy/functions/__init__.py b/phivenv/Lib/site-packages/sympy/functions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ed93b2a11754aa26af5eef3932d177374b3ddfd6 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/__init__.py @@ -0,0 +1,115 @@ +"""A functions module, includes all the standard functions. + +Combinatorial - factorial, fibonacci, harmonic, bernoulli... +Elementary - hyperbolic, trigonometric, exponential, floor and ceiling, sqrt... +Special - gamma, zeta,spherical harmonics... +""" + +from sympy.functions.combinatorial.factorials import (factorial, factorial2, + rf, ff, binomial, RisingFactorial, FallingFactorial, subfactorial) +from sympy.functions.combinatorial.numbers import (carmichael, fibonacci, lucas, tribonacci, + harmonic, bernoulli, bell, euler, catalan, genocchi, andre, partition, divisor_sigma, + udivisor_sigma, legendre_symbol, jacobi_symbol, kronecker_symbol, mobius, + primenu, primeomega, totient, reduced_totient, primepi, motzkin) +from sympy.functions.elementary.miscellaneous import (sqrt, root, Min, Max, + Id, real_root, cbrt, Rem) +from sympy.functions.elementary.complexes import (re, im, sign, Abs, + conjugate, arg, polar_lift, periodic_argument, unbranched_argument, + principal_branch, transpose, adjoint, polarify, unpolarify) +from sympy.functions.elementary.trigonometric import (sin, cos, tan, + sec, csc, cot, sinc, asin, acos, atan, asec, acsc, acot, atan2) +from sympy.functions.elementary.exponential import (exp_polar, exp, log, + LambertW) +from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth, + sech, csch, asinh, acosh, atanh, acoth, asech, acsch) +from sympy.functions.elementary.integers import floor, ceiling, frac +from sympy.functions.elementary.piecewise import (Piecewise, piecewise_fold, + piecewise_exclusive) +from sympy.functions.special.error_functions import (erf, erfc, erfi, erf2, + erfinv, erfcinv, erf2inv, Ei, expint, E1, li, Li, Si, Ci, Shi, Chi, + fresnels, fresnelc) +from sympy.functions.special.gamma_functions import (gamma, lowergamma, + uppergamma, polygamma, loggamma, digamma, trigamma, multigamma) +from sympy.functions.special.zeta_functions import (dirichlet_eta, zeta, + lerchphi, polylog, stieltjes, riemann_xi) +from sympy.functions.special.tensor_functions import (Eijk, LeviCivita, + KroneckerDelta) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.bsplines import bspline_basis, bspline_basis_set, interpolating_spline +from sympy.functions.special.bessel import (besselj, bessely, besseli, besselk, + hankel1, hankel2, jn, yn, jn_zeros, hn1, hn2, airyai, airybi, airyaiprime, airybiprime, marcumq) +from sympy.functions.special.hyper import hyper, meijerg, appellf1 +from sympy.functions.special.polynomials import (legendre, assoc_legendre, + hermite, hermite_prob, chebyshevt, chebyshevu, chebyshevu_root, + chebyshevt_root, laguerre, assoc_laguerre, gegenbauer, jacobi, jacobi_normalized) +from sympy.functions.special.spherical_harmonics import Ynm, Ynm_c, Znm +from sympy.functions.special.elliptic_integrals import (elliptic_k, + elliptic_f, elliptic_e, elliptic_pi) +from sympy.functions.special.beta_functions import beta, betainc, betainc_regularized +from sympy.functions.special.mathieu_functions import (mathieus, mathieuc, + mathieusprime, mathieucprime) +ln = log + +__all__ = [ + 'factorial', 'factorial2', 'rf', 'ff', 'binomial', 'RisingFactorial', + 'FallingFactorial', 'subfactorial', + + 'carmichael', 'fibonacci', 'lucas', 'motzkin', 'tribonacci', 'harmonic', + 'bernoulli', 'bell', 'euler', 'catalan', 'genocchi', 'andre', 'partition', + 'divisor_sigma', 'udivisor_sigma', 'legendre_symbol', 'jacobi_symbol', 'kronecker_symbol', + 'mobius', 'primenu', 'primeomega', 'totient', 'reduced_totient', 'primepi', + + 'sqrt', 'root', 'Min', 'Max', 'Id', 'real_root', 'cbrt', 'Rem', + + 're', 'im', 'sign', 'Abs', 'conjugate', 'arg', 'polar_lift', + 'periodic_argument', 'unbranched_argument', 'principal_branch', + 'transpose', 'adjoint', 'polarify', 'unpolarify', + + 'sin', 'cos', 'tan', 'sec', 'csc', 'cot', 'sinc', 'asin', 'acos', 'atan', + 'asec', 'acsc', 'acot', 'atan2', + + 'exp_polar', 'exp', 'ln', 'log', 'LambertW', + + 'sinh', 'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh', + 'acoth', 'asech', 'acsch', + + 'floor', 'ceiling', 'frac', + + 'Piecewise', 'piecewise_fold', 'piecewise_exclusive', + + 'erf', 'erfc', 'erfi', 'erf2', 'erfinv', 'erfcinv', 'erf2inv', 'Ei', + 'expint', 'E1', 'li', 'Li', 'Si', 'Ci', 'Shi', 'Chi', 'fresnels', + 'fresnelc', + + 'gamma', 'lowergamma', 'uppergamma', 'polygamma', 'loggamma', 'digamma', + 'trigamma', 'multigamma', + + 'dirichlet_eta', 'zeta', 'lerchphi', 'polylog', 'stieltjes', 'riemann_xi', + + 'Eijk', 'LeviCivita', 'KroneckerDelta', + + 'SingularityFunction', + + 'DiracDelta', 'Heaviside', + + 'bspline_basis', 'bspline_basis_set', 'interpolating_spline', + + 'besselj', 'bessely', 'besseli', 'besselk', 'hankel1', 'hankel2', 'jn', + 'yn', 'jn_zeros', 'hn1', 'hn2', 'airyai', 'airybi', 'airyaiprime', + 'airybiprime', 'marcumq', + + 'hyper', 'meijerg', 'appellf1', + + 'legendre', 'assoc_legendre', 'hermite', 'hermite_prob', 'chebyshevt', + 'chebyshevu', 'chebyshevu_root', 'chebyshevt_root', 'laguerre', + 'assoc_laguerre', 'gegenbauer', 'jacobi', 'jacobi_normalized', + + 'Ynm', 'Ynm_c', 'Znm', + + 'elliptic_k', 'elliptic_f', 'elliptic_e', 'elliptic_pi', + + 'beta', 'betainc', 'betainc_regularized', + + 'mathieus', 'mathieuc', 'mathieusprime', 'mathieucprime', +] diff --git a/phivenv/Lib/site-packages/sympy/functions/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eea69cf125192c3f8856d04c2346dc1a006e5dd7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/__init__.py b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..584b3c8d46b5c7600d85efc7db46d7aa190397f8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__init__.py @@ -0,0 +1 @@ +# Stub __init__.py for sympy.functions.combinatorial diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8092af5b86f6ca9f644a6369de096d41b99b6b6b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/factorials.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/factorials.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2dc42202fe666608f88be254985545cad6a18cac Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/factorials.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/numbers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/numbers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a63a24c47549f2c84b1b9453f1cc02c343f0ed6d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/combinatorial/__pycache__/numbers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/factorials.py b/phivenv/Lib/site-packages/sympy/functions/combinatorial/factorials.py new file mode 100644 index 0000000000000000000000000000000000000000..0c6d2f09524debca29d3040b28a019127a244b33 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/combinatorial/factorials.py @@ -0,0 +1,1133 @@ +from __future__ import annotations +from functools import reduce + +from sympy.core import S, sympify, Dummy, Mod +from sympy.core.cache import cacheit +from sympy.core.function import DefinedFunction, ArgumentIndexError, PoleError +from sympy.core.logic import fuzzy_and +from sympy.core.numbers import Integer, pi, I +from sympy.core.relational import Eq +from sympy.external.gmpy import gmpy as _gmpy +from sympy.ntheory import sieve +from sympy.ntheory.residue_ntheory import binomial_mod +from sympy.polys.polytools import Poly + +from math import factorial as _factorial, prod, sqrt as _sqrt + +class CombinatorialFunction(DefinedFunction): + """Base class for combinatorial functions. """ + + def _eval_simplify(self, **kwargs): + from sympy.simplify.combsimp import combsimp + # combinatorial function with non-integer arguments is + # automatically passed to gammasimp + expr = combsimp(self) + measure = kwargs['measure'] + if measure(expr) <= kwargs['ratio']*measure(self): + return expr + return self + + +############################################################################### +######################## FACTORIAL and MULTI-FACTORIAL ######################## +############################################################################### + + +class factorial(CombinatorialFunction): + r"""Implementation of factorial function over nonnegative integers. + By convention (consistent with the gamma function and the binomial + coefficients), factorial of a negative integer is complex infinity. + + The factorial is very important in combinatorics where it gives + the number of ways in which `n` objects can be permuted. It also + arises in calculus, probability, number theory, etc. + + There is strict relation of factorial with gamma function. In + fact `n! = gamma(n+1)` for nonnegative integers. Rewrite of this + kind is very useful in case of combinatorial simplification. + + Computation of the factorial is done using two algorithms. For + small arguments a precomputed look up table is used. However for bigger + input algorithm Prime-Swing is used. It is the fastest algorithm + known and computes `n!` via prime factorization of special class + of numbers, called here the 'Swing Numbers'. + + Examples + ======== + + >>> from sympy import Symbol, factorial, S + >>> n = Symbol('n', integer=True) + + >>> factorial(0) + 1 + + >>> factorial(7) + 5040 + + >>> factorial(-2) + zoo + + >>> factorial(n) + factorial(n) + + >>> factorial(2*n) + factorial(2*n) + + >>> factorial(S(1)/2) + factorial(1/2) + + See Also + ======== + + factorial2, RisingFactorial, FallingFactorial + """ + + def fdiff(self, argindex=1): + from sympy.functions.special.gamma_functions import (gamma, polygamma) + if argindex == 1: + return gamma(self.args[0] + 1)*polygamma(0, self.args[0] + 1) + else: + raise ArgumentIndexError(self, argindex) + + _small_swing = [ + 1, 1, 1, 3, 3, 15, 5, 35, 35, 315, 63, 693, 231, 3003, 429, 6435, 6435, 109395, + 12155, 230945, 46189, 969969, 88179, 2028117, 676039, 16900975, 1300075, + 35102025, 5014575, 145422675, 9694845, 300540195, 300540195 + ] + + _small_factorials: list[int] = [] + + @classmethod + def _swing(cls, n): + if n < 33: + return cls._small_swing[n] + else: + N, primes = int(_sqrt(n)), [] + + for prime in sieve.primerange(3, N + 1): + p, q = 1, n + + while True: + q //= prime + + if q > 0: + if q & 1 == 1: + p *= prime + else: + break + + if p > 1: + primes.append(p) + + for prime in sieve.primerange(N + 1, n//3 + 1): + if (n // prime) & 1 == 1: + primes.append(prime) + + L_product = prod(sieve.primerange(n//2 + 1, n + 1)) + R_product = prod(primes) + + return L_product*R_product + + @classmethod + def _recursive(cls, n): + if n < 2: + return 1 + else: + return (cls._recursive(n//2)**2)*cls._swing(n) + + @classmethod + def eval(cls, n): + n = sympify(n) + + if n.is_Number: + if n.is_zero: + return S.One + elif n is S.Infinity: + return S.Infinity + elif n.is_Integer: + if n.is_negative: + return S.ComplexInfinity + else: + n = n.p + + if n < 20: + if not cls._small_factorials: + result = 1 + for i in range(1, 20): + result *= i + cls._small_factorials.append(result) + result = cls._small_factorials[n-1] + + # GMPY factorial is faster, use it when available + # + # XXX: There is a sympy.external.gmpy.factorial function + # which provides gmpy.fac if available or the flint version + # if flint is used. It could be used here to avoid the + # conditional logic but it needs to be checked whether the + # pure Python fallback used there is as fast as the + # fallback used here (perhaps the fallback here should be + # moved to sympy.external.ntheory). + elif _gmpy is not None: + result = _gmpy.fac(n) + + else: + bits = bin(n).count('1') + result = cls._recursive(n)*2**(n - bits) + + return Integer(result) + + def _facmod(self, n, q): + res, N = 1, int(_sqrt(n)) + + # Exponent of prime p in n! is e_p(n) = [n/p] + [n/p**2] + ... + # for p > sqrt(n), e_p(n) < sqrt(n), the primes with [n/p] = m, + # occur consecutively and are grouped together in pw[m] for + # simultaneous exponentiation at a later stage + pw = [1]*N + + m = 2 # to initialize the if condition below + for prime in sieve.primerange(2, n + 1): + if m > 1: + m, y = 0, n // prime + while y: + m += y + y //= prime + if m < N: + pw[m] = pw[m]*prime % q + else: + res = res*pow(prime, m, q) % q + + for ex, bs in enumerate(pw): + if ex == 0 or bs == 1: + continue + if bs == 0: + return 0 + res = res*pow(bs, ex, q) % q + + return res + + def _eval_Mod(self, q): + n = self.args[0] + if n.is_integer and n.is_nonnegative and q.is_integer: + aq = abs(q) + d = aq - n + if d.is_nonpositive: + return S.Zero + else: + isprime = aq.is_prime + if d == 1: + # Apply Wilson's theorem (if a natural number n > 1 + # is a prime number, then (n-1)! = -1 mod n) and + # its inverse (if n > 4 is a composite number, then + # (n-1)! = 0 mod n) + if isprime: + return -1 % q + elif isprime is False and (aq - 6).is_nonnegative: + return S.Zero + elif n.is_Integer and q.is_Integer: + n, d, aq = map(int, (n, d, aq)) + if isprime and (d - 1 < n): + fc = self._facmod(d - 1, aq) + fc = pow(fc, aq - 2, aq) + if d%2: + fc = -fc + else: + fc = self._facmod(n, aq) + + return fc % q + + def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs): + from sympy.functions.special.gamma_functions import gamma + return gamma(n + 1) + + def _eval_rewrite_as_Product(self, n, **kwargs): + from sympy.concrete.products import Product + if n.is_nonnegative and n.is_integer: + i = Dummy('i', integer=True) + return Product(i, (i, 1, n)) + + def _eval_is_integer(self): + if self.args[0].is_integer and self.args[0].is_nonnegative: + return True + + def _eval_is_positive(self): + if self.args[0].is_integer and self.args[0].is_nonnegative: + return True + + def _eval_is_even(self): + x = self.args[0] + if x.is_integer and x.is_nonnegative: + return (x - 2).is_nonnegative + + def _eval_is_composite(self): + x = self.args[0] + if x.is_integer and x.is_nonnegative: + return (x - 3).is_nonnegative + + def _eval_is_real(self): + x = self.args[0] + if x.is_nonnegative or x.is_noninteger: + return True + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x) + arg0 = arg.subs(x, 0) + if arg0.is_zero: + return S.One + elif not arg0.is_infinite: + return self.func(arg) + raise PoleError("Cannot expand %s around 0" % (self)) + +class MultiFactorial(CombinatorialFunction): + pass + + +class subfactorial(CombinatorialFunction): + r"""The subfactorial counts the derangements of $n$ items and is + defined for non-negative integers as: + + .. math:: !n = \begin{cases} 1 & n = 0 \\ 0 & n = 1 \\ + (n-1)(!(n-1) + !(n-2)) & n > 1 \end{cases} + + It can also be written as ``int(round(n!/exp(1)))`` but the + recursive definition with caching is implemented for this function. + + An interesting analytic expression is the following [2]_ + + .. math:: !x = \Gamma(x + 1, -1)/e + + which is valid for non-negative integers `x`. The above formula + is not very useful in case of non-integers. `\Gamma(x + 1, -1)` is + single-valued only for integral arguments `x`, elsewhere on the positive + real axis it has an infinite number of branches none of which are real. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Subfactorial + .. [2] https://mathworld.wolfram.com/Subfactorial.html + + Examples + ======== + + >>> from sympy import subfactorial + >>> from sympy.abc import n + >>> subfactorial(n + 1) + subfactorial(n + 1) + >>> subfactorial(5) + 44 + + See Also + ======== + + factorial, uppergamma, + sympy.utilities.iterables.generate_derangements + """ + + @classmethod + @cacheit + def _eval(self, n): + if not n: + return S.One + elif n == 1: + return S.Zero + else: + z1, z2 = 1, 0 + for i in range(2, n + 1): + z1, z2 = z2, (i - 1)*(z2 + z1) + return z2 + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg.is_Integer and arg.is_nonnegative: + return cls._eval(arg) + elif arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + + def _eval_is_even(self): + if self.args[0].is_odd and self.args[0].is_nonnegative: + return True + + def _eval_is_integer(self): + if self.args[0].is_integer and self.args[0].is_nonnegative: + return True + + def _eval_rewrite_as_factorial(self, arg, **kwargs): + from sympy.concrete.summations import summation + i = Dummy('i') + f = S.NegativeOne**i / factorial(i) + return factorial(arg) * summation(f, (i, 0, arg)) + + def _eval_rewrite_as_gamma(self, arg, piecewise=True, **kwargs): + from sympy.functions.elementary.exponential import exp + from sympy.functions.special.gamma_functions import (gamma, lowergamma) + return (S.NegativeOne**(arg + 1)*exp(-I*pi*arg)*lowergamma(arg + 1, -1) + + gamma(arg + 1))*exp(-1) + + def _eval_rewrite_as_uppergamma(self, arg, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return uppergamma(arg + 1, -1)/S.Exp1 + + def _eval_is_nonnegative(self): + if self.args[0].is_integer and self.args[0].is_nonnegative: + return True + + def _eval_is_odd(self): + if self.args[0].is_even and self.args[0].is_nonnegative: + return True + + +class factorial2(CombinatorialFunction): + r"""The double factorial `n!!`, not to be confused with `(n!)!` + + The double factorial is defined for nonnegative integers and for odd + negative integers as: + + .. math:: n!! = \begin{cases} 1 & n = 0 \\ + n(n-2)(n-4) \cdots 1 & n\ \text{positive odd} \\ + n(n-2)(n-4) \cdots 2 & n\ \text{positive even} \\ + (n+2)!!/(n+2) & n\ \text{negative odd} \end{cases} + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Double_factorial + + Examples + ======== + + >>> from sympy import factorial2, var + >>> n = var('n') + >>> n + n + >>> factorial2(n + 1) + factorial2(n + 1) + >>> factorial2(5) + 15 + >>> factorial2(-1) + 1 + >>> factorial2(-5) + 1/3 + + See Also + ======== + + factorial, RisingFactorial, FallingFactorial + """ + + @classmethod + def eval(cls, arg): + # TODO: extend this to complex numbers? + + if arg.is_Number: + if not arg.is_Integer: + raise ValueError("argument must be nonnegative integer " + "or negative odd integer") + + # This implementation is faster than the recursive one + # It also avoids "maximum recursion depth exceeded" runtime error + if arg.is_nonnegative: + if arg.is_even: + k = arg / 2 + return 2**k * factorial(k) + return factorial(arg) / factorial2(arg - 1) + + + if arg.is_odd: + return arg*(S.NegativeOne)**((1 - arg)/2) / factorial2(-arg) + raise ValueError("argument must be nonnegative integer " + "or negative odd integer") + + + def _eval_is_even(self): + # Double factorial is even for every positive even input + n = self.args[0] + if n.is_integer: + if n.is_odd: + return False + if n.is_even: + if n.is_positive: + return True + if n.is_zero: + return False + + def _eval_is_integer(self): + # Double factorial is an integer for every nonnegative input, and for + # -1 and -3 + n = self.args[0] + if n.is_integer: + if (n + 1).is_nonnegative: + return True + if n.is_odd: + return (n + 3).is_nonnegative + + def _eval_is_odd(self): + # Double factorial is odd for every odd input not smaller than -3, and + # for 0 + n = self.args[0] + if n.is_odd: + return (n + 3).is_nonnegative + if n.is_even: + if n.is_positive: + return False + if n.is_zero: + return True + + def _eval_is_positive(self): + # Double factorial is positive for every nonnegative input, and for + # every odd negative input which is of the form -1-4k for an + # nonnegative integer k + n = self.args[0] + if n.is_integer: + if (n + 1).is_nonnegative: + return True + if n.is_odd: + return ((n + 1) / 2).is_even + + def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs): + from sympy.functions.elementary.miscellaneous import sqrt + from sympy.functions.elementary.piecewise import Piecewise + from sympy.functions.special.gamma_functions import gamma + return 2**(n/2)*gamma(n/2 + 1) * Piecewise((1, Eq(Mod(n, 2), 0)), + (sqrt(2/pi), Eq(Mod(n, 2), 1))) + + +############################################################################### +######################## RISING and FALLING FACTORIALS ######################## +############################################################################### + + +class RisingFactorial(CombinatorialFunction): + r""" + Rising factorial (also called Pochhammer symbol [1]_) is a double valued + function arising in concrete mathematics, hypergeometric functions + and series expansions. It is defined by: + + .. math:: \texttt{rf(y, k)} = (x)^k = x \cdot (x+1) \cdots (x+k-1) + + where `x` can be arbitrary expression and `k` is an integer. For + more information check "Concrete mathematics" by Graham, pp. 66 + or visit https://mathworld.wolfram.com/RisingFactorial.html page. + + When `x` is a `~.Poly` instance of degree $\ge 1$ with a single variable, + `(x)^k = x(y) \cdot x(y+1) \cdots x(y+k-1)`, where `y` is the + variable of `x`. This is as described in [2]_. + + Examples + ======== + + >>> from sympy import rf, Poly + >>> from sympy.abc import x + >>> rf(x, 0) + 1 + >>> rf(1, 5) + 120 + >>> rf(x, 5) == x*(1 + x)*(2 + x)*(3 + x)*(4 + x) + True + >>> rf(Poly(x**3, x), 2) + Poly(x**6 + 3*x**5 + 3*x**4 + x**3, x, domain='ZZ') + + Rewriting is complicated unless the relationship between + the arguments is known, but rising factorial can + be rewritten in terms of gamma, factorial, binomial, + and falling factorial. + + >>> from sympy import Symbol, factorial, ff, binomial, gamma + >>> n = Symbol('n', integer=True, positive=True) + >>> R = rf(n, n + 2) + >>> for i in (rf, ff, factorial, binomial, gamma): + ... R.rewrite(i) + ... + RisingFactorial(n, n + 2) + FallingFactorial(2*n + 1, n + 2) + factorial(2*n + 1)/factorial(n - 1) + binomial(2*n + 1, n + 2)*factorial(n + 2) + gamma(2*n + 2)/gamma(n) + + See Also + ======== + + factorial, factorial2, FallingFactorial + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Pochhammer_symbol + .. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic + Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268, + 1995. + + """ + + @classmethod + def eval(cls, x, k): + x = sympify(x) + k = sympify(k) + + if x is S.NaN or k is S.NaN: + return S.NaN + elif x is S.One: + return factorial(k) + elif k.is_Integer: + if k.is_zero: + return S.One + else: + if k.is_positive: + if x is S.Infinity: + return S.Infinity + elif x is S.NegativeInfinity: + if k.is_odd: + return S.NegativeInfinity + else: + return S.Infinity + else: + if isinstance(x, Poly): + gens = x.gens + if len(gens)!= 1: + raise ValueError("rf only defined for " + "polynomials on one generator") + else: + return reduce(lambda r, i: + r*(x.shift(i)), + range(int(k)), 1) + else: + return reduce(lambda r, i: r*(x + i), + range(int(k)), 1) + + else: + if x is S.Infinity: + return S.Infinity + elif x is S.NegativeInfinity: + return S.Infinity + else: + if isinstance(x, Poly): + gens = x.gens + if len(gens)!= 1: + raise ValueError("rf only defined for " + "polynomials on one generator") + else: + return 1/reduce(lambda r, i: + r*(x.shift(-i)), + range(1, abs(int(k)) + 1), 1) + else: + return 1/reduce(lambda r, i: + r*(x - i), + range(1, abs(int(k)) + 1), 1) + + if k.is_integer == False: + if x.is_integer and x.is_negative: + return S.Zero + + def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs): + from sympy.functions.elementary.piecewise import Piecewise + from sympy.functions.special.gamma_functions import gamma + if not piecewise: + if (x <= 0) == True: + return S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1) + return gamma(x + k) / gamma(x) + return Piecewise( + (gamma(x + k) / gamma(x), x > 0), + (S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1), True)) + + def _eval_rewrite_as_FallingFactorial(self, x, k, **kwargs): + return FallingFactorial(x + k - 1, k) + + def _eval_rewrite_as_factorial(self, x, k, **kwargs): + from sympy.functions.elementary.piecewise import Piecewise + if x.is_integer and k.is_integer: + return Piecewise( + (factorial(k + x - 1)/factorial(x - 1), x > 0), + (S.NegativeOne**k*factorial(-x)/factorial(-k - x), True)) + + def _eval_rewrite_as_binomial(self, x, k, **kwargs): + if k.is_integer: + return factorial(k) * binomial(x + k - 1, k) + + def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs): + from sympy.functions.special.gamma_functions import gamma + if limitvar: + k_lim = k.subs(limitvar, S.Infinity) + if k_lim is S.Infinity: + return (gamma(x + k).rewrite('tractable', deep=True) / gamma(x)) + elif k_lim is S.NegativeInfinity: + return (S.NegativeOne**k*gamma(1 - x) / gamma(-k - x + 1).rewrite('tractable', deep=True)) + return self.rewrite(gamma).rewrite('tractable', deep=True) + + def _eval_is_integer(self): + return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer, + self.args[1].is_nonnegative)) + + +class FallingFactorial(CombinatorialFunction): + r""" + Falling factorial (related to rising factorial) is a double valued + function arising in concrete mathematics, hypergeometric functions + and series expansions. It is defined by + + .. math:: \texttt{ff(x, k)} = (x)_k = x \cdot (x-1) \cdots (x-k+1) + + where `x` can be arbitrary expression and `k` is an integer. For + more information check "Concrete mathematics" by Graham, pp. 66 + or [1]_. + + When `x` is a `~.Poly` instance of degree $\ge 1$ with single variable, + `(x)_k = x(y) \cdot x(y-1) \cdots x(y-k+1)`, where `y` is the + variable of `x`. This is as described in + + >>> from sympy import ff, Poly, Symbol + >>> from sympy.abc import x + >>> n = Symbol('n', integer=True) + + >>> ff(x, 0) + 1 + >>> ff(5, 5) + 120 + >>> ff(x, 5) == x*(x - 1)*(x - 2)*(x - 3)*(x - 4) + True + >>> ff(Poly(x**2, x), 2) + Poly(x**4 - 2*x**3 + x**2, x, domain='ZZ') + >>> ff(n, n) + factorial(n) + + Rewriting is complicated unless the relationship between + the arguments is known, but falling factorial can + be rewritten in terms of gamma, factorial and binomial + and rising factorial. + + >>> from sympy import factorial, rf, gamma, binomial, Symbol + >>> n = Symbol('n', integer=True, positive=True) + >>> F = ff(n, n - 2) + >>> for i in (rf, ff, factorial, binomial, gamma): + ... F.rewrite(i) + ... + RisingFactorial(3, n - 2) + FallingFactorial(n, n - 2) + factorial(n)/2 + binomial(n, n - 2)*factorial(n - 2) + gamma(n + 1)/2 + + See Also + ======== + + factorial, factorial2, RisingFactorial + + References + ========== + + .. [1] https://mathworld.wolfram.com/FallingFactorial.html + .. [2] Peter Paule, "Greatest Factorial Factorization and Symbolic + Summation", Journal of Symbolic Computation, vol. 20, pp. 235-268, + 1995. + + """ + + @classmethod + def eval(cls, x, k): + x = sympify(x) + k = sympify(k) + + if x is S.NaN or k is S.NaN: + return S.NaN + elif k.is_integer and x == k: + return factorial(x) + elif k.is_Integer: + if k.is_zero: + return S.One + else: + if k.is_positive: + if x is S.Infinity: + return S.Infinity + elif x is S.NegativeInfinity: + if k.is_odd: + return S.NegativeInfinity + else: + return S.Infinity + else: + if isinstance(x, Poly): + gens = x.gens + if len(gens)!= 1: + raise ValueError("ff only defined for " + "polynomials on one generator") + else: + return reduce(lambda r, i: + r*(x.shift(-i)), + range(int(k)), 1) + else: + return reduce(lambda r, i: r*(x - i), + range(int(k)), 1) + else: + if x is S.Infinity: + return S.Infinity + elif x is S.NegativeInfinity: + return S.Infinity + else: + if isinstance(x, Poly): + gens = x.gens + if len(gens)!= 1: + raise ValueError("rf only defined for " + "polynomials on one generator") + else: + return 1/reduce(lambda r, i: + r*(x.shift(i)), + range(1, abs(int(k)) + 1), 1) + else: + return 1/reduce(lambda r, i: r*(x + i), + range(1, abs(int(k)) + 1), 1) + + def _eval_rewrite_as_gamma(self, x, k, piecewise=True, **kwargs): + from sympy.functions.elementary.piecewise import Piecewise + from sympy.functions.special.gamma_functions import gamma + if not piecewise: + if (x < 0) == True: + return S.NegativeOne**k*gamma(k - x) / gamma(-x) + return gamma(x + 1) / gamma(x - k + 1) + return Piecewise( + (gamma(x + 1) / gamma(x - k + 1), x >= 0), + (S.NegativeOne**k*gamma(k - x) / gamma(-x), True)) + + def _eval_rewrite_as_RisingFactorial(self, x, k, **kwargs): + return rf(x - k + 1, k) + + def _eval_rewrite_as_binomial(self, x, k, **kwargs): + if k.is_integer: + return factorial(k) * binomial(x, k) + + def _eval_rewrite_as_factorial(self, x, k, **kwargs): + from sympy.functions.elementary.piecewise import Piecewise + if x.is_integer and k.is_integer: + return Piecewise( + (factorial(x)/factorial(-k + x), x >= 0), + (S.NegativeOne**k*factorial(k - x - 1)/factorial(-x - 1), True)) + + def _eval_rewrite_as_tractable(self, x, k, limitvar=None, **kwargs): + from sympy.functions.special.gamma_functions import gamma + if limitvar: + k_lim = k.subs(limitvar, S.Infinity) + if k_lim is S.Infinity: + return (S.NegativeOne**k*gamma(k - x).rewrite('tractable', deep=True) / gamma(-x)) + elif k_lim is S.NegativeInfinity: + return (gamma(x + 1) / gamma(x - k + 1).rewrite('tractable', deep=True)) + return self.rewrite(gamma).rewrite('tractable', deep=True) + + def _eval_is_integer(self): + return fuzzy_and((self.args[0].is_integer, self.args[1].is_integer, + self.args[1].is_nonnegative)) + + +rf = RisingFactorial +ff = FallingFactorial + +############################################################################### +########################### BINOMIAL COEFFICIENTS ############################# +############################################################################### + + +class binomial(CombinatorialFunction): + r"""Implementation of the binomial coefficient. It can be defined + in two ways depending on its desired interpretation: + + .. math:: \binom{n}{k} = \frac{n!}{k!(n-k)!}\ \text{or}\ + \binom{n}{k} = \frac{(n)_k}{k!} + + First, in a strict combinatorial sense it defines the + number of ways we can choose `k` elements from a set of + `n` elements. In this case both arguments are nonnegative + integers and binomial is computed using an efficient + algorithm based on prime factorization. + + The other definition is generalization for arbitrary `n`, + however `k` must also be nonnegative. This case is very + useful when evaluating summations. + + For the sake of convenience, for negative integer `k` this function + will return zero no matter the other argument. + + To expand the binomial when `n` is a symbol, use either + ``expand_func()`` or ``expand(func=True)``. The former will keep + the polynomial in factored form while the latter will expand the + polynomial itself. See examples for details. + + Examples + ======== + + >>> from sympy import Symbol, Rational, binomial, expand_func + >>> n = Symbol('n', integer=True, positive=True) + + >>> binomial(15, 8) + 6435 + + >>> binomial(n, -1) + 0 + + Rows of Pascal's triangle can be generated with the binomial function: + + >>> for N in range(8): + ... print([binomial(N, i) for i in range(N + 1)]) + ... + [1] + [1, 1] + [1, 2, 1] + [1, 3, 3, 1] + [1, 4, 6, 4, 1] + [1, 5, 10, 10, 5, 1] + [1, 6, 15, 20, 15, 6, 1] + [1, 7, 21, 35, 35, 21, 7, 1] + + As can a given diagonal, e.g. the 4th diagonal: + + >>> N = -4 + >>> [binomial(N, i) for i in range(1 - N)] + [1, -4, 10, -20, 35] + + >>> binomial(Rational(5, 4), 3) + -5/128 + >>> binomial(Rational(-5, 4), 3) + -195/128 + + >>> binomial(n, 3) + binomial(n, 3) + + >>> binomial(n, 3).expand(func=True) + n**3/6 - n**2/2 + n/3 + + >>> expand_func(binomial(n, 3)) + n*(n - 2)*(n - 1)/6 + + In many cases, we can also compute binomial coefficients modulo a + prime p quickly using Lucas' Theorem [2]_, though we need to include + `evaluate=False` to postpone evaluation: + + >>> from sympy import Mod + >>> Mod(binomial(156675, 4433, evaluate=False), 10**5 + 3) + 28625 + + Using a generalisation of Lucas's Theorem given by Granville [3]_, + we can extend this to arbitrary n: + + >>> Mod(binomial(10**18, 10**12, evaluate=False), (10**5 + 3)**2) + 3744312326 + + References + ========== + + .. [1] https://www.johndcook.com/blog/binomial_coefficients/ + .. [2] https://en.wikipedia.org/wiki/Lucas%27s_theorem + .. [3] Binomial coefficients modulo prime powers, Andrew Granville, + Available: https://web.archive.org/web/20170202003812/http://www.dms.umontreal.ca/~andrew/PDF/BinCoeff.pdf + """ + + def fdiff(self, argindex=1): + from sympy.functions.special.gamma_functions import polygamma + if argindex == 1: + # https://functions.wolfram.com/GammaBetaErf/Binomial/20/01/01/ + n, k = self.args + return binomial(n, k)*(polygamma(0, n + 1) - \ + polygamma(0, n - k + 1)) + elif argindex == 2: + # https://functions.wolfram.com/GammaBetaErf/Binomial/20/01/02/ + n, k = self.args + return binomial(n, k)*(polygamma(0, n - k + 1) - \ + polygamma(0, k + 1)) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def _eval(self, n, k): + # n.is_Number and k.is_Integer and k != 1 and n != k + + if k.is_Integer: + if n.is_Integer and n >= 0: + n, k = int(n), int(k) + + if k > n: + return S.Zero + elif k > n // 2: + k = n - k + + # XXX: This conditional logic should be moved to + # sympy.external.gmpy and the pure Python version of bincoef + # should be moved to sympy.external.ntheory. + if _gmpy is not None: + return Integer(_gmpy.bincoef(n, k)) + + d, result = n - k, 1 + for i in range(1, k + 1): + d += 1 + result = result * d // i + return Integer(result) + else: + d, result = n - k, 1 + for i in range(1, k + 1): + d += 1 + result *= d + return result / _factorial(k) + + @classmethod + def eval(cls, n, k): + n, k = map(sympify, (n, k)) + d = n - k + n_nonneg, n_isint = n.is_nonnegative, n.is_integer + if k.is_zero or ((n_nonneg or n_isint is False) + and d.is_zero): + return S.One + if (k - 1).is_zero or ((n_nonneg or n_isint is False) + and (d - 1).is_zero): + return n + if k.is_integer: + if k.is_negative or (n_nonneg and n_isint and d.is_negative): + return S.Zero + elif n.is_number: + res = cls._eval(n, k) + return res.expand(basic=True) if res else res + elif n_nonneg is False and n_isint: + # a special case when binomial evaluates to complex infinity + return S.ComplexInfinity + elif k.is_number: + from sympy.functions.special.gamma_functions import gamma + return gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1)) + + def _eval_Mod(self, q): + n, k = self.args + + if any(x.is_integer is False for x in (n, k, q)): + raise ValueError("Integers expected for binomial Mod") + + if all(x.is_Integer for x in (n, k, q)): + n, k = map(int, (n, k)) + aq, res = abs(q), 1 + + # handle negative integers k or n + if k < 0: + return S.Zero + if n < 0: + n = -n + k - 1 + res = -1 if k%2 else 1 + + # non negative integers k and n + if k > n: + return S.Zero + + isprime = aq.is_prime + aq = int(aq) + if isprime: + if aq < n: + # use Lucas Theorem + N, K = n, k + while N or K: + res = res*binomial(N % aq, K % aq) % aq + N, K = N // aq, K // aq + + else: + # use Factorial Modulo + d = n - k + if k > d: + k, d = d, k + kf = 1 + for i in range(2, k + 1): + kf = kf*i % aq + df = kf + for i in range(k + 1, d + 1): + df = df*i % aq + res *= df + for i in range(d + 1, n + 1): + res = res*i % aq + + res *= pow(kf*df % aq, aq - 2, aq) + res %= aq + + elif _sqrt(q) < k and q != 1: + res = binomial_mod(n, k, q) + + else: + # Binomial Factorization is performed by calculating the + # exponents of primes <= n in `n! /(k! (n - k)!)`, + # for non-negative integers n and k. As the exponent of + # prime in n! is e_p(n) = [n/p] + [n/p**2] + ... + # the exponent of prime in binomial(n, k) would be + # e_p(n) - e_p(k) - e_p(n - k) + M = int(_sqrt(n)) + for prime in sieve.primerange(2, n + 1): + if prime > n - k: + res = res*prime % aq + elif prime > n // 2: + continue + elif prime > M: + if n % prime < k % prime: + res = res*prime % aq + else: + N, K = n, k + exp = a = 0 + + while N > 0: + a = int((N % prime) < (K % prime + a)) + N, K = N // prime, K // prime + exp += a + + if exp > 0: + res *= pow(prime, exp, aq) + res %= aq + + return S(res % q) + + def _eval_expand_func(self, **hints): + """ + Function to expand binomial(n, k) when m is positive integer + Also, + n is self.args[0] and k is self.args[1] while using binomial(n, k) + """ + n = self.args[0] + if n.is_Number: + return binomial(*self.args) + + k = self.args[1] + if (n-k).is_Integer: + k = n - k + + if k.is_Integer: + if k.is_zero: + return S.One + elif k.is_negative: + return S.Zero + else: + n, result = self.args[0], 1 + for i in range(1, k + 1): + result *= n - k + i + return result / _factorial(k) + else: + return binomial(*self.args) + + def _eval_rewrite_as_factorial(self, n, k, **kwargs): + return factorial(n)/(factorial(k)*factorial(n - k)) + + def _eval_rewrite_as_gamma(self, n, k, piecewise=True, **kwargs): + from sympy.functions.special.gamma_functions import gamma + return gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1)) + + def _eval_rewrite_as_tractable(self, n, k, limitvar=None, **kwargs): + return self._eval_rewrite_as_gamma(n, k).rewrite('tractable') + + def _eval_rewrite_as_FallingFactorial(self, n, k, **kwargs): + if k.is_integer: + return ff(n, k) / factorial(k) + + def _eval_is_integer(self): + n, k = self.args + if n.is_integer and k.is_integer: + return True + elif k.is_integer is False: + return False + + def _eval_is_nonnegative(self): + n, k = self.args + if n.is_integer and k.is_integer: + if n.is_nonnegative or k.is_negative or k.is_even: + return True + elif k.is_even is False: + return False + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.functions.special.gamma_functions import gamma + return self.rewrite(gamma)._eval_as_leading_term(x, logx=logx, cdir=cdir) diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/numbers.py b/phivenv/Lib/site-packages/sympy/functions/combinatorial/numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..c0dfc518d4a6784712341edaa5731145469a8d1e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/combinatorial/numbers.py @@ -0,0 +1,3196 @@ +""" +This module implements some special functions that commonly appear in +combinatorial contexts (e.g. in power series); in particular, +sequences of rational numbers such as Bernoulli and Fibonacci numbers. + +Factorials, binomial coefficients and related functions are located in +the separate 'factorials' module. +""" +from __future__ import annotations +from math import prod +from collections import defaultdict +from typing import Callable + +from sympy.core import S, Symbol, Add, Dummy +from sympy.core.cache import cacheit +from sympy.core.containers import Dict +from sympy.core.expr import Expr +from sympy.core.function import ArgumentIndexError, DefinedFunction, expand_mul +from sympy.core.logic import fuzzy_not +from sympy.core.mul import Mul +from sympy.core.numbers import E, I, pi, oo, Rational, Integer +from sympy.core.relational import Eq, is_le, is_gt, is_lt +from sympy.external.gmpy import SYMPY_INTS, remove, lcm, legendre, jacobi, kronecker +from sympy.functions.combinatorial.factorials import (binomial, + factorial, subfactorial) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.piecewise import Piecewise +from sympy.ntheory.factor_ import (factorint, _divisor_sigma, is_carmichael, + find_carmichael_numbers_in_range, find_first_n_carmichaels) +from sympy.ntheory.generate import _primepi +from sympy.ntheory.partitions_ import _partition, _partition_rec +from sympy.ntheory.primetest import isprime, is_square +from sympy.polys.appellseqs import bernoulli_poly, euler_poly, genocchi_poly +from sympy.polys.polytools import cancel +from sympy.utilities.enumerative import MultisetPartitionTraverser +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import multiset, multiset_derangements, iterable +from sympy.utilities.memoization import recurrence_memo +from sympy.utilities.misc import as_int + +from mpmath import mp, workprec +from mpmath.libmp import ifib as _ifib + + +def _product(a, b): + return prod(range(a, b + 1)) + + +# Dummy symbol used for computing polynomial sequences +_sym = Symbol('x') + + +#----------------------------------------------------------------------------# +# # +# Carmichael numbers # +# # +#----------------------------------------------------------------------------# + +class carmichael(DefinedFunction): + r""" + Carmichael Numbers: + + Certain cryptographic algorithms make use of big prime numbers. + However, checking whether a big number is prime is not so easy. + Randomized prime number checking tests exist that offer a high degree of + confidence of accurate determination at low cost, such as the Fermat test. + + Let 'a' be a random number between $2$ and $n - 1$, where $n$ is the + number whose primality we are testing. Then, $n$ is probably prime if it + satisfies the modular arithmetic congruence relation: + + .. math :: a^{n-1} = 1 \pmod{n} + + (where mod refers to the modulo operation) + + If a number passes the Fermat test several times, then it is prime with a + high probability. + + Unfortunately, certain composite numbers (non-primes) still pass the Fermat + test with every number smaller than themselves. + These numbers are called Carmichael numbers. + + A Carmichael number will pass a Fermat primality test to every base $b$ + relatively prime to the number, even though it is not actually prime. + This makes tests based on Fermat's Little Theorem less effective than + strong probable prime tests such as the Baillie-PSW primality test and + the Miller-Rabin primality test. + + Examples + ======== + + >>> from sympy.ntheory.factor_ import find_first_n_carmichaels, find_carmichael_numbers_in_range + >>> find_first_n_carmichaels(5) + [561, 1105, 1729, 2465, 2821] + >>> find_carmichael_numbers_in_range(0, 562) + [561] + >>> find_carmichael_numbers_in_range(0,1000) + [561] + >>> find_carmichael_numbers_in_range(0,2000) + [561, 1105, 1729] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Carmichael_number + .. [2] https://en.wikipedia.org/wiki/Fermat_primality_test + .. [3] https://www.jstor.org/stable/23248683?seq=1#metadata_info_tab_contents + """ + + @staticmethod + def is_perfect_square(n): + sympy_deprecation_warning( + """ +is_perfect_square is just a wrapper around sympy.ntheory.primetest.is_square +so use that directly instead. + """, + deprecated_since_version="1.11", + active_deprecations_target='deprecated-carmichael-static-methods', + ) + return is_square(n) + + @staticmethod + def divides(p, n): + sympy_deprecation_warning( + """ + divides can be replaced by directly testing n % p == 0. + """, + deprecated_since_version="1.11", + active_deprecations_target='deprecated-carmichael-static-methods', + ) + return n % p == 0 + + @staticmethod + def is_prime(n): + sympy_deprecation_warning( + """ +is_prime is just a wrapper around sympy.ntheory.primetest.isprime so use that +directly instead. + """, + deprecated_since_version="1.11", + active_deprecations_target='deprecated-carmichael-static-methods', + ) + return isprime(n) + + @staticmethod + def is_carmichael(n): + sympy_deprecation_warning( + """ +is_carmichael is just a wrapper around sympy.ntheory.factor_.is_carmichael so use that +directly instead. + """, + deprecated_since_version="1.13", + active_deprecations_target='deprecated-ntheory-symbolic-functions', + ) + return is_carmichael(n) + + @staticmethod + def find_carmichael_numbers_in_range(x, y): + sympy_deprecation_warning( + """ +find_carmichael_numbers_in_range is just a wrapper around sympy.ntheory.factor_.find_carmichael_numbers_in_range so use that +directly instead. + """, + deprecated_since_version="1.13", + active_deprecations_target='deprecated-ntheory-symbolic-functions', + ) + return find_carmichael_numbers_in_range(x, y) + + @staticmethod + def find_first_n_carmichaels(n): + sympy_deprecation_warning( + """ +find_first_n_carmichaels is just a wrapper around sympy.ntheory.factor_.find_first_n_carmichaels so use that +directly instead. + """, + deprecated_since_version="1.13", + active_deprecations_target='deprecated-ntheory-symbolic-functions', + ) + return find_first_n_carmichaels(n) + + +#----------------------------------------------------------------------------# +# # +# Fibonacci numbers # +# # +#----------------------------------------------------------------------------# + + +class fibonacci(DefinedFunction): + r""" + Fibonacci numbers / Fibonacci polynomials + + The Fibonacci numbers are the integer sequence defined by the + initial terms `F_0 = 0`, `F_1 = 1` and the two-term recurrence + relation `F_n = F_{n-1} + F_{n-2}`. This definition + extended to arbitrary real and complex arguments using + the formula + + .. math :: F_z = \frac{\phi^z - \cos(\pi z) \phi^{-z}}{\sqrt 5} + + The Fibonacci polynomials are defined by `F_1(x) = 1`, + `F_2(x) = x`, and `F_n(x) = x*F_{n-1}(x) + F_{n-2}(x)` for `n > 2`. + For all positive integers `n`, `F_n(1) = F_n`. + + * ``fibonacci(n)`` gives the `n^{th}` Fibonacci number, `F_n` + * ``fibonacci(n, x)`` gives the `n^{th}` Fibonacci polynomial in `x`, `F_n(x)` + + Examples + ======== + + >>> from sympy import fibonacci, Symbol + + >>> [fibonacci(x) for x in range(11)] + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + >>> fibonacci(5, Symbol('t')) + t**4 + 3*t**2 + 1 + + See Also + ======== + + bell, bernoulli, catalan, euler, harmonic, lucas, genocchi, partition, tribonacci + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Fibonacci_number + .. [2] https://mathworld.wolfram.com/FibonacciNumber.html + + """ + + @staticmethod + def _fib(n): + return _ifib(n) + + @staticmethod + @recurrence_memo([None, S.One, _sym]) + def _fibpoly(n, prev): + return (prev[-2] + _sym*prev[-1]).expand() + + @classmethod + def eval(cls, n, sym=None): + if n is S.Infinity: + return S.Infinity + + if n.is_Integer: + if sym is None: + n = int(n) + if n < 0: + return S.NegativeOne**(n + 1) * fibonacci(-n) + else: + return Integer(cls._fib(n)) + else: + if n < 1: + raise ValueError("Fibonacci polynomials are defined " + "only for positive integer indices.") + return cls._fibpoly(n).subs(_sym, sym) + + def _eval_rewrite_as_tractable(self, n, **kwargs): + from sympy.functions import sqrt, cos + return (S.GoldenRatio**n - cos(S.Pi*n)/S.GoldenRatio**n)/sqrt(5) + + def _eval_rewrite_as_sqrt(self, n, **kwargs): + from sympy.functions.elementary.miscellaneous import sqrt + return 2**(-n)*sqrt(5)*((1 + sqrt(5))**n - (-sqrt(5) + 1)**n) / 5 + + def _eval_rewrite_as_GoldenRatio(self,n, **kwargs): + return (S.GoldenRatio**n - 1/(-S.GoldenRatio)**n)/(2*S.GoldenRatio-1) + + +#----------------------------------------------------------------------------# +# # +# Lucas numbers # +# # +#----------------------------------------------------------------------------# + + +class lucas(DefinedFunction): + """ + Lucas numbers + + Lucas numbers satisfy a recurrence relation similar to that of + the Fibonacci sequence, in which each term is the sum of the + preceding two. They are generated by choosing the initial + values `L_0 = 2` and `L_1 = 1`. + + * ``lucas(n)`` gives the `n^{th}` Lucas number + + Examples + ======== + + >>> from sympy import lucas + + >>> [lucas(x) for x in range(11)] + [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123] + + See Also + ======== + + bell, bernoulli, catalan, euler, fibonacci, harmonic, genocchi, partition, tribonacci + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lucas_number + .. [2] https://mathworld.wolfram.com/LucasNumber.html + + """ + + @classmethod + def eval(cls, n): + if n is S.Infinity: + return S.Infinity + + if n.is_Integer: + return fibonacci(n + 1) + fibonacci(n - 1) + + def _eval_rewrite_as_sqrt(self, n, **kwargs): + from sympy.functions.elementary.miscellaneous import sqrt + return 2**(-n)*((1 + sqrt(5))**n + (-sqrt(5) + 1)**n) + + +#----------------------------------------------------------------------------# +# # +# Tribonacci numbers # +# # +#----------------------------------------------------------------------------# + + +class tribonacci(DefinedFunction): + r""" + Tribonacci numbers / Tribonacci polynomials + + The Tribonacci numbers are the integer sequence defined by the + initial terms `T_0 = 0`, `T_1 = 1`, `T_2 = 1` and the three-term + recurrence relation `T_n = T_{n-1} + T_{n-2} + T_{n-3}`. + + The Tribonacci polynomials are defined by `T_0(x) = 0`, `T_1(x) = 1`, + `T_2(x) = x^2`, and `T_n(x) = x^2 T_{n-1}(x) + x T_{n-2}(x) + T_{n-3}(x)` + for `n > 2`. For all positive integers `n`, `T_n(1) = T_n`. + + * ``tribonacci(n)`` gives the `n^{th}` Tribonacci number, `T_n` + * ``tribonacci(n, x)`` gives the `n^{th}` Tribonacci polynomial in `x`, `T_n(x)` + + Examples + ======== + + >>> from sympy import tribonacci, Symbol + + >>> [tribonacci(x) for x in range(11)] + [0, 1, 1, 2, 4, 7, 13, 24, 44, 81, 149] + >>> tribonacci(5, Symbol('t')) + t**8 + 3*t**5 + 3*t**2 + + See Also + ======== + + bell, bernoulli, catalan, euler, fibonacci, harmonic, lucas, genocchi, partition + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Tribonacci_numbers + .. [2] https://mathworld.wolfram.com/TribonacciNumber.html + .. [3] https://oeis.org/A000073 + + """ + + @staticmethod + @recurrence_memo([S.Zero, S.One, S.One]) + def _trib(n, prev): + return (prev[-3] + prev[-2] + prev[-1]) + + @staticmethod + @recurrence_memo([S.Zero, S.One, _sym**2]) + def _tribpoly(n, prev): + return (prev[-3] + _sym*prev[-2] + _sym**2*prev[-1]).expand() + + @classmethod + def eval(cls, n, sym=None): + if n is S.Infinity: + return S.Infinity + + if n.is_Integer: + n = int(n) + if n < 0: + raise ValueError("Tribonacci polynomials are defined " + "only for non-negative integer indices.") + if sym is None: + return Integer(cls._trib(n)) + else: + return cls._tribpoly(n).subs(_sym, sym) + + def _eval_rewrite_as_sqrt(self, n, **kwargs): + from sympy.functions.elementary.miscellaneous import cbrt, sqrt + w = (-1 + S.ImaginaryUnit * sqrt(3)) / 2 + a = (1 + cbrt(19 + 3*sqrt(33)) + cbrt(19 - 3*sqrt(33))) / 3 + b = (1 + w*cbrt(19 + 3*sqrt(33)) + w**2*cbrt(19 - 3*sqrt(33))) / 3 + c = (1 + w**2*cbrt(19 + 3*sqrt(33)) + w*cbrt(19 - 3*sqrt(33))) / 3 + Tn = (a**(n + 1)/((a - b)*(a - c)) + + b**(n + 1)/((b - a)*(b - c)) + + c**(n + 1)/((c - a)*(c - b))) + return Tn + + def _eval_rewrite_as_TribonacciConstant(self, n, **kwargs): + from sympy.functions.elementary.integers import floor + from sympy.functions.elementary.miscellaneous import cbrt, sqrt + b = cbrt(586 + 102*sqrt(33)) + Tn = 3 * b * S.TribonacciConstant**n / (b**2 - 2*b + 4) + return floor(Tn + S.Half) + + +#----------------------------------------------------------------------------# +# # +# Bernoulli numbers # +# # +#----------------------------------------------------------------------------# + + +class bernoulli(DefinedFunction): + r""" + Bernoulli numbers / Bernoulli polynomials / Bernoulli function + + The Bernoulli numbers are a sequence of rational numbers + defined by `B_0 = 1` and the recursive relation (`n > 0`): + + .. math :: n+1 = \sum_{k=0}^n \binom{n+1}{k} B_k + + They are also commonly defined by their exponential generating + function, which is `\frac{x}{1 - e^{-x}}`. For odd indices > 1, + the Bernoulli numbers are zero. + + The Bernoulli polynomials satisfy the analogous formula: + + .. math :: B_n(x) = \sum_{k=0}^n (-1)^k \binom{n}{k} B_k x^{n-k} + + Bernoulli numbers and Bernoulli polynomials are related as + `B_n(1) = B_n`. + + The generalized Bernoulli function `\operatorname{B}(s, a)` + is defined for any complex `s` and `a`, except where `a` is a + nonpositive integer and `s` is not a nonnegative integer. It is + an entire function of `s` for fixed `a`, related to the Hurwitz + zeta function by + + .. math:: \operatorname{B}(s, a) = \begin{cases} + -s \zeta(1-s, a) & s \ne 0 \\ 1 & s = 0 \end{cases} + + When `s` is a nonnegative integer this function reduces to the + Bernoulli polynomials: `\operatorname{B}(n, x) = B_n(x)`. When + `a` is omitted it is assumed to be 1, yielding the (ordinary) + Bernoulli function which interpolates the Bernoulli numbers and is + related to the Riemann zeta function. + + We compute Bernoulli numbers using Ramanujan's formula: + + .. math :: B_n = \frac{A(n) - S(n)}{\binom{n+3}{n}} + + where: + + .. math :: A(n) = \begin{cases} \frac{n+3}{3} & + n \equiv 0\ \text{or}\ 2 \pmod{6} \\ + -\frac{n+3}{6} & n \equiv 4 \pmod{6} \end{cases} + + and: + + .. math :: S(n) = \sum_{k=1}^{[n/6]} \binom{n+3}{n-6k} B_{n-6k} + + This formula is similar to the sum given in the definition, but + cuts `\frac{2}{3}` of the terms. For Bernoulli polynomials, we use + Appell sequences. + + For `n` a nonnegative integer and `s`, `a`, `x` arbitrary complex numbers, + + * ``bernoulli(n)`` gives the nth Bernoulli number, `B_n` + * ``bernoulli(s)`` gives the Bernoulli function `\operatorname{B}(s)` + * ``bernoulli(n, x)`` gives the nth Bernoulli polynomial in `x`, `B_n(x)` + * ``bernoulli(s, a)`` gives the generalized Bernoulli function + `\operatorname{B}(s, a)` + + .. versionchanged:: 1.12 + ``bernoulli(1)`` gives `+\frac{1}{2}` instead of `-\frac{1}{2}`. + This choice of value confers several theoretical advantages [5]_, + including the extension to complex parameters described above + which this function now implements. The previous behavior, defined + only for nonnegative integers `n`, can be obtained with + ``(-1)**n*bernoulli(n)``. + + Examples + ======== + + >>> from sympy import bernoulli + >>> from sympy.abc import x + >>> [bernoulli(n) for n in range(11)] + [1, 1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66] + >>> bernoulli(1000001) + 0 + >>> bernoulli(3, x) + x**3 - 3*x**2/2 + x/2 + + See Also + ======== + + andre, bell, catalan, euler, fibonacci, harmonic, lucas, genocchi, + partition, tribonacci, sympy.polys.appellseqs.bernoulli_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bernoulli_number + .. [2] https://en.wikipedia.org/wiki/Bernoulli_polynomial + .. [3] https://mathworld.wolfram.com/BernoulliNumber.html + .. [4] https://mathworld.wolfram.com/BernoulliPolynomial.html + .. [5] Peter Luschny, "The Bernoulli Manifesto", + https://luschny.de/math/zeta/The-Bernoulli-Manifesto.html + .. [6] Peter Luschny, "An introduction to the Bernoulli function", + https://arxiv.org/abs/2009.06743 + + """ + + args: tuple[Integer] + + # Calculates B_n for positive even n + @staticmethod + def _calc_bernoulli(n): + s = 0 + a = int(binomial(n + 3, n - 6)) + for j in range(1, n//6 + 1): + s += a * bernoulli(n - 6*j) + # Avoid computing each binomial coefficient from scratch + a *= _product(n - 6 - 6*j + 1, n - 6*j) + a //= _product(6*j + 4, 6*j + 9) + if n % 6 == 4: + s = -Rational(n + 3, 6) - s + else: + s = Rational(n + 3, 3) - s + return s / binomial(n + 3, n) + + # We implement a specialized memoization scheme to handle each + # case modulo 6 separately + _cache = {0: S.One, 1: Rational(1, 2), 2: Rational(1, 6), 4: Rational(-1, 30)} + _highest = {0: 0, 1: 1, 2: 2, 4: 4} + + @classmethod + def eval(cls, n, x=None): + if x is S.One: + return cls(n) + elif n.is_zero: + return S.One + elif n.is_integer is False or n.is_nonnegative is False: + if x is not None and x.is_Integer and x.is_nonpositive: + return S.NaN + return + # Bernoulli numbers + elif x is None: + if n is S.One: + return S.Half + elif n.is_odd and (n-1).is_positive: + return S.Zero + elif n.is_Number: + n = int(n) + # Use mpmath for enormous Bernoulli numbers + if n > 500: + p, q = mp.bernfrac(n) + return Rational(int(p), int(q)) + case = n % 6 + highest_cached = cls._highest[case] + if n <= highest_cached: + return cls._cache[n] + # To avoid excessive recursion when, say, bernoulli(1000) is + # requested, calculate and cache the entire sequence ... B_988, + # B_994, B_1000 in increasing order + for i in range(highest_cached + 6, n + 6, 6): + b = cls._calc_bernoulli(i) + cls._cache[i] = b + cls._highest[case] = i + return b + # Bernoulli polynomials + elif n.is_Number: + return bernoulli_poly(n, x) + + def _eval_rewrite_as_zeta(self, n, x=1, **kwargs): + from sympy.functions.special.zeta_functions import zeta + return Piecewise((1, Eq(n, 0)), (-n * zeta(1-n, x), True)) + + def _eval_evalf(self, prec): + if not all(x.is_number for x in self.args): + return + n = self.args[0]._to_mpmath(prec) + x = (self.args[1] if len(self.args) > 1 else S.One)._to_mpmath(prec) + with workprec(prec): + if n == 0: + res = mp.mpf(1) + elif n == 1: + res = x - mp.mpf(0.5) + elif mp.isint(n) and n >= 0: + res = mp.bernoulli(n) if x == 1 else mp.bernpoly(n, x) + else: + res = -n * mp.zeta(1-n, x) + return Expr._from_mpmath(res, prec) + + +#----------------------------------------------------------------------------# +# # +# Bell numbers # +# # +#----------------------------------------------------------------------------# + + +class bell(DefinedFunction): + r""" + Bell numbers / Bell polynomials + + The Bell numbers satisfy `B_0 = 1` and + + .. math:: B_n = \sum_{k=0}^{n-1} \binom{n-1}{k} B_k. + + They are also given by: + + .. math:: B_n = \frac{1}{e} \sum_{k=0}^{\infty} \frac{k^n}{k!}. + + The Bell polynomials are given by `B_0(x) = 1` and + + .. math:: B_n(x) = x \sum_{k=1}^{n-1} \binom{n-1}{k-1} B_{k-1}(x). + + The second kind of Bell polynomials (are sometimes called "partial" Bell + polynomials or incomplete Bell polynomials) are defined as + + .. math:: B_{n,k}(x_1, x_2,\dotsc x_{n-k+1}) = + \sum_{j_1+j_2+j_2+\dotsb=k \atop j_1+2j_2+3j_2+\dotsb=n} + \frac{n!}{j_1!j_2!\dotsb j_{n-k+1}!} + \left(\frac{x_1}{1!} \right)^{j_1} + \left(\frac{x_2}{2!} \right)^{j_2} \dotsb + \left(\frac{x_{n-k+1}}{(n-k+1)!} \right) ^{j_{n-k+1}}. + + * ``bell(n)`` gives the `n^{th}` Bell number, `B_n`. + * ``bell(n, x)`` gives the `n^{th}` Bell polynomial, `B_n(x)`. + * ``bell(n, k, (x1, x2, ...))`` gives Bell polynomials of the second kind, + `B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1})`. + + Notes + ===== + + Not to be confused with Bernoulli numbers and Bernoulli polynomials, + which use the same notation. + + Examples + ======== + + >>> from sympy import bell, Symbol, symbols + + >>> [bell(n) for n in range(11)] + [1, 1, 2, 5, 15, 52, 203, 877, 4140, 21147, 115975] + >>> bell(30) + 846749014511809332450147 + >>> bell(4, Symbol('t')) + t**4 + 6*t**3 + 7*t**2 + t + >>> bell(6, 2, symbols('x:6')[1:]) + 6*x1*x5 + 15*x2*x4 + 10*x3**2 + + See Also + ======== + + bernoulli, catalan, euler, fibonacci, harmonic, lucas, genocchi, partition, tribonacci + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bell_number + .. [2] https://mathworld.wolfram.com/BellNumber.html + .. [3] https://mathworld.wolfram.com/BellPolynomial.html + + """ + + @staticmethod + @recurrence_memo([1, 1]) + def _bell(n, prev): + s = 1 + a = 1 + for k in range(1, n): + a = a * (n - k) // k + s += a * prev[k] + return s + + @staticmethod + @recurrence_memo([S.One, _sym]) + def _bell_poly(n, prev): + s = 1 + a = 1 + for k in range(2, n + 1): + a = a * (n - k + 1) // (k - 1) + s += a * prev[k - 1] + return expand_mul(_sym * s) + + @staticmethod + def _bell_incomplete_poly(n, k, symbols): + r""" + The second kind of Bell polynomials (incomplete Bell polynomials). + + Calculated by recurrence formula: + + .. math:: B_{n,k}(x_1, x_2, \dotsc, x_{n-k+1}) = + \sum_{m=1}^{n-k+1} + \x_m \binom{n-1}{m-1} B_{n-m,k-1}(x_1, x_2, \dotsc, x_{n-m-k}) + + where + `B_{0,0} = 1;` + `B_{n,0} = 0; for n \ge 1` + `B_{0,k} = 0; for k \ge 1` + + """ + if (n == 0) and (k == 0): + return S.One + elif (n == 0) or (k == 0): + return S.Zero + s = S.Zero + a = S.One + for m in range(1, n - k + 2): + s += a * bell._bell_incomplete_poly( + n - m, k - 1, symbols) * symbols[m - 1] + a = a * (n - m) / m + return expand_mul(s) + + @classmethod + def eval(cls, n, k_sym=None, symbols=None): + if n is S.Infinity: + if k_sym is None: + return S.Infinity + else: + raise ValueError("Bell polynomial is not defined") + + if n.is_negative or n.is_integer is False: + raise ValueError("a non-negative integer expected") + + if n.is_Integer and n.is_nonnegative: + if k_sym is None: + return Integer(cls._bell(int(n))) + elif symbols is None: + return cls._bell_poly(int(n)).subs(_sym, k_sym) + else: + r = cls._bell_incomplete_poly(int(n), int(k_sym), symbols) + return r + + def _eval_rewrite_as_Sum(self, n, k_sym=None, symbols=None, **kwargs): + from sympy.concrete.summations import Sum + if (k_sym is not None) or (symbols is not None): + return self + + # Dobinski's formula + if not n.is_nonnegative: + return self + k = Dummy('k', integer=True, nonnegative=True) + return 1 / E * Sum(k**n / factorial(k), (k, 0, S.Infinity)) + + +#----------------------------------------------------------------------------# +# # +# Harmonic numbers # +# # +#----------------------------------------------------------------------------# + + +class harmonic(DefinedFunction): + r""" + Harmonic numbers + + The nth harmonic number is given by `\operatorname{H}_{n} = + 1 + \frac{1}{2} + \frac{1}{3} + \ldots + \frac{1}{n}`. + + More generally: + + .. math:: \operatorname{H}_{n,m} = \sum_{k=1}^{n} \frac{1}{k^m} + + As `n \rightarrow \infty`, `\operatorname{H}_{n,m} \rightarrow \zeta(m)`, + the Riemann zeta function. + + * ``harmonic(n)`` gives the nth harmonic number, `\operatorname{H}_n` + + * ``harmonic(n, m)`` gives the nth generalized harmonic number + of order `m`, `\operatorname{H}_{n,m}`, where + ``harmonic(n) == harmonic(n, 1)`` + + This function can be extended to complex `n` and `m` where `n` is not a + negative integer or `m` is a nonpositive integer as + + .. math:: \operatorname{H}_{n,m} = \begin{cases} \zeta(m) - \zeta(m, n+1) + & m \ne 1 \\ \psi(n+1) + \gamma & m = 1 \end{cases} + + Examples + ======== + + >>> from sympy import harmonic, oo + + >>> [harmonic(n) for n in range(6)] + [0, 1, 3/2, 11/6, 25/12, 137/60] + >>> [harmonic(n, 2) for n in range(6)] + [0, 1, 5/4, 49/36, 205/144, 5269/3600] + >>> harmonic(oo, 2) + pi**2/6 + + >>> from sympy import Symbol, Sum + >>> n = Symbol("n") + + >>> harmonic(n).rewrite(Sum) + Sum(1/_k, (_k, 1, n)) + + We can evaluate harmonic numbers for all integral and positive + rational arguments: + + >>> from sympy import S, expand_func, simplify + >>> harmonic(8) + 761/280 + >>> harmonic(11) + 83711/27720 + + >>> H = harmonic(1/S(3)) + >>> H + harmonic(1/3) + >>> He = expand_func(H) + >>> He + -log(6) - sqrt(3)*pi/6 + 2*Sum(log(sin(_k*pi/3))*cos(2*_k*pi/3), (_k, 1, 1)) + + 3*Sum(1/(3*_k + 1), (_k, 0, 0)) + >>> He.doit() + -log(6) - sqrt(3)*pi/6 - log(sqrt(3)/2) + 3 + >>> H = harmonic(25/S(7)) + >>> He = simplify(expand_func(H).doit()) + >>> He + log(sin(2*pi/7)**(2*cos(16*pi/7))/(14*sin(pi/7)**(2*cos(pi/7))*cos(pi/14)**(2*sin(pi/14)))) + pi*tan(pi/14)/2 + 30247/9900 + >>> He.n(40) + 1.983697455232980674869851942390639915940 + >>> harmonic(25/S(7)).n(40) + 1.983697455232980674869851942390639915940 + + We can rewrite harmonic numbers in terms of polygamma functions: + + >>> from sympy import digamma, polygamma + >>> m = Symbol("m", integer=True, positive=True) + + >>> harmonic(n).rewrite(digamma) + polygamma(0, n + 1) + EulerGamma + + >>> harmonic(n).rewrite(polygamma) + polygamma(0, n + 1) + EulerGamma + + >>> harmonic(n,3).rewrite(polygamma) + polygamma(2, n + 1)/2 + zeta(3) + + >>> simplify(harmonic(n,m).rewrite(polygamma)) + Piecewise((polygamma(0, n + 1) + EulerGamma, Eq(m, 1)), + (-(-1)**m*polygamma(m - 1, n + 1)/factorial(m - 1) + zeta(m), True)) + + Integer offsets in the argument can be pulled out: + + >>> from sympy import expand_func + + >>> expand_func(harmonic(n+4)) + harmonic(n) + 1/(n + 4) + 1/(n + 3) + 1/(n + 2) + 1/(n + 1) + + >>> expand_func(harmonic(n-4)) + harmonic(n) - 1/(n - 1) - 1/(n - 2) - 1/(n - 3) - 1/n + + Some limits can be computed as well: + + >>> from sympy import limit, oo + + >>> limit(harmonic(n), n, oo) + oo + + >>> limit(harmonic(n, 2), n, oo) + pi**2/6 + + >>> limit(harmonic(n, 3), n, oo) + zeta(3) + + For `m > 1`, `H_{n,m}` tends to `\zeta(m)` in the limit of infinite `n`: + + >>> m = Symbol("m", positive=True) + >>> limit(harmonic(n, m+1), n, oo) + zeta(m + 1) + + See Also + ======== + + bell, bernoulli, catalan, euler, fibonacci, lucas, genocchi, partition, tribonacci + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Harmonic_number + .. [2] https://functions.wolfram.com/GammaBetaErf/HarmonicNumber/ + .. [3] https://functions.wolfram.com/GammaBetaErf/HarmonicNumber2/ + + """ + + # This prevents redundant recalculations and speeds up harmonic number computations. + harmonic_cache: dict[Integer, Callable[[int], Rational]] = {} + + @classmethod + def eval(cls, n, m=None): + from sympy.functions.special.zeta_functions import zeta + if m is S.One: + return cls(n) + if m is None: + m = S.One + if n.is_zero: + return S.Zero + elif m.is_zero: + return n + elif n is S.Infinity: + if m.is_negative: + return S.NaN + elif is_le(m, S.One): + return S.Infinity + elif is_gt(m, S.One): + return zeta(m) + elif m.is_Integer and m.is_nonpositive: + return (bernoulli(1-m, n+1) - bernoulli(1-m)) / (1-m) + elif n.is_Integer: + if n.is_negative and (m.is_integer is False or m.is_nonpositive is False): + return S.ComplexInfinity if m is S.One else S.NaN + if n.is_nonnegative: + if m.is_Integer: + if m not in cls.harmonic_cache: + @recurrence_memo([0]) + def f(n, prev): + return prev[-1] + S.One / n**m + cls.harmonic_cache[m] = f + return cls.harmonic_cache[m](int(n)) + return Add(*(k**(-m) for k in range(1, int(n) + 1))) + + def _eval_rewrite_as_polygamma(self, n, m=S.One, **kwargs): + from sympy.functions.special.gamma_functions import gamma, polygamma + if m.is_integer and m.is_positive: + return Piecewise((polygamma(0, n+1) + S.EulerGamma, Eq(m, 1)), + (S.NegativeOne**m * (polygamma(m-1, 1) - polygamma(m-1, n+1)) / + gamma(m), True)) + + def _eval_rewrite_as_digamma(self, n, m=1, **kwargs): + from sympy.functions.special.gamma_functions import polygamma + return self.rewrite(polygamma) + + def _eval_rewrite_as_trigamma(self, n, m=1, **kwargs): + from sympy.functions.special.gamma_functions import polygamma + return self.rewrite(polygamma) + + def _eval_rewrite_as_Sum(self, n, m=None, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k", integer=True) + if m is None: + m = S.One + return Sum(k**(-m), (k, 1, n)) + + def _eval_rewrite_as_zeta(self, n, m=S.One, **kwargs): + from sympy.functions.special.zeta_functions import zeta + from sympy.functions.special.gamma_functions import digamma + return Piecewise((digamma(n + 1) + S.EulerGamma, Eq(m, 1)), + (zeta(m) - zeta(m, n+1), True)) + + def _eval_expand_func(self, **hints): + from sympy.concrete.summations import Sum + n = self.args[0] + m = self.args[1] if len(self.args) == 2 else 1 + + if m == S.One: + if n.is_Add: + off = n.args[0] + nnew = n - off + if off.is_Integer and off.is_positive: + result = [S.One/(nnew + i) for i in range(off, 0, -1)] + [harmonic(nnew)] + return Add(*result) + elif off.is_Integer and off.is_negative: + result = [-S.One/(nnew + i) for i in range(0, off, -1)] + [harmonic(nnew)] + return Add(*result) + + if n.is_Rational: + # Expansions for harmonic numbers at general rational arguments (u + p/q) + # Split n as u + p/q with p < q + p, q = n.as_numer_denom() + u = p // q + p = p - u * q + if u.is_nonnegative and p.is_positive and q.is_positive and p < q: + from sympy.functions.elementary.exponential import log + from sympy.functions.elementary.integers import floor + from sympy.functions.elementary.trigonometric import sin, cos, cot + k = Dummy("k") + t1 = q * Sum(1 / (q * k + p), (k, 0, u)) + t2 = 2 * Sum(cos((2 * pi * p * k) / S(q)) * + log(sin((pi * k) / S(q))), + (k, 1, floor((q - 1) / S(2)))) + t3 = (pi / 2) * cot((pi * p) / q) + log(2 * q) + return t1 + t2 - t3 + + return self + + def _eval_rewrite_as_tractable(self, n, m=1, limitvar=None, **kwargs): + from sympy.functions.special.zeta_functions import zeta + from sympy.functions.special.gamma_functions import polygamma + pg = self.rewrite(polygamma) + if not isinstance(pg, harmonic): + return pg.rewrite("tractable", deep=True) + arg = m - S.One + if arg.is_nonzero: + return (zeta(m) - zeta(m, n+1)).rewrite("tractable", deep=True) + + def _eval_evalf(self, prec): + if not all(x.is_number for x in self.args): + return + n = self.args[0]._to_mpmath(prec) + m = (self.args[1] if len(self.args) > 1 else S.One)._to_mpmath(prec) + if mp.isint(n) and n < 0: + return S.NaN + with workprec(prec): + if m == 1: + res = mp.harmonic(n) + else: + res = mp.zeta(m) - mp.zeta(m, n+1) + return Expr._from_mpmath(res, prec) + + def fdiff(self, argindex=1): + from sympy.functions.special.zeta_functions import zeta + if len(self.args) == 2: + n, m = self.args + else: + n, m = self.args + (1,) + if argindex == 1: + return m * zeta(m+1, n+1) + else: + raise ArgumentIndexError + + +#----------------------------------------------------------------------------# +# # +# Euler numbers # +# # +#----------------------------------------------------------------------------# + + +class euler(DefinedFunction): + r""" + Euler numbers / Euler polynomials / Euler function + + The Euler numbers are given by: + + .. math:: E_{2n} = I \sum_{k=1}^{2n+1} \sum_{j=0}^k \binom{k}{j} + \frac{(-1)^j (k-2j)^{2n+1}}{2^k I^k k} + + .. math:: E_{2n+1} = 0 + + Euler numbers and Euler polynomials are related by + + .. math:: E_n = 2^n E_n\left(\frac{1}{2}\right). + + We compute symbolic Euler polynomials using Appell sequences, + but numerical evaluation of the Euler polynomial is computed + more efficiently (and more accurately) using the mpmath library. + + The Euler polynomials are special cases of the generalized Euler function, + related to the Genocchi function as + + .. math:: \operatorname{E}(s, a) = -\frac{\operatorname{G}(s+1, a)}{s+1} + + with the limit of `\psi\left(\frac{a+1}{2}\right) - \psi\left(\frac{a}{2}\right)` + being taken when `s = -1`. The (ordinary) Euler function interpolating + the Euler numbers is then obtained as + `\operatorname{E}(s) = 2^s \operatorname{E}\left(s, \frac{1}{2}\right)`. + + * ``euler(n)`` gives the nth Euler number `E_n`. + * ``euler(s)`` gives the Euler function `\operatorname{E}(s)`. + * ``euler(n, x)`` gives the nth Euler polynomial `E_n(x)`. + * ``euler(s, a)`` gives the generalized Euler function `\operatorname{E}(s, a)`. + + Examples + ======== + + >>> from sympy import euler, Symbol, S + >>> [euler(n) for n in range(10)] + [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0] + >>> [2**n*euler(n,1) for n in range(10)] + [1, 1, 0, -2, 0, 16, 0, -272, 0, 7936] + >>> n = Symbol("n") + >>> euler(n + 2*n) + euler(3*n) + + >>> x = Symbol("x") + >>> euler(n, x) + euler(n, x) + + >>> euler(0, x) + 1 + >>> euler(1, x) + x - 1/2 + >>> euler(2, x) + x**2 - x + >>> euler(3, x) + x**3 - 3*x**2/2 + 1/4 + >>> euler(4, x) + x**4 - 2*x**3 + x + + >>> euler(12, S.Half) + 2702765/4096 + >>> euler(12) + 2702765 + + See Also + ======== + + andre, bell, bernoulli, catalan, fibonacci, harmonic, lucas, genocchi, + partition, tribonacci, sympy.polys.appellseqs.euler_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Euler_numbers + .. [2] https://mathworld.wolfram.com/EulerNumber.html + .. [3] https://en.wikipedia.org/wiki/Alternating_permutation + .. [4] https://mathworld.wolfram.com/AlternatingPermutation.html + + """ + + @classmethod + def eval(cls, n, x=None): + if n.is_zero: + return S.One + elif n is S.NegativeOne: + if x is None: + return S.Pi/2 + from sympy.functions.special.gamma_functions import digamma + return digamma((x+1)/2) - digamma(x/2) + elif n.is_integer is False or n.is_nonnegative is False: + return + # Euler numbers + elif x is None: + if n.is_odd and n.is_positive: + return S.Zero + elif n.is_Number: + from mpmath import mp + n = n._to_mpmath(mp.prec) + res = mp.eulernum(n, exact=True) + return Integer(res) + # Euler polynomials + elif n.is_Number: + from sympy.core.evalf import pure_complex + n = int(n) + reim = pure_complex(x, or_real=True) + if reim and all(a.is_Float or a.is_Integer for a in reim) \ + and any(a.is_Float for a in reim): + from mpmath import mp + prec = min([a._prec for a in reim if a.is_Float]) + with workprec(prec): + res = mp.eulerpoly(n, x) + return Expr._from_mpmath(res, prec) + return euler_poly(n, x) + + def _eval_rewrite_as_Sum(self, n, x=None, **kwargs): + from sympy.concrete.summations import Sum + if x is None and n.is_even: + k = Dummy("k", integer=True) + j = Dummy("j", integer=True) + n = n / 2 + Em = (S.ImaginaryUnit * Sum(Sum(binomial(k, j) * (S.NegativeOne**j * + (k - 2*j)**(2*n + 1)) / + (2**k*S.ImaginaryUnit**k * k), (j, 0, k)), (k, 1, 2*n + 1))) + return Em + if x: + k = Dummy("k", integer=True) + return Sum(binomial(n, k)*euler(k)/2**k*(x - S.Half)**(n - k), (k, 0, n)) + + def _eval_rewrite_as_genocchi(self, n, x=None, **kwargs): + if x is None: + return Piecewise((S.Pi/2, Eq(n, -1)), + (-2**n * genocchi(n+1, S.Half) / (n+1), True)) + from sympy.functions.special.gamma_functions import digamma + return Piecewise((digamma((x+1)/2) - digamma(x/2), Eq(n, -1)), + (-genocchi(n+1, x) / (n+1), True)) + + def _eval_evalf(self, prec): + if not all(i.is_number for i in self.args): + return + from mpmath import mp + m, x = (self.args[0], None) if len(self.args) == 1 else self.args + m = m._to_mpmath(prec) + if x is not None: + x = x._to_mpmath(prec) + with workprec(prec): + if mp.isint(m) and m >= 0: + res = mp.eulernum(m) if x is None else mp.eulerpoly(m, x) + else: + if m == -1: + res = mp.pi if x is None else mp.digamma((x+1)/2) - mp.digamma(x/2) + else: + y = 0.5 if x is None else x + res = 2 * (mp.zeta(-m, y) - 2**(m+1) * mp.zeta(-m, (y+1)/2)) + if x is None: + res *= 2**m + return Expr._from_mpmath(res, prec) + + +#----------------------------------------------------------------------------# +# # +# Catalan numbers # +# # +#----------------------------------------------------------------------------# + + +class catalan(DefinedFunction): + r""" + Catalan numbers + + The `n^{th}` catalan number is given by: + + .. math :: C_n = \frac{1}{n+1} \binom{2n}{n} + + * ``catalan(n)`` gives the `n^{th}` Catalan number, `C_n` + + Examples + ======== + + >>> from sympy import (Symbol, binomial, gamma, hyper, + ... catalan, diff, combsimp, Rational, I) + + >>> [catalan(i) for i in range(1,10)] + [1, 2, 5, 14, 42, 132, 429, 1430, 4862] + + >>> n = Symbol("n", integer=True) + + >>> catalan(n) + catalan(n) + + Catalan numbers can be transformed into several other, identical + expressions involving other mathematical functions + + >>> catalan(n).rewrite(binomial) + binomial(2*n, n)/(n + 1) + + >>> catalan(n).rewrite(gamma) + 4**n*gamma(n + 1/2)/(sqrt(pi)*gamma(n + 2)) + + >>> catalan(n).rewrite(hyper) + hyper((-n, 1 - n), (2,), 1) + + For some non-integer values of n we can get closed form + expressions by rewriting in terms of gamma functions: + + >>> catalan(Rational(1, 2)).rewrite(gamma) + 8/(3*pi) + + We can differentiate the Catalan numbers C(n) interpreted as a + continuous real function in n: + + >>> diff(catalan(n), n) + (polygamma(0, n + 1/2) - polygamma(0, n + 2) + log(4))*catalan(n) + + As a more advanced example consider the following ratio + between consecutive numbers: + + >>> combsimp((catalan(n + 1)/catalan(n)).rewrite(binomial)) + 2*(2*n + 1)/(n + 2) + + The Catalan numbers can be generalized to complex numbers: + + >>> catalan(I).rewrite(gamma) + 4**I*gamma(1/2 + I)/(sqrt(pi)*gamma(2 + I)) + + and evaluated with arbitrary precision: + + >>> catalan(I).evalf(20) + 0.39764993382373624267 - 0.020884341620842555705*I + + See Also + ======== + + andre, bell, bernoulli, euler, fibonacci, harmonic, lucas, genocchi, + partition, tribonacci, sympy.functions.combinatorial.factorials.binomial + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Catalan_number + .. [2] https://mathworld.wolfram.com/CatalanNumber.html + .. [3] https://functions.wolfram.com/GammaBetaErf/CatalanNumber/ + .. [4] http://geometer.org/mathcircles/catalan.pdf + + """ + + @classmethod + def eval(cls, n): + from sympy.functions.special.gamma_functions import gamma + if (n.is_Integer and n.is_nonnegative) or \ + (n.is_noninteger and n.is_negative): + return 4**n*gamma(n + S.Half)/(gamma(S.Half)*gamma(n + 2)) + + if (n.is_integer and n.is_negative): + if (n + 1).is_negative: + return S.Zero + if (n + 1).is_zero: + return Rational(-1, 2) + + def fdiff(self, argindex=1): + from sympy.functions.elementary.exponential import log + from sympy.functions.special.gamma_functions import polygamma + n = self.args[0] + return catalan(n)*(polygamma(0, n + S.Half) - polygamma(0, n + 2) + log(4)) + + def _eval_rewrite_as_binomial(self, n, **kwargs): + return binomial(2*n, n)/(n + 1) + + def _eval_rewrite_as_factorial(self, n, **kwargs): + return factorial(2*n) / (factorial(n+1) * factorial(n)) + + def _eval_rewrite_as_gamma(self, n, piecewise=True, **kwargs): + from sympy.functions.special.gamma_functions import gamma + # The gamma function allows to generalize Catalan numbers to complex n + return 4**n*gamma(n + S.Half)/(gamma(S.Half)*gamma(n + 2)) + + def _eval_rewrite_as_hyper(self, n, **kwargs): + from sympy.functions.special.hyper import hyper + return hyper([1 - n, -n], [2], 1) + + def _eval_rewrite_as_Product(self, n, **kwargs): + from sympy.concrete.products import Product + if not (n.is_integer and n.is_nonnegative): + return self + k = Dummy('k', integer=True, positive=True) + return Product((n + k) / k, (k, 2, n)) + + def _eval_is_integer(self): + if self.args[0].is_integer and self.args[0].is_nonnegative: + return True + + def _eval_is_positive(self): + if self.args[0].is_nonnegative: + return True + + def _eval_is_composite(self): + if self.args[0].is_integer and (self.args[0] - 3).is_positive: + return True + + def _eval_evalf(self, prec): + from sympy.functions.special.gamma_functions import gamma + if self.args[0].is_number: + return self.rewrite(gamma)._eval_evalf(prec) + + +#----------------------------------------------------------------------------# +# # +# Genocchi numbers # +# # +#----------------------------------------------------------------------------# + + +class genocchi(DefinedFunction): + r""" + Genocchi numbers / Genocchi polynomials / Genocchi function + + The Genocchi numbers are a sequence of integers `G_n` that satisfy the + relation: + + .. math:: \frac{-2t}{1 + e^{-t}} = \sum_{n=0}^\infty \frac{G_n t^n}{n!} + + They are related to the Bernoulli numbers by + + .. math:: G_n = 2 (1 - 2^n) B_n + + and generalize like the Bernoulli numbers to the Genocchi polynomials and + function as + + .. math:: \operatorname{G}(s, a) = 2 \left(\operatorname{B}(s, a) - + 2^s \operatorname{B}\left(s, \frac{a+1}{2}\right)\right) + + .. versionchanged:: 1.12 + ``genocchi(1)`` gives `-1` instead of `1`. + + Examples + ======== + + >>> from sympy import genocchi, Symbol + >>> [genocchi(n) for n in range(9)] + [0, -1, -1, 0, 1, 0, -3, 0, 17] + >>> n = Symbol('n', integer=True, positive=True) + >>> genocchi(2*n + 1) + 0 + >>> x = Symbol('x') + >>> genocchi(4, x) + -4*x**3 + 6*x**2 - 1 + + See Also + ======== + + bell, bernoulli, catalan, euler, fibonacci, harmonic, lucas, partition, tribonacci + sympy.polys.appellseqs.genocchi_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Genocchi_number + .. [2] https://mathworld.wolfram.com/GenocchiNumber.html + .. [3] Peter Luschny, "An introduction to the Bernoulli function", + https://arxiv.org/abs/2009.06743 + + """ + + @classmethod + def eval(cls, n, x=None): + if x is S.One: + return cls(n) + elif n.is_integer is False or n.is_nonnegative is False: + return + # Genocchi numbers + elif x is None: + if n.is_odd and (n-1).is_positive: + return S.Zero + elif n.is_Number: + return 2 * (1-S(2)**n) * bernoulli(n) + # Genocchi polynomials + elif n.is_Number: + return genocchi_poly(n, x) + + def _eval_rewrite_as_bernoulli(self, n, x=1, **kwargs): + if x == 1 and n.is_integer and n.is_nonnegative: + return 2 * (1-S(2)**n) * bernoulli(n) + return 2 * (bernoulli(n, x) - 2**n * bernoulli(n, (x+1) / 2)) + + def _eval_rewrite_as_dirichlet_eta(self, n, x=1, **kwargs): + from sympy.functions.special.zeta_functions import dirichlet_eta + return -2*n * dirichlet_eta(1-n, x) + + def _eval_is_integer(self): + if len(self.args) > 1 and self.args[1] != 1: + return + n = self.args[0] + if n.is_integer and n.is_nonnegative: + return True + + def _eval_is_negative(self): + if len(self.args) > 1 and self.args[1] != 1: + return + n = self.args[0] + if n.is_integer and n.is_nonnegative: + if n.is_odd: + return fuzzy_not((n-1).is_positive) + return (n/2).is_odd + + def _eval_is_positive(self): + if len(self.args) > 1 and self.args[1] != 1: + return + n = self.args[0] + if n.is_integer and n.is_nonnegative: + if n.is_zero or n.is_odd: + return False + return (n/2).is_even + + def _eval_is_even(self): + if len(self.args) > 1 and self.args[1] != 1: + return + n = self.args[0] + if n.is_integer and n.is_nonnegative: + if n.is_even: + return n.is_zero + return (n-1).is_positive + + def _eval_is_odd(self): + if len(self.args) > 1 and self.args[1] != 1: + return + n = self.args[0] + if n.is_integer and n.is_nonnegative: + if n.is_even: + return fuzzy_not(n.is_zero) + return fuzzy_not((n-1).is_positive) + + def _eval_is_prime(self): + if len(self.args) > 1 and self.args[1] != 1: + return + n = self.args[0] + # only G_6 = -3 and G_8 = 17 are prime, + # but SymPy does not consider negatives as prime + # so only n=8 is tested + return (n-8).is_zero + + def _eval_evalf(self, prec): + if all(i.is_number for i in self.args): + return self.rewrite(bernoulli)._eval_evalf(prec) + + +#----------------------------------------------------------------------------# +# # +# Andre numbers # +# # +#----------------------------------------------------------------------------# + + +class andre(DefinedFunction): + r""" + Andre numbers / Andre function + + The Andre number `\mathcal{A}_n` is Luschny's name for half the number of + *alternating permutations* on `n` elements, where a permutation is alternating + if adjacent elements alternately compare "greater" and "smaller" going from + left to right. For example, `2 < 3 > 1 < 4` is an alternating permutation. + + This sequence is A000111 in the OEIS, which assigns the names *up/down numbers* + and *Euler zigzag numbers*. It satisfies a recurrence relation similar to that + for the Catalan numbers, with `\mathcal{A}_0 = 1` and + + .. math:: 2 \mathcal{A}_{n+1} = \sum_{k=0}^n \binom{n}{k} \mathcal{A}_k \mathcal{A}_{n-k} + + The Bernoulli and Euler numbers are signed transformations of the odd- and + even-indexed elements of this sequence respectively: + + .. math :: \operatorname{B}_{2k} = \frac{2k \mathcal{A}_{2k-1}}{(-4)^k - (-16)^k} + + .. math :: \operatorname{E}_{2k} = (-1)^k \mathcal{A}_{2k} + + Like the Bernoulli and Euler numbers, the Andre numbers are interpolated by the + entire Andre function: + + .. math :: \mathcal{A}(s) = (-i)^{s+1} \operatorname{Li}_{-s}(i) + + i^{s+1} \operatorname{Li}_{-s}(-i) = \\ \frac{2 \Gamma(s+1)}{(2\pi)^{s+1}} + (\zeta(s+1, 1/4) - \zeta(s+1, 3/4) \cos{\pi s}) + + Examples + ======== + + >>> from sympy import andre, euler, bernoulli + >>> [andre(n) for n in range(11)] + [1, 1, 1, 2, 5, 16, 61, 272, 1385, 7936, 50521] + >>> [(-1)**k * andre(2*k) for k in range(7)] + [1, -1, 5, -61, 1385, -50521, 2702765] + >>> [euler(2*k) for k in range(7)] + [1, -1, 5, -61, 1385, -50521, 2702765] + >>> [andre(2*k-1) * (2*k) / ((-4)**k - (-16)**k) for k in range(1, 8)] + [1/6, -1/30, 1/42, -1/30, 5/66, -691/2730, 7/6] + >>> [bernoulli(2*k) for k in range(1, 8)] + [1/6, -1/30, 1/42, -1/30, 5/66, -691/2730, 7/6] + + See Also + ======== + + bernoulli, catalan, euler, sympy.polys.appellseqs.andre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Alternating_permutation + .. [2] https://mathworld.wolfram.com/EulerZigzagNumber.html + .. [3] Peter Luschny, "An introduction to the Bernoulli function", + https://arxiv.org/abs/2009.06743 + """ + + @classmethod + def eval(cls, n): + if n is S.NaN: + return S.NaN + elif n is S.Infinity: + return S.Infinity + if n.is_zero: + return S.One + elif n == -1: + return -log(2) + elif n == -2: + return -2*S.Catalan + elif n.is_Integer: + if n.is_nonnegative and n.is_even: + return abs(euler(n)) + elif n.is_odd: + from sympy.functions.special.zeta_functions import zeta + m = -n-1 + return I**m * Rational(1-2**m, 4**m) * zeta(-n) + + def _eval_rewrite_as_zeta(self, s, **kwargs): + from sympy.functions.elementary.trigonometric import cos + from sympy.functions.special.gamma_functions import gamma + from sympy.functions.special.zeta_functions import zeta + return 2 * gamma(s+1) / (2*pi)**(s+1) * \ + (zeta(s+1, S.One/4) - cos(pi*s) * zeta(s+1, S(3)/4)) + + def _eval_rewrite_as_polylog(self, s, **kwargs): + from sympy.functions.special.zeta_functions import polylog + return (-I)**(s+1) * polylog(-s, I) + I**(s+1) * polylog(-s, -I) + + def _eval_is_integer(self): + n = self.args[0] + if n.is_integer and n.is_nonnegative: + return True + + def _eval_is_positive(self): + if self.args[0].is_nonnegative: + return True + + def _eval_evalf(self, prec): + if not self.args[0].is_number: + return + s = self.args[0]._to_mpmath(prec+12) + with workprec(prec+12): + sp, cp = mp.sinpi(s/2), mp.cospi(s/2) + res = 2*mp.dirichlet(-s, (-sp, cp, sp, -cp)) + return Expr._from_mpmath(res, prec) + + +#----------------------------------------------------------------------------# +# # +# Partition numbers # +# # +#----------------------------------------------------------------------------# + +class partition(DefinedFunction): + r""" + Partition numbers + + The Partition numbers are a sequence of integers `p_n` that represent the + number of distinct ways of representing `n` as a sum of natural numbers + (with order irrelevant). The generating function for `p_n` is given by: + + .. math:: \sum_{n=0}^\infty p_n x^n = \prod_{k=1}^\infty (1 - x^k)^{-1} + + Examples + ======== + + >>> from sympy import partition, Symbol + >>> [partition(n) for n in range(9)] + [1, 1, 2, 3, 5, 7, 11, 15, 22] + >>> n = Symbol('n', integer=True, negative=True) + >>> partition(n) + 0 + + See Also + ======== + + bell, bernoulli, catalan, euler, fibonacci, harmonic, lucas, genocchi, tribonacci + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Partition_(number_theory%29 + .. [2] https://en.wikipedia.org/wiki/Pentagonal_number_theorem + + """ + is_integer = True + is_nonnegative = True + + @classmethod + def eval(cls, n): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_negative is True: + return S.Zero + if n.is_zero is True or n is S.One: + return S.One + if n.is_Integer is True: + return S(_partition(as_int(n))) + + def _eval_is_positive(self): + if self.args[0].is_nonnegative is True: + return True + + def _eval_Mod(self, q): + # Ramanujan's congruences + n = self.args[0] + for p, rem in [(5, 4), (7, 5), (11, 6)]: + if q == p and n % q == rem: + return S.Zero + + +class divisor_sigma(DefinedFunction): + r""" + Calculate the divisor function `\sigma_k(n)` for positive integer n + + ``divisor_sigma(n, k)`` is equal to ``sum([x**k for x in divisors(n)])`` + + If n's prime factorization is: + + .. math :: + n = \prod_{i=1}^\omega p_i^{m_i}, + + then + + .. math :: + \sigma_k(n) = \prod_{i=1}^\omega (1+p_i^k+p_i^{2k}+\cdots + + p_i^{m_ik}). + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import divisor_sigma + >>> divisor_sigma(18, 0) + 6 + >>> divisor_sigma(39, 1) + 56 + >>> divisor_sigma(12, 2) + 210 + >>> divisor_sigma(37) + 38 + + See Also + ======== + + sympy.ntheory.factor_.divisor_count, totient, sympy.ntheory.factor_.divisors, sympy.ntheory.factor_.factorint + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Divisor_function + + """ + is_integer = True + is_positive = True + + @classmethod + def eval(cls, n, k=S.One): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if k.is_integer is False: + raise TypeError("k should be an integer") + if k.is_nonnegative is False: + raise ValueError("k should be a nonnegative integer") + if n.is_prime is True: + return 1 + n**k + if n is S.One: + return S.One + if n.is_Integer is True: + if k.is_zero is True: + return Mul(*[e + 1 for e in factorint(n).values()]) + if k.is_Integer is True: + return S(_divisor_sigma(as_int(n), as_int(k))) + if k.is_zero is False: + return Mul(*[cancel((p**(k*(e + 1)) - 1) / (p**k - 1)) for p, e in factorint(n).items()]) + + +class udivisor_sigma(DefinedFunction): + r""" + Calculate the unitary divisor function `\sigma_k^*(n)` for positive integer n + + ``udivisor_sigma(n, k)`` is equal to ``sum([x**k for x in udivisors(n)])`` + + If n's prime factorization is: + + .. math :: + n = \prod_{i=1}^\omega p_i^{m_i}, + + then + + .. math :: + \sigma_k^*(n) = \prod_{i=1}^\omega (1+ p_i^{m_ik}). + + Parameters + ========== + + k : power of divisors in the sum + + for k = 0, 1: + ``udivisor_sigma(n, 0)`` is equal to ``udivisor_count(n)`` + ``udivisor_sigma(n, 1)`` is equal to ``sum(udivisors(n))`` + + Default for k is 1. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import udivisor_sigma + >>> udivisor_sigma(18, 0) + 4 + >>> udivisor_sigma(74, 1) + 114 + >>> udivisor_sigma(36, 3) + 47450 + >>> udivisor_sigma(111) + 152 + + See Also + ======== + + sympy.ntheory.factor_.divisor_count, totient, sympy.ntheory.factor_.divisors, + sympy.ntheory.factor_.udivisors, sympy.ntheory.factor_.udivisor_count, divisor_sigma, + sympy.ntheory.factor_.factorint + + References + ========== + + .. [1] https://mathworld.wolfram.com/UnitaryDivisorFunction.html + + """ + is_integer = True + is_positive = True + + @classmethod + def eval(cls, n, k=S.One): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if k.is_integer is False: + raise TypeError("k should be an integer") + if k.is_nonnegative is False: + raise ValueError("k should be a nonnegative integer") + if n.is_prime is True: + return 1 + n**k + if n.is_Integer: + return Mul(*[1+p**(k*e) for p, e in factorint(n).items()]) + + +class legendre_symbol(DefinedFunction): + r""" + Returns the Legendre symbol `(a / p)`. + + For an integer ``a`` and an odd prime ``p``, the Legendre symbol is + defined as + + .. math :: + \genfrac(){}{}{a}{p} = \begin{cases} + 0 & \text{if } p \text{ divides } a\\ + 1 & \text{if } a \text{ is a quadratic residue modulo } p\\ + -1 & \text{if } a \text{ is a quadratic nonresidue modulo } p + \end{cases} + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import legendre_symbol + >>> [legendre_symbol(i, 7) for i in range(7)] + [0, 1, 1, -1, 1, -1, -1] + >>> sorted(set([i**2 % 7 for i in range(7)])) + [0, 1, 2, 4] + + See Also + ======== + + sympy.ntheory.residue_ntheory.is_quad_residue, jacobi_symbol + + """ + is_integer = True + is_prime = False + + @classmethod + def eval(cls, a, p): + if a.is_integer is False: + raise TypeError("a should be an integer") + if p.is_integer is False: + raise TypeError("p should be an integer") + if p.is_prime is False or p.is_odd is False: + raise ValueError("p should be an odd prime integer") + if (a % p).is_zero is True: + return S.Zero + if a is S.One: + return S.One + if a.is_Integer is True and p.is_Integer is True: + return S(legendre(as_int(a), as_int(p))) + + +class jacobi_symbol(DefinedFunction): + r""" + Returns the Jacobi symbol `(m / n)`. + + For any integer ``m`` and any positive odd integer ``n`` the Jacobi symbol + is defined as the product of the Legendre symbols corresponding to the + prime factors of ``n``: + + .. math :: + \genfrac(){}{}{m}{n} = + \genfrac(){}{}{m}{p^{1}}^{\alpha_1} + \genfrac(){}{}{m}{p^{2}}^{\alpha_2} + ... + \genfrac(){}{}{m}{p^{k}}^{\alpha_k} + \text{ where } n = + p_1^{\alpha_1} + p_2^{\alpha_2} + ... + p_k^{\alpha_k} + + Like the Legendre symbol, if the Jacobi symbol `\genfrac(){}{}{m}{n} = -1` + then ``m`` is a quadratic nonresidue modulo ``n``. + + But, unlike the Legendre symbol, if the Jacobi symbol + `\genfrac(){}{}{m}{n} = 1` then ``m`` may or may not be a quadratic residue + modulo ``n``. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import jacobi_symbol, legendre_symbol + >>> from sympy import S + >>> jacobi_symbol(45, 77) + -1 + >>> jacobi_symbol(60, 121) + 1 + + The relationship between the ``jacobi_symbol`` and ``legendre_symbol`` can + be demonstrated as follows: + + >>> L = legendre_symbol + >>> S(45).factors() + {3: 2, 5: 1} + >>> jacobi_symbol(7, 45) == L(7, 3)**2 * L(7, 5)**1 + True + + See Also + ======== + + sympy.ntheory.residue_ntheory.is_quad_residue, legendre_symbol + + """ + is_integer = True + is_prime = False + + @classmethod + def eval(cls, m, n): + if m.is_integer is False: + raise TypeError("m should be an integer") + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False or n.is_odd is False: + raise ValueError("n should be an odd positive integer") + if m is S.One or n is S.One: + return S.One + if (m % n).is_zero is True: + return S.Zero + if m.is_Integer is True and n.is_Integer is True: + return S(jacobi(as_int(m), as_int(n))) + + +class kronecker_symbol(DefinedFunction): + r""" + Returns the Kronecker symbol `(a / n)`. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import kronecker_symbol + >>> kronecker_symbol(45, 77) + -1 + >>> kronecker_symbol(13, -120) + 1 + + See Also + ======== + + jacobi_symbol, legendre_symbol + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Kronecker_symbol + + """ + is_integer = True + is_prime = False + + @classmethod + def eval(cls, a, n): + if a.is_integer is False: + raise TypeError("a should be an integer") + if n.is_integer is False: + raise TypeError("n should be an integer") + if a is S.One or n is S.One: + return S.One + if a.is_Integer is True and n.is_Integer is True: + return S(kronecker(as_int(a), as_int(n))) + + +class mobius(DefinedFunction): + """ + Mobius function maps natural number to {-1, 0, 1} + + It is defined as follows: + 1) `1` if `n = 1`. + 2) `0` if `n` has a squared prime factor. + 3) `(-1)^k` if `n` is a square-free positive integer with `k` + number of prime factors. + + It is an important multiplicative function in number theory + and combinatorics. It has applications in mathematical series, + algebraic number theory and also physics (Fermion operator has very + concrete realization with Mobius Function model). + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import mobius + >>> mobius(13*7) + 1 + >>> mobius(1) + 1 + >>> mobius(13*7*5) + -1 + >>> mobius(13**2) + 0 + + Even in the case of a symbol, if it clearly contains a squared prime factor, it will be zero. + + >>> from sympy import Symbol + >>> n = Symbol("n", integer=True, positive=True) + >>> mobius(4*n) + 0 + >>> mobius(n**2) + 0 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/M%C3%B6bius_function + .. [2] Thomas Koshy "Elementary Number Theory with Applications" + .. [3] https://oeis.org/A008683 + + """ + is_integer = True + is_prime = False + + @classmethod + def eval(cls, n): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if n.is_prime is True: + return S.NegativeOne + if n is S.One: + return S.One + result = None + for m, e in (_.as_base_exp() for _ in Mul.make_args(n)): + if m.is_integer is True and m.is_positive is True and \ + e.is_integer is True and e.is_positive is True: + lt = is_lt(S.One, e) # 1 < e + if lt is True: + result = S.Zero + elif m.is_Integer is True: + factors = factorint(m) + if any(v > 1 for v in factors.values()): + result = S.Zero + elif lt is False: + s = S.NegativeOne if len(factors) % 2 else S.One + if result is None: + result = s + else: + result *= s + else: + return + return result + + +class primenu(DefinedFunction): + r""" + Calculate the number of distinct prime factors for a positive integer n. + + If n's prime factorization is: + + .. math :: + n = \prod_{i=1}^k p_i^{m_i}, + + then ``primenu(n)`` or `\nu(n)` is: + + .. math :: + \nu(n) = k. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import primenu + >>> primenu(1) + 0 + >>> primenu(30) + 3 + + See Also + ======== + + sympy.ntheory.factor_.factorint + + References + ========== + + .. [1] https://mathworld.wolfram.com/PrimeFactor.html + .. [2] https://oeis.org/A001221 + + """ + is_integer = True + is_nonnegative = True + + @classmethod + def eval(cls, n): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if n.is_prime is True: + return S.One + if n is S.One: + return S.Zero + if n.is_Integer is True: + return S(len(factorint(n))) + + +class primeomega(DefinedFunction): + r""" + Calculate the number of prime factors counting multiplicities for a + positive integer n. + + If n's prime factorization is: + + .. math :: + n = \prod_{i=1}^k p_i^{m_i}, + + then ``primeomega(n)`` or `\Omega(n)` is: + + .. math :: + \Omega(n) = \sum_{i=1}^k m_i. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import primeomega + >>> primeomega(1) + 0 + >>> primeomega(20) + 3 + + See Also + ======== + + sympy.ntheory.factor_.factorint + + References + ========== + + .. [1] https://mathworld.wolfram.com/PrimeFactor.html + .. [2] https://oeis.org/A001222 + + """ + is_integer = True + is_nonnegative = True + + @classmethod + def eval(cls, n): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if n.is_prime is True: + return S.One + if n is S.One: + return S.Zero + if n.is_Integer is True: + return S(sum(factorint(n).values())) + + +class totient(DefinedFunction): + r""" + Calculate the Euler totient function phi(n) + + ``totient(n)`` or `\phi(n)` is the number of positive integers `\leq` n + that are relatively prime to n. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import totient + >>> totient(1) + 1 + >>> totient(25) + 20 + >>> totient(45) == totient(5)*totient(9) + True + + See Also + ======== + + sympy.ntheory.factor_.divisor_count + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Euler%27s_totient_function + .. [2] https://mathworld.wolfram.com/TotientFunction.html + .. [3] https://oeis.org/A000010 + + """ + is_integer = True + is_positive = True + + @classmethod + def eval(cls, n): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if n is S.One: + return S.One + if n.is_prime is True: + return n - 1 + if isinstance(n, Dict): + return S(prod(p**(k-1)*(p-1) for p, k in n.items())) + if n.is_Integer is True: + return S(prod(p**(k-1)*(p-1) for p, k in factorint(n).items())) + + +class reduced_totient(DefinedFunction): + r""" + Calculate the Carmichael reduced totient function lambda(n) + + ``reduced_totient(n)`` or `\lambda(n)` is the smallest m > 0 such that + `k^m \equiv 1 \mod n` for all k relatively prime to n. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import reduced_totient + >>> reduced_totient(1) + 1 + >>> reduced_totient(8) + 2 + >>> reduced_totient(30) + 4 + + See Also + ======== + + totient + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Carmichael_function + .. [2] https://mathworld.wolfram.com/CarmichaelFunction.html + .. [3] https://oeis.org/A002322 + + """ + is_integer = True + is_positive = True + + @classmethod + def eval(cls, n): + if n.is_integer is False: + raise TypeError("n should be an integer") + if n.is_positive is False: + raise ValueError("n should be a positive integer") + if n is S.One: + return S.One + if n.is_prime is True: + return n - 1 + if isinstance(n, Dict): + t = 1 + if 2 in n: + t = (1 << (n[2] - 2)) if 2 < n[2] else n[2] + return S(lcm(int(t), *(int(p-1)*int(p)**int(k-1) for p, k in n.items() if p != 2))) + if n.is_Integer is True: + n, t = remove(int(n), 2) + if not t: + t = 1 + elif 2 < t: + t = 1 << (t - 2) + return S(lcm(t, *((p-1)*p**(k-1) for p, k in factorint(n).items()))) + + +class primepi(DefinedFunction): + r""" Represents the prime counting function pi(n) = the number + of prime numbers less than or equal to n. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import primepi + >>> from sympy import prime, prevprime, isprime + >>> primepi(25) + 9 + + So there are 9 primes less than or equal to 25. Is 25 prime? + + >>> isprime(25) + False + + It is not. So the first prime less than 25 must be the + 9th prime: + + >>> prevprime(25) == prime(9) + True + + See Also + ======== + + sympy.ntheory.primetest.isprime : Test if n is prime + sympy.ntheory.generate.primerange : Generate all primes in a given range + sympy.ntheory.generate.prime : Return the nth prime + + References + ========== + + .. [1] https://oeis.org/A000720 + + """ + is_integer = True + is_nonnegative = True + + @classmethod + def eval(cls, n): + if n is S.Infinity: + return S.Infinity + if n is S.NegativeInfinity: + return S.Zero + if n.is_real is False: + raise TypeError("n should be a real") + if is_lt(n, S(2)) is True: + return S.Zero + try: + n = int(n) + except TypeError: + return + return S(_primepi(n)) + + +####################################################################### +### +### Functions for enumerating partitions, permutations and combinations +### +####################################################################### + + +class _MultisetHistogram(tuple): + __slots__ = () + + +_N = -1 +_ITEMS = -2 +_M = slice(None, _ITEMS) + + +def _multiset_histogram(n): + """Return tuple used in permutation and combination counting. Input + is a dictionary giving items with counts as values or a sequence of + items (which need not be sorted). + + The data is stored in a class deriving from tuple so it is easily + recognized and so it can be converted easily to a list. + """ + if isinstance(n, dict): # item: count + if not all(isinstance(v, int) and v >= 0 for v in n.values()): + raise ValueError + tot = sum(n.values()) + items = sum(1 for k in n if n[k] > 0) + return _MultisetHistogram([n[k] for k in n if n[k] > 0] + [items, tot]) + else: + n = list(n) + s = set(n) + lens = len(s) + lenn = len(n) + if lens == lenn: + n = [1]*lenn + [lenn, lenn] + return _MultisetHistogram(n) + m = dict(zip(s, range(lens))) + d = dict(zip(range(lens), (0,)*lens)) + for i in n: + d[m[i]] += 1 + return _multiset_histogram(d) + + +def nP(n, k=None, replacement=False): + """Return the number of permutations of ``n`` items taken ``k`` at a time. + + Possible values for ``n``: + + integer - set of length ``n`` + + sequence - converted to a multiset internally + + multiset - {element: multiplicity} + + If ``k`` is None then the total of all permutations of length 0 + through the number of items represented by ``n`` will be returned. + + If ``replacement`` is True then a given item can appear more than once + in the ``k`` items. (For example, for 'ab' permutations of 2 would + include 'aa', 'ab', 'ba' and 'bb'.) The multiplicity of elements in + ``n`` is ignored when ``replacement`` is True but the total number + of elements is considered since no element can appear more times than + the number of elements in ``n``. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import nP + >>> from sympy.utilities.iterables import multiset_permutations, multiset + >>> nP(3, 2) + 6 + >>> nP('abc', 2) == nP(multiset('abc'), 2) == 6 + True + >>> nP('aab', 2) + 3 + >>> nP([1, 2, 2], 2) + 3 + >>> [nP(3, i) for i in range(4)] + [1, 3, 6, 6] + >>> nP(3) == sum(_) + True + + When ``replacement`` is True, each item can have multiplicity + equal to the length represented by ``n``: + + >>> nP('aabc', replacement=True) + 121 + >>> [len(list(multiset_permutations('aaaabbbbcccc', i))) for i in range(5)] + [1, 3, 9, 27, 81] + >>> sum(_) + 121 + + See Also + ======== + sympy.utilities.iterables.multiset_permutations + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Permutation + + """ + try: + n = as_int(n) + except ValueError: + return Integer(_nP(_multiset_histogram(n), k, replacement)) + return Integer(_nP(n, k, replacement)) + + +@cacheit +def _nP(n, k=None, replacement=False): + + if k == 0: + return 1 + if isinstance(n, SYMPY_INTS): # n different items + # assert n >= 0 + if k is None: + return sum(_nP(n, i, replacement) for i in range(n + 1)) + elif replacement: + return n**k + elif k > n: + return 0 + elif k == n: + return factorial(k) + elif k == 1: + return n + else: + # assert k >= 0 + return _product(n - k + 1, n) + elif isinstance(n, _MultisetHistogram): + if k is None: + return sum(_nP(n, i, replacement) for i in range(n[_N] + 1)) + elif replacement: + return n[_ITEMS]**k + elif k == n[_N]: + return factorial(k)/prod([factorial(i) for i in n[_M] if i > 1]) + elif k > n[_N]: + return 0 + elif k == 1: + return n[_ITEMS] + else: + # assert k >= 0 + tot = 0 + n = list(n) + for i in range(len(n[_M])): + if not n[i]: + continue + n[_N] -= 1 + if n[i] == 1: + n[i] = 0 + n[_ITEMS] -= 1 + tot += _nP(_MultisetHistogram(n), k - 1) + n[_ITEMS] += 1 + n[i] = 1 + else: + n[i] -= 1 + tot += _nP(_MultisetHistogram(n), k - 1) + n[i] += 1 + n[_N] += 1 + return tot + + +@cacheit +def _AOP_product(n): + """for n = (m1, m2, .., mk) return the coefficients of the polynomial, + prod(sum(x**i for i in range(nj + 1)) for nj in n); i.e. the coefficients + of the product of AOPs (all-one polynomials) or order given in n. The + resulting coefficient corresponding to x**r is the number of r-length + combinations of sum(n) elements with multiplicities given in n. + The coefficients are given as a default dictionary (so if a query is made + for a key that is not present, 0 will be returned). + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import _AOP_product + >>> from sympy.abc import x + >>> n = (2, 2, 3) # e.g. aabbccc + >>> prod = ((x**2 + x + 1)*(x**2 + x + 1)*(x**3 + x**2 + x + 1)).expand() + >>> c = _AOP_product(n); dict(c) + {0: 1, 1: 3, 2: 6, 3: 8, 4: 8, 5: 6, 6: 3, 7: 1} + >>> [c[i] for i in range(8)] == [prod.coeff(x, i) for i in range(8)] + True + + The generating poly used here is the same as that listed in + https://tinyurl.com/cep849r, but in a refactored form. + + """ + + n = list(n) + ord = sum(n) + need = (ord + 2)//2 + rv = [1]*(n.pop() + 1) + rv.extend((0,) * (need - len(rv))) + rv = rv[:need] + while n: + ni = n.pop() + N = ni + 1 + was = rv[:] + for i in range(1, min(N, len(rv))): + rv[i] += rv[i - 1] + for i in range(N, need): + rv[i] += rv[i - 1] - was[i - N] + rev = list(reversed(rv)) + if ord % 2: + rv = rv + rev + else: + rv[-1:] = rev + d = defaultdict(int) + for i, r in enumerate(rv): + d[i] = r + return d + + +def nC(n, k=None, replacement=False): + """Return the number of combinations of ``n`` items taken ``k`` at a time. + + Possible values for ``n``: + + integer - set of length ``n`` + + sequence - converted to a multiset internally + + multiset - {element: multiplicity} + + If ``k`` is None then the total of all combinations of length 0 + through the number of items represented in ``n`` will be returned. + + If ``replacement`` is True then a given item can appear more than once + in the ``k`` items. (For example, for 'ab' sets of 2 would include 'aa', + 'ab', and 'bb'.) The multiplicity of elements in ``n`` is ignored when + ``replacement`` is True but the total number of elements is considered + since no element can appear more times than the number of elements in + ``n``. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import nC + >>> from sympy.utilities.iterables import multiset_combinations + >>> nC(3, 2) + 3 + >>> nC('abc', 2) + 3 + >>> nC('aab', 2) + 2 + + When ``replacement`` is True, each item can have multiplicity + equal to the length represented by ``n``: + + >>> nC('aabc', replacement=True) + 35 + >>> [len(list(multiset_combinations('aaaabbbbcccc', i))) for i in range(5)] + [1, 3, 6, 10, 15] + >>> sum(_) + 35 + + If there are ``k`` items with multiplicities ``m_1, m_2, ..., m_k`` + then the total of all combinations of length 0 through ``k`` is the + product, ``(m_1 + 1)*(m_2 + 1)*...*(m_k + 1)``. When the multiplicity + of each item is 1 (i.e., k unique items) then there are 2**k + combinations. For example, if there are 4 unique items, the total number + of combinations is 16: + + >>> sum(nC(4, i) for i in range(5)) + 16 + + See Also + ======== + + sympy.utilities.iterables.multiset_combinations + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Combination + .. [2] https://tinyurl.com/cep849r + + """ + + if isinstance(n, SYMPY_INTS): + if k is None: + if not replacement: + return 2**n + return sum(nC(n, i, replacement) for i in range(n + 1)) + if k < 0: + raise ValueError("k cannot be negative") + if replacement: + return binomial(n + k - 1, k) + return binomial(n, k) + if isinstance(n, _MultisetHistogram): + N = n[_N] + if k is None: + if not replacement: + return prod(m + 1 for m in n[_M]) + return sum(nC(n, i, replacement) for i in range(N + 1)) + elif replacement: + return nC(n[_ITEMS], k, replacement) + # assert k >= 0 + elif k in (1, N - 1): + return n[_ITEMS] + elif k in (0, N): + return 1 + return _AOP_product(tuple(n[_M]))[k] + else: + return nC(_multiset_histogram(n), k, replacement) + + +def _eval_stirling1(n, k): + if n == k == 0: + return S.One + if 0 in (n, k): + return S.Zero + + # some special values + if n == k: + return S.One + elif k == n - 1: + return binomial(n, 2) + elif k == n - 2: + return (3*n - 1)*binomial(n, 3)/4 + elif k == n - 3: + return binomial(n, 2)*binomial(n, 4) + + return _stirling1(n, k) + + +@cacheit +def _stirling1(n, k): + row = [0, 1]+[0]*(k-1) # for n = 1 + for i in range(2, n+1): + for j in range(min(k,i), 0, -1): + row[j] = (i-1) * row[j] + row[j-1] + return Integer(row[k]) + + +def _eval_stirling2(n, k): + if n == k == 0: + return S.One + if 0 in (n, k): + return S.Zero + + # some special values + if n == k: + return S.One + elif k == n - 1: + return binomial(n, 2) + elif k == 1: + return S.One + elif k == 2: + return Integer(2**(n - 1) - 1) + + return _stirling2(n, k) + + +@cacheit +def _stirling2(n, k): + row = [0, 1]+[0]*(k-1) # for n = 1 + for i in range(2, n+1): + for j in range(min(k,i), 0, -1): + row[j] = j * row[j] + row[j-1] + return Integer(row[k]) + + +def stirling(n, k, d=None, kind=2, signed=False): + r"""Return Stirling number $S(n, k)$ of the first or second (default) kind. + + The sum of all Stirling numbers of the second kind for $k = 1$ + through $n$ is ``bell(n)``. The recurrence relationship for these numbers + is: + + .. math :: {0 \brace 0} = 1; {n \brace 0} = {0 \brace k} = 0; + + .. math :: {{n+1} \brace k} = j {n \brace k} + {n \brace {k-1}} + + where $j$ is: + $n$ for Stirling numbers of the first kind, + $-n$ for signed Stirling numbers of the first kind, + $k$ for Stirling numbers of the second kind. + + The first kind of Stirling number counts the number of permutations of + ``n`` distinct items that have ``k`` cycles; the second kind counts the + ways in which ``n`` distinct items can be partitioned into ``k`` parts. + If ``d`` is given, the "reduced Stirling number of the second kind" is + returned: $S^{d}(n, k) = S(n - d + 1, k - d + 1)$ with $n \ge k \ge d$. + (This counts the ways to partition $n$ consecutive integers into $k$ + groups with no pairwise difference less than $d$. See example below.) + + To obtain the signed Stirling numbers of the first kind, use keyword + ``signed=True``. Using this keyword automatically sets ``kind`` to 1. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import stirling, bell + >>> from sympy.combinatorics import Permutation + >>> from sympy.utilities.iterables import multiset_partitions, permutations + + First kind (unsigned by default): + + >>> [stirling(6, i, kind=1) for i in range(7)] + [0, 120, 274, 225, 85, 15, 1] + >>> perms = list(permutations(range(4))) + >>> [sum(Permutation(p).cycles == i for p in perms) for i in range(5)] + [0, 6, 11, 6, 1] + >>> [stirling(4, i, kind=1) for i in range(5)] + [0, 6, 11, 6, 1] + + First kind (signed): + + >>> [stirling(4, i, signed=True) for i in range(5)] + [0, -6, 11, -6, 1] + + Second kind: + + >>> [stirling(10, i) for i in range(12)] + [0, 1, 511, 9330, 34105, 42525, 22827, 5880, 750, 45, 1, 0] + >>> sum(_) == bell(10) + True + >>> len(list(multiset_partitions(range(4), 2))) == stirling(4, 2) + True + + Reduced second kind: + + >>> from sympy import subsets, oo + >>> def delta(p): + ... if len(p) == 1: + ... return oo + ... return min(abs(i[0] - i[1]) for i in subsets(p, 2)) + >>> parts = multiset_partitions(range(5), 3) + >>> d = 2 + >>> sum(1 for p in parts if all(delta(i) >= d for i in p)) + 7 + >>> stirling(5, 3, 2) + 7 + + See Also + ======== + sympy.utilities.iterables.multiset_partitions + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind + .. [2] https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind + + """ + # TODO: make this a class like bell() + + n = as_int(n) + k = as_int(k) + if n < 0: + raise ValueError('n must be nonnegative') + if k > n: + return S.Zero + if d: + # assert k >= d + # kind is ignored -- only kind=2 is supported + return _eval_stirling2(n - d + 1, k - d + 1) + elif signed: + # kind is ignored -- only kind=1 is supported + return S.NegativeOne**(n - k)*_eval_stirling1(n, k) + + if kind == 1: + return _eval_stirling1(n, k) + elif kind == 2: + return _eval_stirling2(n, k) + else: + raise ValueError('kind must be 1 or 2, not %s' % k) + + +@cacheit +def _nT(n, k): + """Return the partitions of ``n`` items into ``k`` parts. This + is used by ``nT`` for the case when ``n`` is an integer.""" + # really quick exits + if k > n or k < 0: + return 0 + if k in (1, n): + return 1 + if k == 0: + return 0 + # exits that could be done below but this is quicker + if k == 2: + return n//2 + d = n - k + if d <= 3: + return d + # quick exit + if 3*k >= n: # or, equivalently, 2*k >= d + # all the information needed in this case + # will be in the cache needed to calculate + # partition(d), so... + # update cache + tot = _partition_rec(d) + # and correct for values not needed + if d - k > 0: + tot -= sum(_partition_rec.fetch_item(slice(d - k))) + return tot + # regular exit + # nT(n, k) = Sum(nT(n - k, m), (m, 1, k)); + # calculate needed nT(i, j) values + p = [1]*d + for i in range(2, k + 1): + for m in range(i + 1, d): + p[m] += p[m - i] + d -= 1 + # if p[0] were appended to the end of p then the last + # k values of p are the nT(n, j) values for 0 < j < k in reverse + # order p[-1] = nT(n, 1), p[-2] = nT(n, 2), etc.... Instead of + # putting the 1 from p[0] there, however, it is simply added to + # the sum below which is valid for 1 < k <= n//2 + return (1 + sum(p[1 - k:])) + + +def nT(n, k=None): + """Return the number of ``k``-sized partitions of ``n`` items. + + Possible values for ``n``: + + integer - ``n`` identical items + + sequence - converted to a multiset internally + + multiset - {element: multiplicity} + + Note: the convention for ``nT`` is different than that of ``nC`` and + ``nP`` in that + here an integer indicates ``n`` *identical* items instead of a set of + length ``n``; this is in keeping with the ``partitions`` function which + treats its integer-``n`` input like a list of ``n`` 1s. One can use + ``range(n)`` for ``n`` to indicate ``n`` distinct items. + + If ``k`` is None then the total number of ways to partition the elements + represented in ``n`` will be returned. + + Examples + ======== + + >>> from sympy.functions.combinatorial.numbers import nT + + Partitions of the given multiset: + + >>> [nT('aabbc', i) for i in range(1, 7)] + [1, 8, 11, 5, 1, 0] + >>> nT('aabbc') == sum(_) + True + + >>> [nT("mississippi", i) for i in range(1, 12)] + [1, 74, 609, 1521, 1768, 1224, 579, 197, 50, 9, 1] + + Partitions when all items are identical: + + >>> [nT(5, i) for i in range(1, 6)] + [1, 2, 2, 1, 1] + >>> nT('1'*5) == sum(_) + True + + When all items are different: + + >>> [nT(range(5), i) for i in range(1, 6)] + [1, 15, 25, 10, 1] + >>> nT(range(5)) == sum(_) + True + + Partitions of an integer expressed as a sum of positive integers: + + >>> from sympy import partition + >>> partition(4) + 5 + >>> nT(4, 1) + nT(4, 2) + nT(4, 3) + nT(4, 4) + 5 + >>> nT('1'*4) + 5 + + See Also + ======== + sympy.utilities.iterables.partitions + sympy.utilities.iterables.multiset_partitions + sympy.functions.combinatorial.numbers.partition + + References + ========== + + .. [1] https://web.archive.org/web/20210507012732/https://teaching.csse.uwa.edu.au/units/CITS7209/partition.pdf + + """ + + if isinstance(n, SYMPY_INTS): + # n identical items + if k is None: + return partition(n) + if isinstance(k, SYMPY_INTS): + n = as_int(n) + k = as_int(k) + return Integer(_nT(n, k)) + if not isinstance(n, _MultisetHistogram): + try: + # if n contains hashable items there is some + # quick handling that can be done + u = len(set(n)) + if u <= 1: + return nT(len(n), k) + elif u == len(n): + n = range(u) + raise TypeError + except TypeError: + n = _multiset_histogram(n) + N = n[_N] + if k is None and N == 1: + return 1 + if k in (1, N): + return 1 + if k == 2 or N == 2 and k is None: + m, r = divmod(N, 2) + rv = sum(nC(n, i) for i in range(1, m + 1)) + if not r: + rv -= nC(n, m)//2 + if k is None: + rv += 1 # for k == 1 + return rv + if N == n[_ITEMS]: + # all distinct + if k is None: + return bell(N) + return stirling(N, k) + m = MultisetPartitionTraverser() + if k is None: + return m.count_partitions(n[_M]) + # MultisetPartitionTraverser does not have a range-limited count + # method, so need to enumerate and count + tot = 0 + for discard in m.enum_range(n[_M], k-1, k): + tot += 1 + return tot + + +#-----------------------------------------------------------------------------# +# # +# Motzkin numbers # +# # +#-----------------------------------------------------------------------------# + + +class motzkin(DefinedFunction): + """ + The nth Motzkin number is the number + of ways of drawing non-intersecting chords + between n points on a circle (not necessarily touching + every point by a chord). The Motzkin numbers are named + after Theodore Motzkin and have diverse applications + in geometry, combinatorics and number theory. + + Motzkin numbers are the integer sequence defined by the + initial terms `M_0 = 1`, `M_1 = 1` and the two-term recurrence relation + `M_n = \frac{2*n + 1}{n + 2} * M_{n-1} + \frac{3n - 3}{n + 2} * M_{n-2}`. + + + Examples + ======== + + >>> from sympy import motzkin + + >>> motzkin.is_motzkin(5) + False + >>> motzkin.find_motzkin_numbers_in_range(2,300) + [2, 4, 9, 21, 51, 127] + >>> motzkin.find_motzkin_numbers_in_range(2,900) + [2, 4, 9, 21, 51, 127, 323, 835] + >>> motzkin.find_first_n_motzkins(10) + [1, 1, 2, 4, 9, 21, 51, 127, 323, 835] + + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Motzkin_number + .. [2] https://mathworld.wolfram.com/MotzkinNumber.html + + """ + + @staticmethod + def is_motzkin(n): + try: + n = as_int(n) + except ValueError: + return False + if n > 0: + if n in (1, 2): + return True + + tn1 = 1 + tn = 2 + i = 3 + while tn < n: + a = ((2*i + 1)*tn + (3*i - 3)*tn1)/(i + 2) + i += 1 + tn1 = tn + tn = a + + if tn == n: + return True + else: + return False + + else: + return False + + @staticmethod + def find_motzkin_numbers_in_range(x, y): + if 0 <= x <= y: + motzkins = [] + if x <= 1 <= y: + motzkins.append(1) + tn1 = 1 + tn = 2 + i = 3 + while tn <= y: + if tn >= x: + motzkins.append(tn) + a = ((2*i + 1)*tn + (3*i - 3)*tn1)/(i + 2) + i += 1 + tn1 = tn + tn = int(a) + + return motzkins + + else: + raise ValueError('The provided range is not valid. This condition should satisfy x <= y') + + @staticmethod + def find_first_n_motzkins(n): + try: + n = as_int(n) + except ValueError: + raise ValueError('The provided number must be a positive integer') + if n < 0: + raise ValueError('The provided number must be a positive integer') + motzkins = [1] + if n >= 1: + motzkins.append(1) + tn1 = 1 + tn = 2 + i = 3 + while i <= n: + motzkins.append(tn) + a = ((2*i + 1)*tn + (3*i - 3)*tn1)/(i + 2) + i += 1 + tn1 = tn + tn = int(a) + + return motzkins + + @staticmethod + @recurrence_memo([S.One, S.One]) + def _motzkin(n, prev): + return ((2*n + 1)*prev[-1] + (3*n - 3)*prev[-2]) // (n + 2) + + @classmethod + def eval(cls, n): + try: + n = as_int(n) + except ValueError: + raise ValueError('The provided number must be a positive integer') + if n < 0: + raise ValueError('The provided number must be a positive integer') + return Integer(cls._motzkin(n - 1)) + + +def nD(i=None, brute=None, *, n=None, m=None): + """return the number of derangements for: ``n`` unique items, ``i`` + items (as a sequence or multiset), or multiplicities, ``m`` given + as a sequence or multiset. + + Examples + ======== + + >>> from sympy.utilities.iterables import generate_derangements as enum + >>> from sympy.functions.combinatorial.numbers import nD + + A derangement ``d`` of sequence ``s`` has all ``d[i] != s[i]``: + + >>> set([''.join(i) for i in enum('abc')]) + {'bca', 'cab'} + >>> nD('abc') + 2 + + Input as iterable or dictionary (multiset form) is accepted: + + >>> assert nD([1, 2, 2, 3, 3, 3]) == nD({1: 1, 2: 2, 3: 3}) + + By default, a brute-force enumeration and count of multiset permutations + is only done if there are fewer than 9 elements. There may be cases when + there is high multiplicity with few unique elements that will benefit + from a brute-force enumeration, too. For this reason, the `brute` + keyword (default None) is provided. When False, the brute-force + enumeration will never be used. When True, it will always be used. + + >>> nD('1111222233', brute=True) + 44 + + For convenience, one may specify ``n`` distinct items using the + ``n`` keyword: + + >>> assert nD(n=3) == nD('abc') == 2 + + Since the number of derangments depends on the multiplicity of the + elements and not the elements themselves, it may be more convenient + to give a list or multiset of multiplicities using keyword ``m``: + + >>> assert nD('abc') == nD(m=(1,1,1)) == nD(m={1:3}) == 2 + + """ + from sympy.integrals.integrals import integrate + from sympy.functions.special.polynomials import laguerre + from sympy.abc import x + def ok(x): + if not isinstance(x, SYMPY_INTS): + raise TypeError('expecting integer values') + if x < 0: + raise ValueError('value must not be negative') + return True + + if (i, n, m).count(None) != 2: + raise ValueError('enter only 1 of i, n, or m') + if i is not None: + if isinstance(i, SYMPY_INTS): + raise TypeError('items must be a list or dictionary') + if not i: + return S.Zero + if type(i) is not dict: + s = list(i) + ms = multiset(s) + elif type(i) is dict: + all(ok(_) for _ in i.values()) + ms = {k: v for k, v in i.items() if v} + s = None + if not ms: + return S.Zero + N = sum(ms.values()) + counts = multiset(ms.values()) + nkey = len(ms) + elif n is not None: + ok(n) + if not n: + return S.Zero + return subfactorial(n) + elif m is not None: + if isinstance(m, dict): + all(ok(i) and ok(j) for i, j in m.items()) + counts = {k: v for k, v in m.items() if k*v} + elif iterable(m) or isinstance(m, str): + m = list(m) + all(ok(i) for i in m) + counts = multiset([i for i in m if i]) + else: + raise TypeError('expecting iterable') + if not counts: + return S.Zero + N = sum(k*v for k, v in counts.items()) + nkey = sum(counts.values()) + s = None + big = int(max(counts)) + if big == 1: # no repetition + return subfactorial(nkey) + nval = len(counts) + if big*2 > N: + return S.Zero + if big*2 == N: + if nkey == 2 and nval == 1: + return S.One # aaabbb + if nkey - 1 == big: # one element repeated + return factorial(big) # e.g. abc part of abcddd + if N < 9 and brute is None or brute: + # for all possibilities, this was found to be faster + if s is None: + s = [] + i = 0 + for m, v in counts.items(): + for j in range(v): + s.extend([i]*m) + i += 1 + return Integer(sum(1 for i in multiset_derangements(s))) + from sympy.functions.elementary.exponential import exp + return Integer(abs(integrate(exp(-x)*Mul(*[ + laguerre(i, x)**m for i, m in counts.items()]), (x, 0, oo)))) diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__init__.py b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31b47191a25d36d0c47f63b449f99dc9f23e89e4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_factorials.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_factorials.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7bca89b820a1639fead681e31cdffa999025bc1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_factorials.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_numbers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_numbers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44550b3425c943a56791f9d11b033b48527f656c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/__pycache__/test_comb_numbers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/test_comb_factorials.py b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/test_comb_factorials.py new file mode 100644 index 0000000000000000000000000000000000000000..6e3986c56736cccec0b3370007e047a1f38f06d1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/test_comb_factorials.py @@ -0,0 +1,653 @@ +from sympy.concrete.products import Product +from sympy.core.function import expand_func +from sympy.core.mod import Mod +from sympy.core.mul import Mul +from sympy.core import EulerGamma +from sympy.core.numbers import (Float, I, Rational, nan, oo, pi, zoo) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.combinatorial.factorials import (ff, rf, binomial, factorial, factorial2) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.gamma_functions import (gamma, polygamma) +from sympy.polys.polytools import Poly +from sympy.series.order import O +from sympy.simplify.simplify import simplify +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.functions.combinatorial.factorials import subfactorial +from sympy.functions.special.gamma_functions import uppergamma +from sympy.testing.pytest import XFAIL, raises, slow + +#Solves and Fixes Issue #10388 - This is the updated test for the same solved issue + +def test_rf_eval_apply(): + x, y = symbols('x,y') + n, k = symbols('n k', integer=True) + m = Symbol('m', integer=True, nonnegative=True) + + assert rf(nan, y) is nan + assert rf(x, nan) is nan + + assert unchanged(rf, x, y) + + assert rf(oo, 0) == 1 + assert rf(-oo, 0) == 1 + + assert rf(oo, 6) is oo + assert rf(-oo, 7) is -oo + assert rf(-oo, 6) is oo + + assert rf(oo, -6) is oo + assert rf(-oo, -7) is oo + + assert rf(-1, pi) == 0 + assert rf(-5, 1 + I) == 0 + + assert unchanged(rf, -3, k) + assert unchanged(rf, x, Symbol('k', integer=False)) + assert rf(-3, Symbol('k', integer=False)) == 0 + assert rf(Symbol('x', negative=True, integer=True), Symbol('k', integer=False)) == 0 + + assert rf(x, 0) == 1 + assert rf(x, 1) == x + assert rf(x, 2) == x*(x + 1) + assert rf(x, 3) == x*(x + 1)*(x + 2) + assert rf(x, 5) == x*(x + 1)*(x + 2)*(x + 3)*(x + 4) + + assert rf(x, -1) == 1/(x - 1) + assert rf(x, -2) == 1/((x - 1)*(x - 2)) + assert rf(x, -3) == 1/((x - 1)*(x - 2)*(x - 3)) + + assert rf(1, 100) == factorial(100) + + assert rf(x**2 + 3*x, 2) == (x**2 + 3*x)*(x**2 + 3*x + 1) + assert isinstance(rf(x**2 + 3*x, 2), Mul) + assert rf(x**3 + x, -2) == 1/((x**3 + x - 1)*(x**3 + x - 2)) + + assert rf(Poly(x**2 + 3*x, x), 2) == Poly(x**4 + 8*x**3 + 19*x**2 + 12*x, x) + assert isinstance(rf(Poly(x**2 + 3*x, x), 2), Poly) + raises(ValueError, lambda: rf(Poly(x**2 + 3*x, x, y), 2)) + assert rf(Poly(x**3 + x, x), -2) == 1/(x**6 - 9*x**5 + 35*x**4 - 75*x**3 + 94*x**2 - 66*x + 20) + raises(ValueError, lambda: rf(Poly(x**3 + x, x, y), -2)) + + assert rf(x, m).is_integer is None + assert rf(n, k).is_integer is None + assert rf(n, m).is_integer is True + assert rf(n, k + pi).is_integer is False + assert rf(n, m + pi).is_integer is False + assert rf(pi, m).is_integer is False + + def check(x, k, o, n): + a, b = Dummy(), Dummy() + r = lambda x, k: o(a, b).rewrite(n).subs({a:x,b:k}) + for i in range(-5,5): + for j in range(-5,5): + assert o(i, j) == r(i, j), (o, n, i, j) + check(x, k, rf, ff) + check(x, k, rf, binomial) + check(n, k, rf, factorial) + check(x, y, rf, factorial) + check(x, y, rf, binomial) + + assert rf(x, k).rewrite(ff) == ff(x + k - 1, k) + assert rf(x, k).rewrite(gamma) == Piecewise( + (gamma(k + x)/gamma(x), x > 0), + ((-1)**k*gamma(1 - x)/gamma(-k - x + 1), True)) + assert rf(5, k).rewrite(gamma) == gamma(k + 5)/24 + assert rf(x, k).rewrite(binomial) == factorial(k)*binomial(x + k - 1, k) + assert rf(n, k).rewrite(factorial) == Piecewise( + (factorial(k + n - 1)/factorial(n - 1), n > 0), + ((-1)**k*factorial(-n)/factorial(-k - n), True)) + assert rf(5, k).rewrite(factorial) == factorial(k + 4)/24 + assert rf(x, y).rewrite(factorial) == rf(x, y) + assert rf(x, y).rewrite(binomial) == rf(x, y) + + import random + from mpmath import rf as mpmath_rf + for i in range(100): + x = -500 + 500 * random.random() + k = -500 + 500 * random.random() + assert (abs(mpmath_rf(x, k) - rf(x, k)) < 10**(-15)) + + +def test_ff_eval_apply(): + x, y = symbols('x,y') + n, k = symbols('n k', integer=True) + m = Symbol('m', integer=True, nonnegative=True) + + assert ff(nan, y) is nan + assert ff(x, nan) is nan + + assert unchanged(ff, x, y) + + assert ff(oo, 0) == 1 + assert ff(-oo, 0) == 1 + + assert ff(oo, 6) is oo + assert ff(-oo, 7) is -oo + assert ff(-oo, 6) is oo + + assert ff(oo, -6) is oo + assert ff(-oo, -7) is oo + + assert ff(x, 0) == 1 + assert ff(x, 1) == x + assert ff(x, 2) == x*(x - 1) + assert ff(x, 3) == x*(x - 1)*(x - 2) + assert ff(x, 5) == x*(x - 1)*(x - 2)*(x - 3)*(x - 4) + + assert ff(x, -1) == 1/(x + 1) + assert ff(x, -2) == 1/((x + 1)*(x + 2)) + assert ff(x, -3) == 1/((x + 1)*(x + 2)*(x + 3)) + + assert ff(100, 100) == factorial(100) + + assert ff(2*x**2 - 5*x, 2) == (2*x**2 - 5*x)*(2*x**2 - 5*x - 1) + assert isinstance(ff(2*x**2 - 5*x, 2), Mul) + assert ff(x**2 + 3*x, -2) == 1/((x**2 + 3*x + 1)*(x**2 + 3*x + 2)) + + assert ff(Poly(2*x**2 - 5*x, x), 2) == Poly(4*x**4 - 28*x**3 + 59*x**2 - 35*x, x) + assert isinstance(ff(Poly(2*x**2 - 5*x, x), 2), Poly) + raises(ValueError, lambda: ff(Poly(2*x**2 - 5*x, x, y), 2)) + assert ff(Poly(x**2 + 3*x, x), -2) == 1/(x**4 + 12*x**3 + 49*x**2 + 78*x + 40) + raises(ValueError, lambda: ff(Poly(x**2 + 3*x, x, y), -2)) + + + assert ff(x, m).is_integer is None + assert ff(n, k).is_integer is None + assert ff(n, m).is_integer is True + assert ff(n, k + pi).is_integer is False + assert ff(n, m + pi).is_integer is False + assert ff(pi, m).is_integer is False + + assert isinstance(ff(x, x), ff) + assert ff(n, n) == factorial(n) + + def check(x, k, o, n): + a, b = Dummy(), Dummy() + r = lambda x, k: o(a, b).rewrite(n).subs({a:x,b:k}) + for i in range(-5,5): + for j in range(-5,5): + assert o(i, j) == r(i, j), (o, n) + check(x, k, ff, rf) + check(x, k, ff, gamma) + check(n, k, ff, factorial) + check(x, k, ff, binomial) + check(x, y, ff, factorial) + check(x, y, ff, binomial) + + assert ff(x, k).rewrite(rf) == rf(x - k + 1, k) + assert ff(x, k).rewrite(gamma) == Piecewise( + (gamma(x + 1)/gamma(-k + x + 1), x >= 0), + ((-1)**k*gamma(k - x)/gamma(-x), True)) + assert ff(5, k).rewrite(gamma) == 120/gamma(6 - k) + assert ff(n, k).rewrite(factorial) == Piecewise( + (factorial(n)/factorial(-k + n), n >= 0), + ((-1)**k*factorial(k - n - 1)/factorial(-n - 1), True)) + assert ff(5, k).rewrite(factorial) == 120/factorial(5 - k) + assert ff(x, k).rewrite(binomial) == factorial(k) * binomial(x, k) + assert ff(x, y).rewrite(factorial) == ff(x, y) + assert ff(x, y).rewrite(binomial) == ff(x, y) + + import random + from mpmath import ff as mpmath_ff + for i in range(100): + x = -500 + 500 * random.random() + k = -500 + 500 * random.random() + a = mpmath_ff(x, k) + b = ff(x, k) + assert (abs(a - b) < abs(a) * 10**(-15)) + + +def test_rf_ff_eval_hiprec(): + maple = Float('6.9109401292234329956525265438452') + us = ff(18, Rational(2, 3)).evalf(32) + assert abs(us - maple)/us < 1e-31 + + maple = Float('6.8261540131125511557924466355367') + us = rf(18, Rational(2, 3)).evalf(32) + assert abs(us - maple)/us < 1e-31 + + maple = Float('34.007346127440197150854651814225') + us = rf(Float('4.4', 32), Float('2.2', 32)) + assert abs(us - maple)/us < 1e-31 + + +def test_rf_lambdify_mpmath(): + from sympy.utilities.lambdify import lambdify + x, y = symbols('x,y') + f = lambdify((x,y), rf(x, y), 'mpmath') + maple = Float('34.007346127440197') + us = f(4.4, 2.2) + assert abs(us - maple)/us < 1e-15 + + +def test_factorial(): + x = Symbol('x') + n = Symbol('n', integer=True) + k = Symbol('k', integer=True, nonnegative=True) + r = Symbol('r', integer=False) + s = Symbol('s', integer=False, negative=True) + t = Symbol('t', nonnegative=True) + u = Symbol('u', noninteger=True) + + assert factorial(-2) is zoo + assert factorial(0) == 1 + assert factorial(7) == 5040 + assert factorial(19) == 121645100408832000 + assert factorial(31) == 8222838654177922817725562880000000 + assert factorial(n).func == factorial + assert factorial(2*n).func == factorial + + assert factorial(x).is_integer is None + assert factorial(n).is_integer is None + assert factorial(k).is_integer + assert factorial(r).is_integer is None + + assert factorial(n).is_positive is None + assert factorial(k).is_positive + + assert factorial(x).is_real is None + assert factorial(n).is_real is None + assert factorial(k).is_real is True + assert factorial(r).is_real is None + assert factorial(s).is_real is True + assert factorial(t).is_real is True + assert factorial(u).is_real is True + + assert factorial(x).is_composite is None + assert factorial(n).is_composite is None + assert factorial(k).is_composite is None + assert factorial(k + 3).is_composite is True + assert factorial(r).is_composite is None + assert factorial(s).is_composite is None + assert factorial(t).is_composite is None + assert factorial(u).is_composite is None + + assert factorial(oo) is oo + + +def test_factorial_Mod(): + pr = Symbol('pr', prime=True) + p, q = 10**9 + 9, 10**9 + 33 # prime modulo + r, s = 10**7 + 5, 33333333 # composite modulo + assert Mod(factorial(pr - 1), pr) == pr - 1 + assert Mod(factorial(pr - 1), -pr) == -1 + assert Mod(factorial(r - 1, evaluate=False), r) == 0 + assert Mod(factorial(s - 1, evaluate=False), s) == 0 + assert Mod(factorial(p - 1, evaluate=False), p) == p - 1 + assert Mod(factorial(q - 1, evaluate=False), q) == q - 1 + assert Mod(factorial(p - 50, evaluate=False), p) == 854928834 + assert Mod(factorial(q - 1800, evaluate=False), q) == 905504050 + assert Mod(factorial(153, evaluate=False), r) == Mod(factorial(153), r) + assert Mod(factorial(255, evaluate=False), s) == Mod(factorial(255), s) + assert Mod(factorial(4, evaluate=False), 3) == S.Zero + assert Mod(factorial(5, evaluate=False), 6) == S.Zero + + +def test_factorial_diff(): + n = Symbol('n', integer=True) + + assert factorial(n).diff(n) == \ + gamma(1 + n)*polygamma(0, 1 + n) + assert factorial(n**2).diff(n) == \ + 2*n*gamma(1 + n**2)*polygamma(0, 1 + n**2) + raises(ArgumentIndexError, lambda: factorial(n**2).fdiff(2)) + + +def test_factorial_series(): + n = Symbol('n', integer=True) + + assert factorial(n).series(n, 0, 3) == \ + 1 - n*EulerGamma + n**2*(EulerGamma**2/2 + pi**2/12) + O(n**3) + + +def test_factorial_rewrite(): + n = Symbol('n', integer=True) + k = Symbol('k', integer=True, nonnegative=True) + + assert factorial(n).rewrite(gamma) == gamma(n + 1) + _i = Dummy('i') + assert factorial(k).rewrite(Product).dummy_eq(Product(_i, (_i, 1, k))) + assert factorial(n).rewrite(Product) == factorial(n) + + +def test_factorial2(): + n = Symbol('n', integer=True) + + assert factorial2(-1) == 1 + assert factorial2(0) == 1 + assert factorial2(7) == 105 + assert factorial2(8) == 384 + + # The following is exhaustive + tt = Symbol('tt', integer=True, nonnegative=True) + tte = Symbol('tte', even=True, nonnegative=True) + tpe = Symbol('tpe', even=True, positive=True) + tto = Symbol('tto', odd=True, nonnegative=True) + tf = Symbol('tf', integer=True, nonnegative=False) + tfe = Symbol('tfe', even=True, nonnegative=False) + tfo = Symbol('tfo', odd=True, nonnegative=False) + ft = Symbol('ft', integer=False, nonnegative=True) + ff = Symbol('ff', integer=False, nonnegative=False) + fn = Symbol('fn', integer=False) + nt = Symbol('nt', nonnegative=True) + nf = Symbol('nf', nonnegative=False) + nn = Symbol('nn') + z = Symbol('z', zero=True) + #Solves and Fixes Issue #10388 - This is the updated test for the same solved issue + raises(ValueError, lambda: factorial2(oo)) + raises(ValueError, lambda: factorial2(Rational(5, 2))) + raises(ValueError, lambda: factorial2(-4)) + assert factorial2(n).is_integer is None + assert factorial2(tt - 1).is_integer + assert factorial2(tte - 1).is_integer + assert factorial2(tpe - 3).is_integer + assert factorial2(tto - 4).is_integer + assert factorial2(tto - 2).is_integer + assert factorial2(tf).is_integer is None + assert factorial2(tfe).is_integer is None + assert factorial2(tfo).is_integer is None + assert factorial2(ft).is_integer is None + assert factorial2(ff).is_integer is None + assert factorial2(fn).is_integer is None + assert factorial2(nt).is_integer is None + assert factorial2(nf).is_integer is None + assert factorial2(nn).is_integer is None + + assert factorial2(n).is_positive is None + assert factorial2(tt - 1).is_positive is True + assert factorial2(tte - 1).is_positive is True + assert factorial2(tpe - 3).is_positive is True + assert factorial2(tpe - 1).is_positive is True + assert factorial2(tto - 2).is_positive is True + assert factorial2(tto - 1).is_positive is True + assert factorial2(tf).is_positive is None + assert factorial2(tfe).is_positive is None + assert factorial2(tfo).is_positive is None + assert factorial2(ft).is_positive is None + assert factorial2(ff).is_positive is None + assert factorial2(fn).is_positive is None + assert factorial2(nt).is_positive is None + assert factorial2(nf).is_positive is None + assert factorial2(nn).is_positive is None + + assert factorial2(tt).is_even is None + assert factorial2(tt).is_odd is None + assert factorial2(tte).is_even is None + assert factorial2(tte).is_odd is None + assert factorial2(tte + 2).is_even is True + assert factorial2(tpe).is_even is True + assert factorial2(tpe).is_odd is False + assert factorial2(tto).is_odd is True + assert factorial2(tf).is_even is None + assert factorial2(tf).is_odd is None + assert factorial2(tfe).is_even is None + assert factorial2(tfe).is_odd is None + assert factorial2(tfo).is_even is False + assert factorial2(tfo).is_odd is None + assert factorial2(z).is_even is False + assert factorial2(z).is_odd is True + + +def test_factorial2_rewrite(): + n = Symbol('n', integer=True) + assert factorial2(n).rewrite(gamma) == \ + 2**(n/2)*Piecewise((1, Eq(Mod(n, 2), 0)), (sqrt(2)/sqrt(pi), Eq(Mod(n, 2), 1)))*gamma(n/2 + 1) + assert factorial2(2*n).rewrite(gamma) == 2**n*gamma(n + 1) + assert factorial2(2*n + 1).rewrite(gamma) == \ + sqrt(2)*2**(n + S.Half)*gamma(n + Rational(3, 2))/sqrt(pi) + + +def test_binomial(): + x = Symbol('x') + n = Symbol('n', integer=True) + nz = Symbol('nz', integer=True, nonzero=True) + k = Symbol('k', integer=True) + kp = Symbol('kp', integer=True, positive=True) + kn = Symbol('kn', integer=True, negative=True) + u = Symbol('u', negative=True) + v = Symbol('v', nonnegative=True) + p = Symbol('p', positive=True) + z = Symbol('z', zero=True) + nt = Symbol('nt', integer=False) + kt = Symbol('kt', integer=False) + a = Symbol('a', integer=True, nonnegative=True) + b = Symbol('b', integer=True, nonnegative=True) + + assert binomial(0, 0) == 1 + assert binomial(1, 1) == 1 + assert binomial(10, 10) == 1 + assert binomial(n, z) == 1 + assert binomial(1, 2) == 0 + assert binomial(-1, 2) == 1 + assert binomial(1, -1) == 0 + assert binomial(-1, 1) == -1 + assert binomial(-1, -1) == 0 + assert binomial(S.Half, S.Half) == 1 + assert binomial(-10, 1) == -10 + assert binomial(-10, 7) == -11440 + assert binomial(n, -1) == 0 # holds for all integers (negative, zero, positive) + assert binomial(kp, -1) == 0 + assert binomial(nz, 0) == 1 + assert expand_func(binomial(n, 1)) == n + assert expand_func(binomial(n, 2)) == n*(n - 1)/2 + assert expand_func(binomial(n, n - 2)) == n*(n - 1)/2 + assert expand_func(binomial(n, n - 1)) == n + assert binomial(n, 3).func == binomial + assert binomial(n, 3).expand(func=True) == n**3/6 - n**2/2 + n/3 + assert expand_func(binomial(n, 3)) == n*(n - 2)*(n - 1)/6 + assert binomial(n, n).func == binomial # e.g. (-1, -1) == 0, (2, 2) == 1 + assert binomial(n, n + 1).func == binomial # e.g. (-1, 0) == 1 + assert binomial(kp, kp + 1) == 0 + assert binomial(kn, kn) == 0 # issue #14529 + assert binomial(n, u).func == binomial + assert binomial(kp, u).func == binomial + assert binomial(n, p).func == binomial + assert binomial(n, k).func == binomial + assert binomial(n, n + p).func == binomial + assert binomial(kp, kp + p).func == binomial + + assert expand_func(binomial(n, n - 3)) == n*(n - 2)*(n - 1)/6 + + assert binomial(n, k).is_integer + assert binomial(nt, k).is_integer is None + assert binomial(x, nt).is_integer is False + + assert binomial(gamma(25), 6) == 79232165267303928292058750056084441948572511312165380965440075720159859792344339983120618959044048198214221915637090855535036339620413440000 + assert binomial(1324, 47) == 906266255662694632984994480774946083064699457235920708992926525848438478406790323869952 + assert binomial(1735, 43) == 190910140420204130794758005450919715396159959034348676124678207874195064798202216379800 + assert binomial(2512, 53) == 213894469313832631145798303740098720367984955243020898718979538096223399813295457822575338958939834177325304000 + assert binomial(3383, 52) == 27922807788818096863529701501764372757272890613101645521813434902890007725667814813832027795881839396839287659777235 + assert binomial(4321, 51) == 124595639629264868916081001263541480185227731958274383287107643816863897851139048158022599533438936036467601690983780576 + + assert binomial(a, b).is_nonnegative is True + assert binomial(-1, 2, evaluate=False).is_nonnegative is True + assert binomial(10, 5, evaluate=False).is_nonnegative is True + assert binomial(10, -3, evaluate=False).is_nonnegative is True + assert binomial(-10, -3, evaluate=False).is_nonnegative is True + assert binomial(-10, 2, evaluate=False).is_nonnegative is True + assert binomial(-10, 1, evaluate=False).is_nonnegative is False + assert binomial(-10, 7, evaluate=False).is_nonnegative is False + + # issue #14625 + for _ in (pi, -pi, nt, v, a): + assert binomial(_, _) == 1 + assert binomial(_, _ - 1) == _ + assert isinstance(binomial(u, u), binomial) + assert isinstance(binomial(u, u - 1), binomial) + assert isinstance(binomial(x, x), binomial) + assert isinstance(binomial(x, x - 1), binomial) + + #issue #18802 + assert expand_func(binomial(x + 1, x)) == x + 1 + assert expand_func(binomial(x, x - 1)) == x + assert expand_func(binomial(x + 1, x - 1)) == x*(x + 1)/2 + assert expand_func(binomial(x**2 + 1, x**2)) == x**2 + 1 + + # issue #13980 and #13981 + assert binomial(-7, -5) == 0 + assert binomial(-23, -12) == 0 + assert binomial(Rational(13, 2), -10) == 0 + assert binomial(-49, -51) == 0 + + assert binomial(19, Rational(-7, 2)) == S(-68719476736)/(911337863661225*pi) + assert binomial(0, Rational(3, 2)) == S(-2)/(3*pi) + assert binomial(-3, Rational(-7, 2)) is zoo + assert binomial(kn, kt) is zoo + + assert binomial(nt, kt).func == binomial + assert binomial(nt, Rational(15, 6)) == 8*gamma(nt + 1)/(15*sqrt(pi)*gamma(nt - Rational(3, 2))) + assert binomial(Rational(20, 3), Rational(-10, 8)) == gamma(Rational(23, 3))/(gamma(Rational(-1, 4))*gamma(Rational(107, 12))) + assert binomial(Rational(19, 2), Rational(-7, 2)) == Rational(-1615, 8388608) + assert binomial(Rational(-13, 5), Rational(-7, 8)) == gamma(Rational(-8, 5))/(gamma(Rational(-29, 40))*gamma(Rational(1, 8))) + assert binomial(Rational(-19, 8), Rational(-13, 5)) == gamma(Rational(-11, 8))/(gamma(Rational(-8, 5))*gamma(Rational(49, 40))) + + # binomial for complexes + assert binomial(I, Rational(-89, 8)) == gamma(1 + I)/(gamma(Rational(-81, 8))*gamma(Rational(97, 8) + I)) + assert binomial(I, 2*I) == gamma(1 + I)/(gamma(1 - I)*gamma(1 + 2*I)) + assert binomial(-7, I) is zoo + assert binomial(Rational(-7, 6), I) == gamma(Rational(-1, 6))/(gamma(Rational(-1, 6) - I)*gamma(1 + I)) + assert binomial((1+2*I), (1+3*I)) == gamma(2 + 2*I)/(gamma(1 - I)*gamma(2 + 3*I)) + assert binomial(I, 5) == Rational(1, 3) - I/S(12) + assert binomial((2*I + 3), 7) == -13*I/S(63) + assert isinstance(binomial(I, n), binomial) + assert expand_func(binomial(3, 2, evaluate=False)) == 3 + assert expand_func(binomial(n, 0, evaluate=False)) == 1 + assert expand_func(binomial(n, -2, evaluate=False)) == 0 + assert expand_func(binomial(n, k)) == binomial(n, k) + + +def test_binomial_Mod(): + p, q = 10**5 + 3, 10**9 + 33 # prime modulo + r = 10**7 + 5 # composite modulo + + # A few tests to get coverage + # Lucas Theorem + assert Mod(binomial(156675, 4433, evaluate=False), p) == Mod(binomial(156675, 4433), p) + + # factorial Mod + assert Mod(binomial(1234, 432, evaluate=False), q) == Mod(binomial(1234, 432), q) + + # binomial factorize + assert Mod(binomial(253, 113, evaluate=False), r) == Mod(binomial(253, 113), r) + + # using Granville's generalisation of Lucas' Theorem + assert Mod(binomial(10**18, 10**12, evaluate=False), p*p) == 3744312326 + + +@slow +def test_binomial_Mod_slow(): + p, q = 10**5 + 3, 10**9 + 33 # prime modulo + r, s = 10**7 + 5, 33333333 # composite modulo + + n, k, m = symbols('n k m') + assert (binomial(n, k) % q).subs({n: s, k: p}) == Mod(binomial(s, p), q) + assert (binomial(n, k) % m).subs({n: 8, k: 5, m: 13}) == 4 + assert (binomial(9, k) % 7).subs(k, 2) == 1 + + # Lucas Theorem + assert Mod(binomial(123456, 43253, evaluate=False), p) == Mod(binomial(123456, 43253), p) + assert Mod(binomial(-178911, 237, evaluate=False), p) == Mod(-binomial(178911 + 237 - 1, 237), p) + assert Mod(binomial(-178911, 238, evaluate=False), p) == Mod(binomial(178911 + 238 - 1, 238), p) + + # factorial Mod + assert Mod(binomial(9734, 451, evaluate=False), q) == Mod(binomial(9734, 451), q) + assert Mod(binomial(-10733, 4459, evaluate=False), q) == Mod(binomial(-10733, 4459), q) + assert Mod(binomial(-15733, 4458, evaluate=False), q) == Mod(binomial(-15733, 4458), q) + assert Mod(binomial(23, -38, evaluate=False), q) is S.Zero + assert Mod(binomial(23, 38, evaluate=False), q) is S.Zero + + # binomial factorize + assert Mod(binomial(753, 119, evaluate=False), r) == Mod(binomial(753, 119), r) + assert Mod(binomial(3781, 948, evaluate=False), s) == Mod(binomial(3781, 948), s) + assert Mod(binomial(25773, 1793, evaluate=False), s) == Mod(binomial(25773, 1793), s) + assert Mod(binomial(-753, 118, evaluate=False), r) == Mod(binomial(-753, 118), r) + assert Mod(binomial(-25773, 1793, evaluate=False), s) == Mod(binomial(-25773, 1793), s) + + +def test_binomial_diff(): + n = Symbol('n', integer=True) + k = Symbol('k', integer=True) + + assert binomial(n, k).diff(n) == \ + (-polygamma(0, 1 + n - k) + polygamma(0, 1 + n))*binomial(n, k) + assert binomial(n**2, k**3).diff(n) == \ + 2*n*(-polygamma( + 0, 1 + n**2 - k**3) + polygamma(0, 1 + n**2))*binomial(n**2, k**3) + + assert binomial(n, k).diff(k) == \ + (-polygamma(0, 1 + k) + polygamma(0, 1 + n - k))*binomial(n, k) + assert binomial(n**2, k**3).diff(k) == \ + 3*k**2*(-polygamma( + 0, 1 + k**3) + polygamma(0, 1 + n**2 - k**3))*binomial(n**2, k**3) + raises(ArgumentIndexError, lambda: binomial(n, k).fdiff(3)) + + +def test_binomial_rewrite(): + n = Symbol('n', integer=True) + k = Symbol('k', integer=True) + x = Symbol('x') + + assert binomial(n, k).rewrite( + factorial) == factorial(n)/(factorial(k)*factorial(n - k)) + assert binomial( + n, k).rewrite(gamma) == gamma(n + 1)/(gamma(k + 1)*gamma(n - k + 1)) + assert binomial(n, k).rewrite(ff) == ff(n, k) / factorial(k) + assert binomial(n, x).rewrite(ff) == binomial(n, x) + + +@XFAIL +def test_factorial_simplify_fail(): + # simplify(factorial(x + 1).diff(x) - ((x + 1)*factorial(x)).diff(x))) == 0 + from sympy.abc import x + assert simplify(x*polygamma(0, x + 1) - x*polygamma(0, x + 2) + + polygamma(0, x + 1) - polygamma(0, x + 2) + 1) == 0 + + +def test_subfactorial(): + assert all(subfactorial(i) == ans for i, ans in enumerate( + [1, 0, 1, 2, 9, 44, 265, 1854, 14833, 133496])) + assert subfactorial(oo) is oo + assert subfactorial(nan) is nan + assert subfactorial(23) == 9510425471055777937262 + assert unchanged(subfactorial, 2.2) + + x = Symbol('x') + assert subfactorial(x).rewrite(uppergamma) == uppergamma(x + 1, -1)/S.Exp1 + + tt = Symbol('tt', integer=True, nonnegative=True) + tf = Symbol('tf', integer=True, nonnegative=False) + tn = Symbol('tf', integer=True) + ft = Symbol('ft', integer=False, nonnegative=True) + ff = Symbol('ff', integer=False, nonnegative=False) + fn = Symbol('ff', integer=False) + nt = Symbol('nt', nonnegative=True) + nf = Symbol('nf', nonnegative=False) + nn = Symbol('nf') + te = Symbol('te', even=True, nonnegative=True) + to = Symbol('to', odd=True, nonnegative=True) + assert subfactorial(tt).is_integer + assert subfactorial(tf).is_integer is None + assert subfactorial(tn).is_integer is None + assert subfactorial(ft).is_integer is None + assert subfactorial(ff).is_integer is None + assert subfactorial(fn).is_integer is None + assert subfactorial(nt).is_integer is None + assert subfactorial(nf).is_integer is None + assert subfactorial(nn).is_integer is None + assert subfactorial(tt).is_nonnegative + assert subfactorial(tf).is_nonnegative is None + assert subfactorial(tn).is_nonnegative is None + assert subfactorial(ft).is_nonnegative is None + assert subfactorial(ff).is_nonnegative is None + assert subfactorial(fn).is_nonnegative is None + assert subfactorial(nt).is_nonnegative is None + assert subfactorial(nf).is_nonnegative is None + assert subfactorial(nn).is_nonnegative is None + assert subfactorial(tt).is_even is None + assert subfactorial(tt).is_odd is None + assert subfactorial(te).is_odd is True + assert subfactorial(to).is_even is True diff --git a/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/test_comb_numbers.py b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/test_comb_numbers.py new file mode 100644 index 0000000000000000000000000000000000000000..83a7de89ed8e4fcc433d29f41fc87b9d0d397539 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/combinatorial/tests/test_comb_numbers.py @@ -0,0 +1,1250 @@ +import string + +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.function import (diff, expand_func) +from sympy.core import (EulerGamma, TribonacciConstant) +from sympy.core.numbers import (Float, I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.combinatorial.numbers import carmichael +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.integers import floor +from sympy.polys.polytools import cancel +from sympy.series.limits import limit, Limit +from sympy.series.order import O +from sympy.functions import ( + bernoulli, harmonic, bell, fibonacci, tribonacci, lucas, euler, catalan, + genocchi, andre, partition, divisor_sigma, udivisor_sigma, legendre_symbol, + jacobi_symbol, kronecker_symbol, mobius, + primenu, primeomega, totient, reduced_totient, primepi, + motzkin, binomial, gamma, sqrt, cbrt, hyper, log, digamma, + trigamma, polygamma, factorial, sin, cos, cot, polylog, zeta, dirichlet_eta) +from sympy.functions.combinatorial.numbers import _nT +from sympy.ntheory.factor_ import factorint + +from sympy.core.expr import unchanged +from sympy.core.numbers import GoldenRatio, Integer + +from sympy.testing.pytest import raises, nocache_fail, warns_deprecated_sympy +from sympy.abc import x + + +def test_carmichael(): + with warns_deprecated_sympy(): + assert carmichael.is_prime(2821) == False + + +def test_bernoulli(): + assert bernoulli(0) == 1 + assert bernoulli(1) == Rational(1, 2) + assert bernoulli(2) == Rational(1, 6) + assert bernoulli(3) == 0 + assert bernoulli(4) == Rational(-1, 30) + assert bernoulli(5) == 0 + assert bernoulli(6) == Rational(1, 42) + assert bernoulli(7) == 0 + assert bernoulli(8) == Rational(-1, 30) + assert bernoulli(10) == Rational(5, 66) + assert bernoulli(1000001) == 0 + + assert bernoulli(0, x) == 1 + assert bernoulli(1, x) == x - S.Half + assert bernoulli(2, x) == x**2 - x + Rational(1, 6) + assert bernoulli(3, x) == x**3 - (3*x**2)/2 + x/2 + + # Should be fast; computed with mpmath + b = bernoulli(1000) + assert b.p % 10**10 == 7950421099 + assert b.q == 342999030 + + b = bernoulli(10**6, evaluate=False).evalf() + assert str(b) == '-2.23799235765713e+4767529' + + # Issue #8527 + l = Symbol('l', integer=True) + m = Symbol('m', integer=True, nonnegative=True) + n = Symbol('n', integer=True, positive=True) + assert isinstance(bernoulli(2 * l + 1), bernoulli) + assert isinstance(bernoulli(2 * m + 1), bernoulli) + assert bernoulli(2 * n + 1) == 0 + + assert bernoulli(x, 1) == bernoulli(x) + + assert str(bernoulli(0.0, 2.3).evalf(n=10)) == '1.000000000' + assert str(bernoulli(1.0).evalf(n=10)) == '0.5000000000' + assert str(bernoulli(1.2).evalf(n=10)) == '0.4195995367' + assert str(bernoulli(1.2, 0.8).evalf(n=10)) == '0.2144830348' + assert str(bernoulli(1.2, -0.8).evalf(n=10)) == '-1.158865646 - 0.6745558744*I' + assert str(bernoulli(3.0, 1j).evalf(n=10)) == '1.5 - 0.5*I' + assert str(bernoulli(I).evalf(n=10)) == '0.9268485643 - 0.5821580598*I' + assert str(bernoulli(I, I).evalf(n=10)) == '0.1267792071 + 0.01947413152*I' + assert bernoulli(x).evalf() == bernoulli(x) + + +def test_bernoulli_rewrite(): + from sympy.functions.elementary.piecewise import Piecewise + n = Symbol('n', integer=True, nonnegative=True) + + assert bernoulli(-1).rewrite(zeta) == pi**2/6 + assert bernoulli(-2).rewrite(zeta) == 2*zeta(3) + assert not bernoulli(n, -3).rewrite(zeta).has(harmonic) + assert bernoulli(-4, x).rewrite(zeta) == 4*zeta(5, x) + assert isinstance(bernoulli(n, x).rewrite(zeta), Piecewise) + assert bernoulli(n+1, x).rewrite(zeta) == -(n+1) * zeta(-n, x) + + +def test_fibonacci(): + assert [fibonacci(n) for n in range(-3, 5)] == [2, -1, 1, 0, 1, 1, 2, 3] + assert fibonacci(100) == 354224848179261915075 + assert [lucas(n) for n in range(-3, 5)] == [-4, 3, -1, 2, 1, 3, 4, 7] + assert lucas(100) == 792070839848372253127 + + assert fibonacci(1, x) == 1 + assert fibonacci(2, x) == x + assert fibonacci(3, x) == x**2 + 1 + assert fibonacci(4, x) == x**3 + 2*x + + # issue #8800 + n = Dummy('n') + assert fibonacci(n).limit(n, S.Infinity) is S.Infinity + assert lucas(n).limit(n, S.Infinity) is S.Infinity + + assert fibonacci(n).rewrite(sqrt) == \ + 2**(-n)*sqrt(5)*((1 + sqrt(5))**n - (-sqrt(5) + 1)**n) / 5 + assert fibonacci(n).rewrite(sqrt).subs(n, 10).expand() == fibonacci(10) + assert fibonacci(n).rewrite(GoldenRatio).subs(n,10).evalf() == \ + Float(fibonacci(10)) + assert lucas(n).rewrite(sqrt) == \ + (fibonacci(n-1).rewrite(sqrt) + fibonacci(n+1).rewrite(sqrt)).simplify() + assert lucas(n).rewrite(sqrt).subs(n, 10).expand() == lucas(10) + raises(ValueError, lambda: fibonacci(-3, x)) + + +def test_tribonacci(): + assert [tribonacci(n) for n in range(8)] == [0, 1, 1, 2, 4, 7, 13, 24] + assert tribonacci(100) == 98079530178586034536500564 + + assert tribonacci(0, x) == 0 + assert tribonacci(1, x) == 1 + assert tribonacci(2, x) == x**2 + assert tribonacci(3, x) == x**4 + x + assert tribonacci(4, x) == x**6 + 2*x**3 + 1 + assert tribonacci(5, x) == x**8 + 3*x**5 + 3*x**2 + + n = Dummy('n') + assert tribonacci(n).limit(n, S.Infinity) is S.Infinity + + w = (-1 + S.ImaginaryUnit * sqrt(3)) / 2 + a = (1 + cbrt(19 + 3*sqrt(33)) + cbrt(19 - 3*sqrt(33))) / 3 + b = (1 + w*cbrt(19 + 3*sqrt(33)) + w**2*cbrt(19 - 3*sqrt(33))) / 3 + c = (1 + w**2*cbrt(19 + 3*sqrt(33)) + w*cbrt(19 - 3*sqrt(33))) / 3 + assert tribonacci(n).rewrite(sqrt) == \ + (a**(n + 1)/((a - b)*(a - c)) + + b**(n + 1)/((b - a)*(b - c)) + + c**(n + 1)/((c - a)*(c - b))) + assert tribonacci(n).rewrite(sqrt).subs(n, 4).simplify() == tribonacci(4) + assert tribonacci(n).rewrite(GoldenRatio).subs(n,10).evalf() == \ + Float(tribonacci(10)) + assert tribonacci(n).rewrite(TribonacciConstant) == floor( + 3*TribonacciConstant**n*(102*sqrt(33) + 586)**Rational(1, 3)/ + (-2*(102*sqrt(33) + 586)**Rational(1, 3) + 4 + (102*sqrt(33) + + 586)**Rational(2, 3)) + S.Half) + raises(ValueError, lambda: tribonacci(-1, x)) + + +@nocache_fail +def test_bell(): + assert [bell(n) for n in range(8)] == [1, 1, 2, 5, 15, 52, 203, 877] + + assert bell(0, x) == 1 + assert bell(1, x) == x + assert bell(2, x) == x**2 + x + assert bell(5, x) == x**5 + 10*x**4 + 25*x**3 + 15*x**2 + x + assert bell(oo) is S.Infinity + raises(ValueError, lambda: bell(oo, x)) + + raises(ValueError, lambda: bell(-1)) + raises(ValueError, lambda: bell(S.Half)) + + X = symbols('x:6') + # X = (x0, x1, .. x5) + # at the same time: X[1] = x1, X[2] = x2 for standard readablity. + # but we must supply zero-based indexed object X[1:] = (x1, .. x5) + + assert bell(6, 2, X[1:]) == 6*X[5]*X[1] + 15*X[4]*X[2] + 10*X[3]**2 + assert bell( + 6, 3, X[1:]) == 15*X[4]*X[1]**2 + 60*X[3]*X[2]*X[1] + 15*X[2]**3 + + X = (1, 10, 100, 1000, 10000) + assert bell(6, 2, X) == (6 + 15 + 10)*10000 + + X = (1, 2, 3, 3, 5) + assert bell(6, 2, X) == 6*5 + 15*3*2 + 10*3**2 + + X = (1, 2, 3, 5) + assert bell(6, 3, X) == 15*5 + 60*3*2 + 15*2**3 + + # Dobinski's formula + n = Symbol('n', integer=True, nonnegative=True) + # For large numbers, this is too slow + # For nonintegers, there are significant precision errors + for i in [0, 2, 3, 7, 13, 42, 55]: + # Running without the cache this is either very slow or goes into an + # infinite loop. + assert bell(i).evalf() == bell(n).rewrite(Sum).evalf(subs={n: i}) + + m = Symbol("m") + assert bell(m).rewrite(Sum) == bell(m) + assert bell(n, m).rewrite(Sum) == bell(n, m) + # issue 9184 + n = Dummy('n') + assert bell(n).limit(n, S.Infinity) is S.Infinity + + +def test_harmonic(): + n = Symbol("n") + m = Symbol("m") + + assert harmonic(n, 0) == n + assert harmonic(n).evalf() == harmonic(n) + assert harmonic(n, 1) == harmonic(n) + assert harmonic(1, n) == 1 + + assert harmonic(0, 1) == 0 + assert harmonic(1, 1) == 1 + assert harmonic(2, 1) == Rational(3, 2) + assert harmonic(3, 1) == Rational(11, 6) + assert harmonic(4, 1) == Rational(25, 12) + assert harmonic(0, 2) == 0 + assert harmonic(1, 2) == 1 + assert harmonic(2, 2) == Rational(5, 4) + assert harmonic(3, 2) == Rational(49, 36) + assert harmonic(4, 2) == Rational(205, 144) + assert harmonic(0, 3) == 0 + assert harmonic(1, 3) == 1 + assert harmonic(2, 3) == Rational(9, 8) + assert harmonic(3, 3) == Rational(251, 216) + assert harmonic(4, 3) == Rational(2035, 1728) + + assert harmonic(oo, -1) is S.NaN + assert harmonic(oo, 0) is oo + assert harmonic(oo, S.Half) is oo + assert harmonic(oo, 1) is oo + assert harmonic(oo, 2) == (pi**2)/6 + assert harmonic(oo, 3) == zeta(3) + assert harmonic(oo, Dummy(negative=True)) is S.NaN + ip = Dummy(integer=True, positive=True) + if (1/ip <= 1) is True: #---------------------------------+ + assert None, 'delete this if-block and the next line' #| + ip = Dummy(even=True, positive=True) #--------------------+ + assert harmonic(oo, 1/ip) is oo + assert harmonic(oo, 1 + ip) is zeta(1 + ip) + + assert harmonic(0, m) == 0 + assert harmonic(-1, -1) == 0 + assert harmonic(-1, 0) == -1 + assert harmonic(-1, 1) is S.ComplexInfinity + assert harmonic(-1, 2) is S.NaN + assert harmonic(-3, -2) == -5 + assert harmonic(-3, -3) == 9 + + +def test_harmonic_rational(): + ne = S(6) + no = S(5) + pe = S(8) + po = S(9) + qe = S(10) + qo = S(13) + + Heee = harmonic(ne + pe/qe) + Aeee = (-log(10) + 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8))) + + 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8))) + + pi*sqrt(2*sqrt(5)/5 + 1)/2 + Rational(13944145, 4720968)) + + Heeo = harmonic(ne + pe/qo) + Aeeo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(4, 13)) + 2*log(sin(pi*Rational(2, 13)))*cos(pi*Rational(32, 13)) + + 2*log(sin(pi*Rational(5, 13)))*cos(pi*Rational(80, 13)) - 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(5, 13)) + - 2*log(sin(pi*Rational(4, 13)))*cos(pi/13) + pi*cot(pi*Rational(5, 13))/2 - 2*log(sin(pi/13))*cos(pi*Rational(3, 13)) + + Rational(2422020029, 702257080)) + + Heoe = harmonic(ne + po/qe) + Aeoe = (-log(20) + 2*(Rational(1, 4) + sqrt(5)/4)*log(Rational(-1, 4) + sqrt(5)/4) + + 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8))) + + 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8))) + + 2*(-sqrt(5)/4 + Rational(1, 4))*log(Rational(1, 4) + sqrt(5)/4) + + Rational(11818877030, 4286604231) + pi*sqrt(2*sqrt(5) + 5)/2) + + Heoo = harmonic(ne + po/qo) + Aeoo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(54, 13)) + 2*log(sin(pi*Rational(4, 13)))*cos(pi*Rational(6, 13)) + + 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(108, 13)) - 2*log(sin(pi*Rational(5, 13)))*cos(pi/13) + - 2*log(sin(pi/13))*cos(pi*Rational(5, 13)) + pi*cot(pi*Rational(4, 13))/2 + - 2*log(sin(pi*Rational(2, 13)))*cos(pi*Rational(3, 13)) + Rational(11669332571, 3628714320)) + + Hoee = harmonic(no + pe/qe) + Aoee = (-log(10) + 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8))) + + 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8))) + + pi*sqrt(2*sqrt(5)/5 + 1)/2 + Rational(779405, 277704)) + + Hoeo = harmonic(no + pe/qo) + Aoeo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(4, 13)) + 2*log(sin(pi*Rational(2, 13)))*cos(pi*Rational(32, 13)) + + 2*log(sin(pi*Rational(5, 13)))*cos(pi*Rational(80, 13)) - 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(5, 13)) + - 2*log(sin(pi*Rational(4, 13)))*cos(pi/13) + pi*cot(pi*Rational(5, 13))/2 + - 2*log(sin(pi/13))*cos(pi*Rational(3, 13)) + Rational(53857323, 16331560)) + + Hooe = harmonic(no + po/qe) + Aooe = (-log(20) + 2*(Rational(1, 4) + sqrt(5)/4)*log(Rational(-1, 4) + sqrt(5)/4) + + 2*(Rational(-1, 4) + sqrt(5)/4)*log(sqrt(-sqrt(5)/8 + Rational(5, 8))) + + 2*(-sqrt(5)/4 - Rational(1, 4))*log(sqrt(sqrt(5)/8 + Rational(5, 8))) + + 2*(-sqrt(5)/4 + Rational(1, 4))*log(Rational(1, 4) + sqrt(5)/4) + + Rational(486853480, 186374097) + pi*sqrt(2*sqrt(5) + 5)/2) + + Hooo = harmonic(no + po/qo) + Aooo = (-log(26) + 2*log(sin(pi*Rational(3, 13)))*cos(pi*Rational(54, 13)) + 2*log(sin(pi*Rational(4, 13)))*cos(pi*Rational(6, 13)) + + 2*log(sin(pi*Rational(6, 13)))*cos(pi*Rational(108, 13)) - 2*log(sin(pi*Rational(5, 13)))*cos(pi/13) + - 2*log(sin(pi/13))*cos(pi*Rational(5, 13)) + pi*cot(pi*Rational(4, 13))/2 + - 2*log(sin(pi*Rational(2, 13)))*cos(3*pi/13) + Rational(383693479, 125128080)) + + H = [Heee, Heeo, Heoe, Heoo, Hoee, Hoeo, Hooe, Hooo] + A = [Aeee, Aeeo, Aeoe, Aeoo, Aoee, Aoeo, Aooe, Aooo] + for h, a in zip(H, A): + e = expand_func(h).doit() + assert cancel(e/a) == 1 + assert abs(h.n() - a.n()) < 1e-12 + + +def test_harmonic_evalf(): + assert str(harmonic(1.5).evalf(n=10)) == '1.280372306' + assert str(harmonic(1.5, 2).evalf(n=10)) == '1.154576311' # issue 7443 + assert str(harmonic(4.0, -3).evalf(n=10)) == '100.0000000' + assert str(harmonic(7.0, 1.0).evalf(n=10)) == '2.592857143' + assert str(harmonic(1, pi).evalf(n=10)) == '1.000000000' + assert str(harmonic(2, pi).evalf(n=10)) == '1.113314732' + assert str(harmonic(1000.0, pi).evalf(n=10)) == '1.176241563' + assert str(harmonic(I).evalf(n=10)) == '0.6718659855 + 1.076674047*I' + assert str(harmonic(I, I).evalf(n=10)) == '-0.3970915266 + 1.9629689*I' + + assert harmonic(-1.0, 1).evalf() is S.NaN + assert harmonic(-2.0, 2.0).evalf() is S.NaN + +def test_harmonic_rewrite(): + from sympy.functions.elementary.piecewise import Piecewise + n = Symbol("n") + m = Symbol("m", integer=True, positive=True) + x1 = Symbol("x1", positive=True) + x2 = Symbol("x2", negative=True) + + assert harmonic(n).rewrite(digamma) == polygamma(0, n + 1) + EulerGamma + assert harmonic(n).rewrite(trigamma) == polygamma(0, n + 1) + EulerGamma + assert harmonic(n).rewrite(polygamma) == polygamma(0, n + 1) + EulerGamma + + assert harmonic(n,3).rewrite(polygamma) == polygamma(2, n + 1)/2 - polygamma(2, 1)/2 + assert isinstance(harmonic(n,m).rewrite(polygamma), Piecewise) + + assert expand_func(harmonic(n+4)) == harmonic(n) + 1/(n + 4) + 1/(n + 3) + 1/(n + 2) + 1/(n + 1) + assert expand_func(harmonic(n-4)) == harmonic(n) - 1/(n - 1) - 1/(n - 2) - 1/(n - 3) - 1/n + + assert harmonic(n, m).rewrite("tractable") == harmonic(n, m).rewrite(polygamma) + assert harmonic(n, x1).rewrite("tractable") == harmonic(n, x1) + assert harmonic(n, x1 + 1).rewrite("tractable") == zeta(x1 + 1) - zeta(x1 + 1, n + 1) + assert harmonic(n, x2).rewrite("tractable") == zeta(x2) - zeta(x2, n + 1) + + _k = Dummy("k") + assert harmonic(n).rewrite(Sum).dummy_eq(Sum(1/_k, (_k, 1, n))) + assert harmonic(n, m).rewrite(Sum).dummy_eq(Sum(_k**(-m), (_k, 1, n))) + + +def test_harmonic_calculus(): + y = Symbol("y", positive=True) + z = Symbol("z", negative=True) + assert harmonic(x, 1).limit(x, 0) == 0 + assert harmonic(x, y).limit(x, 0) == 0 + assert harmonic(x, 1).series(x, y, 2) == \ + harmonic(y) + (x - y)*zeta(2, y + 1) + O((x - y)**2, (x, y)) + assert limit(harmonic(x, y), x, oo) == harmonic(oo, y) + assert limit(harmonic(x, y + 1), x, oo) == zeta(y + 1) + assert limit(harmonic(x, y - 1), x, oo) == harmonic(oo, y - 1) + assert limit(harmonic(x, z), x, oo) == Limit(harmonic(x, z), x, oo, dir='-') + assert limit(harmonic(x, z + 1), x, oo) == oo + assert limit(harmonic(x, z + 2), x, oo) == harmonic(oo, z + 2) + assert limit(harmonic(x, z - 1), x, oo) == Limit(harmonic(x, z - 1), x, oo, dir='-') + + +def test_euler(): + assert euler(0) == 1 + assert euler(1) == 0 + assert euler(2) == -1 + assert euler(3) == 0 + assert euler(4) == 5 + assert euler(6) == -61 + assert euler(8) == 1385 + + assert euler(20, evaluate=False) != 370371188237525 + + n = Symbol('n', integer=True) + assert euler(n) != -1 + assert euler(n).subs(n, 2) == -1 + + assert euler(-1) == S.Pi / 2 + assert euler(-1, 1) == 2*log(2) + assert euler(-2).evalf() == (2*S.Catalan).evalf() + assert euler(-3).evalf() == (S.Pi**3 / 16).evalf() + assert str(euler(2.3).evalf(n=10)) == '-1.052850274' + assert str(euler(1.2, 3.4).evalf(n=10)) == '3.575613489' + assert str(euler(I).evalf(n=10)) == '1.248446443 - 0.7675445124*I' + assert str(euler(I, I).evalf(n=10)) == '0.04812930469 + 0.01052411008*I' + + assert euler(20).evalf() == 370371188237525.0 + assert euler(20, evaluate=False).evalf() == 370371188237525.0 + + assert euler(n).rewrite(Sum) == euler(n) + n = Symbol('n', integer=True, nonnegative=True) + assert euler(2*n + 1).rewrite(Sum) == 0 + _j = Dummy('j') + _k = Dummy('k') + assert euler(2*n).rewrite(Sum).dummy_eq( + I*Sum((-1)**_j*2**(-_k)*I**(-_k)*(-2*_j + _k)**(2*n + 1)* + binomial(_k, _j)/_k, (_j, 0, _k), (_k, 1, 2*n + 1))) + + +def test_euler_odd(): + n = Symbol('n', odd=True, positive=True) + assert euler(n) == 0 + n = Symbol('n', odd=True) + assert euler(n) != 0 + + +def test_euler_polynomials(): + assert euler(0, x) == 1 + assert euler(1, x) == x - S.Half + assert euler(2, x) == x**2 - x + assert euler(3, x) == x**3 - (3*x**2)/2 + Rational(1, 4) + m = Symbol('m') + assert isinstance(euler(m, x), euler) + from sympy.core.numbers import Float + A = Float('-0.46237208575048694923364757452876131e8') # from Maple + B = euler(19, S.Pi).evalf(32) + assert abs((A - B)/A) < 1e-31 + z = Float(0.1) + Float(0.2)*I + expected = Float(-3126.54721663773 ) + Float(565.736261497056) * I + assert abs(euler(13, z) - expected) < 1e-10 + + +def test_euler_polynomial_rewrite(): + m = Symbol('m') + A = euler(m, x).rewrite('Sum') + assert A.subs({m:3, x:5}).doit() == euler(3, 5) + + +def test_catalan(): + n = Symbol('n', integer=True) + m = Symbol('m', integer=True, positive=True) + k = Symbol('k', integer=True, nonnegative=True) + p = Symbol('p', nonnegative=True) + + catalans = [1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786] + for i, c in enumerate(catalans): + assert catalan(i) == c + assert catalan(n).rewrite(factorial).subs(n, i) == c + assert catalan(n).rewrite(Product).subs(n, i).doit() == c + + assert unchanged(catalan, x) + assert catalan(2*x).rewrite(binomial) == binomial(4*x, 2*x)/(2*x + 1) + assert catalan(S.Half).rewrite(gamma) == 8/(3*pi) + assert catalan(S.Half).rewrite(factorial).rewrite(gamma) ==\ + 8 / (3 * pi) + assert catalan(3*x).rewrite(gamma) == 4**( + 3*x)*gamma(3*x + S.Half)/(sqrt(pi)*gamma(3*x + 2)) + assert catalan(x).rewrite(hyper) == hyper((-x + 1, -x), (2,), 1) + + assert catalan(n).rewrite(factorial) == factorial(2*n) / (factorial(n + 1) + * factorial(n)) + assert isinstance(catalan(n).rewrite(Product), catalan) + assert isinstance(catalan(m).rewrite(Product), Product) + + assert diff(catalan(x), x) == (polygamma( + 0, x + S.Half) - polygamma(0, x + 2) + log(4))*catalan(x) + + assert catalan(x).evalf() == catalan(x) + c = catalan(S.Half).evalf() + assert str(c) == '0.848826363156775' + c = catalan(I).evalf(3) + assert str((re(c), im(c))) == '(0.398, -0.0209)' + + # Assumptions + assert catalan(p).is_positive is True + assert catalan(k).is_integer is True + assert catalan(m+3).is_composite is True + + +def test_genocchi(): + genocchis = [0, -1, -1, 0, 1, 0, -3, 0, 17] + for n, g in enumerate(genocchis): + assert genocchi(n) == g + + m = Symbol('m', integer=True) + n = Symbol('n', integer=True, positive=True) + assert unchanged(genocchi, m) + assert genocchi(2*n + 1) == 0 + gn = 2 * (1 - 2**n) * bernoulli(n) + assert genocchi(n).rewrite(bernoulli).factor() == gn.factor() + gnx = 2 * (bernoulli(n, x) - 2**n * bernoulli(n, (x+1) / 2)) + assert genocchi(n, x).rewrite(bernoulli).factor() == gnx.factor() + assert genocchi(2 * n).is_odd + assert genocchi(2 * n).is_even is False + assert genocchi(2 * n + 1).is_even + assert genocchi(n).is_integer + assert genocchi(4 * n).is_positive + # these are the only 2 prime Genocchi numbers + assert genocchi(6, evaluate=False).is_prime == S(-3).is_prime + assert genocchi(8, evaluate=False).is_prime + assert genocchi(4 * n + 2).is_negative + assert genocchi(4 * n + 1).is_negative is False + assert genocchi(4 * n - 2).is_negative + + g0 = genocchi(0, evaluate=False) + assert g0.is_positive is False + assert g0.is_negative is False + assert g0.is_even is True + assert g0.is_odd is False + + assert genocchi(0, x) == 0 + assert genocchi(1, x) == -1 + assert genocchi(2, x) == 1 - 2*x + assert genocchi(3, x) == 3*x - 3*x**2 + assert genocchi(4, x) == -1 + 6*x**2 - 4*x**3 + y = Symbol("y") + assert genocchi(5, (x+y)**100) == -5*(x+y)**400 + 10*(x+y)**300 - 5*(x+y)**100 + + assert str(genocchi(5.0, 4.0).evalf(n=10)) == '-660.0000000' + assert str(genocchi(Rational(5, 4)).evalf(n=10)) == '-1.104286457' + assert str(genocchi(-2).evalf(n=10)) == '3.606170709' + assert str(genocchi(1.3, 3.7).evalf(n=10)) == '-1.847375373' + assert str(genocchi(I, 1.0).evalf(n=10)) == '-0.3161917278 - 1.45311955*I' + + n = Symbol('n') + assert genocchi(n, x).rewrite(dirichlet_eta) == -2*n * dirichlet_eta(1-n, x) + + +def test_andre(): + nums = [1, 1, 1, 2, 5, 16, 61, 272, 1385, 7936, 50521] + for n, a in enumerate(nums): + assert andre(n) == a + assert andre(S.Infinity) == S.Infinity + assert andre(-1) == -log(2) + assert andre(-2) == -2*S.Catalan + assert andre(-3) == 3*zeta(3)/16 + assert andre(-5) == -15*zeta(5)/256 + # In fact andre(-2*n) is related to the Dirichlet *beta* function + # at 2*n, but SymPy doesn't implement that (or general L-functions) + assert unchanged(andre, -4) + + n = Symbol('n', integer=True, nonnegative=True) + assert unchanged(andre, n) + assert andre(n).is_integer is True + assert andre(n).is_positive is True + + assert str(andre(10, evaluate=False).evalf(n=10)) == '50521.00000' + assert str(andre(-1, evaluate=False).evalf(n=10)) == '-0.6931471806' + assert str(andre(-2, evaluate=False).evalf(n=10)) == '-1.831931188' + assert str(andre(-4, evaluate=False).evalf(n=10)) == '1.977889103' + assert str(andre(I, evaluate=False).evalf(n=10)) == '2.378417833 + 0.6343322845*I' + + assert andre(x).rewrite(polylog) == \ + (-I)**(x+1) * polylog(-x, I) + I**(x+1) * polylog(-x, -I) + assert andre(x).rewrite(zeta) == \ + 2 * gamma(x+1) / (2*pi)**(x+1) * \ + (zeta(x+1, Rational(1,4)) - cos(pi*x) * zeta(x+1, Rational(3,4))) + + +@nocache_fail +def test_partition(): + partition_nums = [1, 1, 2, 3, 5, 7, 11, 15, 22] + for n, p in enumerate(partition_nums): + assert partition(n) == p + + x = Symbol('x') + y = Symbol('y', real=True) + m = Symbol('m', integer=True) + n = Symbol('n', integer=True, negative=True) + p = Symbol('p', integer=True, nonnegative=True) + assert partition(m).is_integer + assert not partition(m).is_negative + assert partition(m).is_nonnegative + assert partition(n).is_zero + assert partition(p).is_positive + assert partition(x).subs(x, 7) == 15 + assert partition(y).subs(y, 8) == 22 + raises(TypeError, lambda: partition(Rational(5, 4))) + assert partition(9, evaluate=False) % 5 == 0 + assert partition(5*m + 4) % 5 == 0 + assert partition(47, evaluate=False) % 7 == 0 + assert partition(7*m + 5) % 7 == 0 + assert partition(50, evaluate=False) % 11 == 0 + assert partition(11*m + 6) % 11 == 0 + + +def test_divisor_sigma(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: divisor_sigma(m)) + raises(TypeError, lambda: divisor_sigma(4.5)) + raises(TypeError, lambda: divisor_sigma(1, m)) + raises(TypeError, lambda: divisor_sigma(1, 4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: divisor_sigma(m)) + raises(ValueError, lambda: divisor_sigma(0)) + m = Symbol('m', negative=True) + raises(ValueError, lambda: divisor_sigma(1, m)) + raises(ValueError, lambda: divisor_sigma(1, -1)) + + # special case + p = Symbol('p', prime=True) + k = Symbol('k', integer=True) + assert divisor_sigma(p, 1) == p + 1 + assert divisor_sigma(p, k) == p**k + 1 + + # property + n = Symbol('n', integer=True, positive=True) + assert divisor_sigma(n).is_integer is True + assert divisor_sigma(n).is_positive is True + + # symbolic + k = Symbol('k', integer=True, zero=False) + assert divisor_sigma(4, k) == 2**(2*k) + 2**k + 1 + assert divisor_sigma(6, k) == (2**k + 1) * (3**k + 1) + + # Integer + assert divisor_sigma(23450) == 50592 + assert divisor_sigma(23450, 0) == 24 + assert divisor_sigma(23450, 1) == 50592 + assert divisor_sigma(23450, 2) == 730747500 + assert divisor_sigma(23450, 3) == 14666785333344 + + +def test_udivisor_sigma(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: udivisor_sigma(m)) + raises(TypeError, lambda: udivisor_sigma(4.5)) + raises(TypeError, lambda: udivisor_sigma(1, m)) + raises(TypeError, lambda: udivisor_sigma(1, 4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: udivisor_sigma(m)) + raises(ValueError, lambda: udivisor_sigma(0)) + m = Symbol('m', negative=True) + raises(ValueError, lambda: udivisor_sigma(1, m)) + raises(ValueError, lambda: udivisor_sigma(1, -1)) + + # special case + p = Symbol('p', prime=True) + k = Symbol('k', integer=True) + assert udivisor_sigma(p, 1) == p + 1 + assert udivisor_sigma(p, k) == p**k + 1 + + # property + n = Symbol('n', integer=True, positive=True) + assert udivisor_sigma(n).is_integer is True + assert udivisor_sigma(n).is_positive is True + + # Integer + A034444 = [1, 2, 2, 2, 2, 4, 2, 2, 2, 4, 2, 4, 2, 4, 4, 2, 2, 4, 2, 4, + 4, 4, 2, 4, 2, 4, 2, 4, 2, 8, 2, 2, 4, 4, 4, 4, 2, 4, 4, 4, + 2, 8, 2, 4, 4, 4, 2, 4, 2, 4, 4, 4, 2, 4, 4, 4, 4, 4, 2, 8] + for n, val in enumerate(A034444, 1): + assert udivisor_sigma(n, 0) == val + A034448 = [1, 3, 4, 5, 6, 12, 8, 9, 10, 18, 12, 20, 14, 24, 24, 17, 18, + 30, 20, 30, 32, 36, 24, 36, 26, 42, 28, 40, 30, 72, 32, 33, + 48, 54, 48, 50, 38, 60, 56, 54, 42, 96, 44, 60, 60, 72, 48] + for n, val in enumerate(A034448, 1): + assert udivisor_sigma(n, 1) == val + A034676 = [1, 5, 10, 17, 26, 50, 50, 65, 82, 130, 122, 170, 170, 250, + 260, 257, 290, 410, 362, 442, 500, 610, 530, 650, 626, 850, + 730, 850, 842, 1300, 962, 1025, 1220, 1450, 1300, 1394, 1370] + for n, val in enumerate(A034676, 1): + assert udivisor_sigma(n, 2) == val + + +def test_legendre_symbol(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: legendre_symbol(m, 3)) + raises(TypeError, lambda: legendre_symbol(4.5, 3)) + raises(TypeError, lambda: legendre_symbol(1, m)) + raises(TypeError, lambda: legendre_symbol(1, 4.5)) + m = Symbol('m', prime=False) + raises(ValueError, lambda: legendre_symbol(1, m)) + raises(ValueError, lambda: legendre_symbol(1, 6)) + m = Symbol('m', odd=False) + raises(ValueError, lambda: legendre_symbol(1, m)) + raises(ValueError, lambda: legendre_symbol(1, 2)) + + # special case + p = Symbol('p', prime=True) + k = Symbol('k', integer=True) + assert legendre_symbol(p*k, p) == 0 + assert legendre_symbol(1, p) == 1 + + # property + n = Symbol('n') + m = Symbol('m') + assert legendre_symbol(m, n).is_integer is True + assert legendre_symbol(m, n).is_prime is False + + # Integer + assert legendre_symbol(5, 11) == 1 + assert legendre_symbol(25, 41) == 1 + assert legendre_symbol(67, 101) == -1 + assert legendre_symbol(0, 13) == 0 + assert legendre_symbol(9, 3) == 0 + + +def test_jacobi_symbol(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: jacobi_symbol(m, 3)) + raises(TypeError, lambda: jacobi_symbol(4.5, 3)) + raises(TypeError, lambda: jacobi_symbol(1, m)) + raises(TypeError, lambda: jacobi_symbol(1, 4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: jacobi_symbol(1, m)) + raises(ValueError, lambda: jacobi_symbol(1, -6)) + m = Symbol('m', odd=False) + raises(ValueError, lambda: jacobi_symbol(1, m)) + raises(ValueError, lambda: jacobi_symbol(1, 2)) + + # special case + p = Symbol('p', integer=True) + k = Symbol('k', integer=True) + assert jacobi_symbol(p*k, p) == 0 + assert jacobi_symbol(1, p) == 1 + assert jacobi_symbol(1, 1) == 1 + assert jacobi_symbol(0, 1) == 1 + + # property + n = Symbol('n') + m = Symbol('m') + assert jacobi_symbol(m, n).is_integer is True + assert jacobi_symbol(m, n).is_prime is False + + # Integer + assert jacobi_symbol(25, 41) == 1 + assert jacobi_symbol(-23, 83) == -1 + assert jacobi_symbol(3, 9) == 0 + assert jacobi_symbol(42, 97) == -1 + assert jacobi_symbol(3, 5) == -1 + assert jacobi_symbol(7, 9) == 1 + assert jacobi_symbol(0, 3) == 0 + assert jacobi_symbol(0, 1) == 1 + assert jacobi_symbol(2, 1) == 1 + assert jacobi_symbol(1, 3) == 1 + + +def test_kronecker_symbol(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: kronecker_symbol(m, 3)) + raises(TypeError, lambda: kronecker_symbol(4.5, 3)) + raises(TypeError, lambda: kronecker_symbol(1, m)) + raises(TypeError, lambda: kronecker_symbol(1, 4.5)) + + # special case + p = Symbol('p', integer=True) + assert kronecker_symbol(1, p) == 1 + assert kronecker_symbol(1, 1) == 1 + assert kronecker_symbol(0, 1) == 1 + + # property + n = Symbol('n') + m = Symbol('m') + assert kronecker_symbol(m, n).is_integer is True + assert kronecker_symbol(m, n).is_prime is False + + # Integer + for n in range(3, 10, 2): + for a in range(-n, n): + val = kronecker_symbol(a, n) + assert val == jacobi_symbol(a, n) + minus = kronecker_symbol(a, -n) + if a < 0: + assert -minus == val + else: + assert minus == val + even = kronecker_symbol(a, 2 * n) + if a % 2 == 0: + assert even == 0 + elif a % 8 in [1, 7]: + assert even == val + else: + assert -even == val + assert kronecker_symbol(1, 0) == kronecker_symbol(-1, 0) == 1 + assert kronecker_symbol(0, 0) == 0 + + +def test_mobius(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: mobius(m)) + raises(TypeError, lambda: mobius(4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: mobius(m)) + raises(ValueError, lambda: mobius(-3)) + + # special case + p = Symbol('p', prime=True) + assert mobius(p) == -1 + + # property + n = Symbol('n', integer=True, positive=True) + assert mobius(n).is_integer is True + assert mobius(n).is_prime is False + + # symbolic + n = Symbol('n', integer=True, positive=True) + k = Symbol('k', integer=True, positive=True) + assert mobius(n**2) == 0 + assert mobius(4*n) == 0 + assert isinstance(mobius(n**k), mobius) + assert mobius(n**(k+1)) == 0 + assert isinstance(mobius(3**k), mobius) + assert mobius(3**(k+1)) == 0 + m = Symbol('m') + assert isinstance(mobius(4*m), mobius) + + # Integer + assert mobius(13*7) == 1 + assert mobius(1) == 1 + assert mobius(13*7*5) == -1 + assert mobius(13**2) == 0 + A008683 = [1, -1, -1, 0, -1, 1, -1, 0, 0, 1, -1, 0, -1, 1, 1, 0, -1, 0, + -1, 0, 1, 1, -1, 0, 0, 1, 0, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, + 1, 1, 0, -1, -1, -1, 0, 0, 1, -1, 0, 0, 0, 1, 0, -1, 0, 1, 0] + for n, val in enumerate(A008683, 1): + assert mobius(n) == val + + +def test_primenu(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: primenu(m)) + raises(TypeError, lambda: primenu(4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: primenu(m)) + raises(ValueError, lambda: primenu(0)) + + # special case + p = Symbol('p', prime=True) + assert primenu(p) == 1 + + # property + n = Symbol('n', integer=True, positive=True) + assert primenu(n).is_integer is True + assert primenu(n).is_nonnegative is True + + # Integer + assert primenu(7*13) == 2 + assert primenu(2*17*19) == 3 + assert primenu(2**3 * 17 * 19**2) == 3 + A001221 = [0, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, 2, + 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 1, 2, 2, 2, 2] + for n, val in enumerate(A001221, 1): + assert primenu(n) == val + + +def test_primeomega(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: primeomega(m)) + raises(TypeError, lambda: primeomega(4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: primeomega(m)) + raises(ValueError, lambda: primeomega(0)) + + # special case + p = Symbol('p', prime=True) + assert primeomega(p) == 1 + + # property + n = Symbol('n', integer=True, positive=True) + assert primeomega(n).is_integer is True + assert primeomega(n).is_nonnegative is True + + # Integer + assert primeomega(7*13) == 2 + assert primeomega(2*17*19) == 3 + assert primeomega(2**3 * 17 * 19**2) == 6 + A001222 = [0, 1, 1, 2, 1, 2, 1, 3, 2, 2, 1, 3, 1, 2, 2, 4, 1, 3, + 1, 3, 2, 2, 1, 4, 2, 2, 3, 3, 1, 3, 1, 5, 2, 2, 2, 4] + for n, val in enumerate(A001222, 1): + assert primeomega(n) == val + + +def test_totient(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: totient(m)) + raises(TypeError, lambda: totient(4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: totient(m)) + raises(ValueError, lambda: totient(0)) + + # special case + p = Symbol('p', prime=True) + assert totient(p) == p - 1 + + # property + n = Symbol('n', integer=True, positive=True) + assert totient(n).is_integer is True + assert totient(n).is_positive is True + + # Integer + assert totient(7*13) == totient(factorint(7*13)) == (7-1)*(13-1) + assert totient(2*17*19) == totient(factorint(2*17*19)) == (17-1)*(19-1) + assert totient(2**3 * 17 * 19**2) == totient({2: 3, 17: 1, 19: 2}) == 2**2 * (17-1) * 19*(19-1) + A000010 = [1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, + 6, 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, + 20, 16, 24, 12, 36, 18, 24, 16, 40, 12, 42, 20, 24, 22] + for n, val in enumerate(A000010, 1): + assert totient(n) == val + + +def test_reduced_totient(): + # error + m = Symbol('m', integer=False) + raises(TypeError, lambda: reduced_totient(m)) + raises(TypeError, lambda: reduced_totient(4.5)) + m = Symbol('m', positive=False) + raises(ValueError, lambda: reduced_totient(m)) + raises(ValueError, lambda: reduced_totient(0)) + + # special case + p = Symbol('p', prime=True) + assert reduced_totient(p) == p - 1 + + # property + n = Symbol('n', integer=True, positive=True) + assert reduced_totient(n).is_integer is True + assert reduced_totient(n).is_positive is True + + # Integer + assert reduced_totient(7*13) == reduced_totient(factorint(7*13)) == 12 + assert reduced_totient(2*17*19) == reduced_totient(factorint(2*17*19)) == 144 + assert reduced_totient(2**2 * 11) == reduced_totient({2: 2, 11: 1}) == 10 + assert reduced_totient(2**3 * 17 * 19**2) == reduced_totient({2: 3, 17: 1, 19: 2}) == 2736 + A002322 = [1, 1, 2, 2, 4, 2, 6, 2, 6, 4, 10, 2, 12, 6, 4, 4, 16, 6, + 18, 4, 6, 10, 22, 2, 20, 12, 18, 6, 28, 4, 30, 8, 10, 16, + 12, 6, 36, 18, 12, 4, 40, 6, 42, 10, 12, 22, 46, 4, 42] + for n, val in enumerate(A002322, 1): + assert reduced_totient(n) == val + + +def test_primepi(): + # error + z = Symbol('z', real=False) + raises(TypeError, lambda: primepi(z)) + raises(TypeError, lambda: primepi(I)) + + # property + n = Symbol('n', integer=True, positive=True) + assert primepi(n).is_integer is True + assert primepi(n).is_nonnegative is True + + # infinity + assert primepi(oo) == oo + assert primepi(-oo) == 0 + + # symbol + x = Symbol('x') + assert isinstance(primepi(x), primepi) + + # Integer + assert primepi(0) == 0 + A000720 = [0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15] + for n, val in enumerate(A000720, 1): + assert primepi(n) == primepi(n + 0.5) == val + + +def test__nT(): + assert [_nT(i, j) for i in range(5) for j in range(i + 2)] == [ + 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 1, 0] + check = [_nT(10, i) for i in range(11)] + assert check == [0, 1, 5, 8, 9, 7, 5, 3, 2, 1, 1] + assert all(type(i) is int for i in check) + assert _nT(10, 5) == 7 + assert _nT(100, 98) == 2 + assert _nT(100, 100) == 1 + assert _nT(10, 3) == 8 + + +def test_nC_nP_nT(): + from sympy.utilities.iterables import ( + multiset_permutations, multiset_combinations, multiset_partitions, + partitions, subsets, permutations) + from sympy.functions.combinatorial.numbers import ( + nP, nC, nT, stirling, _stirling1, _stirling2, _multiset_histogram, _AOP_product) + + from sympy.combinatorics.permutations import Permutation + from sympy.core.random import choice + + c = string.ascii_lowercase + for i in range(100): + s = ''.join(choice(c) for i in range(7)) + u = len(s) == len(set(s)) + try: + tot = 0 + for i in range(8): + check = nP(s, i) + tot += check + assert len(list(multiset_permutations(s, i))) == check + if u: + assert nP(len(s), i) == check + assert nP(s) == tot + except AssertionError: + print(s, i, 'failed perm test') + raise ValueError() + + for i in range(100): + s = ''.join(choice(c) for i in range(7)) + u = len(s) == len(set(s)) + try: + tot = 0 + for i in range(8): + check = nC(s, i) + tot += check + assert len(list(multiset_combinations(s, i))) == check + if u: + assert nC(len(s), i) == check + assert nC(s) == tot + if u: + assert nC(len(s)) == tot + except AssertionError: + print(s, i, 'failed combo test') + raise ValueError() + + for i in range(1, 10): + tot = 0 + for j in range(1, i + 2): + check = nT(i, j) + assert check.is_Integer + tot += check + assert sum(1 for p in partitions(i, j, size=True) if p[0] == j) == check + assert nT(i) == tot + + for i in range(1, 10): + tot = 0 + for j in range(1, i + 2): + check = nT(range(i), j) + tot += check + assert len(list(multiset_partitions(list(range(i)), j))) == check + assert nT(range(i)) == tot + + for i in range(100): + s = ''.join(choice(c) for i in range(7)) + u = len(s) == len(set(s)) + try: + tot = 0 + for i in range(1, 8): + check = nT(s, i) + tot += check + assert len(list(multiset_partitions(s, i))) == check + if u: + assert nT(range(len(s)), i) == check + if u: + assert nT(range(len(s))) == tot + assert nT(s) == tot + except AssertionError: + print(s, i, 'failed partition test') + raise ValueError() + + # tests for Stirling numbers of the first kind that are not tested in the + # above + assert [stirling(9, i, kind=1) for i in range(11)] == [ + 0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1, 0] + perms = list(permutations(range(4))) + assert [sum(1 for p in perms if Permutation(p).cycles == i) + for i in range(5)] == [0, 6, 11, 6, 1] == [ + stirling(4, i, kind=1) for i in range(5)] + # http://oeis.org/A008275 + assert [stirling(n, k, signed=1) + for n in range(10) for k in range(1, n + 1)] == [ + 1, -1, + 1, 2, -3, + 1, -6, 11, -6, + 1, 24, -50, 35, -10, + 1, -120, 274, -225, 85, -15, + 1, 720, -1764, 1624, -735, 175, -21, + 1, -5040, 13068, -13132, 6769, -1960, 322, -28, + 1, 40320, -109584, 118124, -67284, 22449, -4536, 546, -36, 1] + # https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind + assert [stirling(n, k, kind=1) + for n in range(10) for k in range(n+1)] == [ + 1, + 0, 1, + 0, 1, 1, + 0, 2, 3, 1, + 0, 6, 11, 6, 1, + 0, 24, 50, 35, 10, 1, + 0, 120, 274, 225, 85, 15, 1, + 0, 720, 1764, 1624, 735, 175, 21, 1, + 0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1, + 0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1] + # https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind + assert [stirling(n, k, kind=2) + for n in range(10) for k in range(n+1)] == [ + 1, + 0, 1, + 0, 1, 1, + 0, 1, 3, 1, + 0, 1, 7, 6, 1, + 0, 1, 15, 25, 10, 1, + 0, 1, 31, 90, 65, 15, 1, + 0, 1, 63, 301, 350, 140, 21, 1, + 0, 1, 127, 966, 1701, 1050, 266, 28, 1, + 0, 1, 255, 3025, 7770, 6951, 2646, 462, 36, 1] + assert stirling(3, 4, kind=1) == stirling(3, 4, kind=1) == 0 + raises(ValueError, lambda: stirling(-2, 2)) + + # Assertion that the return type is SymPy Integer. + assert isinstance(_stirling1(6, 3), Integer) + assert isinstance(_stirling2(6, 3), Integer) + + def delta(p): + if len(p) == 1: + return oo + return min(abs(i[0] - i[1]) for i in subsets(p, 2)) + parts = multiset_partitions(range(5), 3) + d = 2 + assert (sum(1 for p in parts if all(delta(i) >= d for i in p)) == + stirling(5, 3, d=d) == 7) + + # other coverage tests + assert nC('abb', 2) == nC('aab', 2) == 2 + assert nP(3, 3, replacement=True) == nP('aabc', 3, replacement=True) == 27 + assert nP(3, 4) == 0 + assert nP('aabc', 5) == 0 + assert nC(4, 2, replacement=True) == nC('abcdd', 2, replacement=True) == \ + len(list(multiset_combinations('aabbccdd', 2))) == 10 + assert nC('abcdd') == sum(nC('abcdd', i) for i in range(6)) == 24 + assert nC(list('abcdd'), 4) == 4 + assert nT('aaaa') == nT(4) == len(list(partitions(4))) == 5 + assert nT('aaab') == len(list(multiset_partitions('aaab'))) == 7 + assert nC('aabb'*3, 3) == 4 # aaa, bbb, abb, baa + assert dict(_AOP_product((4,1,1,1))) == { + 0: 1, 1: 4, 2: 7, 3: 8, 4: 8, 5: 7, 6: 4, 7: 1} + # the following was the first t that showed a problem in a previous form of + # the function, so it's not as random as it may appear + t = (3, 9, 4, 6, 6, 5, 5, 2, 10, 4) + assert sum(_AOP_product(t)[i] for i in range(55)) == 58212000 + raises(ValueError, lambda: _multiset_histogram({1:'a'})) + + +def test_PR_14617(): + from sympy.functions.combinatorial.numbers import nT + for n in (0, []): + for k in (-1, 0, 1): + if k == 0: + assert nT(n, k) == 1 + else: + assert nT(n, k) == 0 + + +def test_issue_8496(): + n = Symbol("n") + k = Symbol("k") + + raises(TypeError, lambda: catalan(n, k)) + + +def test_issue_8601(): + n = Symbol('n', integer=True, negative=True) + + assert catalan(n - 1) is S.Zero + assert catalan(Rational(-1, 2)) is S.ComplexInfinity + assert catalan(-S.One) == Rational(-1, 2) + c1 = catalan(-5.6).evalf() + assert str(c1) == '6.93334070531408e-5' + c2 = catalan(-35.4).evalf() + assert str(c2) == '-4.14189164517449e-24' + + +def test_motzkin(): + assert motzkin.is_motzkin(4) == True + assert motzkin.is_motzkin(9) == True + assert motzkin.is_motzkin(10) == False + assert motzkin.find_motzkin_numbers_in_range(10,200) == [21, 51, 127] + assert motzkin.find_motzkin_numbers_in_range(10,400) == [21, 51, 127, 323] + assert motzkin.find_motzkin_numbers_in_range(10,1600) == [21, 51, 127, 323, 835] + assert motzkin.find_first_n_motzkins(5) == [1, 1, 2, 4, 9] + assert motzkin.find_first_n_motzkins(7) == [1, 1, 2, 4, 9, 21, 51] + assert motzkin.find_first_n_motzkins(10) == [1, 1, 2, 4, 9, 21, 51, 127, 323, 835] + raises(ValueError, lambda: motzkin.eval(77.58)) + raises(ValueError, lambda: motzkin.eval(-8)) + raises(ValueError, lambda: motzkin.find_motzkin_numbers_in_range(-2,7)) + raises(ValueError, lambda: motzkin.find_motzkin_numbers_in_range(13,7)) + raises(ValueError, lambda: motzkin.find_first_n_motzkins(112.8)) + + +def test_nD_derangements(): + from sympy.utilities.iterables import (partitions, multiset, + multiset_derangements, multiset_permutations) + from sympy.functions.combinatorial.numbers import nD + + got = [] + for i in partitions(8, k=4): + s = [] + it = 0 + for k, v in i.items(): + for i in range(v): + s.extend([it]*k) + it += 1 + ms = multiset(s) + c1 = sum(1 for i in multiset_permutations(s) if + all(i != j for i, j in zip(i, s))) + assert c1 == nD(ms) == nD(ms, 0) == nD(ms, 1) + v = [tuple(i) for i in multiset_derangements(s)] + c2 = len(v) + assert c2 == len(set(v)) + assert c1 == c2 + got.append(c1) + assert got == [1, 4, 6, 12, 24, 24, 61, 126, 315, 780, 297, 772, + 2033, 5430, 14833] + + assert nD('1112233456', brute=True) == nD('1112233456') == 16356 + assert nD('') == nD([]) == nD({}) == 0 + assert nD({1: 0}) == 0 + raises(ValueError, lambda: nD({1: -1})) + assert nD('112') == 0 + assert nD(i='112') == 0 + assert [nD(n=i) for i in range(6)] == [0, 0, 1, 2, 9, 44] + assert nD((i for i in range(4))) == nD('0123') == 9 + assert nD(m=(i for i in range(4))) == 3 + assert nD(m={0: 1, 1: 1, 2: 1, 3: 1}) == 3 + assert nD(m=[0, 1, 2, 3]) == 3 + raises(TypeError, lambda: nD(m=0)) + raises(TypeError, lambda: nD(-1)) + assert nD({-1: 1, -2: 1}) == 1 + assert nD(m={0: 3}) == 0 + raises(ValueError, lambda: nD(i='123', n=3)) + raises(ValueError, lambda: nD(i='123', m=(1,2))) + raises(ValueError, lambda: nD(n=0, m=(1,2))) + raises(ValueError, lambda: nD({1: -1})) + raises(ValueError, lambda: nD(m={-1: 1, 2: 1})) + raises(ValueError, lambda: nD(m={1: -1, 2: 1})) + raises(ValueError, lambda: nD(m=[-1, 2])) + raises(TypeError, lambda: nD({1: x})) + raises(TypeError, lambda: nD(m={1: x})) + raises(TypeError, lambda: nD(m={x: 1})) + + +def test_deprecated_ntheory_symbolic_functions(): + from sympy.testing.pytest import warns_deprecated_sympy + + with warns_deprecated_sympy(): + assert not carmichael.is_carmichael(3) + with warns_deprecated_sympy(): + assert carmichael.find_carmichael_numbers_in_range(10, 20) == [] + with warns_deprecated_sympy(): + assert carmichael.find_first_n_carmichaels(1) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__init__.py b/phivenv/Lib/site-packages/sympy/functions/elementary/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..78034e72ef2ed722c3ae685a87cf4df618a982b0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/__init__.py @@ -0,0 +1 @@ +# Stub __init__.py for sympy.functions.elementary diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca62d65be679b0b892dba2a28daf6f5066553d64 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/_trigonometric_special.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/_trigonometric_special.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..053c01b8ddf612caa1358542399189f633954c3e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/_trigonometric_special.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/complexes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/complexes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3d8dfbb6e7f4bb54e633b15ab54a7a5ec35865c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/complexes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/exponential.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/exponential.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d5446840fc10b9f0e316987e11bcc5832597c27 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/exponential.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/hyperbolic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/hyperbolic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5bf7047231fceabdfb96549d28394ee6b38b3650 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/hyperbolic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/integers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/integers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6eb5b93a7e2a4b7261f8261eeaeddcec99dadce4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/integers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/miscellaneous.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/miscellaneous.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c3d076aa139dafe95352a81dd230976d8d2c78e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/miscellaneous.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/piecewise.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/piecewise.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1197af024a7ddeb4c91e2ac32809e97d02d2d8b9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/piecewise.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/trigonometric.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/trigonometric.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..968c833226ec3c4edffdec65be47808356de2cdd Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/__pycache__/trigonometric.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/_trigonometric_special.py b/phivenv/Lib/site-packages/sympy/functions/elementary/_trigonometric_special.py new file mode 100644 index 0000000000000000000000000000000000000000..fdf8c9d06241b46e791afe76836ea33e6d4fb1c8 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/_trigonometric_special.py @@ -0,0 +1,261 @@ +r"""A module for special angle formulas for trigonometric functions + +TODO +==== + +This module should be developed in the future to contain direct square root +representation of + +.. math + F(\frac{n}{m} \pi) + +for every + +- $m \in \{ 3, 5, 17, 257, 65537 \}$ +- $n \in \mathbb{N}$, $0 \le n < m$ +- $F \in \{\sin, \cos, \tan, \csc, \sec, \cot\}$ + +Without multi-step rewrites +(e.g. $\tan \to \cos/\sin \to \cos/\sqrt \to \ sqrt$) +or using chebyshev identities +(e.g. $\cos \to \cos + \cos^2 + \cdots \to \sqrt{} + \sqrt{}^2 + \cdots $), +which are trivial to implement in sympy, +and had used to give overly complicated expressions. + +The reference can be found below, if anyone may need help implementing them. + +References +========== + +.. [*] Gottlieb, Christian. (1999). The Simple and straightforward construction + of the regular 257-gon. The Mathematical Intelligencer. 21. 31-37. + 10.1007/BF03024829. +.. [*] https://resources.wolframcloud.com/FunctionRepository/resources/Cos2PiOverFermatPrime +""" +from __future__ import annotations +from typing import Callable +from functools import reduce +from sympy.core.expr import Expr +from sympy.core.singleton import S +from sympy.core.intfunc import igcdex +from sympy.core.numbers import Integer +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.core.cache import cacheit + + +def migcdex(*x: int) -> tuple[tuple[int, ...], int]: + r"""Compute extended gcd for multiple integers. + + Explanation + =========== + + Given the integers $x_1, \cdots, x_n$ and + an extended gcd for multiple arguments are defined as a solution + $(y_1, \cdots, y_n), g$ for the diophantine equation + $x_1 y_1 + \cdots + x_n y_n = g$ such that + $g = \gcd(x_1, \cdots, x_n)$. + + Examples + ======== + + >>> from sympy.functions.elementary._trigonometric_special import migcdex + >>> migcdex() + ((), 0) + >>> migcdex(4) + ((1,), 4) + >>> migcdex(4, 6) + ((-1, 1), 2) + >>> migcdex(6, 10, 15) + ((1, 1, -1), 1) + """ + if not x: + return (), 0 + + if len(x) == 1: + return (1,), x[0] + + if len(x) == 2: + u, v, h = igcdex(x[0], x[1]) + return (u, v), h + + y, g = migcdex(*x[1:]) + u, v, h = igcdex(x[0], g) + return (u, *(v * i for i in y)), h + + +def ipartfrac(*denoms: int) -> tuple[int, ...]: + r"""Compute the partial fraction decomposition. + + Explanation + =========== + + Given a rational number $\frac{1}{q_1 \cdots q_n}$ where all + $q_1, \cdots, q_n$ are pairwise coprime, + + A partial fraction decomposition is defined as + + .. math:: + \frac{1}{q_1 \cdots q_n} = \frac{p_1}{q_1} + \cdots + \frac{p_n}{q_n} + + And it can be derived from solving the following diophantine equation for + the $p_1, \cdots, p_n$ + + .. math:: + 1 = p_1 \prod_{i \ne 1}q_i + \cdots + p_n \prod_{i \ne n}q_i + + Where $q_1, \cdots, q_n$ being pairwise coprime implies + $\gcd(\prod_{i \ne 1}q_i, \cdots, \prod_{i \ne n}q_i) = 1$, + which guarantees the existence of the solution. + + It is sufficient to compute partial fraction decomposition only + for numerator $1$ because partial fraction decomposition for any + $\frac{n}{q_1 \cdots q_n}$ can be easily computed by multiplying + the result by $n$ afterwards. + + Parameters + ========== + + denoms : int + The pairwise coprime integer denominators $q_i$ which defines the + rational number $\frac{1}{q_1 \cdots q_n}$ + + Returns + ======= + + tuple[int, ...] + The list of numerators which semantically corresponds to $p_i$ of the + partial fraction decomposition + $\frac{1}{q_1 \cdots q_n} = \frac{p_1}{q_1} + \cdots + \frac{p_n}{q_n}$ + + Examples + ======== + + >>> from sympy import Rational, Mul + >>> from sympy.functions.elementary._trigonometric_special import ipartfrac + + >>> denoms = 2, 3, 5 + >>> numers = ipartfrac(2, 3, 5) + >>> numers + (1, 7, -14) + + >>> Rational(1, Mul(*denoms)) + 1/30 + >>> out = 0 + >>> for n, d in zip(numers, denoms): + ... out += Rational(n, d) + >>> out + 1/30 + """ + if not denoms: + return () + + def mul(x: int, y: int) -> int: + return x * y + + denom = reduce(mul, denoms) + a = [denom // x for x in denoms] + h, _ = migcdex(*a) + return h + + +def fermat_coords(n: int) -> list[int] | None: + """If n can be factored in terms of Fermat primes with + multiplicity of each being 1, return those primes, else + None + """ + primes = [] + for p in [3, 5, 17, 257, 65537]: + quotient, remainder = divmod(n, p) + if remainder == 0: + n = quotient + primes.append(p) + if n == 1: + return primes + return None + + +@cacheit +def cos_3() -> Expr: + r"""Computes $\cos \frac{\pi}{3}$ in square roots""" + return S.Half + + +@cacheit +def cos_5() -> Expr: + r"""Computes $\cos \frac{\pi}{5}$ in square roots""" + return (sqrt(5) + 1) / 4 + + +@cacheit +def cos_17() -> Expr: + r"""Computes $\cos \frac{\pi}{17}$ in square roots""" + return sqrt( + (15 + sqrt(17)) / 32 + sqrt(2) * (sqrt(17 - sqrt(17)) + + sqrt(sqrt(2) * (-8 * sqrt(17 + sqrt(17)) - (1 - sqrt(17)) + * sqrt(17 - sqrt(17))) + 6 * sqrt(17) + 34)) / 32) + + +@cacheit +def cos_257() -> Expr: + r"""Computes $\cos \frac{\pi}{257}$ in square roots + + References + ========== + + .. [*] https://math.stackexchange.com/questions/516142/how-does-cos2-pi-257-look-like-in-real-radicals + .. [*] https://r-knott.surrey.ac.uk/Fibonacci/simpleTrig.html + """ + def f1(a: Expr, b: Expr) -> tuple[Expr, Expr]: + return (a + sqrt(a**2 + b)) / 2, (a - sqrt(a**2 + b)) / 2 + + def f2(a: Expr, b: Expr) -> Expr: + return (a - sqrt(a**2 + b))/2 + + t1, t2 = f1(S.NegativeOne, Integer(256)) + z1, z3 = f1(t1, Integer(64)) + z2, z4 = f1(t2, Integer(64)) + y1, y5 = f1(z1, 4*(5 + t1 + 2*z1)) + y6, y2 = f1(z2, 4*(5 + t2 + 2*z2)) + y3, y7 = f1(z3, 4*(5 + t1 + 2*z3)) + y8, y4 = f1(z4, 4*(5 + t2 + 2*z4)) + x1, x9 = f1(y1, -4*(t1 + y1 + y3 + 2*y6)) + x2, x10 = f1(y2, -4*(t2 + y2 + y4 + 2*y7)) + x3, x11 = f1(y3, -4*(t1 + y3 + y5 + 2*y8)) + x4, x12 = f1(y4, -4*(t2 + y4 + y6 + 2*y1)) + x5, x13 = f1(y5, -4*(t1 + y5 + y7 + 2*y2)) + x6, x14 = f1(y6, -4*(t2 + y6 + y8 + 2*y3)) + x15, x7 = f1(y7, -4*(t1 + y7 + y1 + 2*y4)) + x8, x16 = f1(y8, -4*(t2 + y8 + y2 + 2*y5)) + v1 = f2(x1, -4*(x1 + x2 + x3 + x6)) + v2 = f2(x2, -4*(x2 + x3 + x4 + x7)) + v3 = f2(x8, -4*(x8 + x9 + x10 + x13)) + v4 = f2(x9, -4*(x9 + x10 + x11 + x14)) + v5 = f2(x10, -4*(x10 + x11 + x12 + x15)) + v6 = f2(x16, -4*(x16 + x1 + x2 + x5)) + u1 = -f2(-v1, -4*(v2 + v3)) + u2 = -f2(-v4, -4*(v5 + v6)) + w1 = -2*f2(-u1, -4*u2) + return sqrt(sqrt(2)*sqrt(w1 + 4)/8 + S.Half) + + +def cos_table() -> dict[int, Callable[[], Expr]]: + r"""Lazily evaluated table for $\cos \frac{\pi}{n}$ in square roots for + $n \in \{3, 5, 17, 257, 65537\}$. + + Notes + ===== + + 65537 is the only other known Fermat prime and it is nearly impossible to + build in the current SymPy due to performance issues. + + References + ========== + + https://r-knott.surrey.ac.uk/Fibonacci/simpleTrig.html + """ + return { + 3: cos_3, + 5: cos_5, + 17: cos_17, + 257: cos_257 + } diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__init__.py b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd9e40f4496d600dcbbdef5b2e6c281c4db28f31 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__pycache__/bench_exp.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__pycache__/bench_exp.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c72337fb36f08c32306e654a00fe3560ad8b0bbb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/__pycache__/bench_exp.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/bench_exp.py b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/bench_exp.py new file mode 100644 index 0000000000000000000000000000000000000000..fa18d29f87bcd249baec1d278a030fa7a133c3ba --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/benchmarks/bench_exp.py @@ -0,0 +1,11 @@ +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import exp + +x, y = symbols('x,y') + +e = exp(2*x) +q = exp(3*x) + + +def timeit_exp_subs(): + e.subs(q, y) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/complexes.py b/phivenv/Lib/site-packages/sympy/functions/elementary/complexes.py new file mode 100644 index 0000000000000000000000000000000000000000..dd837e4e242057050370f38c4b4e9c26aa5d06c9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/complexes.py @@ -0,0 +1,1492 @@ +from __future__ import annotations + +from sympy.core import S, Add, Mul, sympify, Symbol, Dummy, Basic +from sympy.core.expr import Expr +from sympy.core.exprtools import factor_terms +from sympy.core.function import (DefinedFunction, Derivative, ArgumentIndexError, + AppliedUndef, expand_mul, PoleError) +from sympy.core.logic import fuzzy_not, fuzzy_or +from sympy.core.numbers import pi, I, oo +from sympy.core.power import Pow +from sympy.core.relational import Eq +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise + +############################################################################### +######################### REAL and IMAGINARY PARTS ############################ +############################################################################### + + +class re(DefinedFunction): + """ + Returns real part of expression. This function performs only + elementary analysis and so it will fail to decompose properly + more complicated expressions. If completely simplified result + is needed then use ``Basic.as_real_imag()`` or perform complex + expansion on instance of this function. + + Examples + ======== + + >>> from sympy import re, im, I, E, symbols + >>> x, y = symbols('x y', real=True) + >>> re(2*E) + 2*E + >>> re(2*I + 17) + 17 + >>> re(2*I) + 0 + >>> re(im(x) + x*I + 2) + 2 + >>> re(5 + I + 2) + 7 + + Parameters + ========== + + arg : Expr + Real or complex expression. + + Returns + ======= + + expr : Expr + Real part of expression. + + See Also + ======== + + im + """ + + args: tuple[Expr] + + is_extended_real = True + unbranched = True # implicitly works on the projection to C + _singularities = True # non-holomorphic + + @classmethod + def eval(cls, arg): + if arg is S.NaN: + return S.NaN + elif arg is S.ComplexInfinity: + return S.NaN + elif arg.is_extended_real: + return arg + elif arg.is_imaginary or (I*arg).is_extended_real: + return S.Zero + elif arg.is_Matrix: + return arg.as_real_imag()[0] + elif arg.is_Function and isinstance(arg, conjugate): + return re(arg.args[0]) + else: + + included, reverted, excluded = [], [], [] + args = Add.make_args(arg) + for term in args: + coeff = term.as_coefficient(I) + + if coeff is not None: + if not coeff.is_extended_real: + reverted.append(coeff) + elif not term.has(I) and term.is_extended_real: + excluded.append(term) + else: + # Try to do some advanced expansion. If + # impossible, don't try to do re(arg) again + # (because this is what we are trying to do now). + real_imag = term.as_real_imag(ignore=arg) + if real_imag: + excluded.append(real_imag[0]) + else: + included.append(term) + + if len(args) != len(included): + a, b, c = (Add(*xs) for xs in [included, reverted, excluded]) + + return cls(a) - im(b) + c + + def as_real_imag(self, deep=True, **hints): + """ + Returns the real number with a zero imaginary part. + + """ + return (self, S.Zero) + + def _eval_derivative(self, x): + if x.is_extended_real or self.args[0].is_extended_real: + return re(Derivative(self.args[0], x, evaluate=True)) + if x.is_imaginary or self.args[0].is_imaginary: + return -I \ + * im(Derivative(self.args[0], x, evaluate=True)) + + def _eval_rewrite_as_im(self, arg, **kwargs): + return self.args[0] - I*im(self.args[0]) + + def _eval_is_algebraic(self): + return self.args[0].is_algebraic + + def _eval_is_zero(self): + # is_imaginary implies nonzero + return fuzzy_or([self.args[0].is_imaginary, self.args[0].is_zero]) + + def _eval_is_finite(self): + if self.args[0].is_finite: + return True + + def _eval_is_complex(self): + if self.args[0].is_finite: + return True + + +class im(DefinedFunction): + """ + Returns imaginary part of expression. This function performs only + elementary analysis and so it will fail to decompose properly more + complicated expressions. If completely simplified result is needed then + use ``Basic.as_real_imag()`` or perform complex expansion on instance of + this function. + + Examples + ======== + + >>> from sympy import re, im, E, I + >>> from sympy.abc import x, y + >>> im(2*E) + 0 + >>> im(2*I + 17) + 2 + >>> im(x*I) + re(x) + >>> im(re(x) + y) + im(y) + >>> im(2 + 3*I) + 3 + + Parameters + ========== + + arg : Expr + Real or complex expression. + + Returns + ======= + + expr : Expr + Imaginary part of expression. + + See Also + ======== + + re + """ + + args: tuple[Expr] + + is_extended_real = True + unbranched = True # implicitly works on the projection to C + _singularities = True # non-holomorphic + + @classmethod + def eval(cls, arg): + if arg is S.NaN: + return S.NaN + elif arg is S.ComplexInfinity: + return S.NaN + elif arg.is_extended_real: + return S.Zero + elif arg.is_imaginary or (I*arg).is_extended_real: + return -I * arg + elif arg.is_Matrix: + return arg.as_real_imag()[1] + elif arg.is_Function and isinstance(arg, conjugate): + return -im(arg.args[0]) + else: + included, reverted, excluded = [], [], [] + args = Add.make_args(arg) + for term in args: + coeff = term.as_coefficient(I) + + if coeff is not None: + if not coeff.is_extended_real: + reverted.append(coeff) + else: + excluded.append(coeff) + elif term.has(I) or not term.is_extended_real: + # Try to do some advanced expansion. If + # impossible, don't try to do im(arg) again + # (because this is what we are trying to do now). + real_imag = term.as_real_imag(ignore=arg) + if real_imag: + excluded.append(real_imag[1]) + else: + included.append(term) + + if len(args) != len(included): + a, b, c = (Add(*xs) for xs in [included, reverted, excluded]) + + return cls(a) + re(b) + c + + def as_real_imag(self, deep=True, **hints): + """ + Return the imaginary part with a zero real part. + + """ + return (self, S.Zero) + + def _eval_derivative(self, x): + if x.is_extended_real or self.args[0].is_extended_real: + return im(Derivative(self.args[0], x, evaluate=True)) + if x.is_imaginary or self.args[0].is_imaginary: + return -I \ + * re(Derivative(self.args[0], x, evaluate=True)) + + def _eval_rewrite_as_re(self, arg, **kwargs): + return -I*(self.args[0] - re(self.args[0])) + + def _eval_is_algebraic(self): + return self.args[0].is_algebraic + + def _eval_is_zero(self): + return self.args[0].is_extended_real + + def _eval_is_finite(self): + if self.args[0].is_finite: + return True + + def _eval_is_complex(self): + if self.args[0].is_finite: + return True + +############################################################################### +############### SIGN, ABSOLUTE VALUE, ARGUMENT and CONJUGATION ################ +############################################################################### + +class sign(DefinedFunction): + """ + Returns the complex sign of an expression: + + Explanation + =========== + + If the expression is real the sign will be: + + * $1$ if expression is positive + * $0$ if expression is equal to zero + * $-1$ if expression is negative + + If the expression is imaginary the sign will be: + + * $I$ if im(expression) is positive + * $-I$ if im(expression) is negative + + Otherwise an unevaluated expression will be returned. When evaluated, the + result (in general) will be ``cos(arg(expr)) + I*sin(arg(expr))``. + + Examples + ======== + + >>> from sympy import sign, I + + >>> sign(-1) + -1 + >>> sign(0) + 0 + >>> sign(-3*I) + -I + >>> sign(1 + I) + sign(1 + I) + >>> _.evalf() + 0.707106781186548 + 0.707106781186548*I + + Parameters + ========== + + arg : Expr + Real or imaginary expression. + + Returns + ======= + + expr : Expr + Complex sign of expression. + + See Also + ======== + + Abs, conjugate + """ + + is_complex = True + _singularities = True + + def doit(self, **hints): + s = super().doit() + if s == self and self.args[0].is_zero is False: + return self.args[0] / Abs(self.args[0]) + return s + + @classmethod + def eval(cls, arg): + # handle what we can + if arg.is_Mul: + c, args = arg.as_coeff_mul() + unk = [] + s = sign(c) + for a in args: + if a.is_extended_negative: + s = -s + elif a.is_extended_positive: + pass + else: + if a.is_imaginary: + ai = im(a) + if ai.is_comparable: # i.e. a = I*real + s *= I + if ai.is_extended_negative: + # can't use sign(ai) here since ai might not be + # a Number + s = -s + else: + unk.append(a) + else: + unk.append(a) + if c is S.One and len(unk) == len(args): + return None + return s * cls(arg._new_rawargs(*unk)) + if arg is S.NaN: + return S.NaN + if arg.is_zero: # it may be an Expr that is zero + return S.Zero + if arg.is_extended_positive: + return S.One + if arg.is_extended_negative: + return S.NegativeOne + if arg.is_Function: + if isinstance(arg, sign): + return arg + if arg.is_imaginary: + if arg.is_Pow and arg.exp is S.Half: + # we catch this because non-trivial sqrt args are not expanded + # e.g. sqrt(1-sqrt(2)) --x--> to I*sqrt(sqrt(2) - 1) + return I + arg2 = -I * arg + if arg2.is_extended_positive: + return I + if arg2.is_extended_negative: + return -I + + def _eval_Abs(self): + if fuzzy_not(self.args[0].is_zero): + return S.One + + def _eval_conjugate(self): + return sign(conjugate(self.args[0])) + + def _eval_derivative(self, x): + if self.args[0].is_extended_real: + from sympy.functions.special.delta_functions import DiracDelta + return 2 * Derivative(self.args[0], x, evaluate=True) \ + * DiracDelta(self.args[0]) + elif self.args[0].is_imaginary: + from sympy.functions.special.delta_functions import DiracDelta + return 2 * Derivative(self.args[0], x, evaluate=True) \ + * DiracDelta(-I * self.args[0]) + + def _eval_is_nonnegative(self): + if self.args[0].is_nonnegative: + return True + + def _eval_is_nonpositive(self): + if self.args[0].is_nonpositive: + return True + + def _eval_is_imaginary(self): + return self.args[0].is_imaginary + + def _eval_is_integer(self): + return self.args[0].is_extended_real + + def _eval_is_zero(self): + return self.args[0].is_zero + + def _eval_power(self, other): + if ( + fuzzy_not(self.args[0].is_zero) and + other.is_integer and + other.is_even + ): + return S.One + + def _eval_nseries(self, x, n, logx, cdir=0): + arg0 = self.args[0] + x0 = arg0.subs(x, 0) + if x0 != 0: + return self.func(x0) + if cdir != 0: + cdir = arg0.dir(x, cdir) + return -S.One if re(cdir) < 0 else S.One + + def _eval_rewrite_as_Piecewise(self, arg, **kwargs): + if arg.is_extended_real: + return Piecewise((1, arg > 0), (-1, arg < 0), (0, True)) + + def _eval_rewrite_as_Heaviside(self, arg, **kwargs): + from sympy.functions.special.delta_functions import Heaviside + if arg.is_extended_real: + return Heaviside(arg) * 2 - 1 + + def _eval_rewrite_as_Abs(self, arg, **kwargs): + return Piecewise((0, Eq(arg, 0)), (arg / Abs(arg), True)) + + def _eval_simplify(self, **kwargs): + return self.func(factor_terms(self.args[0])) # XXX include doit? + + +class Abs(DefinedFunction): + """ + Return the absolute value of the argument. + + Explanation + =========== + + This is an extension of the built-in function ``abs()`` to accept symbolic + values. If you pass a SymPy expression to the built-in ``abs()``, it will + pass it automatically to ``Abs()``. + + Examples + ======== + + >>> from sympy import Abs, Symbol, S, I + >>> Abs(-1) + 1 + >>> x = Symbol('x', real=True) + >>> Abs(-x) + Abs(x) + >>> Abs(x**2) + x**2 + >>> abs(-x) # The Python built-in + Abs(x) + >>> Abs(3*x + 2*I) + sqrt(9*x**2 + 4) + >>> Abs(8*I) + 8 + + Note that the Python built-in will return either an Expr or int depending on + the argument:: + + >>> type(abs(-1)) + <... 'int'> + >>> type(abs(S.NegativeOne)) + + + Abs will always return a SymPy object. + + Parameters + ========== + + arg : Expr + Real or complex expression. + + Returns + ======= + + expr : Expr + Absolute value returned can be an expression or integer depending on + input arg. + + See Also + ======== + + sign, conjugate + """ + + args: tuple[Expr] + + is_extended_real = True + is_extended_negative = False + is_extended_nonnegative = True + unbranched = True + _singularities = True # non-holomorphic + + def fdiff(self, argindex=1): + """ + Get the first derivative of the argument to Abs(). + + """ + if argindex == 1: + return sign(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + from sympy.simplify.simplify import signsimp + + if hasattr(arg, '_eval_Abs'): + obj = arg._eval_Abs() + if obj is not None: + return obj + if not isinstance(arg, Expr): + raise TypeError("Bad argument type for Abs(): %s" % type(arg)) + + # handle what we can + arg = signsimp(arg, evaluate=False) + n, d = arg.as_numer_denom() + if d.free_symbols and not n.free_symbols: + return cls(n)/cls(d) + + if arg.is_Mul: + known = [] + unk = [] + for t in arg.args: + if t.is_Pow and t.exp.is_integer and t.exp.is_negative: + bnew = cls(t.base) + if isinstance(bnew, cls): + unk.append(t) + else: + known.append(Pow(bnew, t.exp)) + else: + tnew = cls(t) + if isinstance(tnew, cls): + unk.append(t) + else: + known.append(tnew) + known = Mul(*known) + unk = cls(Mul(*unk), evaluate=False) if unk else S.One + return known*unk + if arg is S.NaN: + return S.NaN + if arg is S.ComplexInfinity: + return oo + from sympy.functions.elementary.exponential import exp, log + + if arg.is_Pow: + base, exponent = arg.as_base_exp() + if base.is_extended_real: + if exponent.is_integer: + if exponent.is_even: + return arg + if base is S.NegativeOne: + return S.One + return Abs(base)**exponent + if base.is_extended_nonnegative: + return base**re(exponent) + if base.is_extended_negative: + return (-base)**re(exponent)*exp(-pi*im(exponent)) + return + elif not base.has(Symbol): # complex base + # express base**exponent as exp(exponent*log(base)) + a, b = log(base).as_real_imag() + z = a + I*b + return exp(re(exponent*z)) + if isinstance(arg, exp): + return exp(re(arg.args[0])) + if isinstance(arg, AppliedUndef): + if arg.is_positive: + return arg + elif arg.is_negative: + return -arg + return + if arg.is_Add and arg.has(oo, S.NegativeInfinity): + if any(a.is_infinite for a in arg.as_real_imag()): + return oo + if arg.is_zero: + return S.Zero + if arg.is_extended_nonnegative: + return arg + if arg.is_extended_nonpositive: + return -arg + if arg.is_imaginary: + arg2 = -I * arg + if arg2.is_extended_nonnegative: + return arg2 + if arg.is_extended_real: + return + # reject result if all new conjugates are just wrappers around + # an expression that was already in the arg + conj = signsimp(arg.conjugate(), evaluate=False) + new_conj = conj.atoms(conjugate) - arg.atoms(conjugate) + if new_conj and all(arg.has(i.args[0]) for i in new_conj): + return + if arg != conj and arg != -conj: + ignore = arg.atoms(Abs) + abs_free_arg = arg.xreplace({i: Dummy(real=True) for i in ignore}) + unk = [a for a in abs_free_arg.free_symbols if a.is_extended_real is None] + if not unk or not all(conj.has(conjugate(u)) for u in unk): + return sqrt(expand_mul(arg*conj)) + + def _eval_is_real(self): + if self.args[0].is_finite: + return True + + def _eval_is_integer(self): + if self.args[0].is_extended_real: + return self.args[0].is_integer + + def _eval_is_extended_nonzero(self): + return fuzzy_not(self._args[0].is_zero) + + def _eval_is_zero(self): + return self._args[0].is_zero + + def _eval_is_extended_positive(self): + return fuzzy_not(self._args[0].is_zero) + + def _eval_is_rational(self): + if self.args[0].is_extended_real: + return self.args[0].is_rational + + def _eval_is_even(self): + if self.args[0].is_extended_real: + return self.args[0].is_even + + def _eval_is_odd(self): + if self.args[0].is_extended_real: + return self.args[0].is_odd + + def _eval_is_algebraic(self): + return self.args[0].is_algebraic + + def _eval_power(self, exponent): + if self.args[0].is_extended_real and exponent.is_integer: + if exponent.is_even: + return self.args[0]**exponent + elif exponent is not S.NegativeOne and exponent.is_Integer: + return self.args[0]**(exponent - 1)*self + return + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.functions.elementary.exponential import log + direction = self.args[0].leadterm(x)[0] + if direction.has(log(x)): + direction = direction.subs(log(x), logx) + s = self.args[0]._eval_nseries(x, n=n, logx=logx) + return (sign(direction)*s).expand() + + def _eval_derivative(self, x): + if self.args[0].is_extended_real or self.args[0].is_imaginary: + return Derivative(self.args[0], x, evaluate=True) \ + * sign(conjugate(self.args[0])) + rv = (re(self.args[0]) * Derivative(re(self.args[0]), x, + evaluate=True) + im(self.args[0]) * Derivative(im(self.args[0]), + x, evaluate=True)) / Abs(self.args[0]) + return rv.rewrite(sign) + + def _eval_rewrite_as_Heaviside(self, arg, **kwargs): + # Note this only holds for real arg (since Heaviside is not defined + # for complex arguments). + from sympy.functions.special.delta_functions import Heaviside + if arg.is_extended_real: + return arg*(Heaviside(arg) - Heaviside(-arg)) + + def _eval_rewrite_as_Piecewise(self, arg, **kwargs): + if arg.is_extended_real: + return Piecewise((arg, arg >= 0), (-arg, True)) + elif arg.is_imaginary: + return Piecewise((I*arg, I*arg >= 0), (-I*arg, True)) + + def _eval_rewrite_as_sign(self, arg, **kwargs): + return arg/sign(arg) + + def _eval_rewrite_as_conjugate(self, arg, **kwargs): + return sqrt(arg*conjugate(arg)) + + +class arg(DefinedFunction): + r""" + Returns the argument (in radians) of a complex number. The argument is + evaluated in consistent convention with ``atan2`` where the branch-cut is + taken along the negative real axis and ``arg(z)`` is in the interval + $(-\pi,\pi]$. For a positive number, the argument is always 0; the + argument of a negative number is $\pi$; and the argument of 0 + is undefined and returns ``nan``. So the ``arg`` function will never nest + greater than 3 levels since at the 4th application, the result must be + nan; for a real number, nan is returned on the 3rd application. + + Examples + ======== + + >>> from sympy import arg, I, sqrt, Dummy + >>> from sympy.abc import x + >>> arg(2.0) + 0 + >>> arg(I) + pi/2 + >>> arg(sqrt(2) + I*sqrt(2)) + pi/4 + >>> arg(sqrt(3)/2 + I/2) + pi/6 + >>> arg(4 + 3*I) + atan(3/4) + >>> arg(0.8 + 0.6*I) + 0.643501108793284 + >>> arg(arg(arg(arg(x)))) + nan + >>> real = Dummy(real=True) + >>> arg(arg(arg(real))) + nan + + Parameters + ========== + + arg : Expr + Real or complex expression. + + Returns + ======= + + value : Expr + Returns arc tangent of arg measured in radians. + + """ + + is_extended_real = True + is_real = True + is_finite = True + _singularities = True # non-holomorphic + + @classmethod + def eval(cls, arg): + a = arg + for i in range(3): + if isinstance(a, cls): + a = a.args[0] + else: + if i == 2 and a.is_extended_real: + return S.NaN + break + else: + return S.NaN + from sympy.functions.elementary.exponential import exp, exp_polar + if isinstance(arg, exp_polar): + return periodic_argument(arg, oo) + elif isinstance(arg, exp): + i_ = im(arg.args[0]) + if i_.is_comparable: + i_ %= 2*S.Pi + if i_ > S.Pi: + i_ -= 2*S.Pi + return i_ + + if not arg.is_Atom: + c, arg_ = factor_terms(arg).as_coeff_Mul() + if arg_.is_Mul: + arg_ = Mul(*[a if (sign(a) not in (-1, 1)) else + sign(a) for a in arg_.args]) + arg_ = sign(c)*arg_ + else: + arg_ = arg + if any(i.is_extended_positive is None for i in arg_.atoms(AppliedUndef)): + return + from sympy.functions.elementary.trigonometric import atan2 + x, y = arg_.as_real_imag() + rv = atan2(y, x) + if rv.is_number: + return rv + if arg_ != arg: + return cls(arg_, evaluate=False) + + def _eval_derivative(self, t): + x, y = self.args[0].as_real_imag() + return (x * Derivative(y, t, evaluate=True) - y * + Derivative(x, t, evaluate=True)) / (x**2 + y**2) + + def _eval_rewrite_as_atan2(self, arg, **kwargs): + from sympy.functions.elementary.trigonometric import atan2 + x, y = self.args[0].as_real_imag() + return atan2(y, x) + + def _eval_as_leading_term(self, x, logx, cdir): + arg0 = self.args[0] + t = Dummy('t', positive=True) + if cdir == 0: + cdir = 1 + z = arg0.subs(x, cdir*t) + if z.is_positive: + return S.Zero + elif z.is_negative: + return S.Pi + else: + raise PoleError("Cannot expand %s around 0" % (self)) + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.series.order import Order + if n <= 0: + return Order(1) + return self._eval_as_leading_term(x, logx=logx, cdir=cdir) + + +class conjugate(DefinedFunction): + """ + Returns the *complex conjugate* [1]_ of an argument. + In mathematics, the complex conjugate of a complex number + is given by changing the sign of the imaginary part. + + Thus, the conjugate of the complex number + :math:`a + ib` (where $a$ and $b$ are real numbers) is :math:`a - ib` + + Examples + ======== + + >>> from sympy import conjugate, I + >>> conjugate(2) + 2 + >>> conjugate(I) + -I + >>> conjugate(3 + 2*I) + 3 - 2*I + >>> conjugate(5 - I) + 5 + I + + Parameters + ========== + + arg : Expr + Real or complex expression. + + Returns + ======= + + arg : Expr + Complex conjugate of arg as real, imaginary or mixed expression. + + See Also + ======== + + sign, Abs + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Complex_conjugation + """ + _singularities = True # non-holomorphic + + @classmethod + def eval(cls, arg): + obj = arg._eval_conjugate() + if obj is not None: + return obj + + def inverse(self): + return conjugate + + def _eval_Abs(self): + return Abs(self.args[0], evaluate=True) + + def _eval_adjoint(self): + return transpose(self.args[0]) + + def _eval_conjugate(self): + return self.args[0] + + def _eval_derivative(self, x): + if x.is_real: + return conjugate(Derivative(self.args[0], x, evaluate=True)) + elif x.is_imaginary: + return -conjugate(Derivative(self.args[0], x, evaluate=True)) + + def _eval_transpose(self): + return adjoint(self.args[0]) + + def _eval_is_algebraic(self): + return self.args[0].is_algebraic + + +class transpose(DefinedFunction): + """ + Linear map transposition. + + Examples + ======== + + >>> from sympy import transpose, Matrix, MatrixSymbol + >>> A = MatrixSymbol('A', 25, 9) + >>> transpose(A) + A.T + >>> B = MatrixSymbol('B', 9, 22) + >>> transpose(B) + B.T + >>> transpose(A*B) + B.T*A.T + >>> M = Matrix([[4, 5], [2, 1], [90, 12]]) + >>> M + Matrix([ + [ 4, 5], + [ 2, 1], + [90, 12]]) + >>> transpose(M) + Matrix([ + [4, 2, 90], + [5, 1, 12]]) + + Parameters + ========== + + arg : Matrix + Matrix or matrix expression to take the transpose of. + + Returns + ======= + + value : Matrix + Transpose of arg. + + """ + + @classmethod + def eval(cls, arg): + obj = arg._eval_transpose() + if obj is not None: + return obj + + def _eval_adjoint(self): + return conjugate(self.args[0]) + + def _eval_conjugate(self): + return adjoint(self.args[0]) + + def _eval_transpose(self): + return self.args[0] + + +class adjoint(DefinedFunction): + """ + Conjugate transpose or Hermite conjugation. + + Examples + ======== + + >>> from sympy import adjoint, MatrixSymbol + >>> A = MatrixSymbol('A', 10, 5) + >>> adjoint(A) + Adjoint(A) + + Parameters + ========== + + arg : Matrix + Matrix or matrix expression to take the adjoint of. + + Returns + ======= + + value : Matrix + Represents the conjugate transpose or Hermite + conjugation of arg. + + """ + + @classmethod + def eval(cls, arg): + obj = arg._eval_adjoint() + if obj is not None: + return obj + obj = arg._eval_transpose() + if obj is not None: + return conjugate(obj) + + def _eval_adjoint(self): + return self.args[0] + + def _eval_conjugate(self): + return transpose(self.args[0]) + + def _eval_transpose(self): + return conjugate(self.args[0]) + + def _latex(self, printer, exp=None, *args): + arg = printer._print(self.args[0]) + tex = r'%s^{\dagger}' % arg + if exp: + tex = r'\left(%s\right)^{%s}' % (tex, exp) + return tex + + def _pretty(self, printer, *args): + from sympy.printing.pretty.stringpict import prettyForm + pform = printer._print(self.args[0], *args) + if printer._use_unicode: + pform = pform**prettyForm('\N{DAGGER}') + else: + pform = pform**prettyForm('+') + return pform + +############################################################################### +############### HANDLING OF POLAR NUMBERS ##################################### +############################################################################### + + +class polar_lift(DefinedFunction): + """ + Lift argument to the Riemann surface of the logarithm, using the + standard branch. + + Examples + ======== + + >>> from sympy import Symbol, polar_lift, I + >>> p = Symbol('p', polar=True) + >>> x = Symbol('x') + >>> polar_lift(4) + 4*exp_polar(0) + >>> polar_lift(-4) + 4*exp_polar(I*pi) + >>> polar_lift(-I) + exp_polar(-I*pi/2) + >>> polar_lift(I + 2) + polar_lift(2 + I) + + >>> polar_lift(4*x) + 4*polar_lift(x) + >>> polar_lift(4*p) + 4*p + + Parameters + ========== + + arg : Expr + Real or complex expression. + + See Also + ======== + + sympy.functions.elementary.exponential.exp_polar + periodic_argument + """ + + is_polar = True + is_comparable = False # Cannot be evalf'd. + + @classmethod + def eval(cls, arg): + from sympy.functions.elementary.complexes import arg as argument + if arg.is_number: + ar = argument(arg) + # In general we want to affirm that something is known, + # e.g. `not ar.has(argument) and not ar.has(atan)` + # but for now we will just be more restrictive and + # see that it has evaluated to one of the known values. + if ar in (0, pi/2, -pi/2, pi): + from sympy.functions.elementary.exponential import exp_polar + return exp_polar(I*ar)*abs(arg) + + if arg.is_Mul: + args = arg.args + else: + args = [arg] + included = [] + excluded = [] + positive = [] + for arg in args: + if arg.is_polar: + included += [arg] + elif arg.is_positive: + positive += [arg] + else: + excluded += [arg] + if len(excluded) < len(args): + if excluded: + return Mul(*(included + positive))*polar_lift(Mul(*excluded)) + elif included: + return Mul(*(included + positive)) + else: + from sympy.functions.elementary.exponential import exp_polar + return Mul(*positive)*exp_polar(0) + + def _eval_evalf(self, prec): + """ Careful! any evalf of polar numbers is flaky """ + return self.args[0]._eval_evalf(prec) + + def _eval_Abs(self): + return Abs(self.args[0], evaluate=True) + + +class periodic_argument(DefinedFunction): + r""" + Represent the argument on a quotient of the Riemann surface of the + logarithm. That is, given a period $P$, always return a value in + $(-P/2, P/2]$, by using $\exp(PI) = 1$. + + Examples + ======== + + >>> from sympy import exp_polar, periodic_argument + >>> from sympy import I, pi + >>> periodic_argument(exp_polar(10*I*pi), 2*pi) + 0 + >>> periodic_argument(exp_polar(5*I*pi), 4*pi) + pi + >>> from sympy import exp_polar, periodic_argument + >>> from sympy import I, pi + >>> periodic_argument(exp_polar(5*I*pi), 2*pi) + pi + >>> periodic_argument(exp_polar(5*I*pi), 3*pi) + -pi + >>> periodic_argument(exp_polar(5*I*pi), pi) + 0 + + Parameters + ========== + + ar : Expr + A polar number. + + period : Expr + The period $P$. + + See Also + ======== + + sympy.functions.elementary.exponential.exp_polar + polar_lift : Lift argument to the Riemann surface of the logarithm + principal_branch + """ + + @classmethod + def _getunbranched(cls, ar): + from sympy.functions.elementary.exponential import exp_polar, log + if ar.is_Mul: + args = ar.args + else: + args = [ar] + unbranched = 0 + for a in args: + if not a.is_polar: + unbranched += arg(a) + elif isinstance(a, exp_polar): + unbranched += a.exp.as_real_imag()[1] + elif a.is_Pow: + re, im = a.exp.as_real_imag() + unbranched += re*unbranched_argument( + a.base) + im*log(abs(a.base)) + elif isinstance(a, polar_lift): + unbranched += arg(a.args[0]) + else: + return None + return unbranched + + @classmethod + def eval(cls, ar, period): + # Our strategy is to evaluate the argument on the Riemann surface of the + # logarithm, and then reduce. + # NOTE evidently this means it is a rather bad idea to use this with + # period != 2*pi and non-polar numbers. + if not period.is_extended_positive: + return None + if period == oo and isinstance(ar, principal_branch): + return periodic_argument(*ar.args) + if isinstance(ar, polar_lift) and period >= 2*pi: + return periodic_argument(ar.args[0], period) + if ar.is_Mul: + newargs = [x for x in ar.args if not x.is_positive] + if len(newargs) != len(ar.args): + return periodic_argument(Mul(*newargs), period) + unbranched = cls._getunbranched(ar) + if unbranched is None: + return None + from sympy.functions.elementary.trigonometric import atan, atan2 + if unbranched.has(periodic_argument, atan2, atan): + return None + if period == oo: + return unbranched + if period != oo: + from sympy.functions.elementary.integers import ceiling + n = ceiling(unbranched/period - S.Half)*period + if not n.has(ceiling): + return unbranched - n + + def _eval_evalf(self, prec): + z, period = self.args + if period == oo: + unbranched = periodic_argument._getunbranched(z) + if unbranched is None: + return self + return unbranched._eval_evalf(prec) + ub = periodic_argument(z, oo)._eval_evalf(prec) + from sympy.functions.elementary.integers import ceiling + return (ub - ceiling(ub/period - S.Half)*period)._eval_evalf(prec) + + +def unbranched_argument(arg): + ''' + Returns periodic argument of arg with period as infinity. + + Examples + ======== + + >>> from sympy import exp_polar, unbranched_argument + >>> from sympy import I, pi + >>> unbranched_argument(exp_polar(15*I*pi)) + 15*pi + >>> unbranched_argument(exp_polar(7*I*pi)) + 7*pi + + See also + ======== + + periodic_argument + ''' + return periodic_argument(arg, oo) + + +class principal_branch(DefinedFunction): + """ + Represent a polar number reduced to its principal branch on a quotient + of the Riemann surface of the logarithm. + + Explanation + =========== + + This is a function of two arguments. The first argument is a polar + number `z`, and the second one a positive real number or infinity, `p`. + The result is ``z mod exp_polar(I*p)``. + + Examples + ======== + + >>> from sympy import exp_polar, principal_branch, oo, I, pi + >>> from sympy.abc import z + >>> principal_branch(z, oo) + z + >>> principal_branch(exp_polar(2*pi*I)*3, 2*pi) + 3*exp_polar(0) + >>> principal_branch(exp_polar(2*pi*I)*3*z, 2*pi) + 3*principal_branch(z, 2*pi) + + Parameters + ========== + + x : Expr + A polar number. + + period : Expr + Positive real number or infinity. + + See Also + ======== + + sympy.functions.elementary.exponential.exp_polar + polar_lift : Lift argument to the Riemann surface of the logarithm + periodic_argument + """ + + is_polar = True + is_comparable = False # cannot always be evalf'd + + @classmethod + def eval(self, x, period): + from sympy.functions.elementary.exponential import exp_polar + if isinstance(x, polar_lift): + return principal_branch(x.args[0], period) + if period == oo: + return x + ub = periodic_argument(x, oo) + barg = periodic_argument(x, period) + if ub != barg and not ub.has(periodic_argument) \ + and not barg.has(periodic_argument): + pl = polar_lift(x) + + def mr(expr): + if not isinstance(expr, Symbol): + return polar_lift(expr) + return expr + pl = pl.replace(polar_lift, mr) + # Recompute unbranched argument + ub = periodic_argument(pl, oo) + if not pl.has(polar_lift): + if ub != barg: + res = exp_polar(I*(barg - ub))*pl + else: + res = pl + if not res.is_polar and not res.has(exp_polar): + res *= exp_polar(0) + return res + + if not x.free_symbols: + c, m = x, () + else: + c, m = x.as_coeff_mul(*x.free_symbols) + others = [] + for y in m: + if y.is_positive: + c *= y + else: + others += [y] + m = tuple(others) + arg = periodic_argument(c, period) + if arg.has(periodic_argument): + return None + if arg.is_number and (unbranched_argument(c) != arg or + (arg == 0 and m != () and c != 1)): + if arg == 0: + return abs(c)*principal_branch(Mul(*m), period) + return principal_branch(exp_polar(I*arg)*Mul(*m), period)*abs(c) + if arg.is_number and ((abs(arg) < period/2) == True or arg == period/2) \ + and m == (): + return exp_polar(arg*I)*abs(c) + + def _eval_evalf(self, prec): + z, period = self.args + p = periodic_argument(z, period)._eval_evalf(prec) + if abs(p) > pi or p == -pi: + return self # Cannot evalf for this argument. + from sympy.functions.elementary.exponential import exp + return (abs(z)*exp(I*p))._eval_evalf(prec) + + +def _polarify(eq, lift, pause=False): + from sympy.integrals.integrals import Integral + if eq.is_polar: + return eq + if eq.is_number and not pause: + return polar_lift(eq) + if isinstance(eq, Symbol) and not pause and lift: + return polar_lift(eq) + elif eq.is_Atom: + return eq + elif eq.is_Add: + r = eq.func(*[_polarify(arg, lift, pause=True) for arg in eq.args]) + if lift: + return polar_lift(r) + return r + elif eq.is_Pow and eq.base == S.Exp1: + return eq.func(S.Exp1, _polarify(eq.exp, lift, pause=False)) + elif eq.is_Function: + return eq.func(*[_polarify(arg, lift, pause=False) for arg in eq.args]) + elif isinstance(eq, Integral): + # Don't lift the integration variable + func = _polarify(eq.function, lift, pause=pause) + limits = [] + for limit in eq.args[1:]: + var = _polarify(limit[0], lift=False, pause=pause) + rest = _polarify(limit[1:], lift=lift, pause=pause) + limits.append((var,) + rest) + return Integral(*((func,) + tuple(limits))) + else: + return eq.func(*[_polarify(arg, lift, pause=pause) + if isinstance(arg, Expr) else arg for arg in eq.args]) + + +def polarify(eq, subs=True, lift=False): + """ + Turn all numbers in eq into their polar equivalents (under the standard + choice of argument). + + Note that no attempt is made to guess a formal convention of adding + polar numbers, expressions like $1 + x$ will generally not be altered. + + Note also that this function does not promote ``exp(x)`` to ``exp_polar(x)``. + + If ``subs`` is ``True``, all symbols which are not already polar will be + substituted for polar dummies; in this case the function behaves much + like :func:`~.posify`. + + If ``lift`` is ``True``, both addition statements and non-polar symbols are + changed to their ``polar_lift()``ed versions. + Note that ``lift=True`` implies ``subs=False``. + + Examples + ======== + + >>> from sympy import polarify, sin, I + >>> from sympy.abc import x, y + >>> expr = (-x)**y + >>> expr.expand() + (-x)**y + >>> polarify(expr) + ((_x*exp_polar(I*pi))**_y, {_x: x, _y: y}) + >>> polarify(expr)[0].expand() + _x**_y*exp_polar(_y*I*pi) + >>> polarify(x, lift=True) + polar_lift(x) + >>> polarify(x*(1+y), lift=True) + polar_lift(x)*polar_lift(y + 1) + + Adds are treated carefully: + + >>> polarify(1 + sin((1 + I)*x)) + (sin(_x*polar_lift(1 + I)) + 1, {_x: x}) + """ + if lift: + subs = False + eq = _polarify(sympify(eq), lift) + if not subs: + return eq + reps = {s: Dummy(s.name, polar=True) for s in eq.free_symbols} + eq = eq.subs(reps) + return eq, {r: s for s, r in reps.items()} + + +def _unpolarify(eq, exponents_only, pause=False): + if not isinstance(eq, Basic) or eq.is_Atom: + return eq + + if not pause: + from sympy.functions.elementary.exponential import exp, exp_polar + if isinstance(eq, exp_polar): + return exp(_unpolarify(eq.exp, exponents_only)) + if isinstance(eq, principal_branch) and eq.args[1] == 2*pi: + return _unpolarify(eq.args[0], exponents_only) + if ( + eq.is_Add or eq.is_Mul or eq.is_Boolean or + eq.is_Relational and ( + eq.rel_op in ('==', '!=') and 0 in eq.args or + eq.rel_op not in ('==', '!=')) + ): + return eq.func(*[_unpolarify(x, exponents_only) for x in eq.args]) + if isinstance(eq, polar_lift): + return _unpolarify(eq.args[0], exponents_only) + + if eq.is_Pow: + expo = _unpolarify(eq.exp, exponents_only) + base = _unpolarify(eq.base, exponents_only, + not (expo.is_integer and not pause)) + return base**expo + + if eq.is_Function and getattr(eq.func, 'unbranched', False): + return eq.func(*[_unpolarify(x, exponents_only, exponents_only) + for x in eq.args]) + + return eq.func(*[_unpolarify(x, exponents_only, True) for x in eq.args]) + + +def unpolarify(eq, subs=None, exponents_only=False): + """ + If `p` denotes the projection from the Riemann surface of the logarithm to + the complex line, return a simplified version `eq'` of `eq` such that + `p(eq') = p(eq)`. + Also apply the substitution subs in the end. (This is a convenience, since + ``unpolarify``, in a certain sense, undoes :func:`polarify`.) + + Examples + ======== + + >>> from sympy import unpolarify, polar_lift, sin, I + >>> unpolarify(polar_lift(I + 2)) + 2 + I + >>> unpolarify(sin(polar_lift(I + 7))) + sin(7 + I) + """ + if isinstance(eq, bool): + return eq + + eq = sympify(eq) + if subs is not None: + return unpolarify(eq.subs(subs)) + changed = True + pause = False + if exponents_only: + pause = True + while changed: + changed = False + res = _unpolarify(eq, exponents_only, pause) + if res != eq: + changed = True + eq = res + if isinstance(res, bool): + return res + # Finally, replacing Exp(0) by 1 is always correct. + # So is polar_lift(0) -> 0. + from sympy.functions.elementary.exponential import exp_polar + return res.subs({exp_polar(0): 1, polar_lift(0): 0}) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/exponential.py b/phivenv/Lib/site-packages/sympy/functions/elementary/exponential.py new file mode 100644 index 0000000000000000000000000000000000000000..2bb0333cb34a35a96248c12a4640e848986f2feb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/exponential.py @@ -0,0 +1,1286 @@ +from __future__ import annotations +from itertools import product + +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.expr import Expr +from sympy.core.function import (DefinedFunction, ArgumentIndexError, expand_log, + expand_mul, FunctionClass, PoleError, expand_multinomial, expand_complex) +from sympy.core.logic import fuzzy_and, fuzzy_not, fuzzy_or +from sympy.core.mul import Mul +from sympy.core.numbers import Integer, Rational, pi, I +from sympy.core.parameters import global_parameters +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Wild, Dummy +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import arg, unpolarify, im, re, Abs +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.ntheory import multiplicity, perfect_power +from sympy.ntheory.factor_ import factorint + +# NOTE IMPORTANT +# The series expansion code in this file is an important part of the gruntz +# algorithm for determining limits. _eval_nseries has to return a generalized +# power series with coefficients in C(log(x), log). +# In more detail, the result of _eval_nseries(self, x, n) must be +# c_0*x**e_0 + ... (finitely many terms) +# where e_i are numbers (not necessarily integers) and c_i involve only +# numbers, the function log, and log(x). [This also means it must not contain +# log(x(1+p)), this *has* to be expanded to log(x)+log(1+p) if x.is_positive and +# p.is_positive.] + + +class ExpBase(DefinedFunction): + + unbranched = True + _singularities = (S.ComplexInfinity,) + + @property + def kind(self): + return self.exp.kind + + def inverse(self, argindex=1): + """ + Returns the inverse function of ``exp(x)``. + """ + return log + + def as_numer_denom(self): + """ + Returns this with a positive exponent as a 2-tuple (a fraction). + + Examples + ======== + + >>> from sympy import exp + >>> from sympy.abc import x + >>> exp(-x).as_numer_denom() + (1, exp(x)) + >>> exp(x).as_numer_denom() + (exp(x), 1) + """ + # this should be the same as Pow.as_numer_denom wrt + # exponent handling + if not self.is_commutative: + return self, S.One + exp = self.exp + neg_exp = exp.is_negative + if not neg_exp and not (-exp).is_negative: + neg_exp = exp.could_extract_minus_sign() + if neg_exp: + return S.One, self.func(-exp) + return self, S.One + + @property + def exp(self): + """ + Returns the exponent of the function. + """ + return self.args[0] + + def as_base_exp(self): + """ + Returns the 2-tuple (base, exponent). + """ + return self.func(1), Mul(*self.args) + + def _eval_adjoint(self): + return self.func(self.exp.adjoint()) + + def _eval_conjugate(self): + return self.func(self.exp.conjugate()) + + def _eval_transpose(self): + return self.func(self.exp.transpose()) + + def _eval_is_finite(self): + arg = self.exp + if arg.is_infinite: + if arg.is_extended_negative: + return True + if arg.is_extended_positive: + return False + if arg.is_finite: + return True + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + z = s.exp.is_zero + if z: + return True + elif s.exp.is_rational and fuzzy_not(z): + return False + else: + return s.is_rational + + def _eval_is_zero(self): + return self.exp is S.NegativeInfinity + + def _eval_power(self, other): + """exp(arg)**e -> exp(arg*e) if assumptions allow it. + """ + b, e = self.as_base_exp() + return Pow._eval_power(Pow(b, e, evaluate=False), other) + + def _eval_expand_power_exp(self, **hints): + from sympy.concrete.products import Product + from sympy.concrete.summations import Sum + arg = self.args[0] + if arg.is_Add and arg.is_commutative: + return Mul.fromiter(self.func(x) for x in arg.args) + elif isinstance(arg, Sum) and arg.is_commutative: + return Product(self.func(arg.function), *arg.limits) + return self.func(arg) + + +class exp_polar(ExpBase): + r""" + Represent a *polar number* (see g-function Sphinx documentation). + + Explanation + =========== + + ``exp_polar`` represents the function + `Exp: \mathbb{C} \rightarrow \mathcal{S}`, sending the complex number + `z = a + bi` to the polar number `r = exp(a), \theta = b`. It is one of + the main functions to construct polar numbers. + + Examples + ======== + + >>> from sympy import exp_polar, pi, I, exp + + The main difference is that polar numbers do not "wrap around" at `2 \pi`: + + >>> exp(2*pi*I) + 1 + >>> exp_polar(2*pi*I) + exp_polar(2*I*pi) + + apart from that they behave mostly like classical complex numbers: + + >>> exp_polar(2)*exp_polar(3) + exp_polar(5) + + See Also + ======== + + sympy.simplify.powsimp.powsimp + polar_lift + periodic_argument + principal_branch + """ + + is_polar = True + is_comparable = False # cannot be evalf'd + + def _eval_Abs(self): # Abs is never a polar number + return exp(re(self.args[0])) + + def _eval_evalf(self, prec): + """ Careful! any evalf of polar numbers is flaky """ + i = im(self.args[0]) + try: + bad = (i <= -pi or i > pi) + except TypeError: + bad = True + if bad: + return self # cannot evalf for this argument + res = exp(self.args[0])._eval_evalf(prec) + if i > 0 and im(res) < 0: + # i ~ pi, but exp(I*i) evaluated to argument slightly bigger than pi + return re(res) + return res + + def _eval_power(self, other): + return self.func(self.args[0]*other) + + def _eval_is_extended_real(self): + if self.args[0].is_extended_real: + return True + + def as_base_exp(self): + # XXX exp_polar(0) is special! + if self.args[0] == 0: + return self, S.One + return ExpBase.as_base_exp(self) + + +class ExpMeta(FunctionClass): + def __instancecheck__(cls, instance): + if exp in instance.__class__.__mro__: + return True + return isinstance(instance, Pow) and instance.base is S.Exp1 + + +class exp(ExpBase, metaclass=ExpMeta): + """ + The exponential function, :math:`e^x`. + + Examples + ======== + + >>> from sympy import exp, I, pi + >>> from sympy.abc import x + >>> exp(x) + exp(x) + >>> exp(x).diff(x) + exp(x) + >>> exp(I*pi) + -1 + + Parameters + ========== + + arg : Expr + + See Also + ======== + + log + """ + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return self + else: + raise ArgumentIndexError(self, argindex) + + def _eval_refine(self, assumptions): + from sympy.assumptions import ask, Q + arg = self.args[0] + if arg.is_Mul: + Ioo = I*S.Infinity + if arg in [Ioo, -Ioo]: + return S.NaN + + coeff = arg.as_coefficient(pi*I) + if coeff: + if ask(Q.integer(2*coeff)): + if ask(Q.even(coeff)): + return S.One + elif ask(Q.odd(coeff)): + return S.NegativeOne + elif ask(Q.even(coeff + S.Half)): + return -I + elif ask(Q.odd(coeff + S.Half)): + return I + + @classmethod + def eval(cls, arg): + from sympy.calculus import AccumBounds + from sympy.matrices.matrixbase import MatrixBase + from sympy.sets.setexpr import SetExpr + from sympy.simplify.simplify import logcombine + if isinstance(arg, MatrixBase): + return arg.exp() + elif global_parameters.exp_is_pow: + return Pow(S.Exp1, arg) + elif arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg.is_zero: + return S.One + elif arg is S.One: + return S.Exp1 + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.Zero + elif arg is S.ComplexInfinity: + return S.NaN + elif isinstance(arg, log): + return arg.args[0] + elif isinstance(arg, AccumBounds): + return AccumBounds(exp(arg.min), exp(arg.max)) + elif isinstance(arg, SetExpr): + return arg._eval_func(cls) + elif arg.is_Mul: + coeff = arg.as_coefficient(pi*I) + if coeff: + if (2*coeff).is_integer: + if coeff.is_even: + return S.One + elif coeff.is_odd: + return S.NegativeOne + elif (coeff + S.Half).is_even: + return -I + elif (coeff + S.Half).is_odd: + return I + elif coeff.is_Rational: + ncoeff = coeff % 2 # restrict to [0, 2pi) + if ncoeff > 1: # restrict to (-pi, pi] + ncoeff -= 2 + if ncoeff != coeff: + return cls(ncoeff*pi*I) + + # Warning: code in risch.py will be very sensitive to changes + # in this (see DifferentialExtension). + + # look for a single log factor + + coeff, terms = arg.as_coeff_Mul() + + # but it can't be multiplied by oo + if coeff in [S.NegativeInfinity, S.Infinity]: + if terms.is_number: + if coeff is S.NegativeInfinity: + terms = -terms + if re(terms).is_zero and terms is not S.Zero: + return S.NaN + if re(terms).is_positive and im(terms) is not S.Zero: + return S.ComplexInfinity + if re(terms).is_negative: + return S.Zero + return None + + coeffs, log_term = [coeff], None + for term in Mul.make_args(terms): + term_ = logcombine(term) + if isinstance(term_, log): + if log_term is None: + log_term = term_.args[0] + else: + return None + elif term.is_comparable: + coeffs.append(term) + else: + return None + + return log_term**Mul(*coeffs) if log_term else None + + elif arg.is_Add: + out = [] + add = [] + argchanged = False + for a in arg.args: + if a is S.One: + add.append(a) + continue + newa = cls(a) + if isinstance(newa, cls): + if newa.args[0] != a: + add.append(newa.args[0]) + argchanged = True + else: + add.append(a) + else: + out.append(newa) + if out or argchanged: + return Mul(*out)*cls(Add(*add), evaluate=False) + + if arg.is_zero: + return S.One + + @property + def base(self): + """ + Returns the base of the exponential function. + """ + return S.Exp1 + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + """ + Calculates the next term in the Taylor series expansion. + """ + if n < 0: + return S.Zero + if n == 0: + return S.One + x = sympify(x) + if previous_terms: + p = previous_terms[-1] + if p is not None: + return p * x / n + return x**n/factorial(n) + + def as_real_imag(self, deep=True, **hints): + """ + Returns this function as a 2-tuple representing a complex number. + + Examples + ======== + + >>> from sympy import exp, I + >>> from sympy.abc import x + >>> exp(x).as_real_imag() + (exp(re(x))*cos(im(x)), exp(re(x))*sin(im(x))) + >>> exp(1).as_real_imag() + (E, 0) + >>> exp(I).as_real_imag() + (cos(1), sin(1)) + >>> exp(1+I).as_real_imag() + (E*cos(1), E*sin(1)) + + See Also + ======== + + sympy.functions.elementary.complexes.re + sympy.functions.elementary.complexes.im + """ + from sympy.functions.elementary.trigonometric import cos, sin + re, im = self.args[0].as_real_imag() + if deep: + re = re.expand(deep, **hints) + im = im.expand(deep, **hints) + cos, sin = cos(im), sin(im) + return (exp(re)*cos, exp(re)*sin) + + def _eval_subs(self, old, new): + # keep processing of power-like args centralized in Pow + if old.is_Pow: # handle (exp(3*log(x))).subs(x**2, z) -> z**(3/2) + old = exp(old.exp*log(old.base)) + elif old is S.Exp1 and new.is_Function: + old = exp + if isinstance(old, exp) or old is S.Exp1: + f = lambda a: Pow(*a.as_base_exp(), evaluate=False) if ( + a.is_Pow or isinstance(a, exp)) else a + return Pow._eval_subs(f(self), f(old), new) + + if old is exp and not new.is_Function: + return new**self.exp._subs(old, new) + return super()._eval_subs(old, new) + + def _eval_is_extended_real(self): + if self.args[0].is_extended_real: + return True + elif self.args[0].is_imaginary: + arg2 = -S(2) * I * self.args[0] / pi + return arg2.is_even + + def _eval_is_complex(self): + def complex_extended_negative(arg): + yield arg.is_complex + yield arg.is_extended_negative + return fuzzy_or(complex_extended_negative(self.args[0])) + + def _eval_is_algebraic(self): + if (self.exp / pi / I).is_rational: + return True + if fuzzy_not(self.exp.is_zero): + if self.exp.is_algebraic: + return False + elif (self.exp / pi).is_rational: + return False + + def _eval_is_extended_positive(self): + if self.exp.is_extended_real: + return self.args[0] is not S.NegativeInfinity + elif self.exp.is_imaginary: + arg2 = -I * self.args[0] / pi + return arg2.is_even + + def _eval_nseries(self, x, n, logx, cdir=0): + # NOTE Please see the comment at the beginning of this file, labelled + # IMPORTANT. + from sympy.functions.elementary.complexes import sign + from sympy.functions.elementary.integers import ceiling + from sympy.series.limits import limit + from sympy.series.order import Order + from sympy.simplify.powsimp import powsimp + arg = self.exp + arg_series = arg._eval_nseries(x, n=n, logx=logx) + if arg_series.is_Order: + return 1 + arg_series + arg0 = limit(arg_series.removeO(), x, 0) + if arg0 is S.NegativeInfinity: + return Order(x**n, x) + if arg0 is S.Infinity: + return self + if arg0.is_infinite: + raise PoleError("Cannot expand %s around 0" % (self)) + # checking for indecisiveness/ sign terms in arg0 + if any(isinstance(arg, sign) for arg in arg0.args): + return self + t = Dummy("t") + nterms = n + try: + cf = Order(arg.as_leading_term(x, logx=logx), x).getn() + except (NotImplementedError, PoleError): + cf = 0 + if cf and cf > 0: + nterms = ceiling(n/cf) + exp_series = exp(t)._taylor(t, nterms) + r = exp(arg0)*exp_series.subs(t, arg_series - arg0) + rep = {logx: log(x)} if logx is not None else {} + if r.subs(rep) == self: + return r + if cf and cf > 1: + r += Order((arg_series - arg0)**n, x)/x**((cf-1)*n) + else: + r += Order((arg_series - arg0)**n, x) + r = r.expand() + r = powsimp(r, deep=True, combine='exp') + # powsimp may introduce unexpanded (-1)**Rational; see PR #17201 + simplerat = lambda x: x.is_Rational and x.q in [3, 4, 6] + w = Wild('w', properties=[simplerat]) + r = r.replace(S.NegativeOne**w, expand_complex(S.NegativeOne**w)) + return r + + def _taylor(self, x, n): + l = [] + g = None + for i in range(n): + g = self.taylor_term(i, self.args[0], g) + g = g.nseries(x, n=n) + l.append(g.removeO()) + return Add(*l) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.util import AccumBounds + arg = self.args[0].cancel().as_leading_term(x, logx=logx) + arg0 = arg.subs(x, 0) + if arg is S.NaN: + return S.NaN + if isinstance(arg0, AccumBounds): + # This check addresses a corner case involving AccumBounds. + # if isinstance(arg, AccumBounds) is True, then arg0 can either be 0, + # AccumBounds(-oo, 0) or AccumBounds(-oo, oo). + # Check out function: test_issue_18473() in test_exponential.py and + # test_limits.py for more information. + if re(cdir) < S.Zero: + return exp(-arg0) + return exp(arg0) + if arg0 is S.NaN: + arg0 = arg.limit(x, 0) + if arg0.is_infinite is False: + return exp(arg0) + raise PoleError("Cannot expand %s around 0" % (self)) + + def _eval_rewrite_as_sin(self, arg, **kwargs): + from sympy.functions.elementary.trigonometric import sin + return sin(I*arg + pi/2) - I*sin(I*arg) + + def _eval_rewrite_as_cos(self, arg, **kwargs): + from sympy.functions.elementary.trigonometric import cos + return cos(I*arg) + I*cos(I*arg + pi/2) + + def _eval_rewrite_as_tanh(self, arg, **kwargs): + from sympy.functions.elementary.hyperbolic import tanh + return (1 + tanh(arg/2))/(1 - tanh(arg/2)) + + def _eval_rewrite_as_sqrt(self, arg, **kwargs): + from sympy.functions.elementary.trigonometric import sin, cos + if arg.is_Mul: + coeff = arg.coeff(pi*I) + if coeff and coeff.is_number: + cosine, sine = cos(pi*coeff), sin(pi*coeff) + if not isinstance(cosine, cos) and not isinstance (sine, sin): + return cosine + I*sine + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + if arg.is_Mul: + logs = [a for a in arg.args if isinstance(a, log) and len(a.args) == 1] + if logs: + return Pow(logs[0].args[0], arg.coeff(logs[0])) + + +def match_real_imag(expr): + r""" + Try to match expr with $a + Ib$ for real $a$ and $b$. + + ``match_real_imag`` returns a tuple containing the real and imaginary + parts of expr or ``(None, None)`` if direct matching is not possible. Contrary + to :func:`~.re`, :func:`~.im``, and ``as_real_imag()``, this helper will not force things + by returning expressions themselves containing ``re()`` or ``im()`` and it + does not expand its argument either. + + """ + r_, i_ = expr.as_independent(I, as_Add=True) + if i_ == 0 and r_.is_real: + return (r_, i_) + i_ = i_.as_coefficient(I) + if i_ and i_.is_real and r_.is_real: + return (r_, i_) + else: + return (None, None) # simpler to check for than None + + +class log(DefinedFunction): + r""" + The natural logarithm function `\ln(x)` or `\log(x)`. + + Explanation + =========== + + Logarithms are taken with the natural base, `e`. To get + a logarithm of a different base ``b``, use ``log(x, b)``, + which is essentially short-hand for ``log(x)/log(b)``. + + ``log`` represents the principal branch of the natural + logarithm. As such it has a branch cut along the negative + real axis and returns values having a complex argument in + `(-\pi, \pi]`. + + Examples + ======== + + >>> from sympy import log, sqrt, S, I + >>> log(8, 2) + 3 + >>> log(S(8)/3, 2) + -log(3)/log(2) + 3 + >>> log(-1 + I*sqrt(3)) + log(2) + 2*I*pi/3 + + See Also + ======== + + exp + + """ + + args: tuple[Expr] + + _singularities = (S.Zero, S.ComplexInfinity) + + def fdiff(self, argindex=1): + """ + Returns the first derivative of the function. + """ + if argindex == 1: + return 1/self.args[0] + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + r""" + Returns `e^x`, the inverse function of `\log(x)`. + """ + return exp + + @classmethod + def eval(cls, arg, base=None): + from sympy.calculus import AccumBounds + from sympy.sets.setexpr import SetExpr + + arg = sympify(arg) + + if base is not None: + base = sympify(base) + if base == 1: + if arg == 1: + return S.NaN + else: + return S.ComplexInfinity + try: + # handle extraction of powers of the base now + # or else expand_log in Mul would have to handle this + n = multiplicity(base, arg) + if n: + return n + log(arg / base**n) / log(base) + else: + return log(arg)/log(base) + except ValueError: + pass + if base is not S.Exp1: + return cls(arg)/cls(base) + else: + return cls(arg) + + if arg.is_Number: + if arg.is_zero: + return S.ComplexInfinity + elif arg is S.One: + return S.Zero + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.Infinity + elif arg is S.NaN: + return S.NaN + elif arg.is_Rational and arg.p == 1: + return -cls(arg.q) + + if arg.is_Pow and arg.base is S.Exp1 and arg.exp.is_extended_real: + return arg.exp + if isinstance(arg, exp) and arg.exp.is_extended_real: + return arg.exp + elif isinstance(arg, exp) and arg.exp.is_number: + r_, i_ = match_real_imag(arg.exp) + if i_ and i_.is_comparable: + i_ %= 2*pi + if i_ > pi: + i_ -= 2*pi + return r_ + expand_mul(i_ * I, deep=False) + elif isinstance(arg, exp_polar): + return unpolarify(arg.exp) + elif isinstance(arg, AccumBounds): + if arg.min.is_positive: + return AccumBounds(log(arg.min), log(arg.max)) + elif arg.min.is_zero: + return AccumBounds(S.NegativeInfinity, log(arg.max)) + else: + return S.NaN + elif isinstance(arg, SetExpr): + return arg._eval_func(cls) + + if arg.is_number: + if arg.is_negative: + return pi * I + cls(-arg) + elif arg is S.ComplexInfinity: + return S.ComplexInfinity + elif arg is S.Exp1: + return S.One + + if arg.is_zero: + return S.ComplexInfinity + + # don't autoexpand Pow or Mul (see the issue 3351): + if not arg.is_Add: + coeff = arg.as_coefficient(I) + + if coeff is not None: + if coeff is S.Infinity: + return S.Infinity + elif coeff is S.NegativeInfinity: + return S.Infinity + elif coeff.is_Rational: + if coeff.is_nonnegative: + return pi * I * S.Half + cls(coeff) + else: + return -pi * I * S.Half + cls(-coeff) + + if arg.is_number and arg.is_algebraic: + # Match arg = coeff*(r_ + i_*I) with coeff>0, r_ and i_ real. + coeff, arg_ = arg.as_independent(I, as_Add=False) + if coeff.is_negative: + coeff *= -1 + arg_ *= -1 + arg_ = expand_mul(arg_, deep=False) + r_, i_ = arg_.as_independent(I, as_Add=True) + i_ = i_.as_coefficient(I) + if coeff.is_real and i_ and i_.is_real and r_.is_real: + if r_.is_zero: + if i_.is_positive: + return pi * I * S.Half + cls(coeff * i_) + elif i_.is_negative: + return -pi * I * S.Half + cls(coeff * -i_) + else: + from sympy.simplify import ratsimp + # Check for arguments involving rational multiples of pi + t = (i_/r_).cancel() + t1 = (-t).cancel() + atan_table = _log_atan_table() + if t in atan_table: + modulus = ratsimp(coeff * Abs(arg_)) + if r_.is_positive: + return cls(modulus) + I * atan_table[t] + else: + return cls(modulus) + I * (atan_table[t] - pi) + elif t1 in atan_table: + modulus = ratsimp(coeff * Abs(arg_)) + if r_.is_positive: + return cls(modulus) + I * (-atan_table[t1]) + else: + return cls(modulus) + I * (pi - atan_table[t1]) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): # of log(1+x) + r""" + Returns the next term in the Taylor series expansion of `\log(1+x)`. + """ + from sympy.simplify.powsimp import powsimp + if n < 0: + return S.Zero + x = sympify(x) + if n == 0: + return x + if previous_terms: + p = previous_terms[-1] + if p is not None: + return powsimp((-n) * p * x / (n + 1), deep=True, combine='exp') + return (1 - 2*(n % 2)) * x**(n + 1)/(n + 1) + + def _eval_expand_log(self, deep=True, **hints): + from sympy.concrete import Sum, Product + force = hints.get('force', False) + factor = hints.get('factor', False) + if (len(self.args) == 2): + return expand_log(self.func(*self.args), deep=deep, force=force) + arg = self.args[0] + if arg.is_Integer: + # remove perfect powers + p = perfect_power(arg) + logarg = None + coeff = 1 + if p is not False: + arg, coeff = p + logarg = self.func(arg) + # expand as product of its prime factors if factor=True + if factor: + p = factorint(arg) + if arg not in p.keys(): + logarg = sum(n*log(val) for val, n in p.items()) + if logarg is not None: + return coeff*logarg + elif arg.is_Rational: + return log(arg.p) - log(arg.q) + elif arg.is_Mul: + expr = [] + nonpos = [] + for x in arg.args: + if force or x.is_positive or x.is_polar: + a = self.func(x) + if isinstance(a, log): + expr.append(self.func(x)._eval_expand_log(**hints)) + else: + expr.append(a) + elif x.is_negative: + a = self.func(-x) + expr.append(a) + nonpos.append(S.NegativeOne) + else: + nonpos.append(x) + return Add(*expr) + log(Mul(*nonpos)) + elif arg.is_Pow or isinstance(arg, exp): + if force or (arg.exp.is_extended_real and (arg.base.is_positive or ((arg.exp+1) + .is_positive and (arg.exp-1).is_nonpositive))) or arg.base.is_polar: + b = arg.base + e = arg.exp + a = self.func(b) + if isinstance(a, log): + return unpolarify(e) * a._eval_expand_log(**hints) + else: + return unpolarify(e) * a + elif isinstance(arg, Product): + if force or arg.function.is_positive: + return Sum(log(arg.function), *arg.limits) + + return self.func(arg) + + def _eval_simplify(self, **kwargs): + from sympy.simplify.simplify import expand_log, simplify, inversecombine + if len(self.args) == 2: # it's unevaluated + return simplify(self.func(*self.args), **kwargs) + + expr = self.func(simplify(self.args[0], **kwargs)) + if kwargs['inverse']: + expr = inversecombine(expr) + expr = expand_log(expr, deep=True) + return min([expr, self], key=kwargs['measure']) + + def as_real_imag(self, deep=True, **hints): + """ + Returns this function as a complex coordinate. + + Examples + ======== + + >>> from sympy import I, log + >>> from sympy.abc import x + >>> log(x).as_real_imag() + (log(Abs(x)), arg(x)) + >>> log(I).as_real_imag() + (0, pi/2) + >>> log(1 + I).as_real_imag() + (log(sqrt(2)), pi/4) + >>> log(I*x).as_real_imag() + (log(Abs(x)), arg(I*x)) + + """ + sarg = self.args[0] + if deep: + sarg = self.args[0].expand(deep, **hints) + sarg_abs = Abs(sarg) + if sarg_abs == sarg: + return self, S.Zero + sarg_arg = arg(sarg) + if hints.get('log', False): # Expand the log + hints['complex'] = False + return (log(sarg_abs).expand(deep, **hints), sarg_arg) + else: + return log(sarg_abs), sarg_arg + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + if (self.args[0] - 1).is_zero: + return True + if s.args[0].is_rational and fuzzy_not((self.args[0] - 1).is_zero): + return False + else: + return s.is_rational + + def _eval_is_algebraic(self): + s = self.func(*self.args) + if s.func == self.func: + if (self.args[0] - 1).is_zero: + return True + elif fuzzy_not((self.args[0] - 1).is_zero): + if self.args[0].is_algebraic: + return False + else: + return s.is_algebraic + + def _eval_is_extended_real(self): + return self.args[0].is_extended_positive + + def _eval_is_complex(self): + z = self.args[0] + return fuzzy_and([z.is_complex, fuzzy_not(z.is_zero)]) + + def _eval_is_finite(self): + arg = self.args[0] + if arg.is_zero: + return False + return arg.is_finite + + def _eval_is_extended_positive(self): + return (self.args[0] - 1).is_extended_positive + + def _eval_is_zero(self): + return (self.args[0] - 1).is_zero + + def _eval_is_extended_nonnegative(self): + return (self.args[0] - 1).is_extended_nonnegative + + def _eval_nseries(self, x, n, logx, cdir=0): + # NOTE Please see the comment at the beginning of this file, labelled + # IMPORTANT. + from sympy.series.order import Order + from sympy.simplify.simplify import logcombine + from sympy.core.symbol import Dummy + + if self.args[0] == x: + return log(x) if logx is None else logx + arg = self.args[0] + t = Dummy('t', positive=True) + if cdir == 0: + cdir = 1 + z = arg.subs(x, cdir*t) + + k, l = Wild("k"), Wild("l") + r = z.match(k*t**l) + if r is not None: + k, l = r[k], r[l] + if l != 0 and not l.has(t) and not k.has(t): + r = l*log(x) if logx is None else l*logx + r += log(k) - l*log(cdir) # XXX true regardless of assumptions? + return r + + def coeff_exp(term, x): + coeff, exp = S.One, S.Zero + for factor in Mul.make_args(term): + if factor.has(x): + base, exp = factor.as_base_exp() + if base != x: + try: + return term.leadterm(x) + except ValueError: + return term, S.Zero + else: + coeff *= factor + return coeff, exp + + # TODO new and probably slow + try: + a, b = z.leadterm(t, logx=logx, cdir=1) + except (ValueError, NotImplementedError, PoleError): + s = z._eval_nseries(t, n=n, logx=logx, cdir=1) + while s.is_Order: + n += 1 + s = z._eval_nseries(t, n=n, logx=logx, cdir=1) + try: + a, b = s.removeO().leadterm(t, cdir=1) + except ValueError: + a, b = s.removeO().as_leading_term(t, cdir=1), S.Zero + + p = (z/(a*t**b) - 1).cancel()._eval_nseries(t, n=n, logx=logx, cdir=1) + if p.has(exp): + p = logcombine(p) + if isinstance(p, Order): + n = p.getn() + _, d = coeff_exp(p, t) + logx = log(x) if logx is None else logx + + if not d.is_positive: + res = log(a) - b*log(cdir) + b*logx + _res = res + logflags = {"deep": True, "log": True, "mul": False, "power_exp": False, + "power_base": False, "multinomial": False, "basic": False, "force": True, + "factor": False} + expr = self.expand(**logflags) + if (not a.could_extract_minus_sign() and + logx.could_extract_minus_sign()): + _res = _res.subs(-logx, -log(x)).expand(**logflags) + else: + _res = _res.subs(logx, log(x)).expand(**logflags) + if _res == expr: + return res + return res + Order(x**n, x) + + def mul(d1, d2): + res = {} + for e1, e2 in product(d1, d2): + ex = e1 + e2 + if ex < n: + res[ex] = res.get(ex, S.Zero) + d1[e1]*d2[e2] + return res + + pterms = {} + + for term in Add.make_args(p.removeO()): + co1, e1 = coeff_exp(term, t) + pterms[e1] = pterms.get(e1, S.Zero) + co1 + + k = S.One + terms = {} + pk = pterms + + while k*d < n: + coeff = -S.NegativeOne**k/k + for ex in pk: + terms[ex] = terms.get(ex, S.Zero) + coeff*pk[ex] + pk = mul(pk, pterms) + k += S.One + + res = log(a) - b*log(cdir) + b*logx + for ex in terms: + res += terms[ex].cancel()*t**(ex) + + if a.is_negative and im(z) != 0: + from sympy.functions.special.delta_functions import Heaviside + for i, term in enumerate(z.lseries(t)): + if not term.is_real or i == 5: + break + if i < 5: + coeff, _ = term.as_coeff_exponent(t) + res += -2*I*pi*Heaviside(-im(coeff), 0) + + res = res.subs(t, x/cdir) + return res + Order(x**n, x) + + def _eval_as_leading_term(self, x, logx, cdir): + # NOTE + # Refer https://github.com/sympy/sympy/pull/23592 for more information + # on each of the following steps involved in this method. + arg0 = self.args[0].together() + + # STEP 1 + t = Dummy('t', positive=True) + if cdir == 0: + cdir = 1 + z = arg0.subs(x, cdir*t) + + # STEP 2 + try: + c, e = z.leadterm(t, logx=logx, cdir=1) + except ValueError: + arg = arg0.as_leading_term(x, logx=logx, cdir=cdir) + return log(arg) + if c.has(t): + c = c.subs(t, x/cdir) + if e != 0: + raise PoleError("Cannot expand %s around 0" % (self)) + return log(c) + + # STEP 3 + if c == S.One and e == S.Zero: + return (arg0 - S.One).as_leading_term(x, logx=logx) + + # STEP 4 + res = log(c) - e*log(cdir) + logx = log(x) if logx is None else logx + res += e*logx + + # STEP 5 + if c.is_negative and im(z) != 0: + from sympy.functions.special.delta_functions import Heaviside + for i, term in enumerate(z.lseries(t)): + if not term.is_real or i == 5: + break + if i < 5: + coeff, _ = term.as_coeff_exponent(t) + res += -2*I*pi*Heaviside(-im(coeff), 0) + return res + + +class LambertW(DefinedFunction): + r""" + The Lambert W function $W(z)$ is defined as the inverse + function of $w \exp(w)$ [1]_. + + Explanation + =========== + + In other words, the value of $W(z)$ is such that $z = W(z) \exp(W(z))$ + for any complex number $z$. The Lambert W function is a multivalued + function with infinitely many branches $W_k(z)$, indexed by + $k \in \mathbb{Z}$. Each branch gives a different solution $w$ + of the equation $z = w \exp(w)$. + + The Lambert W function has two partially real branches: the + principal branch ($k = 0$) is real for real $z > -1/e$, and the + $k = -1$ branch is real for $-1/e < z < 0$. All branches except + $k = 0$ have a logarithmic singularity at $z = 0$. + + Examples + ======== + + >>> from sympy import LambertW + >>> LambertW(1.2) + 0.635564016364870 + >>> LambertW(1.2, -1).n() + -1.34747534407696 - 4.41624341514535*I + >>> LambertW(-1).is_real + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Lambert_W_function + """ + _singularities = (-Pow(S.Exp1, -1, evaluate=False), S.ComplexInfinity) + + @classmethod + def eval(cls, x, k=None): + if k == S.Zero: + return cls(x) + elif k is None: + k = S.Zero + + if k.is_zero: + if x.is_zero: + return S.Zero + if x is S.Exp1: + return S.One + if x == -1/S.Exp1: + return S.NegativeOne + if x == -log(2)/2: + return -log(2) + if x == 2*log(2): + return log(2) + if x == -pi/2: + return I*pi/2 + if x == exp(1 + S.Exp1): + return S.Exp1 + if x is S.Infinity: + return S.Infinity + + if fuzzy_not(k.is_zero): + if x.is_zero: + return S.NegativeInfinity + if k is S.NegativeOne: + if x == -pi/2: + return -I*pi/2 + elif x == -1/S.Exp1: + return S.NegativeOne + elif x == -2*exp(-2): + return -Integer(2) + + def fdiff(self, argindex=1): + """ + Return the first derivative of this function. + """ + x = self.args[0] + + if len(self.args) == 1: + if argindex == 1: + return LambertW(x)/(x*(1 + LambertW(x))) + else: + k = self.args[1] + if argindex == 1: + return LambertW(x, k)/(x*(1 + LambertW(x, k))) + + raise ArgumentIndexError(self, argindex) + + def _eval_is_extended_real(self): + x = self.args[0] + if len(self.args) == 1: + k = S.Zero + else: + k = self.args[1] + if k.is_zero: + if (x + 1/S.Exp1).is_positive: + return True + elif (x + 1/S.Exp1).is_nonpositive: + return False + elif (k + 1).is_zero: + if x.is_negative and (x + 1/S.Exp1).is_positive: + return True + elif x.is_nonpositive or (x + 1/S.Exp1).is_nonnegative: + return False + elif fuzzy_not(k.is_zero) and fuzzy_not((k + 1).is_zero): + if x.is_extended_real: + return False + + def _eval_is_finite(self): + return self.args[0].is_finite + + def _eval_is_algebraic(self): + s = self.func(*self.args) + if s.func == self.func: + if fuzzy_not(self.args[0].is_zero) and self.args[0].is_algebraic: + return False + else: + return s.is_algebraic + + def _eval_as_leading_term(self, x, logx, cdir): + if len(self.args) == 1: + arg = self.args[0] + arg0 = arg.subs(x, 0).cancel() + if not arg0.is_zero: + return self.func(arg0) + return arg.as_leading_term(x) + + def _eval_nseries(self, x, n, logx, cdir=0): + if len(self.args) == 1: + from sympy.functions.elementary.integers import ceiling + from sympy.series.order import Order + arg = self.args[0].nseries(x, n=n, logx=logx) + lt = arg.as_leading_term(x, logx=logx) + lte = 1 + if lt.is_Pow: + lte = lt.exp + if ceiling(n/lte) >= 1: + s = Add(*[(-S.One)**(k - 1)*Integer(k)**(k - 2)/ + factorial(k - 1)*arg**k for k in range(1, ceiling(n/lte))]) + s = expand_multinomial(s) + else: + s = S.Zero + + return s + Order(x**n, x) + return super()._eval_nseries(x, n, logx) + + def _eval_is_zero(self): + x = self.args[0] + if len(self.args) == 1: + return x.is_zero + else: + return fuzzy_and([x.is_zero, self.args[1].is_zero]) + + +@cacheit +def _log_atan_table(): + return { + # first quadrant only + sqrt(3): pi / 3, + 1: pi / 4, + sqrt(5 - 2 * sqrt(5)): pi / 5, + sqrt(2) * sqrt(5 - sqrt(5)) / (1 + sqrt(5)): pi / 5, + sqrt(5 + 2 * sqrt(5)): pi * Rational(2, 5), + sqrt(2) * sqrt(sqrt(5) + 5) / (-1 + sqrt(5)): pi * Rational(2, 5), + sqrt(3) / 3: pi / 6, + sqrt(2) - 1: pi / 8, + sqrt(2 - sqrt(2)) / sqrt(sqrt(2) + 2): pi / 8, + sqrt(2) + 1: pi * Rational(3, 8), + sqrt(sqrt(2) + 2) / sqrt(2 - sqrt(2)): pi * Rational(3, 8), + sqrt(1 - 2 * sqrt(5) / 5): pi / 10, + (-sqrt(2) + sqrt(10)) / (2 * sqrt(sqrt(5) + 5)): pi / 10, + sqrt(1 + 2 * sqrt(5) / 5): pi * Rational(3, 10), + (sqrt(2) + sqrt(10)) / (2 * sqrt(5 - sqrt(5))): pi * Rational(3, 10), + 2 - sqrt(3): pi / 12, + (-1 + sqrt(3)) / (1 + sqrt(3)): pi / 12, + 2 + sqrt(3): pi * Rational(5, 12), + (1 + sqrt(3)) / (-1 + sqrt(3)): pi * Rational(5, 12) + } diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/hyperbolic.py b/phivenv/Lib/site-packages/sympy/functions/elementary/hyperbolic.py new file mode 100644 index 0000000000000000000000000000000000000000..1031d035373bb641d26e61a395e6048906285bfe --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/hyperbolic.py @@ -0,0 +1,2285 @@ +from sympy.core import S, sympify, cacheit +from sympy.core.add import Add +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.logic import fuzzy_or, fuzzy_and, fuzzy_not, FuzzyBool +from sympy.core.numbers import I, pi, Rational +from sympy.core.symbol import Dummy +from sympy.functions.combinatorial.factorials import (binomial, factorial, + RisingFactorial) +from sympy.functions.combinatorial.numbers import bernoulli, euler, nC +from sympy.functions.elementary.complexes import Abs, im, re +from sympy.functions.elementary.exponential import exp, log, match_real_imag +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import ( + acos, acot, asin, atan, cos, cot, csc, sec, sin, tan, + _imaginary_unit_as_coefficient) +from sympy.polys.specialpolys import symmetric_poly + + +def _rewrite_hyperbolics_as_exp(expr): + return expr.xreplace({h: h.rewrite(exp) + for h in expr.atoms(HyperbolicFunction)}) + + +@cacheit +def _acosh_table(): + return { + I: log(I*(1 + sqrt(2))), + -I: log(-I*(1 + sqrt(2))), + S.Half: pi/3, + Rational(-1, 2): pi*Rational(2, 3), + sqrt(2)/2: pi/4, + -sqrt(2)/2: pi*Rational(3, 4), + 1/sqrt(2): pi/4, + -1/sqrt(2): pi*Rational(3, 4), + sqrt(3)/2: pi/6, + -sqrt(3)/2: pi*Rational(5, 6), + (sqrt(3) - 1)/sqrt(2**3): pi*Rational(5, 12), + -(sqrt(3) - 1)/sqrt(2**3): pi*Rational(7, 12), + sqrt(2 + sqrt(2))/2: pi/8, + -sqrt(2 + sqrt(2))/2: pi*Rational(7, 8), + sqrt(2 - sqrt(2))/2: pi*Rational(3, 8), + -sqrt(2 - sqrt(2))/2: pi*Rational(5, 8), + (1 + sqrt(3))/(2*sqrt(2)): pi/12, + -(1 + sqrt(3))/(2*sqrt(2)): pi*Rational(11, 12), + (sqrt(5) + 1)/4: pi/5, + -(sqrt(5) + 1)/4: pi*Rational(4, 5) + } + + +@cacheit +def _acsch_table(): + return { + I: -pi / 2, + I*(sqrt(2) + sqrt(6)): -pi / 12, + I*(1 + sqrt(5)): -pi / 10, + I*2 / sqrt(2 - sqrt(2)): -pi / 8, + I*2: -pi / 6, + I*sqrt(2 + 2/sqrt(5)): -pi / 5, + I*sqrt(2): -pi / 4, + I*(sqrt(5)-1): -3*pi / 10, + I*2 / sqrt(3): -pi / 3, + I*2 / sqrt(2 + sqrt(2)): -3*pi / 8, + I*sqrt(2 - 2/sqrt(5)): -2*pi / 5, + I*(sqrt(6) - sqrt(2)): -5*pi / 12, + S(2): -I*log((1+sqrt(5))/2), + } + + +@cacheit +def _asech_table(): + return { + I: - (pi*I / 2) + log(1 + sqrt(2)), + -I: (pi*I / 2) + log(1 + sqrt(2)), + (sqrt(6) - sqrt(2)): pi / 12, + (sqrt(2) - sqrt(6)): 11*pi / 12, + sqrt(2 - 2/sqrt(5)): pi / 10, + -sqrt(2 - 2/sqrt(5)): 9*pi / 10, + 2 / sqrt(2 + sqrt(2)): pi / 8, + -2 / sqrt(2 + sqrt(2)): 7*pi / 8, + 2 / sqrt(3): pi / 6, + -2 / sqrt(3): 5*pi / 6, + (sqrt(5) - 1): pi / 5, + (1 - sqrt(5)): 4*pi / 5, + sqrt(2): pi / 4, + -sqrt(2): 3*pi / 4, + sqrt(2 + 2/sqrt(5)): 3*pi / 10, + -sqrt(2 + 2/sqrt(5)): 7*pi / 10, + S(2): pi / 3, + -S(2): 2*pi / 3, + sqrt(2*(2 + sqrt(2))): 3*pi / 8, + -sqrt(2*(2 + sqrt(2))): 5*pi / 8, + (1 + sqrt(5)): 2*pi / 5, + (-1 - sqrt(5)): 3*pi / 5, + (sqrt(6) + sqrt(2)): 5*pi / 12, + (-sqrt(6) - sqrt(2)): 7*pi / 12, + I*S.Infinity: -pi*I / 2, + I*S.NegativeInfinity: pi*I / 2, + } + +############################################################################### +########################### HYPERBOLIC FUNCTIONS ############################## +############################################################################### + + +class HyperbolicFunction(DefinedFunction): + """ + Base class for hyperbolic functions. + + See Also + ======== + + sinh, cosh, tanh, coth + """ + + unbranched = True + + +def _peeloff_ipi(arg): + r""" + Split ARG into two parts, a "rest" and a multiple of $I\pi$. + This assumes ARG to be an ``Add``. + The multiple of $I\pi$ returned in the second position is always a ``Rational``. + + Examples + ======== + + >>> from sympy.functions.elementary.hyperbolic import _peeloff_ipi as peel + >>> from sympy import pi, I + >>> from sympy.abc import x, y + >>> peel(x + I*pi/2) + (x, 1/2) + >>> peel(x + I*2*pi/3 + I*pi*y) + (x + I*pi*y + I*pi/6, 1/2) + """ + ipi = pi*I + for a in Add.make_args(arg): + if a == ipi: + K = S.One + break + elif a.is_Mul: + K, p = a.as_two_terms() + if p == ipi and K.is_Rational: + break + else: + return arg, S.Zero + + m1 = (K % S.Half) + m2 = K - m1 + return arg - m2*ipi, m2 + + +class sinh(HyperbolicFunction): + r""" + ``sinh(x)`` is the hyperbolic sine of ``x``. + + The hyperbolic sine function is $\frac{e^x - e^{-x}}{2}$. + + Examples + ======== + + >>> from sympy import sinh + >>> from sympy.abc import x + >>> sinh(x) + sinh(x) + + See Also + ======== + + cosh, tanh, asinh + """ + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function. + """ + if argindex == 1: + return cosh(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return asinh + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.NegativeInfinity + elif arg.is_zero: + return S.Zero + elif arg.is_negative: + return -cls(-arg) + else: + if arg is S.ComplexInfinity: + return S.NaN + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + return I * sin(i_coeff) + else: + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_Add: + x, m = _peeloff_ipi(arg) + if m: + m = m*pi*I + return sinh(m)*cosh(x) + cosh(m)*sinh(x) + + if arg.is_zero: + return S.Zero + + if arg.func == asinh: + return arg.args[0] + + if arg.func == acosh: + x = arg.args[0] + return sqrt(x - 1) * sqrt(x + 1) + + if arg.func == atanh: + x = arg.args[0] + return x/sqrt(1 - x**2) + + if arg.func == acoth: + x = arg.args[0] + return 1/(sqrt(x - 1) * sqrt(x + 1)) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + """ + Returns the next term in the Taylor series expansion. + """ + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + if len(previous_terms) > 2: + p = previous_terms[-2] + return p * x**2 / (n*(n - 1)) + else: + return x**(n) / factorial(n) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + """ + Returns this function as a complex coordinate. + """ + if self.args[0].is_extended_real: + if deep: + hints['complex'] = False + return (self.expand(deep, **hints), S.Zero) + else: + return (self, S.Zero) + if deep: + re, im = self.args[0].expand(deep, **hints).as_real_imag() + else: + re, im = self.args[0].as_real_imag() + return (sinh(re)*cos(im), cosh(re)*sin(im)) + + def _eval_expand_complex(self, deep=True, **hints): + re_part, im_part = self.as_real_imag(deep=deep, **hints) + return re_part + im_part*I + + def _eval_expand_trig(self, deep=True, **hints): + if deep: + arg = self.args[0].expand(deep, **hints) + else: + arg = self.args[0] + x = None + if arg.is_Add: # TODO, implement more if deep stuff here + x, y = arg.as_two_terms() + else: + coeff, terms = arg.as_coeff_Mul(rational=True) + if coeff is not S.One and coeff.is_Integer and terms is not S.One: + x = terms + y = (coeff - 1)*x + if x is not None: + return (sinh(x)*cosh(y) + sinh(y)*cosh(x)).expand(trig=True) + return sinh(arg) + + def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs): + return (exp(arg) - exp(-arg)) / 2 + + def _eval_rewrite_as_exp(self, arg, **kwargs): + return (exp(arg) - exp(-arg)) / 2 + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return -I * sin(I * arg) + + def _eval_rewrite_as_csc(self, arg, **kwargs): + return -I / csc(I * arg) + + def _eval_rewrite_as_cosh(self, arg, **kwargs): + return -I*cosh(arg + pi*I/2) + + def _eval_rewrite_as_tanh(self, arg, **kwargs): + tanh_half = tanh(S.Half*arg) + return 2*tanh_half/(1 - tanh_half**2) + + def _eval_rewrite_as_coth(self, arg, **kwargs): + coth_half = coth(S.Half*arg) + return 2*coth_half/(coth_half**2 - 1) + + def _eval_rewrite_as_csch(self, arg, **kwargs): + return 1 / csch(arg) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if cdir.is_negative else '+') + if arg0.is_zero: + return arg + elif arg0.is_finite: + return self.func(arg0) + else: + return self + + def _eval_is_real(self): + arg = self.args[0] + if arg.is_real: + return True + + # if `im` is of the form n*pi + # else, check if it is a number + re, im = arg.as_real_imag() + return (im%pi).is_zero + + def _eval_is_extended_real(self): + if self.args[0].is_extended_real: + return True + + def _eval_is_positive(self): + if self.args[0].is_extended_real: + return self.args[0].is_positive + + def _eval_is_negative(self): + if self.args[0].is_extended_real: + return self.args[0].is_negative + + def _eval_is_finite(self): + arg = self.args[0] + return arg.is_finite + + def _eval_is_zero(self): + rest, ipi_mult = _peeloff_ipi(self.args[0]) + if rest.is_zero: + return ipi_mult.is_integer + + +class cosh(HyperbolicFunction): + r""" + ``cosh(x)`` is the hyperbolic cosine of ``x``. + + The hyperbolic cosine function is $\frac{e^x + e^{-x}}{2}$. + + Examples + ======== + + >>> from sympy import cosh + >>> from sympy.abc import x + >>> cosh(x) + cosh(x) + + See Also + ======== + + sinh, tanh, acosh + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return sinh(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + from sympy.functions.elementary.trigonometric import cos + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.Infinity + elif arg.is_zero: + return S.One + elif arg.is_negative: + return cls(-arg) + else: + if arg is S.ComplexInfinity: + return S.NaN + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + return cos(i_coeff) + else: + if arg.could_extract_minus_sign(): + return cls(-arg) + + if arg.is_Add: + x, m = _peeloff_ipi(arg) + if m: + m = m*pi*I + return cosh(m)*cosh(x) + sinh(m)*sinh(x) + + if arg.is_zero: + return S.One + + if arg.func == asinh: + return sqrt(1 + arg.args[0]**2) + + if arg.func == acosh: + return arg.args[0] + + if arg.func == atanh: + return 1/sqrt(1 - arg.args[0]**2) + + if arg.func == acoth: + x = arg.args[0] + return x/(sqrt(x - 1) * sqrt(x + 1)) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + + if len(previous_terms) > 2: + p = previous_terms[-2] + return p * x**2 / (n*(n - 1)) + else: + return x**(n)/factorial(n) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + if self.args[0].is_extended_real: + if deep: + hints['complex'] = False + return (self.expand(deep, **hints), S.Zero) + else: + return (self, S.Zero) + if deep: + re, im = self.args[0].expand(deep, **hints).as_real_imag() + else: + re, im = self.args[0].as_real_imag() + + return (cosh(re)*cos(im), sinh(re)*sin(im)) + + def _eval_expand_complex(self, deep=True, **hints): + re_part, im_part = self.as_real_imag(deep=deep, **hints) + return re_part + im_part*I + + def _eval_expand_trig(self, deep=True, **hints): + if deep: + arg = self.args[0].expand(deep, **hints) + else: + arg = self.args[0] + x = None + if arg.is_Add: # TODO, implement more if deep stuff here + x, y = arg.as_two_terms() + else: + coeff, terms = arg.as_coeff_Mul(rational=True) + if coeff is not S.One and coeff.is_Integer and terms is not S.One: + x = terms + y = (coeff - 1)*x + if x is not None: + return (cosh(x)*cosh(y) + sinh(x)*sinh(y)).expand(trig=True) + return cosh(arg) + + def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs): + return (exp(arg) + exp(-arg)) / 2 + + def _eval_rewrite_as_exp(self, arg, **kwargs): + return (exp(arg) + exp(-arg)) / 2 + + def _eval_rewrite_as_cos(self, arg, **kwargs): + return cos(I * arg, evaluate=False) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + return 1 / sec(I * arg, evaluate=False) + + def _eval_rewrite_as_sinh(self, arg, **kwargs): + return -I*sinh(arg + pi*I/2, evaluate=False) + + def _eval_rewrite_as_tanh(self, arg, **kwargs): + tanh_half = tanh(S.Half*arg)**2 + return (1 + tanh_half)/(1 - tanh_half) + + def _eval_rewrite_as_coth(self, arg, **kwargs): + coth_half = coth(S.Half*arg)**2 + return (coth_half + 1)/(coth_half - 1) + + def _eval_rewrite_as_sech(self, arg, **kwargs): + return 1 / sech(arg) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if cdir.is_negative else '+') + if arg0.is_zero: + return S.One + elif arg0.is_finite: + return self.func(arg0) + else: + return self + + def _eval_is_real(self): + arg = self.args[0] + + # `cosh(x)` is real for real OR purely imaginary `x` + if arg.is_real or arg.is_imaginary: + return True + + # cosh(a+ib) = cos(b)*cosh(a) + i*sin(b)*sinh(a) + # the imaginary part can be an expression like n*pi + # if not, check if the imaginary part is a number + re, im = arg.as_real_imag() + return (im%pi).is_zero + + def _eval_is_positive(self): + # cosh(x+I*y) = cos(y)*cosh(x) + I*sin(y)*sinh(x) + # cosh(z) is positive iff it is real and the real part is positive. + # So we need sin(y)*sinh(x) = 0 which gives x=0 or y=n*pi + # Case 1 (y=n*pi): cosh(z) = (-1)**n * cosh(x) -> positive for n even + # Case 2 (x=0): cosh(z) = cos(y) -> positive when cos(y) is positive + z = self.args[0] + + x, y = z.as_real_imag() + ymod = y % (2*pi) + + yzero = ymod.is_zero + # shortcut if ymod is zero + if yzero: + return True + + xzero = x.is_zero + # shortcut x is not zero + if xzero is False: + return yzero + + return fuzzy_or([ + # Case 1: + yzero, + # Case 2: + fuzzy_and([ + xzero, + fuzzy_or([ymod < pi/2, ymod > 3*pi/2]) + ]) + ]) + + + def _eval_is_nonnegative(self): + z = self.args[0] + + x, y = z.as_real_imag() + ymod = y % (2*pi) + + yzero = ymod.is_zero + # shortcut if ymod is zero + if yzero: + return True + + xzero = x.is_zero + # shortcut x is not zero + if xzero is False: + return yzero + + return fuzzy_or([ + # Case 1: + yzero, + # Case 2: + fuzzy_and([ + xzero, + fuzzy_or([ymod <= pi/2, ymod >= 3*pi/2]) + ]) + ]) + + def _eval_is_finite(self): + arg = self.args[0] + return arg.is_finite + + def _eval_is_zero(self): + rest, ipi_mult = _peeloff_ipi(self.args[0]) + if ipi_mult and rest.is_zero: + return (ipi_mult - S.Half).is_integer + + +class tanh(HyperbolicFunction): + r""" + ``tanh(x)`` is the hyperbolic tangent of ``x``. + + The hyperbolic tangent function is $\frac{\sinh(x)}{\cosh(x)}$. + + Examples + ======== + + >>> from sympy import tanh + >>> from sympy.abc import x + >>> tanh(x) + tanh(x) + + See Also + ======== + + sinh, cosh, atanh + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return S.One - tanh(self.args[0])**2 + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return atanh + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.One + elif arg is S.NegativeInfinity: + return S.NegativeOne + elif arg.is_zero: + return S.Zero + elif arg.is_negative: + return -cls(-arg) + else: + if arg is S.ComplexInfinity: + return S.NaN + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + if i_coeff.could_extract_minus_sign(): + return -I * tan(-i_coeff) + return I * tan(i_coeff) + else: + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_Add: + x, m = _peeloff_ipi(arg) + if m: + tanhm = tanh(m*pi*I) + if tanhm is S.ComplexInfinity: + return coth(x) + else: # tanhm == 0 + return tanh(x) + + if arg.is_zero: + return S.Zero + + if arg.func == asinh: + x = arg.args[0] + return x/sqrt(1 + x**2) + + if arg.func == acosh: + x = arg.args[0] + return sqrt(x - 1) * sqrt(x + 1) / x + + if arg.func == atanh: + return arg.args[0] + + if arg.func == acoth: + return 1/arg.args[0] + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + a = 2**(n + 1) + + B = bernoulli(n + 1) + F = factorial(n + 1) + + return a*(a - 1) * B/F * x**n + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + if self.args[0].is_extended_real: + if deep: + hints['complex'] = False + return (self.expand(deep, **hints), S.Zero) + else: + return (self, S.Zero) + if deep: + re, im = self.args[0].expand(deep, **hints).as_real_imag() + else: + re, im = self.args[0].as_real_imag() + denom = sinh(re)**2 + cos(im)**2 + return (sinh(re)*cosh(re)/denom, sin(im)*cos(im)/denom) + + def _eval_expand_trig(self, **hints): + arg = self.args[0] + if arg.is_Add: + n = len(arg.args) + TX = [tanh(x, evaluate=False)._eval_expand_trig() + for x in arg.args] + p = [0, 0] # [den, num] + for i in range(n + 1): + p[i % 2] += symmetric_poly(i, TX) + return p[1]/p[0] + elif arg.is_Mul: + coeff, terms = arg.as_coeff_Mul() + if coeff.is_Integer and coeff > 1: + T = tanh(terms) + n = [nC(range(coeff), k)*T**k for k in range(1, coeff + 1, 2)] + d = [nC(range(coeff), k)*T**k for k in range(0, coeff + 1, 2)] + return Add(*n)/Add(*d) + return tanh(arg) + + def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs): + neg_exp, pos_exp = exp(-arg), exp(arg) + return (pos_exp - neg_exp)/(pos_exp + neg_exp) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + neg_exp, pos_exp = exp(-arg), exp(arg) + return (pos_exp - neg_exp)/(pos_exp + neg_exp) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + return -I * tan(I * arg, evaluate=False) + + def _eval_rewrite_as_cot(self, arg, **kwargs): + return -I / cot(I * arg, evaluate=False) + + def _eval_rewrite_as_sinh(self, arg, **kwargs): + return I*sinh(arg)/sinh(pi*I/2 - arg, evaluate=False) + + def _eval_rewrite_as_cosh(self, arg, **kwargs): + return I*cosh(pi*I/2 - arg, evaluate=False)/cosh(arg) + + def _eval_rewrite_as_coth(self, arg, **kwargs): + return 1/coth(arg) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.series.order import Order + arg = self.args[0].as_leading_term(x) + + if x in arg.free_symbols and Order(1, x).contains(arg): + return arg + else: + return self.func(arg) + + def _eval_is_real(self): + arg = self.args[0] + if arg.is_real: + return True + + re, im = arg.as_real_imag() + + # if denom = 0, tanh(arg) = zoo + if re == 0 and im % pi == pi/2: + return None + + # check if im is of the form n*pi/2 to make sin(2*im) = 0 + # if not, im could be a number, return False in that case + return (im % (pi/2)).is_zero + + def _eval_is_extended_real(self): + if self.args[0].is_extended_real: + return True + + def _eval_is_positive(self): + if self.args[0].is_extended_real: + return self.args[0].is_positive + + def _eval_is_negative(self): + if self.args[0].is_extended_real: + return self.args[0].is_negative + + def _eval_is_finite(self): + arg = self.args[0] + + re, im = arg.as_real_imag() + denom = cos(im)**2 + sinh(re)**2 + if denom == 0: + return False + elif denom.is_number: + return True + if arg.is_extended_real: + return True + + def _eval_is_zero(self): + arg = self.args[0] + if arg.is_zero: + return True + + +class coth(HyperbolicFunction): + r""" + ``coth(x)`` is the hyperbolic cotangent of ``x``. + + The hyperbolic cotangent function is $\frac{\cosh(x)}{\sinh(x)}$. + + Examples + ======== + + >>> from sympy import coth + >>> from sympy.abc import x + >>> coth(x) + coth(x) + + See Also + ======== + + sinh, cosh, acoth + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return -1/sinh(self.args[0])**2 + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return acoth + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.One + elif arg is S.NegativeInfinity: + return S.NegativeOne + elif arg.is_zero: + return S.ComplexInfinity + elif arg.is_negative: + return -cls(-arg) + else: + if arg is S.ComplexInfinity: + return S.NaN + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + if i_coeff.could_extract_minus_sign(): + return I * cot(-i_coeff) + return -I * cot(i_coeff) + else: + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_Add: + x, m = _peeloff_ipi(arg) + if m: + cothm = coth(m*pi*I) + if cothm is S.ComplexInfinity: + return coth(x) + else: # cothm == 0 + return tanh(x) + + if arg.is_zero: + return S.ComplexInfinity + + if arg.func == asinh: + x = arg.args[0] + return sqrt(1 + x**2)/x + + if arg.func == acosh: + x = arg.args[0] + return x/(sqrt(x - 1) * sqrt(x + 1)) + + if arg.func == atanh: + return 1/arg.args[0] + + if arg.func == acoth: + return arg.args[0] + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return 1 / sympify(x) + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + B = bernoulli(n + 1) + F = factorial(n + 1) + + return 2**(n + 1) * B/F * x**n + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + from sympy.functions.elementary.trigonometric import (cos, sin) + if self.args[0].is_extended_real: + if deep: + hints['complex'] = False + return (self.expand(deep, **hints), S.Zero) + else: + return (self, S.Zero) + if deep: + re, im = self.args[0].expand(deep, **hints).as_real_imag() + else: + re, im = self.args[0].as_real_imag() + denom = sinh(re)**2 + sin(im)**2 + return (sinh(re)*cosh(re)/denom, -sin(im)*cos(im)/denom) + + def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs): + neg_exp, pos_exp = exp(-arg), exp(arg) + return (pos_exp + neg_exp)/(pos_exp - neg_exp) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + neg_exp, pos_exp = exp(-arg), exp(arg) + return (pos_exp + neg_exp)/(pos_exp - neg_exp) + + def _eval_rewrite_as_sinh(self, arg, **kwargs): + return -I*sinh(pi*I/2 - arg, evaluate=False)/sinh(arg) + + def _eval_rewrite_as_cosh(self, arg, **kwargs): + return -I*cosh(arg)/cosh(pi*I/2 - arg, evaluate=False) + + def _eval_rewrite_as_tanh(self, arg, **kwargs): + return 1/tanh(arg) + + def _eval_is_positive(self): + if self.args[0].is_extended_real: + return self.args[0].is_positive + + def _eval_is_negative(self): + if self.args[0].is_extended_real: + return self.args[0].is_negative + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.series.order import Order + arg = self.args[0].as_leading_term(x) + + if x in arg.free_symbols and Order(1, x).contains(arg): + return 1/arg + else: + return self.func(arg) + + def _eval_expand_trig(self, **hints): + arg = self.args[0] + if arg.is_Add: + CX = [coth(x, evaluate=False)._eval_expand_trig() for x in arg.args] + p = [[], []] + n = len(arg.args) + for i in range(n, -1, -1): + p[(n - i) % 2].append(symmetric_poly(i, CX)) + return Add(*p[0])/Add(*p[1]) + elif arg.is_Mul: + coeff, x = arg.as_coeff_Mul(rational=True) + if coeff.is_Integer and coeff > 1: + c = coth(x, evaluate=False) + p = [[], []] + for i in range(coeff, -1, -1): + p[(coeff - i) % 2].append(binomial(coeff, i)*c**i) + return Add(*p[0])/Add(*p[1]) + return coth(arg) + + +class ReciprocalHyperbolicFunction(HyperbolicFunction): + """Base class for reciprocal functions of hyperbolic functions. """ + + #To be defined in class + _reciprocal_of = None + _is_even: FuzzyBool = None + _is_odd: FuzzyBool = None + + @classmethod + def eval(cls, arg): + if arg.could_extract_minus_sign(): + if cls._is_even: + return cls(-arg) + if cls._is_odd: + return -cls(-arg) + + t = cls._reciprocal_of.eval(arg) + if hasattr(arg, 'inverse') and arg.inverse() == cls: + return arg.args[0] + return 1/t if t is not None else t + + def _call_reciprocal(self, method_name, *args, **kwargs): + # Calls method_name on _reciprocal_of + o = self._reciprocal_of(self.args[0]) + return getattr(o, method_name)(*args, **kwargs) + + def _calculate_reciprocal(self, method_name, *args, **kwargs): + # If calling method_name on _reciprocal_of returns a value != None + # then return the reciprocal of that value + t = self._call_reciprocal(method_name, *args, **kwargs) + return 1/t if t is not None else t + + def _rewrite_reciprocal(self, method_name, arg): + # Special handling for rewrite functions. If reciprocal rewrite returns + # unmodified expression, then return None + t = self._call_reciprocal(method_name, arg) + if t is not None and t != self._reciprocal_of(arg): + return 1/t + + def _eval_rewrite_as_exp(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_exp", arg) + + def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_tractable", arg) + + def _eval_rewrite_as_tanh(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_tanh", arg) + + def _eval_rewrite_as_coth(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_coth", arg) + + def as_real_imag(self, deep = True, **hints): + return (1 / self._reciprocal_of(self.args[0])).as_real_imag(deep, **hints) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def _eval_expand_complex(self, deep=True, **hints): + re_part, im_part = self.as_real_imag(deep=True, **hints) + return re_part + I*im_part + + def _eval_expand_trig(self, **hints): + return self._calculate_reciprocal("_eval_expand_trig", **hints) + + def _eval_as_leading_term(self, x, logx, cdir): + return (1/self._reciprocal_of(self.args[0]))._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_is_extended_real(self): + return self._reciprocal_of(self.args[0]).is_extended_real + + def _eval_is_finite(self): + return (1/self._reciprocal_of(self.args[0])).is_finite + + +class csch(ReciprocalHyperbolicFunction): + r""" + ``csch(x)`` is the hyperbolic cosecant of ``x``. + + The hyperbolic cosecant function is $\frac{2}{e^x - e^{-x}}$ + + Examples + ======== + + >>> from sympy import csch + >>> from sympy.abc import x + >>> csch(x) + csch(x) + + See Also + ======== + + sinh, cosh, tanh, sech, asinh, acosh + """ + + _reciprocal_of = sinh + _is_odd = True + + def fdiff(self, argindex=1): + """ + Returns the first derivative of this function + """ + if argindex == 1: + return -coth(self.args[0]) * csch(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + """ + Returns the next term in the Taylor series expansion + """ + if n == 0: + return 1/sympify(x) + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + B = bernoulli(n + 1) + F = factorial(n + 1) + + return 2 * (1 - 2**n) * B/F * x**n + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return I / sin(I * arg, evaluate=False) + + def _eval_rewrite_as_csc(self, arg, **kwargs): + return I * csc(I * arg, evaluate=False) + + def _eval_rewrite_as_cosh(self, arg, **kwargs): + return I / cosh(arg + I * pi / 2, evaluate=False) + + def _eval_rewrite_as_sinh(self, arg, **kwargs): + return 1 / sinh(arg) + + def _eval_is_positive(self): + if self.args[0].is_extended_real: + return self.args[0].is_positive + + def _eval_is_negative(self): + if self.args[0].is_extended_real: + return self.args[0].is_negative + + +class sech(ReciprocalHyperbolicFunction): + r""" + ``sech(x)`` is the hyperbolic secant of ``x``. + + The hyperbolic secant function is $\frac{2}{e^x + e^{-x}}$ + + Examples + ======== + + >>> from sympy import sech + >>> from sympy.abc import x + >>> sech(x) + sech(x) + + See Also + ======== + + sinh, cosh, tanh, coth, csch, asinh, acosh + """ + + _reciprocal_of = cosh + _is_even = True + + def fdiff(self, argindex=1): + if argindex == 1: + return - tanh(self.args[0])*sech(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + return euler(n) / factorial(n) * x**(n) + + def _eval_rewrite_as_cos(self, arg, **kwargs): + return 1 / cos(I * arg, evaluate=False) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + return sec(I * arg, evaluate=False) + + def _eval_rewrite_as_sinh(self, arg, **kwargs): + return I / sinh(arg + I * pi /2, evaluate=False) + + def _eval_rewrite_as_cosh(self, arg, **kwargs): + return 1 / cosh(arg) + + def _eval_is_positive(self): + if self.args[0].is_extended_real: + return True + + +############################################################################### +############################# HYPERBOLIC INVERSES ############################# +############################################################################### + +class InverseHyperbolicFunction(DefinedFunction): + """Base class for inverse hyperbolic functions.""" + + pass + + +class asinh(InverseHyperbolicFunction): + """ + ``asinh(x)`` is the inverse hyperbolic sine of ``x``. + + The inverse hyperbolic sine function. + + Examples + ======== + + >>> from sympy import asinh + >>> from sympy.abc import x + >>> asinh(x).diff(x) + 1/sqrt(x**2 + 1) + >>> asinh(1) + log(1 + sqrt(2)) + + See Also + ======== + + acosh, atanh, sinh + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return 1/sqrt(self.args[0]**2 + 1) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.NegativeInfinity + elif arg.is_zero: + return S.Zero + elif arg is S.One: + return log(sqrt(2) + 1) + elif arg is S.NegativeOne: + return log(sqrt(2) - 1) + elif arg.is_negative: + return -cls(-arg) + else: + if arg is S.ComplexInfinity: + return S.ComplexInfinity + + if arg.is_zero: + return S.Zero + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + return I * asin(i_coeff) + else: + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if isinstance(arg, sinh) and arg.args[0].is_number: + z = arg.args[0] + if z.is_real: + return z + r, i = match_real_imag(z) + if r is not None and i is not None: + f = floor((i + pi/2)/pi) + m = z - I*pi*f + even = f.is_even + if even is True: + return m + elif even is False: + return -m + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) >= 2 and n > 2: + p = previous_terms[-2] + return -p * (n - 2)**2/(n*(n - 1)) * x**2 + else: + k = (n - 1) // 2 + R = RisingFactorial(S.Half, k) + F = factorial(k) + return S.NegativeOne**k * R / F * x**n / n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0.is_zero: + return arg.as_leading_term(x) + + if x0 is S.NaN: + expr = self.func(arg.as_leading_term(x)) + if expr.is_finite: + return expr + else: + return self + + # Handling branch points + if x0 in (-I, I, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + # Handling points lying on branch cuts (-I*oo, -I) U (I, I*oo) + if (1 + x0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if re(ndir).is_positive: + if im(x0).is_negative: + return -self.func(x0) - I*pi + elif re(ndir).is_negative: + if im(x0).is_positive: + return -self.func(x0) + I*pi + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # asinh + arg = self.args[0] + arg0 = arg.subs(x, 0) + + # Handling branch points + if arg0 in (I, -I): + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + + # Handling points lying on branch cuts (-I*oo, -I) U (I, I*oo) + if (1 + arg0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if re(ndir).is_positive: + if im(arg0).is_negative: + return -res - I*pi + elif re(ndir).is_negative: + if im(arg0).is_positive: + return -res + I*pi + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, x, **kwargs): + return log(x + sqrt(x**2 + 1)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_atanh(self, x, **kwargs): + return atanh(x/sqrt(1 + x**2)) + + def _eval_rewrite_as_acosh(self, x, **kwargs): + ix = I*x + return I*(sqrt(1 - ix)/sqrt(ix - 1) * acosh(ix) - pi/2) + + def _eval_rewrite_as_asin(self, x, **kwargs): + return -I * asin(I * x, evaluate=False) + + def _eval_rewrite_as_acos(self, x, **kwargs): + return I * acos(I * x, evaluate=False) - I*pi/2 + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return sinh + + def _eval_is_zero(self): + return self.args[0].is_zero + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + def _eval_is_finite(self): + return self.args[0].is_finite + + +class acosh(InverseHyperbolicFunction): + """ + ``acosh(x)`` is the inverse hyperbolic cosine of ``x``. + + The inverse hyperbolic cosine function. + + Examples + ======== + + >>> from sympy import acosh + >>> from sympy.abc import x + >>> acosh(x).diff(x) + 1/(sqrt(x - 1)*sqrt(x + 1)) + >>> acosh(1) + 0 + + See Also + ======== + + asinh, atanh, cosh + """ + + def fdiff(self, argindex=1): + if argindex == 1: + arg = self.args[0] + return 1/(sqrt(arg - 1)*sqrt(arg + 1)) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.Infinity + elif arg.is_zero: + return pi*I / 2 + elif arg is S.One: + return S.Zero + elif arg is S.NegativeOne: + return pi*I + + if arg.is_number: + cst_table = _acosh_table() + + if arg in cst_table: + if arg.is_extended_real: + return cst_table[arg]*I + return cst_table[arg] + + if arg is S.ComplexInfinity: + return S.ComplexInfinity + if arg == I*S.Infinity: + return S.Infinity + I*pi/2 + if arg == -I*S.Infinity: + return S.Infinity - I*pi/2 + + if arg.is_zero: + return pi*I*S.Half + + if isinstance(arg, cosh) and arg.args[0].is_number: + z = arg.args[0] + if z.is_real: + return Abs(z) + r, i = match_real_imag(z) + if r is not None and i is not None: + f = floor(i/pi) + m = z - I*pi*f + even = f.is_even + if even is True: + if r.is_nonnegative: + return m + elif r.is_negative: + return -m + elif even is False: + m -= I*pi + if r.is_nonpositive: + return -m + elif r.is_positive: + return m + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return I*pi/2 + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) >= 2 and n > 2: + p = previous_terms[-2] + return p * (n - 2)**2/(n*(n - 1)) * x**2 + else: + k = (n - 1) // 2 + R = RisingFactorial(S.Half, k) + F = factorial(k) + return -R / F * I * x**n / n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + # Handling branch points + if x0 in (-S.One, S.Zero, S.One, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + if x0 is S.NaN: + expr = self.func(arg.as_leading_term(x)) + if expr.is_finite: + return expr + else: + return self + + # Handling points lying on branch cuts (-oo, 1) + if (x0 - 1).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if (x0 + 1).is_negative: + return self.func(x0) - 2*I*pi + return -self.func(x0) + elif not im(ndir).is_positive: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # acosh + arg = self.args[0] + arg0 = arg.subs(x, 0) + + # Handling branch points + if arg0 in (S.One, S.NegativeOne): + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + + # Handling points lying on branch cuts (-oo, 1) + if (arg0 - 1).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if (arg0 + 1).is_negative: + return res - 2*I*pi + return -res + elif not im(ndir).is_positive: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, x, **kwargs): + return log(x + sqrt(x + 1) * sqrt(x - 1)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_acos(self, x, **kwargs): + return sqrt(x - 1)/sqrt(1 - x) * acos(x) + + def _eval_rewrite_as_asin(self, x, **kwargs): + return sqrt(x - 1)/sqrt(1 - x) * (pi/2 - asin(x)) + + def _eval_rewrite_as_asinh(self, x, **kwargs): + return sqrt(x - 1)/sqrt(1 - x) * (pi/2 + I*asinh(I*x, evaluate=False)) + + def _eval_rewrite_as_atanh(self, x, **kwargs): + sxm1 = sqrt(x - 1) + s1mx = sqrt(1 - x) + sx2m1 = sqrt(x**2 - 1) + return (pi/2*sxm1/s1mx*(1 - x * sqrt(1/x**2)) + + sxm1*sqrt(x + 1)/sx2m1 * atanh(sx2m1/x)) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return cosh + + def _eval_is_zero(self): + if (self.args[0] - 1).is_zero: + return True + + def _eval_is_extended_real(self): + return fuzzy_and([self.args[0].is_extended_real, (self.args[0] - 1).is_extended_nonnegative]) + + def _eval_is_finite(self): + return self.args[0].is_finite + + +class atanh(InverseHyperbolicFunction): + """ + ``atanh(x)`` is the inverse hyperbolic tangent of ``x``. + + The inverse hyperbolic tangent function. + + Examples + ======== + + >>> from sympy import atanh + >>> from sympy.abc import x + >>> atanh(x).diff(x) + 1/(1 - x**2) + + See Also + ======== + + asinh, acosh, tanh + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return 1/(1 - self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg.is_zero: + return S.Zero + elif arg is S.One: + return S.Infinity + elif arg is S.NegativeOne: + return S.NegativeInfinity + elif arg is S.Infinity: + return -I * atan(arg) + elif arg is S.NegativeInfinity: + return I * atan(-arg) + elif arg.is_negative: + return -cls(-arg) + else: + if arg is S.ComplexInfinity: + from sympy.calculus.accumulationbounds import AccumBounds + return I*AccumBounds(-pi/2, pi/2) + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + return I * atan(i_coeff) + else: + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_zero: + return S.Zero + + if isinstance(arg, tanh) and arg.args[0].is_number: + z = arg.args[0] + if z.is_real: + return z + r, i = match_real_imag(z) + if r is not None and i is not None: + f = floor(2*i/pi) + even = f.is_even + m = z - I*f*pi/2 + if even is True: + return m + elif even is False: + return m - I*pi/2 + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + return x**n / n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0.is_zero: + return arg.as_leading_term(x) + if x0 is S.NaN: + expr = self.func(arg.as_leading_term(x)) + if expr.is_finite: + return expr + else: + return self + + # Handling branch points + if x0 in (-S.One, S.One, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + # Handling points lying on branch cuts (-oo, -1] U [1, oo) + if (1 - x0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if x0.is_negative: + return self.func(x0) - I*pi + elif im(ndir).is_positive: + if x0.is_positive: + return self.func(x0) + I*pi + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # atanh + arg = self.args[0] + arg0 = arg.subs(x, 0) + + # Handling branch points + if arg0 in (S.One, S.NegativeOne): + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + + # Handling points lying on branch cuts (-oo, -1] U [1, oo) + if (1 - arg0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if arg0.is_negative: + return res - I*pi + elif im(ndir).is_positive: + if arg0.is_positive: + return res + I*pi + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, x, **kwargs): + return (log(1 + x) - log(1 - x)) / 2 + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_asinh(self, x, **kwargs): + f = sqrt(1/(x**2 - 1)) + return (pi*x/(2*sqrt(-x**2)) - + sqrt(-x)*sqrt(1 - x**2)/sqrt(x)*f*asinh(f)) + + def _eval_is_zero(self): + if self.args[0].is_zero: + return True + + def _eval_is_extended_real(self): + return fuzzy_and([self.args[0].is_extended_real, (1 - self.args[0]).is_nonnegative, (self.args[0] + 1).is_nonnegative]) + + def _eval_is_finite(self): + return fuzzy_not(fuzzy_or([(self.args[0] - 1).is_zero, (self.args[0] + 1).is_zero])) + + def _eval_is_imaginary(self): + return self.args[0].is_imaginary + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return tanh + + +class acoth(InverseHyperbolicFunction): + """ + ``acoth(x)`` is the inverse hyperbolic cotangent of ``x``. + + The inverse hyperbolic cotangent function. + + Examples + ======== + + >>> from sympy import acoth + >>> from sympy.abc import x + >>> acoth(x).diff(x) + 1/(1 - x**2) + + See Also + ======== + + asinh, acosh, coth + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return 1/(1 - self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Zero + elif arg is S.NegativeInfinity: + return S.Zero + elif arg.is_zero: + return pi*I / 2 + elif arg is S.One: + return S.Infinity + elif arg is S.NegativeOne: + return S.NegativeInfinity + elif arg.is_negative: + return -cls(-arg) + else: + if arg is S.ComplexInfinity: + return S.Zero + + i_coeff = _imaginary_unit_as_coefficient(arg) + + if i_coeff is not None: + return -I * acot(i_coeff) + else: + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_zero: + return pi*I*S.Half + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return -I*pi/2 + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + return x**n / n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.ComplexInfinity: + return (1/arg).as_leading_term(x) + if x0 is S.NaN: + expr = self.func(arg.as_leading_term(x)) + if expr.is_finite: + return expr + else: + return self + + # Handling branch points + if x0 in (-S.One, S.One, S.Zero): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + # Handling points lying on branch cuts [-1, 1] + if x0.is_real and (1 - x0**2).is_positive: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if x0.is_positive: + return self.func(x0) + I*pi + elif im(ndir).is_positive: + if x0.is_negative: + return self.func(x0) - I*pi + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # acoth + arg = self.args[0] + arg0 = arg.subs(x, 0) + + # Handling branch points + if arg0 in (S.One, S.NegativeOne): + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + + # Handling points lying on branch cuts [-1, 1] + if arg0.is_real and (1 - arg0**2).is_positive: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if arg0.is_positive: + return res + I*pi + elif im(ndir).is_positive: + if arg0.is_negative: + return res - I*pi + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, x, **kwargs): + return (log(1 + 1/x) - log(1 - 1/x)) / 2 + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_atanh(self, x, **kwargs): + return atanh(1/x) + + def _eval_rewrite_as_asinh(self, x, **kwargs): + return (pi*I/2*(sqrt((x - 1)/x)*sqrt(x/(x - 1)) - sqrt(1 + 1/x)*sqrt(x/(x + 1))) + + x*sqrt(1/x**2)*asinh(sqrt(1/(x**2 - 1)))) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return coth + + def _eval_is_extended_real(self): + return fuzzy_and([self.args[0].is_extended_real, fuzzy_or([(self.args[0] - 1).is_extended_nonnegative, (self.args[0] + 1).is_extended_nonpositive])]) + + def _eval_is_finite(self): + return fuzzy_not(fuzzy_or([(self.args[0] - 1).is_zero, (self.args[0] + 1).is_zero])) + + +class asech(InverseHyperbolicFunction): + """ + ``asech(x)`` is the inverse hyperbolic secant of ``x``. + + The inverse hyperbolic secant function. + + Examples + ======== + + >>> from sympy import asech, sqrt, S + >>> from sympy.abc import x + >>> asech(x).diff(x) + -1/(x*sqrt(1 - x**2)) + >>> asech(1).diff(x) + 0 + >>> asech(1) + 0 + >>> asech(S(2)) + I*pi/3 + >>> asech(-sqrt(2)) + 3*I*pi/4 + >>> asech((sqrt(6) - sqrt(2))) + I*pi/12 + + See Also + ======== + + asinh, atanh, cosh, acoth + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hyperbolic_function + .. [2] https://dlmf.nist.gov/4.37 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcSech/ + + """ + + def fdiff(self, argindex=1): + if argindex == 1: + z = self.args[0] + return -1/(z*sqrt(1 - z**2)) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return pi*I / 2 + elif arg is S.NegativeInfinity: + return pi*I / 2 + elif arg.is_zero: + return S.Infinity + elif arg is S.One: + return S.Zero + elif arg is S.NegativeOne: + return pi*I + + if arg.is_number: + cst_table = _asech_table() + + if arg in cst_table: + if arg.is_extended_real: + return cst_table[arg]*I + return cst_table[arg] + + if arg is S.ComplexInfinity: + from sympy.calculus.accumulationbounds import AccumBounds + return I*AccumBounds(-pi/2, pi/2) + + if arg.is_zero: + return S.Infinity + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return log(2 / x) + elif n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 2 and n > 2: + p = previous_terms[-2] + return p * ((n - 1)*(n-2)) * x**2/(4 * (n//2)**2) + else: + k = n // 2 + R = RisingFactorial(S.Half, k) * n + F = factorial(k) * n // 2 * n // 2 + return -1 * R / F * x**n / 4 + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + # Handling branch points + if x0 in (-S.One, S.Zero, S.One, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + if x0 is S.NaN: + expr = self.func(arg.as_leading_term(x)) + if expr.is_finite: + return expr + else: + return self + + # Handling points lying on branch cuts (-oo, 0] U (1, oo) + if x0.is_negative or (1 - x0).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_positive: + if x0.is_positive or (x0 + 1).is_negative: + return -self.func(x0) + return self.func(x0) - 2*I*pi + elif not im(ndir).is_negative: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # asech + from sympy.series.order import O + arg = self.args[0] + arg0 = arg.subs(x, 0) + + # Handling branch points + if arg0 is S.One: + t = Dummy('t', positive=True) + ser = asech(S.One - t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.One - self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + if arg0 is S.NegativeOne: + t = Dummy('t', positive=True) + ser = asech(S.NegativeOne + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.One + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else I*pi + O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + + # Handling points lying on branch cuts (-oo, 0] U (1, oo) + if arg0.is_negative or (1 - arg0).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_positive: + if arg0.is_positive or (arg0 + 1).is_negative: + return -res + return res - 2*I*pi + elif not im(ndir).is_negative: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return sech + + def _eval_rewrite_as_log(self, arg, **kwargs): + return log(1/arg + sqrt(1/arg - 1) * sqrt(1/arg + 1)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_acosh(self, arg, **kwargs): + return acosh(1/arg) + + def _eval_rewrite_as_asinh(self, arg, **kwargs): + return sqrt(1/arg - 1)/sqrt(1 - 1/arg)*(I*asinh(I/arg, evaluate=False) + + pi*S.Half) + + def _eval_rewrite_as_atanh(self, x, **kwargs): + return (I*pi*(1 - sqrt(x)*sqrt(1/x) - I/2*sqrt(-x)/sqrt(x) - I/2*sqrt(x**2)/sqrt(-x**2)) + + sqrt(1/(x + 1))*sqrt(x + 1)*atanh(sqrt(1 - x**2))) + + def _eval_rewrite_as_acsch(self, x, **kwargs): + return sqrt(1/x - 1)/sqrt(1 - 1/x)*(pi/2 - I*acsch(I*x, evaluate=False)) + + def _eval_is_extended_real(self): + return fuzzy_and([self.args[0].is_extended_real, self.args[0].is_nonnegative, (1 - self.args[0]).is_nonnegative]) + + def _eval_is_finite(self): + return fuzzy_not(self.args[0].is_zero) + + +class acsch(InverseHyperbolicFunction): + """ + ``acsch(x)`` is the inverse hyperbolic cosecant of ``x``. + + The inverse hyperbolic cosecant function. + + Examples + ======== + + >>> from sympy import acsch, sqrt, I + >>> from sympy.abc import x + >>> acsch(x).diff(x) + -1/(x**2*sqrt(1 + x**(-2))) + >>> acsch(1).diff(x) + 0 + >>> acsch(1) + log(1 + sqrt(2)) + >>> acsch(I) + -I*pi/2 + >>> acsch(-2*I) + I*pi/6 + >>> acsch(I*(sqrt(6) - sqrt(2))) + -5*I*pi/12 + + See Also + ======== + + asinh + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hyperbolic_function + .. [2] https://dlmf.nist.gov/4.37 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcCsch/ + + """ + + def fdiff(self, argindex=1): + if argindex == 1: + z = self.args[0] + return -1/(z**2*sqrt(1 + 1/z**2)) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Zero + elif arg is S.NegativeInfinity: + return S.Zero + elif arg.is_zero: + return S.ComplexInfinity + elif arg is S.One: + return log(1 + sqrt(2)) + elif arg is S.NegativeOne: + return - log(1 + sqrt(2)) + + if arg.is_number: + cst_table = _acsch_table() + + if arg in cst_table: + return cst_table[arg]*I + + if arg is S.ComplexInfinity: + return S.Zero + + if arg.is_infinite: + return S.Zero + + if arg.is_zero: + return S.ComplexInfinity + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return log(2 / x) + elif n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 2 and n > 2: + p = previous_terms[-2] + return -p * ((n - 1)*(n-2)) * x**2/(4 * (n//2)**2) + else: + k = n // 2 + R = RisingFactorial(S.Half, k) * n + F = factorial(k) * n // 2 * n // 2 + return S.NegativeOne**(k +1) * R / F * x**n / 4 + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + # Handling branch points + if x0 in (-I, I, S.Zero): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + if x0 is S.NaN: + expr = self.func(arg.as_leading_term(x)) + if expr.is_finite: + return expr + else: + return self + + if x0 is S.ComplexInfinity: + return (1/arg).as_leading_term(x) + # Handling points lying on branch cuts (-I, I) + if x0.is_imaginary and (1 + x0**2).is_positive: + ndir = arg.dir(x, cdir if cdir else 1) + if re(ndir).is_positive: + if im(x0).is_positive: + return -self.func(x0) - I*pi + elif re(ndir).is_negative: + if im(x0).is_negative: + return -self.func(x0) + I*pi + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # acsch + from sympy.series.order import O + arg = self.args[0] + arg0 = arg.subs(x, 0) + + # Handling branch points + if arg0 is I: + t = Dummy('t', positive=True) + ser = acsch(I + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = -I + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else -I*pi/2 + O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + res = ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + return res + + if arg0 == S.NegativeOne*I: + t = Dummy('t', positive=True) + ser = acsch(-I + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = I + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else I*pi/2 + O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + + # Handling points lying on branch cuts (-I, I) + if arg0.is_imaginary and (1 + arg0**2).is_positive: + ndir = self.args[0].dir(x, cdir if cdir else 1) + if re(ndir).is_positive: + if im(arg0).is_positive: + return -res - I*pi + elif re(ndir).is_negative: + if im(arg0).is_negative: + return -res + I*pi + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return csch + + def _eval_rewrite_as_log(self, arg, **kwargs): + return log(1/arg + sqrt(1/arg**2 + 1)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_asinh(self, arg, **kwargs): + return asinh(1/arg) + + def _eval_rewrite_as_acosh(self, arg, **kwargs): + return I*(sqrt(1 - I/arg)/sqrt(I/arg - 1)* + acosh(I/arg, evaluate=False) - pi*S.Half) + + def _eval_rewrite_as_atanh(self, arg, **kwargs): + arg2 = arg**2 + arg2p1 = arg2 + 1 + return sqrt(-arg2)/arg*(pi*S.Half - + sqrt(-arg2p1**2)/arg2p1*atanh(sqrt(arg2p1))) + + def _eval_is_zero(self): + return self.args[0].is_infinite + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + def _eval_is_finite(self): + return fuzzy_not(self.args[0].is_zero) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/integers.py b/phivenv/Lib/site-packages/sympy/functions/elementary/integers.py new file mode 100644 index 0000000000000000000000000000000000000000..d0b58d32399144c39133855475d70c01b70b1a3f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/integers.py @@ -0,0 +1,710 @@ +from __future__ import annotations + +from sympy.core.basic import Basic +from sympy.core.expr import Expr + +from sympy.core import Add, S +from sympy.core.evalf import get_integer_part, PrecisionExhausted +from sympy.core.function import DefinedFunction +from sympy.core.logic import fuzzy_or, fuzzy_and +from sympy.core.numbers import Integer, int_valued +from sympy.core.relational import Gt, Lt, Ge, Le, Relational, is_eq, is_le, is_lt +from sympy.core.sympify import _sympify +from sympy.functions.elementary.complexes import im, re +from sympy.multipledispatch import dispatch + +############################################################################### +######################### FLOOR and CEILING FUNCTIONS ######################### +############################################################################### + + +class RoundFunction(DefinedFunction): + """Abstract base class for rounding functions.""" + + args: tuple[Expr] + + @classmethod + def eval(cls, arg): + if (v := cls._eval_number(arg)) is not None: + return v + if (v := cls._eval_const_number(arg)) is not None: + return v + + if arg.is_integer or arg.is_finite is False: + return arg + if arg.is_imaginary or (S.ImaginaryUnit*arg).is_real: + i = im(arg) + if not i.has(S.ImaginaryUnit): + return cls(i)*S.ImaginaryUnit + return cls(arg, evaluate=False) + + # Integral, numerical, symbolic part + ipart = npart = spart = S.Zero + + # Extract integral (or complex integral) terms + intof = lambda x: int(x) if int_valued(x) else ( + x if x.is_integer else None) + for t in Add.make_args(arg): + if t.is_imaginary and (i := intof(im(t))) is not None: + ipart += i*S.ImaginaryUnit + elif (i := intof(t)) is not None: + ipart += i + elif t.is_number: + npart += t + else: + spart += t + + if not (npart or spart): + return ipart + + # Evaluate npart numerically if independent of spart + if npart and ( + not spart or + npart.is_real and (spart.is_imaginary or (S.ImaginaryUnit*spart).is_real) or + npart.is_imaginary and spart.is_real): + try: + r, i = get_integer_part( + npart, cls._dir, {}, return_ints=True) + ipart += Integer(r) + Integer(i)*S.ImaginaryUnit + npart = S.Zero + except (PrecisionExhausted, NotImplementedError): + pass + + spart += npart + if not spart: + return ipart + elif spart.is_imaginary or (S.ImaginaryUnit*spart).is_real: + return ipart + cls(im(spart), evaluate=False)*S.ImaginaryUnit + elif isinstance(spart, (floor, ceiling)): + return ipart + spart + else: + return ipart + cls(spart, evaluate=False) + + @classmethod + def _eval_number(cls, arg): + raise NotImplementedError() + + def _eval_is_finite(self): + return self.args[0].is_finite + + def _eval_is_real(self): + return self.args[0].is_real + + def _eval_is_integer(self): + return self.args[0].is_real + + +class floor(RoundFunction): + """ + Floor is a univariate function which returns the largest integer + value not greater than its argument. This implementation + generalizes floor to complex numbers by taking the floor of the + real and imaginary parts separately. + + Examples + ======== + + >>> from sympy import floor, E, I, S, Float, Rational + >>> floor(17) + 17 + >>> floor(Rational(23, 10)) + 2 + >>> floor(2*E) + 5 + >>> floor(-Float(0.567)) + -1 + >>> floor(-I/2) + -I + >>> floor(S(5)/2 + 5*I/2) + 2 + 2*I + + See Also + ======== + + sympy.functions.elementary.integers.ceiling + + References + ========== + + .. [1] "Concrete mathematics" by Graham, pp. 87 + .. [2] https://mathworld.wolfram.com/FloorFunction.html + + """ + _dir = -1 + + @classmethod + def _eval_number(cls, arg): + if arg.is_Number: + return arg.floor() + if any(isinstance(i, j) + for i in (arg, -arg) for j in (floor, ceiling)): + return arg + if arg.is_NumberSymbol: + return arg.approximation_interval(Integer)[0] + + @classmethod + def _eval_const_number(cls, arg): + if arg.is_real: + if arg.is_zero: + return S.Zero + if arg.is_positive: + num, den = arg.as_numer_denom() + s = den.is_negative + if s is None: + return None + if s: + num, den = -num, -den + # 0 <= num/den < 1 -> 0 + if is_lt(num, den): + return S.Zero + # 1 <= num/den < 2 -> 1 + if fuzzy_and([is_le(den, num), is_lt(num, 2*den)]): + return S.One + if arg.is_negative: + num, den = arg.as_numer_denom() + s = den.is_negative + if s is None: + return None + if s: + num, den = -num, -den + # -1 <= num/den < 0 -> -1 + if is_le(-den, num): + return S.NegativeOne + # -2 <= num/den < -1 -> -2 + if fuzzy_and([is_le(-2*den, num), is_lt(num, -den)]): + return Integer(-2) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + arg = self.args[0] + arg0 = arg.subs(x, 0) + r = self.subs(x, 0) + if arg0 is S.NaN or isinstance(arg0, AccumBounds): + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + r = floor(arg0) + if arg0.is_finite: + if arg0 == r: + ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) + if ndir.is_negative: + return r - 1 + elif ndir.is_positive: + return r + else: + raise NotImplementedError("Not sure of sign of %s" % ndir) + else: + return r + return arg.as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + arg = self.args[0] + arg0 = arg.subs(x, 0) + r = self.subs(x, 0) + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + r = floor(arg0) + if arg0.is_infinite: + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.series.order import Order + s = arg._eval_nseries(x, n, logx, cdir) + o = Order(1, (x, 0)) if n <= 0 else AccumBounds(-1, 0) + return s + o + if arg0 == r: + ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) + if ndir.is_negative: + return r - 1 + elif ndir.is_positive: + return r + else: + raise NotImplementedError("Not sure of sign of %s" % ndir) + else: + return r + + def _eval_is_negative(self): + return self.args[0].is_negative + + def _eval_is_nonnegative(self): + return self.args[0].is_nonnegative + + def _eval_rewrite_as_ceiling(self, arg, **kwargs): + return -ceiling(-arg) + + def _eval_rewrite_as_frac(self, arg, **kwargs): + return arg - frac(arg) + + def __le__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] < other + 1 + if other.is_number and other.is_real: + return self.args[0] < ceiling(other) + if self.args[0] == other and other.is_real: + return S.true + if other is S.Infinity and self.is_finite: + return S.true + + return Le(self, other, evaluate=False) + + def __ge__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] >= other + if other.is_number and other.is_real: + return self.args[0] >= ceiling(other) + if self.args[0] == other and other.is_real and other.is_noninteger: + return S.false + if other is S.NegativeInfinity and self.is_finite: + return S.true + + return Ge(self, other, evaluate=False) + + def __gt__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] >= other + 1 + if other.is_number and other.is_real: + return self.args[0] >= ceiling(other) + if self.args[0] == other and other.is_real: + return S.false + if other is S.NegativeInfinity and self.is_finite: + return S.true + + return Gt(self, other, evaluate=False) + + def __lt__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] < other + if other.is_number and other.is_real: + return self.args[0] < ceiling(other) + if self.args[0] == other and other.is_real and other.is_noninteger: + return S.true + if other is S.Infinity and self.is_finite: + return S.true + + return Lt(self, other, evaluate=False) + + +@dispatch(floor, Expr) +def _eval_is_eq(lhs, rhs): # noqa:F811 + return is_eq(lhs.rewrite(ceiling), rhs) or \ + is_eq(lhs.rewrite(frac),rhs) + + +class ceiling(RoundFunction): + """ + Ceiling is a univariate function which returns the smallest integer + value not less than its argument. This implementation + generalizes ceiling to complex numbers by taking the ceiling of the + real and imaginary parts separately. + + Examples + ======== + + >>> from sympy import ceiling, E, I, S, Float, Rational + >>> ceiling(17) + 17 + >>> ceiling(Rational(23, 10)) + 3 + >>> ceiling(2*E) + 6 + >>> ceiling(-Float(0.567)) + 0 + >>> ceiling(I/2) + I + >>> ceiling(S(5)/2 + 5*I/2) + 3 + 3*I + + See Also + ======== + + sympy.functions.elementary.integers.floor + + References + ========== + + .. [1] "Concrete mathematics" by Graham, pp. 87 + .. [2] https://mathworld.wolfram.com/CeilingFunction.html + + """ + _dir = 1 + + @classmethod + def _eval_number(cls, arg): + if arg.is_Number: + return arg.ceiling() + if any(isinstance(i, j) + for i in (arg, -arg) for j in (floor, ceiling)): + return arg + if arg.is_NumberSymbol: + return arg.approximation_interval(Integer)[1] + + @classmethod + def _eval_const_number(cls, arg): + if arg.is_real: + if arg.is_zero: + return S.Zero + if arg.is_positive: + num, den = arg.as_numer_denom() + s = den.is_negative + if s is None: + return None + if s: + num, den = -num, -den + # 0 < num/den <= 1 -> 1 + if is_le(num, den): + return S.One + # 1 < num/den <= 2 -> 2 + if fuzzy_and([is_lt(den, num), is_le(num, 2*den)]): + return Integer(2) + if arg.is_negative: + num, den = arg.as_numer_denom() + s = den.is_negative + if s is None: + return None + if s: + num, den = -num, -den + # -1 < num/den <= 0 -> 0 + if is_lt(-den, num): + return S.Zero + # -2 < num/den <= -1 -> -1 + if fuzzy_and([is_lt(-2*den, num), is_le(num, -den)]): + return S.NegativeOne + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + arg = self.args[0] + arg0 = arg.subs(x, 0) + r = self.subs(x, 0) + if arg0 is S.NaN or isinstance(arg0, AccumBounds): + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + r = ceiling(arg0) + if arg0.is_finite: + if arg0 == r: + ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) + if ndir.is_negative: + return r + elif ndir.is_positive: + return r + 1 + else: + raise NotImplementedError("Not sure of sign of %s" % ndir) + else: + return r + return arg.as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + arg = self.args[0] + arg0 = arg.subs(x, 0) + r = self.subs(x, 0) + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + r = ceiling(arg0) + if arg0.is_infinite: + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.series.order import Order + s = arg._eval_nseries(x, n, logx, cdir) + o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1) + return s + o + if arg0 == r: + ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1) + if ndir.is_negative: + return r + elif ndir.is_positive: + return r + 1 + else: + raise NotImplementedError("Not sure of sign of %s" % ndir) + else: + return r + + def _eval_rewrite_as_floor(self, arg, **kwargs): + return -floor(-arg) + + def _eval_rewrite_as_frac(self, arg, **kwargs): + return arg + frac(-arg) + + def _eval_is_positive(self): + return self.args[0].is_positive + + def _eval_is_nonpositive(self): + return self.args[0].is_nonpositive + + def __lt__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] <= other - 1 + if other.is_number and other.is_real: + return self.args[0] <= floor(other) + if self.args[0] == other and other.is_real: + return S.false + if other is S.Infinity and self.is_finite: + return S.true + + return Lt(self, other, evaluate=False) + + def __gt__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] > other + if other.is_number and other.is_real: + return self.args[0] > floor(other) + if self.args[0] == other and other.is_real and other.is_noninteger: + return S.true + if other is S.NegativeInfinity and self.is_finite: + return S.true + + return Gt(self, other, evaluate=False) + + def __ge__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] > other - 1 + if other.is_number and other.is_real: + return self.args[0] > floor(other) + if self.args[0] == other and other.is_real: + return S.true + if other is S.NegativeInfinity and self.is_finite: + return S.true + + return Ge(self, other, evaluate=False) + + def __le__(self, other): + other = S(other) + if self.args[0].is_real: + if other.is_integer: + return self.args[0] <= other + if other.is_number and other.is_real: + return self.args[0] <= floor(other) + if self.args[0] == other and other.is_real and other.is_noninteger: + return S.false + if other is S.Infinity and self.is_finite: + return S.true + + return Le(self, other, evaluate=False) + + +@dispatch(ceiling, Basic) # type:ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + return is_eq(lhs.rewrite(floor), rhs) or is_eq(lhs.rewrite(frac),rhs) + + +class frac(DefinedFunction): + r"""Represents the fractional part of x + + For real numbers it is defined [1]_ as + + .. math:: + x - \left\lfloor{x}\right\rfloor + + Examples + ======== + + >>> from sympy import Symbol, frac, Rational, floor, I + >>> frac(Rational(4, 3)) + 1/3 + >>> frac(-Rational(4, 3)) + 2/3 + + returns zero for integer arguments + + >>> n = Symbol('n', integer=True) + >>> frac(n) + 0 + + rewrite as floor + + >>> x = Symbol('x') + >>> frac(x).rewrite(floor) + x - floor(x) + + for complex arguments + + >>> r = Symbol('r', real=True) + >>> t = Symbol('t', real=True) + >>> frac(t + I*r) + I*frac(r) + frac(t) + + See Also + ======== + + sympy.functions.elementary.integers.floor + sympy.functions.elementary.integers.ceiling + + References + =========== + + .. [1] https://en.wikipedia.org/wiki/Fractional_part + .. [2] https://mathworld.wolfram.com/FractionalPart.html + + """ + @classmethod + def eval(cls, arg): + from sympy.calculus.accumulationbounds import AccumBounds + + def _eval(arg): + if arg in (S.Infinity, S.NegativeInfinity): + return AccumBounds(0, 1) + if arg.is_integer: + return S.Zero + if arg.is_number: + if arg is S.NaN: + return S.NaN + elif arg is S.ComplexInfinity: + return S.NaN + else: + return arg - floor(arg) + return cls(arg, evaluate=False) + + real, imag = S.Zero, S.Zero + for t in Add.make_args(arg): + # Two checks are needed for complex arguments + # see issue-7649 for details + if t.is_imaginary or (S.ImaginaryUnit*t).is_real: + i = im(t) + if not i.has(S.ImaginaryUnit): + imag += i + else: + real += t + else: + real += t + + real = _eval(real) + imag = _eval(imag) + return real + S.ImaginaryUnit*imag + + def _eval_rewrite_as_floor(self, arg, **kwargs): + return arg - floor(arg) + + def _eval_rewrite_as_ceiling(self, arg, **kwargs): + return arg + ceiling(-arg) + + def _eval_is_finite(self): + return True + + def _eval_is_real(self): + return self.args[0].is_extended_real + + def _eval_is_imaginary(self): + return self.args[0].is_imaginary + + def _eval_is_integer(self): + return self.args[0].is_integer + + def _eval_is_zero(self): + return fuzzy_or([self.args[0].is_zero, self.args[0].is_integer]) + + def _eval_is_negative(self): + return False + + def __ge__(self, other): + if self.is_extended_real: + other = _sympify(other) + # Check if other <= 0 + if other.is_extended_nonpositive: + return S.true + # Check if other >= 1 + res = self._value_one_or_more(other) + if res is not None: + return not(res) + return Ge(self, other, evaluate=False) + + def __gt__(self, other): + if self.is_extended_real: + other = _sympify(other) + # Check if other < 0 + res = self._value_one_or_more(other) + if res is not None: + return not(res) + # Check if other >= 1 + if other.is_extended_negative: + return S.true + return Gt(self, other, evaluate=False) + + def __le__(self, other): + if self.is_extended_real: + other = _sympify(other) + # Check if other < 0 + if other.is_extended_negative: + return S.false + # Check if other >= 1 + res = self._value_one_or_more(other) + if res is not None: + return res + return Le(self, other, evaluate=False) + + def __lt__(self, other): + if self.is_extended_real: + other = _sympify(other) + # Check if other <= 0 + if other.is_extended_nonpositive: + return S.false + # Check if other >= 1 + res = self._value_one_or_more(other) + if res is not None: + return res + return Lt(self, other, evaluate=False) + + def _value_one_or_more(self, other): + if other.is_extended_real: + if other.is_number: + res = other >= 1 + if res and not isinstance(res, Relational): + return S.true + if other.is_integer and other.is_positive: + return S.true + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + arg = self.args[0] + arg0 = arg.subs(x, 0) + r = self.subs(x, 0) + + if arg0.is_finite: + if r.is_zero: + ndir = arg.dir(x, cdir=cdir) + if ndir.is_negative: + return S.One + return (arg - arg0).as_leading_term(x, logx=logx, cdir=cdir) + else: + return r + elif arg0 in (S.ComplexInfinity, S.Infinity, S.NegativeInfinity): + return AccumBounds(0, 1) + return arg.as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.series.order import Order + arg = self.args[0] + arg0 = arg.subs(x, 0) + r = self.subs(x, 0) + + if arg0.is_infinite: + from sympy.calculus.accumulationbounds import AccumBounds + o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1) + Order(x**n, (x, 0)) + return o + else: + res = (arg - arg0)._eval_nseries(x, n, logx=logx, cdir=cdir) + if r.is_zero: + ndir = arg.dir(x, cdir=cdir) + res += S.One if ndir.is_negative else S.Zero + else: + res += r + return res + + +@dispatch(frac, Basic) # type:ignore +def _eval_is_eq(lhs, rhs): # noqa:F811 + if (lhs.rewrite(floor) == rhs) or \ + (lhs.rewrite(ceiling) == rhs): + return True + # Check if other < 0 + if rhs.is_extended_negative: + return False + # Check if other >= 1 + res = lhs._value_one_or_more(rhs) + if res is not None: + return False diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/miscellaneous.py b/phivenv/Lib/site-packages/sympy/functions/elementary/miscellaneous.py new file mode 100644 index 0000000000000000000000000000000000000000..c7f3016bc7ea0d5c4ad778cf9922c941acb7fc44 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/miscellaneous.py @@ -0,0 +1,915 @@ +from sympy.core import S, sympify, NumberKind +from sympy.utilities.iterables import sift +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.operations import LatticeOp, ShortCircuit +from sympy.core.function import (Application, Lambda, + ArgumentIndexError, DefinedFunction) +from sympy.core.expr import Expr +from sympy.core.exprtools import factor_terms +from sympy.core.mod import Mod +from sympy.core.mul import Mul +from sympy.core.numbers import Rational +from sympy.core.power import Pow +from sympy.core.relational import Eq, Relational +from sympy.core.singleton import Singleton +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy +from sympy.core.rules import Transform +from sympy.core.logic import fuzzy_and, fuzzy_or, _torf +from sympy.core.traversal import walk +from sympy.core.numbers import Integer +from sympy.logic.boolalg import And, Or + + +def _minmax_as_Piecewise(op, *args): + # helper for Min/Max rewrite as Piecewise + from sympy.functions.elementary.piecewise import Piecewise + ec = [] + for i, a in enumerate(args): + c = [Relational(a, args[j], op) for j in range(i + 1, len(args))] + ec.append((a, And(*c))) + return Piecewise(*ec) + + +class IdentityFunction(Lambda, metaclass=Singleton): + """ + The identity function + + Examples + ======== + + >>> from sympy import Id, Symbol + >>> x = Symbol('x') + >>> Id(x) + x + + """ + + _symbol = Dummy('x') + + @property + def signature(self): + return Tuple(self._symbol) + + @property + def expr(self): + return self._symbol + + +Id = S.IdentityFunction + +############################################################################### +############################# ROOT and SQUARE ROOT FUNCTION ################### +############################################################################### + + +def sqrt(arg, evaluate=None): + """Returns the principal square root. + + Parameters + ========== + + evaluate : bool, optional + The parameter determines if the expression should be evaluated. + If ``None``, its value is taken from + ``global_parameters.evaluate``. + + Examples + ======== + + >>> from sympy import sqrt, Symbol, S + >>> x = Symbol('x') + + >>> sqrt(x) + sqrt(x) + + >>> sqrt(x)**2 + x + + Note that sqrt(x**2) does not simplify to x. + + >>> sqrt(x**2) + sqrt(x**2) + + This is because the two are not equal to each other in general. + For example, consider x == -1: + + >>> from sympy import Eq + >>> Eq(sqrt(x**2), x).subs(x, -1) + False + + This is because sqrt computes the principal square root, so the square may + put the argument in a different branch. This identity does hold if x is + positive: + + >>> y = Symbol('y', positive=True) + >>> sqrt(y**2) + y + + You can force this simplification by using the powdenest() function with + the force option set to True: + + >>> from sympy import powdenest + >>> sqrt(x**2) + sqrt(x**2) + >>> powdenest(sqrt(x**2), force=True) + x + + To get both branches of the square root you can use the rootof function: + + >>> from sympy import rootof + + >>> [rootof(x**2-3,i) for i in (0,1)] + [-sqrt(3), sqrt(3)] + + Although ``sqrt`` is printed, there is no ``sqrt`` function so looking for + ``sqrt`` in an expression will fail: + + >>> from sympy.utilities.misc import func_name + >>> func_name(sqrt(x)) + 'Pow' + >>> sqrt(x).has(sqrt) + False + + To find ``sqrt`` look for ``Pow`` with an exponent of ``1/2``: + + >>> (x + 1/sqrt(x)).find(lambda i: i.is_Pow and abs(i.exp) is S.Half) + {1/sqrt(x)} + + See Also + ======== + + sympy.polys.rootoftools.rootof, root, real_root + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Square_root + .. [2] https://en.wikipedia.org/wiki/Principal_value + """ + # arg = sympify(arg) is handled by Pow + return Pow(arg, S.Half, evaluate=evaluate) + + +def cbrt(arg, evaluate=None): + """Returns the principal cube root. + + Parameters + ========== + + evaluate : bool, optional + The parameter determines if the expression should be evaluated. + If ``None``, its value is taken from + ``global_parameters.evaluate``. + + Examples + ======== + + >>> from sympy import cbrt, Symbol + >>> x = Symbol('x') + + >>> cbrt(x) + x**(1/3) + + >>> cbrt(x)**3 + x + + Note that cbrt(x**3) does not simplify to x. + + >>> cbrt(x**3) + (x**3)**(1/3) + + This is because the two are not equal to each other in general. + For example, consider `x == -1`: + + >>> from sympy import Eq + >>> Eq(cbrt(x**3), x).subs(x, -1) + False + + This is because cbrt computes the principal cube root, this + identity does hold if `x` is positive: + + >>> y = Symbol('y', positive=True) + >>> cbrt(y**3) + y + + See Also + ======== + + sympy.polys.rootoftools.rootof, root, real_root + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Cube_root + .. [2] https://en.wikipedia.org/wiki/Principal_value + + """ + return Pow(arg, Rational(1, 3), evaluate=evaluate) + + +def root(arg, n, k=0, evaluate=None): + r"""Returns the *k*-th *n*-th root of ``arg``. + + Parameters + ========== + + k : int, optional + Should be an integer in $\{0, 1, ..., n-1\}$. + Defaults to the principal root if $0$. + + evaluate : bool, optional + The parameter determines if the expression should be evaluated. + If ``None``, its value is taken from + ``global_parameters.evaluate``. + + Examples + ======== + + >>> from sympy import root, Rational + >>> from sympy.abc import x, n + + >>> root(x, 2) + sqrt(x) + + >>> root(x, 3) + x**(1/3) + + >>> root(x, n) + x**(1/n) + + >>> root(x, -Rational(2, 3)) + x**(-3/2) + + To get the k-th n-th root, specify k: + + >>> root(-2, 3, 2) + -(-1)**(2/3)*2**(1/3) + + To get all n n-th roots you can use the rootof function. + The following examples show the roots of unity for n + equal 2, 3 and 4: + + >>> from sympy import rootof + + >>> [rootof(x**2 - 1, i) for i in range(2)] + [-1, 1] + + >>> [rootof(x**3 - 1,i) for i in range(3)] + [1, -1/2 - sqrt(3)*I/2, -1/2 + sqrt(3)*I/2] + + >>> [rootof(x**4 - 1,i) for i in range(4)] + [-1, 1, -I, I] + + SymPy, like other symbolic algebra systems, returns the + complex root of negative numbers. This is the principal + root and differs from the text-book result that one might + be expecting. For example, the cube root of -8 does not + come back as -2: + + >>> root(-8, 3) + 2*(-1)**(1/3) + + The real_root function can be used to either make the principal + result real (or simply to return the real root directly): + + >>> from sympy import real_root + >>> real_root(_) + -2 + >>> real_root(-32, 5) + -2 + + Alternatively, the n//2-th n-th root of a negative number can be + computed with root: + + >>> root(-32, 5, 5//2) + -2 + + See Also + ======== + + sympy.polys.rootoftools.rootof + sympy.core.intfunc.integer_nthroot + sqrt, real_root + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Square_root + .. [2] https://en.wikipedia.org/wiki/Real_root + .. [3] https://en.wikipedia.org/wiki/Root_of_unity + .. [4] https://en.wikipedia.org/wiki/Principal_value + .. [5] https://mathworld.wolfram.com/CubeRoot.html + + """ + n = sympify(n) + if k: + return Mul(Pow(arg, S.One/n, evaluate=evaluate), S.NegativeOne**(2*k/n), evaluate=evaluate) + return Pow(arg, 1/n, evaluate=evaluate) + + +def real_root(arg, n=None, evaluate=None): + r"""Return the real *n*'th-root of *arg* if possible. + + Parameters + ========== + + n : int or None, optional + If *n* is ``None``, then all instances of + $(-n)^{1/\text{odd}}$ will be changed to $-n^{1/\text{odd}}$. + This will only create a real root of a principal root. + The presence of other factors may cause the result to not be + real. + + evaluate : bool, optional + The parameter determines if the expression should be evaluated. + If ``None``, its value is taken from + ``global_parameters.evaluate``. + + Examples + ======== + + >>> from sympy import root, real_root + + >>> real_root(-8, 3) + -2 + >>> root(-8, 3) + 2*(-1)**(1/3) + >>> real_root(_) + -2 + + If one creates a non-principal root and applies real_root, the + result will not be real (so use with caution): + + >>> root(-8, 3, 2) + -2*(-1)**(2/3) + >>> real_root(_) + -2*(-1)**(2/3) + + See Also + ======== + + sympy.polys.rootoftools.rootof + sympy.core.intfunc.integer_nthroot + root, sqrt + """ + from sympy.functions.elementary.complexes import Abs, im, sign + from sympy.functions.elementary.piecewise import Piecewise + if n is not None: + return Piecewise( + (root(arg, n, evaluate=evaluate), Or(Eq(n, S.One), Eq(n, S.NegativeOne))), + (Mul(sign(arg), root(Abs(arg), n, evaluate=evaluate), evaluate=evaluate), + And(Eq(im(arg), S.Zero), Eq(Mod(n, 2), S.One))), + (root(arg, n, evaluate=evaluate), True)) + rv = sympify(arg) + n1pow = Transform(lambda x: -(-x.base)**x.exp, + lambda x: + x.is_Pow and + x.base.is_negative and + x.exp.is_Rational and + x.exp.p == 1 and x.exp.q % 2) + return rv.xreplace(n1pow) + +############################################################################### +############################# MINIMUM and MAXIMUM ############################# +############################################################################### + + +class MinMaxBase(Expr, LatticeOp): + def __new__(cls, *args, **assumptions): + from sympy.core.parameters import global_parameters + evaluate = assumptions.pop('evaluate', global_parameters.evaluate) + args = (sympify(arg) for arg in args) + + # first standard filter, for cls.zero and cls.identity + # also reshape Max(a, Max(b, c)) to Max(a, b, c) + + if evaluate: + try: + args = frozenset(cls._new_args_filter(args)) + except ShortCircuit: + return cls.zero + # remove redundant args that are easily identified + args = cls._collapse_arguments(args, **assumptions) + # find local zeros + args = cls._find_localzeros(args, **assumptions) + args = frozenset(args) + + if not args: + return cls.identity + + if len(args) == 1: + return list(args).pop() + + # base creation + obj = Expr.__new__(cls, *ordered(args), **assumptions) + obj._argset = args + return obj + + @classmethod + def _collapse_arguments(cls, args, **assumptions): + """Remove redundant args. + + Examples + ======== + + >>> from sympy import Min, Max + >>> from sympy.abc import a, b, c, d, e + + Any arg in parent that appears in any + parent-like function in any of the flat args + of parent can be removed from that sub-arg: + + >>> Min(a, Max(b, Min(a, c, d))) + Min(a, Max(b, Min(c, d))) + + If the arg of parent appears in an opposite-than parent + function in any of the flat args of parent that function + can be replaced with the arg: + + >>> Min(a, Max(b, Min(c, d, Max(a, e)))) + Min(a, Max(b, Min(a, c, d))) + """ + if not args: + return args + args = list(ordered(args)) + if cls == Min: + other = Max + else: + other = Min + + # find global comparable max of Max and min of Min if a new + # value is being introduced in these args at position 0 of + # the ordered args + if args[0].is_number: + sifted = mins, maxs = [], [] + for i in args: + for v in walk(i, Min, Max): + if v.args[0].is_comparable: + sifted[isinstance(v, Max)].append(v) + small = Min.identity + for i in mins: + v = i.args[0] + if v.is_number and (v < small) == True: + small = v + big = Max.identity + for i in maxs: + v = i.args[0] + if v.is_number and (v > big) == True: + big = v + # at the point when this function is called from __new__, + # there may be more than one numeric arg present since + # local zeros have not been handled yet, so look through + # more than the first arg + if cls == Min: + for arg in args: + if not arg.is_number: + break + if (arg < small) == True: + small = arg + elif cls == Max: + for arg in args: + if not arg.is_number: + break + if (arg > big) == True: + big = arg + T = None + if cls == Min: + if small != Min.identity: + other = Max + T = small + elif big != Max.identity: + other = Min + T = big + if T is not None: + # remove numerical redundancy + for i in range(len(args)): + a = args[i] + if isinstance(a, other): + a0 = a.args[0] + if ((a0 > T) if other == Max else (a0 < T)) == True: + args[i] = cls.identity + + # remove redundant symbolic args + def do(ai, a): + if not isinstance(ai, (Min, Max)): + return ai + cond = a in ai.args + if not cond: + return ai.func(*[do(i, a) for i in ai.args], + evaluate=False) + if isinstance(ai, cls): + return ai.func(*[do(i, a) for i in ai.args if i != a], + evaluate=False) + return a + for i, a in enumerate(args): + args[i + 1:] = [do(ai, a) for ai in args[i + 1:]] + + # factor out common elements as for + # Min(Max(x, y), Max(x, z)) -> Max(x, Min(y, z)) + # and vice versa when swapping Min/Max -- do this only for the + # easy case where all functions contain something in common; + # trying to find some optimal subset of args to modify takes + # too long + + def factor_minmax(args): + is_other = lambda arg: isinstance(arg, other) + other_args, remaining_args = sift(args, is_other, binary=True) + if not other_args: + return args + + # Min(Max(x, y, z), Max(x, y, u, v)) -> {x,y}, ({z}, {u,v}) + arg_sets = [set(arg.args) for arg in other_args] + common = set.intersection(*arg_sets) + if not common: + return args + + new_other_args = list(common) + arg_sets_diff = [arg_set - common for arg_set in arg_sets] + + # If any set is empty after removing common then all can be + # discarded e.g. Min(Max(a, b, c), Max(a, b)) -> Max(a, b) + if all(arg_sets_diff): + other_args_diff = [other(*s, evaluate=False) for s in arg_sets_diff] + new_other_args.append(cls(*other_args_diff, evaluate=False)) + + other_args_factored = other(*new_other_args, evaluate=False) + return remaining_args + [other_args_factored] + + if len(args) > 1: + args = factor_minmax(args) + + return args + + @classmethod + def _new_args_filter(cls, arg_sequence): + """ + Generator filtering args. + + first standard filter, for cls.zero and cls.identity. + Also reshape ``Max(a, Max(b, c))`` to ``Max(a, b, c)``, + and check arguments for comparability + """ + for arg in arg_sequence: + # pre-filter, checking comparability of arguments + if not isinstance(arg, Expr) or arg.is_extended_real is False or ( + arg.is_number and + not arg.is_comparable): + raise ValueError("The argument '%s' is not comparable." % arg) + + if arg == cls.zero: + raise ShortCircuit(arg) + elif arg == cls.identity: + continue + elif arg.func == cls: + yield from arg.args + else: + yield arg + + @classmethod + def _find_localzeros(cls, values, **options): + """ + Sequentially allocate values to localzeros. + + When a value is identified as being more extreme than another member it + replaces that member; if this is never true, then the value is simply + appended to the localzeros. + """ + localzeros = set() + for v in values: + is_newzero = True + localzeros_ = list(localzeros) + for z in localzeros_: + if id(v) == id(z): + is_newzero = False + else: + con = cls._is_connected(v, z) + if con: + is_newzero = False + if con is True or con == cls: + localzeros.remove(z) + localzeros.update([v]) + if is_newzero: + localzeros.update([v]) + return localzeros + + @classmethod + def _is_connected(cls, x, y): + """ + Check if x and y are connected somehow. + """ + for i in range(2): + if x == y: + return True + t, f = Max, Min + for op in "><": + for j in range(2): + try: + if op == ">": + v = x >= y + else: + v = x <= y + except TypeError: + return False # non-real arg + if not v.is_Relational: + return t if v else f + t, f = f, t + x, y = y, x + x, y = y, x # run next pass with reversed order relative to start + # simplification can be expensive, so be conservative + # in what is attempted + x = factor_terms(x - y) + y = S.Zero + + return False + + def _eval_derivative(self, s): + # f(x).diff(s) -> x.diff(s) * f.fdiff(1)(s) + i = 0 + l = [] + for a in self.args: + i += 1 + da = a.diff(s) + if da.is_zero: + continue + try: + df = self.fdiff(i) + except ArgumentIndexError: + df = super().fdiff(i) + l.append(df * da) + return Add(*l) + + def _eval_rewrite_as_Abs(self, *args, **kwargs): + from sympy.functions.elementary.complexes import Abs + s = (args[0] + self.func(*args[1:]))/2 + d = abs(args[0] - self.func(*args[1:]))/2 + return (s + d if isinstance(self, Max) else s - d).rewrite(Abs) + + def evalf(self, n=15, **options): + return self.func(*[a.evalf(n, **options) for a in self.args]) + + def n(self, *args, **kwargs): + return self.evalf(*args, **kwargs) + + _eval_is_algebraic = lambda s: _torf(i.is_algebraic for i in s.args) + _eval_is_antihermitian = lambda s: _torf(i.is_antihermitian for i in s.args) + _eval_is_commutative = lambda s: _torf(i.is_commutative for i in s.args) + _eval_is_complex = lambda s: _torf(i.is_complex for i in s.args) + _eval_is_composite = lambda s: _torf(i.is_composite for i in s.args) + _eval_is_even = lambda s: _torf(i.is_even for i in s.args) + _eval_is_finite = lambda s: _torf(i.is_finite for i in s.args) + _eval_is_hermitian = lambda s: _torf(i.is_hermitian for i in s.args) + _eval_is_imaginary = lambda s: _torf(i.is_imaginary for i in s.args) + _eval_is_infinite = lambda s: _torf(i.is_infinite for i in s.args) + _eval_is_integer = lambda s: _torf(i.is_integer for i in s.args) + _eval_is_irrational = lambda s: _torf(i.is_irrational for i in s.args) + _eval_is_negative = lambda s: _torf(i.is_negative for i in s.args) + _eval_is_noninteger = lambda s: _torf(i.is_noninteger for i in s.args) + _eval_is_nonnegative = lambda s: _torf(i.is_nonnegative for i in s.args) + _eval_is_nonpositive = lambda s: _torf(i.is_nonpositive for i in s.args) + _eval_is_nonzero = lambda s: _torf(i.is_nonzero for i in s.args) + _eval_is_odd = lambda s: _torf(i.is_odd for i in s.args) + _eval_is_polar = lambda s: _torf(i.is_polar for i in s.args) + _eval_is_positive = lambda s: _torf(i.is_positive for i in s.args) + _eval_is_prime = lambda s: _torf(i.is_prime for i in s.args) + _eval_is_rational = lambda s: _torf(i.is_rational for i in s.args) + _eval_is_real = lambda s: _torf(i.is_real for i in s.args) + _eval_is_extended_real = lambda s: _torf(i.is_extended_real for i in s.args) + _eval_is_transcendental = lambda s: _torf(i.is_transcendental for i in s.args) + _eval_is_zero = lambda s: _torf(i.is_zero for i in s.args) + + +class Max(MinMaxBase, Application): + r""" + Return, if possible, the maximum value of the list. + + When number of arguments is equal one, then + return this argument. + + When number of arguments is equal two, then + return, if possible, the value from (a, b) that is $\ge$ the other. + + In common case, when the length of list greater than 2, the task + is more complicated. Return only the arguments, which are greater + than others, if it is possible to determine directional relation. + + If is not possible to determine such a relation, return a partially + evaluated result. + + Assumptions are used to make the decision too. + + Also, only comparable arguments are permitted. + + It is named ``Max`` and not ``max`` to avoid conflicts + with the built-in function ``max``. + + + Examples + ======== + + >>> from sympy import Max, Symbol, oo + >>> from sympy.abc import x, y, z + >>> p = Symbol('p', positive=True) + >>> n = Symbol('n', negative=True) + + >>> Max(x, -2) + Max(-2, x) + >>> Max(x, -2).subs(x, 3) + 3 + >>> Max(p, -2) + p + >>> Max(x, y) + Max(x, y) + >>> Max(x, y) == Max(y, x) + True + >>> Max(x, Max(y, z)) + Max(x, y, z) + >>> Max(n, 8, p, 7, -oo) + Max(8, p) + >>> Max (1, x, oo) + oo + + * Algorithm + + The task can be considered as searching of supremums in the + directed complete partial orders [1]_. + + The source values are sequentially allocated by the isolated subsets + in which supremums are searched and result as Max arguments. + + If the resulted supremum is single, then it is returned. + + The isolated subsets are the sets of values which are only the comparable + with each other in the current set. E.g. natural numbers are comparable with + each other, but not comparable with the `x` symbol. Another example: the + symbol `x` with negative assumption is comparable with a natural number. + + Also there are "least" elements, which are comparable with all others, + and have a zero property (maximum or minimum for all elements). + For example, in case of $\infty$, the allocation operation is terminated + and only this value is returned. + + Assumption: + - if $A > B > C$ then $A > C$ + - if $A = B$ then $B$ can be removed + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Directed_complete_partial_order + .. [2] https://en.wikipedia.org/wiki/Lattice_%28order%29 + + See Also + ======== + + Min : find minimum values + """ + zero = S.Infinity + identity = S.NegativeInfinity + + def fdiff( self, argindex ): + from sympy.functions.special.delta_functions import Heaviside + n = len(self.args) + if 0 < argindex and argindex <= n: + argindex -= 1 + if n == 2: + return Heaviside(self.args[argindex] - self.args[1 - argindex]) + newargs = tuple([self.args[i] for i in range(n) if i != argindex]) + return Heaviside(self.args[argindex] - Max(*newargs)) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Heaviside(self, *args, **kwargs): + from sympy.functions.special.delta_functions import Heaviside + return Add(*[j*Mul(*[Heaviside(j - i) for i in args if i!=j]) \ + for j in args]) + + def _eval_rewrite_as_Piecewise(self, *args, **kwargs): + return _minmax_as_Piecewise('>=', *args) + + def _eval_is_positive(self): + return fuzzy_or(a.is_positive for a in self.args) + + def _eval_is_nonnegative(self): + return fuzzy_or(a.is_nonnegative for a in self.args) + + def _eval_is_negative(self): + return fuzzy_and(a.is_negative for a in self.args) + + +class Min(MinMaxBase, Application): + """ + Return, if possible, the minimum value of the list. + It is named ``Min`` and not ``min`` to avoid conflicts + with the built-in function ``min``. + + Examples + ======== + + >>> from sympy import Min, Symbol, oo + >>> from sympy.abc import x, y + >>> p = Symbol('p', positive=True) + >>> n = Symbol('n', negative=True) + + >>> Min(x, -2) + Min(-2, x) + >>> Min(x, -2).subs(x, 3) + -2 + >>> Min(p, -3) + -3 + >>> Min(x, y) + Min(x, y) + >>> Min(n, 8, p, -7, p, oo) + Min(-7, n) + + See Also + ======== + + Max : find maximum values + """ + zero = S.NegativeInfinity + identity = S.Infinity + + def fdiff( self, argindex ): + from sympy.functions.special.delta_functions import Heaviside + n = len(self.args) + if 0 < argindex and argindex <= n: + argindex -= 1 + if n == 2: + return Heaviside( self.args[1-argindex] - self.args[argindex] ) + newargs = tuple([ self.args[i] for i in range(n) if i != argindex]) + return Heaviside( Min(*newargs) - self.args[argindex] ) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Heaviside(self, *args, **kwargs): + from sympy.functions.special.delta_functions import Heaviside + return Add(*[j*Mul(*[Heaviside(i-j) for i in args if i!=j]) \ + for j in args]) + + def _eval_rewrite_as_Piecewise(self, *args, **kwargs): + return _minmax_as_Piecewise('<=', *args) + + def _eval_is_positive(self): + return fuzzy_and(a.is_positive for a in self.args) + + def _eval_is_nonnegative(self): + return fuzzy_and(a.is_nonnegative for a in self.args) + + def _eval_is_negative(self): + return fuzzy_or(a.is_negative for a in self.args) + + +class Rem(DefinedFunction): + """Returns the remainder when ``p`` is divided by ``q`` where ``p`` is finite + and ``q`` is not equal to zero. The result, ``p - int(p/q)*q``, has the same sign + as the divisor. + + Parameters + ========== + + p : Expr + Dividend. + + q : Expr + Divisor. + + Notes + ===== + + ``Rem`` corresponds to the ``%`` operator in C. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import Rem + >>> Rem(x**3, y) + Rem(x**3, y) + >>> Rem(x**3, y).subs({x: -5, y: 3}) + -2 + + See Also + ======== + + Mod + """ + kind = NumberKind + + @classmethod + def eval(cls, p, q): + """Return the function remainder if both p, q are numbers and q is not + zero. + """ + + if q.is_zero: + raise ZeroDivisionError("Division by zero") + if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False: + return S.NaN + if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1): + return S.Zero + + if q.is_Number: + if p.is_Number: + return p - Integer(p/q)*q diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/piecewise.py b/phivenv/Lib/site-packages/sympy/functions/elementary/piecewise.py new file mode 100644 index 0000000000000000000000000000000000000000..fe4a4d4f57e2c3af170dac994e11782b9ed54b8f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/piecewise.py @@ -0,0 +1,1517 @@ +from sympy.core import S, diff, Tuple, Dummy, Mul +from sympy.core.basic import Basic, as_Basic +from sympy.core.function import DefinedFunction +from sympy.core.numbers import Rational, NumberSymbol, _illegal +from sympy.core.parameters import global_parameters +from sympy.core.relational import (Lt, Gt, Eq, Ne, Relational, + _canonical, _canonical_coeff) +from sympy.core.sorting import ordered +from sympy.functions.elementary.miscellaneous import Max, Min +from sympy.logic.boolalg import (And, Boolean, distribute_and_over_or, Not, + true, false, Or, ITE, simplify_logic, to_cnf, distribute_or_over_and) +from sympy.utilities.iterables import uniq, sift, common_prefix +from sympy.utilities.misc import filldedent, func_name + +from itertools import product + +Undefined = S.NaN # Piecewise() + +class ExprCondPair(Tuple): + """Represents an expression, condition pair.""" + + def __new__(cls, expr, cond): + expr = as_Basic(expr) + if cond == True: + return Tuple.__new__(cls, expr, true) + elif cond == False: + return Tuple.__new__(cls, expr, false) + elif isinstance(cond, Basic) and cond.has(Piecewise): + cond = piecewise_fold(cond) + if isinstance(cond, Piecewise): + cond = cond.rewrite(ITE) + + if not isinstance(cond, Boolean): + raise TypeError(filldedent(''' + Second argument must be a Boolean, + not `%s`''' % func_name(cond))) + return Tuple.__new__(cls, expr, cond) + + @property + def expr(self): + """ + Returns the expression of this pair. + """ + return self.args[0] + + @property + def cond(self): + """ + Returns the condition of this pair. + """ + return self.args[1] + + @property + def is_commutative(self): + return self.expr.is_commutative + + def __iter__(self): + yield self.expr + yield self.cond + + def _eval_simplify(self, **kwargs): + return self.func(*[a.simplify(**kwargs) for a in self.args]) + + +class Piecewise(DefinedFunction): + """ + Represents a piecewise function. + + Usage: + + Piecewise( (expr,cond), (expr,cond), ... ) + - Each argument is a 2-tuple defining an expression and condition + - The conds are evaluated in turn returning the first that is True. + If any of the evaluated conds are not explicitly False, + e.g. ``x < 1``, the function is returned in symbolic form. + - If the function is evaluated at a place where all conditions are False, + nan will be returned. + - Pairs where the cond is explicitly False, will be removed and no pair + appearing after a True condition will ever be retained. If a single + pair with a True condition remains, it will be returned, even when + evaluation is False. + + Examples + ======== + + >>> from sympy import Piecewise, log, piecewise_fold + >>> from sympy.abc import x, y + >>> f = x**2 + >>> g = log(x) + >>> p = Piecewise((0, x < -1), (f, x <= 1), (g, True)) + >>> p.subs(x,1) + 1 + >>> p.subs(x,5) + log(5) + + Booleans can contain Piecewise elements: + + >>> cond = (x < y).subs(x, Piecewise((2, x < 0), (3, True))); cond + Piecewise((2, x < 0), (3, True)) < y + + The folded version of this results in a Piecewise whose + expressions are Booleans: + + >>> folded_cond = piecewise_fold(cond); folded_cond + Piecewise((2 < y, x < 0), (3 < y, True)) + + When a Boolean containing Piecewise (like cond) or a Piecewise + with Boolean expressions (like folded_cond) is used as a condition, + it is converted to an equivalent :class:`~.ITE` object: + + >>> Piecewise((1, folded_cond)) + Piecewise((1, ITE(x < 0, y > 2, y > 3))) + + When a condition is an ``ITE``, it will be converted to a simplified + Boolean expression: + + >>> piecewise_fold(_) + Piecewise((1, ((x >= 0) | (y > 2)) & ((y > 3) | (x < 0)))) + + See Also + ======== + + piecewise_fold + piecewise_exclusive + ITE + """ + + nargs = None + is_Piecewise = True + + def __new__(cls, *args, **options): + if len(args) == 0: + raise TypeError("At least one (expr, cond) pair expected.") + # (Try to) sympify args first + newargs = [] + for ec in args: + # ec could be a ExprCondPair or a tuple + pair = ExprCondPair(*getattr(ec, 'args', ec)) + cond = pair.cond + if cond is false: + continue + newargs.append(pair) + if cond is true: + break + + eval = options.pop('evaluate', global_parameters.evaluate) + if eval: + r = cls.eval(*newargs) + if r is not None: + return r + elif len(newargs) == 1 and newargs[0].cond == True: + return newargs[0].expr + + return Basic.__new__(cls, *newargs, **options) + + @classmethod + def eval(cls, *_args): + """Either return a modified version of the args or, if no + modifications were made, return None. + + Modifications that are made here: + + 1. relationals are made canonical + 2. any False conditions are dropped + 3. any repeat of a previous condition is ignored + 4. any args past one with a true condition are dropped + + If there are no args left, nan will be returned. + If there is a single arg with a True condition, its + corresponding expression will be returned. + + EXAMPLES + ======== + + >>> from sympy import Piecewise + >>> from sympy.abc import x + >>> cond = -x < -1 + >>> args = [(1, cond), (4, cond), (3, False), (2, True), (5, x < 1)] + >>> Piecewise(*args, evaluate=False) + Piecewise((1, -x < -1), (4, -x < -1), (2, True)) + >>> Piecewise(*args) + Piecewise((1, x > 1), (2, True)) + """ + if not _args: + return Undefined + + if len(_args) == 1 and _args[0][-1] == True: + return _args[0][0] + + newargs = _piecewise_collapse_arguments(_args) + + # some conditions may have been redundant + missing = len(newargs) != len(_args) + # some conditions may have changed + same = all(a == b for a, b in zip(newargs, _args)) + # if either change happened we return the expr with the + # updated args + if not newargs: + raise ValueError(filldedent(''' + There are no conditions (or none that + are not trivially false) to define an + expression.''')) + if missing or not same: + return cls(*newargs) + + def doit(self, **hints): + """ + Evaluate this piecewise function. + """ + newargs = [] + for e, c in self.args: + if hints.get('deep', True): + if isinstance(e, Basic): + newe = e.doit(**hints) + if newe != self: + e = newe + if isinstance(c, Basic): + c = c.doit(**hints) + newargs.append((e, c)) + return self.func(*newargs) + + def _eval_simplify(self, **kwargs): + return piecewise_simplify(self, **kwargs) + + def _eval_as_leading_term(self, x, logx, cdir): + for e, c in self.args: + if c == True or c.subs(x, 0) == True: + return e.as_leading_term(x) + + def _eval_adjoint(self): + return self.func(*[(e.adjoint(), c) for e, c in self.args]) + + def _eval_conjugate(self): + return self.func(*[(e.conjugate(), c) for e, c in self.args]) + + def _eval_derivative(self, x): + return self.func(*[(diff(e, x), c) for e, c in self.args]) + + def _eval_evalf(self, prec): + return self.func(*[(e._evalf(prec), c) for e, c in self.args]) + + def _eval_is_meromorphic(self, x, a): + # Conditions often implicitly assume that the argument is real. + # Hence, there needs to be some check for as_set. + if not a.is_real: + return None + + # Then, scan ExprCondPairs in the given order to find a piece that would contain a, + # possibly as a boundary point. + for e, c in self.args: + cond = c.subs(x, a) + + if cond.is_Relational: + return None + if a in c.as_set().boundary: + return None + # Apply expression if a is an interior point of the domain of e. + if cond: + return e._eval_is_meromorphic(x, a) + + def piecewise_integrate(self, x, **kwargs): + """Return the Piecewise with each expression being + replaced with its antiderivative. To obtain a continuous + antiderivative, use the :func:`~.integrate` function or method. + + Examples + ======== + + >>> from sympy import Piecewise + >>> from sympy.abc import x + >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True)) + >>> p.piecewise_integrate(x) + Piecewise((0, x < 0), (x, x < 1), (2*x, True)) + + Note that this does not give a continuous function, e.g. + at x = 1 the 3rd condition applies and the antiderivative + there is 2*x so the value of the antiderivative is 2: + + >>> anti = _ + >>> anti.subs(x, 1) + 2 + + The continuous derivative accounts for the integral *up to* + the point of interest, however: + + >>> p.integrate(x) + Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True)) + >>> _.subs(x, 1) + 1 + + See Also + ======== + Piecewise._eval_integral + """ + from sympy.integrals import integrate + return self.func(*[(integrate(e, x, **kwargs), c) for e, c in self.args]) + + def _handle_irel(self, x, handler): + """Return either None (if the conditions of self depend only on x) else + a Piecewise expression whose expressions (handled by the handler that + was passed) are paired with the governing x-independent relationals, + e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> + Piecewise( + (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), + (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), + (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), + (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) + """ + # identify governing relationals + rel = self.atoms(Relational) + irel = list(ordered([r for r in rel if x not in r.free_symbols + and r not in (S.true, S.false)])) + if irel: + args = {} + exprinorder = [] + for truth in product((1, 0), repeat=len(irel)): + reps = dict(zip(irel, truth)) + # only store the true conditions since the false are implied + # when they appear lower in the Piecewise args + if 1 not in truth: + cond = None # flag this one so it doesn't get combined + else: + andargs = Tuple(*[i for i in reps if reps[i]]) + free = list(andargs.free_symbols) + if len(free) == 1: + from sympy.solvers.inequalities import ( + reduce_inequalities, _solve_inequality) + try: + t = reduce_inequalities(andargs, free[0]) + # ValueError when there are potentially + # nonvanishing imaginary parts + except (ValueError, NotImplementedError): + # at least isolate free symbol on left + t = And(*[_solve_inequality( + a, free[0], linear=True) + for a in andargs]) + else: + t = And(*andargs) + if t is S.false: + continue # an impossible combination + cond = t + expr = handler(self.xreplace(reps)) + if isinstance(expr, self.func) and len(expr.args) == 1: + expr, econd = expr.args[0] + cond = And(econd, True if cond is None else cond) + # the ec pairs are being collected since all possibilities + # are being enumerated, but don't put the last one in since + # its expr might match a previous expression and it + # must appear last in the args + if cond is not None: + args.setdefault(expr, []).append(cond) + # but since we only store the true conditions we must maintain + # the order so that the expression with the most true values + # comes first + exprinorder.append(expr) + # convert collected conditions as args of Or + for k in args: + args[k] = Or(*args[k]) + # take them in the order obtained + args = [(e, args[e]) for e in uniq(exprinorder)] + # add in the last arg + args.append((expr, True)) + return Piecewise(*args) + + def _eval_integral(self, x, _first=True, **kwargs): + """Return the indefinite integral of the + Piecewise such that subsequent substitution of x with a + value will give the value of the integral (not including + the constant of integration) up to that point. To only + integrate the individual parts of Piecewise, use the + ``piecewise_integrate`` method. + + Examples + ======== + + >>> from sympy import Piecewise + >>> from sympy.abc import x + >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True)) + >>> p.integrate(x) + Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True)) + >>> p.piecewise_integrate(x) + Piecewise((0, x < 0), (x, x < 1), (2*x, True)) + + See Also + ======== + Piecewise.piecewise_integrate + """ + from sympy.integrals.integrals import integrate + + if _first: + def handler(ipw): + if isinstance(ipw, self.func): + return ipw._eval_integral(x, _first=False, **kwargs) + else: + return ipw.integrate(x, **kwargs) + irv = self._handle_irel(x, handler) + if irv is not None: + return irv + + # handle a Piecewise from -oo to oo with and no x-independent relationals + # ----------------------------------------------------------------------- + ok, abei = self._intervals(x) + if not ok: + from sympy.integrals.integrals import Integral + return Integral(self, x) # unevaluated + + pieces = [(a, b) for a, b, _, _ in abei] + oo = S.Infinity + done = [(-oo, oo, -1)] + for k, p in enumerate(pieces): + if p == (-oo, oo): + # all undone intervals will get this key + for j, (a, b, i) in enumerate(done): + if i == -1: + done[j] = a, b, k + break # nothing else to consider + N = len(done) - 1 + for j, (a, b, i) in enumerate(reversed(done)): + if i == -1: + j = N - j + done[j: j + 1] = _clip(p, (a, b), k) + done = [(a, b, i) for a, b, i in done if a != b] + + # append an arg if there is a hole so a reference to + # argument -1 will give Undefined + if any(i == -1 for (a, b, i) in done): + abei.append((-oo, oo, Undefined, -1)) + + # return the sum of the intervals + args = [] + sum = None + for a, b, i in done: + anti = integrate(abei[i][-2], x, **kwargs) + if sum is None: + sum = anti + else: + sum = sum.subs(x, a) + e = anti._eval_interval(x, a, x) + if sum.has(*_illegal) or e.has(*_illegal): + sum = anti + else: + sum += e + # see if we know whether b is contained in original + # condition + if b is S.Infinity: + cond = True + elif self.args[abei[i][-1]].cond.subs(x, b) == False: + cond = (x < b) + else: + cond = (x <= b) + args.append((sum, cond)) + return Piecewise(*args) + + def _eval_interval(self, sym, a, b, _first=True): + """Evaluates the function along the sym in a given interval [a, b]""" + # FIXME: Currently complex intervals are not supported. A possible + # replacement algorithm, discussed in issue 5227, can be found in the + # following papers; + # http://portal.acm.org/citation.cfm?id=281649 + # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4127&rep=rep1&type=pdf + + if a is None or b is None: + # In this case, it is just simple substitution + return super()._eval_interval(sym, a, b) + else: + x, lo, hi = map(as_Basic, (sym, a, b)) + + if _first: # get only x-dependent relationals + def handler(ipw): + if isinstance(ipw, self.func): + return ipw._eval_interval(x, lo, hi, _first=None) + else: + return ipw._eval_interval(x, lo, hi) + irv = self._handle_irel(x, handler) + if irv is not None: + return irv + + if (lo < hi) is S.false or ( + lo is S.Infinity or hi is S.NegativeInfinity): + rv = self._eval_interval(x, hi, lo, _first=False) + if isinstance(rv, Piecewise): + rv = Piecewise(*[(-e, c) for e, c in rv.args]) + else: + rv = -rv + return rv + + if (lo < hi) is S.true or ( + hi is S.Infinity or lo is S.NegativeInfinity): + pass + else: + _a = Dummy('lo') + _b = Dummy('hi') + a = lo if lo.is_comparable else _a + b = hi if hi.is_comparable else _b + pos = self._eval_interval(x, a, b, _first=False) + if a == _a and b == _b: + # it's purely symbolic so just swap lo and hi and + # change the sign to get the value for when lo > hi + neg, pos = (-pos.xreplace({_a: hi, _b: lo}), + pos.xreplace({_a: lo, _b: hi})) + else: + # at least one of the bounds was comparable, so allow + # _eval_interval to use that information when computing + # the interval with lo and hi reversed + neg, pos = (-self._eval_interval(x, hi, lo, _first=False), + pos.xreplace({_a: lo, _b: hi})) + + # allow simplification based on ordering of lo and hi + p = Dummy('', positive=True) + if lo.is_Symbol: + pos = pos.xreplace({lo: hi - p}).xreplace({p: hi - lo}) + neg = neg.xreplace({lo: hi + p}).xreplace({p: lo - hi}) + elif hi.is_Symbol: + pos = pos.xreplace({hi: lo + p}).xreplace({p: hi - lo}) + neg = neg.xreplace({hi: lo - p}).xreplace({p: lo - hi}) + # evaluate limits that may have unevaluate Min/Max + touch = lambda _: _.replace( + lambda x: isinstance(x, (Min, Max)), + lambda x: x.func(*x.args)) + neg = touch(neg) + pos = touch(pos) + # assemble return expression; make the first condition be Lt + # b/c then the first expression will look the same whether + # the lo or hi limit is symbolic + if a == _a: # the lower limit was symbolic + rv = Piecewise( + (pos, + lo < hi), + (neg, + True)) + else: + rv = Piecewise( + (neg, + hi < lo), + (pos, + True)) + + if rv == Undefined: + raise ValueError("Can't integrate across undefined region.") + if any(isinstance(i, Piecewise) for i in (pos, neg)): + rv = piecewise_fold(rv) + return rv + + # handle a Piecewise with lo <= hi and no x-independent relationals + # ----------------------------------------------------------------- + ok, abei = self._intervals(x) + if not ok: + from sympy.integrals.integrals import Integral + # not being able to do the interval of f(x) can + # be stated as not being able to do the integral + # of f'(x) over the same range + return Integral(self.diff(x), (x, lo, hi)) # unevaluated + + pieces = [(a, b) for a, b, _, _ in abei] + done = [(lo, hi, -1)] + oo = S.Infinity + for k, p in enumerate(pieces): + if p[:2] == (-oo, oo): + # all undone intervals will get this key + for j, (a, b, i) in enumerate(done): + if i == -1: + done[j] = a, b, k + break # nothing else to consider + N = len(done) - 1 + for j, (a, b, i) in enumerate(reversed(done)): + if i == -1: + j = N - j + done[j: j + 1] = _clip(p, (a, b), k) + done = [(a, b, i) for a, b, i in done if a != b] + + # return the sum of the intervals + sum = S.Zero + upto = None + for a, b, i in done: + if i == -1: + if upto is None: + return Undefined + # TODO simplify hi <= upto + return Piecewise((sum, hi <= upto), (Undefined, True)) + sum += abei[i][-2]._eval_interval(x, a, b) + upto = b + return sum + + def _intervals(self, sym, err_on_Eq=False): + r"""Return a bool and a message (when bool is False), else a + list of unique tuples, (a, b, e, i), where a and b + are the lower and upper bounds in which the expression e of + argument i in self is defined and $a < b$ (when involving + numbers) or $a \le b$ when involving symbols. + + If there are any relationals not involving sym, or any + relational cannot be solved for sym, the bool will be False + a message be given as the second return value. The calling + routine should have removed such relationals before calling + this routine. + + The evaluated conditions will be returned as ranges. + Discontinuous ranges will be returned separately with + identical expressions. The first condition that evaluates to + True will be returned as the last tuple with a, b = -oo, oo. + """ + from sympy.solvers.inequalities import _solve_inequality + + assert isinstance(self, Piecewise) + + def nonsymfail(cond): + return False, filldedent(''' + A condition not involving + %s appeared: %s''' % (sym, cond)) + + def _solve_relational(r): + if sym not in r.free_symbols: + return nonsymfail(r) + try: + rv = _solve_inequality(r, sym) + except NotImplementedError: + return False, 'Unable to solve relational %s for %s.' % (r, sym) + if isinstance(rv, Relational): + free = rv.args[1].free_symbols + if rv.args[0] != sym or sym in free: + return False, 'Unable to solve relational %s for %s.' % (r, sym) + if rv.rel_op == '==': + # this equality has been affirmed to have the form + # Eq(sym, rhs) where rhs is sym-free; it represents + # a zero-width interval which will be ignored + # whether it is an isolated condition or contained + # within an And or an Or + rv = S.false + elif rv.rel_op == '!=': + try: + rv = Or(sym < rv.rhs, sym > rv.rhs) + except TypeError: + # e.g. x != I ==> all real x satisfy + rv = S.true + elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): + rv = S.true + return True, rv + + args = list(self.args) + # make self canonical wrt Relationals + keys = self.atoms(Relational) + reps = {} + for r in keys: + ok, s = _solve_relational(r) + if ok != True: + return False, ok + reps[r] = s + # process args individually so if any evaluate, their position + # in the original Piecewise will be known + args = [i.xreplace(reps) for i in self.args] + + # precondition args + expr_cond = [] + default = idefault = None + for i, (expr, cond) in enumerate(args): + if cond is S.false: + continue + if cond is S.true: + default = expr + idefault = i + break + if isinstance(cond, Eq): + # unanticipated condition, but it is here in case a + # replacement caused an Eq to appear + if err_on_Eq: + return False, 'encountered Eq condition: %s' % cond + continue # zero width interval + + cond = to_cnf(cond) + if isinstance(cond, And): + cond = distribute_or_over_and(cond) + + if isinstance(cond, Or): + expr_cond.extend( + [(i, expr, o) for o in cond.args + if not isinstance(o, Eq)]) + elif cond is not S.false: + expr_cond.append((i, expr, cond)) + elif cond is S.true: + default = expr + idefault = i + break + + # determine intervals represented by conditions + int_expr = [] + for iarg, expr, cond in expr_cond: + if isinstance(cond, And): + lower = S.NegativeInfinity + upper = S.Infinity + exclude = [] + for cond2 in cond.args: + if not isinstance(cond2, Relational): + return False, 'expecting only Relationals' + if isinstance(cond2, Eq): + lower = upper # ignore + if err_on_Eq: + return False, 'encountered secondary Eq condition' + break + elif isinstance(cond2, Ne): + l, r = cond2.args + if l == sym: + exclude.append(r) + elif r == sym: + exclude.append(l) + else: + return nonsymfail(cond2) + continue + elif cond2.lts == sym: + upper = Min(cond2.gts, upper) + elif cond2.gts == sym: + lower = Max(cond2.lts, lower) + else: + return nonsymfail(cond2) # should never get here + if exclude: + exclude = list(ordered(exclude)) + newcond = [] + for i, e in enumerate(exclude): + if e < lower == True or e > upper == True: + continue + if not newcond: + newcond.append((None, lower)) # add a primer + newcond.append((newcond[-1][1], e)) + newcond.append((newcond[-1][1], upper)) + newcond.pop(0) # remove the primer + expr_cond.extend([(iarg, expr, And(i[0] < sym, sym < i[1])) for i in newcond]) + continue + elif isinstance(cond, Relational) and cond.rel_op != '!=': + lower, upper = cond.lts, cond.gts # part 1: initialize with givens + if cond.lts == sym: # part 1a: expand the side ... + lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 + elif cond.gts == sym: # part 1a: ... that can be expanded + upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 + else: + return nonsymfail(cond) + else: + return False, 'unrecognized condition: %s' % cond + + upper = Max(lower, upper) + if err_on_Eq and lower == upper: + return False, 'encountered Eq condition' + if (lower >= upper) is not S.true: + int_expr.append((lower, upper, expr, iarg)) + + if default is not None: + int_expr.append( + (S.NegativeInfinity, S.Infinity, default, idefault)) + + return True, list(uniq(int_expr)) + + def _eval_nseries(self, x, n, logx, cdir=0): + args = [(ec.expr._eval_nseries(x, n, logx), ec.cond) for ec in self.args] + return self.func(*args) + + def _eval_power(self, s): + return self.func(*[(e**s, c) for e, c in self.args]) + + def _eval_subs(self, old, new): + # this is strictly not necessary, but we can keep track + # of whether True or False conditions arise and be + # somewhat more efficient by avoiding other substitutions + # and avoiding invalid conditions that appear after a + # True condition + args = list(self.args) + args_exist = False + for i, (e, c) in enumerate(args): + c = c._subs(old, new) + if c != False: + args_exist = True + e = e._subs(old, new) + args[i] = (e, c) + if c == True: + break + if not args_exist: + args = ((Undefined, True),) + return self.func(*args) + + def _eval_transpose(self): + return self.func(*[(e.transpose(), c) for e, c in self.args]) + + def _eval_template_is_attr(self, is_attr): + b = None + for expr, _ in self.args: + a = getattr(expr, is_attr) + if a is None: + return + if b is None: + b = a + elif b is not a: + return + return b + + _eval_is_finite = lambda self: self._eval_template_is_attr( + 'is_finite') + _eval_is_complex = lambda self: self._eval_template_is_attr('is_complex') + _eval_is_even = lambda self: self._eval_template_is_attr('is_even') + _eval_is_imaginary = lambda self: self._eval_template_is_attr( + 'is_imaginary') + _eval_is_integer = lambda self: self._eval_template_is_attr('is_integer') + _eval_is_irrational = lambda self: self._eval_template_is_attr( + 'is_irrational') + _eval_is_negative = lambda self: self._eval_template_is_attr('is_negative') + _eval_is_nonnegative = lambda self: self._eval_template_is_attr( + 'is_nonnegative') + _eval_is_nonpositive = lambda self: self._eval_template_is_attr( + 'is_nonpositive') + _eval_is_nonzero = lambda self: self._eval_template_is_attr( + 'is_nonzero') + _eval_is_odd = lambda self: self._eval_template_is_attr('is_odd') + _eval_is_polar = lambda self: self._eval_template_is_attr('is_polar') + _eval_is_positive = lambda self: self._eval_template_is_attr('is_positive') + _eval_is_extended_real = lambda self: self._eval_template_is_attr( + 'is_extended_real') + _eval_is_extended_positive = lambda self: self._eval_template_is_attr( + 'is_extended_positive') + _eval_is_extended_negative = lambda self: self._eval_template_is_attr( + 'is_extended_negative') + _eval_is_extended_nonzero = lambda self: self._eval_template_is_attr( + 'is_extended_nonzero') + _eval_is_extended_nonpositive = lambda self: self._eval_template_is_attr( + 'is_extended_nonpositive') + _eval_is_extended_nonnegative = lambda self: self._eval_template_is_attr( + 'is_extended_nonnegative') + _eval_is_real = lambda self: self._eval_template_is_attr('is_real') + _eval_is_zero = lambda self: self._eval_template_is_attr( + 'is_zero') + + @classmethod + def __eval_cond(cls, cond): + """Return the truth value of the condition.""" + if cond == True: + return True + if isinstance(cond, Eq): + try: + diff = cond.lhs - cond.rhs + if diff.is_commutative: + return diff.is_zero + except TypeError: + pass + + def as_expr_set_pairs(self, domain=None): + """Return tuples for each argument of self that give + the expression and the interval in which it is valid + which is contained within the given domain. + If a condition cannot be converted to a set, an error + will be raised. The variable of the conditions is + assumed to be real; sets of real values are returned. + + Examples + ======== + + >>> from sympy import Piecewise, Interval + >>> from sympy.abc import x + >>> p = Piecewise( + ... (1, x < 2), + ... (2,(x > 0) & (x < 4)), + ... (3, True)) + >>> p.as_expr_set_pairs() + [(1, Interval.open(-oo, 2)), + (2, Interval.Ropen(2, 4)), + (3, Interval(4, oo))] + >>> p.as_expr_set_pairs(Interval(0, 3)) + [(1, Interval.Ropen(0, 2)), + (2, Interval(2, 3))] + """ + if domain is None: + domain = S.Reals + exp_sets = [] + U = domain + complex = not domain.is_subset(S.Reals) + cond_free = set() + for expr, cond in self.args: + cond_free |= cond.free_symbols + if len(cond_free) > 1: + raise NotImplementedError(filldedent(''' + multivariate conditions are not handled.''')) + if complex: + for i in cond.atoms(Relational): + if not isinstance(i, (Eq, Ne)): + raise ValueError(filldedent(''' + Inequalities in the complex domain are + not supported. Try the real domain by + setting domain=S.Reals''')) + cond_int = U.intersect(cond.as_set()) + U = U - cond_int + if cond_int != S.EmptySet: + exp_sets.append((expr, cond_int)) + return exp_sets + + def _eval_rewrite_as_ITE(self, *args, **kwargs): + byfree = {} + args = list(args) + default = any(c == True for b, c in args) + for i, (b, c) in enumerate(args): + if not isinstance(b, Boolean) and b != True: + raise TypeError(filldedent(''' + Expecting Boolean or bool but got `%s` + ''' % func_name(b))) + if c == True: + break + # loop over independent conditions for this b + for c in c.args if isinstance(c, Or) else [c]: + free = c.free_symbols + x = free.pop() + try: + byfree[x] = byfree.setdefault( + x, S.EmptySet).union(c.as_set()) + except NotImplementedError: + if not default: + raise NotImplementedError(filldedent(''' + A method to determine whether a multivariate + conditional is consistent with a complete coverage + of all variables has not been implemented so the + rewrite is being stopped after encountering `%s`. + This error would not occur if a default expression + like `(foo, True)` were given. + ''' % c)) + if byfree[x] in (S.UniversalSet, S.Reals): + # collapse the ith condition to True and break + args[i] = list(args[i]) + c = args[i][1] = True + break + if c == True: + break + if c != True: + raise ValueError(filldedent(''' + Conditions must cover all reals or a final default + condition `(foo, True)` must be given. + ''')) + last, _ = args[i] # ignore all past ith arg + for a, c in reversed(args[:i]): + last = ITE(c, a, last) + return _canonical(last) + + def _eval_rewrite_as_KroneckerDelta(self, *args, **kwargs): + from sympy.functions.special.tensor_functions import KroneckerDelta + + rules = { + And: [False, False], + Or: [True, True], + Not: [True, False], + Eq: [None, None], + Ne: [None, None] + } + + class UnrecognizedCondition(Exception): + pass + + def rewrite(cond): + if isinstance(cond, Eq): + return KroneckerDelta(*cond.args) + if isinstance(cond, Ne): + return 1 - KroneckerDelta(*cond.args) + + cls, args = type(cond), cond.args + if cls not in rules: + raise UnrecognizedCondition(cls) + + b1, b2 = rules[cls] + k = Mul(*[1 - rewrite(c) for c in args]) if b1 else Mul(*[rewrite(c) for c in args]) + + if b2: + return 1 - k + return k + + conditions = [] + true_value = None + for value, cond in args: + if type(cond) in rules: + conditions.append((value, cond)) + elif cond is S.true: + if true_value is None: + true_value = value + else: + return + + if true_value is not None: + result = true_value + + for value, cond in conditions[::-1]: + try: + k = rewrite(cond) + result = k * value + (1 - k) * result + except UnrecognizedCondition: + return + + return result + + +def piecewise_fold(expr, evaluate=True): + """ + Takes an expression containing a piecewise function and returns the + expression in piecewise form. In addition, any ITE conditions are + rewritten in negation normal form and simplified. + + The final Piecewise is evaluated (default) but if the raw form + is desired, send ``evaluate=False``; if trivial evaluation is + desired, send ``evaluate=None`` and duplicate conditions and + processing of True and False will be handled. + + Examples + ======== + + >>> from sympy import Piecewise, piecewise_fold, S + >>> from sympy.abc import x + >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) + >>> piecewise_fold(x*p) + Piecewise((x**2, x < 1), (x, True)) + + See Also + ======== + + Piecewise + piecewise_exclusive + """ + if not isinstance(expr, Basic) or not expr.has(Piecewise): + return expr + + new_args = [] + if isinstance(expr, (ExprCondPair, Piecewise)): + for e, c in expr.args: + if not isinstance(e, Piecewise): + e = piecewise_fold(e) + # we don't keep Piecewise in condition because + # it has to be checked to see that it's complete + # and we convert it to ITE at that time + assert not c.has(Piecewise) # pragma: no cover + if isinstance(c, ITE): + c = c.to_nnf() + c = simplify_logic(c, form='cnf') + if isinstance(e, Piecewise): + new_args.extend([(piecewise_fold(ei), And(ci, c)) + for ei, ci in e.args]) + else: + new_args.append((e, c)) + else: + # Given + # P1 = Piecewise((e11, c1), (e12, c2), A) + # P2 = Piecewise((e21, c1), (e22, c2), B) + # ... + # the folding of f(P1, P2) is trivially + # Piecewise( + # (f(e11, e21), c1), + # (f(e12, e22), c2), + # (f(Piecewise(A), Piecewise(B)), True)) + # Certain objects end up rewriting themselves as thus, so + # we do that grouping before the more generic folding. + # The following applies this idea when f = Add or f = Mul + # (and the expression is commutative). + if expr.is_Add or expr.is_Mul and expr.is_commutative: + p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) + pc = sift(p, lambda x: tuple([c for e,c in x.args])) + for c in list(ordered(pc)): + if len(pc[c]) > 1: + pargs = [list(i.args) for i in pc[c]] + # the first one is the same; there may be more + com = common_prefix(*[ + [i.cond for i in j] for j in pargs]) + n = len(com) + collected = [] + for i in range(n): + collected.append(( + expr.func(*[ai[i].expr for ai in pargs]), + com[i])) + remains = [] + for a in pargs: + if n == len(a): # no more args + continue + if a[n].cond == True: # no longer Piecewise + remains.append(a[n].expr) + else: # restore the remaining Piecewise + remains.append( + Piecewise(*a[n:], evaluate=False)) + if remains: + collected.append((expr.func(*remains), True)) + args.append(Piecewise(*collected, evaluate=False)) + continue + args.extend(pc[c]) + else: + args = expr.args + # fold + folded = list(map(piecewise_fold, args)) + for ec in product(*[ + (i.args if isinstance(i, Piecewise) else + [(i, true)]) for i in folded]): + e, c = zip(*ec) + new_args.append((expr.func(*e), And(*c))) + + if evaluate is None: + # don't return duplicate conditions, otherwise don't evaluate + new_args = list(reversed([(e, c) for c, e in { + c: e for e, c in reversed(new_args)}.items()])) + rv = Piecewise(*new_args, evaluate=evaluate) + if evaluate is None and len(rv.args) == 1 and rv.args[0].cond == True: + return rv.args[0].expr + if any(s.expr.has(Piecewise) for p in rv.atoms(Piecewise) for s in p.args): + return piecewise_fold(rv) + return rv + + +def _clip(A, B, k): + """Return interval B as intervals that are covered by A (keyed + to k) and all other intervals of B not covered by A keyed to -1. + + The reference point of each interval is the rhs; if the lhs is + greater than the rhs then an interval of zero width interval will + result, e.g. (4, 1) is treated like (1, 1). + + Examples + ======== + + >>> from sympy.functions.elementary.piecewise import _clip + >>> from sympy import Tuple + >>> A = Tuple(1, 3) + >>> B = Tuple(2, 4) + >>> _clip(A, B, 0) + [(2, 3, 0), (3, 4, -1)] + + Interpretation: interval portion (2, 3) of interval (2, 4) is + covered by interval (1, 3) and is keyed to 0 as requested; + interval (3, 4) was not covered by (1, 3) and is keyed to -1. + """ + a, b = B + c, d = A + c, d = Min(Max(c, a), b), Min(Max(d, a), b) + a = Min(a, b) + p = [] + if a != c: + p.append((a, c, -1)) + else: + pass + if c != d: + p.append((c, d, k)) + else: + pass + if b != d: + if d == c and p and p[-1][-1] == -1: + p[-1] = p[-1][0], b, -1 + else: + p.append((d, b, -1)) + else: + pass + + return p + + +def piecewise_simplify_arguments(expr, **kwargs): + from sympy.simplify.simplify import simplify + + # simplify conditions + f1 = expr.args[0].cond.free_symbols + args = None + if len(f1) == 1 and not expr.atoms(Eq): + x = f1.pop() + # this won't return intervals involving Eq + # and it won't handle symbols treated as + # booleans + ok, abe_ = expr._intervals(x, err_on_Eq=True) + def include(c, x, a): + "return True if c.subs(x, a) is True, else False" + try: + return c.subs(x, a) == True + except TypeError: + return False + if ok: + args = [] + covered = S.EmptySet + from sympy.sets.sets import Interval + for a, b, e, i in abe_: + c = expr.args[i].cond + incl_a = include(c, x, a) + incl_b = include(c, x, b) + iv = Interval(a, b, not incl_a, not incl_b) + cset = iv - covered + if not cset: + continue + try: + a = cset.inf + except NotImplementedError: + pass # continue with the given `a` + else: + incl_a = include(c, x, a) + if incl_a and incl_b: + if a.is_infinite and b.is_infinite: + c = S.true + elif b.is_infinite: + c = (x > a) if a in covered else (x >= a) + elif a.is_infinite: + c = (x <= b) + elif a in covered: + c = And(a < x, x <= b) + else: + c = And(a <= x, x <= b) + elif incl_a: + if a.is_infinite: + c = (x < b) + elif a in covered: + c = And(a < x, x < b) + else: + c = And(a <= x, x < b) + elif incl_b: + if b.is_infinite: + c = (x > a) + else: + c = And(a < x, x <= b) + else: + if a in covered: + c = (x < b) + else: + c = And(a < x, x < b) + covered |= iv + if a is S.NegativeInfinity and incl_a: + covered |= {S.NegativeInfinity} + if b is S.Infinity and incl_b: + covered |= {S.Infinity} + args.append((e, c)) + if not S.Reals.is_subset(covered): + args.append((Undefined, True)) + if args is None: + args = list(expr.args) + for i in range(len(args)): + e, c = args[i] + if isinstance(c, Basic): + c = simplify(c, **kwargs) + args[i] = (e, c) + + # simplify expressions + doit = kwargs.pop('doit', None) + for i in range(len(args)): + e, c = args[i] + if isinstance(e, Basic): + # Skip doit to avoid growth at every call for some integrals + # and sums, see sympy/sympy#17165 + newe = simplify(e, doit=False, **kwargs) + if newe != e: + e = newe + args[i] = (e, c) + + # restore kwargs flag + if doit is not None: + kwargs['doit'] = doit + + return Piecewise(*args) + + +def _piecewise_collapse_arguments(_args): + newargs = [] # the unevaluated conditions + current_cond = set() # the conditions up to a given e, c pair + for expr, cond in _args: + cond = cond.replace( + lambda _: _.is_Relational, _canonical_coeff) + # Check here if expr is a Piecewise and collapse if one of + # the conds in expr matches cond. This allows the collapsing + # of Piecewise((Piecewise((x,x<0)),x<0)) to Piecewise((x,x<0)). + # This is important when using piecewise_fold to simplify + # multiple Piecewise instances having the same conds. + # Eventually, this code should be able to collapse Piecewise's + # having different intervals, but this will probably require + # using the new assumptions. + if isinstance(expr, Piecewise): + unmatching = [] + for i, (e, c) in enumerate(expr.args): + if c in current_cond: + # this would already have triggered + continue + if c == cond: + if c != True: + # nothing past this condition will ever + # trigger and only those args before this + # that didn't match a previous condition + # could possibly trigger + if unmatching: + expr = Piecewise(*( + unmatching + [(e, c)])) + else: + expr = e + break + else: + unmatching.append((e, c)) + + # check for condition repeats + got = False + # -- if an And contains a condition that was + # already encountered, then the And will be + # False: if the previous condition was False + # then the And will be False and if the previous + # condition is True then then we wouldn't get to + # this point. In either case, we can skip this condition. + for i in ([cond] + + (list(cond.args) if isinstance(cond, And) else + [])): + if i in current_cond: + got = True + break + if got: + continue + + # -- if not(c) is already in current_cond then c is + # a redundant condition in an And. This does not + # apply to Or, however: (e1, c), (e2, Or(~c, d)) + # is not (e1, c), (e2, d) because if c and d are + # both False this would give no results when the + # true answer should be (e2, True) + if isinstance(cond, And): + nonredundant = [] + for c in cond.args: + if isinstance(c, Relational): + if c.negated.canonical in current_cond: + continue + # if a strict inequality appears after + # a non-strict one, then the condition is + # redundant + if isinstance(c, (Lt, Gt)) and ( + c.weak in current_cond): + cond = False + break + nonredundant.append(c) + else: + cond = cond.func(*nonredundant) + elif isinstance(cond, Relational): + if cond.negated.canonical in current_cond: + cond = S.true + + current_cond.add(cond) + + # collect successive e,c pairs when exprs or cond match + if newargs: + if newargs[-1].expr == expr: + orcond = Or(cond, newargs[-1].cond) + if isinstance(orcond, (And, Or)): + orcond = distribute_and_over_or(orcond) + newargs[-1] = ExprCondPair(expr, orcond) + continue + elif newargs[-1].cond == cond: + continue + newargs.append(ExprCondPair(expr, cond)) + return newargs + + +_blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and ( + getattr(e.rhs, '_diff_wrt', None) or + isinstance(e.rhs, (Rational, NumberSymbol))) + + +def piecewise_simplify(expr, **kwargs): + expr = piecewise_simplify_arguments(expr, **kwargs) + if not isinstance(expr, Piecewise): + return expr + args = list(expr.args) + + args = _piecewise_simplify_eq_and(args) + args = _piecewise_simplify_equal_to_next_segment(args) + return Piecewise(*args) + + +def _piecewise_simplify_equal_to_next_segment(args): + """ + See if expressions valid for an Equal expression happens to evaluate + to the same function as in the next piecewise segment, see: + https://github.com/sympy/sympy/issues/8458 + """ + prevexpr = None + for i, (expr, cond) in reversed(list(enumerate(args))): + if prevexpr is not None: + if isinstance(cond, And): + eqs, other = sift(cond.args, + lambda i: isinstance(i, Eq), binary=True) + elif isinstance(cond, Eq): + eqs, other = [cond], [] + else: + eqs = other = [] + _prevexpr = prevexpr + _expr = expr + if eqs and not other: + eqs = list(ordered(eqs)) + for e in eqs: + # allow 2 args to collapse into 1 for any e + # otherwise limit simplification to only simple-arg + # Eq instances + if len(args) == 2 or _blessed(e): + _prevexpr = _prevexpr.subs(*e.args) + _expr = _expr.subs(*e.args) + # Did it evaluate to the same? + if _prevexpr == _expr: + # Set the expression for the Not equal section to the same + # as the next. These will be merged when creating the new + # Piecewise + args[i] = args[i].func(args[i + 1][0], cond) + else: + # Update the expression that we compare against + prevexpr = expr + else: + prevexpr = expr + return args + + +def _piecewise_simplify_eq_and(args): + """ + Try to simplify conditions and the expression for + equalities that are part of the condition, e.g. + Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) + -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) + """ + for i, (expr, cond) in enumerate(args): + if isinstance(cond, And): + eqs, other = sift(cond.args, + lambda i: isinstance(i, Eq), binary=True) + elif isinstance(cond, Eq): + eqs, other = [cond], [] + else: + eqs = other = [] + if eqs: + eqs = list(ordered(eqs)) + for j, e in enumerate(eqs): + # these blessed lhs objects behave like Symbols + # and the rhs are simple replacements for the "symbols" + if _blessed(e): + expr = expr.subs(*e.args) + eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] + other = [ei.subs(*e.args) for ei in other] + cond = And(*(eqs + other)) + args[i] = args[i].func(expr, cond) + return args + + +def piecewise_exclusive(expr, *, skip_nan=False, deep=True): + """ + Rewrite :class:`Piecewise` with mutually exclusive conditions. + + Explanation + =========== + + SymPy represents the conditions of a :class:`Piecewise` in an + "if-elif"-fashion, allowing more than one condition to be simultaneously + True. The interpretation is that the first condition that is True is the + case that holds. While this is a useful representation computationally it + is not how a piecewise formula is typically shown in a mathematical text. + The :func:`piecewise_exclusive` function can be used to rewrite any + :class:`Piecewise` with more typical mutually exclusive conditions. + + Note that further manipulation of the resulting :class:`Piecewise`, e.g. + simplifying it, will most likely make it non-exclusive. Hence, this is + primarily a function to be used in conjunction with printing the Piecewise + or if one would like to reorder the expression-condition pairs. + + If it is not possible to determine that all possibilities are covered by + the different cases of the :class:`Piecewise` then a final + :class:`~sympy.core.numbers.NaN` case will be included explicitly. This + can be prevented by passing ``skip_nan=True``. + + Examples + ======== + + >>> from sympy import piecewise_exclusive, Symbol, Piecewise, S + >>> x = Symbol('x', real=True) + >>> p = Piecewise((0, x < 0), (S.Half, x <= 0), (1, True)) + >>> piecewise_exclusive(p) + Piecewise((0, x < 0), (1/2, Eq(x, 0)), (1, x > 0)) + >>> piecewise_exclusive(Piecewise((2, x > 1))) + Piecewise((2, x > 1), (nan, x <= 1)) + >>> piecewise_exclusive(Piecewise((2, x > 1)), skip_nan=True) + Piecewise((2, x > 1)) + + Parameters + ========== + + expr: a SymPy expression. + Any :class:`Piecewise` in the expression will be rewritten. + skip_nan: ``bool`` (default ``False``) + If ``skip_nan`` is set to ``True`` then a final + :class:`~sympy.core.numbers.NaN` case will not be included. + deep: ``bool`` (default ``True``) + If ``deep`` is ``True`` then :func:`piecewise_exclusive` will rewrite + any :class:`Piecewise` subexpressions in ``expr`` rather than just + rewriting ``expr`` itself. + + Returns + ======= + + An expression equivalent to ``expr`` but where all :class:`Piecewise` have + been rewritten with mutually exclusive conditions. + + See Also + ======== + + Piecewise + piecewise_fold + """ + + def make_exclusive(*pwargs): + + cumcond = false + newargs = [] + + # Handle the first n-1 cases + for expr_i, cond_i in pwargs[:-1]: + cancond = And(cond_i, Not(cumcond)).simplify() + cumcond = Or(cond_i, cumcond).simplify() + newargs.append((expr_i, cancond)) + + # For the nth case defer simplification of cumcond + expr_n, cond_n = pwargs[-1] + cancond_n = And(cond_n, Not(cumcond)).simplify() + newargs.append((expr_n, cancond_n)) + + if not skip_nan: + cumcond = Or(cond_n, cumcond).simplify() + if cumcond is not true: + newargs.append((Undefined, Not(cumcond).simplify())) + + return Piecewise(*newargs, evaluate=False) + + if deep: + return expr.replace(Piecewise, make_exclusive) + elif isinstance(expr, Piecewise): + return make_exclusive(*expr.args) + else: + return expr diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__init__.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29865439f31049edbe1decbeee6e75498630bec0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_complexes.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_complexes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a8b1891cd42fd066ba13668fee662e9e5f9f941 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_complexes.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_exponential.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_exponential.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b22ba9559a17874d50ff56d9d1fbfecc789d4e3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_exponential.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_hyperbolic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_hyperbolic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb75458541730a3da6682894454c1cdb6a0579b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_hyperbolic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_integers.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_integers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd91cd4ef8c7954f1f69a96effe661dc55c3995f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_integers.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_interface.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_interface.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1213146d880e01d4b09794c751f9be59ed63c120 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_interface.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_miscellaneous.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_miscellaneous.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e1564e08f2f9ac19763c5d3c49c582a05beb974 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_miscellaneous.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_piecewise.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_piecewise.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92c8b143e09e43d9f3ba6d9b3d56558898da38af Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_piecewise.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_trigonometric.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_trigonometric.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd72f17b72d8ab304ba4b80128047e253021cdfb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/__pycache__/test_trigonometric.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_complexes.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_complexes.py new file mode 100644 index 0000000000000000000000000000000000000000..699c0fef966c99147b713aaa80710b7b8cf21c73 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_complexes.py @@ -0,0 +1,1030 @@ +from sympy.core.function import (Derivative, Function, Lambda, expand, PoleError) +from sympy.core.numbers import (E, I, Rational, comp, nan, oo, pi, zoo) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (Abs, adjoint, arg, conjugate, im, re, sign, transpose) +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, atan, atan2, cos, sin) +from sympy.functions.elementary.hyperbolic import sinh +from sympy.functions.special.delta_functions import (DiracDelta, Heaviside) +from sympy.integrals.integrals import Integral +from sympy.matrices.dense import Matrix +from sympy.matrices.expressions.funcmatrix import FunctionMatrix +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.matrices.immutable import (ImmutableMatrix, ImmutableSparseMatrix) +from sympy.matrices import SparseMatrix +from sympy.sets.sets import Interval +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.series.order import Order +from sympy.testing.pytest import XFAIL, raises, _both_exp_pow + + +def N_equals(a, b): + """Check whether two complex numbers are numerically close""" + return comp(a.n(), b.n(), 1.e-6) + + +def test_re(): + x, y = symbols('x,y') + a, b = symbols('a,b', real=True) + + r = Symbol('r', real=True) + i = Symbol('i', imaginary=True) + + assert re(nan) is nan + + assert re(oo) is oo + assert re(-oo) is -oo + + assert re(0) == 0 + + assert re(1) == 1 + assert re(-1) == -1 + + assert re(E) == E + assert re(-E) == -E + + assert unchanged(re, x) + assert re(x*I) == -im(x) + assert re(r*I) == 0 + assert re(r) == r + assert re(i*I) == I * i + assert re(i) == 0 + + assert re(x + y) == re(x) + re(y) + assert re(x + r) == re(x) + r + + assert re(re(x)) == re(x) + + assert re(2 + I) == 2 + assert re(x + I) == re(x) + + assert re(x + y*I) == re(x) - im(y) + assert re(x + r*I) == re(x) + + assert re(log(2*I)) == log(2) + + assert re((2 + I)**2).expand(complex=True) == 3 + + assert re(conjugate(x)) == re(x) + assert conjugate(re(x)) == re(x) + + assert re(x).as_real_imag() == (re(x), 0) + + assert re(i*r*x).diff(r) == re(i*x) + assert re(i*r*x).diff(i) == I*r*im(x) + + assert re( + sqrt(a + b*I)) == (a**2 + b**2)**Rational(1, 4)*cos(atan2(b, a)/2) + assert re(a * (2 + b*I)) == 2*a + + assert re((1 + sqrt(a + b*I))/2) == \ + (a**2 + b**2)**Rational(1, 4)*cos(atan2(b, a)/2)/2 + S.Half + + assert re(x).rewrite(im) == x - S.ImaginaryUnit*im(x) + assert (x + re(y)).rewrite(re, im) == x + y - S.ImaginaryUnit*im(y) + + a = Symbol('a', algebraic=True) + t = Symbol('t', transcendental=True) + x = Symbol('x') + assert re(a).is_algebraic + assert re(x).is_algebraic is None + assert re(t).is_algebraic is False + + assert re(S.ComplexInfinity) is S.NaN + + n, m, l = symbols('n m l') + A = MatrixSymbol('A',n,m) + assert re(A) == (S.Half) * (A + conjugate(A)) + + A = Matrix([[1 + 4*I,2],[0, -3*I]]) + assert re(A) == Matrix([[1, 2],[0, 0]]) + + A = ImmutableMatrix([[1 + 3*I, 3-2*I],[0, 2*I]]) + assert re(A) == ImmutableMatrix([[1, 3],[0, 0]]) + + X = SparseMatrix([[2*j + i*I for i in range(5)] for j in range(5)]) + assert re(X) - Matrix([[0, 0, 0, 0, 0], + [2, 2, 2, 2, 2], + [4, 4, 4, 4, 4], + [6, 6, 6, 6, 6], + [8, 8, 8, 8, 8]]) == Matrix.zeros(5) + + assert im(X) - Matrix([[0, 1, 2, 3, 4], + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4]]) == Matrix.zeros(5) + + X = FunctionMatrix(3, 3, Lambda((n, m), n + m*I)) + assert re(X) == Matrix([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) + + +def test_im(): + x, y = symbols('x,y') + a, b = symbols('a,b', real=True) + + r = Symbol('r', real=True) + i = Symbol('i', imaginary=True) + + assert im(nan) is nan + + assert im(oo*I) is oo + assert im(-oo*I) is -oo + + assert im(0) == 0 + + assert im(1) == 0 + assert im(-1) == 0 + + assert im(E*I) == E + assert im(-E*I) == -E + + assert unchanged(im, x) + assert im(x*I) == re(x) + assert im(r*I) == r + assert im(r) == 0 + assert im(i*I) == 0 + assert im(i) == -I * i + + assert im(x + y) == im(x) + im(y) + assert im(x + r) == im(x) + assert im(x + r*I) == im(x) + r + + assert im(im(x)*I) == im(x) + + assert im(2 + I) == 1 + assert im(x + I) == im(x) + 1 + + assert im(x + y*I) == im(x) + re(y) + assert im(x + r*I) == im(x) + r + + assert im(log(2*I)) == pi/2 + + assert im((2 + I)**2).expand(complex=True) == 4 + + assert im(conjugate(x)) == -im(x) + assert conjugate(im(x)) == im(x) + + assert im(x).as_real_imag() == (im(x), 0) + + assert im(i*r*x).diff(r) == im(i*x) + assert im(i*r*x).diff(i) == -I * re(r*x) + + assert im( + sqrt(a + b*I)) == (a**2 + b**2)**Rational(1, 4)*sin(atan2(b, a)/2) + assert im(a * (2 + b*I)) == a*b + + assert im((1 + sqrt(a + b*I))/2) == \ + (a**2 + b**2)**Rational(1, 4)*sin(atan2(b, a)/2)/2 + + assert im(x).rewrite(re) == -S.ImaginaryUnit * (x - re(x)) + assert (x + im(y)).rewrite(im, re) == x - S.ImaginaryUnit * (y - re(y)) + + a = Symbol('a', algebraic=True) + t = Symbol('t', transcendental=True) + x = Symbol('x') + assert re(a).is_algebraic + assert re(x).is_algebraic is None + assert re(t).is_algebraic is False + + assert im(S.ComplexInfinity) is S.NaN + + n, m, l = symbols('n m l') + A = MatrixSymbol('A',n,m) + + assert im(A) == (S.One/(2*I)) * (A - conjugate(A)) + + A = Matrix([[1 + 4*I, 2],[0, -3*I]]) + assert im(A) == Matrix([[4, 0],[0, -3]]) + + A = ImmutableMatrix([[1 + 3*I, 3-2*I],[0, 2*I]]) + assert im(A) == ImmutableMatrix([[3, -2],[0, 2]]) + + X = ImmutableSparseMatrix( + [[i*I + i for i in range(5)] for i in range(5)]) + Y = SparseMatrix([list(range(5)) for i in range(5)]) + assert im(X).as_immutable() == Y + + X = FunctionMatrix(3, 3, Lambda((n, m), n + m*I)) + assert im(X) == Matrix([[0, 1, 2], [0, 1, 2], [0, 1, 2]]) + +def test_sign(): + assert sign(1.2) == 1 + assert sign(-1.2) == -1 + assert sign(3*I) == I + assert sign(-3*I) == -I + assert sign(0) == 0 + assert sign(0, evaluate=False).doit() == 0 + assert sign(oo, evaluate=False).doit() == 1 + assert sign(nan) is nan + assert sign(2 + 2*I).doit() == sqrt(2)*(2 + 2*I)/4 + assert sign(2 + 3*I).simplify() == sign(2 + 3*I) + assert sign(2 + 2*I).simplify() == sign(1 + I) + assert sign(im(sqrt(1 - sqrt(3)))) == 1 + assert sign(sqrt(1 - sqrt(3))) == I + + x = Symbol('x') + assert sign(x).is_finite is True + assert sign(x).is_complex is True + assert sign(x).is_imaginary is None + assert sign(x).is_integer is None + assert sign(x).is_real is None + assert sign(x).is_zero is None + assert sign(x).doit() == sign(x) + assert sign(1.2*x) == sign(x) + assert sign(2*x) == sign(x) + assert sign(I*x) == I*sign(x) + assert sign(-2*I*x) == -I*sign(x) + assert sign(conjugate(x)) == conjugate(sign(x)) + + p = Symbol('p', positive=True) + n = Symbol('n', negative=True) + m = Symbol('m', negative=True) + assert sign(2*p*x) == sign(x) + assert sign(n*x) == -sign(x) + assert sign(n*m*x) == sign(x) + + x = Symbol('x', imaginary=True) + assert sign(x).is_imaginary is True + assert sign(x).is_integer is False + assert sign(x).is_real is False + assert sign(x).is_zero is False + assert sign(x).diff(x) == 2*DiracDelta(-I*x) + assert sign(x).doit() == x / Abs(x) + assert conjugate(sign(x)) == -sign(x) + + x = Symbol('x', real=True) + assert sign(x).is_imaginary is False + assert sign(x).is_integer is True + assert sign(x).is_real is True + assert sign(x).is_zero is None + assert sign(x).diff(x) == 2*DiracDelta(x) + assert sign(x).doit() == sign(x) + assert conjugate(sign(x)) == sign(x) + + x = Symbol('x', nonzero=True) + assert sign(x).is_imaginary is False + assert sign(x).is_integer is True + assert sign(x).is_real is True + assert sign(x).is_zero is False + assert sign(x).doit() == x / Abs(x) + assert sign(Abs(x)) == 1 + assert Abs(sign(x)) == 1 + + x = Symbol('x', positive=True) + assert sign(x).is_imaginary is False + assert sign(x).is_integer is True + assert sign(x).is_real is True + assert sign(x).is_zero is False + assert sign(x).doit() == x / Abs(x) + assert sign(Abs(x)) == 1 + assert Abs(sign(x)) == 1 + + x = 0 + assert sign(x).is_imaginary is False + assert sign(x).is_integer is True + assert sign(x).is_real is True + assert sign(x).is_zero is True + assert sign(x).doit() == 0 + assert sign(Abs(x)) == 0 + assert Abs(sign(x)) == 0 + + nz = Symbol('nz', nonzero=True, integer=True) + assert sign(nz).is_imaginary is False + assert sign(nz).is_integer is True + assert sign(nz).is_real is True + assert sign(nz).is_zero is False + assert sign(nz)**2 == 1 + assert (sign(nz)**3).args == (sign(nz), 3) + + assert sign(Symbol('x', nonnegative=True)).is_nonnegative + assert sign(Symbol('x', nonnegative=True)).is_nonpositive is None + assert sign(Symbol('x', nonpositive=True)).is_nonnegative is None + assert sign(Symbol('x', nonpositive=True)).is_nonpositive + assert sign(Symbol('x', real=True)).is_nonnegative is None + assert sign(Symbol('x', real=True)).is_nonpositive is None + assert sign(Symbol('x', real=True, zero=False)).is_nonpositive is None + + x, y = Symbol('x', real=True), Symbol('y') + f = Function('f') + assert sign(x).rewrite(Piecewise) == \ + Piecewise((1, x > 0), (-1, x < 0), (0, True)) + assert sign(y).rewrite(Piecewise) == sign(y) + assert sign(x).rewrite(Heaviside) == 2*Heaviside(x, H0=S(1)/2) - 1 + assert sign(y).rewrite(Heaviside) == sign(y) + assert sign(y).rewrite(Abs) == Piecewise((0, Eq(y, 0)), (y/Abs(y), True)) + assert sign(f(y)).rewrite(Abs) == Piecewise((0, Eq(f(y), 0)), (f(y)/Abs(f(y)), True)) + + # evaluate what can be evaluated + assert sign(exp_polar(I*pi)*pi) is S.NegativeOne + + eq = -sqrt(10 + 6*sqrt(3)) + sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) + # if there is a fast way to know when and when you cannot prove an + # expression like this is zero then the equality to zero is ok + assert sign(eq).func is sign or sign(eq) == 0 + # but sometimes it's hard to do this so it's better not to load + # abs down with tests that will be very slow + q = 1 + sqrt(2) - 2*sqrt(3) + 1331*sqrt(6) + p = expand(q**3)**Rational(1, 3) + d = p - q + assert sign(d).func is sign or sign(d) == 0 + + +def test_as_real_imag(): + n = pi**1000 + # the special code for working out the real + # and complex parts of a power with Integer exponent + # should not run if there is no imaginary part, hence + # this should not hang + assert n.as_real_imag() == (n, 0) + + # issue 6261 + x = Symbol('x') + assert sqrt(x).as_real_imag() == \ + ((re(x)**2 + im(x)**2)**Rational(1, 4)*cos(atan2(im(x), re(x))/2), + (re(x)**2 + im(x)**2)**Rational(1, 4)*sin(atan2(im(x), re(x))/2)) + + # issue 3853 + a, b = symbols('a,b', real=True) + assert ((1 + sqrt(a + b*I))/2).as_real_imag() == \ + ( + (a**2 + b**2)**Rational( + 1, 4)*cos(atan2(b, a)/2)/2 + S.Half, + (a**2 + b**2)**Rational(1, 4)*sin(atan2(b, a)/2)/2) + + assert sqrt(a**2).as_real_imag() == (sqrt(a**2), 0) + i = symbols('i', imaginary=True) + assert sqrt(i**2).as_real_imag() == (0, abs(i)) + + assert ((1 + I)/(1 - I)).as_real_imag() == (0, 1) + assert ((1 + I)**3/(1 - I)).as_real_imag() == (-2, 0) + + +@XFAIL +def test_sign_issue_3068(): + n = pi**1000 + i = int(n) + x = Symbol('x') + assert (n - i).round() == 1 # doesn't hang + assert sign(n - i) == 1 + # perhaps it's not possible to get the sign right when + # only 1 digit is being requested for this situation; + # 2 digits works + assert (n - x).n(1, subs={x: i}) > 0 + assert (n - x).n(2, subs={x: i}) > 0 + + +def test_Abs(): + raises(TypeError, lambda: Abs(Interval(2, 3))) # issue 8717 + + x, y = symbols('x,y') + assert sign(sign(x)) == sign(x) + assert sign(x*y).func is sign + assert Abs(0) == 0 + assert Abs(1) == 1 + assert Abs(-1) == 1 + assert Abs(I) == 1 + assert Abs(-I) == 1 + assert Abs(nan) is nan + assert Abs(zoo) is oo + assert Abs(I * pi) == pi + assert Abs(-I * pi) == pi + assert Abs(I * x) == Abs(x) + assert Abs(-I * x) == Abs(x) + assert Abs(-2*x) == 2*Abs(x) + assert Abs(-2.0*x) == 2.0*Abs(x) + assert Abs(2*pi*x*y) == 2*pi*Abs(x*y) + assert Abs(conjugate(x)) == Abs(x) + assert conjugate(Abs(x)) == Abs(x) + assert Abs(x).expand(complex=True) == sqrt(re(x)**2 + im(x)**2) + + a = Symbol('a', positive=True) + assert Abs(2*pi*x*a) == 2*pi*a*Abs(x) + assert Abs(2*pi*I*x*a) == 2*pi*a*Abs(x) + + x = Symbol('x', real=True) + n = Symbol('n', integer=True) + assert Abs((-1)**n) == 1 + assert x**(2*n) == Abs(x)**(2*n) + assert Abs(x).diff(x) == sign(x) + assert abs(x) == Abs(x) # Python built-in + assert Abs(x)**3 == x**2*Abs(x) + assert Abs(x)**4 == x**4 + assert ( + Abs(x)**(3*n)).args == (Abs(x), 3*n) # leave symbolic odd unchanged + assert (1/Abs(x)).args == (Abs(x), -1) + assert 1/Abs(x)**3 == 1/(x**2*Abs(x)) + assert Abs(x)**-3 == Abs(x)/(x**4) + assert Abs(x**3) == x**2*Abs(x) + assert Abs(I**I) == exp(-pi/2) + assert Abs((4 + 5*I)**(6 + 7*I)) == 68921*exp(-7*atan(Rational(5, 4))) + y = Symbol('y', real=True) + assert Abs(I**y) == 1 + y = Symbol('y') + assert Abs(I**y) == exp(-pi*im(y)/2) + + x = Symbol('x', imaginary=True) + assert Abs(x).diff(x) == -sign(x) + + eq = -sqrt(10 + 6*sqrt(3)) + sqrt(1 + sqrt(3)) + sqrt(3 + 3*sqrt(3)) + # if there is a fast way to know when you can and when you cannot prove an + # expression like this is zero then the equality to zero is ok + assert abs(eq).func is Abs or abs(eq) == 0 + # but sometimes it's hard to do this so it's better not to load + # abs down with tests that will be very slow + q = 1 + sqrt(2) - 2*sqrt(3) + 1331*sqrt(6) + p = expand(q**3)**Rational(1, 3) + d = p - q + assert abs(d).func is Abs or abs(d) == 0 + + assert Abs(4*exp(pi*I/4)) == 4 + assert Abs(3**(2 + I)) == 9 + assert Abs((-3)**(1 - I)) == 3*exp(pi) + + assert Abs(oo) is oo + assert Abs(-oo) is oo + assert Abs(oo + I) is oo + assert Abs(oo + I*oo) is oo + + a = Symbol('a', algebraic=True) + t = Symbol('t', transcendental=True) + x = Symbol('x') + assert re(a).is_algebraic + assert re(x).is_algebraic is None + assert re(t).is_algebraic is False + assert Abs(x).fdiff() == sign(x) + raises(ArgumentIndexError, lambda: Abs(x).fdiff(2)) + + # doesn't have recursion error + arg = sqrt(acos(1 - I)*acos(1 + I)) + assert abs(arg) == arg + + # special handling to put Abs in denom + assert abs(1/x) == 1/Abs(x) + e = abs(2/x**2) + assert e.is_Mul and e == 2/Abs(x**2) + assert unchanged(Abs, y/x) + assert unchanged(Abs, x/(x + 1)) + assert unchanged(Abs, x*y) + p = Symbol('p', positive=True) + assert abs(x/p) == abs(x)/p + + # coverage + assert unchanged(Abs, Symbol('x', real=True)**y) + # issue 19627 + f = Function('f', positive=True) + assert sqrt(f(x)**2) == f(x) + # issue 21625 + assert unchanged(Abs, S("im(acos(-i + acosh(-g + i)))")) + + +def test_Abs_rewrite(): + x = Symbol('x', real=True) + a = Abs(x).rewrite(Heaviside).expand() + assert a == x*Heaviside(x) - x*Heaviside(-x) + for i in [-2, -1, 0, 1, 2]: + assert a.subs(x, i) == abs(i) + y = Symbol('y') + assert Abs(y).rewrite(Heaviside) == Abs(y) + + x, y = Symbol('x', real=True), Symbol('y') + assert Abs(x).rewrite(Piecewise) == Piecewise((x, x >= 0), (-x, True)) + assert Abs(y).rewrite(Piecewise) == Abs(y) + assert Abs(y).rewrite(sign) == y/sign(y) + + i = Symbol('i', imaginary=True) + assert abs(i).rewrite(Piecewise) == Piecewise((I*i, I*i >= 0), (-I*i, True)) + + + assert Abs(y).rewrite(conjugate) == sqrt(y*conjugate(y)) + assert Abs(i).rewrite(conjugate) == sqrt(-i**2) # == -I*i + + y = Symbol('y', extended_real=True) + assert (Abs(exp(-I*x)-exp(-I*y))**2).rewrite(conjugate) == \ + -exp(I*x)*exp(-I*y) + 2 - exp(-I*x)*exp(I*y) + + +def test_Abs_real(): + # test some properties of abs that only apply + # to real numbers + x = Symbol('x', complex=True) + assert sqrt(x**2) != Abs(x) + assert Abs(x**2) != x**2 + + x = Symbol('x', real=True) + assert sqrt(x**2) == Abs(x) + assert Abs(x**2) == x**2 + + # if the symbol is zero, the following will still apply + nn = Symbol('nn', nonnegative=True, real=True) + np = Symbol('np', nonpositive=True, real=True) + assert Abs(nn) == nn + assert Abs(np) == -np + + +def test_Abs_properties(): + x = Symbol('x') + assert Abs(x).is_real is None + assert Abs(x).is_extended_real is True + assert Abs(x).is_rational is None + assert Abs(x).is_positive is None + assert Abs(x).is_nonnegative is None + assert Abs(x).is_extended_positive is None + assert Abs(x).is_extended_nonnegative is True + + f = Symbol('x', finite=True) + assert Abs(f).is_real is True + assert Abs(f).is_extended_real is True + assert Abs(f).is_rational is None + assert Abs(f).is_positive is None + assert Abs(f).is_nonnegative is True + assert Abs(f).is_extended_positive is None + assert Abs(f).is_extended_nonnegative is True + + z = Symbol('z', complex=True, zero=False) + assert Abs(z).is_real is True # since complex implies finite + assert Abs(z).is_extended_real is True + assert Abs(z).is_rational is None + assert Abs(z).is_positive is True + assert Abs(z).is_extended_positive is True + assert Abs(z).is_zero is False + + p = Symbol('p', positive=True) + assert Abs(p).is_real is True + assert Abs(p).is_extended_real is True + assert Abs(p).is_rational is None + assert Abs(p).is_positive is True + assert Abs(p).is_zero is False + + q = Symbol('q', rational=True) + assert Abs(q).is_real is True + assert Abs(q).is_rational is True + assert Abs(q).is_integer is None + assert Abs(q).is_positive is None + assert Abs(q).is_nonnegative is True + + i = Symbol('i', integer=True) + assert Abs(i).is_real is True + assert Abs(i).is_integer is True + assert Abs(i).is_positive is None + assert Abs(i).is_nonnegative is True + + e = Symbol('n', even=True) + ne = Symbol('ne', real=True, even=False) + assert Abs(e).is_even is True + assert Abs(ne).is_even is False + assert Abs(i).is_even is None + + o = Symbol('n', odd=True) + no = Symbol('no', real=True, odd=False) + assert Abs(o).is_odd is True + assert Abs(no).is_odd is False + assert Abs(i).is_odd is None + + +def test_abs(): + # this tests that abs calls Abs; don't rename to + # test_Abs since that test is already above + a = Symbol('a', positive=True) + assert abs(I*(1 + a)**2) == (1 + a)**2 + + +def test_arg(): + assert arg(0) is nan + assert arg(1) == 0 + assert arg(-1) == pi + assert arg(I) == pi/2 + assert arg(-I) == -pi/2 + assert arg(1 + I) == pi/4 + assert arg(-1 + I) == pi*Rational(3, 4) + assert arg(1 - I) == -pi/4 + assert arg(exp_polar(4*pi*I)) == 4*pi + assert arg(exp_polar(-7*pi*I)) == -7*pi + assert arg(exp_polar(5 - 3*pi*I/4)) == pi*Rational(-3, 4) + + assert arg(exp(I*pi/7)) == pi/7 # issue 17300 + assert arg(exp(16*I)) == 16 - 6*pi + assert arg(exp(13*I*pi/12)) == -11*pi/12 + assert arg(exp(123 - 5*I)) == -5 + 2*pi + assert arg(exp(sin(1 + 3*I))) == -2*pi + cos(1)*sinh(3) + r = Symbol('r', real=True) + assert arg(exp(r - 2*I)) == -2 + + f = Function('f') + assert not arg(f(0) + I*f(1)).atoms(re) + + # check nesting + x = Symbol('x') + assert arg(arg(arg(x))) is not S.NaN + assert arg(arg(arg(arg(x)))) is S.NaN + r = Symbol('r', extended_real=True) + assert arg(arg(r)) is not S.NaN + assert arg(arg(arg(r))) is S.NaN + + p = Function('p', extended_positive=True) + assert arg(p(x)) == 0 + assert arg((3 + I)*p(x)) == arg(3 + I) + + p = Symbol('p', positive=True) + assert arg(p) == 0 + assert arg(p*I) == pi/2 + + n = Symbol('n', negative=True) + assert arg(n) == pi + assert arg(n*I) == -pi/2 + + x = Symbol('x') + assert conjugate(arg(x)) == arg(x) + + e = p + I*p**2 + assert arg(e) == arg(1 + p*I) + # make sure sign doesn't swap + e = -2*p + 4*I*p**2 + assert arg(e) == arg(-1 + 2*p*I) + # make sure sign isn't lost + x = symbols('x', real=True) # could be zero + e = x + I*x + assert arg(e) == arg(x*(1 + I)) + assert arg(e/p) == arg(x*(1 + I)) + e = p*cos(p) + I*log(p)*exp(p) + assert arg(e).args[0] == e + # keep it simple -- let the user do more advanced cancellation + e = (p + 1) + I*(p**2 - 1) + assert arg(e).args[0] == e + + f = Function('f') + e = 2*x*(f(0) - 1) - 2*x*f(0) + assert arg(e) == arg(-2*x) + assert arg(f(0)).func == arg and arg(f(0)).args == (f(0),) + + +def test_arg_rewrite(): + assert arg(1 + I) == atan2(1, 1) + + x = Symbol('x', real=True) + y = Symbol('y', real=True) + assert arg(x + I*y).rewrite(atan2) == atan2(y, x) + + +def test_arg_leading_term_and_series(): + x = Symbol('x') + assert arg(x).as_leading_term(x, cdir = 1) == 0 + assert arg(x).as_leading_term(x, cdir = -1) == pi + raises(PoleError, lambda: arg(x + I).as_leading_term(x, cdir = 1)) + raises(PoleError, lambda: arg(2*x).as_leading_term(x, cdir = I)) + + assert arg(x).nseries(x) == 0 + assert arg(x).nseries(x, n=0) == Order(1) + + +def test_adjoint(): + a = Symbol('a', antihermitian=True) + b = Symbol('b', hermitian=True) + assert adjoint(a) == -a + assert adjoint(I*a) == I*a + assert adjoint(b) == b + assert adjoint(I*b) == -I*b + assert adjoint(a*b) == -b*a + assert adjoint(I*a*b) == I*b*a + + x, y = symbols('x y') + assert adjoint(adjoint(x)) == x + assert adjoint(x + y) == conjugate(x) + conjugate(y) + assert adjoint(x - y) == conjugate(x) - conjugate(y) + assert adjoint(x * y) == conjugate(x) * conjugate(y) + assert adjoint(x / y) == conjugate(x) / conjugate(y) + assert adjoint(-x) == -conjugate(x) + + x, y = symbols('x y', commutative=False) + assert adjoint(adjoint(x)) == x + assert adjoint(x + y) == adjoint(x) + adjoint(y) + assert adjoint(x - y) == adjoint(x) - adjoint(y) + assert adjoint(x * y) == adjoint(y) * adjoint(x) + assert adjoint(x / y) == 1 / adjoint(y) * adjoint(x) + assert adjoint(-x) == -adjoint(x) + + +def test_conjugate(): + a = Symbol('a', real=True) + b = Symbol('b', imaginary=True) + assert conjugate(a) == a + assert conjugate(I*a) == -I*a + assert conjugate(b) == -b + assert conjugate(I*b) == I*b + assert conjugate(a*b) == -a*b + assert conjugate(I*a*b) == I*a*b + + x, y = symbols('x y') + assert conjugate(conjugate(x)) == x + assert conjugate(x).inverse() == conjugate + assert conjugate(x + y) == conjugate(x) + conjugate(y) + assert conjugate(x - y) == conjugate(x) - conjugate(y) + assert conjugate(x * y) == conjugate(x) * conjugate(y) + assert conjugate(x / y) == conjugate(x) / conjugate(y) + assert conjugate(-x) == -conjugate(x) + + a = Symbol('a', algebraic=True) + t = Symbol('t', transcendental=True) + assert re(a).is_algebraic + assert re(x).is_algebraic is None + assert re(t).is_algebraic is False + + +def test_conjugate_transpose(): + x = Symbol('x', commutative=False) + assert conjugate(transpose(x)) == adjoint(x) + assert transpose(conjugate(x)) == adjoint(x) + assert adjoint(transpose(x)) == conjugate(x) + assert transpose(adjoint(x)) == conjugate(x) + assert adjoint(conjugate(x)) == transpose(x) + assert conjugate(adjoint(x)) == transpose(x) + + x = Symbol('x') + assert conjugate(x) == adjoint(x) + assert transpose(x) == x + + +def test_transpose(): + a = Symbol('a', complex=True) + assert transpose(a) == a + assert transpose(I*a) == I*a + + x, y = symbols('x y') + assert transpose(transpose(x)) == x + assert transpose(x + y) == x + y + assert transpose(x - y) == x - y + assert transpose(x * y) == x * y + assert transpose(x / y) == x / y + assert transpose(-x) == -x + + x, y = symbols('x y', commutative=False) + assert transpose(transpose(x)) == x + assert transpose(x + y) == transpose(x) + transpose(y) + assert transpose(x - y) == transpose(x) - transpose(y) + assert transpose(x * y) == transpose(y) * transpose(x) + assert transpose(x / y) == 1 / transpose(y) * transpose(x) + assert transpose(-x) == -transpose(x) + + +@_both_exp_pow +def test_polarify(): + from sympy.functions.elementary.complexes import (polar_lift, polarify) + x = Symbol('x') + z = Symbol('z', polar=True) + f = Function('f') + ES = {} + + assert polarify(-1) == (polar_lift(-1), ES) + assert polarify(1 + I) == (polar_lift(1 + I), ES) + + assert polarify(exp(x), subs=False) == exp(x) + assert polarify(1 + x, subs=False) == 1 + x + assert polarify(f(I) + x, subs=False) == f(polar_lift(I)) + x + + assert polarify(x, lift=True) == polar_lift(x) + assert polarify(z, lift=True) == z + assert polarify(f(x), lift=True) == f(polar_lift(x)) + assert polarify(1 + x, lift=True) == polar_lift(1 + x) + assert polarify(1 + f(x), lift=True) == polar_lift(1 + f(polar_lift(x))) + + newex, subs = polarify(f(x) + z) + assert newex.subs(subs) == f(x) + z + + mu = Symbol("mu") + sigma = Symbol("sigma", positive=True) + + # Make sure polarify(lift=True) doesn't try to lift the integration + # variable + assert polarify( + Integral(sqrt(2)*x*exp(-(-mu + x)**2/(2*sigma**2))/(2*sqrt(pi)*sigma), + (x, -oo, oo)), lift=True) == Integral(sqrt(2)*(sigma*exp_polar(0))**exp_polar(I*pi)* + exp((sigma*exp_polar(0))**(2*exp_polar(I*pi))*exp_polar(I*pi)*polar_lift(-mu + x)** + (2*exp_polar(0))/2)*exp_polar(0)*polar_lift(x)/(2*sqrt(pi)), (x, -oo, oo)) + + +def test_unpolarify(): + from sympy.functions.elementary.complexes import (polar_lift, principal_branch, unpolarify) + from sympy.core.relational import Ne + from sympy.functions.elementary.hyperbolic import tanh + from sympy.functions.special.error_functions import erf + from sympy.functions.special.gamma_functions import (gamma, uppergamma) + from sympy.abc import x + p = exp_polar(7*I) + 1 + u = exp(7*I) + 1 + + assert unpolarify(1) == 1 + assert unpolarify(p) == u + assert unpolarify(p**2) == u**2 + assert unpolarify(p**x) == p**x + assert unpolarify(p*x) == u*x + assert unpolarify(p + x) == u + x + assert unpolarify(sqrt(sin(p))) == sqrt(sin(u)) + + # Test reduction to principal branch 2*pi. + t = principal_branch(x, 2*pi) + assert unpolarify(t) == x + assert unpolarify(sqrt(t)) == sqrt(t) + + # Test exponents_only. + assert unpolarify(p**p, exponents_only=True) == p**u + assert unpolarify(uppergamma(x, p**p)) == uppergamma(x, p**u) + + # Test functions. + assert unpolarify(sin(p)) == sin(u) + assert unpolarify(tanh(p)) == tanh(u) + assert unpolarify(gamma(p)) == gamma(u) + assert unpolarify(erf(p)) == erf(u) + assert unpolarify(uppergamma(x, p)) == uppergamma(x, p) + + assert unpolarify(uppergamma(sin(p), sin(p + exp_polar(0)))) == \ + uppergamma(sin(u), sin(u + 1)) + assert unpolarify(uppergamma(polar_lift(0), 2*exp_polar(0))) == \ + uppergamma(0, 2) + + assert unpolarify(Eq(p, 0)) == Eq(u, 0) + assert unpolarify(Ne(p, 0)) == Ne(u, 0) + assert unpolarify(polar_lift(x) > 0) == (x > 0) + + # Test bools + assert unpolarify(True) is True + + +def test_issue_4035(): + x = Symbol('x') + assert Abs(x).expand(trig=True) == Abs(x) + assert sign(x).expand(trig=True) == sign(x) + assert arg(x).expand(trig=True) == arg(x) + + +def test_issue_3206(): + x = Symbol('x') + assert Abs(Abs(x)) == Abs(x) + + +def test_issue_4754_derivative_conjugate(): + x = Symbol('x', real=True) + y = Symbol('y', imaginary=True) + f = Function('f') + assert (f(x).conjugate()).diff(x) == (f(x).diff(x)).conjugate() + assert (f(y).conjugate()).diff(y) == -(f(y).diff(y)).conjugate() + + +def test_derivatives_issue_4757(): + x = Symbol('x', real=True) + y = Symbol('y', imaginary=True) + f = Function('f') + assert re(f(x)).diff(x) == re(f(x).diff(x)) + assert im(f(x)).diff(x) == im(f(x).diff(x)) + assert re(f(y)).diff(y) == -I*im(f(y).diff(y)) + assert im(f(y)).diff(y) == -I*re(f(y).diff(y)) + assert Abs(f(x)).diff(x).subs(f(x), 1 + I*x).doit() == x/sqrt(1 + x**2) + assert arg(f(x)).diff(x).subs(f(x), 1 + I*x**2).doit() == 2*x/(1 + x**4) + assert Abs(f(y)).diff(y).subs(f(y), 1 + y).doit() == -y/sqrt(1 - y**2) + assert arg(f(y)).diff(y).subs(f(y), I + y**2).doit() == 2*y/(1 + y**4) + + +def test_issue_11413(): + from sympy.simplify.simplify import simplify + v0 = Symbol('v0') + v1 = Symbol('v1') + v2 = Symbol('v2') + V = Matrix([[v0],[v1],[v2]]) + U = V.normalized() + assert U == Matrix([ + [v0/sqrt(Abs(v0)**2 + Abs(v1)**2 + Abs(v2)**2)], + [v1/sqrt(Abs(v0)**2 + Abs(v1)**2 + Abs(v2)**2)], + [v2/sqrt(Abs(v0)**2 + Abs(v1)**2 + Abs(v2)**2)]]) + U.norm = sqrt(v0**2/(v0**2 + v1**2 + v2**2) + v1**2/(v0**2 + v1**2 + v2**2) + v2**2/(v0**2 + v1**2 + v2**2)) + assert simplify(U.norm) == 1 + + +def test_periodic_argument(): + from sympy.functions.elementary.complexes import (periodic_argument, polar_lift, principal_branch, unbranched_argument) + x = Symbol('x') + p = Symbol('p', positive=True) + + assert unbranched_argument(2 + I) == periodic_argument(2 + I, oo) + assert unbranched_argument(1 + x) == periodic_argument(1 + x, oo) + assert N_equals(unbranched_argument((1 + I)**2), pi/2) + assert N_equals(unbranched_argument((1 - I)**2), -pi/2) + assert N_equals(periodic_argument((1 + I)**2, 3*pi), pi/2) + assert N_equals(periodic_argument((1 - I)**2, 3*pi), -pi/2) + + assert unbranched_argument(principal_branch(x, pi)) == \ + periodic_argument(x, pi) + + assert unbranched_argument(polar_lift(2 + I)) == unbranched_argument(2 + I) + assert periodic_argument(polar_lift(2 + I), 2*pi) == \ + periodic_argument(2 + I, 2*pi) + assert periodic_argument(polar_lift(2 + I), 3*pi) == \ + periodic_argument(2 + I, 3*pi) + assert periodic_argument(polar_lift(2 + I), pi) == \ + periodic_argument(polar_lift(2 + I), pi) + + assert unbranched_argument(polar_lift(1 + I)) == pi/4 + assert periodic_argument(2*p, p) == periodic_argument(p, p) + assert periodic_argument(pi*p, p) == periodic_argument(p, p) + + assert Abs(polar_lift(1 + I)) == Abs(1 + I) + + +@XFAIL +def test_principal_branch_fail(): + # TODO XXX why does abs(x)._eval_evalf() not fall back to global evalf? + from sympy.functions.elementary.complexes import principal_branch + assert N_equals(principal_branch((1 + I)**2, pi/2), 0) + + +def test_principal_branch(): + from sympy.functions.elementary.complexes import (polar_lift, principal_branch) + p = Symbol('p', positive=True) + x = Symbol('x') + neg = Symbol('x', negative=True) + + assert principal_branch(polar_lift(x), p) == principal_branch(x, p) + assert principal_branch(polar_lift(2 + I), p) == principal_branch(2 + I, p) + assert principal_branch(2*x, p) == 2*principal_branch(x, p) + assert principal_branch(1, pi) == exp_polar(0) + assert principal_branch(-1, 2*pi) == exp_polar(I*pi) + assert principal_branch(-1, pi) == exp_polar(0) + assert principal_branch(exp_polar(3*pi*I)*x, 2*pi) == \ + principal_branch(exp_polar(I*pi)*x, 2*pi) + assert principal_branch(neg*exp_polar(pi*I), 2*pi) == neg*exp_polar(-I*pi) + # related to issue #14692 + assert principal_branch(exp_polar(-I*pi/2)/polar_lift(neg), 2*pi) == \ + exp_polar(-I*pi/2)/neg + + assert N_equals(principal_branch((1 + I)**2, 2*pi), 2*I) + assert N_equals(principal_branch((1 + I)**2, 3*pi), 2*I) + assert N_equals(principal_branch((1 + I)**2, 1*pi), 2*I) + + # test argument sanitization + assert principal_branch(x, I).func is principal_branch + assert principal_branch(x, -4).func is principal_branch + assert principal_branch(x, -oo).func is principal_branch + assert principal_branch(x, zoo).func is principal_branch + + +@XFAIL +def test_issue_6167_6151(): + n = pi**1000 + i = int(n) + assert sign(n - i) == 1 + assert abs(n - i) == n - i + x = Symbol('x') + eps = pi**-1500 + big = pi**1000 + one = cos(x)**2 + sin(x)**2 + e = big*one - big + eps + from sympy.simplify.simplify import simplify + assert sign(simplify(e)) == 1 + for xi in (111, 11, 1, Rational(1, 10)): + assert sign(e.subs(x, xi)) == 1 + + +def test_issue_14216(): + from sympy.functions.elementary.complexes import unpolarify + A = MatrixSymbol("A", 2, 2) + assert unpolarify(A[0, 0]) == A[0, 0] + assert unpolarify(A[0, 0]*A[1, 0]) == A[0, 0]*A[1, 0] + + +def test_issue_14238(): + # doesn't cause recursion error + r = Symbol('r', real=True) + assert Abs(r + Piecewise((0, r > 0), (1 - r, True))) + + +def test_issue_22189(): + x = Symbol('x') + for a in (sqrt(7 - 2*x) - 2, 1 - x): + assert Abs(a) - Abs(-a) == 0, a + + +def test_zero_assumptions(): + nr = Symbol('nonreal', real=False, finite=True) + ni = Symbol('nonimaginary', imaginary=False) + # imaginary implies not zero + nzni = Symbol('nonzerononimaginary', zero=False, imaginary=False) + + assert re(nr).is_zero is None + assert im(nr).is_zero is False + + assert re(ni).is_zero is None + assert im(ni).is_zero is None + + assert re(nzni).is_zero is False + assert im(nzni).is_zero is None + + +@_both_exp_pow +def test_issue_15893(): + f = Function('f', real=True) + x = Symbol('x', real=True) + eq = Derivative(Abs(f(x)), f(x)) + assert eq.doit() == sign(f(x)) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_exponential.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_exponential.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8c311d01e98d7fd6831ad754e854fae409aa0c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_exponential.py @@ -0,0 +1,810 @@ +from sympy.assumptions.refine import refine +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.concrete.products import Product +from sympy.concrete.summations import Sum +from sympy.core.function import expand_log +from sympy.core.numbers import (E, Float, I, Rational, nan, oo, pi, zoo) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (adjoint, conjugate, re, sign, transpose) +from sympy.functions.elementary.exponential import (LambertW, exp, exp_polar, log) +from sympy.functions.elementary.hyperbolic import (cosh, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin, tan) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.polys.polytools import gcd +from sympy.series.order import O +from sympy.simplify.simplify import simplify +from sympy.core.parameters import global_parameters +from sympy.functions.elementary.exponential import match_real_imag +from sympy.abc import x, y, z +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.testing.pytest import raises, XFAIL, _both_exp_pow + + +@_both_exp_pow +def test_exp_values(): + if global_parameters.exp_is_pow: + assert type(exp(x)) is Pow + else: + assert type(exp(x)) is exp + + k = Symbol('k', integer=True) + + assert exp(nan) is nan + + assert exp(oo) is oo + assert exp(-oo) == 0 + + assert exp(0) == 1 + assert exp(1) == E + assert exp(-1 + x).as_base_exp() == (S.Exp1, x - 1) + assert exp(1 + x).as_base_exp() == (S.Exp1, x + 1) + + assert exp(pi*I/2) == I + assert exp(pi*I) == -1 + assert exp(pi*I*Rational(3, 2)) == -I + assert exp(2*pi*I) == 1 + + assert refine(exp(pi*I*2*k)) == 1 + assert refine(exp(pi*I*2*(k + S.Half))) == -1 + assert refine(exp(pi*I*2*(k + Rational(1, 4)))) == I + assert refine(exp(pi*I*2*(k + Rational(3, 4)))) == -I + + assert exp(log(x)) == x + assert exp(2*log(x)) == x**2 + assert exp(pi*log(x)) == x**pi + + assert exp(17*log(x) + E*log(y)) == x**17 * y**E + + assert exp(x*log(x)) != x**x + assert exp(sin(x)*log(x)) != x + + assert exp(3*log(x) + oo*x) == exp(oo*x) * x**3 + assert exp(4*log(x)*log(y) + 3*log(x)) == x**3 * exp(4*log(x)*log(y)) + + assert exp(-oo, evaluate=False).is_finite is True + assert exp(oo, evaluate=False).is_finite is False + + +@_both_exp_pow +def test_exp_period(): + assert exp(I*pi*Rational(9, 4)) == exp(I*pi/4) + assert exp(I*pi*Rational(46, 18)) == exp(I*pi*Rational(5, 9)) + assert exp(I*pi*Rational(25, 7)) == exp(I*pi*Rational(-3, 7)) + assert exp(I*pi*Rational(-19, 3)) == exp(-I*pi/3) + assert exp(I*pi*Rational(37, 8)) - exp(I*pi*Rational(-11, 8)) == 0 + assert exp(I*pi*Rational(-5, 3)) / exp(I*pi*Rational(11, 5)) * exp(I*pi*Rational(148, 15)) == 1 + + assert exp(2 - I*pi*Rational(17, 5)) == exp(2 + I*pi*Rational(3, 5)) + assert exp(log(3) + I*pi*Rational(29, 9)) == 3 * exp(I*pi*Rational(-7, 9)) + + n = Symbol('n', integer=True) + e = Symbol('e', even=True) + assert exp(e*I*pi) == 1 + assert exp((e + 1)*I*pi) == -1 + assert exp((1 + 4*n)*I*pi/2) == I + assert exp((-1 + 4*n)*I*pi/2) == -I + + +@_both_exp_pow +def test_exp_log(): + x = Symbol("x", real=True) + assert log(exp(x)) == x + assert exp(log(x)) == x + + if not global_parameters.exp_is_pow: + assert log(x).inverse() == exp + assert exp(x).inverse() == log + + y = Symbol("y", polar=True) + assert log(exp_polar(z)) == z + assert exp(log(y)) == y + + +@_both_exp_pow +def test_exp_expand(): + e = exp(log(Rational(2))*(1 + x) - log(Rational(2))*x) + assert e.expand() == 2 + assert exp(x + y) != exp(x)*exp(y) + assert exp(x + y).expand() == exp(x)*exp(y) + + +@_both_exp_pow +def test_exp__as_base_exp(): + assert exp(x).as_base_exp() == (E, x) + assert exp(2*x).as_base_exp() == (E, 2*x) + assert exp(x*y).as_base_exp() == (E, x*y) + assert exp(-x).as_base_exp() == (E, -x) + + # Pow( *expr.as_base_exp() ) == expr invariant should hold + assert E**x == exp(x) + assert E**(2*x) == exp(2*x) + assert E**(x*y) == exp(x*y) + + assert exp(x).base is S.Exp1 + assert exp(x).exp == x + + +@_both_exp_pow +def test_exp_infinity(): + assert exp(I*y) != nan + assert refine(exp(I*oo)) is nan + assert refine(exp(-I*oo)) is nan + assert exp(y*I*oo) != nan + assert exp(zoo) is nan + x = Symbol('x', extended_real=True, finite=False) + assert exp(x).is_complex is None + + +@_both_exp_pow +def test_exp_subs(): + x = Symbol('x') + e = (exp(3*log(x), evaluate=False)) # evaluates to x**3 + assert e.subs(x**3, y**3) == e + assert e.subs(x**2, 5) == e + assert (x**3).subs(x**2, y) != y**Rational(3, 2) + assert exp(exp(x) + exp(x**2)).subs(exp(exp(x)), y) == y * exp(exp(x**2)) + assert exp(x).subs(E, y) == y**x + x = symbols('x', real=True) + assert exp(5*x).subs(exp(7*x), y) == y**Rational(5, 7) + assert exp(2*x + 7).subs(exp(3*x), y) == y**Rational(2, 3) * exp(7) + x = symbols('x', positive=True) + assert exp(3*log(x)).subs(x**2, y) == y**Rational(3, 2) + # differentiate between E and exp + assert exp(exp(x + E)).subs(exp, 3) == 3**(3**(x + E)) + assert exp(exp(x + E)).subs(exp, sin) == sin(sin(x + E)) + assert exp(exp(x + E)).subs(E, 3) == 3**(3**(x + 3)) + assert exp(3).subs(E, sin) == sin(3) + + +def test_exp_adjoint(): + x = Symbol('x', commutative=False) + assert adjoint(exp(x)) == exp(adjoint(x)) + + +def test_exp_conjugate(): + assert conjugate(exp(x)) == exp(conjugate(x)) + + +@_both_exp_pow +def test_exp_transpose(): + assert transpose(exp(x)) == exp(transpose(x)) + + +@_both_exp_pow +def test_exp_rewrite(): + assert exp(x).rewrite(sin) == sinh(x) + cosh(x) + assert exp(x*I).rewrite(cos) == cos(x) + I*sin(x) + assert exp(1).rewrite(cos) == sinh(1) + cosh(1) + assert exp(1).rewrite(sin) == sinh(1) + cosh(1) + assert exp(1).rewrite(sin) == sinh(1) + cosh(1) + assert exp(x).rewrite(tanh) == (1 + tanh(x/2))/(1 - tanh(x/2)) + assert exp(pi*I/4).rewrite(sqrt) == sqrt(2)/2 + sqrt(2)*I/2 + assert exp(pi*I/3).rewrite(sqrt) == S.Half + sqrt(3)*I/2 + if not global_parameters.exp_is_pow: + assert exp(x*log(y)).rewrite(Pow) == y**x + assert exp(log(x)*log(y)).rewrite(Pow) in [x**log(y), y**log(x)] + assert exp(log(log(x))*y).rewrite(Pow) == log(x)**y + + n = Symbol('n', integer=True) + + assert Sum((exp(pi*I/2)/2)**n, (n, 0, oo)).rewrite(sqrt).doit() == Rational(4, 5) + I*2/5 + assert Sum((exp(pi*I/4)/2)**n, (n, 0, oo)).rewrite(sqrt).doit() == 1/(1 - sqrt(2)*(1 + I)/4) + assert (Sum((exp(pi*I/3)/2)**n, (n, 0, oo)).rewrite(sqrt).doit().cancel() + == 4*I/(sqrt(3) + 3*I)) + + +@_both_exp_pow +def test_exp_leading_term(): + assert exp(x).as_leading_term(x) == 1 + assert exp(2 + x).as_leading_term(x) == exp(2) + assert exp((2*x + 3) / (x+1)).as_leading_term(x) == exp(3) + + # The following tests are commented, since now SymPy returns the + # original function when the leading term in the series expansion does + # not exist. + # raises(NotImplementedError, lambda: exp(1/x).as_leading_term(x)) + # raises(NotImplementedError, lambda: exp((x + 1) / x**2).as_leading_term(x)) + # raises(NotImplementedError, lambda: exp(x + 1/x).as_leading_term(x)) + + +@_both_exp_pow +def test_exp_taylor_term(): + x = symbols('x') + assert exp(x).taylor_term(1, x) == x + assert exp(x).taylor_term(3, x) == x**3/6 + assert exp(x).taylor_term(4, x) == x**4/24 + assert exp(x).taylor_term(-1, x) is S.Zero + + +def test_exp_MatrixSymbol(): + A = MatrixSymbol("A", 2, 2) + assert exp(A).has(exp) + + +def test_exp_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: exp(x).fdiff(2)) + + +def test_log_values(): + assert log(nan) is nan + + assert log(oo) is oo + assert log(-oo) is oo + + assert log(zoo) is zoo + assert log(-zoo) is zoo + + assert log(0) is zoo + + assert log(1) == 0 + assert log(-1) == I*pi + + assert log(E) == 1 + assert log(-E).expand() == 1 + I*pi + + assert unchanged(log, pi) + assert log(-pi).expand() == log(pi) + I*pi + + assert unchanged(log, 17) + assert log(-17) == log(17) + I*pi + + assert log(I) == I*pi/2 + assert log(-I) == -I*pi/2 + + assert log(17*I) == I*pi/2 + log(17) + assert log(-17*I).expand() == -I*pi/2 + log(17) + + assert log(oo*I) is oo + assert log(-oo*I) is oo + assert log(0, 2) is zoo + assert log(0, 5) is zoo + + assert exp(-log(3))**(-1) == 3 + + assert log(S.Half) == -log(2) + assert log(2*3).func is log + assert log(2*3**2).func is log + + +def test_match_real_imag(): + x, y = symbols('x,y', real=True) + i = Symbol('i', imaginary=True) + assert match_real_imag(S.One) == (1, 0) + assert match_real_imag(I) == (0, 1) + assert match_real_imag(3 - 5*I) == (3, -5) + assert match_real_imag(-sqrt(3) + S.Half*I) == (-sqrt(3), S.Half) + assert match_real_imag(x + y*I) == (x, y) + assert match_real_imag(x*I + y*I) == (0, x + y) + assert match_real_imag((x + y)*I) == (0, x + y) + assert match_real_imag(Rational(-2, 3)*i*I) == (None, None) + assert match_real_imag(1 - 2*i) == (None, None) + assert match_real_imag(sqrt(2)*(3 - 5*I)) == (None, None) + + +def test_log_exact(): + # check for pi/2, pi/3, pi/4, pi/6, pi/8, pi/12; pi/5, pi/10: + for n in range(-23, 24): + if gcd(n, 24) != 1: + assert log(exp(n*I*pi/24).rewrite(sqrt)) == n*I*pi/24 + for n in range(-9, 10): + assert log(exp(n*I*pi/10).rewrite(sqrt)) == n*I*pi/10 + + assert log(S.Half - I*sqrt(3)/2) == -I*pi/3 + assert log(Rational(-1, 2) + I*sqrt(3)/2) == I*pi*Rational(2, 3) + assert log(-sqrt(2)/2 - I*sqrt(2)/2) == -I*pi*Rational(3, 4) + assert log(-sqrt(3)/2 - I*S.Half) == -I*pi*Rational(5, 6) + + assert log(Rational(-1, 4) + sqrt(5)/4 - I*sqrt(sqrt(5)/8 + Rational(5, 8))) == -I*pi*Rational(2, 5) + assert log(sqrt(Rational(5, 8) - sqrt(5)/8) + I*(Rational(1, 4) + sqrt(5)/4)) == I*pi*Rational(3, 10) + assert log(-sqrt(sqrt(2)/4 + S.Half) + I*sqrt(S.Half - sqrt(2)/4)) == I*pi*Rational(7, 8) + assert log(-sqrt(6)/4 - sqrt(2)/4 + I*(-sqrt(6)/4 + sqrt(2)/4)) == -I*pi*Rational(11, 12) + + assert log(-1 + I*sqrt(3)) == log(2) + I*pi*Rational(2, 3) + assert log(5 + 5*I) == log(5*sqrt(2)) + I*pi/4 + assert log(sqrt(-12)) == log(2*sqrt(3)) + I*pi/2 + assert log(-sqrt(6) + sqrt(2) - I*sqrt(6) - I*sqrt(2)) == log(4) - I*pi*Rational(7, 12) + assert log(-sqrt(6-3*sqrt(2)) - I*sqrt(6+3*sqrt(2))) == log(2*sqrt(3)) - I*pi*Rational(5, 8) + assert log(1 + I*sqrt(2-sqrt(2))/sqrt(2+sqrt(2))) == log(2/sqrt(sqrt(2) + 2)) + I*pi/8 + assert log(cos(pi*Rational(7, 12)) + I*sin(pi*Rational(7, 12))) == I*pi*Rational(7, 12) + assert log(cos(pi*Rational(6, 5)) + I*sin(pi*Rational(6, 5))) == I*pi*Rational(-4, 5) + + assert log(5*(1 + I)/sqrt(2)) == log(5) + I*pi/4 + assert log(sqrt(2)*(-sqrt(3) + 1 - sqrt(3)*I - I)) == log(4) - I*pi*Rational(7, 12) + assert log(-sqrt(2)*(1 - I*sqrt(3))) == log(2*sqrt(2)) + I*pi*Rational(2, 3) + assert log(sqrt(3)*I*(-sqrt(6 - 3*sqrt(2)) - I*sqrt(3*sqrt(2) + 6))) == log(6) - I*pi/8 + + zero = (1 + sqrt(2))**2 - 3 - 2*sqrt(2) + assert log(zero - I*sqrt(3)) == log(sqrt(3)) - I*pi/2 + assert unchanged(log, zero + I*zero) or log(zero + zero*I) is zoo + + # bail quickly if no obvious simplification is possible: + assert unchanged(log, (sqrt(2)-1/sqrt(sqrt(3)+I))**1000) + # beware of non-real coefficients + assert unchanged(log, sqrt(2-sqrt(5))*(1 + I)) + + +def test_log_base(): + assert log(1, 2) == 0 + assert log(2, 2) == 1 + assert log(3, 2) == log(3)/log(2) + assert log(6, 2) == 1 + log(3)/log(2) + assert log(6, 3) == 1 + log(2)/log(3) + assert log(2**3, 2) == 3 + assert log(3**3, 3) == 3 + assert log(5, 1) is zoo + assert log(1, 1) is nan + assert log(Rational(2, 3), 10) == log(Rational(2, 3))/log(10) + assert log(Rational(2, 3), Rational(1, 3)) == -log(2)/log(3) + 1 + assert log(Rational(2, 3), Rational(2, 5)) == \ + log(Rational(2, 3))/log(Rational(2, 5)) + # issue 17148 + assert log(Rational(8, 3), 2) == -log(3)/log(2) + 3 + + +def test_log_symbolic(): + assert log(x, exp(1)) == log(x) + assert log(exp(x)) != x + + assert log(x, exp(1)) == log(x) + assert log(x*y) != log(x) + log(y) + assert log(x/y).expand() != log(x) - log(y) + assert log(x/y).expand(force=True) == log(x) - log(y) + assert log(x**y).expand() != y*log(x) + assert log(x**y).expand(force=True) == y*log(x) + + assert log(x, 2) == log(x)/log(2) + assert log(E, 2) == 1/log(2) + + p, q = symbols('p,q', positive=True) + r = Symbol('r', real=True) + + assert log(p**2) != 2*log(p) + assert log(p**2).expand() == 2*log(p) + assert log(x**2).expand() != 2*log(x) + assert log(p**q) != q*log(p) + assert log(exp(p)) == p + assert log(p*q) != log(p) + log(q) + assert log(p*q).expand() == log(p) + log(q) + + assert log(-sqrt(3)) == log(sqrt(3)) + I*pi + assert log(-exp(p)) != p + I*pi + assert log(-exp(x)).expand() != x + I*pi + assert log(-exp(r)).expand() == r + I*pi + + assert log(x**y) != y*log(x) + + assert (log(x**-5)**-1).expand() != -1/log(x)/5 + assert (log(p**-5)**-1).expand() == -1/log(p)/5 + assert log(-x).func is log and log(-x).args[0] == -x + assert log(-p).func is log and log(-p).args[0] == -p + + +def test_log_exp(): + assert log(exp(4*I*pi)) == 0 # exp evaluates + assert log(exp(-5*I*pi)) == I*pi # exp evaluates + assert log(exp(I*pi*Rational(19, 4))) == I*pi*Rational(3, 4) + assert log(exp(I*pi*Rational(25, 7))) == I*pi*Rational(-3, 7) + assert log(exp(-5*I)) == -5*I + 2*I*pi + + +@_both_exp_pow +def test_exp_assumptions(): + r = Symbol('r', real=True) + i = Symbol('i', imaginary=True) + for e in exp, exp_polar: + assert e(x).is_real is None + assert e(x).is_imaginary is None + assert e(i).is_real is None + assert e(i).is_imaginary is None + assert e(r).is_real is True + assert e(r).is_imaginary is False + assert e(re(x)).is_extended_real is True + assert e(re(x)).is_imaginary is False + + assert Pow(E, I*pi, evaluate=False).is_imaginary == False + assert Pow(E, 2*I*pi, evaluate=False).is_imaginary == False + assert Pow(E, I*pi/2, evaluate=False).is_imaginary == True + assert Pow(E, I*pi/3, evaluate=False).is_imaginary is None + + assert exp(0, evaluate=False).is_algebraic + + a = Symbol('a', algebraic=True) + an = Symbol('an', algebraic=True, nonzero=True) + r = Symbol('r', rational=True) + rn = Symbol('rn', rational=True, nonzero=True) + assert exp(a).is_algebraic is None + assert exp(an).is_algebraic is False + assert exp(pi*r).is_algebraic is None + assert exp(pi*rn).is_algebraic is False + + assert exp(0, evaluate=False).is_algebraic is True + assert exp(I*pi/3, evaluate=False).is_algebraic is True + assert exp(I*pi*r, evaluate=False).is_algebraic is True + + +@_both_exp_pow +def test_exp_AccumBounds(): + assert exp(AccumBounds(1, 2)) == AccumBounds(E, E**2) + + +def test_log_assumptions(): + p = symbols('p', positive=True) + n = symbols('n', negative=True) + z = symbols('z', zero=True) + x = symbols('x', infinite=True, extended_positive=True) + + assert log(z).is_positive is False + assert log(x).is_extended_positive is True + assert log(2) > 0 + assert log(1, evaluate=False).is_zero + assert log(1 + z).is_zero + assert log(p).is_zero is None + assert log(n).is_zero is False + assert log(0.5).is_negative is True + assert log(exp(p) + 1).is_positive + + assert log(1, evaluate=False).is_algebraic + assert log(42, evaluate=False).is_algebraic is False + + assert log(1 + z).is_rational + + +def test_log_hashing(): + assert x != log(log(x)) + assert hash(x) != hash(log(log(x))) + assert log(x) != log(log(log(x))) + + e = 1/log(log(x) + log(log(x))) + assert e.base.func is log + e = 1/log(log(x) + log(log(log(x)))) + assert e.base.func is log + + e = log(log(x)) + assert e.func is log + assert x.func is not log + assert hash(log(log(x))) != hash(x) + assert e != x + + +def test_log_sign(): + assert sign(log(2)) == 1 + + +def test_log_expand_complex(): + assert log(1 + I).expand(complex=True) == log(2)/2 + I*pi/4 + assert log(1 - sqrt(2)).expand(complex=True) == log(sqrt(2) - 1) + I*pi + + +def test_log_apply_evalf(): + value = (log(3)/log(2) - 1).evalf() + assert value.epsilon_eq(Float("0.58496250072115618145373")) + + +def test_log_leading_term(): + p = Symbol('p') + + # Test for STEP 3 + assert log(1 + x + x**2).as_leading_term(x, cdir=1) == x + # Test for STEP 4 + assert log(2*x).as_leading_term(x, cdir=1) == log(x) + log(2) + assert log(2*x).as_leading_term(x, cdir=-1) == log(x) + log(2) + assert log(-2*x).as_leading_term(x, cdir=1, logx=p) == p + log(2) + I*pi + assert log(-2*x).as_leading_term(x, cdir=-1, logx=p) == p + log(2) - I*pi + # Test for STEP 5 + assert log(-2*x + (3 - I)*x**2).as_leading_term(x, cdir=1) == log(x) + log(2) - I*pi + assert log(-2*x + (3 - I)*x**2).as_leading_term(x, cdir=-1) == log(x) + log(2) - I*pi + assert log(2*x + (3 - I)*x**2).as_leading_term(x, cdir=1) == log(x) + log(2) + assert log(2*x + (3 - I)*x**2).as_leading_term(x, cdir=-1) == log(x) + log(2) - 2*I*pi + assert log(-1 + x - I*x**2 + I*x**3).as_leading_term(x, cdir=1) == -I*pi + assert log(-1 + x - I*x**2 + I*x**3).as_leading_term(x, cdir=-1) == -I*pi + assert log(-1/(1 - x)).as_leading_term(x, cdir=1) == I*pi + assert log(-1/(1 - x)).as_leading_term(x, cdir=-1) == I*pi + + +def test_log_nseries(): + p = Symbol('p') + assert log(1/x)._eval_nseries(x, 4, logx=-p, cdir=1) == p + assert log(1/x)._eval_nseries(x, 4, logx=-p, cdir=-1) == p + 2*I*pi + assert log(x - 1)._eval_nseries(x, 4, None, I) == I*pi - x - x**2/2 - x**3/3 + O(x**4) + assert log(x - 1)._eval_nseries(x, 4, None, -I) == -I*pi - x - x**2/2 - x**3/3 + O(x**4) + assert log(I*x + I*x**3 - 1)._eval_nseries(x, 3, None, 1) == I*pi - I*x + x**2/2 + O(x**3) + assert log(I*x + I*x**3 - 1)._eval_nseries(x, 3, None, -1) == -I*pi - I*x + x**2/2 + O(x**3) + assert log(I*x**2 + I*x**3 - 1)._eval_nseries(x, 3, None, 1) == I*pi - I*x**2 + O(x**3) + assert log(I*x**2 + I*x**3 - 1)._eval_nseries(x, 3, None, -1) == I*pi - I*x**2 + O(x**3) + assert log(2*x + (3 - I)*x**2)._eval_nseries(x, 3, None, 1) == log(2) + log(x) + \ + x*(S(3)/2 - I/2) + x**2*(-1 + 3*I/4) + O(x**3) + assert log(2*x + (3 - I)*x**2)._eval_nseries(x, 3, None, -1) == -2*I*pi + log(2) + \ + log(x) - x*(-S(3)/2 + I/2) + x**2*(-1 + 3*I/4) + O(x**3) + assert log(-2*x + (3 - I)*x**2)._eval_nseries(x, 3, None, 1) == -I*pi + log(2) + log(x) + \ + x*(-S(3)/2 + I/2) + x**2*(-1 + 3*I/4) + O(x**3) + assert log(-2*x + (3 - I)*x**2)._eval_nseries(x, 3, None, -1) == -I*pi + log(2) + log(x) - \ + x*(S(3)/2 - I/2) + x**2*(-1 + 3*I/4) + O(x**3) + assert log(sqrt(-I*x**2 - 3)*sqrt(-I*x**2 - 1) - 2)._eval_nseries(x, 3, None, 1) == -I*pi + \ + log(sqrt(3) + 2) + 2*sqrt(3)*I*x**2/(3*sqrt(3) + 6) + O(x**3) + assert log(-1/(1 - x))._eval_nseries(x, 3, None, 1) == I*pi + x + x**2/2 + O(x**3) + assert log(-1/(1 - x))._eval_nseries(x, 3, None, -1) == I*pi + x + x**2/2 + O(x**3) + + +def test_log_series(): + # Note Series at infinities other than oo/-oo were introduced as a part of + # pull request 23798. Refer https://github.com/sympy/sympy/pull/23798 for + # more information. + expr1 = log(1 + x) + expr2 = log(x + sqrt(x**2 + 1)) + + assert expr1.series(x, x0=I*oo, n=4) == 1/(3*x**3) - 1/(2*x**2) + 1/x + \ + I*pi/2 - log(I/x) + O(x**(-4), (x, oo*I)) + assert expr1.series(x, x0=-I*oo, n=4) == 1/(3*x**3) - 1/(2*x**2) + 1/x - \ + I*pi/2 - log(-I/x) + O(x**(-4), (x, -oo*I)) + assert expr2.series(x, x0=I*oo, n=4) == 1/(4*x**2) + I*pi/2 + log(2) - \ + log(I/x) + O(x**(-4), (x, oo*I)) + assert expr2.series(x, x0=-I*oo, n=4) == -1/(4*x**2) - I*pi/2 - log(2) + \ + log(-I/x) + O(x**(-4), (x, -oo*I)) + + +def test_log_expand(): + w = Symbol("w", positive=True) + e = log(w**(log(5)/log(3))) + assert e.expand() == log(5)/log(3) * log(w) + x, y, z = symbols('x,y,z', positive=True) + assert log(x*(y + z)).expand(mul=False) == log(x) + log(y + z) + assert log(log(x**2)*log(y*z)).expand() in [log(2*log(x)*log(y) + + 2*log(x)*log(z)), log(log(x)*log(z) + log(y)*log(x)) + log(2), + log((log(y) + log(z))*log(x)) + log(2)] + assert log(x**log(x**2)).expand(deep=False) == log(x)*log(x**2) + assert log(x**log(x**2)).expand() == 2*log(x)**2 + x, y = symbols('x,y') + assert log(x*y).expand(force=True) == log(x) + log(y) + assert log(x**y).expand(force=True) == y*log(x) + assert log(exp(x)).expand(force=True) == x + + # there's generally no need to expand out logs since this requires + # factoring and if simplification is sought, it's cheaper to put + # logs together than it is to take them apart. + assert log(2*3**2).expand() != 2*log(3) + log(2) + + +@XFAIL +def test_log_expand_fail(): + x, y, z = symbols('x,y,z', positive=True) + assert (log(x*(y + z))*(x + y)).expand(mul=True, log=True) == y*log( + x) + y*log(y + z) + z*log(x) + z*log(y + z) + + +def test_log_simplify(): + x = Symbol("x", positive=True) + assert log(x**2).expand() == 2*log(x) + assert expand_log(log(x**(2 + log(2)))) == (2 + log(2))*log(x) + + z = Symbol('z') + assert log(sqrt(z)).expand() == log(z)/2 + assert expand_log(log(z**(log(2) - 1))) == (log(2) - 1)*log(z) + assert log(z**(-1)).expand() != -log(z) + assert log(z**(x/(x+1))).expand() == x*log(z)/(x + 1) + + +def test_log_AccumBounds(): + assert log(AccumBounds(1, E)) == AccumBounds(0, 1) + assert log(AccumBounds(0, E)) == AccumBounds(-oo, 1) + assert log(AccumBounds(-1, E)) == S.NaN + assert log(AccumBounds(0, oo)) == AccumBounds(-oo, oo) + assert log(AccumBounds(-oo, 0)) == S.NaN + assert log(AccumBounds(-oo, oo)) == S.NaN + + +@_both_exp_pow +def test_lambertw(): + k = Symbol('k') + + assert LambertW(x, 0) == LambertW(x) + assert LambertW(x, 0, evaluate=False) != LambertW(x) + assert LambertW(0) == 0 + assert LambertW(E) == 1 + assert LambertW(-1/E) == -1 + assert LambertW(-log(2)/2) == -log(2) + assert LambertW(oo) is oo + assert LambertW(0, 1) is -oo + assert LambertW(0, 42) is -oo + assert LambertW(-pi/2, -1) == -I*pi/2 + assert LambertW(-1/E, -1) == -1 + assert LambertW(-2*exp(-2), -1) == -2 + assert LambertW(2*log(2)) == log(2) + assert LambertW(-pi/2) == I*pi/2 + assert LambertW(exp(1 + E)) == E + + assert LambertW(x**2).diff(x) == 2*LambertW(x**2)/x/(1 + LambertW(x**2)) + assert LambertW(x, k).diff(x) == LambertW(x, k)/x/(1 + LambertW(x, k)) + + assert LambertW(sqrt(2)).evalf(30).epsilon_eq( + Float("0.701338383413663009202120278965", 30), 1e-29) + assert re(LambertW(2, -1)).evalf().epsilon_eq(Float("-0.834310366631110")) + + assert LambertW(-1).is_real is False # issue 5215 + assert LambertW(2, evaluate=False).is_real + p = Symbol('p', positive=True) + assert LambertW(p, evaluate=False).is_real + assert LambertW(p - 1, evaluate=False).is_real is None + assert LambertW(-p - 2/S.Exp1, evaluate=False).is_real is False + assert LambertW(S.Half, -1, evaluate=False).is_real is False + assert LambertW(Rational(-1, 10), -1, evaluate=False).is_real + assert LambertW(-10, -1, evaluate=False).is_real is False + assert LambertW(-2, 2, evaluate=False).is_real is False + + assert LambertW(0, evaluate=False).is_algebraic + na = Symbol('na', nonzero=True, algebraic=True) + assert LambertW(na).is_algebraic is False + assert LambertW(p).is_zero is False + n = Symbol('n', negative=True) + assert LambertW(n).is_zero is False + + +def test_issue_5673(): + e = LambertW(-1) + assert e.is_comparable is False + assert e.is_positive is not True + e2 = 1 - 1/(1 - exp(-1000)) + assert e2.is_positive is not True + e3 = -2 + exp(exp(LambertW(log(2)))*LambertW(log(2))) + assert e3.is_nonzero is not True + + +def test_log_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: log(x).fdiff(2)) + + +def test_log_taylor_term(): + x = symbols('x') + assert log(x).taylor_term(0, x) == x + assert log(x).taylor_term(1, x) == -x**2/2 + assert log(x).taylor_term(4, x) == x**5/5 + assert log(x).taylor_term(-1, x) is S.Zero + + +def test_exp_expand_NC(): + A, B, C = symbols('A,B,C', commutative=False) + + assert exp(A + B).expand() == exp(A + B) + assert exp(A + B + C).expand() == exp(A + B + C) + assert exp(x + y).expand() == exp(x)*exp(y) + assert exp(x + y + z).expand() == exp(x)*exp(y)*exp(z) + + +@_both_exp_pow +def test_as_numer_denom(): + n = symbols('n', negative=True) + assert exp(x).as_numer_denom() == (exp(x), 1) + assert exp(-x).as_numer_denom() == (1, exp(x)) + assert exp(-2*x).as_numer_denom() == (1, exp(2*x)) + assert exp(-2).as_numer_denom() == (1, exp(2)) + assert exp(n).as_numer_denom() == (1, exp(-n)) + assert exp(-n).as_numer_denom() == (exp(-n), 1) + assert exp(-I*x).as_numer_denom() == (1, exp(I*x)) + assert exp(-I*n).as_numer_denom() == (1, exp(I*n)) + assert exp(-n).as_numer_denom() == (exp(-n), 1) + # Check noncommutativity + a = symbols('a', commutative=False) + assert exp(-a).as_numer_denom() == (exp(-a), 1) + + +@_both_exp_pow +def test_polar(): + x, y = symbols('x y', polar=True) + + assert abs(exp_polar(I*4)) == 1 + assert abs(exp_polar(0)) == 1 + assert abs(exp_polar(2 + 3*I)) == exp(2) + assert exp_polar(I*10).n() == exp_polar(I*10) + + assert log(exp_polar(z)) == z + assert log(x*y).expand() == log(x) + log(y) + assert log(x**z).expand() == z*log(x) + + assert exp_polar(3).exp == 3 + + # Compare exp(1.0*pi*I). + assert (exp_polar(1.0*pi*I).n(n=5)).as_real_imag()[1] >= 0 + + assert exp_polar(0).is_rational is True # issue 8008 + + +def test_exp_summation(): + w = symbols("w") + m, n, i, j = symbols("m n i j") + expr = exp(Sum(w*i, (i, 0, n), (j, 0, m))) + assert expr.expand() == Product(exp(w*i), (i, 0, n), (j, 0, m)) + + +def test_log_product(): + from sympy.abc import n, m + + i, j = symbols('i,j', positive=True, integer=True) + x, y = symbols('x,y', positive=True) + z = symbols('z', real=True) + w = symbols('w') + + expr = log(Product(x**i, (i, 1, n))) + assert simplify(expr) == expr + assert expr.expand() == Sum(i*log(x), (i, 1, n)) + expr = log(Product(x**i*y**j, (i, 1, n), (j, 1, m))) + assert simplify(expr) == expr + assert expr.expand() == Sum(i*log(x) + j*log(y), (i, 1, n), (j, 1, m)) + + expr = log(Product(-2, (n, 0, 4))) + assert simplify(expr) == expr + assert expr.expand() == expr + assert expr.expand(force=True) == Sum(log(-2), (n, 0, 4)) + + expr = log(Product(exp(z*i), (i, 0, n))) + assert expr.expand() == Sum(z*i, (i, 0, n)) + + expr = log(Product(exp(w*i), (i, 0, n))) + assert expr.expand() == expr + assert expr.expand(force=True) == Sum(w*i, (i, 0, n)) + + expr = log(Product(i**2*abs(j), (i, 1, n), (j, 1, m))) + assert expr.expand() == Sum(2*log(i) + log(j), (i, 1, n), (j, 1, m)) + + +@XFAIL +def test_log_product_simplify_to_sum(): + from sympy.abc import n, m + i, j = symbols('i,j', positive=True, integer=True) + x, y = symbols('x,y', positive=True) + assert simplify(log(Product(x**i, (i, 1, n)))) == Sum(i*log(x), (i, 1, n)) + assert simplify(log(Product(x**i*y**j, (i, 1, n), (j, 1, m)))) == \ + Sum(i*log(x) + j*log(y), (i, 1, n), (j, 1, m)) + + +def test_issue_8866(): + assert simplify(log(x, 10, evaluate=False)) == simplify(log(x, 10)) + assert expand_log(log(x, 10, evaluate=False)) == expand_log(log(x, 10)) + + y = Symbol('y', positive=True) + l1 = log(exp(y), exp(10)) + b1 = log(exp(y), exp(5)) + l2 = log(exp(y), exp(10), evaluate=False) + b2 = log(exp(y), exp(5), evaluate=False) + assert simplify(log(l1, b1)) == simplify(log(l2, b2)) + assert expand_log(log(l1, b1)) == expand_log(log(l2, b2)) + + +def test_log_expand_factor(): + assert (log(18)/log(3) - 2).expand(factor=True) == log(2)/log(3) + assert (log(12)/log(2)).expand(factor=True) == log(3)/log(2) + 2 + assert (log(15)/log(3)).expand(factor=True) == 1 + log(5)/log(3) + assert (log(2)/(-log(12) + log(24))).expand(factor=True) == 1 + + assert expand_log(log(12), factor=True) == log(3) + 2*log(2) + assert expand_log(log(21)/log(7), factor=False) == log(3)/log(7) + 1 + assert expand_log(log(45)/log(5) + log(20), factor=False) == \ + 1 + 2*log(3)/log(5) + log(20) + assert expand_log(log(45)/log(5) + log(26), factor=True) == \ + log(2) + log(13) + (log(5) + 2*log(3))/log(5) + + +def test_issue_9116(): + n = Symbol('n', positive=True, integer=True) + assert log(n).is_nonnegative is True + + +def test_issue_18473(): + assert exp(x*log(cos(1/x))).as_leading_term(x) == S.NaN + assert exp(x*log(tan(1/x))).as_leading_term(x) == S.NaN + assert log(cos(1/x)).as_leading_term(x) == S.NaN + assert log(tan(1/x)).as_leading_term(x) == S.NaN + assert log(cos(1/x) + 2).as_leading_term(x) == AccumBounds(0, log(3)) + assert exp(x*log(cos(1/x) + 2)).as_leading_term(x) == 1 + assert log(cos(1/x) - 2).as_leading_term(x) == S.NaN + assert exp(x*log(cos(1/x) - 2)).as_leading_term(x) == S.NaN + assert log(cos(1/x) + 1).as_leading_term(x) == AccumBounds(-oo, log(2)) + assert exp(x*log(cos(1/x) + 1)).as_leading_term(x) == AccumBounds(0, 1) + assert log(sin(1/x)**2).as_leading_term(x) == AccumBounds(-oo, 0) + assert exp(x*log(sin(1/x)**2)).as_leading_term(x) == AccumBounds(0, 1) + assert log(tan(1/x)**2).as_leading_term(x) == AccumBounds(-oo, oo) + assert exp(2*x*(log(tan(1/x)**2))).as_leading_term(x) == AccumBounds(0, oo) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_hyperbolic.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_hyperbolic.py new file mode 100644 index 0000000000000000000000000000000000000000..1ad9f1d51598b9d605b0472e254c5a710d4ed4f5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_hyperbolic.py @@ -0,0 +1,1553 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core.function import (expand_mul, expand_trig) +from sympy.core.numbers import (E, I, Integer, Rational, nan, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (acosh, acoth, acsch, asech, asinh, atanh, cosh, coth, csch, sech, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, asin, cos, cot, sec, sin, tan) +from sympy.series.order import O + +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError, PoleError +from sympy.testing.pytest import raises + + +def test_sinh(): + x, y = symbols('x,y') + + k = Symbol('k', integer=True) + + assert sinh(nan) is nan + assert sinh(zoo) is nan + + assert sinh(oo) is oo + assert sinh(-oo) is -oo + + assert sinh(0) == 0 + + assert unchanged(sinh, 1) + assert sinh(-1) == -sinh(1) + + assert unchanged(sinh, x) + assert sinh(-x) == -sinh(x) + + assert unchanged(sinh, pi) + assert sinh(-pi) == -sinh(pi) + + assert unchanged(sinh, 2**1024 * E) + assert sinh(-2**1024 * E) == -sinh(2**1024 * E) + + assert sinh(pi*I) == 0 + assert sinh(-pi*I) == 0 + assert sinh(2*pi*I) == 0 + assert sinh(-2*pi*I) == 0 + assert sinh(-3*10**73*pi*I) == 0 + assert sinh(7*10**103*pi*I) == 0 + + assert sinh(pi*I/2) == I + assert sinh(-pi*I/2) == -I + assert sinh(pi*I*Rational(5, 2)) == I + assert sinh(pi*I*Rational(7, 2)) == -I + + assert sinh(pi*I/3) == S.Half*sqrt(3)*I + assert sinh(pi*I*Rational(-2, 3)) == Rational(-1, 2)*sqrt(3)*I + + assert sinh(pi*I/4) == S.Half*sqrt(2)*I + assert sinh(-pi*I/4) == Rational(-1, 2)*sqrt(2)*I + assert sinh(pi*I*Rational(17, 4)) == S.Half*sqrt(2)*I + assert sinh(pi*I*Rational(-3, 4)) == Rational(-1, 2)*sqrt(2)*I + + assert sinh(pi*I/6) == S.Half*I + assert sinh(-pi*I/6) == Rational(-1, 2)*I + assert sinh(pi*I*Rational(7, 6)) == Rational(-1, 2)*I + assert sinh(pi*I*Rational(-5, 6)) == Rational(-1, 2)*I + + assert sinh(pi*I/105) == sin(pi/105)*I + assert sinh(-pi*I/105) == -sin(pi/105)*I + + assert unchanged(sinh, 2 + 3*I) + + assert sinh(x*I) == sin(x)*I + + assert sinh(k*pi*I) == 0 + assert sinh(17*k*pi*I) == 0 + + assert sinh(k*pi*I/2) == sin(k*pi/2)*I + + assert sinh(x).as_real_imag(deep=False) == (cos(im(x))*sinh(re(x)), + sin(im(x))*cosh(re(x))) + x = Symbol('x', extended_real=True) + assert sinh(x).as_real_imag(deep=False) == (sinh(x), 0) + + x = Symbol('x', real=True) + assert sinh(I*x).is_finite is True + assert sinh(x).is_real is True + assert sinh(I).is_real is False + p = Symbol('p', positive=True) + assert sinh(p).is_zero is False + assert sinh(0, evaluate=False).is_zero is True + assert sinh(2*pi*I, evaluate=False).is_zero is True + + +def test_sinh_series(): + x = Symbol('x') + assert sinh(x).series(x, 0, 10) == \ + x + x**3/6 + x**5/120 + x**7/5040 + x**9/362880 + O(x**10) + + +def test_sinh_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: sinh(x).fdiff(2)) + + +def test_cosh(): + x, y = symbols('x,y') + + k = Symbol('k', integer=True) + + assert cosh(nan) is nan + assert cosh(zoo) is nan + + assert cosh(oo) is oo + assert cosh(-oo) is oo + + assert cosh(0) == 1 + + assert unchanged(cosh, 1) + assert cosh(-1) == cosh(1) + + assert unchanged(cosh, x) + assert cosh(-x) == cosh(x) + + assert cosh(pi*I) == cos(pi) + assert cosh(-pi*I) == cos(pi) + + assert unchanged(cosh, 2**1024 * E) + assert cosh(-2**1024 * E) == cosh(2**1024 * E) + + assert cosh(pi*I/2) == 0 + assert cosh(-pi*I/2) == 0 + assert cosh((-3*10**73 + 1)*pi*I/2) == 0 + assert cosh((7*10**103 + 1)*pi*I/2) == 0 + + assert cosh(pi*I) == -1 + assert cosh(-pi*I) == -1 + assert cosh(5*pi*I) == -1 + assert cosh(8*pi*I) == 1 + + assert cosh(pi*I/3) == S.Half + assert cosh(pi*I*Rational(-2, 3)) == Rational(-1, 2) + + assert cosh(pi*I/4) == S.Half*sqrt(2) + assert cosh(-pi*I/4) == S.Half*sqrt(2) + assert cosh(pi*I*Rational(11, 4)) == Rational(-1, 2)*sqrt(2) + assert cosh(pi*I*Rational(-3, 4)) == Rational(-1, 2)*sqrt(2) + + assert cosh(pi*I/6) == S.Half*sqrt(3) + assert cosh(-pi*I/6) == S.Half*sqrt(3) + assert cosh(pi*I*Rational(7, 6)) == Rational(-1, 2)*sqrt(3) + assert cosh(pi*I*Rational(-5, 6)) == Rational(-1, 2)*sqrt(3) + + assert cosh(pi*I/105) == cos(pi/105) + assert cosh(-pi*I/105) == cos(pi/105) + + assert unchanged(cosh, 2 + 3*I) + + assert cosh(x*I) == cos(x) + + assert cosh(k*pi*I) == cos(k*pi) + assert cosh(17*k*pi*I) == cos(17*k*pi) + + assert unchanged(cosh, k*pi) + + assert cosh(x).as_real_imag(deep=False) == (cos(im(x))*cosh(re(x)), + sin(im(x))*sinh(re(x))) + x = Symbol('x', extended_real=True) + assert cosh(x).as_real_imag(deep=False) == (cosh(x), 0) + + x = Symbol('x', real=True) + assert cosh(I*x).is_finite is True + assert cosh(I*x).is_real is True + assert cosh(I*2 + 1).is_real is False + assert cosh(5*I*S.Pi/2, evaluate=False).is_zero is True + assert cosh(x).is_zero is False + + +def test_cosh_series(): + x = Symbol('x') + assert cosh(x).series(x, 0, 10) == \ + 1 + x**2/2 + x**4/24 + x**6/720 + x**8/40320 + O(x**10) + + +def test_cosh_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: cosh(x).fdiff(2)) + + +def test_tanh(): + x, y = symbols('x,y') + + k = Symbol('k', integer=True) + + assert tanh(nan) is nan + assert tanh(zoo) is nan + + assert tanh(oo) == 1 + assert tanh(-oo) == -1 + + assert tanh(0) == 0 + + assert unchanged(tanh, 1) + assert tanh(-1) == -tanh(1) + + assert unchanged(tanh, x) + assert tanh(-x) == -tanh(x) + + assert unchanged(tanh, pi) + assert tanh(-pi) == -tanh(pi) + + assert unchanged(tanh, 2**1024 * E) + assert tanh(-2**1024 * E) == -tanh(2**1024 * E) + + assert tanh(pi*I) == 0 + assert tanh(-pi*I) == 0 + assert tanh(2*pi*I) == 0 + assert tanh(-2*pi*I) == 0 + assert tanh(-3*10**73*pi*I) == 0 + assert tanh(7*10**103*pi*I) == 0 + + assert tanh(pi*I/2) is zoo + assert tanh(-pi*I/2) is zoo + assert tanh(pi*I*Rational(5, 2)) is zoo + assert tanh(pi*I*Rational(7, 2)) is zoo + + assert tanh(pi*I/3) == sqrt(3)*I + assert tanh(pi*I*Rational(-2, 3)) == sqrt(3)*I + + assert tanh(pi*I/4) == I + assert tanh(-pi*I/4) == -I + assert tanh(pi*I*Rational(17, 4)) == I + assert tanh(pi*I*Rational(-3, 4)) == I + + assert tanh(pi*I/6) == I/sqrt(3) + assert tanh(-pi*I/6) == -I/sqrt(3) + assert tanh(pi*I*Rational(7, 6)) == I/sqrt(3) + assert tanh(pi*I*Rational(-5, 6)) == I/sqrt(3) + + assert tanh(pi*I/105) == tan(pi/105)*I + assert tanh(-pi*I/105) == -tan(pi/105)*I + + assert unchanged(tanh, 2 + 3*I) + + assert tanh(x*I) == tan(x)*I + + assert tanh(k*pi*I) == 0 + assert tanh(17*k*pi*I) == 0 + + assert tanh(k*pi*I/2) == tan(k*pi/2)*I + + assert tanh(x).as_real_imag(deep=False) == (sinh(re(x))*cosh(re(x))/(cos(im(x))**2 + + sinh(re(x))**2), + sin(im(x))*cos(im(x))/(cos(im(x))**2 + sinh(re(x))**2)) + x = Symbol('x', extended_real=True) + assert tanh(x).as_real_imag(deep=False) == (tanh(x), 0) + assert tanh(I*pi/3 + 1).is_real is False + assert tanh(x).is_real is True + assert tanh(I*pi*x/2).is_real is None + + +def test_tanh_series(): + x = Symbol('x') + assert tanh(x).series(x, 0, 10) == \ + x - x**3/3 + 2*x**5/15 - 17*x**7/315 + 62*x**9/2835 + O(x**10) + + +def test_tanh_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: tanh(x).fdiff(2)) + + +def test_coth(): + x, y = symbols('x,y') + + k = Symbol('k', integer=True) + + assert coth(nan) is nan + assert coth(zoo) is nan + + assert coth(oo) == 1 + assert coth(-oo) == -1 + + assert coth(0) is zoo + assert unchanged(coth, 1) + assert coth(-1) == -coth(1) + + assert unchanged(coth, x) + assert coth(-x) == -coth(x) + + assert coth(pi*I) == -I*cot(pi) + assert coth(-pi*I) == cot(pi)*I + + assert unchanged(coth, 2**1024 * E) + assert coth(-2**1024 * E) == -coth(2**1024 * E) + + assert coth(pi*I) == -I*cot(pi) + assert coth(-pi*I) == I*cot(pi) + assert coth(2*pi*I) == -I*cot(2*pi) + assert coth(-2*pi*I) == I*cot(2*pi) + assert coth(-3*10**73*pi*I) == I*cot(3*10**73*pi) + assert coth(7*10**103*pi*I) == -I*cot(7*10**103*pi) + + assert coth(pi*I/2) == 0 + assert coth(-pi*I/2) == 0 + assert coth(pi*I*Rational(5, 2)) == 0 + assert coth(pi*I*Rational(7, 2)) == 0 + + assert coth(pi*I/3) == -I/sqrt(3) + assert coth(pi*I*Rational(-2, 3)) == -I/sqrt(3) + + assert coth(pi*I/4) == -I + assert coth(-pi*I/4) == I + assert coth(pi*I*Rational(17, 4)) == -I + assert coth(pi*I*Rational(-3, 4)) == -I + + assert coth(pi*I/6) == -sqrt(3)*I + assert coth(-pi*I/6) == sqrt(3)*I + assert coth(pi*I*Rational(7, 6)) == -sqrt(3)*I + assert coth(pi*I*Rational(-5, 6)) == -sqrt(3)*I + + assert coth(pi*I/105) == -cot(pi/105)*I + assert coth(-pi*I/105) == cot(pi/105)*I + + assert unchanged(coth, 2 + 3*I) + + assert coth(x*I) == -cot(x)*I + + assert coth(k*pi*I) == -cot(k*pi)*I + assert coth(17*k*pi*I) == -cot(17*k*pi)*I + + assert coth(k*pi*I) == -cot(k*pi)*I + + assert coth(log(tan(2))) == coth(log(-tan(2))) + assert coth(1 + I*pi/2) == tanh(1) + + assert coth(x).as_real_imag(deep=False) == (sinh(re(x))*cosh(re(x))/(sin(im(x))**2 + + sinh(re(x))**2), + -sin(im(x))*cos(im(x))/(sin(im(x))**2 + sinh(re(x))**2)) + x = Symbol('x', extended_real=True) + assert coth(x).as_real_imag(deep=False) == (coth(x), 0) + + assert expand_trig(coth(2*x)) == (coth(x)**2 + 1)/(2*coth(x)) + assert expand_trig(coth(3*x)) == (coth(x)**3 + 3*coth(x))/(1 + 3*coth(x)**2) + + assert expand_trig(coth(x + y)) == (1 + coth(x)*coth(y))/(coth(x) + coth(y)) + + +def test_coth_series(): + x = Symbol('x') + assert coth(x).series(x, 0, 8) == \ + 1/x + x/3 - x**3/45 + 2*x**5/945 - x**7/4725 + O(x**8) + + +def test_coth_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: coth(x).fdiff(2)) + + +def test_csch(): + x, y = symbols('x,y') + + k = Symbol('k', integer=True) + n = Symbol('n', positive=True) + + assert csch(nan) is nan + assert csch(zoo) is nan + + assert csch(oo) == 0 + assert csch(-oo) == 0 + + assert csch(0) is zoo + + assert csch(-1) == -csch(1) + + assert csch(-x) == -csch(x) + assert csch(-pi) == -csch(pi) + assert csch(-2**1024 * E) == -csch(2**1024 * E) + + assert csch(pi*I) is zoo + assert csch(-pi*I) is zoo + assert csch(2*pi*I) is zoo + assert csch(-2*pi*I) is zoo + assert csch(-3*10**73*pi*I) is zoo + assert csch(7*10**103*pi*I) is zoo + + assert csch(pi*I/2) == -I + assert csch(-pi*I/2) == I + assert csch(pi*I*Rational(5, 2)) == -I + assert csch(pi*I*Rational(7, 2)) == I + + assert csch(pi*I/3) == -2/sqrt(3)*I + assert csch(pi*I*Rational(-2, 3)) == 2/sqrt(3)*I + + assert csch(pi*I/4) == -sqrt(2)*I + assert csch(-pi*I/4) == sqrt(2)*I + assert csch(pi*I*Rational(7, 4)) == sqrt(2)*I + assert csch(pi*I*Rational(-3, 4)) == sqrt(2)*I + + assert csch(pi*I/6) == -2*I + assert csch(-pi*I/6) == 2*I + assert csch(pi*I*Rational(7, 6)) == 2*I + assert csch(pi*I*Rational(-7, 6)) == -2*I + assert csch(pi*I*Rational(-5, 6)) == 2*I + + assert csch(pi*I/105) == -1/sin(pi/105)*I + assert csch(-pi*I/105) == 1/sin(pi/105)*I + + assert csch(x*I) == -1/sin(x)*I + + assert csch(k*pi*I) is zoo + assert csch(17*k*pi*I) is zoo + + assert csch(k*pi*I/2) == -1/sin(k*pi/2)*I + + assert csch(n).is_real is True + + assert expand_trig(csch(x + y)) == 1/(sinh(x)*cosh(y) + cosh(x)*sinh(y)) + + +def test_csch_series(): + x = Symbol('x') + assert csch(x).series(x, 0, 10) == \ + 1/ x - x/6 + 7*x**3/360 - 31*x**5/15120 + 127*x**7/604800 \ + - 73*x**9/3421440 + O(x**10) + + +def test_csch_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: csch(x).fdiff(2)) + + +def test_sech(): + x, y = symbols('x, y') + + k = Symbol('k', integer=True) + n = Symbol('n', positive=True) + + assert sech(nan) is nan + assert sech(zoo) is nan + + assert sech(oo) == 0 + assert sech(-oo) == 0 + + assert sech(0) == 1 + + assert sech(-1) == sech(1) + assert sech(-x) == sech(x) + + assert sech(pi*I) == sec(pi) + + assert sech(-pi*I) == sec(pi) + assert sech(-2**1024 * E) == sech(2**1024 * E) + + assert sech(pi*I/2) is zoo + assert sech(-pi*I/2) is zoo + assert sech((-3*10**73 + 1)*pi*I/2) is zoo + assert sech((7*10**103 + 1)*pi*I/2) is zoo + + assert sech(pi*I) == -1 + assert sech(-pi*I) == -1 + assert sech(5*pi*I) == -1 + assert sech(8*pi*I) == 1 + + assert sech(pi*I/3) == 2 + assert sech(pi*I*Rational(-2, 3)) == -2 + + assert sech(pi*I/4) == sqrt(2) + assert sech(-pi*I/4) == sqrt(2) + assert sech(pi*I*Rational(5, 4)) == -sqrt(2) + assert sech(pi*I*Rational(-5, 4)) == -sqrt(2) + + assert sech(pi*I/6) == 2/sqrt(3) + assert sech(-pi*I/6) == 2/sqrt(3) + assert sech(pi*I*Rational(7, 6)) == -2/sqrt(3) + assert sech(pi*I*Rational(-5, 6)) == -2/sqrt(3) + + assert sech(pi*I/105) == 1/cos(pi/105) + assert sech(-pi*I/105) == 1/cos(pi/105) + + assert sech(x*I) == 1/cos(x) + + assert sech(k*pi*I) == 1/cos(k*pi) + assert sech(17*k*pi*I) == 1/cos(17*k*pi) + + assert sech(n).is_real is True + + assert expand_trig(sech(x + y)) == 1/(cosh(x)*cosh(y) + sinh(x)*sinh(y)) + + +def test_sech_series(): + x = Symbol('x') + assert sech(x).series(x, 0, 10) == \ + 1 - x**2/2 + 5*x**4/24 - 61*x**6/720 + 277*x**8/8064 + O(x**10) + + +def test_sech_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: sech(x).fdiff(2)) + + +def test_asinh(): + x, y = symbols('x,y') + assert unchanged(asinh, x) + assert asinh(-x) == -asinh(x) + + # at specific points + assert asinh(nan) is nan + assert asinh( 0) == 0 + assert asinh(+1) == log(sqrt(2) + 1) + + assert asinh(-1) == log(sqrt(2) - 1) + assert asinh(I) == pi*I/2 + assert asinh(-I) == -pi*I/2 + assert asinh(I/2) == pi*I/6 + assert asinh(-I/2) == -pi*I/6 + + # at infinites + assert asinh(oo) is oo + assert asinh(-oo) is -oo + + assert asinh(I*oo) is oo + assert asinh(-I *oo) is -oo + + assert asinh(zoo) is zoo + + # properties + assert asinh(I *(sqrt(3) - 1)/(2**Rational(3, 2))) == pi*I/12 + assert asinh(-I *(sqrt(3) - 1)/(2**Rational(3, 2))) == -pi*I/12 + + assert asinh(I*(sqrt(5) - 1)/4) == pi*I/10 + assert asinh(-I*(sqrt(5) - 1)/4) == -pi*I/10 + + assert asinh(I*(sqrt(5) + 1)/4) == pi*I*Rational(3, 10) + assert asinh(-I*(sqrt(5) + 1)/4) == pi*I*Rational(-3, 10) + + # reality + assert asinh(S(2)).is_real is True + assert asinh(S(2)).is_finite is True + assert asinh(S(-2)).is_real is True + assert asinh(S(oo)).is_extended_real is True + assert asinh(-S(oo)).is_real is False + assert (asinh(2) - oo) == -oo + assert asinh(symbols('y', real=True)).is_real is True + + # Symmetry + assert asinh(Rational(-1, 2)) == -asinh(S.Half) + + # inverse composition + assert unchanged(asinh, sinh(Symbol('v1'))) + + assert asinh(sinh(0, evaluate=False)) == 0 + assert asinh(sinh(-3, evaluate=False)) == -3 + assert asinh(sinh(2, evaluate=False)) == 2 + assert asinh(sinh(I, evaluate=False)) == I + assert asinh(sinh(-I, evaluate=False)) == -I + assert asinh(sinh(5*I, evaluate=False)) == -2*I*pi + 5*I + assert asinh(sinh(15 + 11*I)) == 15 - 4*I*pi + 11*I + assert asinh(sinh(-73 + 97*I)) == 73 - 97*I + 31*I*pi + assert asinh(sinh(-7 - 23*I)) == 7 - 7*I*pi + 23*I + assert asinh(sinh(13 - 3*I)) == -13 - I*pi + 3*I + p = Symbol('p', positive=True) + assert asinh(p).is_zero is False + assert asinh(sinh(0, evaluate=False), evaluate=False).is_zero is True + + +def test_asinh_rewrite(): + x = Symbol('x') + assert asinh(x).rewrite(log) == log(x + sqrt(x**2 + 1)) + assert asinh(x).rewrite(atanh) == atanh(x/sqrt(1 + x**2)) + assert asinh(x).rewrite(asin) == -I*asin(I*x, evaluate=False) + assert asinh(x*(1 + I)).rewrite(asin) == -I*asin(I*x*(1+I)) + assert asinh(x).rewrite(acos) == I*acos(I*x, evaluate=False) - I*pi/2 + + +def test_asinh_leading_term(): + x = Symbol('x') + assert asinh(x).as_leading_term(x, cdir=1) == x + # Tests concerning branch points + assert asinh(x + I).as_leading_term(x, cdir=1) == I*pi/2 + assert asinh(x - I).as_leading_term(x, cdir=1) == -I*pi/2 + assert asinh(1/x).as_leading_term(x, cdir=1) == -log(x) + log(2) + assert asinh(1/x).as_leading_term(x, cdir=-1) == log(x) - log(2) - I*pi + # Tests concerning points lying on branch cuts + assert asinh(x + 2*I).as_leading_term(x, cdir=1) == I*asin(2) + assert asinh(x + 2*I).as_leading_term(x, cdir=-1) == -I*asin(2) + I*pi + assert asinh(x - 2*I).as_leading_term(x, cdir=1) == -I*pi + I*asin(2) + assert asinh(x - 2*I).as_leading_term(x, cdir=-1) == -I*asin(2) + # Tests concerning re(ndir) == 0 + assert asinh(2*I + I*x - x**2).as_leading_term(x, cdir=1) == log(2 - sqrt(3)) + I*pi/2 + assert asinh(2*I + I*x - x**2).as_leading_term(x, cdir=-1) == log(2 - sqrt(3)) + I*pi/2 + + +def test_asinh_series(): + x = Symbol('x') + assert asinh(x).series(x, 0, 8) == \ + x - x**3/6 + 3*x**5/40 - 5*x**7/112 + O(x**8) + t5 = asinh(x).taylor_term(5, x) + assert t5 == 3*x**5/40 + assert asinh(x).taylor_term(7, x, t5, 0) == -5*x**7/112 + + +def test_asinh_nseries(): + x = Symbol('x') + # Tests concerning branch points + assert asinh(x + I)._eval_nseries(x, 4, None) == I*pi/2 - \ + sqrt(2)*sqrt(I)*I*sqrt(x) + sqrt(2)*sqrt(I)*x**(S(3)/2)/12 + 3*sqrt(2)*sqrt(I)*I*x**(S(5)/2)/160 - \ + 5*sqrt(2)*sqrt(I)*x**(S(7)/2)/896 + O(x**4) + assert asinh(x - I)._eval_nseries(x, 4, None) == -I*pi/2 + \ + sqrt(2)*I*sqrt(x)*sqrt(-I) + sqrt(2)*x**(S(3)/2)*sqrt(-I)/12 - \ + 3*sqrt(2)*I*x**(S(5)/2)*sqrt(-I)/160 - 5*sqrt(2)*x**(S(7)/2)*sqrt(-I)/896 + O(x**4) + # Tests concerning points lying on branch cuts + assert asinh(x + 2*I)._eval_nseries(x, 4, None, cdir=1) == I*asin(2) - \ + sqrt(3)*I*x/3 + sqrt(3)*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + assert asinh(x + 2*I)._eval_nseries(x, 4, None, cdir=-1) == I*pi - I*asin(2) + \ + sqrt(3)*I*x/3 - sqrt(3)*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + assert asinh(x - 2*I)._eval_nseries(x, 4, None, cdir=1) == I*asin(2) - I*pi + \ + sqrt(3)*I*x/3 + sqrt(3)*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + assert asinh(x - 2*I)._eval_nseries(x, 4, None, cdir=-1) == -I*asin(2) - \ + sqrt(3)*I*x/3 - sqrt(3)*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + # Tests concerning re(ndir) == 0 + assert asinh(2*I + I*x - x**2)._eval_nseries(x, 4, None) == I*pi/2 + log(2 - sqrt(3)) + \ + x*(-3 + 2*sqrt(3))/(-6 + 3*sqrt(3)) + x**2*(12 - 36*I + sqrt(3)*(-7 + 21*I))/(-63 + \ + 36*sqrt(3)) + x**3*(-168 + sqrt(3)*(97 - 388*I) + 672*I)/(-1746 + 1008*sqrt(3)) + O(x**4) + + +def test_asinh_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: asinh(x).fdiff(2)) + + +def test_acosh(): + x = Symbol('x') + + assert unchanged(acosh, -x) + + #at specific points + assert acosh(1) == 0 + assert acosh(-1) == pi*I + assert acosh(0) == I*pi/2 + assert acosh(S.Half) == I*pi/3 + assert acosh(Rational(-1, 2)) == pi*I*Rational(2, 3) + assert acosh(nan) is nan + + # at infinites + assert acosh(oo) is oo + assert acosh(-oo) is oo + + assert acosh(I*oo) == oo + I*pi/2 + assert acosh(-I*oo) == oo - I*pi/2 + + assert acosh(zoo) is zoo + + assert acosh(I) == log(I*(1 + sqrt(2))) + assert acosh(-I) == log(-I*(1 + sqrt(2))) + assert acosh((sqrt(3) - 1)/(2*sqrt(2))) == pi*I*Rational(5, 12) + assert acosh(-(sqrt(3) - 1)/(2*sqrt(2))) == pi*I*Rational(7, 12) + assert acosh(sqrt(2)/2) == I*pi/4 + assert acosh(-sqrt(2)/2) == I*pi*Rational(3, 4) + assert acosh(sqrt(3)/2) == I*pi/6 + assert acosh(-sqrt(3)/2) == I*pi*Rational(5, 6) + assert acosh(sqrt(2 + sqrt(2))/2) == I*pi/8 + assert acosh(-sqrt(2 + sqrt(2))/2) == I*pi*Rational(7, 8) + assert acosh(sqrt(2 - sqrt(2))/2) == I*pi*Rational(3, 8) + assert acosh(-sqrt(2 - sqrt(2))/2) == I*pi*Rational(5, 8) + assert acosh((1 + sqrt(3))/(2*sqrt(2))) == I*pi/12 + assert acosh(-(1 + sqrt(3))/(2*sqrt(2))) == I*pi*Rational(11, 12) + assert acosh((sqrt(5) + 1)/4) == I*pi/5 + assert acosh(-(sqrt(5) + 1)/4) == I*pi*Rational(4, 5) + + assert str(acosh(5*I).n(6)) == '2.31244 + 1.5708*I' + assert str(acosh(-5*I).n(6)) == '2.31244 - 1.5708*I' + + # inverse composition + assert unchanged(acosh, Symbol('v1')) + + assert acosh(cosh(-3, evaluate=False)) == 3 + assert acosh(cosh(3, evaluate=False)) == 3 + assert acosh(cosh(0, evaluate=False)) == 0 + assert acosh(cosh(I, evaluate=False)) == I + assert acosh(cosh(-I, evaluate=False)) == I + assert acosh(cosh(7*I, evaluate=False)) == -2*I*pi + 7*I + assert acosh(cosh(1 + I)) == 1 + I + assert acosh(cosh(3 - 3*I)) == 3 - 3*I + assert acosh(cosh(-3 + 2*I)) == 3 - 2*I + assert acosh(cosh(-5 - 17*I)) == 5 - 6*I*pi + 17*I + assert acosh(cosh(-21 + 11*I)) == 21 - 11*I + 4*I*pi + assert acosh(cosh(cosh(1) + I)) == cosh(1) + I + assert acosh(1, evaluate=False).is_zero is True + + # Reality + assert acosh(S(2)).is_real is True + assert acosh(S(2)).is_extended_real is True + assert acosh(oo).is_extended_real is True + assert acosh(S(2)).is_finite is True + assert acosh(S(1) / 5).is_real is False + assert (acosh(2) - oo) == -oo + assert acosh(symbols('y', real=True)).is_real is None + + +def test_acosh_rewrite(): + x = Symbol('x') + assert acosh(x).rewrite(log) == log(x + sqrt(x - 1)*sqrt(x + 1)) + assert acosh(x).rewrite(asin) == sqrt(x - 1)*(-asin(x) + pi/2)/sqrt(1 - x) + assert acosh(x).rewrite(asinh) == sqrt(x - 1)*(I*asinh(I*x, evaluate=False) + pi/2)/sqrt(1 - x) + assert acosh(x).rewrite(atanh) == \ + (sqrt(x - 1)*sqrt(x + 1)*atanh(sqrt(x**2 - 1)/x)/sqrt(x**2 - 1) + + pi*sqrt(x - 1)*(-x*sqrt(x**(-2)) + 1)/(2*sqrt(1 - x))) + x = Symbol('x', positive=True) + assert acosh(x).rewrite(atanh) == \ + sqrt(x - 1)*sqrt(x + 1)*atanh(sqrt(x**2 - 1)/x)/sqrt(x**2 - 1) + + +def test_acosh_leading_term(): + x = Symbol('x') + # Tests concerning branch points + assert acosh(x).as_leading_term(x) == I*pi/2 + assert acosh(x + 1).as_leading_term(x) == sqrt(2)*sqrt(x) + assert acosh(x - 1).as_leading_term(x) == I*pi + assert acosh(1/x).as_leading_term(x, cdir=1) == -log(x) + log(2) + assert acosh(1/x).as_leading_term(x, cdir=-1) == -log(x) + log(2) + 2*I*pi + # Tests concerning points lying on branch cuts + assert acosh(I*x - 2).as_leading_term(x, cdir=1) == acosh(-2) + assert acosh(-I*x - 2).as_leading_term(x, cdir=1) == -2*I*pi + acosh(-2) + assert acosh(x**2 - I*x + S(1)/3).as_leading_term(x, cdir=1) == -acosh(S(1)/3) + assert acosh(x**2 - I*x + S(1)/3).as_leading_term(x, cdir=-1) == acosh(S(1)/3) + assert acosh(1/(I*x - 3)).as_leading_term(x, cdir=1) == -acosh(-S(1)/3) + assert acosh(1/(I*x - 3)).as_leading_term(x, cdir=-1) == acosh(-S(1)/3) + # Tests concerning im(ndir) == 0 + assert acosh(-I*x**2 + x - 2).as_leading_term(x, cdir=1) == log(sqrt(3) + 2) - I*pi + assert acosh(-I*x**2 + x - 2).as_leading_term(x, cdir=-1) == log(sqrt(3) + 2) - I*pi + + +def test_acosh_series(): + x = Symbol('x') + assert acosh(x).series(x, 0, 8) == \ + -I*x + pi*I/2 - I*x**3/6 - 3*I*x**5/40 - 5*I*x**7/112 + O(x**8) + t5 = acosh(x).taylor_term(5, x) + assert t5 == - 3*I*x**5/40 + assert acosh(x).taylor_term(7, x, t5, 0) == - 5*I*x**7/112 + + +def test_acosh_nseries(): + x = Symbol('x') + # Tests concerning branch points + assert acosh(x + 1)._eval_nseries(x, 4, None) == sqrt(2)*sqrt(x) - \ + sqrt(2)*x**(S(3)/2)/12 + 3*sqrt(2)*x**(S(5)/2)/160 - 5*sqrt(2)*x**(S(7)/2)/896 + O(x**4) + # Tests concerning points lying on branch cuts + assert acosh(x - 1)._eval_nseries(x, 4, None) == I*pi - \ + sqrt(2)*I*sqrt(x) - sqrt(2)*I*x**(S(3)/2)/12 - 3*sqrt(2)*I*x**(S(5)/2)/160 - \ + 5*sqrt(2)*I*x**(S(7)/2)/896 + O(x**4) + assert acosh(I*x - 2)._eval_nseries(x, 4, None, cdir=1) == acosh(-2) - \ + sqrt(3)*I*x/3 + sqrt(3)*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + assert acosh(-I*x - 2)._eval_nseries(x, 4, None, cdir=1) == acosh(-2) - \ + 2*I*pi + sqrt(3)*I*x/3 + sqrt(3)*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + assert acosh(1/(I*x - 3))._eval_nseries(x, 4, None, cdir=1) == -acosh(-S(1)/3) + \ + sqrt(2)*x/12 + 17*sqrt(2)*I*x**2/576 - 443*sqrt(2)*x**3/41472 + O(x**4) + assert acosh(1/(I*x - 3))._eval_nseries(x, 4, None, cdir=-1) == acosh(-S(1)/3) - \ + sqrt(2)*x/12 - 17*sqrt(2)*I*x**2/576 + 443*sqrt(2)*x**3/41472 + O(x**4) + # Tests concerning im(ndir) == 0 + assert acosh(-I*x**2 + x - 2)._eval_nseries(x, 4, None) == -I*pi + log(sqrt(3) + 2) + \ + x*(-2*sqrt(3) - 3)/(3*sqrt(3) + 6) + x**2*(-12 + 36*I + sqrt(3)*(-7 + 21*I))/(36*sqrt(3) + \ + 63) + x**3*(-168 + 672*I + sqrt(3)*(-97 + 388*I))/(1008*sqrt(3) + 1746) + O(x**4) + + +def test_acosh_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: acosh(x).fdiff(2)) + + +def test_asech(): + x = Symbol('x') + + assert unchanged(asech, -x) + + # values at fixed points + assert asech(1) == 0 + assert asech(-1) == pi*I + assert asech(0) is oo + assert asech(2) == I*pi/3 + assert asech(-2) == 2*I*pi / 3 + assert asech(nan) is nan + + # at infinites + assert asech(oo) == I*pi/2 + assert asech(-oo) == I*pi/2 + assert asech(zoo) == I*AccumBounds(-pi/2, pi/2) + + assert asech(I) == log(1 + sqrt(2)) - I*pi/2 + assert asech(-I) == log(1 + sqrt(2)) + I*pi/2 + assert asech(sqrt(2) - sqrt(6)) == 11*I*pi / 12 + assert asech(sqrt(2 - 2/sqrt(5))) == I*pi / 10 + assert asech(-sqrt(2 - 2/sqrt(5))) == 9*I*pi / 10 + assert asech(2 / sqrt(2 + sqrt(2))) == I*pi / 8 + assert asech(-2 / sqrt(2 + sqrt(2))) == 7*I*pi / 8 + assert asech(sqrt(5) - 1) == I*pi / 5 + assert asech(1 - sqrt(5)) == 4*I*pi / 5 + assert asech(-sqrt(2*(2 + sqrt(2)))) == 5*I*pi / 8 + + # properties + # asech(x) == acosh(1/x) + assert asech(sqrt(2)) == acosh(1/sqrt(2)) + assert asech(2/sqrt(3)) == acosh(sqrt(3)/2) + assert asech(2/sqrt(2 + sqrt(2))) == acosh(sqrt(2 + sqrt(2))/2) + assert asech(2) == acosh(S.Half) + + # reality + assert asech(S(2)).is_real is False + assert asech(-S(1) / 3).is_real is False + assert asech(S(2) / 3).is_finite is True + assert asech(S(0)).is_real is False + assert asech(S(0)).is_extended_real is True + assert asech(symbols('y', real=True)).is_real is None + + # asech(x) == I*acos(1/x) + # (Note: the exact formula is asech(x) == +/- I*acos(1/x)) + assert asech(-sqrt(2)) == I*acos(-1/sqrt(2)) + assert asech(-2/sqrt(3)) == I*acos(-sqrt(3)/2) + assert asech(-S(2)) == I*acos(Rational(-1, 2)) + assert asech(-2/sqrt(2)) == I*acos(-sqrt(2)/2) + + # sech(asech(x)) / x == 1 + assert expand_mul(sech(asech(sqrt(6) - sqrt(2))) / (sqrt(6) - sqrt(2))) == 1 + assert expand_mul(sech(asech(sqrt(6) + sqrt(2))) / (sqrt(6) + sqrt(2))) == 1 + assert (sech(asech(sqrt(2 + 2/sqrt(5)))) / (sqrt(2 + 2/sqrt(5)))).simplify() == 1 + assert (sech(asech(-sqrt(2 + 2/sqrt(5)))) / (-sqrt(2 + 2/sqrt(5)))).simplify() == 1 + assert (sech(asech(sqrt(2*(2 + sqrt(2))))) / (sqrt(2*(2 + sqrt(2))))).simplify() == 1 + assert expand_mul(sech(asech(1 + sqrt(5))) / (1 + sqrt(5))) == 1 + assert expand_mul(sech(asech(-1 - sqrt(5))) / (-1 - sqrt(5))) == 1 + assert expand_mul(sech(asech(-sqrt(6) - sqrt(2))) / (-sqrt(6) - sqrt(2))) == 1 + + # numerical evaluation + assert str(asech(5*I).n(6)) == '0.19869 - 1.5708*I' + assert str(asech(-5*I).n(6)) == '0.19869 + 1.5708*I' + + +def test_asech_leading_term(): + x = Symbol('x') + # Tests concerning branch points + assert asech(x).as_leading_term(x, cdir=1) == -log(x) + log(2) + assert asech(x).as_leading_term(x, cdir=-1) == -log(x) + log(2) + 2*I*pi + assert asech(x + 1).as_leading_term(x, cdir=1) == sqrt(2)*I*sqrt(x) + assert asech(1/x).as_leading_term(x, cdir=1) == I*pi/2 + # Tests concerning points lying on branch cuts + assert asech(x - 1).as_leading_term(x, cdir=1) == I*pi + assert asech(I*x + 3).as_leading_term(x, cdir=1) == -asech(3) + assert asech(-I*x + 3).as_leading_term(x, cdir=1) == asech(3) + assert asech(I*x - 3).as_leading_term(x, cdir=1) == -asech(-3) + assert asech(-I*x - 3).as_leading_term(x, cdir=1) == asech(-3) + assert asech(I*x - S(1)/3).as_leading_term(x, cdir=1) == -2*I*pi + asech(-S(1)/3) + assert asech(I*x - S(1)/3).as_leading_term(x, cdir=-1) == asech(-S(1)/3) + # Tests concerning im(ndir) == 0 + assert asech(-I*x**2 + x - 3).as_leading_term(x, cdir=1) == log(-S(1)/3 + 2*sqrt(2)*I/3) + assert asech(-I*x**2 + x - 3).as_leading_term(x, cdir=-1) == log(-S(1)/3 + 2*sqrt(2)*I/3) + + +def test_asech_series(): + x = Symbol('x') + assert asech(x).series(x, 0, 9, cdir=1) == log(2) - log(x) - x**2/4 - 3*x**4/32 \ + - 5*x**6/96 - 35*x**8/1024 + O(x**9) + assert asech(x).series(x, 0, 9, cdir=-1) == I*pi + log(2) - log(-x) - x**2/4 - \ + 3*x**4/32 - 5*x**6/96 - 35*x**8/1024 + O(x**9) + t6 = asech(x).taylor_term(6, x) + assert t6 == -5*x**6/96 + assert asech(x).taylor_term(8, x, t6, 0) == -35*x**8/1024 + + +def test_asech_nseries(): + x = Symbol('x') + # Tests concerning branch points + assert asech(x + 1)._eval_nseries(x, 4, None) == sqrt(2)*sqrt(-x) + 5*sqrt(2)*(-x)**(S(3)/2)/12 + \ + 43*sqrt(2)*(-x)**(S(5)/2)/160 + 177*sqrt(2)*(-x)**(S(7)/2)/896 + O(x**4) + # Tests concerning points lying on branch cuts + assert asech(x - 1)._eval_nseries(x, 4, None) == I*pi + sqrt(2)*sqrt(x) + \ + 5*sqrt(2)*x**(S(3)/2)/12 + 43*sqrt(2)*x**(S(5)/2)/160 + 177*sqrt(2)*x**(S(7)/2)/896 + O(x**4) + assert asech(I*x + 3)._eval_nseries(x, 4, None) == -asech(3) + sqrt(2)*x/12 - \ + 17*sqrt(2)*I*x**2/576 - 443*sqrt(2)*x**3/41472 + O(x**4) + assert asech(-I*x + 3)._eval_nseries(x, 4, None) == asech(3) + sqrt(2)*x/12 + \ + 17*sqrt(2)*I*x**2/576 - 443*sqrt(2)*x**3/41472 + O(x**4) + assert asech(I*x - 3)._eval_nseries(x, 4, None) == -asech(-3) - sqrt(2)*x/12 - \ + 17*sqrt(2)*I*x**2/576 + 443*sqrt(2)*x**3/41472 + O(x**4) + assert asech(-I*x - 3)._eval_nseries(x, 4, None) == asech(-3) - sqrt(2)*x/12 + \ + 17*sqrt(2)*I*x**2/576 + 443*sqrt(2)*x**3/41472 + O(x**4) + # Tests concerning im(ndir) == 0 + assert asech(-I*x**2 + x - 2)._eval_nseries(x, 3, None) == 2*I*pi/3 + \ + x*(-sqrt(3) + 3*I)/(6*sqrt(3) + 6*I) + x**2*(36 + sqrt(3)*(7 - 12*I) + 21*I)/(72*sqrt(3) - \ + 72*I) + O(x**3) + + +def test_asech_rewrite(): + x = Symbol('x') + assert asech(x).rewrite(log) == log(1/x + sqrt(1/x - 1) * sqrt(1/x + 1)) + assert asech(x).rewrite(acosh) == acosh(1/x) + assert asech(x).rewrite(asinh) == sqrt(-1 + 1/x)*(I*asinh(I/x, evaluate=False) + pi/2)/sqrt(1 - 1/x) + assert asech(x).rewrite(atanh) == \ + sqrt(x + 1)*sqrt(1/(x + 1))*atanh(sqrt(1 - x**2)) + I*pi*(-sqrt(x)*sqrt(1/x) + 1 - I*sqrt(x**2)/(2*sqrt(-x**2)) - I*sqrt(-x)/(2*sqrt(x))) + + +def test_asech_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: asech(x).fdiff(2)) + + +def test_acsch(): + x = Symbol('x') + + assert unchanged(acsch, x) + assert acsch(-x) == -acsch(x) + + # values at fixed points + assert acsch(1) == log(1 + sqrt(2)) + assert acsch(-1) == - log(1 + sqrt(2)) + assert acsch(0) is zoo + assert acsch(2) == log((1+sqrt(5))/2) + assert acsch(-2) == - log((1+sqrt(5))/2) + + assert acsch(I) == - I*pi/2 + assert acsch(-I) == I*pi/2 + assert acsch(-I*(sqrt(6) + sqrt(2))) == I*pi / 12 + assert acsch(I*(sqrt(2) + sqrt(6))) == -I*pi / 12 + assert acsch(-I*(1 + sqrt(5))) == I*pi / 10 + assert acsch(I*(1 + sqrt(5))) == -I*pi / 10 + assert acsch(-I*2 / sqrt(2 - sqrt(2))) == I*pi / 8 + assert acsch(I*2 / sqrt(2 - sqrt(2))) == -I*pi / 8 + assert acsch(-I*2) == I*pi / 6 + assert acsch(I*2) == -I*pi / 6 + assert acsch(-I*sqrt(2 + 2/sqrt(5))) == I*pi / 5 + assert acsch(I*sqrt(2 + 2/sqrt(5))) == -I*pi / 5 + assert acsch(-I*sqrt(2)) == I*pi / 4 + assert acsch(I*sqrt(2)) == -I*pi / 4 + assert acsch(-I*(sqrt(5)-1)) == 3*I*pi / 10 + assert acsch(I*(sqrt(5)-1)) == -3*I*pi / 10 + assert acsch(-I*2 / sqrt(3)) == I*pi / 3 + assert acsch(I*2 / sqrt(3)) == -I*pi / 3 + assert acsch(-I*2 / sqrt(2 + sqrt(2))) == 3*I*pi / 8 + assert acsch(I*2 / sqrt(2 + sqrt(2))) == -3*I*pi / 8 + assert acsch(-I*sqrt(2 - 2/sqrt(5))) == 2*I*pi / 5 + assert acsch(I*sqrt(2 - 2/sqrt(5))) == -2*I*pi / 5 + assert acsch(-I*(sqrt(6) - sqrt(2))) == 5*I*pi / 12 + assert acsch(I*(sqrt(6) - sqrt(2))) == -5*I*pi / 12 + assert acsch(nan) is nan + + # properties + # acsch(x) == asinh(1/x) + assert acsch(-I*sqrt(2)) == asinh(I/sqrt(2)) + assert acsch(-I*2 / sqrt(3)) == asinh(I*sqrt(3) / 2) + + # reality + assert acsch(S(2)).is_real is True + assert acsch(S(2)).is_finite is True + assert acsch(S(-2)).is_real is True + assert acsch(S(oo)).is_extended_real is True + assert acsch(-S(oo)).is_real is True + assert (acsch(2) - oo) == -oo + assert acsch(symbols('y', extended_real=True)).is_extended_real is True + + # acsch(x) == -I*asin(I/x) + assert acsch(-I*sqrt(2)) == -I*asin(-1/sqrt(2)) + assert acsch(-I*2 / sqrt(3)) == -I*asin(-sqrt(3)/2) + + # csch(acsch(x)) / x == 1 + assert expand_mul(csch(acsch(-I*(sqrt(6) + sqrt(2)))) / (-I*(sqrt(6) + sqrt(2)))) == 1 + assert expand_mul(csch(acsch(I*(1 + sqrt(5)))) / (I*(1 + sqrt(5)))) == 1 + assert (csch(acsch(I*sqrt(2 - 2/sqrt(5)))) / (I*sqrt(2 - 2/sqrt(5)))).simplify() == 1 + assert (csch(acsch(-I*sqrt(2 - 2/sqrt(5)))) / (-I*sqrt(2 - 2/sqrt(5)))).simplify() == 1 + + # numerical evaluation + assert str(acsch(5*I+1).n(6)) == '0.0391819 - 0.193363*I' + assert str(acsch(-5*I+1).n(6)) == '0.0391819 + 0.193363*I' + + +def test_acsch_infinities(): + assert acsch(oo) == 0 + assert acsch(-oo) == 0 + assert acsch(zoo) == 0 + + +def test_acsch_leading_term(): + x = Symbol('x') + assert acsch(1/x).as_leading_term(x) == x + # Tests concerning branch points + assert acsch(x + I).as_leading_term(x) == -I*pi/2 + assert acsch(x - I).as_leading_term(x) == I*pi/2 + # Tests concerning points lying on branch cuts + assert acsch(x).as_leading_term(x, cdir=1) == -log(x) + log(2) + assert acsch(x).as_leading_term(x, cdir=-1) == log(x) - log(2) - I*pi + assert acsch(x + I/2).as_leading_term(x, cdir=1) == -I*pi - acsch(I/2) + assert acsch(x + I/2).as_leading_term(x, cdir=-1) == acsch(I/2) + assert acsch(x - I/2).as_leading_term(x, cdir=1) == -acsch(I/2) + assert acsch(x - I/2).as_leading_term(x, cdir=-1) == acsch(I/2) + I*pi + # Tests concerning re(ndir) == 0 + assert acsch(I/2 + I*x - x**2).as_leading_term(x, cdir=1) == log(2 - sqrt(3)) - I*pi/2 + assert acsch(I/2 + I*x - x**2).as_leading_term(x, cdir=-1) == log(2 - sqrt(3)) - I*pi/2 + + +def test_acsch_series(): + x = Symbol('x') + assert acsch(x).series(x, 0, 9) == log(2) - log(x) + x**2/4 - 3*x**4/32 \ + + 5*x**6/96 - 35*x**8/1024 + O(x**9) + t4 = acsch(x).taylor_term(4, x) + assert t4 == -3*x**4/32 + assert acsch(x).taylor_term(6, x, t4, 0) == 5*x**6/96 + + +def test_acsch_nseries(): + x = Symbol('x') + # Tests concerning branch points + assert acsch(x + I)._eval_nseries(x, 4, None) == -I*pi/2 + \ + sqrt(2)*I*sqrt(x)*sqrt(-I) - 5*x**(S(3)/2)*(1 - I)/12 - \ + 43*sqrt(2)*I*x**(S(5)/2)*sqrt(-I)/160 + 177*x**(S(7)/2)*(1 - I)/896 + O(x**4) + assert acsch(x - I)._eval_nseries(x, 4, None) == I*pi/2 - \ + sqrt(2)*sqrt(I)*I*sqrt(x) - 5*x**(S(3)/2)*(1 + I)/12 + \ + 43*sqrt(2)*sqrt(I)*I*x**(S(5)/2)/160 + 177*x**(S(7)/2)*(1 + I)/896 + O(x**4) + # Tests concerning points lying on branch cuts + assert acsch(x + I/2)._eval_nseries(x, 4, None, cdir=1) == -acsch(I/2) - \ + I*pi + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4) + assert acsch(x + I/2)._eval_nseries(x, 4, None, cdir=-1) == acsch(I/2) - \ + 4*sqrt(3)*I*x/3 + 8*sqrt(3)*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4) + assert acsch(x - I/2)._eval_nseries(x, 4, None, cdir=1) == -acsch(I/2) - \ + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4) + assert acsch(x - I/2)._eval_nseries(x, 4, None, cdir=-1) == I*pi + \ + acsch(I/2) + 4*sqrt(3)*I*x/3 + 8*sqrt(3)*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4) + # Tests concerning re(ndir) == 0 + assert acsch(I/2 + I*x - x**2)._eval_nseries(x, 4, None) == -I*pi/2 + \ + log(2 - sqrt(3)) + x*(12 - 8*sqrt(3))/(-6 + 3*sqrt(3)) + x**2*(-96 + \ + sqrt(3)*(56 - 84*I) + 144*I)/(-63 + 36*sqrt(3)) + x**3*(2688 - 2688*I + \ + sqrt(3)*(-1552 + 1552*I))/(-873 + 504*sqrt(3)) + O(x**4) + + +def test_acsch_rewrite(): + x = Symbol('x') + assert acsch(x).rewrite(log) == log(1/x + sqrt(1/x**2 + 1)) + assert acsch(x).rewrite(asinh) == asinh(1/x) + assert acsch(x).rewrite(atanh) == (sqrt(-x**2)*(-sqrt(-(x**2 + 1)**2) + *atanh(sqrt(x**2 + 1))/(x**2 + 1) + + pi/2)/x) + + +def test_acsch_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: acsch(x).fdiff(2)) + + +def test_atanh(): + x = Symbol('x') + + # at specific points + assert atanh(0) == 0 + assert atanh(I) == I*pi/4 + assert atanh(-I) == -I*pi/4 + assert atanh(1) is oo + assert atanh(-1) is -oo + assert atanh(nan) is nan + + # at infinites + assert atanh(oo) == -I*pi/2 + assert atanh(-oo) == I*pi/2 + + assert atanh(I*oo) == I*pi/2 + assert atanh(-I*oo) == -I*pi/2 + + assert atanh(zoo) == I*AccumBounds(-pi/2, pi/2) + + # properties + assert atanh(-x) == -atanh(x) + + # reality + assert atanh(S(2)).is_real is False + assert atanh(S(-1)/5).is_real is True + assert atanh(symbols('y', extended_real=True)).is_real is None + assert atanh(S(1)).is_real is False + assert atanh(S(1)).is_extended_real is True + assert atanh(S(-1)).is_real is False + + # special values + assert atanh(I/sqrt(3)) == I*pi/6 + assert atanh(-I/sqrt(3)) == -I*pi/6 + assert atanh(I*sqrt(3)) == I*pi/3 + assert atanh(-I*sqrt(3)) == -I*pi/3 + assert atanh(I*(1 + sqrt(2))) == pi*I*Rational(3, 8) + assert atanh(I*(sqrt(2) - 1)) == pi*I/8 + assert atanh(I*(1 - sqrt(2))) == -pi*I/8 + assert atanh(-I*(1 + sqrt(2))) == pi*I*Rational(-3, 8) + assert atanh(I*sqrt(5 + 2*sqrt(5))) == I*pi*Rational(2, 5) + assert atanh(-I*sqrt(5 + 2*sqrt(5))) == I*pi*Rational(-2, 5) + assert atanh(I*(2 - sqrt(3))) == pi*I/12 + assert atanh(I*(sqrt(3) - 2)) == -pi*I/12 + assert atanh(oo) == -I*pi/2 + + # Symmetry + assert atanh(Rational(-1, 2)) == -atanh(S.Half) + + # inverse composition + assert unchanged(atanh, tanh(Symbol('v1'))) + + assert atanh(tanh(-5, evaluate=False)) == -5 + assert atanh(tanh(0, evaluate=False)) == 0 + assert atanh(tanh(7, evaluate=False)) == 7 + assert atanh(tanh(I, evaluate=False)) == I + assert atanh(tanh(-I, evaluate=False)) == -I + assert atanh(tanh(-11*I, evaluate=False)) == -11*I + 4*I*pi + assert atanh(tanh(3 + I)) == 3 + I + assert atanh(tanh(4 + 5*I)) == 4 - 2*I*pi + 5*I + assert atanh(tanh(pi/2)) == pi/2 + assert atanh(tanh(pi)) == pi + assert atanh(tanh(-3 + 7*I)) == -3 - 2*I*pi + 7*I + assert atanh(tanh(9 - I*2/3)) == 9 - I*2/3 + assert atanh(tanh(-32 - 123*I)) == -32 - 123*I + 39*I*pi + + +def test_atanh_rewrite(): + x = Symbol('x') + assert atanh(x).rewrite(log) == (log(1 + x) - log(1 - x)) / 2 + assert atanh(x).rewrite(asinh) == \ + pi*x/(2*sqrt(-x**2)) - sqrt(-x)*sqrt(1 - x**2)*sqrt(1/(x**2 - 1))*asinh(sqrt(1/(x**2 - 1)))/sqrt(x) + + +def test_atanh_leading_term(): + x = Symbol('x') + assert atanh(x).as_leading_term(x) == x + # Tests concerning branch points + assert atanh(x + 1).as_leading_term(x, cdir=1) == -log(x)/2 + log(2)/2 - I*pi/2 + assert atanh(x + 1).as_leading_term(x, cdir=-1) == -log(x)/2 + log(2)/2 + I*pi/2 + assert atanh(x - 1).as_leading_term(x, cdir=1) == log(x)/2 - log(2)/2 + assert atanh(x - 1).as_leading_term(x, cdir=-1) == log(x)/2 - log(2)/2 + assert atanh(1/x).as_leading_term(x, cdir=1) == -I*pi/2 + assert atanh(1/x).as_leading_term(x, cdir=-1) == I*pi/2 + # Tests concerning points lying on branch cuts + assert atanh(I*x + 2).as_leading_term(x, cdir=1) == atanh(2) + I*pi + assert atanh(-I*x + 2).as_leading_term(x, cdir=1) == atanh(2) + assert atanh(I*x - 2).as_leading_term(x, cdir=1) == -atanh(2) + assert atanh(-I*x - 2).as_leading_term(x, cdir=1) == -I*pi - atanh(2) + # Tests concerning im(ndir) == 0 + assert atanh(-I*x**2 + x - 2).as_leading_term(x, cdir=1) == -log(3)/2 - I*pi/2 + assert atanh(-I*x**2 + x - 2).as_leading_term(x, cdir=-1) == -log(3)/2 - I*pi/2 + + +def test_atanh_series(): + x = Symbol('x') + assert atanh(x).series(x, 0, 10) == \ + x + x**3/3 + x**5/5 + x**7/7 + x**9/9 + O(x**10) + + +def test_atanh_nseries(): + x = Symbol('x') + # Tests concerning branch points + assert atanh(x + 1)._eval_nseries(x, 4, None, cdir=1) == -I*pi/2 + log(2)/2 - \ + log(x)/2 + x/4 - x**2/16 + x**3/48 + O(x**4) + assert atanh(x + 1)._eval_nseries(x, 4, None, cdir=-1) == I*pi/2 + log(2)/2 - \ + log(x)/2 + x/4 - x**2/16 + x**3/48 + O(x**4) + assert atanh(x - 1)._eval_nseries(x, 4, None, cdir=1) == -log(2)/2 + log(x)/2 + \ + x/4 + x**2/16 + x**3/48 + O(x**4) + assert atanh(x - 1)._eval_nseries(x, 4, None, cdir=-1) == -log(2)/2 + log(x)/2 + \ + x/4 + x**2/16 + x**3/48 + O(x**4) + # Tests concerning points lying on branch cuts + assert atanh(I*x + 2)._eval_nseries(x, 4, None, cdir=1) == I*pi + atanh(2) - \ + I*x/3 - 2*x**2/9 + 13*I*x**3/81 + O(x**4) + assert atanh(I*x + 2)._eval_nseries(x, 4, None, cdir=-1) == atanh(2) - I*x/3 - \ + 2*x**2/9 + 13*I*x**3/81 + O(x**4) + assert atanh(I*x - 2)._eval_nseries(x, 4, None, cdir=1) == -atanh(2) - I*x/3 + \ + 2*x**2/9 + 13*I*x**3/81 + O(x**4) + assert atanh(I*x - 2)._eval_nseries(x, 4, None, cdir=-1) == -atanh(2) - I*pi - \ + I*x/3 + 2*x**2/9 + 13*I*x**3/81 + O(x**4) + # Tests concerning im(ndir) == 0 + assert atanh(-I*x**2 + x - 2)._eval_nseries(x, 4, None) == -I*pi/2 - log(3)/2 - x/3 + \ + x**2*(-S(1)/4 + I/2) + x**2*(S(1)/36 - I/6) + x**3*(-S(1)/6 + I/2) + x**3*(S(1)/162 - I/18) + O(x**4) + + +def test_atanh_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: atanh(x).fdiff(2)) + + +def test_acoth(): + x = Symbol('x') + + #at specific points + assert acoth(0) == I*pi/2 + assert acoth(I) == -I*pi/4 + assert acoth(-I) == I*pi/4 + assert acoth(1) is oo + assert acoth(-1) is -oo + assert acoth(nan) is nan + + # at infinites + assert acoth(oo) == 0 + assert acoth(-oo) == 0 + assert acoth(I*oo) == 0 + assert acoth(-I*oo) == 0 + assert acoth(zoo) == 0 + + #properties + assert acoth(-x) == -acoth(x) + + assert acoth(I/sqrt(3)) == -I*pi/3 + assert acoth(-I/sqrt(3)) == I*pi/3 + assert acoth(I*sqrt(3)) == -I*pi/6 + assert acoth(-I*sqrt(3)) == I*pi/6 + assert acoth(I*(1 + sqrt(2))) == -pi*I/8 + assert acoth(-I*(sqrt(2) + 1)) == pi*I/8 + assert acoth(I*(1 - sqrt(2))) == pi*I*Rational(3, 8) + assert acoth(I*(sqrt(2) - 1)) == pi*I*Rational(-3, 8) + assert acoth(I*sqrt(5 + 2*sqrt(5))) == -I*pi/10 + assert acoth(-I*sqrt(5 + 2*sqrt(5))) == I*pi/10 + assert acoth(I*(2 + sqrt(3))) == -pi*I/12 + assert acoth(-I*(2 + sqrt(3))) == pi*I/12 + assert acoth(I*(2 - sqrt(3))) == pi*I*Rational(-5, 12) + assert acoth(I*(sqrt(3) - 2)) == pi*I*Rational(5, 12) + + # reality + assert acoth(S(2)).is_real is True + assert acoth(S(2)).is_finite is True + assert acoth(S(2)).is_extended_real is True + assert acoth(S(-2)).is_real is True + assert acoth(S(1)).is_real is False + assert acoth(S(1)).is_extended_real is True + assert acoth(S(-1)).is_real is False + assert acoth(symbols('y', real=True)).is_real is None + + # Symmetry + assert acoth(Rational(-1, 2)) == -acoth(S.Half) + + +def test_acoth_rewrite(): + x = Symbol('x') + assert acoth(x).rewrite(log) == (log(1 + 1/x) - log(1 - 1/x)) / 2 + assert acoth(x).rewrite(atanh) == atanh(1/x) + assert acoth(x).rewrite(asinh) == \ + x*sqrt(x**(-2))*asinh(sqrt(1/(x**2 - 1))) + I*pi*(sqrt((x - 1)/x)*sqrt(x/(x - 1)) - sqrt(x/(x + 1))*sqrt(1 + 1/x))/2 + + +def test_acoth_leading_term(): + x = Symbol('x') + # Tests concerning branch points + assert acoth(x + 1).as_leading_term(x, cdir=1) == -log(x)/2 + log(2)/2 + assert acoth(x + 1).as_leading_term(x, cdir=-1) == -log(x)/2 + log(2)/2 + assert acoth(x - 1).as_leading_term(x, cdir=1) == log(x)/2 - log(2)/2 + I*pi/2 + assert acoth(x - 1).as_leading_term(x, cdir=-1) == log(x)/2 - log(2)/2 - I*pi/2 + # Tests concerning points lying on branch cuts + assert acoth(x).as_leading_term(x, cdir=-1) == I*pi/2 + assert acoth(x).as_leading_term(x, cdir=1) == -I*pi/2 + assert acoth(I*x + 1/2).as_leading_term(x, cdir=1) == acoth(1/2) + assert acoth(-I*x + 1/2).as_leading_term(x, cdir=1) == acoth(1/2) + I*pi + assert acoth(I*x - 1/2).as_leading_term(x, cdir=1) == -I*pi - acoth(1/2) + assert acoth(-I*x - 1/2).as_leading_term(x, cdir=1) == -acoth(1/2) + # Tests concerning im(ndir) == 0 + assert acoth(-I*x**2 - x - S(1)/2).as_leading_term(x, cdir=1) == -log(3)/2 + I*pi/2 + assert acoth(-I*x**2 - x - S(1)/2).as_leading_term(x, cdir=-1) == -log(3)/2 + I*pi/2 + + +def test_acoth_series(): + x = Symbol('x') + assert acoth(x).series(x, 0, 10) == \ + -I*pi/2 + x + x**3/3 + x**5/5 + x**7/7 + x**9/9 + O(x**10) + + +def test_acoth_nseries(): + x = Symbol('x') + # Tests concerning branch points + assert acoth(x + 1)._eval_nseries(x, 4, None) == log(2)/2 - log(x)/2 + x/4 - \ + x**2/16 + x**3/48 + O(x**4) + assert acoth(x - 1)._eval_nseries(x, 4, None, cdir=1) == I*pi/2 - log(2)/2 + \ + log(x)/2 + x/4 + x**2/16 + x**3/48 + O(x**4) + assert acoth(x - 1)._eval_nseries(x, 4, None, cdir=-1) == -I*pi/2 - log(2)/2 + \ + log(x)/2 + x/4 + x**2/16 + x**3/48 + O(x**4) + # Tests concerning points lying on branch cuts + assert acoth(I*x + S(1)/2)._eval_nseries(x, 4, None, cdir=1) == acoth(S(1)/2) + \ + 4*I*x/3 - 8*x**2/9 - 112*I*x**3/81 + O(x**4) + assert acoth(I*x + S(1)/2)._eval_nseries(x, 4, None, cdir=-1) == I*pi + \ + acoth(S(1)/2) + 4*I*x/3 - 8*x**2/9 - 112*I*x**3/81 + O(x**4) + assert acoth(I*x - S(1)/2)._eval_nseries(x, 4, None, cdir=1) == -acoth(S(1)/2) - \ + I*pi + 4*I*x/3 + 8*x**2/9 - 112*I*x**3/81 + O(x**4) + assert acoth(I*x - S(1)/2)._eval_nseries(x, 4, None, cdir=-1) == -acoth(S(1)/2) + \ + 4*I*x/3 + 8*x**2/9 - 112*I*x**3/81 + O(x**4) + # Tests concerning im(ndir) == 0 + assert acoth(-I*x**2 - x - S(1)/2)._eval_nseries(x, 4, None) == I*pi/2 - log(3)/2 - \ + 4*x/3 + x**2*(-S(8)/9 + 2*I/3) - 2*I*x**2 + x**3*(S(104)/81 - 16*I/9) - 8*x**3/3 + O(x**4) + + +def test_acoth_fdiff(): + x = Symbol('x') + raises(ArgumentIndexError, lambda: acoth(x).fdiff(2)) + + +def test_inverses(): + x = Symbol('x') + assert sinh(x).inverse() == asinh + raises(AttributeError, lambda: cosh(x).inverse()) + assert tanh(x).inverse() == atanh + assert coth(x).inverse() == acoth + assert asinh(x).inverse() == sinh + assert acosh(x).inverse() == cosh + assert atanh(x).inverse() == tanh + assert acoth(x).inverse() == coth + assert asech(x).inverse() == sech + assert acsch(x).inverse() == csch + + +def test_leading_term(): + x = Symbol('x') + assert cosh(x).as_leading_term(x) == 1 + assert coth(x).as_leading_term(x) == 1/x + for func in [sinh, tanh]: + assert func(x).as_leading_term(x) == x + for func in [sinh, cosh, tanh, coth]: + for ar in (1/x, S.Half): + eq = func(ar) + assert eq.as_leading_term(x) == eq + for func in [csch, sech]: + eq = func(S.Half) + assert eq.as_leading_term(x) == eq + + +def test_complex(): + a, b = symbols('a,b', real=True) + z = a + b*I + for func in [sinh, cosh, tanh, coth, sech, csch]: + assert func(z).conjugate() == func(a - b*I) + for deep in [True, False]: + assert sinh(z).expand( + complex=True, deep=deep) == sinh(a)*cos(b) + I*cosh(a)*sin(b) + assert cosh(z).expand( + complex=True, deep=deep) == cosh(a)*cos(b) + I*sinh(a)*sin(b) + assert tanh(z).expand(complex=True, deep=deep) == sinh(a)*cosh( + a)/(cos(b)**2 + sinh(a)**2) + I*sin(b)*cos(b)/(cos(b)**2 + sinh(a)**2) + assert coth(z).expand(complex=True, deep=deep) == sinh(a)*cosh( + a)/(sin(b)**2 + sinh(a)**2) - I*sin(b)*cos(b)/(sin(b)**2 + sinh(a)**2) + assert csch(z).expand(complex=True, deep=deep) == cos(b) * sinh(a) / (sin(b)**2\ + *cosh(a)**2 + cos(b)**2 * sinh(a)**2) - I*sin(b) * cosh(a) / (sin(b)**2\ + *cosh(a)**2 + cos(b)**2 * sinh(a)**2) + assert sech(z).expand(complex=True, deep=deep) == cos(b) * cosh(a) / (sin(b)**2\ + *sinh(a)**2 + cos(b)**2 * cosh(a)**2) - I*sin(b) * sinh(a) / (sin(b)**2\ + *sinh(a)**2 + cos(b)**2 * cosh(a)**2) + + +def test_complex_2899(): + a, b = symbols('a,b', real=True) + for deep in [True, False]: + for func in [sinh, cosh, tanh, coth]: + assert func(a).expand(complex=True, deep=deep) == func(a) + + +def test_simplifications(): + x = Symbol('x') + assert sinh(asinh(x)) == x + assert sinh(acosh(x)) == sqrt(x - 1) * sqrt(x + 1) + assert sinh(atanh(x)) == x/sqrt(1 - x**2) + assert sinh(acoth(x)) == 1/(sqrt(x - 1) * sqrt(x + 1)) + + assert cosh(asinh(x)) == sqrt(1 + x**2) + assert cosh(acosh(x)) == x + assert cosh(atanh(x)) == 1/sqrt(1 - x**2) + assert cosh(acoth(x)) == x/(sqrt(x - 1) * sqrt(x + 1)) + + assert tanh(asinh(x)) == x/sqrt(1 + x**2) + assert tanh(acosh(x)) == sqrt(x - 1) * sqrt(x + 1) / x + assert tanh(atanh(x)) == x + assert tanh(acoth(x)) == 1/x + + assert coth(asinh(x)) == sqrt(1 + x**2)/x + assert coth(acosh(x)) == x/(sqrt(x - 1) * sqrt(x + 1)) + assert coth(atanh(x)) == 1/x + assert coth(acoth(x)) == x + + assert csch(asinh(x)) == 1/x + assert csch(acosh(x)) == 1/(sqrt(x - 1) * sqrt(x + 1)) + assert csch(atanh(x)) == sqrt(1 - x**2)/x + assert csch(acoth(x)) == sqrt(x - 1) * sqrt(x + 1) + + assert sech(asinh(x)) == 1/sqrt(1 + x**2) + assert sech(acosh(x)) == 1/x + assert sech(atanh(x)) == sqrt(1 - x**2) + assert sech(acoth(x)) == sqrt(x - 1) * sqrt(x + 1)/x + + +def test_issue_4136(): + assert cosh(asinh(Integer(3)/2)) == sqrt(Integer(13)/4) + + +def test_sinh_rewrite(): + x = Symbol('x') + assert sinh(x).rewrite(exp) == (exp(x) - exp(-x))/2 \ + == sinh(x).rewrite('tractable') + assert sinh(x).rewrite(cosh) == -I*cosh(x + I*pi/2) + tanh_half = tanh(S.Half*x) + assert sinh(x).rewrite(tanh) == 2*tanh_half/(1 - tanh_half**2) + coth_half = coth(S.Half*x) + assert sinh(x).rewrite(coth) == 2*coth_half/(coth_half**2 - 1) + + +def test_cosh_rewrite(): + x = Symbol('x') + assert cosh(x).rewrite(exp) == (exp(x) + exp(-x))/2 \ + == cosh(x).rewrite('tractable') + assert cosh(x).rewrite(sinh) == -I*sinh(x + I*pi/2, evaluate=False) + tanh_half = tanh(S.Half*x)**2 + assert cosh(x).rewrite(tanh) == (1 + tanh_half)/(1 - tanh_half) + coth_half = coth(S.Half*x)**2 + assert cosh(x).rewrite(coth) == (coth_half + 1)/(coth_half - 1) + + +def test_tanh_rewrite(): + x = Symbol('x') + assert tanh(x).rewrite(exp) == (exp(x) - exp(-x))/(exp(x) + exp(-x)) \ + == tanh(x).rewrite('tractable') + assert tanh(x).rewrite(sinh) == I*sinh(x)/sinh(I*pi/2 - x, evaluate=False) + assert tanh(x).rewrite(cosh) == I*cosh(I*pi/2 - x, evaluate=False)/cosh(x) + assert tanh(x).rewrite(coth) == 1/coth(x) + + +def test_coth_rewrite(): + x = Symbol('x') + assert coth(x).rewrite(exp) == (exp(x) + exp(-x))/(exp(x) - exp(-x)) \ + == coth(x).rewrite('tractable') + assert coth(x).rewrite(sinh) == -I*sinh(I*pi/2 - x, evaluate=False)/sinh(x) + assert coth(x).rewrite(cosh) == -I*cosh(x)/cosh(I*pi/2 - x, evaluate=False) + assert coth(x).rewrite(tanh) == 1/tanh(x) + + +def test_csch_rewrite(): + x = Symbol('x') + assert csch(x).rewrite(exp) == 1 / (exp(x)/2 - exp(-x)/2) \ + == csch(x).rewrite('tractable') + assert csch(x).rewrite(cosh) == I/cosh(x + I*pi/2, evaluate=False) + tanh_half = tanh(S.Half*x) + assert csch(x).rewrite(tanh) == (1 - tanh_half**2)/(2*tanh_half) + coth_half = coth(S.Half*x) + assert csch(x).rewrite(coth) == (coth_half**2 - 1)/(2*coth_half) + + +def test_sech_rewrite(): + x = Symbol('x') + assert sech(x).rewrite(exp) == 1 / (exp(x)/2 + exp(-x)/2) \ + == sech(x).rewrite('tractable') + assert sech(x).rewrite(sinh) == I/sinh(x + I*pi/2, evaluate=False) + tanh_half = tanh(S.Half*x)**2 + assert sech(x).rewrite(tanh) == (1 - tanh_half)/(1 + tanh_half) + coth_half = coth(S.Half*x)**2 + assert sech(x).rewrite(coth) == (coth_half - 1)/(coth_half + 1) + + +def test_derivs(): + x = Symbol('x') + assert coth(x).diff(x) == -sinh(x)**(-2) + assert sinh(x).diff(x) == cosh(x) + assert cosh(x).diff(x) == sinh(x) + assert tanh(x).diff(x) == -tanh(x)**2 + 1 + assert csch(x).diff(x) == -coth(x)*csch(x) + assert sech(x).diff(x) == -tanh(x)*sech(x) + assert acoth(x).diff(x) == 1/(-x**2 + 1) + assert asinh(x).diff(x) == 1/sqrt(x**2 + 1) + assert acosh(x).diff(x) == 1/(sqrt(x - 1)*sqrt(x + 1)) + assert acosh(x).diff(x) == acosh(x).rewrite(log).diff(x).together() + assert atanh(x).diff(x) == 1/(-x**2 + 1) + assert asech(x).diff(x) == -1/(x*sqrt(1 - x**2)) + assert acsch(x).diff(x) == -1/(x**2*sqrt(1 + x**(-2))) + + +def test_sinh_expansion(): + x, y = symbols('x,y') + assert sinh(x+y).expand(trig=True) == sinh(x)*cosh(y) + cosh(x)*sinh(y) + assert sinh(2*x).expand(trig=True) == 2*sinh(x)*cosh(x) + assert sinh(3*x).expand(trig=True).expand() == \ + sinh(x)**3 + 3*sinh(x)*cosh(x)**2 + + +def test_cosh_expansion(): + x, y = symbols('x,y') + assert cosh(x+y).expand(trig=True) == cosh(x)*cosh(y) + sinh(x)*sinh(y) + assert cosh(2*x).expand(trig=True) == cosh(x)**2 + sinh(x)**2 + assert cosh(3*x).expand(trig=True).expand() == \ + 3*sinh(x)**2*cosh(x) + cosh(x)**3 + +def test_cosh_positive(): + # See issue 11721 + # cosh(x) is positive for real values of x + k = symbols('k', real=True) + n = symbols('n', integer=True) + + assert cosh(k, evaluate=False).is_positive is True + assert cosh(k + 2*n*pi*I, evaluate=False).is_positive is True + assert cosh(I*pi/4, evaluate=False).is_positive is True + assert cosh(3*I*pi/4, evaluate=False).is_positive is False + +def test_cosh_nonnegative(): + k = symbols('k', real=True) + n = symbols('n', integer=True) + + assert cosh(k, evaluate=False).is_nonnegative is True + assert cosh(k + 2*n*pi*I, evaluate=False).is_nonnegative is True + assert cosh(I*pi/4, evaluate=False).is_nonnegative is True + assert cosh(3*I*pi/4, evaluate=False).is_nonnegative is False + assert cosh(S.Zero, evaluate=False).is_nonnegative is True + +def test_real_assumptions(): + z = Symbol('z', real=False) + assert sinh(z).is_real is None + assert cosh(z).is_real is None + assert tanh(z).is_real is None + assert sech(z).is_real is None + assert csch(z).is_real is None + assert coth(z).is_real is None + +def test_sign_assumptions(): + p = Symbol('p', positive=True) + n = Symbol('n', negative=True) + assert sinh(n).is_negative is True + assert sinh(p).is_positive is True + assert cosh(n).is_positive is True + assert cosh(p).is_positive is True + assert tanh(n).is_negative is True + assert tanh(p).is_positive is True + assert csch(n).is_negative is True + assert csch(p).is_positive is True + assert sech(n).is_positive is True + assert sech(p).is_positive is True + assert coth(n).is_negative is True + assert coth(p).is_positive is True + + +def test_issue_25847(): + x = Symbol('x') + + #atanh + assert atanh(sin(x)/x).as_leading_term(x) == atanh(sin(x)/x) + raises(PoleError, lambda: atanh(exp(1/x)).as_leading_term(x)) + + #asinh + assert asinh(sin(x)/x).as_leading_term(x) == log(1 + sqrt(2)) + raises(PoleError, lambda: asinh(exp(1/x)).as_leading_term(x)) + + #acosh + assert acosh(sin(x)/x).as_leading_term(x) == 0 + raises(PoleError, lambda: acosh(exp(1/x)).as_leading_term(x)) + + #acoth + assert acoth(sin(x)/x).as_leading_term(x) == acoth(sin(x)/x) + raises(PoleError, lambda: acoth(exp(1/x)).as_leading_term(x)) + + #asech + assert asech(sinh(x)/x).as_leading_term(x) == 0 + raises(PoleError, lambda: asech(exp(1/x)).as_leading_term(x)) + + #acsch + assert acsch(sin(x)/x).as_leading_term(x) == log(1 + sqrt(2)) + raises(PoleError, lambda: acsch(exp(1/x)).as_leading_term(x)) + + +def test_issue_25175(): + x = Symbol('x') + g1 = 2*acosh(1 + 2*x/3) - acosh(S(5)/3 - S(8)/3/(x + 4)) + g2 = 2*log(sqrt((x + 4)/3)*(sqrt(x + 3)+sqrt(x))**2/(2*sqrt(x + 3) + sqrt(x))) + assert (g1 - g2).series(x) == O(x**6) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_integers.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_integers.py new file mode 100644 index 0000000000000000000000000000000000000000..a48ad2ac24c4a857d57b2f24e3308ac90078a9b1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_integers.py @@ -0,0 +1,688 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core.numbers import (E, Float, I, Rational, Integer, nan, oo, pi, zoo) +from sympy.core.relational import (Eq, Ge, Gt, Le, Lt, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.integers import (ceiling, floor, frac) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin, cos, tan, asin +from sympy.polys.rootoftools import RootOf, CRootOf +from sympy import Integers +from sympy.sets.sets import Interval +from sympy.sets.fancysets import ImageSet +from sympy.core.function import Lambda + +from sympy.core.expr import unchanged +from sympy.testing.pytest import XFAIL, raises + +x = Symbol('x') +i = Symbol('i', imaginary=True) +y = Symbol('y', real=True) +k, n = symbols('k,n', integer=True) +b = Symbol('b', real=True, noninteger=True) +m = Symbol('m', positive=True) + + +def test_floor(): + + assert floor(nan) is nan + + assert floor(oo) is oo + assert floor(-oo) is -oo + assert floor(zoo) is zoo + + assert floor(0) == 0 + + assert floor(1) == 1 + assert floor(-1) == -1 + + assert floor(I*log(asin(5)/abs(asin(5)))) == 0 + assert floor(-I*log(asin(7)/abs(asin(7)))) == -2 + + assert floor(E) == 2 + assert floor(-E) == -3 + + assert floor(2*E) == 5 + assert floor(-2*E) == -6 + + assert floor(pi) == 3 + assert floor(-pi) == -4 + + assert floor(S.Half) == 0 + assert floor(Rational(-1, 2)) == -1 + + assert floor(Rational(7, 3)) == 2 + assert floor(Rational(-7, 3)) == -3 + assert floor(-Rational(7, 3)) == -3 + + assert floor(Float(17.0)) == 17 + assert floor(-Float(17.0)) == -17 + + assert floor(Float(7.69)) == 7 + assert floor(-Float(7.69)) == -8 + + assert floor(1/(m+1)) == S.Zero + assert floor((m+2)/(m+1)) == S.One + assert floor(-1/(m+1)) == S.NegativeOne + assert floor((m+2)/(-m-1)) == Integer(-2) + + assert floor(I) == I + assert floor(-I) == -I + e = floor(i) + assert e.func is floor and e.args[0] == i + + assert floor(oo*I) == oo*I + assert floor(-oo*I) == -oo*I + assert floor(exp(I*pi/4)*oo) == exp(I*pi/4)*oo + + assert floor(2*I) == 2*I + assert floor(-2*I) == -2*I + + assert floor(I/2) == 0 + assert floor(-I/2) == -I + + assert floor(E + 17) == 19 + assert floor(pi + 2) == 5 + + assert floor(E + pi) == 5 + assert floor(I + pi) == 3 + I + + assert floor(floor(pi)) == 3 + assert floor(floor(y)) == floor(y) + assert floor(floor(x)) == floor(x) + + assert unchanged(floor, x) + assert unchanged(floor, 2*x) + assert unchanged(floor, k*x) + + assert floor(k) == k + assert floor(2*k) == 2*k + assert floor(k*n) == k*n + + assert unchanged(floor, k/2) + + assert unchanged(floor, x + y) + + assert floor(x + 3) == floor(x) + 3 + assert floor(x + k) == floor(x) + k + + assert floor(y + 3) == floor(y) + 3 + assert floor(y + k) == floor(y) + k + + assert floor(3 + I*y + pi) == 6 + floor(y)*I + + assert floor(k + n) == k + n + + assert unchanged(floor, x*I) + assert floor(k*I) == k*I + + assert floor(Rational(23, 10) - E*I) == 2 - 3*I + + assert floor(sin(1)) == 0 + assert floor(sin(-1)) == -1 + + assert floor(exp(2)) == 7 + + assert floor(log(8)/log(2)) != 2 + assert int(floor(log(8)/log(2)).evalf(chop=True)) == 3 + + assert floor(factorial(50)/exp(1)) == \ + 11188719610782480504630258070757734324011354208865721592720336800 + + assert (floor(y) < y).is_Relational + assert (floor(y) <= y) == True + assert (floor(y) > y) == False + assert (floor(y) >= y).is_Relational + assert (floor(x) <= x).is_Relational # x could be non-real + assert (floor(x) > x).is_Relational + assert (floor(x) <= y).is_Relational # arg is not same as rhs + assert (floor(x) > y).is_Relational + assert (floor(y) <= oo) == True + assert (floor(y) < oo) == True + assert (floor(y) >= -oo) == True + assert (floor(y) > -oo) == True + assert (floor(b) < b) == True + assert (floor(b) <= b) == True + assert (floor(b) > b) == False + assert (floor(b) >= b) == False + + assert floor(y).rewrite(frac) == y - frac(y) + assert floor(y).rewrite(ceiling) == -ceiling(-y) + assert floor(y).rewrite(frac).subs(y, -pi) == floor(-pi) + assert floor(y).rewrite(frac).subs(y, E) == floor(E) + assert floor(y).rewrite(ceiling).subs(y, E) == -ceiling(-E) + assert floor(y).rewrite(ceiling).subs(y, -pi) == -ceiling(pi) + + assert Eq(floor(y), y - frac(y)) + assert Eq(floor(y), -ceiling(-y)) + + neg = Symbol('neg', negative=True) + nn = Symbol('nn', nonnegative=True) + pos = Symbol('pos', positive=True) + np = Symbol('np', nonpositive=True) + + assert (floor(neg) < 0) == True + assert (floor(neg) <= 0) == True + assert (floor(neg) > 0) == False + assert (floor(neg) >= 0) == False + assert (floor(neg) <= -1) == True + assert (floor(neg) >= -3) == (neg >= -3) + assert (floor(neg) < 5) == (neg < 5) + + assert (floor(nn) < 0) == False + assert (floor(nn) >= 0) == True + + assert (floor(pos) < 0) == False + assert (floor(pos) <= 0) == (pos < 1) + assert (floor(pos) > 0) == (pos >= 1) + assert (floor(pos) >= 0) == True + assert (floor(pos) >= 3) == (pos >= 3) + + assert (floor(np) <= 0) == True + assert (floor(np) > 0) == False + + assert floor(neg).is_negative == True + assert floor(neg).is_nonnegative == False + assert floor(nn).is_negative == False + assert floor(nn).is_nonnegative == True + assert floor(pos).is_negative == False + assert floor(pos).is_nonnegative == True + assert floor(np).is_negative is None + assert floor(np).is_nonnegative is None + + assert (floor(7, evaluate=False) >= 7) == True + assert (floor(7, evaluate=False) > 7) == False + assert (floor(7, evaluate=False) <= 7) == True + assert (floor(7, evaluate=False) < 7) == False + + assert (floor(7, evaluate=False) >= 6) == True + assert (floor(7, evaluate=False) > 6) == True + assert (floor(7, evaluate=False) <= 6) == False + assert (floor(7, evaluate=False) < 6) == False + + assert (floor(7, evaluate=False) >= 8) == False + assert (floor(7, evaluate=False) > 8) == False + assert (floor(7, evaluate=False) <= 8) == True + assert (floor(7, evaluate=False) < 8) == True + + assert (floor(x) <= 5.5) == Le(floor(x), 5.5, evaluate=False) + assert (floor(x) >= -3.2) == Ge(floor(x), -3.2, evaluate=False) + assert (floor(x) < 2.9) == Lt(floor(x), 2.9, evaluate=False) + assert (floor(x) > -1.7) == Gt(floor(x), -1.7, evaluate=False) + + assert (floor(y) <= 5.5) == (y < 6) + assert (floor(y) >= -3.2) == (y >= -3) + assert (floor(y) < 2.9) == (y < 3) + assert (floor(y) > -1.7) == (y >= -1) + + assert (floor(y) <= n) == (y < n + 1) + assert (floor(y) >= n) == (y >= n) + assert (floor(y) < n) == (y < n) + assert (floor(y) > n) == (y >= n + 1) + + assert floor(RootOf(x**3 - 27*x, 2)) == 5 + + +def test_ceiling(): + + assert ceiling(nan) is nan + + assert ceiling(oo) is oo + assert ceiling(-oo) is -oo + assert ceiling(zoo) is zoo + + assert ceiling(0) == 0 + + assert ceiling(1) == 1 + assert ceiling(-1) == -1 + + assert ceiling(I*log(asin(5)/abs(asin(5)))) == 1 + assert ceiling(-I*log(asin(7)/abs(asin(7)))) == -1 + + assert ceiling(E) == 3 + assert ceiling(-E) == -2 + + assert ceiling(2*E) == 6 + assert ceiling(-2*E) == -5 + + assert ceiling(pi) == 4 + assert ceiling(-pi) == -3 + + assert ceiling(S.Half) == 1 + assert ceiling(Rational(-1, 2)) == 0 + + assert ceiling(Rational(7, 3)) == 3 + assert ceiling(-Rational(7, 3)) == -2 + + assert ceiling(Float(17.0)) == 17 + assert ceiling(-Float(17.0)) == -17 + + assert ceiling(Float(7.69)) == 8 + assert ceiling(-Float(7.69)) == -7 + + assert ceiling(1/(m+1)) == S.One + assert ceiling((m+2)/(m+1)) == Integer(2) + assert ceiling(-1/(m+1)) == S.Zero + assert ceiling((m+2)/(-m-1)) == S.NegativeOne + + assert ceiling(I) == I + assert ceiling(-I) == -I + e = ceiling(i) + assert e.func is ceiling and e.args[0] == i + + assert ceiling(oo*I) == oo*I + assert ceiling(-oo*I) == -oo*I + assert ceiling(exp(I*pi/4)*oo) == exp(I*pi/4)*oo + + assert ceiling(2*I) == 2*I + assert ceiling(-2*I) == -2*I + + assert ceiling(I/2) == I + assert ceiling(-I/2) == 0 + + assert ceiling(E + 17) == 20 + assert ceiling(pi + 2) == 6 + + assert ceiling(E + pi) == 6 + assert ceiling(I + pi) == I + 4 + + assert ceiling(ceiling(pi)) == 4 + assert ceiling(ceiling(y)) == ceiling(y) + assert ceiling(ceiling(x)) == ceiling(x) + + assert unchanged(ceiling, x) + assert unchanged(ceiling, 2*x) + assert unchanged(ceiling, k*x) + + assert ceiling(k) == k + assert ceiling(2*k) == 2*k + assert ceiling(k*n) == k*n + + assert unchanged(ceiling, k/2) + + assert unchanged(ceiling, x + y) + + assert ceiling(x + 3) == ceiling(x) + 3 + assert ceiling(x + 3.0) == ceiling(x) + 3 + assert ceiling(x + 3.0*I) == ceiling(x) + 3*I + assert ceiling(x + k) == ceiling(x) + k + + assert ceiling(y + 3) == ceiling(y) + 3 + assert ceiling(y + k) == ceiling(y) + k + + assert ceiling(3 + pi + y*I) == 7 + ceiling(y)*I + + assert ceiling(k + n) == k + n + + assert unchanged(ceiling, x*I) + assert ceiling(k*I) == k*I + + assert ceiling(Rational(23, 10) - E*I) == 3 - 2*I + + assert ceiling(sin(1)) == 1 + assert ceiling(sin(-1)) == 0 + + assert ceiling(exp(2)) == 8 + + assert ceiling(-log(8)/log(2)) != -2 + assert int(ceiling(-log(8)/log(2)).evalf(chop=True)) == -3 + + assert ceiling(factorial(50)/exp(1)) == \ + 11188719610782480504630258070757734324011354208865721592720336801 + + assert (ceiling(y) >= y) == True + assert (ceiling(y) > y).is_Relational + assert (ceiling(y) < y) == False + assert (ceiling(y) <= y).is_Relational + assert (ceiling(x) >= x).is_Relational # x could be non-real + assert (ceiling(x) < x).is_Relational + assert (ceiling(x) >= y).is_Relational # arg is not same as rhs + assert (ceiling(x) < y).is_Relational + assert (ceiling(y) >= -oo) == True + assert (ceiling(y) > -oo) == True + assert (ceiling(y) <= oo) == True + assert (ceiling(y) < oo) == True + assert (ceiling(b) < b) == False + assert (ceiling(b) <= b) == False + assert (ceiling(b) > b) == True + assert (ceiling(b) >= b) == True + + assert ceiling(y).rewrite(floor) == -floor(-y) + assert ceiling(y).rewrite(frac) == y + frac(-y) + assert ceiling(y).rewrite(floor).subs(y, -pi) == -floor(pi) + assert ceiling(y).rewrite(floor).subs(y, E) == -floor(-E) + assert ceiling(y).rewrite(frac).subs(y, pi) == ceiling(pi) + assert ceiling(y).rewrite(frac).subs(y, -E) == ceiling(-E) + + assert Eq(ceiling(y), y + frac(-y)) + assert Eq(ceiling(y), -floor(-y)) + + neg = Symbol('neg', negative=True) + nn = Symbol('nn', nonnegative=True) + pos = Symbol('pos', positive=True) + np = Symbol('np', nonpositive=True) + + assert (ceiling(neg) <= 0) == True + assert (ceiling(neg) < 0) == (neg <= -1) + assert (ceiling(neg) > 0) == False + assert (ceiling(neg) >= 0) == (neg > -1) + assert (ceiling(neg) > -3) == (neg > -3) + assert (ceiling(neg) <= 10) == (neg <= 10) + + assert (ceiling(nn) < 0) == False + assert (ceiling(nn) >= 0) == True + + assert (ceiling(pos) < 0) == False + assert (ceiling(pos) <= 0) == False + assert (ceiling(pos) > 0) == True + assert (ceiling(pos) >= 0) == True + assert (ceiling(pos) >= 1) == True + assert (ceiling(pos) > 5) == (pos > 5) + + assert (ceiling(np) <= 0) == True + assert (ceiling(np) > 0) == False + + assert ceiling(neg).is_positive == False + assert ceiling(neg).is_nonpositive == True + assert ceiling(nn).is_positive is None + assert ceiling(nn).is_nonpositive is None + assert ceiling(pos).is_positive == True + assert ceiling(pos).is_nonpositive == False + assert ceiling(np).is_positive == False + assert ceiling(np).is_nonpositive == True + + assert (ceiling(7, evaluate=False) >= 7) == True + assert (ceiling(7, evaluate=False) > 7) == False + assert (ceiling(7, evaluate=False) <= 7) == True + assert (ceiling(7, evaluate=False) < 7) == False + + assert (ceiling(7, evaluate=False) >= 6) == True + assert (ceiling(7, evaluate=False) > 6) == True + assert (ceiling(7, evaluate=False) <= 6) == False + assert (ceiling(7, evaluate=False) < 6) == False + + assert (ceiling(7, evaluate=False) >= 8) == False + assert (ceiling(7, evaluate=False) > 8) == False + assert (ceiling(7, evaluate=False) <= 8) == True + assert (ceiling(7, evaluate=False) < 8) == True + + assert (ceiling(x) <= 5.5) == Le(ceiling(x), 5.5, evaluate=False) + assert (ceiling(x) >= -3.2) == Ge(ceiling(x), -3.2, evaluate=False) + assert (ceiling(x) < 2.9) == Lt(ceiling(x), 2.9, evaluate=False) + assert (ceiling(x) > -1.7) == Gt(ceiling(x), -1.7, evaluate=False) + + assert (ceiling(y) <= 5.5) == (y <= 5) + assert (ceiling(y) >= -3.2) == (y > -4) + assert (ceiling(y) < 2.9) == (y <= 2) + assert (ceiling(y) > -1.7) == (y > -2) + + assert (ceiling(y) <= n) == (y <= n) + assert (ceiling(y) >= n) == (y > n - 1) + assert (ceiling(y) < n) == (y <= n - 1) + assert (ceiling(y) > n) == (y > n) + + assert ceiling(RootOf(x**3 - 27*x, 2)) == 6 + s = ImageSet(Lambda(n, n + (CRootOf(x**5 - x**2 + 1, 0))), Integers) + f = CRootOf(x**5 - x**2 + 1, 0) + s = ImageSet(Lambda(n, n + f), Integers) + assert s.intersect(Interval(-10, 10)) == {i + f for i in range(-9, 11)} + + +def test_frac(): + assert isinstance(frac(x), frac) + assert frac(oo) == AccumBounds(0, 1) + assert frac(-oo) == AccumBounds(0, 1) + assert frac(zoo) is nan + + assert frac(n) == 0 + assert frac(nan) is nan + assert frac(Rational(4, 3)) == Rational(1, 3) + assert frac(-Rational(4, 3)) == Rational(2, 3) + assert frac(Rational(-4, 3)) == Rational(2, 3) + + r = Symbol('r', real=True) + assert frac(I*r) == I*frac(r) + assert frac(1 + I*r) == I*frac(r) + assert frac(0.5 + I*r) == 0.5 + I*frac(r) + assert frac(n + I*r) == I*frac(r) + assert frac(n + I*k) == 0 + assert unchanged(frac, x + I*x) + assert frac(x + I*n) == frac(x) + + assert frac(x).rewrite(floor) == x - floor(x) + assert frac(x).rewrite(ceiling) == x + ceiling(-x) + assert frac(y).rewrite(floor).subs(y, pi) == frac(pi) + assert frac(y).rewrite(floor).subs(y, -E) == frac(-E) + assert frac(y).rewrite(ceiling).subs(y, -pi) == frac(-pi) + assert frac(y).rewrite(ceiling).subs(y, E) == frac(E) + + assert Eq(frac(y), y - floor(y)) + assert Eq(frac(y), y + ceiling(-y)) + + r = Symbol('r', real=True) + p_i = Symbol('p_i', integer=True, positive=True) + n_i = Symbol('p_i', integer=True, negative=True) + np_i = Symbol('np_i', integer=True, nonpositive=True) + nn_i = Symbol('nn_i', integer=True, nonnegative=True) + p_r = Symbol('p_r', positive=True) + n_r = Symbol('n_r', negative=True) + np_r = Symbol('np_r', real=True, nonpositive=True) + nn_r = Symbol('nn_r', real=True, nonnegative=True) + + # Real frac argument, integer rhs + assert frac(r) <= p_i + assert not frac(r) <= n_i + assert (frac(r) <= np_i).has(Le) + assert (frac(r) <= nn_i).has(Le) + assert frac(r) < p_i + assert not frac(r) < n_i + assert not frac(r) < np_i + assert (frac(r) < nn_i).has(Lt) + assert not frac(r) >= p_i + assert frac(r) >= n_i + assert frac(r) >= np_i + assert (frac(r) >= nn_i).has(Ge) + assert not frac(r) > p_i + assert frac(r) > n_i + assert (frac(r) > np_i).has(Gt) + assert (frac(r) > nn_i).has(Gt) + + assert not Eq(frac(r), p_i) + assert not Eq(frac(r), n_i) + assert Eq(frac(r), np_i).has(Eq) + assert Eq(frac(r), nn_i).has(Eq) + + assert Ne(frac(r), p_i) + assert Ne(frac(r), n_i) + assert Ne(frac(r), np_i).has(Ne) + assert Ne(frac(r), nn_i).has(Ne) + + + # Real frac argument, real rhs + assert (frac(r) <= p_r).has(Le) + assert not frac(r) <= n_r + assert (frac(r) <= np_r).has(Le) + assert (frac(r) <= nn_r).has(Le) + assert (frac(r) < p_r).has(Lt) + assert not frac(r) < n_r + assert not frac(r) < np_r + assert (frac(r) < nn_r).has(Lt) + assert (frac(r) >= p_r).has(Ge) + assert frac(r) >= n_r + assert frac(r) >= np_r + assert (frac(r) >= nn_r).has(Ge) + assert (frac(r) > p_r).has(Gt) + assert frac(r) > n_r + assert (frac(r) > np_r).has(Gt) + assert (frac(r) > nn_r).has(Gt) + + assert not Eq(frac(r), n_r) + assert Eq(frac(r), p_r).has(Eq) + assert Eq(frac(r), np_r).has(Eq) + assert Eq(frac(r), nn_r).has(Eq) + + assert Ne(frac(r), p_r).has(Ne) + assert Ne(frac(r), n_r) + assert Ne(frac(r), np_r).has(Ne) + assert Ne(frac(r), nn_r).has(Ne) + + # Real frac argument, +/- oo rhs + assert frac(r) < oo + assert frac(r) <= oo + assert not frac(r) > oo + assert not frac(r) >= oo + + assert not frac(r) < -oo + assert not frac(r) <= -oo + assert frac(r) > -oo + assert frac(r) >= -oo + + assert frac(r) < 1 + assert frac(r) <= 1 + assert not frac(r) > 1 + assert not frac(r) >= 1 + + assert not frac(r) < 0 + assert (frac(r) <= 0).has(Le) + assert (frac(r) > 0).has(Gt) + assert frac(r) >= 0 + + # Some test for numbers + assert frac(r) <= sqrt(2) + assert (frac(r) <= sqrt(3) - sqrt(2)).has(Le) + assert not frac(r) <= sqrt(2) - sqrt(3) + assert not frac(r) >= sqrt(2) + assert (frac(r) >= sqrt(3) - sqrt(2)).has(Ge) + assert frac(r) >= sqrt(2) - sqrt(3) + + assert not Eq(frac(r), sqrt(2)) + assert Eq(frac(r), sqrt(3) - sqrt(2)).has(Eq) + assert not Eq(frac(r), sqrt(2) - sqrt(3)) + assert Ne(frac(r), sqrt(2)) + assert Ne(frac(r), sqrt(3) - sqrt(2)).has(Ne) + assert Ne(frac(r), sqrt(2) - sqrt(3)) + + assert frac(p_i, evaluate=False).is_zero + assert frac(p_i, evaluate=False).is_finite + assert frac(p_i, evaluate=False).is_integer + assert frac(p_i, evaluate=False).is_real + assert frac(r).is_finite + assert frac(r).is_real + assert frac(r).is_zero is None + assert frac(r).is_integer is None + + assert frac(oo).is_finite + assert frac(oo).is_real + + +def test_series(): + x, y = symbols('x,y') + assert floor(x).nseries(x, y, 100) == floor(y) + assert ceiling(x).nseries(x, y, 100) == ceiling(y) + assert floor(x).nseries(x, pi, 100) == 3 + assert ceiling(x).nseries(x, pi, 100) == 4 + assert floor(x).nseries(x, 0, 100) == 0 + assert ceiling(x).nseries(x, 0, 100) == 1 + assert floor(-x).nseries(x, 0, 100) == -1 + assert ceiling(-x).nseries(x, 0, 100) == 0 + + +def test_issue_14355(): + # This test checks the leading term and series for the floor and ceil + # function when arg0 evaluates to S.NaN. + assert floor((x**3 + x)/(x**2 - x)).as_leading_term(x, cdir = 1) == -2 + assert floor((x**3 + x)/(x**2 - x)).as_leading_term(x, cdir = -1) == -1 + assert floor((cos(x) - 1)/x).as_leading_term(x, cdir = 1) == -1 + assert floor((cos(x) - 1)/x).as_leading_term(x, cdir = -1) == 0 + assert floor(sin(x)/x).as_leading_term(x, cdir = 1) == 0 + assert floor(sin(x)/x).as_leading_term(x, cdir = -1) == 0 + assert floor(-tan(x)/x).as_leading_term(x, cdir = 1) == -2 + assert floor(-tan(x)/x).as_leading_term(x, cdir = -1) == -2 + assert floor(sin(x)/x/3).as_leading_term(x, cdir = 1) == 0 + assert floor(sin(x)/x/3).as_leading_term(x, cdir = -1) == 0 + assert ceiling((x**3 + x)/(x**2 - x)).as_leading_term(x, cdir = 1) == -1 + assert ceiling((x**3 + x)/(x**2 - x)).as_leading_term(x, cdir = -1) == 0 + assert ceiling((cos(x) - 1)/x).as_leading_term(x, cdir = 1) == 0 + assert ceiling((cos(x) - 1)/x).as_leading_term(x, cdir = -1) == 1 + assert ceiling(sin(x)/x).as_leading_term(x, cdir = 1) == 1 + assert ceiling(sin(x)/x).as_leading_term(x, cdir = -1) == 1 + assert ceiling(-tan(x)/x).as_leading_term(x, cdir = 1) == -1 + assert ceiling(-tan(x)/x).as_leading_term(x, cdir = 1) == -1 + assert ceiling(sin(x)/x/3).as_leading_term(x, cdir = 1) == 1 + assert ceiling(sin(x)/x/3).as_leading_term(x, cdir = -1) == 1 + # test for series + assert floor(sin(x)/x).series(x, 0, 100, cdir = 1) == 0 + assert floor(sin(x)/x).series(x, 0, 100, cdir = 1) == 0 + assert floor((x**3 + x)/(x**2 - x)).series(x, 0, 100, cdir = 1) == -2 + assert floor((x**3 + x)/(x**2 - x)).series(x, 0, 100, cdir = -1) == -1 + assert ceiling(sin(x)/x).series(x, 0, 100, cdir = 1) == 1 + assert ceiling(sin(x)/x).series(x, 0, 100, cdir = -1) == 1 + assert ceiling((x**3 + x)/(x**2 - x)).series(x, 0, 100, cdir = 1) == -1 + assert ceiling((x**3 + x)/(x**2 - x)).series(x, 0, 100, cdir = -1) == 0 + + +def test_frac_leading_term(): + assert frac(x).as_leading_term(x) == x + assert frac(x).as_leading_term(x, cdir = 1) == x + assert frac(x).as_leading_term(x, cdir = -1) == 1 + assert frac(x + S.Half).as_leading_term(x, cdir = 1) == S.Half + assert frac(x + S.Half).as_leading_term(x, cdir = -1) == S.Half + assert frac(-2*x + 1).as_leading_term(x, cdir = 1) == S.One + assert frac(-2*x + 1).as_leading_term(x, cdir = -1) == -2*x + assert frac(sin(x) + 5).as_leading_term(x, cdir = 1) == x + assert frac(sin(x) + 5).as_leading_term(x, cdir = -1) == S.One + assert frac(sin(x**2) + 5).as_leading_term(x, cdir = 1) == x**2 + assert frac(sin(x**2) + 5).as_leading_term(x, cdir = -1) == x**2 + + +@XFAIL +def test_issue_4149(): + assert floor(3 + pi*I + y*I) == 3 + floor(pi + y)*I + assert floor(3*I + pi*I + y*I) == floor(3 + pi + y)*I + assert floor(3 + E + pi*I + y*I) == 5 + floor(pi + y)*I + + +def test_issue_21651(): + k = Symbol('k', positive=True, integer=True) + exp = 2*2**(-k) + assert isinstance(floor(exp), floor) + + +def test_issue_11207(): + assert floor(floor(x)) == floor(x) + assert floor(ceiling(x)) == ceiling(x) + assert ceiling(floor(x)) == floor(x) + assert ceiling(ceiling(x)) == ceiling(x) + + +def test_nested_floor_ceiling(): + assert floor(-floor(ceiling(x**3)/y)) == -floor(ceiling(x**3)/y) + assert ceiling(-floor(ceiling(x**3)/y)) == -floor(ceiling(x**3)/y) + assert floor(ceiling(-floor(x**Rational(7, 2)/y))) == -floor(x**Rational(7, 2)/y) + assert -ceiling(-ceiling(floor(x)/y)) == ceiling(floor(x)/y) + +def test_issue_18689(): + assert floor(floor(floor(x)) + 3) == floor(x) + 3 + assert ceiling(ceiling(ceiling(x)) + 1) == ceiling(x) + 1 + assert ceiling(ceiling(floor(x)) + 3) == floor(x) + 3 + +def test_issue_18421(): + assert floor(float(0)) is S.Zero + assert ceiling(float(0)) is S.Zero + +def test_issue_25230(): + a = Symbol('a', real = True) + b = Symbol('b', positive = True) + c = Symbol('c', negative = True) + raises(NotImplementedError, lambda: floor(x/a).as_leading_term(x, cdir = 1)) + raises(NotImplementedError, lambda: ceiling(x/a).as_leading_term(x, cdir = 1)) + assert floor(x/b).as_leading_term(x, cdir = 1) == 0 + assert floor(x/b).as_leading_term(x, cdir = -1) == -1 + assert floor(x/c).as_leading_term(x, cdir = 1) == -1 + assert floor(x/c).as_leading_term(x, cdir = -1) == 0 + assert ceiling(x/b).as_leading_term(x, cdir = 1) == 1 + assert ceiling(x/b).as_leading_term(x, cdir = -1) == 0 + assert ceiling(x/c).as_leading_term(x, cdir = 1) == 0 + assert ceiling(x/c).as_leading_term(x, cdir = -1) == 1 diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_interface.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..6ae2f78b50bea24c64079066076971e315660d69 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_interface.py @@ -0,0 +1,82 @@ +# This test file tests the SymPy function interface, that people use to create +# their own new functions. It should be as easy as possible. +# +# We test that it works with both Function and DefinedFunction. New code should +# use DefinedFunction because it has better type inference. Old code still +# using Function should continue to work though. +from sympy.core.function import Function, DefinedFunction +from sympy.core.sympify import sympify +from sympy.functions.elementary.hyperbolic import tanh +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.series.limits import limit +from sympy.abc import x + + +def test_function_series1(): + """Create our new "sin" function.""" + + for F in [Function, DefinedFunction]: + + class my_function(F): + + def fdiff(self, argindex=1): + return cos(self.args[0]) + + @classmethod + def eval(cls, arg): + arg = sympify(arg) + if arg == 0: + return sympify(0) + + #Test that the taylor series is correct + assert my_function(x).series(x, 0, 10) == sin(x).series(x, 0, 10) + assert limit(my_function(x)/x, x, 0) == 1 + + +def test_function_series2(): + """Create our new "cos" function.""" + + for F in [Function, DefinedFunction]: + + class my_function2(F): + + def fdiff(self, argindex=1): + return -sin(self.args[0]) + + @classmethod + def eval(cls, arg): + arg = sympify(arg) + if arg == 0: + return sympify(1) + + #Test that the taylor series is correct + assert my_function2(x).series(x, 0, 10) == cos(x).series(x, 0, 10) + + +def test_function_series3(): + """ + Test our easy "tanh" function. + + This test tests two things: + * that the Function interface works as expected and it's easy to use + * that the general algorithm for the series expansion works even when the + derivative is defined recursively in terms of the original function, + since tanh(x).diff(x) == 1-tanh(x)**2 + """ + + for F in [Function, DefinedFunction]: + + class mytanh(F): + + def fdiff(self, argindex=1): + return 1 - mytanh(self.args[0])**2 + + @classmethod + def eval(cls, arg): + arg = sympify(arg) + if arg == 0: + return sympify(0) + + e = tanh(x) + f = mytanh(x) + assert e.series(x, 0, 6) == f.series(x, 0, 6) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_miscellaneous.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_miscellaneous.py new file mode 100644 index 0000000000000000000000000000000000000000..374c4fb50eaae54a9884015c124c245385e1761e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_miscellaneous.py @@ -0,0 +1,504 @@ +import itertools as it + +from sympy.core.expr import unchanged +from sympy.core.function import Function +from sympy.core.numbers import I, oo, Rational +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.external import import_module +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.integers import floor, ceiling +from sympy.functions.elementary.miscellaneous import (sqrt, cbrt, root, Min, + Max, real_root, Rem) +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.functions.special.delta_functions import Heaviside + +from sympy.utilities.lambdify import lambdify +from sympy.testing.pytest import raises, skip, ignore_warnings + +def test_Min(): + from sympy.abc import x, y, z + n = Symbol('n', negative=True) + n_ = Symbol('n_', negative=True) + nn = Symbol('nn', nonnegative=True) + nn_ = Symbol('nn_', nonnegative=True) + p = Symbol('p', positive=True) + p_ = Symbol('p_', positive=True) + np = Symbol('np', nonpositive=True) + np_ = Symbol('np_', nonpositive=True) + r = Symbol('r', real=True) + + assert Min(5, 4) == 4 + assert Min(-oo, -oo) is -oo + assert Min(-oo, n) is -oo + assert Min(n, -oo) is -oo + assert Min(-oo, np) is -oo + assert Min(np, -oo) is -oo + assert Min(-oo, 0) is -oo + assert Min(0, -oo) is -oo + assert Min(-oo, nn) is -oo + assert Min(nn, -oo) is -oo + assert Min(-oo, p) is -oo + assert Min(p, -oo) is -oo + assert Min(-oo, oo) is -oo + assert Min(oo, -oo) is -oo + assert Min(n, n) == n + assert unchanged(Min, n, np) + assert Min(np, n) == Min(n, np) + assert Min(n, 0) == n + assert Min(0, n) == n + assert Min(n, nn) == n + assert Min(nn, n) == n + assert Min(n, p) == n + assert Min(p, n) == n + assert Min(n, oo) == n + assert Min(oo, n) == n + assert Min(np, np) == np + assert Min(np, 0) == np + assert Min(0, np) == np + assert Min(np, nn) == np + assert Min(nn, np) == np + assert Min(np, p) == np + assert Min(p, np) == np + assert Min(np, oo) == np + assert Min(oo, np) == np + assert Min(0, 0) == 0 + assert Min(0, nn) == 0 + assert Min(nn, 0) == 0 + assert Min(0, p) == 0 + assert Min(p, 0) == 0 + assert Min(0, oo) == 0 + assert Min(oo, 0) == 0 + assert Min(nn, nn) == nn + assert unchanged(Min, nn, p) + assert Min(p, nn) == Min(nn, p) + assert Min(nn, oo) == nn + assert Min(oo, nn) == nn + assert Min(p, p) == p + assert Min(p, oo) == p + assert Min(oo, p) == p + assert Min(oo, oo) is oo + + assert Min(n, n_).func is Min + assert Min(nn, nn_).func is Min + assert Min(np, np_).func is Min + assert Min(p, p_).func is Min + + # lists + assert Min() is S.Infinity + assert Min(x) == x + assert Min(x, y) == Min(y, x) + assert Min(x, y, z) == Min(z, y, x) + assert Min(x, Min(y, z)) == Min(z, y, x) + assert Min(x, Max(y, -oo)) == Min(x, y) + assert Min(p, oo, n, p, p, p_) == n + assert Min(p_, n_, p) == n_ + assert Min(n, oo, -7, p, p, 2) == Min(n, -7) + assert Min(2, x, p, n, oo, n_, p, 2, -2, -2) == Min(-2, x, n, n_) + assert Min(0, x, 1, y) == Min(0, x, y) + assert Min(1000, 100, -100, x, p, n) == Min(n, x, -100) + assert unchanged(Min, sin(x), cos(x)) + assert Min(sin(x), cos(x)) == Min(cos(x), sin(x)) + assert Min(cos(x), sin(x)).subs(x, 1) == cos(1) + assert Min(cos(x), sin(x)).subs(x, S.Half) == sin(S.Half) + raises(ValueError, lambda: Min(cos(x), sin(x)).subs(x, I)) + raises(ValueError, lambda: Min(I)) + raises(ValueError, lambda: Min(I, x)) + raises(ValueError, lambda: Min(S.ComplexInfinity, x)) + + assert Min(1, x).diff(x) == Heaviside(1 - x) + assert Min(x, 1).diff(x) == Heaviside(1 - x) + assert Min(0, -x, 1 - 2*x).diff(x) == -Heaviside(x + Min(0, -2*x + 1)) \ + - 2*Heaviside(2*x + Min(0, -x) - 1) + + # issue 7619 + f = Function('f') + assert Min(1, 2*Min(f(1), 2)) # doesn't fail + + # issue 7233 + e = Min(0, x) + assert e.n().args == (0, x) + + # issue 8643 + m = Min(n, p_, n_, r) + assert m.is_positive is False + assert m.is_nonnegative is False + assert m.is_negative is True + + m = Min(p, p_) + assert m.is_positive is True + assert m.is_nonnegative is True + assert m.is_negative is False + + m = Min(p, nn_, p_) + assert m.is_positive is None + assert m.is_nonnegative is True + assert m.is_negative is False + + m = Min(nn, p, r) + assert m.is_positive is None + assert m.is_nonnegative is None + assert m.is_negative is None + + +def test_Max(): + from sympy.abc import x, y, z + n = Symbol('n', negative=True) + n_ = Symbol('n_', negative=True) + nn = Symbol('nn', nonnegative=True) + p = Symbol('p', positive=True) + p_ = Symbol('p_', positive=True) + r = Symbol('r', real=True) + + assert Max(5, 4) == 5 + + # lists + + assert Max() is S.NegativeInfinity + assert Max(x) == x + assert Max(x, y) == Max(y, x) + assert Max(x, y, z) == Max(z, y, x) + assert Max(x, Max(y, z)) == Max(z, y, x) + assert Max(x, Min(y, oo)) == Max(x, y) + assert Max(n, -oo, n_, p, 2) == Max(p, 2) + assert Max(n, -oo, n_, p) == p + assert Max(2, x, p, n, -oo, S.NegativeInfinity, n_, p, 2) == Max(2, x, p) + assert Max(0, x, 1, y) == Max(1, x, y) + assert Max(r, r + 1, r - 1) == 1 + r + assert Max(1000, 100, -100, x, p, n) == Max(p, x, 1000) + assert Max(cos(x), sin(x)) == Max(sin(x), cos(x)) + assert Max(cos(x), sin(x)).subs(x, 1) == sin(1) + assert Max(cos(x), sin(x)).subs(x, S.Half) == cos(S.Half) + raises(ValueError, lambda: Max(cos(x), sin(x)).subs(x, I)) + raises(ValueError, lambda: Max(I)) + raises(ValueError, lambda: Max(I, x)) + raises(ValueError, lambda: Max(S.ComplexInfinity, 1)) + assert Max(n, -oo, n_, p, 2) == Max(p, 2) + assert Max(n, -oo, n_, p, 1000) == Max(p, 1000) + + assert Max(1, x).diff(x) == Heaviside(x - 1) + assert Max(x, 1).diff(x) == Heaviside(x - 1) + assert Max(x**2, 1 + x, 1).diff(x) == \ + 2*x*Heaviside(x**2 - Max(1, x + 1)) \ + + Heaviside(x - Max(1, x**2) + 1) + + e = Max(0, x) + assert e.n().args == (0, x) + + # issue 8643 + m = Max(p, p_, n, r) + assert m.is_positive is True + assert m.is_nonnegative is True + assert m.is_negative is False + + m = Max(n, n_) + assert m.is_positive is False + assert m.is_nonnegative is False + assert m.is_negative is True + + m = Max(n, n_, r) + assert m.is_positive is None + assert m.is_nonnegative is None + assert m.is_negative is None + + m = Max(n, nn, r) + assert m.is_positive is None + assert m.is_nonnegative is True + assert m.is_negative is False + + +def test_minmax_assumptions(): + r = Symbol('r', real=True) + a = Symbol('a', real=True, algebraic=True) + t = Symbol('t', real=True, transcendental=True) + q = Symbol('q', rational=True) + p = Symbol('p', irrational=True) + n = Symbol('n', rational=True, integer=False) + i = Symbol('i', integer=True) + o = Symbol('o', odd=True) + e = Symbol('e', even=True) + k = Symbol('k', prime=True) + reals = [r, a, t, q, p, n, i, o, e, k] + + for ext in (Max, Min): + for x, y in it.product(reals, repeat=2): + + # Must be real + assert ext(x, y).is_real + + # Algebraic? + if x.is_algebraic and y.is_algebraic: + assert ext(x, y).is_algebraic + elif x.is_transcendental and y.is_transcendental: + assert ext(x, y).is_transcendental + else: + assert ext(x, y).is_algebraic is None + + # Rational? + if x.is_rational and y.is_rational: + assert ext(x, y).is_rational + elif x.is_irrational and y.is_irrational: + assert ext(x, y).is_irrational + else: + assert ext(x, y).is_rational is None + + # Integer? + if x.is_integer and y.is_integer: + assert ext(x, y).is_integer + elif x.is_noninteger and y.is_noninteger: + assert ext(x, y).is_noninteger + else: + assert ext(x, y).is_integer is None + + # Odd? + if x.is_odd and y.is_odd: + assert ext(x, y).is_odd + elif x.is_odd is False and y.is_odd is False: + assert ext(x, y).is_odd is False + else: + assert ext(x, y).is_odd is None + + # Even? + if x.is_even and y.is_even: + assert ext(x, y).is_even + elif x.is_even is False and y.is_even is False: + assert ext(x, y).is_even is False + else: + assert ext(x, y).is_even is None + + # Prime? + if x.is_prime and y.is_prime: + assert ext(x, y).is_prime + elif x.is_prime is False and y.is_prime is False: + assert ext(x, y).is_prime is False + else: + assert ext(x, y).is_prime is None + + +def test_issue_8413(): + x = Symbol('x', real=True) + # we can't evaluate in general because non-reals are not + # comparable: Min(floor(3.2 + I), 3.2 + I) -> ValueError + assert Min(floor(x), x) == floor(x) + assert Min(ceiling(x), x) == x + assert Max(floor(x), x) == x + assert Max(ceiling(x), x) == ceiling(x) + + +def test_root(): + from sympy.abc import x + n = Symbol('n', integer=True) + k = Symbol('k', integer=True) + + assert root(2, 2) == sqrt(2) + assert root(2, 1) == 2 + assert root(2, 3) == 2**Rational(1, 3) + assert root(2, 3) == cbrt(2) + assert root(2, -5) == 2**Rational(4, 5)/2 + + assert root(-2, 1) == -2 + + assert root(-2, 2) == sqrt(2)*I + assert root(-2, 1) == -2 + + assert root(x, 2) == sqrt(x) + assert root(x, 1) == x + assert root(x, 3) == x**Rational(1, 3) + assert root(x, 3) == cbrt(x) + assert root(x, -5) == x**Rational(-1, 5) + + assert root(x, n) == x**(1/n) + assert root(x, -n) == x**(-1/n) + + assert root(x, n, k) == (-1)**(2*k/n)*x**(1/n) + + +def test_real_root(): + assert real_root(-8, 3) == -2 + assert real_root(-16, 4) == root(-16, 4) + r = root(-7, 4) + assert real_root(r) == r + r1 = root(-1, 3) + r2 = r1**2 + r3 = root(-1, 4) + assert real_root(r1 + r2 + r3) == -1 + r2 + r3 + assert real_root(root(-2, 3)) == -root(2, 3) + assert real_root(-8., 3) == -2.0 + x = Symbol('x') + n = Symbol('n') + g = real_root(x, n) + assert g.subs({"x": -8, "n": 3}) == -2 + assert g.subs({"x": 8, "n": 3}) == 2 + # give principle root if there is no real root -- if this is not desired + # then maybe a Root class is needed to raise an error instead + assert g.subs({"x": I, "n": 3}) == cbrt(I) + assert g.subs({"x": -8, "n": 2}) == sqrt(-8) + assert g.subs({"x": I, "n": 2}) == sqrt(I) + + +def test_issue_11463(): + numpy = import_module('numpy') + if not numpy: + skip("numpy not installed.") + x = Symbol('x') + f = lambdify(x, real_root((log(x/(x-2))), 3), 'numpy') + # numpy.select evaluates all options before considering conditions, + # so it raises a warning about root of negative number which does + # not affect the outcome. This warning is suppressed here + with ignore_warnings(RuntimeWarning): + assert f(numpy.array(-1)) < -1 + + +def test_rewrite_MaxMin_as_Heaviside(): + from sympy.abc import x + assert Max(0, x).rewrite(Heaviside) == x*Heaviside(x) + assert Max(3, x).rewrite(Heaviside) == x*Heaviside(x - 3) + \ + 3*Heaviside(-x + 3) + assert Max(0, x+2, 2*x).rewrite(Heaviside) == \ + 2*x*Heaviside(2*x)*Heaviside(x - 2) + \ + (x + 2)*Heaviside(-x + 2)*Heaviside(x + 2) + + assert Min(0, x).rewrite(Heaviside) == x*Heaviside(-x) + assert Min(3, x).rewrite(Heaviside) == x*Heaviside(-x + 3) + \ + 3*Heaviside(x - 3) + assert Min(x, -x, -2).rewrite(Heaviside) == \ + x*Heaviside(-2*x)*Heaviside(-x - 2) - \ + x*Heaviside(2*x)*Heaviside(x - 2) \ + - 2*Heaviside(-x + 2)*Heaviside(x + 2) + + +def test_rewrite_MaxMin_as_Piecewise(): + from sympy.core.symbol import symbols + from sympy.functions.elementary.piecewise import Piecewise + x, y, z, a, b = symbols('x y z a b', real=True) + vx, vy, va = symbols('vx vy va') + assert Max(a, b).rewrite(Piecewise) == Piecewise((a, a >= b), (b, True)) + assert Max(x, y, z).rewrite(Piecewise) == Piecewise((x, (x >= y) & (x >= z)), (y, y >= z), (z, True)) + assert Max(x, y, a, b).rewrite(Piecewise) == Piecewise((a, (a >= b) & (a >= x) & (a >= y)), + (b, (b >= x) & (b >= y)), (x, x >= y), (y, True)) + assert Min(a, b).rewrite(Piecewise) == Piecewise((a, a <= b), (b, True)) + assert Min(x, y, z).rewrite(Piecewise) == Piecewise((x, (x <= y) & (x <= z)), (y, y <= z), (z, True)) + assert Min(x, y, a, b).rewrite(Piecewise) == Piecewise((a, (a <= b) & (a <= x) & (a <= y)), + (b, (b <= x) & (b <= y)), (x, x <= y), (y, True)) + + # Piecewise rewriting of Min/Max does also takes place for not explicitly real arguments + assert Max(vx, vy).rewrite(Piecewise) == Piecewise((vx, vx >= vy), (vy, True)) + assert Min(va, vx, vy).rewrite(Piecewise) == Piecewise((va, (va <= vx) & (va <= vy)), (vx, vx <= vy), (vy, True)) + + +def test_issue_11099(): + from sympy.abc import x, y + # some fixed value tests + fixed_test_data = {x: -2, y: 3} + assert Min(x, y).evalf(subs=fixed_test_data) == \ + Min(x, y).subs(fixed_test_data).evalf() + assert Max(x, y).evalf(subs=fixed_test_data) == \ + Max(x, y).subs(fixed_test_data).evalf() + # randomly generate some test data + from sympy.core.random import randint + for i in range(20): + random_test_data = {x: randint(-100, 100), y: randint(-100, 100)} + assert Min(x, y).evalf(subs=random_test_data) == \ + Min(x, y).subs(random_test_data).evalf() + assert Max(x, y).evalf(subs=random_test_data) == \ + Max(x, y).subs(random_test_data).evalf() + + +def test_issue_12638(): + from sympy.abc import a, b, c + assert Min(a, b, c, Max(a, b)) == Min(a, b, c) + assert Min(a, b, Max(a, b, c)) == Min(a, b) + assert Min(a, b, Max(a, c)) == Min(a, b) + +def test_issue_21399(): + from sympy.abc import a, b, c + assert Max(Min(a, b), Min(a, b, c)) == Min(a, b) + + +def test_instantiation_evaluation(): + from sympy.abc import v, w, x, y, z + assert Min(1, Max(2, x)) == 1 + assert Max(3, Min(2, x)) == 3 + assert Min(Max(x, y), Max(x, z)) == Max(x, Min(y, z)) + assert set(Min(Max(w, x), Max(y, z)).args) == { + Max(w, x), Max(y, z)} + assert Min(Max(x, y), Max(x, z), w) == Min( + w, Max(x, Min(y, z))) + A, B = Min, Max + for i in range(2): + assert A(x, B(x, y)) == x + assert A(x, B(y, A(x, w, z))) == A(x, B(y, A(w, z))) + A, B = B, A + assert Min(w, Max(x, y), Max(v, x, z)) == Min( + w, Max(x, Min(y, Max(v, z)))) + +def test_rewrite_as_Abs(): + from itertools import permutations + from sympy.functions.elementary.complexes import Abs + from sympy.abc import x, y, z, w + def test(e): + free = e.free_symbols + a = e.rewrite(Abs) + assert not a.has(Min, Max) + for i in permutations(range(len(free))): + reps = dict(zip(free, i)) + assert a.xreplace(reps) == e.xreplace(reps) + test(Min(x, y)) + test(Max(x, y)) + test(Min(x, y, z)) + test(Min(Max(w, x), Max(y, z))) + +def test_issue_14000(): + assert isinstance(sqrt(4, evaluate=False), Pow) == True + assert isinstance(cbrt(3.5, evaluate=False), Pow) == True + assert isinstance(root(16, 4, evaluate=False), Pow) == True + + assert sqrt(4, evaluate=False) == Pow(4, S.Half, evaluate=False) + assert cbrt(3.5, evaluate=False) == Pow(3.5, Rational(1, 3), evaluate=False) + assert root(4, 2, evaluate=False) == Pow(4, S.Half, evaluate=False) + + assert root(16, 4, 2, evaluate=False).has(Pow) == True + assert real_root(-8, 3, evaluate=False).has(Pow) == True + +def test_issue_6899(): + from sympy.core.function import Lambda + x = Symbol('x') + eqn = Lambda(x, x) + assert eqn.func(*eqn.args) == eqn + +def test_Rem(): + from sympy.abc import x, y + assert Rem(5, 3) == 2 + assert Rem(-5, 3) == -2 + assert Rem(5, -3) == 2 + assert Rem(-5, -3) == -2 + assert Rem(x**3, y) == Rem(x**3, y) + assert Rem(Rem(-5, 3) + 3, 3) == 1 + + +def test_minmax_no_evaluate(): + from sympy import evaluate + p = Symbol('p', positive=True) + + assert Max(1, 3) == 3 + assert Max(1, 3).args == () + assert Max(0, p) == p + assert Max(0, p).args == () + assert Min(0, p) == 0 + assert Min(0, p).args == () + + assert Max(1, 3, evaluate=False) != 3 + assert Max(1, 3, evaluate=False).args == (1, 3) + assert Max(0, p, evaluate=False) != p + assert Max(0, p, evaluate=False).args == (0, p) + assert Min(0, p, evaluate=False) != 0 + assert Min(0, p, evaluate=False).args == (0, p) + + with evaluate(False): + assert Max(1, 3) != 3 + assert Max(1, 3).args == (1, 3) + assert Max(0, p) != p + assert Max(0, p).args == (0, p) + assert Min(0, p) != 0 + assert Min(0, p).args == (0, p) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_piecewise.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_piecewise.py new file mode 100644 index 0000000000000000000000000000000000000000..7d0728095578b49480a1334857a1c237012d2534 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_piecewise.py @@ -0,0 +1,1639 @@ +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import unchanged +from sympy.core.function import (Function, diff, expand) +from sympy.core.mul import Mul +from sympy.core.mod import Mod +from sympy.core.numbers import (Float, I, Rational, oo, pi, zoo) +from sympy.core.relational import (Eq, Ge, Gt, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (Abs, adjoint, arg, conjugate, im, re, transpose) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt) +from sympy.functions.elementary.piecewise import (Piecewise, + piecewise_fold, piecewise_exclusive, Undefined, ExprCondPair) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.delta_functions import (DiracDelta, Heaviside) +from sympy.functions.special.tensor_functions import KroneckerDelta +from sympy.integrals.integrals import (Integral, integrate) +from sympy.logic.boolalg import (And, ITE, Not, Or) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.printing import srepr +from sympy.sets.contains import Contains +from sympy.sets.sets import Interval +from sympy.solvers.solvers import solve +from sympy.testing.pytest import raises, slow +from sympy.utilities.lambdify import lambdify + +a, b, c, d, x, y = symbols('a:d, x, y') +z = symbols('z', nonzero=True) + + +def test_piecewise1(): + + # Test canonicalization + assert Piecewise((x, x < 1.)).has(1.0) # doesn't get changed to x < 1 + assert unchanged(Piecewise, ExprCondPair(x, x < 1), ExprCondPair(0, True)) + assert Piecewise((x, x < 1), (0, True)) == Piecewise(ExprCondPair(x, x < 1), + ExprCondPair(0, True)) + assert Piecewise((x, x < 1), (0, True), (1, True)) == \ + Piecewise((x, x < 1), (0, True)) + assert Piecewise((x, x < 1), (0, False), (-1, 1 > 2)) == \ + Piecewise((x, x < 1)) + assert Piecewise((x, x < 1), (0, x < 1), (0, True)) == \ + Piecewise((x, x < 1), (0, True)) + assert Piecewise((x, x < 1), (0, x < 2), (0, True)) == \ + Piecewise((x, x < 1), (0, True)) + assert Piecewise((x, x < 1), (x, x < 2), (0, True)) == \ + Piecewise((x, Or(x < 1, x < 2)), (0, True)) + assert Piecewise((x, x < 1), (x, x < 2), (x, True)) == x + assert Piecewise((x, True)) == x + # Explicitly constructed empty Piecewise not accepted + raises(TypeError, lambda: Piecewise()) + # False condition is never retained + assert Piecewise((2*x, x < 0), (x, False)) == \ + Piecewise((2*x, x < 0), (x, False), evaluate=False) == \ + Piecewise((2*x, x < 0)) + assert Piecewise((x, False)) == Undefined + raises(TypeError, lambda: Piecewise(x)) + assert Piecewise((x, 1)) == x # 1 and 0 are accepted as True/False + raises(TypeError, lambda: Piecewise((x, 2))) + raises(TypeError, lambda: Piecewise((x, x**2))) + raises(TypeError, lambda: Piecewise(([1], True))) + assert Piecewise(((1, 2), True)) == Tuple(1, 2) + cond = (Piecewise((1, x < 0), (2, True)) < y) + assert Piecewise((1, cond) + ) == Piecewise((1, ITE(x < 0, y > 1, y > 2))) + + assert Piecewise((1, x > 0), (2, And(x <= 0, x > -1)) + ) == Piecewise((1, x > 0), (2, x > -1)) + assert Piecewise((1, x <= 0), (2, (x < 0) & (x > -1)) + ) == Piecewise((1, x <= 0)) + + # test for supporting Contains in Piecewise + pwise = Piecewise( + (1, And(x <= 6, x > 1, Contains(x, S.Integers))), + (0, True)) + assert pwise.subs(x, pi) == 0 + assert pwise.subs(x, 2) == 1 + assert pwise.subs(x, 7) == 0 + + # Test subs + p = Piecewise((-1, x < -1), (x**2, x < 0), (log(x), x >= 0)) + p_x2 = Piecewise((-1, x**2 < -1), (x**4, x**2 < 0), (log(x**2), x**2 >= 0)) + assert p.subs(x, x**2) == p_x2 + assert p.subs(x, -5) == -1 + assert p.subs(x, -1) == 1 + assert p.subs(x, 1) == log(1) + + # More subs tests + p2 = Piecewise((1, x < pi), (-1, x < 2*pi), (0, x > 2*pi)) + p3 = Piecewise((1, Eq(x, 0)), (1/x, True)) + p4 = Piecewise((1, Eq(x, 0)), (2, 1/x>2)) + assert p2.subs(x, 2) == 1 + assert p2.subs(x, 4) == -1 + assert p2.subs(x, 10) == 0 + assert p3.subs(x, 0.0) == 1 + assert p4.subs(x, 0.0) == 1 + + + f, g, h = symbols('f,g,h', cls=Function) + pf = Piecewise((f(x), x < -1), (f(x) + h(x) + 2, x <= 1)) + pg = Piecewise((g(x), x < -1), (g(x) + h(x) + 2, x <= 1)) + assert pg.subs(g, f) == pf + + assert Piecewise((1, Eq(x, 0)), (0, True)).subs(x, 0) == 1 + assert Piecewise((1, Eq(x, 0)), (0, True)).subs(x, 1) == 0 + assert Piecewise((1, Eq(x, y)), (0, True)).subs(x, y) == 1 + assert Piecewise((1, Eq(x, z)), (0, True)).subs(x, z) == 1 + assert Piecewise((1, Eq(exp(x), cos(z))), (0, True)).subs(x, z) == \ + Piecewise((1, Eq(exp(z), cos(z))), (0, True)) + + p5 = Piecewise( (0, Eq(cos(x) + y, 0)), (1, True)) + assert p5.subs(y, 0) == Piecewise( (0, Eq(cos(x), 0)), (1, True)) + + assert Piecewise((-1, y < 1), (0, x < 0), (1, Eq(x, 0)), (2, True) + ).subs(x, 1) == Piecewise((-1, y < 1), (2, True)) + assert Piecewise((1, Eq(x**2, -1)), (2, x < 0)).subs(x, I) == 1 + + p6 = Piecewise((x, x > 0)) + n = symbols('n', negative=True) + assert p6.subs(x, n) == Undefined + + # Test evalf + assert p.evalf() == Piecewise((-1.0, x < -1), (x**2, x < 0), (log(x), True)) + assert p.evalf(subs={x: -2}) == -1.0 + assert p.evalf(subs={x: -1}) == 1.0 + assert p.evalf(subs={x: 1}) == log(1) + assert p6.evalf(subs={x: -5}) == Undefined + + # Test doit + f_int = Piecewise((Integral(x, (x, 0, 1)), x < 1)) + assert f_int.doit() == Piecewise( (S.Half, x < 1) ) + + # Test differentiation + f = x + fp = x*p + dp = Piecewise((0, x < -1), (2*x, x < 0), (1/x, x >= 0)) + fp_dx = x*dp + p + assert diff(p, x) == dp + assert diff(f*p, x) == fp_dx + + # Test simple arithmetic + assert x*p == fp + assert x*p + p == p + x*p + assert p + f == f + p + assert p + dp == dp + p + assert p - dp == -(dp - p) + + # Test power + dp2 = Piecewise((0, x < -1), (4*x**2, x < 0), (1/x**2, x >= 0)) + assert dp**2 == dp2 + + # Test _eval_interval + f1 = x*y + 2 + f2 = x*y**2 + 3 + peval = Piecewise((f1, x < 0), (f2, x > 0)) + peval_interval = f1.subs( + x, 0) - f1.subs(x, -1) + f2.subs(x, 1) - f2.subs(x, 0) + assert peval._eval_interval(x, 0, 0) == 0 + assert peval._eval_interval(x, -1, 1) == peval_interval + peval2 = Piecewise((f1, x < 0), (f2, True)) + assert peval2._eval_interval(x, 0, 0) == 0 + assert peval2._eval_interval(x, 1, -1) == -peval_interval + assert peval2._eval_interval(x, -1, -2) == f1.subs(x, -2) - f1.subs(x, -1) + assert peval2._eval_interval(x, -1, 1) == peval_interval + assert peval2._eval_interval(x, None, 0) == peval2.subs(x, 0) + assert peval2._eval_interval(x, -1, None) == -peval2.subs(x, -1) + + # Test integration + assert p.integrate() == Piecewise( + (-x, x < -1), + (x**3/3 + Rational(4, 3), x < 0), + (x*log(x) - x + Rational(4, 3), True)) + p = Piecewise((x, x < 1), (x**2, -1 <= x), (x, 3 < x)) + assert integrate(p, (x, -2, 2)) == Rational(5, 6) + assert integrate(p, (x, 2, -2)) == Rational(-5, 6) + p = Piecewise((0, x < 0), (1, x < 1), (0, x < 2), (1, x < 3), (0, True)) + assert integrate(p, (x, -oo, oo)) == 2 + p = Piecewise((x, x < -10), (x**2, x <= -1), (x, 1 < x)) + assert integrate(p, (x, -2, 2)) == Undefined + + # Test commutativity + assert isinstance(p, Piecewise) and p.is_commutative is True + + +def test_piecewise_free_symbols(): + f = Piecewise((x, a < 0), (y, True)) + assert f.free_symbols == {x, y, a} + + +def test_piecewise_integrate1(): + x, y = symbols('x y', real=True) + + f = Piecewise(((x - 2)**2, x >= 0), (1, True)) + assert integrate(f, (x, -2, 2)) == Rational(14, 3) + + g = Piecewise(((x - 5)**5, x >= 4), (f, True)) + assert integrate(g, (x, -2, 2)) == Rational(14, 3) + assert integrate(g, (x, -2, 5)) == Rational(43, 6) + + assert g == Piecewise(((x - 5)**5, x >= 4), (f, x < 4)) + + g = Piecewise(((x - 5)**5, 2 <= x), (f, x < 2)) + assert integrate(g, (x, -2, 2)) == Rational(14, 3) + assert integrate(g, (x, -2, 5)) == Rational(-701, 6) + + assert g == Piecewise(((x - 5)**5, 2 <= x), (f, True)) + + g = Piecewise(((x - 5)**5, 2 <= x), (2*f, True)) + assert integrate(g, (x, -2, 2)) == Rational(28, 3) + assert integrate(g, (x, -2, 5)) == Rational(-673, 6) + + +def test_piecewise_integrate1b(): + g = Piecewise((1, x > 0), (0, Eq(x, 0)), (-1, x < 0)) + assert integrate(g, (x, -1, 1)) == 0 + + g = Piecewise((1, x - y < 0), (0, True)) + assert integrate(g, (y, -oo, 0)) == -Min(0, x) + assert g.subs(x, -3).integrate((y, -oo, 0)) == 3 + assert integrate(g, (y, 0, -oo)) == Min(0, x) + assert integrate(g, (y, 0, oo)) == -Max(0, x) + oo + assert integrate(g, (y, -oo, 42)) == -Min(42, x) + 42 + assert integrate(g, (y, -oo, oo)) == -x + oo + + g = Piecewise((0, x < 0), (x, x <= 1), (1, True)) + gy1 = g.integrate((x, y, 1)) + g1y = g.integrate((x, 1, y)) + for yy in (-1, S.Half, 2): + assert g.integrate((x, yy, 1)) == gy1.subs(y, yy) + assert g.integrate((x, 1, yy)) == g1y.subs(y, yy) + assert gy1 == Piecewise( + (-Min(1, Max(0, y))**2/2 + S.Half, y < 1), + (-y + 1, True)) + assert g1y == Piecewise( + (Min(1, Max(0, y))**2/2 - S.Half, y < 1), + (y - 1, True)) + + +@slow +def test_piecewise_integrate1ca(): + y = symbols('y', real=True) + g = Piecewise( + (1 - x, Interval(0, 1).contains(x)), + (1 + x, Interval(-1, 0).contains(x)), + (0, True) + ) + gy1 = g.integrate((x, y, 1)) + g1y = g.integrate((x, 1, y)) + + assert g.integrate((x, -2, 1)) == gy1.subs(y, -2) + assert g.integrate((x, 1, -2)) == g1y.subs(y, -2) + assert g.integrate((x, 0, 1)) == gy1.subs(y, 0) + assert g.integrate((x, 1, 0)) == g1y.subs(y, 0) + assert g.integrate((x, 2, 1)) == gy1.subs(y, 2) + assert g.integrate((x, 1, 2)) == g1y.subs(y, 2) + assert piecewise_fold(gy1.rewrite(Piecewise) + ).simplify() == Piecewise( + (1, y <= -1), + (-y**2/2 - y + S.Half, y <= 0), + (y**2/2 - y + S.Half, y < 1), + (0, True)) + assert piecewise_fold(g1y.rewrite(Piecewise) + ).simplify() == Piecewise( + (-1, y <= -1), + (y**2/2 + y - S.Half, y <= 0), + (-y**2/2 + y - S.Half, y < 1), + (0, True)) + assert gy1 == Piecewise( + ( + -Min(1, Max(-1, y))**2/2 - Min(1, Max(-1, y)) + + Min(1, Max(0, y))**2 + S.Half, y < 1), + (0, True) + ) + assert g1y == Piecewise( + ( + Min(1, Max(-1, y))**2/2 + Min(1, Max(-1, y)) - + Min(1, Max(0, y))**2 - S.Half, y < 1), + (0, True)) + + +@slow +def test_piecewise_integrate1cb(): + y = symbols('y', real=True) + g = Piecewise( + (0, Or(x <= -1, x >= 1)), + (1 - x, x > 0), + (1 + x, True) + ) + gy1 = g.integrate((x, y, 1)) + g1y = g.integrate((x, 1, y)) + + assert g.integrate((x, -2, 1)) == gy1.subs(y, -2) + assert g.integrate((x, 1, -2)) == g1y.subs(y, -2) + assert g.integrate((x, 0, 1)) == gy1.subs(y, 0) + assert g.integrate((x, 1, 0)) == g1y.subs(y, 0) + assert g.integrate((x, 2, 1)) == gy1.subs(y, 2) + assert g.integrate((x, 1, 2)) == g1y.subs(y, 2) + + assert piecewise_fold(gy1.rewrite(Piecewise) + ).simplify() == Piecewise( + (1, y <= -1), + (-y**2/2 - y + S.Half, y <= 0), + (y**2/2 - y + S.Half, y < 1), + (0, True)) + assert piecewise_fold(g1y.rewrite(Piecewise) + ).simplify() == Piecewise( + (-1, y <= -1), + (y**2/2 + y - S.Half, y <= 0), + (-y**2/2 + y - S.Half, y < 1), + (0, True)) + + # g1y and gy1 should simplify if the condition that y < 1 + # is applied, e.g. Min(1, Max(-1, y)) --> Max(-1, y) + assert gy1 == Piecewise( + ( + -Min(1, Max(-1, y))**2/2 - Min(1, Max(-1, y)) + + Min(1, Max(0, y))**2 + S.Half, y < 1), + (0, True) + ) + assert g1y == Piecewise( + ( + Min(1, Max(-1, y))**2/2 + Min(1, Max(-1, y)) - + Min(1, Max(0, y))**2 - S.Half, y < 1), + (0, True)) + + +def test_piecewise_integrate2(): + from itertools import permutations + lim = Tuple(x, c, d) + p = Piecewise((1, x < a), (2, x > b), (3, True)) + q = p.integrate(lim) + assert q == Piecewise( + (-c + 2*d - 2*Min(d, Max(a, c)) + Min(d, Max(a, b, c)), c < d), + (-2*c + d + 2*Min(c, Max(a, d)) - Min(c, Max(a, b, d)), True)) + for v in permutations((1, 2, 3, 4)): + r = dict(zip((a, b, c, d), v)) + assert p.subs(r).integrate(lim.subs(r)) == q.subs(r) + + +def test_meijer_bypass(): + # totally bypass meijerg machinery when dealing + # with Piecewise in integrate + assert Piecewise((1, x < 4), (0, True)).integrate((x, oo, 1)) == -3 + + +def test_piecewise_integrate3_inequality_conditions(): + from sympy.utilities.iterables import cartes + lim = (x, 0, 5) + # set below includes two pts below range, 2 pts in range, + # 2 pts above range, and the boundaries + N = (-2, -1, 0, 1, 2, 5, 6, 7) + + p = Piecewise((1, x > a), (2, x > b), (0, True)) + ans = p.integrate(lim) + for i, j in cartes(N, repeat=2): + reps = dict(zip((a, b), (i, j))) + assert ans.subs(reps) == p.subs(reps).integrate(lim) + assert ans.subs(a, 4).subs(b, 1) == 0 + 2*3 + 1 + + p = Piecewise((1, x > a), (2, x < b), (0, True)) + ans = p.integrate(lim) + for i, j in cartes(N, repeat=2): + reps = dict(zip((a, b), (i, j))) + assert ans.subs(reps) == p.subs(reps).integrate(lim) + + # delete old tests that involved c1 and c2 since those + # reduce to the above except that a value of 0 was used + # for two expressions whereas the above uses 3 different + # values + + +@slow +def test_piecewise_integrate4_symbolic_conditions(): + a = Symbol('a', real=True) + b = Symbol('b', real=True) + x = Symbol('x', real=True) + y = Symbol('y', real=True) + p0 = Piecewise((0, Or(x < a, x > b)), (1, True)) + p1 = Piecewise((0, x < a), (0, x > b), (1, True)) + p2 = Piecewise((0, x > b), (0, x < a), (1, True)) + p3 = Piecewise((0, x < a), (1, x < b), (0, True)) + p4 = Piecewise((0, x > b), (1, x > a), (0, True)) + p5 = Piecewise((1, And(a < x, x < b)), (0, True)) + + # check values of a=1, b=3 (and reversed) with values + # of y of 0, 1, 2, 3, 4 + lim = Tuple(x, -oo, y) + for p in (p0, p1, p2, p3, p4, p5): + ans = p.integrate(lim) + for i in range(5): + reps = {a:1, b:3, y:i} + assert ans.subs(reps) == p.subs(reps).integrate(lim.subs(reps)) + reps = {a: 3, b:1, y:i} + assert ans.subs(reps) == p.subs(reps).integrate(lim.subs(reps)) + lim = Tuple(x, y, oo) + for p in (p0, p1, p2, p3, p4, p5): + ans = p.integrate(lim) + for i in range(5): + reps = {a:1, b:3, y:i} + assert ans.subs(reps) == p.subs(reps).integrate(lim.subs(reps)) + reps = {a:3, b:1, y:i} + assert ans.subs(reps) == p.subs(reps).integrate(lim.subs(reps)) + + ans = Piecewise( + (0, x <= Min(a, b)), + (x - Min(a, b), x <= b), + (b - Min(a, b), True)) + for i in (p0, p1, p2, p4): + assert i.integrate(x) == ans + assert p3.integrate(x) == Piecewise( + (0, x < a), + (-a + x, x <= Max(a, b)), + (-a + Max(a, b), True)) + assert p5.integrate(x) == Piecewise( + (0, x <= a), + (-a + x, x <= Max(a, b)), + (-a + Max(a, b), True)) + + p1 = Piecewise((0, x < a), (S.Half, x > b), (1, True)) + p2 = Piecewise((S.Half, x > b), (0, x < a), (1, True)) + p3 = Piecewise((0, x < a), (1, x < b), (S.Half, True)) + p4 = Piecewise((S.Half, x > b), (1, x > a), (0, True)) + p5 = Piecewise((1, And(a < x, x < b)), (S.Half, x > b), (0, True)) + + # check values of a=1, b=3 (and reversed) with values + # of y of 0, 1, 2, 3, 4 + lim = Tuple(x, -oo, y) + for p in (p1, p2, p3, p4, p5): + ans = p.integrate(lim) + for i in range(5): + reps = {a:1, b:3, y:i} + assert ans.subs(reps) == p.subs(reps).integrate(lim.subs(reps)) + reps = {a: 3, b:1, y:i} + assert ans.subs(reps) == p.subs(reps).integrate(lim.subs(reps)) + + +def test_piecewise_integrate5_independent_conditions(): + p = Piecewise((0, Eq(y, 0)), (x*y, True)) + assert integrate(p, (x, 1, 3)) == Piecewise((0, Eq(y, 0)), (4*y, True)) + + +def test_issue_22917(): + p = (Piecewise((0, ITE((x - y > 1) | (2 * x - 2 * y > 1), False, + ITE(x - y > 1, 2 * y - 2 < -1, 2 * x - 2 * y > 1))), + (Piecewise((0, ITE(x - y > 1, True, 2 * x - 2 * y > 1)), + (2 * Piecewise((0, x - y > 1), (y, True)), True)), True)) + + 2 * Piecewise((1, ITE((x - y > 1) | (2 * x - 2 * y > 1), False, + ITE(x - y > 1, 2 * y - 2 < -1, 2 * x - 2 * y > 1))), + (Piecewise((1, ITE(x - y > 1, True, 2 * x - 2 * y > 1)), + (2 * Piecewise((1, x - y > 1), (x, True)), True)), True))) + assert piecewise_fold(p) == Piecewise((2, (x - y > S.Half) | (x - y > 1)), + (2*y + 4, x - y > 1), + (4*x + 2*y, True)) + assert piecewise_fold(p > 1).rewrite(ITE) == ITE((x - y > S.Half) | (x - y > 1), True, + ITE(x - y > 1, 2*y + 4 > 1, 4*x + 2*y > 1)) + + +def test_piecewise_simplify(): + p = Piecewise(((x**2 + 1)/x**2, Eq(x*(1 + x) - x**2, 0)), + ((-1)**x*(-1), True)) + assert p.simplify() == \ + Piecewise((zoo, Eq(x, 0)), ((-1)**(x + 1), True)) + # simplify when there are Eq in conditions + assert Piecewise( + (a, And(Eq(a, 0), Eq(a + b, 0))), (1, True)).simplify( + ) == Piecewise( + (0, And(Eq(a, 0), Eq(b, 0))), (1, True)) + assert Piecewise((2*x*factorial(a)/(factorial(y)*factorial(-y + a)), + Eq(y, 0) & Eq(-y + a, 0)), (2*factorial(a)/(factorial(y)*factorial(-y + + a)), Eq(y, 0) & Eq(-y + a, 1)), (0, True)).simplify( + ) == Piecewise( + (2*x, And(Eq(a, 0), Eq(y, 0))), + (2, And(Eq(a, 1), Eq(y, 0))), + (0, True)) + args = (2, And(Eq(x, 2), Ge(y, 0))), (x, True) + assert Piecewise(*args).simplify() == Piecewise(*args) + args = (1, Eq(x, 0)), (sin(x)/x, True) + assert Piecewise(*args).simplify() == Piecewise(*args) + assert Piecewise((2 + y, And(Eq(x, 2), Eq(y, 0))), (x, True) + ).simplify() == x + # check that x or f(x) are recognized as being Symbol-like for lhs + args = Tuple((1, Eq(x, 0)), (sin(x) + 1 + x, True)) + ans = x + sin(x) + 1 + f = Function('f') + assert Piecewise(*args).simplify() == ans + assert Piecewise(*args.subs(x, f(x))).simplify() == ans.subs(x, f(x)) + + # issue 18634 + d = Symbol("d", integer=True) + n = Symbol("n", integer=True) + t = Symbol("t", positive=True) + expr = Piecewise((-d + 2*n, Eq(1/t, 1)), (t**(1 - 4*n)*t**(4*n - 1)*(-d + 2*n), True)) + assert expr.simplify() == -d + 2*n + + # issue 22747 + p = Piecewise((0, (t < -2) & (t < -1) & (t < 0)), ((t/2 + 1)*(t + + 1)*(t + 2), (t < -1) & (t < 0)), ((S.Half - t/2)*(1 - t)*(t + 1), + (t < -2) & (t < -1) & (t < 1)), ((t + 1)*(-t*(t/2 + 1) + (S.Half + - t/2)*(1 - t)), (t < -2) & (t < -1) & (t < 0) & (t < 1)), ((t + + 1)*((S.Half - t/2)*(1 - t) + (t/2 + 1)*(t + 2)), (t < -1) & (t < + 1)), ((t + 1)*(-t*(t/2 + 1) + (S.Half - t/2)*(1 - t)), (t < -1) & + (t < 0) & (t < 1)), (0, (t < -2) & (t < -1)), ((t/2 + 1)*(t + + 1)*(t + 2), t < -1), ((t + 1)*(-t*(t/2 + 1) + (S.Half - t/2)*(t + + 1)), (t < 0) & ((t < -2) | (t < 0))), ((S.Half - t/2)*(1 - t)*(t + + 1), (t < 1) & ((t < -2) | (t < 1))), (0, True)) + Piecewise((0, + (t < -1) & (t < 0) & (t < 1)), ((1 - t)*(t/2 + S.Half)*(t + 1), + (t < 0) & (t < 1)), ((1 - t)*(1 - t/2)*(2 - t), (t < -1) & (t < + 0) & (t < 2)), ((1 - t)*((1 - t)*(t/2 + S.Half) + (1 - t/2)*(2 - + t)), (t < -1) & (t < 0) & (t < 1) & (t < 2)), ((1 - t)*((1 - + t/2)*(2 - t) + (t/2 + S.Half)*(t + 1)), (t < 0) & (t < 2)), ((1 - + t)*((1 - t)*(t/2 + S.Half) + (1 - t/2)*(2 - t)), (t < 0) & (t < + 1) & (t < 2)), (0, (t < -1) & (t < 0)), ((1 - t)*(t/2 + + S.Half)*(t + 1), t < 0), ((1 - t)*(t*(1 - t/2) + (1 - t)*(t/2 + + S.Half)), (t < 1) & ((t < -1) | (t < 1))), ((1 - t)*(1 - t/2)*(2 + - t), (t < 2) & ((t < -1) | (t < 2))), (0, True)) + assert p.simplify() == Piecewise( + (0, t < -2), ((t + 1)*(t + 2)**2/2, t < -1), (-3*t**3/2 + - 5*t**2/2 + 1, t < 0), (3*t**3/2 - 5*t**2/2 + 1, t < 1), ((1 - + t)*(t - 2)**2/2, t < 2), (0, True)) + + # coverage + nan = Undefined + assert Piecewise((1, x > 3), (2, x < 2), (3, x > 1)).simplify( + ) == Piecewise((1, x > 3), (2, x < 2), (3, True)) + assert Piecewise((1, x < 2), (2, x < 1), (3, True)).simplify( + ) == Piecewise((1, x < 2), (3, True)) + assert Piecewise((1, x > 2)).simplify() == Piecewise((1, x > 2), + (nan, True)) + assert Piecewise((1, (x >= 2) & (x < oo)) + ).simplify() == Piecewise((1, (x >= 2) & (x < oo)), (nan, True)) + assert Piecewise((1, x < 2), (2, (x > 1) & (x < 3)), (3, True) + ). simplify() == Piecewise((1, x < 2), (2, x < 3), (3, True)) + assert Piecewise((1, x < 2), (2, (x <= 3) & (x > 1)), (3, True) + ).simplify() == Piecewise((1, x < 2), (2, x <= 3), (3, True)) + assert Piecewise((1, x < 2), (2, (x > 2) & (x < 3)), (3, True) + ).simplify() == Piecewise((1, x < 2), (2, (x > 2) & (x < 3)), + (3, True)) + assert Piecewise((1, x < 2), (2, (x >= 1) & (x <= 3)), (3, True) + ).simplify() == Piecewise((1, x < 2), (2, x <= 3), (3, True)) + assert Piecewise((1, x < 1), (2, (x >= 2) & (x <= 3)), (3, True) + ).simplify() == Piecewise((1, x < 1), (2, (x >= 2) & (x <= 3)), + (3, True)) + # https://github.com/sympy/sympy/issues/25603 + assert Piecewise((log(x), (x <= 5) & (x > 3)), (x, True) + ).simplify() == Piecewise((log(x), (x <= 5) & (x > 3)), (x, True)) + + assert Piecewise((1, (x >= 1) & (x < 3)), (2, (x > 2) & (x < 4)) + ).simplify() == Piecewise((1, (x >= 1) & (x < 3)), ( + 2, (x >= 3) & (x < 4)), (nan, True)) + assert Piecewise((1, (x >= 1) & (x <= 3)), (2, (x > 2) & (x < 4)) + ).simplify() == Piecewise((1, (x >= 1) & (x <= 3)), ( + 2, (x > 3) & (x < 4)), (nan, True)) + + # involves a symbolic range so cset.inf fails + L = Symbol('L', nonnegative=True) + p = Piecewise((nan, x <= 0), (0, (x >= 0) & (L > x) & (L - x <= 0)), + (x - L/2, (L > x) & (L - x <= 0)), + (L/2 - x, (x >= 0) & (L > x)), + (0, L > x), (nan, True)) + assert p.simplify() == Piecewise( + (nan, x <= 0), (L/2 - x, L > x), (nan, True)) + assert p.subs(L, y).simplify() == Piecewise( + (nan, x <= 0), (-x + y/2, x < Max(0, y)), (0, x < y), (nan, True)) + + +def test_piecewise_solve(): + abs2 = Piecewise((-x, x <= 0), (x, x > 0)) + f = abs2.subs(x, x - 2) + assert solve(f, x) == [2] + assert solve(f - 1, x) == [1, 3] + + f = Piecewise(((x - 2)**2, x >= 0), (1, True)) + assert solve(f, x) == [2] + + g = Piecewise(((x - 5)**5, x >= 4), (f, True)) + assert solve(g, x) == [2, 5] + + g = Piecewise(((x - 5)**5, x >= 4), (f, x < 4)) + assert solve(g, x) == [2, 5] + + g = Piecewise(((x - 5)**5, x >= 2), (f, x < 2)) + assert solve(g, x) == [5] + + g = Piecewise(((x - 5)**5, x >= 2), (f, True)) + assert solve(g, x) == [5] + + g = Piecewise(((x - 5)**5, x >= 2), (f, True), (10, False)) + assert solve(g, x) == [5] + + g = Piecewise(((x - 5)**5, x >= 2), + (-x + 2, x - 2 <= 0), (x - 2, x - 2 > 0)) + assert solve(g, x) == [5] + + # if no symbol is given the piecewise detection must still work + assert solve(Piecewise((x - 2, x > 2), (2 - x, True)) - 3) == [-1, 5] + + f = Piecewise(((x - 2)**2, x >= 0), (0, True)) + raises(NotImplementedError, lambda: solve(f, x)) + + def nona(ans): + return list(filter(lambda x: x is not S.NaN, ans)) + p = Piecewise((x**2 - 4, x < y), (x - 2, True)) + ans = solve(p, x) + assert nona([i.subs(y, -2) for i in ans]) == [2] + assert nona([i.subs(y, 2) for i in ans]) == [-2, 2] + assert nona([i.subs(y, 3) for i in ans]) == [-2, 2] + assert ans == [ + Piecewise((-2, y > -2), (S.NaN, True)), + Piecewise((2, y <= 2), (S.NaN, True)), + Piecewise((2, y > 2), (S.NaN, True))] + + # issue 6060 + absxm3 = Piecewise( + (x - 3, 0 <= x - 3), + (3 - x, 0 > x - 3) + ) + assert solve(absxm3 - y, x) == [ + Piecewise((-y + 3, -y < 0), (S.NaN, True)), + Piecewise((y + 3, y >= 0), (S.NaN, True))] + p = Symbol('p', positive=True) + assert solve(absxm3 - p, x) == [-p + 3, p + 3] + + # issue 6989 + f = Function('f') + assert solve(Eq(-f(x), Piecewise((1, x > 0), (0, True))), f(x)) == \ + [Piecewise((-1, x > 0), (0, True))] + + # issue 8587 + f = Piecewise((2*x**2, And(0 < x, x < 1)), (2, True)) + assert solve(f - 1) == [1/sqrt(2)] + + +def test_piecewise_fold(): + p = Piecewise((x, x < 1), (1, 1 <= x)) + + assert piecewise_fold(x*p) == Piecewise((x**2, x < 1), (x, 1 <= x)) + assert piecewise_fold(p + p) == Piecewise((2*x, x < 1), (2, 1 <= x)) + assert piecewise_fold(Piecewise((1, x < 0), (2, True)) + + Piecewise((10, x < 0), (-10, True))) == \ + Piecewise((11, x < 0), (-8, True)) + + p1 = Piecewise((0, x < 0), (x, x <= 1), (0, True)) + p2 = Piecewise((0, x < 0), (1 - x, x <= 1), (0, True)) + + p = 4*p1 + 2*p2 + assert integrate( + piecewise_fold(p), (x, -oo, oo)) == integrate(2*x + 2, (x, 0, 1)) + + assert piecewise_fold( + Piecewise((1, y <= 0), (-Piecewise((2, y >= 0)), True) + )) == Piecewise((1, y <= 0), (-2, y >= 0)) + + assert piecewise_fold(Piecewise((x, ITE(x > 0, y < 1, y > 1))) + ) == Piecewise((x, ((x <= 0) | (y < 1)) & ((x > 0) | (y > 1)))) + + a, b = (Piecewise((2, Eq(x, 0)), (0, True)), + Piecewise((x, Eq(-x + y, 0)), (1, Eq(-x + y, 1)), (0, True))) + assert piecewise_fold(Mul(a, b, evaluate=False) + ) == piecewise_fold(Mul(b, a, evaluate=False)) + + +def test_piecewise_fold_piecewise_in_cond(): + p1 = Piecewise((cos(x), x < 0), (0, True)) + p2 = Piecewise((0, Eq(p1, 0)), (p1 / Abs(p1), True)) + assert p2.subs(x, -pi/2) == 0 + assert p2.subs(x, 1) == 0 + assert p2.subs(x, -pi/4) == 1 + p4 = Piecewise((0, Eq(p1, 0)), (1,True)) + ans = piecewise_fold(p4) + for i in range(-1, 1): + assert ans.subs(x, i) == p4.subs(x, i) + + r1 = 1 < Piecewise((1, x < 1), (3, True)) + ans = piecewise_fold(r1) + for i in range(2): + assert ans.subs(x, i) == r1.subs(x, i) + + p5 = Piecewise((1, x < 0), (3, True)) + p6 = Piecewise((1, x < 1), (3, True)) + p7 = Piecewise((1, p5 < p6), (0, True)) + ans = piecewise_fold(p7) + for i in range(-1, 2): + assert ans.subs(x, i) == p7.subs(x, i) + + +def test_piecewise_fold_piecewise_in_cond_2(): + p1 = Piecewise((cos(x), x < 0), (0, True)) + p2 = Piecewise((0, Eq(p1, 0)), (1 / p1, True)) + p3 = Piecewise( + (0, (x >= 0) | Eq(cos(x), 0)), + (1/cos(x), x < 0), + (zoo, True)) # redundant b/c all x are already covered + assert(piecewise_fold(p2) == p3) + + +def test_piecewise_fold_expand(): + p1 = Piecewise((1, Interval(0, 1, False, True).contains(x)), (0, True)) + + p2 = piecewise_fold(expand((1 - x)*p1)) + cond = ((x >= 0) & (x < 1)) + assert piecewise_fold(expand((1 - x)*p1), evaluate=False + ) == Piecewise((1 - x, cond), (-x, cond), (1, cond), (0, True), evaluate=False) + assert piecewise_fold(expand((1 - x)*p1), evaluate=None + ) == Piecewise((1 - x, cond), (0, True)) + assert p2 == Piecewise((1 - x, cond), (0, True)) + assert p2 == expand(piecewise_fold((1 - x)*p1)) + + +def test_piecewise_duplicate(): + p = Piecewise((x, x < -10), (x**2, x <= -1), (x, 1 < x)) + assert p == Piecewise(*p.args) + + +def test_doit(): + p1 = Piecewise((x, x < 1), (x**2, -1 <= x), (x, 3 < x)) + p2 = Piecewise((x, x < 1), (Integral(2 * x), -1 <= x), (x, 3 < x)) + assert p2.doit() == p1 + assert p2.doit(deep=False) == p2 + # issue 17165 + p1 = Sum(y**x, (x, -1, oo)).doit() + assert p1.doit() == p1 + + +def test_piecewise_interval(): + p1 = Piecewise((x, Interval(0, 1).contains(x)), (0, True)) + assert p1.subs(x, -0.5) == 0 + assert p1.subs(x, 0.5) == 0.5 + assert p1.diff(x) == Piecewise((1, Interval(0, 1).contains(x)), (0, True)) + assert integrate(p1, x) == Piecewise( + (0, x <= 0), + (x**2/2, x <= 1), + (S.Half, True)) + + +def test_piecewise_exclusive(): + p = Piecewise((0, x < 0), (S.Half, x <= 0), (1, True)) + assert piecewise_exclusive(p) == Piecewise((0, x < 0), (S.Half, Eq(x, 0)), + (1, x > 0), evaluate=False) + assert piecewise_exclusive(p + 2) == Piecewise((0, x < 0), (S.Half, Eq(x, 0)), + (1, x > 0), evaluate=False) + 2 + assert piecewise_exclusive(Piecewise((1, y <= 0), + (-Piecewise((2, y >= 0)), True))) == \ + Piecewise((1, y <= 0), + (-Piecewise((2, y >= 0), + (S.NaN, y < 0), evaluate=False), y > 0), evaluate=False) + assert piecewise_exclusive(Piecewise((1, x > y))) == Piecewise((1, x > y), + (S.NaN, x <= y), + evaluate=False) + assert piecewise_exclusive(Piecewise((1, x > y)), + skip_nan=True) == Piecewise((1, x > y)) + + xr, yr = symbols('xr, yr', real=True) + + p1 = Piecewise((1, xr < 0), (2, True), evaluate=False) + p1x = Piecewise((1, xr < 0), (2, xr >= 0), evaluate=False) + + p2 = Piecewise((p1, yr < 0), (3, True), evaluate=False) + p2x = Piecewise((p1, yr < 0), (3, yr >= 0), evaluate=False) + p2xx = Piecewise((p1x, yr < 0), (3, yr >= 0), evaluate=False) + + assert piecewise_exclusive(p2) == p2xx + assert piecewise_exclusive(p2, deep=False) == p2x + + +def test_piecewise_collapse(): + assert Piecewise((x, True)) == x + a = x < 1 + assert Piecewise((x, a), (x + 1, a)) == Piecewise((x, a)) + assert Piecewise((x, a), (x + 1, a.reversed)) == Piecewise((x, a)) + b = x < 5 + def canonical(i): + if isinstance(i, Piecewise): + return Piecewise(*i.args) + return i + for args in [ + ((1, a), (Piecewise((2, a), (3, b)), b)), + ((1, a), (Piecewise((2, a), (3, b.reversed)), b)), + ((1, a), (Piecewise((2, a), (3, b)), b), (4, True)), + ((1, a), (Piecewise((2, a), (3, b), (4, True)), b)), + ((1, a), (Piecewise((2, a), (3, b), (4, True)), b), (5, True))]: + for i in (0, 2, 10): + assert canonical( + Piecewise(*args, evaluate=False).subs(x, i) + ) == canonical(Piecewise(*args).subs(x, i)) + r1, r2, r3, r4 = symbols('r1:5') + a = x < r1 + b = x < r2 + c = x < r3 + d = x < r4 + assert Piecewise((1, a), (Piecewise( + (2, a), (3, b), (4, c)), b), (5, c) + ) == Piecewise((1, a), (3, b), (5, c)) + assert Piecewise((1, a), (Piecewise( + (2, a), (3, b), (4, c), (6, True)), c), (5, d) + ) == Piecewise((1, a), (Piecewise( + (3, b), (4, c)), c), (5, d)) + assert Piecewise((1, Or(a, d)), (Piecewise( + (2, d), (3, b), (4, c)), b), (5, c) + ) == Piecewise((1, Or(a, d)), (Piecewise( + (2, d), (3, b)), b), (5, c)) + assert Piecewise((1, c), (2, ~c), (3, S.true) + ) == Piecewise((1, c), (2, S.true)) + assert Piecewise((1, c), (2, And(~c, b)), (3,True) + ) == Piecewise((1, c), (2, b), (3, True)) + assert Piecewise((1, c), (2, Or(~c, b)), (3,True) + ).subs(dict(zip((r1, r2, r3, r4, x), (1, 2, 3, 4, 3.5)))) == 2 + assert Piecewise((1, c), (2, ~c)) == Piecewise((1, c), (2, True)) + + +def test_piecewise_lambdify(): + p = Piecewise( + (x**2, x < 0), + (x, Interval(0, 1, False, True).contains(x)), + (2 - x, x >= 1), + (0, True) + ) + + f = lambdify(x, p) + assert f(-2.0) == 4.0 + assert f(0.0) == 0.0 + assert f(0.5) == 0.5 + assert f(2.0) == 0.0 + + +def test_piecewise_series(): + from sympy.series.order import O + p1 = Piecewise((sin(x), x < 0), (cos(x), x > 0)) + p2 = Piecewise((x + O(x**2), x < 0), (1 + O(x**2), x > 0)) + assert p1.nseries(x, n=2) == p2 + + +def test_piecewise_as_leading_term(): + p1 = Piecewise((1/x, x > 1), (0, True)) + p2 = Piecewise((x, x > 1), (0, True)) + p3 = Piecewise((1/x, x > 1), (x, True)) + p4 = Piecewise((x, x > 1), (1/x, True)) + p5 = Piecewise((1/x, x > 1), (x, True)) + p6 = Piecewise((1/x, x < 1), (x, True)) + p7 = Piecewise((x, x < 1), (1/x, True)) + p8 = Piecewise((x, x > 1), (1/x, True)) + assert p1.as_leading_term(x) == 0 + assert p2.as_leading_term(x) == 0 + assert p3.as_leading_term(x) == x + assert p4.as_leading_term(x) == 1/x + assert p5.as_leading_term(x) == x + assert p6.as_leading_term(x) == 1/x + assert p7.as_leading_term(x) == x + assert p8.as_leading_term(x) == 1/x + + +def test_piecewise_complex(): + p1 = Piecewise((2, x < 0), (1, 0 <= x)) + p2 = Piecewise((2*I, x < 0), (I, 0 <= x)) + p3 = Piecewise((I*x, x > 1), (1 + I, True)) + p4 = Piecewise((-I*conjugate(x), x > 1), (1 - I, True)) + + assert conjugate(p1) == p1 + assert conjugate(p2) == piecewise_fold(-p2) + assert conjugate(p3) == p4 + + assert p1.is_imaginary is False + assert p1.is_real is True + assert p2.is_imaginary is True + assert p2.is_real is False + assert p3.is_imaginary is None + assert p3.is_real is None + + assert p1.as_real_imag() == (p1, 0) + assert p2.as_real_imag() == (0, -I*p2) + + +def test_conjugate_transpose(): + A, B = symbols("A B", commutative=False) + p = Piecewise((A*B**2, x > 0), (A**2*B, True)) + assert p.adjoint() == \ + Piecewise((adjoint(A*B**2), x > 0), (adjoint(A**2*B), True)) + assert p.conjugate() == \ + Piecewise((conjugate(A*B**2), x > 0), (conjugate(A**2*B), True)) + assert p.transpose() == \ + Piecewise((transpose(A*B**2), x > 0), (transpose(A**2*B), True)) + + +def test_piecewise_evaluate(): + assert Piecewise((x, True)) == x + assert Piecewise((x, True), evaluate=True) == x + assert Piecewise((1, Eq(1, x))).args == ((1, Eq(x, 1)),) + assert Piecewise((1, Eq(1, x)), evaluate=False).args == ( + (1, Eq(1, x)),) + # like the additive and multiplicative identities that + # cannot be kept in Add/Mul, we also do not keep a single True + p = Piecewise((x, True), evaluate=False) + assert p == x + + +def test_as_expr_set_pairs(): + assert Piecewise((x, x > 0), (-x, x <= 0)).as_expr_set_pairs() == \ + [(x, Interval(0, oo, True, True)), (-x, Interval(-oo, 0))] + + assert Piecewise(((x - 2)**2, x >= 0), (0, True)).as_expr_set_pairs() == \ + [((x - 2)**2, Interval(0, oo)), (0, Interval(-oo, 0, True, True))] + + +def test_S_srepr_is_identity(): + p = Piecewise((10, Eq(x, 0)), (12, True)) + q = S(srepr(p)) + assert p == q + + +def test_issue_12587(): + # sort holes into intervals + p = Piecewise((1, x > 4), (2, Not((x <= 3) & (x > -1))), (3, True)) + assert p.integrate((x, -5, 5)) == 23 + p = Piecewise((1, x > 1), (2, x < y), (3, True)) + lim = x, -3, 3 + ans = p.integrate(lim) + for i in range(-1, 3): + assert ans.subs(y, i) == p.subs(y, i).integrate(lim) + + +def test_issue_11045(): + assert integrate(1/(x*sqrt(x**2 - 1)), (x, 1, 2)) == pi/3 + + # handle And with Or arguments + assert Piecewise((1, And(Or(x < 1, x > 3), x < 2)), (0, True) + ).integrate((x, 0, 3)) == 1 + + # hidden false + assert Piecewise((1, x > 1), (2, x > x + 1), (3, True) + ).integrate((x, 0, 3)) == 5 + # targetcond is Eq + assert Piecewise((1, x > 1), (2, Eq(1, x)), (3, True) + ).integrate((x, 0, 4)) == 6 + # And has Relational needing to be solved + assert Piecewise((1, And(2*x > x + 1, x < 2)), (0, True) + ).integrate((x, 0, 3)) == 1 + # Or has Relational needing to be solved + assert Piecewise((1, Or(2*x > x + 2, x < 1)), (0, True) + ).integrate((x, 0, 3)) == 2 + # ignore hidden false (handled in canonicalization) + assert Piecewise((1, x > 1), (2, x > x + 1), (3, True) + ).integrate((x, 0, 3)) == 5 + # watch for hidden True Piecewise + assert Piecewise((2, Eq(1 - x, x*(1/x - 1))), (0, True) + ).integrate((x, 0, 3)) == 6 + + # overlapping conditions of targetcond are recognized and ignored; + # the condition x > 3 will be pre-empted by the first condition + assert Piecewise((1, Or(x < 1, x > 2)), (2, x > 3), (3, True) + ).integrate((x, 0, 4)) == 6 + + # convert Ne to Or + assert Piecewise((1, Ne(x, 0)), (2, True) + ).integrate((x, -1, 1)) == 2 + + # no default but well defined + assert Piecewise((x, (x > 1) & (x < 3)), (1, (x < 4)) + ).integrate((x, 1, 4)) == 5 + + p = Piecewise((x, (x > 1) & (x < 3)), (1, (x < 4))) + nan = Undefined + i = p.integrate((x, 1, y)) + assert i == Piecewise( + (y - 1, y < 1), + (Min(3, y)**2/2 - Min(3, y) + Min(4, y) - S.Half, + y <= Min(4, y)), + (nan, True)) + assert p.integrate((x, 1, -1)) == i.subs(y, -1) + assert p.integrate((x, 1, 4)) == 5 + assert p.integrate((x, 1, 5)) is nan + + # handle Not + p = Piecewise((1, x > 1), (2, Not(And(x > 1, x< 3))), (3, True)) + assert p.integrate((x, 0, 3)) == 4 + + # handle updating of int_expr when there is overlap + p = Piecewise( + (1, And(5 > x, x > 1)), + (2, Or(x < 3, x > 7)), + (4, x < 8)) + assert p.integrate((x, 0, 10)) == 20 + + # And with Eq arg handling + assert Piecewise((1, x < 1), (2, And(Eq(x, 3), x > 1)) + ).integrate((x, 0, 3)) is S.NaN + assert Piecewise((1, x < 1), (2, And(Eq(x, 3), x > 1)), (3, True) + ).integrate((x, 0, 3)) == 7 + assert Piecewise((1, x < 0), (2, And(Eq(x, 3), x < 1)), (3, True) + ).integrate((x, -1, 1)) == 4 + # middle condition doesn't matter: it's a zero width interval + assert Piecewise((1, x < 1), (2, Eq(x, 3) & (y < x)), (3, True) + ).integrate((x, 0, 3)) == 7 + + +def test_holes(): + nan = Undefined + assert Piecewise((1, x < 2)).integrate(x) == Piecewise( + (x, x < 2), (nan, True)) + assert Piecewise((1, And(x > 1, x < 2))).integrate(x) == Piecewise( + (nan, x < 1), (x, x < 2), (nan, True)) + assert Piecewise((1, And(x > 1, x < 2))).integrate((x, 0, 3)) is nan + assert Piecewise((1, And(x > 0, x < 4))).integrate((x, 1, 3)) == 2 + + # this also tests that the integrate method is used on non-Piecwise + # arguments in _eval_integral + A, B = symbols("A B") + a, b = symbols('a b', real=True) + assert Piecewise((A, And(x < 0, a < 1)), (B, Or(x < 1, a > 2)) + ).integrate(x) == Piecewise( + (B*x, (a > 2)), + (Piecewise((A*x, x < 0), (B*x, x < 1), (nan, True)), a < 1), + (Piecewise((B*x, x < 1), (nan, True)), True)) + + +def test_issue_11922(): + def f(x): + return Piecewise((0, x < -1), (1 - x**2, x < 1), (0, True)) + autocorr = lambda k: ( + f(x) * f(x + k)).integrate((x, -1, 1)) + assert autocorr(1.9) > 0 + k = symbols('k') + good_autocorr = lambda k: ( + (1 - x**2) * f(x + k)).integrate((x, -1, 1)) + a = good_autocorr(k) + assert a.subs(k, 3) == 0 + k = symbols('k', positive=True) + a = good_autocorr(k) + assert a.subs(k, 3) == 0 + assert Piecewise((0, x < 1), (10, (x >= 1)) + ).integrate() == Piecewise((0, x < 1), (10*x - 10, True)) + + +def test_issue_5227(): + f = 0.0032513612725229*Piecewise((0, x < -80.8461538461539), + (-0.0160799238820171*x + 1.33215984776403, x < 2), + (Piecewise((0.3, x > 123), (0.7, True)) + + Piecewise((0.4, x > 2), (0.6, True)), x <= + 123), (-0.00817409766454352*x + 2.10541401273885, x < + 380.571428571429), (0, True)) + i = integrate(f, (x, -oo, oo)) + assert i == Integral(f, (x, -oo, oo)).doit() + assert str(i) == '1.00195081676351' + assert Piecewise((1, x - y < 0), (0, True) + ).integrate(y) == Piecewise((0, y <= x), (-x + y, True)) + + +def test_issue_10137(): + a = Symbol('a', real=True) + b = Symbol('b', real=True) + x = Symbol('x', real=True) + y = Symbol('y', real=True) + p0 = Piecewise((0, Or(x < a, x > b)), (1, True)) + p1 = Piecewise((0, Or(a > x, b < x)), (1, True)) + assert integrate(p0, (x, y, oo)) == integrate(p1, (x, y, oo)) + p3 = Piecewise((1, And(0 < x, x < a)), (0, True)) + p4 = Piecewise((1, And(a > x, x > 0)), (0, True)) + ip3 = integrate(p3, x) + assert ip3 == Piecewise( + (0, x <= 0), + (x, x <= Max(0, a)), + (Max(0, a), True)) + ip4 = integrate(p4, x) + assert ip4 == ip3 + assert p3.integrate((x, 2, 4)) == Min(4, Max(2, a)) - 2 + assert p4.integrate((x, 2, 4)) == Min(4, Max(2, a)) - 2 + + +def test_stackoverflow_43852159(): + f = lambda x: Piecewise((1, (x >= -1) & (x <= 1)), (0, True)) + Conv = lambda x: integrate(f(x - y)*f(y), (y, -oo, +oo)) + cx = Conv(x) + assert cx.subs(x, -1.5) == cx.subs(x, 1.5) + assert cx.subs(x, 3) == 0 + assert piecewise_fold(f(x - y)*f(y)) == Piecewise( + (1, (y >= -1) & (y <= 1) & (x - y >= -1) & (x - y <= 1)), + (0, True)) + + +def test_issue_12557(): + ''' + # 3200 seconds to compute the fourier part of issue + import sympy as sym + x,y,z,t = sym.symbols('x y z t') + k = sym.symbols("k", integer=True) + fourier = sym.fourier_series(sym.cos(k*x)*sym.sqrt(x**2), + (x, -sym.pi, sym.pi)) + assert fourier == FourierSeries( + sqrt(x**2)*cos(k*x), (x, -pi, pi), (Piecewise((pi**2, + Eq(k, 0)), (2*(-1)**k/k**2 - 2/k**2, True))/(2*pi), + SeqFormula(Piecewise((pi**2, (Eq(_n, 0) & Eq(k, 0)) | (Eq(_n, 0) & + Eq(_n, k) & Eq(k, 0)) | (Eq(_n, 0) & Eq(k, 0) & Eq(_n, -k)) | (Eq(_n, + 0) & Eq(_n, k) & Eq(k, 0) & Eq(_n, -k))), (pi**2/2, Eq(_n, k) | Eq(_n, + -k) | (Eq(_n, 0) & Eq(_n, k)) | (Eq(_n, k) & Eq(k, 0)) | (Eq(_n, 0) & + Eq(_n, -k)) | (Eq(_n, k) & Eq(_n, -k)) | (Eq(k, 0) & Eq(_n, -k)) | + (Eq(_n, 0) & Eq(_n, k) & Eq(_n, -k)) | (Eq(_n, k) & Eq(k, 0) & Eq(_n, + -k))), ((-1)**k*pi**2*_n**3*sin(pi*_n)/(pi*_n**4 - 2*pi*_n**2*k**2 + + pi*k**4) - (-1)**k*pi**2*_n**3*sin(pi*_n)/(-pi*_n**4 + 2*pi*_n**2*k**2 + - pi*k**4) + (-1)**k*pi*_n**2*cos(pi*_n)/(pi*_n**4 - 2*pi*_n**2*k**2 + + pi*k**4) - (-1)**k*pi*_n**2*cos(pi*_n)/(-pi*_n**4 + 2*pi*_n**2*k**2 - + pi*k**4) - (-1)**k*pi**2*_n*k**2*sin(pi*_n)/(pi*_n**4 - + 2*pi*_n**2*k**2 + pi*k**4) + + (-1)**k*pi**2*_n*k**2*sin(pi*_n)/(-pi*_n**4 + 2*pi*_n**2*k**2 - + pi*k**4) + (-1)**k*pi*k**2*cos(pi*_n)/(pi*_n**4 - 2*pi*_n**2*k**2 + + pi*k**4) - (-1)**k*pi*k**2*cos(pi*_n)/(-pi*_n**4 + 2*pi*_n**2*k**2 - + pi*k**4) - (2*_n**2 + 2*k**2)/(_n**4 - 2*_n**2*k**2 + k**4), + True))*cos(_n*x)/pi, (_n, 1, oo)), SeqFormula(0, (_k, 1, oo)))) + ''' + x = symbols("x", real=True) + k = symbols('k', integer=True, finite=True) + abs2 = lambda x: Piecewise((-x, x <= 0), (x, x > 0)) + assert integrate(abs2(x), (x, -pi, pi)) == pi**2 + func = cos(k*x)*sqrt(x**2) + assert integrate(func, (x, -pi, pi)) == Piecewise( + (2*(-1)**k/k**2 - 2/k**2, Ne(k, 0)), (pi**2, True)) + +def test_issue_6900(): + from itertools import permutations + t0, t1, T, t = symbols('t0, t1 T t') + f = Piecewise((0, t < t0), (x, And(t0 <= t, t < t1)), (0, t >= t1)) + g = f.integrate(t) + assert g == Piecewise( + (0, t <= t0), + (t*x - t0*x, t <= Max(t0, t1)), + (-t0*x + x*Max(t0, t1), True)) + for i in permutations(range(2)): + reps = dict(zip((t0,t1), i)) + for tt in range(-1,3): + assert (g.xreplace(reps).subs(t,tt) == + f.xreplace(reps).integrate(t).subs(t,tt)) + lim = Tuple(t, t0, T) + g = f.integrate(lim) + ans = Piecewise( + (-t0*x + x*Min(T, Max(t0, t1)), T > t0), + (0, True)) + for i in permutations(range(3)): + reps = dict(zip((t0,t1,T), i)) + tru = f.xreplace(reps).integrate(lim.xreplace(reps)) + assert tru == ans.xreplace(reps) + assert g == ans + + +def test_issue_10122(): + assert solve(abs(x) + abs(x - 1) - 1 > 0, x + ) == Or(And(-oo < x, x < S.Zero), And(S.One < x, x < oo)) + + +def test_issue_4313(): + u = Piecewise((0, x <= 0), (1, x >= a), (x/a, True)) + e = (u - u.subs(x, y))**2/(x - y)**2 + M = Max(0, a) + assert integrate(e, x).expand() == Piecewise( + (Piecewise( + (0, x <= 0), + (-y**2/(a**2*x - a**2*y) + x/a**2 - 2*y*log(-y)/a**2 + + 2*y*log(x - y)/a**2 - y/a**2, x <= M), + (-y**2/(-a**2*y + a**2*M) + 1/(-y + M) - + 1/(x - y) - 2*y*log(-y)/a**2 + 2*y*log(-y + + M)/a**2 - y/a**2 + M/a**2, True)), + ((a <= y) & (y <= 0)) | ((y <= 0) & (y > -oo))), + (Piecewise( + (-1/(x - y), x <= 0), + (-a**2/(a**2*x - a**2*y) + 2*a*y/(a**2*x - a**2*y) - + y**2/(a**2*x - a**2*y) + 2*log(-y)/a - 2*log(x - y)/a + + 2/a + x/a**2 - 2*y*log(-y)/a**2 + 2*y*log(x - y)/a**2 - + y/a**2, x <= M), + (-a**2/(-a**2*y + a**2*M) + 2*a*y/(-a**2*y + + a**2*M) - y**2/(-a**2*y + a**2*M) + + 2*log(-y)/a - 2*log(-y + M)/a + 2/a - + 2*y*log(-y)/a**2 + 2*y*log(-y + M)/a**2 - + y/a**2 + M/a**2, True)), + a <= y), + (Piecewise( + (-y**2/(a**2*x - a**2*y), x <= 0), + (x/a**2 + y/a**2, x <= M), + (a**2/(-a**2*y + a**2*M) - + a**2/(a**2*x - a**2*y) - 2*a*y/(-a**2*y + a**2*M) + + 2*a*y/(a**2*x - a**2*y) + y**2/(-a**2*y + a**2*M) - + y**2/(a**2*x - a**2*y) + y/a**2 + M/a**2, True)), + True)) + + +def test__intervals(): + assert Piecewise((x + 2, Eq(x, 3)))._intervals(x) == (True, []) + assert Piecewise( + (1, x > x + 1), + (Piecewise((1, x < x + 1)), 2*x < 2*x + 1), + (1, True))._intervals(x) == (True, [(-oo, oo, 1, 1)]) + assert Piecewise((1, Ne(x, I)), (0, True))._intervals(x) == (True, + [(-oo, oo, 1, 0)]) + assert Piecewise((-cos(x), sin(x) >= 0), (cos(x), True) + )._intervals(x) == (True, + [(0, pi, -cos(x), 0), (-oo, oo, cos(x), 1)]) + # the following tests that duplicates are removed and that non-Eq + # generated zero-width intervals are removed + assert Piecewise((1, Abs(x**(-2)) > 1), (0, True) + )._intervals(x) == (True, + [(-1, 0, 1, 0), (0, 1, 1, 0), (-oo, oo, 0, 1)]) + + +def test_containment(): + a, b, c, d, e = [1, 2, 3, 4, 5] + p = (Piecewise((d, x > 1), (e, True))* + Piecewise((a, Abs(x - 1) < 1), (b, Abs(x - 2) < 2), (c, True))) + assert p.integrate(x).diff(x) == Piecewise( + (c*e, x <= 0), + (a*e, x <= 1), + (a*d, x < 2), # this is what we want to get right + (b*d, x < 4), + (c*d, True)) + + +def test_piecewise_with_DiracDelta(): + d1 = DiracDelta(x - 1) + assert integrate(d1, (x, -oo, oo)) == 1 + assert integrate(d1, (x, 0, 2)) == 1 + assert Piecewise((d1, Eq(x, 2)), (0, True)).integrate(x) == 0 + assert Piecewise((d1, x < 2), (0, True)).integrate(x) == Piecewise( + (Heaviside(x - 1), x < 2), (1, True)) + # TODO raise error if function is discontinuous at limit of + # integration, e.g. integrate(d1, (x, -2, 1)) or Piecewise( + # (d1, Eq(x, 1) + + +def test_issue_10258(): + assert Piecewise((0, x < 1), (1, True)).is_zero is None + assert Piecewise((-1, x < 1), (1, True)).is_zero is False + a = Symbol('a', zero=True) + assert Piecewise((0, x < 1), (a, True)).is_zero + assert Piecewise((1, x < 1), (a, x < 3)).is_zero is None + a = Symbol('a') + assert Piecewise((0, x < 1), (a, True)).is_zero is None + assert Piecewise((0, x < 1), (1, True)).is_nonzero is None + assert Piecewise((1, x < 1), (2, True)).is_nonzero + assert Piecewise((0, x < 1), (oo, True)).is_finite is None + assert Piecewise((0, x < 1), (1, True)).is_finite + b = Basic() + assert Piecewise((b, x < 1)).is_finite is None + + # 10258 + c = Piecewise((1, x < 0), (2, True)) < 3 + assert c != True + assert piecewise_fold(c) == True + + +def test_issue_10087(): + a, b = Piecewise((x, x > 1), (2, True)), Piecewise((x, x > 3), (3, True)) + m = a*b + f = piecewise_fold(m) + for i in (0, 2, 4): + assert m.subs(x, i) == f.subs(x, i) + m = a + b + f = piecewise_fold(m) + for i in (0, 2, 4): + assert m.subs(x, i) == f.subs(x, i) + + +def test_issue_8919(): + c = symbols('c:5') + x = symbols("x") + f1 = Piecewise((c[1], x < 1), (c[2], True)) + f2 = Piecewise((c[3], x < Rational(1, 3)), (c[4], True)) + assert integrate(f1*f2, (x, 0, 2) + ) == c[1]*c[3]/3 + 2*c[1]*c[4]/3 + c[2]*c[4] + f1 = Piecewise((0, x < 1), (2, True)) + f2 = Piecewise((3, x < 2), (0, True)) + assert integrate(f1*f2, (x, 0, 3)) == 6 + + y = symbols("y", positive=True) + a, b, c, x, z = symbols("a,b,c,x,z", real=True) + I = Integral(Piecewise( + (0, (x >= y) | (x < 0) | (b > c)), + (a, True)), (x, 0, z)) + ans = I.doit() + assert ans == Piecewise((0, b > c), (a*Min(y, z) - a*Min(0, z), True)) + for cond in (True, False): + for yy in range(1, 3): + for zz in range(-yy, 0, yy): + reps = [(b > c, cond), (y, yy), (z, zz)] + assert ans.subs(reps) == I.subs(reps).doit() + + +def test_unevaluated_integrals(): + f = Function('f') + p = Piecewise((1, Eq(f(x) - 1, 0)), (2, x - 10 < 0), (0, True)) + assert p.integrate(x) == Integral(p, x) + assert p.integrate((x, 0, 5)) == Integral(p, (x, 0, 5)) + # test it by replacing f(x) with x%2 which will not + # affect the answer: the integrand is essentially 2 over + # the domain of integration + assert Integral(p, (x, 0, 5)).subs(f(x), x%2).n() == 10.0 + + # this is a test of using _solve_inequality when + # solve_univariate_inequality fails + assert p.integrate(y) == Piecewise( + (y, Eq(f(x), 1) | ((x < 10) & Eq(f(x), 1))), + (2*y, (x > -oo) & (x < 10)), (0, True)) + + +def test_conditions_as_alternate_booleans(): + a, b, c = symbols('a:c') + assert Piecewise((x, Piecewise((y < 1, x > 0), (y > 1, True))) + ) == Piecewise((x, ITE(x > 0, y < 1, y > 1))) + + +def test_Piecewise_rewrite_as_ITE(): + a, b, c, d = symbols('a:d') + + def _ITE(*args): + return Piecewise(*args).rewrite(ITE) + + assert _ITE((a, x < 1), (b, x >= 1)) == ITE(x < 1, a, b) + assert _ITE((a, x < 1), (b, x < oo)) == ITE(x < 1, a, b) + assert _ITE((a, x < 1), (b, Or(y < 1, x < oo)), (c, y > 0) + ) == ITE(x < 1, a, b) + assert _ITE((a, x < 1), (b, True)) == ITE(x < 1, a, b) + assert _ITE((a, x < 1), (b, x < 2), (c, True) + ) == ITE(x < 1, a, ITE(x < 2, b, c)) + assert _ITE((a, x < 1), (b, y < 2), (c, True) + ) == ITE(x < 1, a, ITE(y < 2, b, c)) + assert _ITE((a, x < 1), (b, x < oo), (c, y < 1) + ) == ITE(x < 1, a, b) + assert _ITE((a, x < 1), (c, y < 1), (b, x < oo), (d, True) + ) == ITE(x < 1, a, ITE(y < 1, c, b)) + assert _ITE((a, x < 0), (b, Or(x < oo, y < 1)) + ) == ITE(x < 0, a, b) + raises(TypeError, lambda: _ITE((x + 1, x < 1), (x, True))) + # if `a` in the following were replaced with y then the coverage + # is complete but something other than as_set would need to be + # used to detect this + raises(NotImplementedError, lambda: _ITE((x, x < y), (y, x >= a))) + raises(ValueError, lambda: _ITE((a, x < 2), (b, x > 3))) + + +def test_Piecewise_replace_relational_27538(): + x, y = symbols('x, y') + p1 = Piecewise( + (0, Eq(x, True)), + (1, True), + ) + p2 = p1.xreplace({x: y < 1}) + assert p2.subs(y, 0) == 0 + assert p2.subs(y, 1) == 1 + + +def test_issue_14052(): + assert integrate(abs(sin(x)), (x, 0, 2*pi)) == 4 + + +def test_issue_14240(): + assert piecewise_fold( + Piecewise((1, a), (2, b), (4, True)) + + Piecewise((8, a), (16, True)) + ) == Piecewise((9, a), (18, b), (20, True)) + assert piecewise_fold( + Piecewise((2, a), (3, b), (5, True)) * + Piecewise((7, a), (11, True)) + ) == Piecewise((14, a), (33, b), (55, True)) + # these will hang if naive folding is used + assert piecewise_fold(Add(*[ + Piecewise((i, a), (0, True)) for i in range(40)]) + ) == Piecewise((780, a), (0, True)) + assert piecewise_fold(Mul(*[ + Piecewise((i, a), (0, True)) for i in range(1, 41)]) + ) == Piecewise((factorial(40), a), (0, True)) + + +def test_issue_14787(): + x = Symbol('x') + f = Piecewise((x, x < 1), ((S(58) / 7), True)) + assert str(f.evalf()) == "Piecewise((x, x < 1), (8.28571428571429, True))" + +def test_issue_21481(): + b, e = symbols('b e') + C = Piecewise( + (2, + ((b > 1) & (e > 0)) | + ((b > 0) & (b < 1) & (e < 0)) | + ((e >= 2) & (b < -1) & Eq(Mod(e, 2), 0)) | + ((e <= -2) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))), + (S.Half, + ((b > 1) & (e < 0)) | + ((b > 0) & (e > 0) & (b < 1)) | + ((e <= -2) & (b < -1) & Eq(Mod(e, 2), 0)) | + ((e >= 2) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 0))), + (-S.Half, + Eq(Mod(e, 2), 1) & + (((e <= -1) & (b < -1)) | ((e >= 1) & (b > -1) & (b < 0)))), + (-2, + ((e >= 1) & (b < -1) & Eq(Mod(e, 2), 1)) | + ((e <= -1) & (b > -1) & (b < 0) & Eq(Mod(e, 2), 1))) + ) + A = Piecewise( + (1, Eq(b, 1) | Eq(e, 0) | (Eq(b, -1) & Eq(Mod(e, 2), 0))), + (0, Eq(b, 0) & (e > 0)), + (-1, Eq(b, -1) & Eq(Mod(e, 2), 1)), + (C, Eq(im(b), 0) & Eq(im(e), 0)) + ) + + B = piecewise_fold(A) + sa = A.simplify() + sb = B.simplify() + v = (-2, -1, -S.Half, 0, S.Half, 1, 2) + for i in v: + for j in v: + r = {b:i, e:j} + ok = [k.xreplace(r) for k in (A, B, sa, sb)] + assert len(set(ok)) == 1 + + +def test_issue_8458(): + x, y = symbols('x y') + # Original issue + p1 = Piecewise((0, Eq(x, 0)), (sin(x), True)) + assert p1.simplify() == sin(x) + # Slightly larger variant + p2 = Piecewise((x, Eq(x, 0)), (4*x + (y-2)**4, Eq(x, 0) & Eq(x+y, 2)), (sin(x), True)) + assert p2.simplify() == sin(x) + # Test for problem highlighted during review + p3 = Piecewise((x+1, Eq(x, -1)), (4*x + (y-2)**4, Eq(x, 0) & Eq(x+y, 2)), (sin(x), True)) + assert p3.simplify() == Piecewise((0, Eq(x, -1)), (sin(x), True)) + + +def test_issue_16417(): + z = Symbol('z') + assert unchanged(Piecewise, (1, Or(Eq(im(z), 0), Gt(re(z), 0))), (2, True)) + + x = Symbol('x') + assert unchanged(Piecewise, (S.Pi, re(x) < 0), + (0, Or(re(x) > 0, Ne(im(x), 0))), + (S.NaN, True)) + r = Symbol('r', real=True) + p = Piecewise((S.Pi, re(r) < 0), + (0, Or(re(r) > 0, Ne(im(r), 0))), + (S.NaN, True)) + assert p == Piecewise((S.Pi, r < 0), + (0, r > 0), + (S.NaN, True), evaluate=False) + # Does not work since imaginary != 0... + #i = Symbol('i', imaginary=True) + #p = Piecewise((S.Pi, re(i) < 0), + # (0, Or(re(i) > 0, Ne(im(i), 0))), + # (S.NaN, True)) + #assert p == Piecewise((0, Ne(im(i), 0)), + # (S.NaN, True), evaluate=False) + i = I*r + p = Piecewise((S.Pi, re(i) < 0), + (0, Or(re(i) > 0, Ne(im(i), 0))), + (S.NaN, True)) + assert p == Piecewise((0, Ne(im(i), 0)), + (S.NaN, True), evaluate=False) + assert p == Piecewise((0, Ne(r, 0)), + (S.NaN, True), evaluate=False) + + +def test_eval_rewrite_as_KroneckerDelta(): + x, y, z, n, t, m = symbols('x y z n t m') + K = KroneckerDelta + f = lambda p: expand(p.rewrite(K)) + + p1 = Piecewise((0, Eq(x, y)), (1, True)) + assert f(p1) == 1 - K(x, y) + + p2 = Piecewise((x, Eq(y,0)), (z, Eq(t,0)), (n, True)) + assert f(p2) == n*K(0, t)*K(0, y) - n*K(0, t) - n*K(0, y) + n + \ + x*K(0, y) - z*K(0, t)*K(0, y) + z*K(0, t) + + p3 = Piecewise((1, Ne(x, y)), (0, True)) + assert f(p3) == 1 - K(x, y) + + p4 = Piecewise((1, Eq(x, 3)), (4, True), (5, True)) + assert f(p4) == 4 - 3*K(3, x) + + p5 = Piecewise((3, Ne(x, 2)), (4, Eq(y, 2)), (5, True)) + assert f(p5) == -K(2, x)*K(2, y) + 2*K(2, x) + 3 + + p6 = Piecewise((0, Ne(x, 1) & Ne(y, 4)), (1, True)) + assert f(p6) == -K(1, x)*K(4, y) + K(1, x) + K(4, y) + + p7 = Piecewise((2, Eq(y, 3) & Ne(x, 2)), (1, True)) + assert f(p7) == -K(2, x)*K(3, y) + K(3, y) + 1 + + p8 = Piecewise((4, Eq(x, 3) & Ne(y, 2)), (1, True)) + assert f(p8) == -3*K(2, y)*K(3, x) + 3*K(3, x) + 1 + + p9 = Piecewise((6, Eq(x, 4) & Eq(y, 1)), (1, True)) + assert f(p9) == 5 * K(1, y) * K(4, x) + 1 + + p10 = Piecewise((4, Ne(x, -4) | Ne(y, 1)), (1, True)) + assert f(p10) == -3 * K(-4, x) * K(1, y) + 4 + + p11 = Piecewise((1, Eq(y, 2) | Ne(x, -3)), (2, True)) + assert f(p11) == -K(-3, x)*K(2, y) + K(-3, x) + 1 + + p12 = Piecewise((-1, Eq(x, 1) | Ne(y, 3)), (1, True)) + assert f(p12) == -2*K(1, x)*K(3, y) + 2*K(3, y) - 1 + + p13 = Piecewise((3, Eq(x, 2) | Eq(y, 4)), (1, True)) + assert f(p13) == -2*K(2, x)*K(4, y) + 2*K(2, x) + 2*K(4, y) + 1 + + p14 = Piecewise((1, Ne(x, 0) | Ne(y, 1)), (3, True)) + assert f(p14) == 2 * K(0, x) * K(1, y) + 1 + + p15 = Piecewise((2, Eq(x, 3) | Ne(y, 2)), (3, Eq(x, 4) & Eq(y, 5)), (1, True)) + assert f(p15) == -2*K(2, y)*K(3, x)*K(4, x)*K(5, y) + K(2, y)*K(3, x) + \ + 2*K(2, y)*K(4, x)*K(5, y) - K(2, y) + 2 + + p16 = Piecewise((0, Ne(m, n)), (1, True))*Piecewise((0, Ne(n, t)), (1, True))\ + *Piecewise((0, Ne(n, x)), (1, True)) - Piecewise((0, Ne(t, x)), (1, True)) + assert f(p16) == K(m, n)*K(n, t)*K(n, x) - K(t, x) + + p17 = Piecewise((0, Ne(t, x) & (Ne(m, n) | Ne(n, t) | Ne(n, x))), + (1, Ne(t, x)), (-1, Ne(m, n) | Ne(n, t) | Ne(n, x)), (0, True)) + assert f(p17) == K(m, n)*K(n, t)*K(n, x) - K(t, x) + + p18 = Piecewise((-4, Eq(y, 1) | (Eq(x, -5) & Eq(x, z))), (4, True)) + assert f(p18) == 8*K(-5, x)*K(1, y)*K(x, z) - 8*K(-5, x)*K(x, z) - 8*K(1, y) + 4 + + p19 = Piecewise((0, x > 2), (1, True)) + assert f(p19) == p19 + + p20 = Piecewise((0, And(x < 2, x > -5)), (1, True)) + assert f(p20) == p20 + + p21 = Piecewise((0, Or(x > 1, x < 0)), (1, True)) + assert f(p21) == p21 + + p22 = Piecewise((0, ~((Eq(y, -1) | Ne(x, 0)) & (Ne(x, 1) | Ne(y, -1)))), (1, True)) + assert f(p22) == K(-1, y)*K(0, x) - K(-1, y)*K(1, x) - K(0, x) + 1 + + +@slow +def test_identical_conds_issue(): + from sympy.stats import Uniform, density + u1 = Uniform('u1', 0, 1) + u2 = Uniform('u2', 0, 1) + # Result is quite big, so not really important here (and should ideally be + # simpler). Should not give an exception though. + density(u1 + u2) + + +def test_issue_7370(): + f = Piecewise((1, x <= 2400)) + v = integrate(f, (x, 0, Float("252.4", 30))) + assert str(v) == '252.400000000000000000000000000' + + +def test_issue_14933(): + x = Symbol('x') + y = Symbol('y') + + inp = MatrixSymbol('inp', 1, 1) + rep_dict = {y: inp[0, 0], x: inp[0, 0]} + + p = Piecewise((1, ITE(y > 0, x < 0, True))) + assert p.xreplace(rep_dict) == Piecewise((1, ITE(inp[0, 0] > 0, inp[0, 0] < 0, True))) + + +def test_issue_16715(): + raises(NotImplementedError, lambda: Piecewise((x, x<0), (0, y>1)).as_expr_set_pairs()) + + +def test_issue_20360(): + t, tau = symbols("t tau", real=True) + n = symbols("n", integer=True) + lam = pi * (n - S.Half) + eq = integrate(exp(lam * tau), (tau, 0, t)) + assert eq.simplify() == (2*exp(pi*t*(2*n - 1)/2) - 2)/(pi*(2*n - 1)) + + +def test_piecewise_eval(): + # XXX these tests might need modification if this + # simplification is moved out of eval and into + # boolalg or Piecewise simplification functions + f = lambda x: x.args[0].cond + # unsimplified + assert f(Piecewise((x, (x > -oo) & (x < 3))) + ) == ((x > -oo) & (x < 3)) + assert f(Piecewise((x, (x > -oo) & (x < oo))) + ) == ((x > -oo) & (x < oo)) + assert f(Piecewise((x, (x > -3) & (x < 3))) + ) == ((x > -3) & (x < 3)) + assert f(Piecewise((x, (x > -3) & (x < oo))) + ) == ((x > -3) & (x < oo)) + assert f(Piecewise((x, (x <= 3) & (x > -oo))) + ) == ((x <= 3) & (x > -oo)) + assert f(Piecewise((x, (x <= 3) & (x > -3))) + ) == ((x <= 3) & (x > -3)) + assert f(Piecewise((x, (x >= -3) & (x < 3))) + ) == ((x >= -3) & (x < 3)) + assert f(Piecewise((x, (x >= -3) & (x < oo))) + ) == ((x >= -3) & (x < oo)) + assert f(Piecewise((x, (x >= -3) & (x <= 3))) + ) == ((x >= -3) & (x <= 3)) + # could simplify by keeping only the first + # arg of result + assert f(Piecewise((x, (x <= oo) & (x > -oo))) + ) == (x > -oo) & (x <= oo) + assert f(Piecewise((x, (x <= oo) & (x > -3))) + ) == (x > -3) & (x <= oo) + assert f(Piecewise((x, (x >= -oo) & (x < 3))) + ) == (x < 3) & (x >= -oo) + assert f(Piecewise((x, (x >= -oo) & (x < oo))) + ) == (x < oo) & (x >= -oo) + assert f(Piecewise((x, (x >= -oo) & (x <= 3))) + ) == (x <= 3) & (x >= -oo) + assert f(Piecewise((x, (x >= -oo) & (x <= oo))) + ) == (x <= oo) & (x >= -oo) # but cannot be True unless x is real + assert f(Piecewise((x, (x >= -3) & (x <= oo))) + ) == (x >= -3) & (x <= oo) + assert f(Piecewise((x, (Abs(arg(a)) <= 1) | (Abs(arg(a)) < 1))) + ) == (Abs(arg(a)) <= 1) | (Abs(arg(a)) < 1) + + +def test_issue_22533(): + x = Symbol('x', real=True) + f = Piecewise((-1 / x, x <= 0), (1 / x, True)) + assert integrate(f, x) == Piecewise((-log(x), x <= 0), (log(x), True)) + + +def test_issue_24072(): + assert Piecewise((1, x > 1), (2, x <= 1), (3, x <= 1) + ) == Piecewise((1, x > 1), (2, True)) + + +def test_piecewise__eval_is_meromorphic(): + """ Issue 24127: Tests eval_is_meromorphic auxiliary method """ + x = symbols('x', real=True) + f = Piecewise((1, x < 0), (sqrt(1 - x), True)) + assert f.is_meromorphic(x, I) is None + assert f.is_meromorphic(x, -1) == True + assert f.is_meromorphic(x, 0) == None + assert f.is_meromorphic(x, 1) == False + assert f.is_meromorphic(x, 2) == True + assert f.is_meromorphic(x, Symbol('a')) == None + assert f.is_meromorphic(x, Symbol('a', real=True)) == None diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_trigonometric.py b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_trigonometric.py new file mode 100644 index 0000000000000000000000000000000000000000..815f424093aac72ee3a078d8ce62e5c195a625dc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/tests/test_trigonometric.py @@ -0,0 +1,2236 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core.add import Add +from sympy.core.function import (Lambda, diff) +from sympy.core.mod import Mod +from sympy.core.mul import Mul +from sympy.core.numbers import (E, Float, I, Rational, nan, oo, pi, zoo) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (arg, conjugate, im, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (acoth, asinh, atanh, cosh, coth, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, acot, acsc, asec, asin, atan, atan2, + cos, cot, csc, sec, sin, sinc, tan) +from sympy.functions.special.bessel import (besselj, jn) +from sympy.functions.special.delta_functions import Heaviside +from sympy.matrices.dense import Matrix +from sympy.polys.polytools import (cancel, gcd) +from sympy.series.limits import limit +from sympy.series.order import O +from sympy.series.series import series +from sympy.sets.fancysets import ImageSet +from sympy.sets.sets import (FiniteSet, Interval) +from sympy.simplify.simplify import simplify +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError, PoleError +from sympy.core.relational import Ne, Eq +from sympy.functions.elementary.piecewise import Piecewise +from sympy.sets.setexpr import SetExpr +from sympy.testing.pytest import XFAIL, slow, raises + + +x, y, z = symbols('x y z') +r = Symbol('r', real=True) +k, m = symbols('k m', integer=True) +p = Symbol('p', positive=True) +n = Symbol('n', negative=True) +np = Symbol('p', nonpositive=True) +nn = Symbol('n', nonnegative=True) +nz = Symbol('nz', nonzero=True) +ep = Symbol('ep', extended_positive=True) +en = Symbol('en', extended_negative=True) +enp = Symbol('ep', extended_nonpositive=True) +enn = Symbol('en', extended_nonnegative=True) +enz = Symbol('enz', extended_nonzero=True) +a = Symbol('a', algebraic=True) +na = Symbol('na', nonzero=True, algebraic=True) + + +def test_sin(): + x, y = symbols('x y') + z = symbols('z', imaginary=True) + + assert sin.nargs == FiniteSet(1) + assert sin(nan) is nan + assert sin(zoo) is nan + + assert sin(oo) == AccumBounds(-1, 1) + assert sin(oo) - sin(oo) == AccumBounds(-2, 2) + assert sin(oo*I) == oo*I + assert sin(-oo*I) == -oo*I + assert 0*sin(oo) is S.Zero + assert 0/sin(oo) is S.Zero + assert 0 + sin(oo) == AccumBounds(-1, 1) + assert 5 + sin(oo) == AccumBounds(4, 6) + + assert sin(0) == 0 + + assert sin(z*I) == I*sinh(z) + assert sin(asin(x)) == x + assert sin(atan(x)) == x / sqrt(1 + x**2) + assert sin(acos(x)) == sqrt(1 - x**2) + assert sin(acot(x)) == 1 / (sqrt(1 + 1 / x**2) * x) + assert sin(acsc(x)) == 1 / x + assert sin(asec(x)) == sqrt(1 - 1 / x**2) + assert sin(atan2(y, x)) == y / sqrt(x**2 + y**2) + + assert sin(pi*I) == sinh(pi)*I + assert sin(-pi*I) == -sinh(pi)*I + assert sin(-2*I) == -sinh(2)*I + + assert sin(pi) == 0 + assert sin(-pi) == 0 + assert sin(2*pi) == 0 + assert sin(-2*pi) == 0 + assert sin(-3*10**73*pi) == 0 + assert sin(7*10**103*pi) == 0 + + assert sin(pi/2) == 1 + assert sin(-pi/2) == -1 + assert sin(pi*Rational(5, 2)) == 1 + assert sin(pi*Rational(7, 2)) == -1 + + ne = symbols('ne', integer=True, even=False) + e = symbols('e', even=True) + assert sin(pi*ne/2) == (-1)**(ne/2 - S.Half) + assert sin(pi*k/2).func == sin + assert sin(pi*e/2) == 0 + assert sin(pi*k) == 0 + assert sin(pi*k).subs(k, 3) == sin(pi*k/2).subs(k, 6) # issue 8298 + + assert sin(pi/3) == S.Half*sqrt(3) + assert sin(pi*Rational(-2, 3)) == Rational(-1, 2)*sqrt(3) + + assert sin(pi/4) == S.Half*sqrt(2) + assert sin(-pi/4) == Rational(-1, 2)*sqrt(2) + assert sin(pi*Rational(17, 4)) == S.Half*sqrt(2) + assert sin(pi*Rational(-3, 4)) == Rational(-1, 2)*sqrt(2) + + assert sin(pi/6) == S.Half + assert sin(-pi/6) == Rational(-1, 2) + assert sin(pi*Rational(7, 6)) == Rational(-1, 2) + assert sin(pi*Rational(-5, 6)) == Rational(-1, 2) + + assert sin(pi*Rational(1, 5)) == sqrt((5 - sqrt(5)) / 8) + assert sin(pi*Rational(2, 5)) == sqrt((5 + sqrt(5)) / 8) + assert sin(pi*Rational(3, 5)) == sin(pi*Rational(2, 5)) + assert sin(pi*Rational(4, 5)) == sin(pi*Rational(1, 5)) + assert sin(pi*Rational(6, 5)) == -sin(pi*Rational(1, 5)) + assert sin(pi*Rational(8, 5)) == -sin(pi*Rational(2, 5)) + + assert sin(pi*Rational(-1273, 5)) == -sin(pi*Rational(2, 5)) + + assert sin(pi/8) == sqrt((2 - sqrt(2))/4) + + assert sin(pi/10) == Rational(-1, 4) + sqrt(5)/4 + + assert sin(pi/12) == -sqrt(2)/4 + sqrt(6)/4 + assert sin(pi*Rational(5, 12)) == sqrt(2)/4 + sqrt(6)/4 + assert sin(pi*Rational(-7, 12)) == -sqrt(2)/4 - sqrt(6)/4 + assert sin(pi*Rational(-11, 12)) == sqrt(2)/4 - sqrt(6)/4 + + assert sin(pi*Rational(104, 105)) == sin(pi/105) + assert sin(pi*Rational(106, 105)) == -sin(pi/105) + + assert sin(pi*Rational(-104, 105)) == -sin(pi/105) + assert sin(pi*Rational(-106, 105)) == sin(pi/105) + + assert sin(x*I) == sinh(x)*I + + assert sin(k*pi) == 0 + assert sin(17*k*pi) == 0 + assert sin(2*k*pi + 4) == sin(4) + assert sin(2*k*pi + m*pi + 1) == (-1)**(m + 2*k)*sin(1) + + assert sin(k*pi*I) == sinh(k*pi)*I + + assert sin(r).is_real is True + + assert sin(0, evaluate=False).is_algebraic + assert sin(a).is_algebraic is None + assert sin(na).is_algebraic is False + q = Symbol('q', rational=True) + assert sin(pi*q).is_algebraic + qn = Symbol('qn', rational=True, nonzero=True) + assert sin(qn).is_rational is False + assert sin(q).is_rational is None # issue 8653 + + assert isinstance(sin( re(x) - im(y)), sin) is True + assert isinstance(sin(-re(x) + im(y)), sin) is False + + assert sin(SetExpr(Interval(0, 1))) == SetExpr(ImageSet(Lambda(x, sin(x)), + Interval(0, 1))) + + for d in list(range(1, 22)) + [60, 85]: + for n in range(d*2 + 1): + x = n*pi/d + e = abs( float(sin(x)) - sin(float(x)) ) + assert e < 1e-12 + + assert sin(0, evaluate=False).is_zero is True + assert sin(k*pi, evaluate=False).is_zero is True + + assert sin(Add(1, -1, evaluate=False), evaluate=False).is_zero is True + + +def test_sin_cos(): + for d in [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 24, 30, 40, 60, 120]: # list is not exhaustive... + for n in range(-2*d, d*2): + x = n*pi/d + assert sin(x + pi/2) == cos(x), "fails for %d*pi/%d" % (n, d) + assert sin(x - pi/2) == -cos(x), "fails for %d*pi/%d" % (n, d) + assert sin(x) == cos(x - pi/2), "fails for %d*pi/%d" % (n, d) + assert -sin(x) == cos(x + pi/2), "fails for %d*pi/%d" % (n, d) + + +def test_sin_series(): + assert sin(x).series(x, 0, 9) == \ + x - x**3/6 + x**5/120 - x**7/5040 + O(x**9) + + +def test_sin_rewrite(): + assert sin(x).rewrite(exp) == -I*(exp(I*x) - exp(-I*x))/2 + assert sin(x).rewrite(tan) == 2*tan(x/2)/(1 + tan(x/2)**2) + assert sin(x).rewrite(cot) == \ + Piecewise((0, Eq(im(x), 0) & Eq(Mod(x, pi), 0)), + (2*cot(x/2)/(cot(x/2)**2 + 1), True)) + assert sin(sinh(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, sinh(3)).n() + assert sin(cosh(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, cosh(3)).n() + assert sin(tanh(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, tanh(3)).n() + assert sin(coth(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, coth(3)).n() + assert sin(sin(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, sin(3)).n() + assert sin(cos(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, cos(3)).n() + assert sin(tan(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, tan(3)).n() + assert sin(cot(x)).rewrite( + exp).subs(x, 3).n() == sin(x).rewrite(exp).subs(x, cot(3)).n() + assert sin(log(x)).rewrite(Pow) == I*x**-I / 2 - I*x**I /2 + assert sin(x).rewrite(csc) == 1/csc(x) + assert sin(x).rewrite(cos) == cos(x - pi / 2, evaluate=False) + assert sin(x).rewrite(sec) == 1 / sec(x - pi / 2, evaluate=False) + assert sin(cos(x)).rewrite(Pow) == sin(cos(x)) + assert sin(x).rewrite(besselj) == sqrt(pi*x/2)*besselj(S.Half, x) + assert sin(x).rewrite(besselj).subs(x, 0) == sin(0) + + +def _test_extrig(f, i, e): + from sympy.core.function import expand_trig + assert unchanged(f, i) + assert expand_trig(f(i)) == f(i) + # testing directly instead of with .expand(trig=True) + # because the other expansions undo the unevaluated Mul + assert expand_trig(f(Mul(i, 1, evaluate=False))) == e + assert abs(f(i) - e).n() < 1e-10 + + +def test_sin_expansion(): + # Note: these formulas are not unique. The ones here come from the + # Chebyshev formulas. + assert sin(x + y).expand(trig=True) == sin(x)*cos(y) + cos(x)*sin(y) + assert sin(x - y).expand(trig=True) == sin(x)*cos(y) - cos(x)*sin(y) + assert sin(y - x).expand(trig=True) == cos(x)*sin(y) - sin(x)*cos(y) + assert sin(2*x).expand(trig=True) == 2*sin(x)*cos(x) + assert sin(3*x).expand(trig=True) == -4*sin(x)**3 + 3*sin(x) + assert sin(4*x).expand(trig=True) == -8*sin(x)**3*cos(x) + 4*sin(x)*cos(x) + assert sin(2*pi/17).expand(trig=True) == sin(2*pi/17, evaluate=False) + assert sin(x+pi/17).expand(trig=True) == sin(pi/17)*cos(x) + cos(pi/17)*sin(x) + _test_extrig(sin, 2, 2*sin(1)*cos(1)) + _test_extrig(sin, 3, -4*sin(1)**3 + 3*sin(1)) + + +def test_sin_AccumBounds(): + assert sin(AccumBounds(-oo, oo)) == AccumBounds(-1, 1) + assert sin(AccumBounds(0, oo)) == AccumBounds(-1, 1) + assert sin(AccumBounds(-oo, 0)) == AccumBounds(-1, 1) + assert sin(AccumBounds(0, 2*S.Pi)) == AccumBounds(-1, 1) + assert sin(AccumBounds(0, S.Pi*Rational(3, 4))) == AccumBounds(0, 1) + assert sin(AccumBounds(S.Pi*Rational(3, 4), S.Pi*Rational(7, 4))) == AccumBounds(-1, sin(S.Pi*Rational(3, 4))) + assert sin(AccumBounds(S.Pi/4, S.Pi/3)) == AccumBounds(sin(S.Pi/4), sin(S.Pi/3)) + assert sin(AccumBounds(S.Pi*Rational(3, 4), S.Pi*Rational(5, 6))) == AccumBounds(sin(S.Pi*Rational(5, 6)), sin(S.Pi*Rational(3, 4))) + + +def test_sin_fdiff(): + assert sin(x).fdiff() == cos(x) + raises(ArgumentIndexError, lambda: sin(x).fdiff(2)) + + +def test_trig_symmetry(): + assert sin(-x) == -sin(x) + assert cos(-x) == cos(x) + assert tan(-x) == -tan(x) + assert cot(-x) == -cot(x) + assert sin(x + pi) == -sin(x) + assert sin(x + 2*pi) == sin(x) + assert sin(x + 3*pi) == -sin(x) + assert sin(x + 4*pi) == sin(x) + assert sin(x - 5*pi) == -sin(x) + assert cos(x + pi) == -cos(x) + assert cos(x + 2*pi) == cos(x) + assert cos(x + 3*pi) == -cos(x) + assert cos(x + 4*pi) == cos(x) + assert cos(x - 5*pi) == -cos(x) + assert tan(x + pi) == tan(x) + assert tan(x - 3*pi) == tan(x) + assert cot(x + pi) == cot(x) + assert cot(x - 3*pi) == cot(x) + assert sin(pi/2 - x) == cos(x) + assert sin(pi*Rational(3, 2) - x) == -cos(x) + assert sin(pi*Rational(5, 2) - x) == cos(x) + assert cos(pi/2 - x) == sin(x) + assert cos(pi*Rational(3, 2) - x) == -sin(x) + assert cos(pi*Rational(5, 2) - x) == sin(x) + assert tan(pi/2 - x) == cot(x) + assert tan(pi*Rational(3, 2) - x) == cot(x) + assert tan(pi*Rational(5, 2) - x) == cot(x) + assert cot(pi/2 - x) == tan(x) + assert cot(pi*Rational(3, 2) - x) == tan(x) + assert cot(pi*Rational(5, 2) - x) == tan(x) + assert sin(pi/2 + x) == cos(x) + assert cos(pi/2 + x) == -sin(x) + assert tan(pi/2 + x) == -cot(x) + assert cot(pi/2 + x) == -tan(x) + + +def test_cos(): + x, y = symbols('x y') + + assert cos.nargs == FiniteSet(1) + assert cos(nan) is nan + + assert cos(oo) == AccumBounds(-1, 1) + assert cos(oo) - cos(oo) == AccumBounds(-2, 2) + assert cos(oo*I) is oo + assert cos(-oo*I) is oo + assert cos(zoo) is nan + + assert cos(0) == 1 + + assert cos(acos(x)) == x + assert cos(atan(x)) == 1 / sqrt(1 + x**2) + assert cos(asin(x)) == sqrt(1 - x**2) + assert cos(acot(x)) == 1 / sqrt(1 + 1 / x**2) + assert cos(acsc(x)) == sqrt(1 - 1 / x**2) + assert cos(asec(x)) == 1 / x + assert cos(atan2(y, x)) == x / sqrt(x**2 + y**2) + + assert cos(pi*I) == cosh(pi) + assert cos(-pi*I) == cosh(pi) + assert cos(-2*I) == cosh(2) + + assert cos(pi/2) == 0 + assert cos(-pi/2) == 0 + assert cos(pi/2) == 0 + assert cos(-pi/2) == 0 + assert cos((-3*10**73 + 1)*pi/2) == 0 + assert cos((7*10**103 + 1)*pi/2) == 0 + + n = symbols('n', integer=True, even=False) + e = symbols('e', even=True) + assert cos(pi*n/2) == 0 + assert cos(pi*e/2) == (-1)**(e/2) + + assert cos(pi) == -1 + assert cos(-pi) == -1 + assert cos(2*pi) == 1 + assert cos(5*pi) == -1 + assert cos(8*pi) == 1 + + assert cos(pi/3) == S.Half + assert cos(pi*Rational(-2, 3)) == Rational(-1, 2) + + assert cos(pi/4) == S.Half*sqrt(2) + assert cos(-pi/4) == S.Half*sqrt(2) + assert cos(pi*Rational(11, 4)) == Rational(-1, 2)*sqrt(2) + assert cos(pi*Rational(-3, 4)) == Rational(-1, 2)*sqrt(2) + + assert cos(pi/6) == S.Half*sqrt(3) + assert cos(-pi/6) == S.Half*sqrt(3) + assert cos(pi*Rational(7, 6)) == Rational(-1, 2)*sqrt(3) + assert cos(pi*Rational(-5, 6)) == Rational(-1, 2)*sqrt(3) + + assert cos(pi*Rational(1, 5)) == (sqrt(5) + 1)/4 + assert cos(pi*Rational(2, 5)) == (sqrt(5) - 1)/4 + assert cos(pi*Rational(3, 5)) == -cos(pi*Rational(2, 5)) + assert cos(pi*Rational(4, 5)) == -cos(pi*Rational(1, 5)) + assert cos(pi*Rational(6, 5)) == -cos(pi*Rational(1, 5)) + assert cos(pi*Rational(8, 5)) == cos(pi*Rational(2, 5)) + + assert cos(pi*Rational(-1273, 5)) == -cos(pi*Rational(2, 5)) + + assert cos(pi/8) == sqrt((2 + sqrt(2))/4) + + assert cos(pi/12) == sqrt(2)/4 + sqrt(6)/4 + assert cos(pi*Rational(5, 12)) == -sqrt(2)/4 + sqrt(6)/4 + assert cos(pi*Rational(7, 12)) == sqrt(2)/4 - sqrt(6)/4 + assert cos(pi*Rational(11, 12)) == -sqrt(2)/4 - sqrt(6)/4 + + assert cos(pi*Rational(104, 105)) == -cos(pi/105) + assert cos(pi*Rational(106, 105)) == -cos(pi/105) + + assert cos(pi*Rational(-104, 105)) == -cos(pi/105) + assert cos(pi*Rational(-106, 105)) == -cos(pi/105) + + assert cos(x*I) == cosh(x) + assert cos(k*pi*I) == cosh(k*pi) + + assert cos(r).is_real is True + + assert cos(0, evaluate=False).is_algebraic + assert cos(a).is_algebraic is None + assert cos(na).is_algebraic is False + q = Symbol('q', rational=True) + assert cos(pi*q).is_algebraic + assert cos(pi*Rational(2, 7)).is_algebraic + + assert cos(k*pi) == (-1)**k + assert cos(2*k*pi) == 1 + assert cos(0, evaluate=False).is_zero is False + assert cos(Rational(1, 2)).is_zero is False + # The following test will return None as the result, but really it should + # be True even if it is not always possible to resolve an assumptions query. + assert cos(asin(-1, evaluate=False), evaluate=False).is_zero is None + for d in list(range(1, 22)) + [60, 85]: + for n in range(2*d + 1): + x = n*pi/d + e = abs( float(cos(x)) - cos(float(x)) ) + assert e < 1e-12 + + +def test_issue_6190(): + c = Float('123456789012345678901234567890.25', '') + for cls in [sin, cos, tan, cot]: + assert cls(c*pi) == cls(pi/4) + assert cls(4.125*pi) == cls(pi/8) + assert cls(4.7*pi) == cls((4.7 % 2)*pi) + + +def test_cos_series(): + assert cos(x).series(x, 0, 9) == \ + 1 - x**2/2 + x**4/24 - x**6/720 + x**8/40320 + O(x**9) + + +def test_cos_rewrite(): + assert cos(x).rewrite(exp) == exp(I*x)/2 + exp(-I*x)/2 + assert cos(x).rewrite(tan) == (1 - tan(x/2)**2)/(1 + tan(x/2)**2) + assert cos(x).rewrite(cot) == \ + Piecewise((1, Eq(im(x), 0) & Eq(Mod(x, 2*pi), 0)), + ((cot(x/2)**2 - 1)/(cot(x/2)**2 + 1), True)) + assert cos(sinh(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, sinh(3)).n() + assert cos(cosh(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, cosh(3)).n() + assert cos(tanh(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, tanh(3)).n() + assert cos(coth(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, coth(3)).n() + assert cos(sin(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, sin(3)).n() + assert cos(cos(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, cos(3)).n() + assert cos(tan(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, tan(3)).n() + assert cos(cot(x)).rewrite( + exp).subs(x, 3).n() == cos(x).rewrite(exp).subs(x, cot(3)).n() + assert cos(log(x)).rewrite(Pow) == x**I/2 + x**-I/2 + assert cos(x).rewrite(sec) == 1/sec(x) + assert cos(x).rewrite(sin) == sin(x + pi/2, evaluate=False) + assert cos(x).rewrite(csc) == 1/csc(-x + pi/2, evaluate=False) + assert cos(sin(x)).rewrite(Pow) == cos(sin(x)) + assert cos(x).rewrite(besselj) == Piecewise( + (sqrt(pi*x/2)*besselj(-S.Half, x), Ne(x, 0)), + (1, True) + ) + assert cos(x).rewrite(besselj).subs(x, 0) == cos(0) + + +def test_cos_expansion(): + assert cos(x + y).expand(trig=True) == cos(x)*cos(y) - sin(x)*sin(y) + assert cos(x - y).expand(trig=True) == cos(x)*cos(y) + sin(x)*sin(y) + assert cos(y - x).expand(trig=True) == cos(x)*cos(y) + sin(x)*sin(y) + assert cos(2*x).expand(trig=True) == 2*cos(x)**2 - 1 + assert cos(3*x).expand(trig=True) == 4*cos(x)**3 - 3*cos(x) + assert cos(4*x).expand(trig=True) == 8*cos(x)**4 - 8*cos(x)**2 + 1 + assert cos(2*pi/17).expand(trig=True) == cos(2*pi/17, evaluate=False) + assert cos(x+pi/17).expand(trig=True) == cos(pi/17)*cos(x) - sin(pi/17)*sin(x) + _test_extrig(cos, 2, 2*cos(1)**2 - 1) + _test_extrig(cos, 3, 4*cos(1)**3 - 3*cos(1)) + + +def test_cos_AccumBounds(): + assert cos(AccumBounds(-oo, oo)) == AccumBounds(-1, 1) + assert cos(AccumBounds(0, oo)) == AccumBounds(-1, 1) + assert cos(AccumBounds(-oo, 0)) == AccumBounds(-1, 1) + assert cos(AccumBounds(0, 2*S.Pi)) == AccumBounds(-1, 1) + assert cos(AccumBounds(-S.Pi/3, S.Pi/4)) == AccumBounds(cos(-S.Pi/3), 1) + assert cos(AccumBounds(S.Pi*Rational(3, 4), S.Pi*Rational(5, 4))) == AccumBounds(-1, cos(S.Pi*Rational(3, 4))) + assert cos(AccumBounds(S.Pi*Rational(5, 4), S.Pi*Rational(4, 3))) == AccumBounds(cos(S.Pi*Rational(5, 4)), cos(S.Pi*Rational(4, 3))) + assert cos(AccumBounds(S.Pi/4, S.Pi/3)) == AccumBounds(cos(S.Pi/3), cos(S.Pi/4)) + + +def test_cos_fdiff(): + assert cos(x).fdiff() == -sin(x) + raises(ArgumentIndexError, lambda: cos(x).fdiff(2)) + + +def test_tan(): + assert tan(nan) is nan + + assert tan(zoo) is nan + assert tan(oo) == AccumBounds(-oo, oo) + assert tan(oo) - tan(oo) == AccumBounds(-oo, oo) + assert tan.nargs == FiniteSet(1) + assert tan(oo*I) == I + assert tan(-oo*I) == -I + + assert tan(0) == 0 + + assert tan(atan(x)) == x + assert tan(asin(x)) == x / sqrt(1 - x**2) + assert tan(acos(x)) == sqrt(1 - x**2) / x + assert tan(acot(x)) == 1 / x + assert tan(acsc(x)) == 1 / (sqrt(1 - 1 / x**2) * x) + assert tan(asec(x)) == sqrt(1 - 1 / x**2) * x + assert tan(atan2(y, x)) == y/x + + assert tan(pi*I) == tanh(pi)*I + assert tan(-pi*I) == -tanh(pi)*I + assert tan(-2*I) == -tanh(2)*I + + assert tan(pi) == 0 + assert tan(-pi) == 0 + assert tan(2*pi) == 0 + assert tan(-2*pi) == 0 + assert tan(-3*10**73*pi) == 0 + + assert tan(pi/2) is zoo + assert tan(pi*Rational(3, 2)) is zoo + + assert tan(pi/3) == sqrt(3) + assert tan(pi*Rational(-2, 3)) == sqrt(3) + + assert tan(pi/4) is S.One + assert tan(-pi/4) is S.NegativeOne + assert tan(pi*Rational(17, 4)) is S.One + assert tan(pi*Rational(-3, 4)) is S.One + + assert tan(pi/5) == sqrt(5 - 2*sqrt(5)) + assert tan(pi*Rational(2, 5)) == sqrt(5 + 2*sqrt(5)) + assert tan(pi*Rational(18, 5)) == -sqrt(5 + 2*sqrt(5)) + assert tan(pi*Rational(-16, 5)) == -sqrt(5 - 2*sqrt(5)) + + assert tan(pi/6) == 1/sqrt(3) + assert tan(-pi/6) == -1/sqrt(3) + assert tan(pi*Rational(7, 6)) == 1/sqrt(3) + assert tan(pi*Rational(-5, 6)) == 1/sqrt(3) + + assert tan(pi/8) == -1 + sqrt(2) + assert tan(pi*Rational(3, 8)) == 1 + sqrt(2) # issue 15959 + assert tan(pi*Rational(5, 8)) == -1 - sqrt(2) + assert tan(pi*Rational(7, 8)) == 1 - sqrt(2) + + assert tan(pi/10) == sqrt(1 - 2*sqrt(5)/5) + assert tan(pi*Rational(3, 10)) == sqrt(1 + 2*sqrt(5)/5) + assert tan(pi*Rational(17, 10)) == -sqrt(1 + 2*sqrt(5)/5) + assert tan(pi*Rational(-31, 10)) == -sqrt(1 - 2*sqrt(5)/5) + + assert tan(pi/12) == -sqrt(3) + 2 + assert tan(pi*Rational(5, 12)) == sqrt(3) + 2 + assert tan(pi*Rational(7, 12)) == -sqrt(3) - 2 + assert tan(pi*Rational(11, 12)) == sqrt(3) - 2 + + assert tan(pi/24).radsimp() == -2 - sqrt(3) + sqrt(2) + sqrt(6) + assert tan(pi*Rational(5, 24)).radsimp() == -2 + sqrt(3) - sqrt(2) + sqrt(6) + assert tan(pi*Rational(7, 24)).radsimp() == 2 - sqrt(3) - sqrt(2) + sqrt(6) + assert tan(pi*Rational(11, 24)).radsimp() == 2 + sqrt(3) + sqrt(2) + sqrt(6) + assert tan(pi*Rational(13, 24)).radsimp() == -2 - sqrt(3) - sqrt(2) - sqrt(6) + assert tan(pi*Rational(17, 24)).radsimp() == -2 + sqrt(3) + sqrt(2) - sqrt(6) + assert tan(pi*Rational(19, 24)).radsimp() == 2 - sqrt(3) + sqrt(2) - sqrt(6) + assert tan(pi*Rational(23, 24)).radsimp() == 2 + sqrt(3) - sqrt(2) - sqrt(6) + + assert tan(x*I) == tanh(x)*I + + assert tan(k*pi) == 0 + assert tan(17*k*pi) == 0 + + assert tan(k*pi*I) == tanh(k*pi)*I + + assert tan(r).is_real is None + assert tan(r).is_extended_real is True + + assert tan(0, evaluate=False).is_algebraic + assert tan(a).is_algebraic is None + assert tan(na).is_algebraic is False + + assert tan(pi*Rational(10, 7)) == tan(pi*Rational(3, 7)) + assert tan(pi*Rational(11, 7)) == -tan(pi*Rational(3, 7)) + assert tan(pi*Rational(-11, 7)) == tan(pi*Rational(3, 7)) + + assert tan(pi*Rational(15, 14)) == tan(pi/14) + assert tan(pi*Rational(-15, 14)) == -tan(pi/14) + + assert tan(r).is_finite is None + assert tan(I*r).is_finite is True + + # https://github.com/sympy/sympy/issues/21177 + f = tan(pi*(x + S(3)/2))/(3*x) + assert f.as_leading_term(x) == -1/(3*pi*x**2) + + +def test_tan_series(): + assert tan(x).series(x, 0, 9) == \ + x + x**3/3 + 2*x**5/15 + 17*x**7/315 + O(x**9) + + +def test_tan_rewrite(): + neg_exp, pos_exp = exp(-x*I), exp(x*I) + assert tan(x).rewrite(exp) == I*(neg_exp - pos_exp)/(neg_exp + pos_exp) + assert tan(x).rewrite(sin) == 2*sin(x)**2/sin(2*x) + assert tan(x).rewrite(cos) == cos(x - S.Pi/2, evaluate=False)/cos(x) + assert tan(x).rewrite(cot) == 1/cot(x) + assert tan(sinh(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, sinh(3)).n() + assert tan(cosh(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, cosh(3)).n() + assert tan(tanh(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, tanh(3)).n() + assert tan(coth(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, coth(3)).n() + assert tan(sin(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, sin(3)).n() + assert tan(cos(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, cos(3)).n() + assert tan(tan(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, tan(3)).n() + assert tan(cot(x)).rewrite(exp).subs(x, 3).n() == tan(x).rewrite(exp).subs(x, cot(3)).n() + assert tan(log(x)).rewrite(Pow) == I*(x**-I - x**I)/(x**-I + x**I) + assert tan(x).rewrite(sec) == sec(x)/sec(x - pi/2, evaluate=False) + assert tan(x).rewrite(csc) == csc(-x + pi/2, evaluate=False)/csc(x) + assert tan(sin(x)).rewrite(Pow) == tan(sin(x)) + assert tan(pi*Rational(2, 5), evaluate=False).rewrite(sqrt) == sqrt(sqrt(5)/8 + + Rational(5, 8))/(Rational(-1, 4) + sqrt(5)/4) + assert tan(x).rewrite(besselj) == besselj(S.Half, x)/besselj(-S.Half, x) + assert tan(x).rewrite(besselj).subs(x, 0) == tan(0) + + +@slow +def test_tan_rewrite_slow(): + assert 0 == (cos(pi/34)*tan(pi/34) - sin(pi/34)).rewrite(pow) + assert 0 == (cos(pi/17)*tan(pi/17) - sin(pi/17)).rewrite(pow) + assert tan(pi/19).rewrite(pow) == tan(pi/19) + assert tan(pi*Rational(8, 19)).rewrite(sqrt) == tan(pi*Rational(8, 19)) + assert tan(pi*Rational(2, 5), evaluate=False).rewrite(sqrt) == sqrt(sqrt(5)/8 + + Rational(5, 8))/(Rational(-1, 4) + sqrt(5)/4) + + +def test_tan_subs(): + assert tan(x).subs(tan(x), y) == y + assert tan(x).subs(x, y) == tan(y) + assert tan(x).subs(x, S.Pi/2) is zoo + assert tan(x).subs(x, S.Pi*Rational(3, 2)) is zoo + + +def test_tan_expansion(): + assert tan(x + y).expand(trig=True) == ((tan(x) + tan(y))/(1 - tan(x)*tan(y))).expand() + assert tan(x - y).expand(trig=True) == ((tan(x) - tan(y))/(1 + tan(x)*tan(y))).expand() + assert tan(x + y + z).expand(trig=True) == ( + (tan(x) + tan(y) + tan(z) - tan(x)*tan(y)*tan(z))/ + (1 - tan(x)*tan(y) - tan(x)*tan(z) - tan(y)*tan(z))).expand() + assert 0 == tan(2*x).expand(trig=True).rewrite(tan).subs([(tan(x), Rational(1, 7))])*24 - 7 + assert 0 == tan(3*x).expand(trig=True).rewrite(tan).subs([(tan(x), Rational(1, 5))])*55 - 37 + assert 0 == tan(4*x - pi/4).expand(trig=True).rewrite(tan).subs([(tan(x), Rational(1, 5))])*239 - 1 + _test_extrig(tan, 2, 2*tan(1)/(1 - tan(1)**2)) + _test_extrig(tan, 3, (-tan(1)**3 + 3*tan(1))/(1 - 3*tan(1)**2)) + + +def test_tan_AccumBounds(): + assert tan(AccumBounds(-oo, oo)) == AccumBounds(-oo, oo) + assert tan(AccumBounds(S.Pi/3, S.Pi*Rational(2, 3))) == AccumBounds(-oo, oo) + assert tan(AccumBounds(S.Pi/6, S.Pi/3)) == AccumBounds(tan(S.Pi/6), tan(S.Pi/3)) + + +def test_tan_fdiff(): + assert tan(x).fdiff() == tan(x)**2 + 1 + raises(ArgumentIndexError, lambda: tan(x).fdiff(2)) + + +def test_cot(): + assert cot(nan) is nan + + assert cot.nargs == FiniteSet(1) + assert cot(oo*I) == -I + assert cot(-oo*I) == I + assert cot(zoo) is nan + + assert cot(0) is zoo + assert cot(2*pi) is zoo + + assert cot(acot(x)) == x + assert cot(atan(x)) == 1 / x + assert cot(asin(x)) == sqrt(1 - x**2) / x + assert cot(acos(x)) == x / sqrt(1 - x**2) + assert cot(acsc(x)) == sqrt(1 - 1 / x**2) * x + assert cot(asec(x)) == 1 / (sqrt(1 - 1 / x**2) * x) + assert cot(atan2(y, x)) == x/y + + assert cot(pi*I) == -coth(pi)*I + assert cot(-pi*I) == coth(pi)*I + assert cot(-2*I) == coth(2)*I + + assert cot(pi) == cot(2*pi) == cot(3*pi) + assert cot(-pi) == cot(-2*pi) == cot(-3*pi) + + assert cot(pi/2) == 0 + assert cot(-pi/2) == 0 + assert cot(pi*Rational(5, 2)) == 0 + assert cot(pi*Rational(7, 2)) == 0 + + assert cot(pi/3) == 1/sqrt(3) + assert cot(pi*Rational(-2, 3)) == 1/sqrt(3) + + assert cot(pi/4) is S.One + assert cot(-pi/4) is S.NegativeOne + assert cot(pi*Rational(17, 4)) is S.One + assert cot(pi*Rational(-3, 4)) is S.One + + assert cot(pi/6) == sqrt(3) + assert cot(-pi/6) == -sqrt(3) + assert cot(pi*Rational(7, 6)) == sqrt(3) + assert cot(pi*Rational(-5, 6)) == sqrt(3) + + assert cot(pi/8) == 1 + sqrt(2) + assert cot(pi*Rational(3, 8)) == -1 + sqrt(2) + assert cot(pi*Rational(5, 8)) == 1 - sqrt(2) + assert cot(pi*Rational(7, 8)) == -1 - sqrt(2) + + assert cot(pi/12) == sqrt(3) + 2 + assert cot(pi*Rational(5, 12)) == -sqrt(3) + 2 + assert cot(pi*Rational(7, 12)) == sqrt(3) - 2 + assert cot(pi*Rational(11, 12)) == -sqrt(3) - 2 + + assert cot(pi/24).radsimp() == sqrt(2) + sqrt(3) + 2 + sqrt(6) + assert cot(pi*Rational(5, 24)).radsimp() == -sqrt(2) - sqrt(3) + 2 + sqrt(6) + assert cot(pi*Rational(7, 24)).radsimp() == -sqrt(2) + sqrt(3) - 2 + sqrt(6) + assert cot(pi*Rational(11, 24)).radsimp() == sqrt(2) - sqrt(3) - 2 + sqrt(6) + assert cot(pi*Rational(13, 24)).radsimp() == -sqrt(2) + sqrt(3) + 2 - sqrt(6) + assert cot(pi*Rational(17, 24)).radsimp() == sqrt(2) - sqrt(3) + 2 - sqrt(6) + assert cot(pi*Rational(19, 24)).radsimp() == sqrt(2) + sqrt(3) - 2 - sqrt(6) + assert cot(pi*Rational(23, 24)).radsimp() == -sqrt(2) - sqrt(3) - 2 - sqrt(6) + + assert cot(x*I) == -coth(x)*I + assert cot(k*pi*I) == -coth(k*pi)*I + + assert cot(r).is_real is None + assert cot(r).is_extended_real is True + + assert cot(a).is_algebraic is None + assert cot(na).is_algebraic is False + + assert cot(pi*Rational(10, 7)) == cot(pi*Rational(3, 7)) + assert cot(pi*Rational(11, 7)) == -cot(pi*Rational(3, 7)) + assert cot(pi*Rational(-11, 7)) == cot(pi*Rational(3, 7)) + + assert cot(pi*Rational(39, 34)) == cot(pi*Rational(5, 34)) + assert cot(pi*Rational(-41, 34)) == -cot(pi*Rational(7, 34)) + + assert cot(x).is_finite is None + assert cot(r).is_finite is None + i = Symbol('i', imaginary=True) + assert cot(i).is_finite is True + + assert cot(x).subs(x, 3*pi) is zoo + + # https://github.com/sympy/sympy/issues/21177 + f = cot(pi*(x + 4))/(3*x) + assert f.as_leading_term(x) == 1/(3*pi*x**2) + + +def test_tan_cot_sin_cos_evalf(): + assert abs((tan(pi*Rational(8, 15))*cos(pi*Rational(8, 15))/sin(pi*Rational(8, 15)) - 1).evalf()) < 1e-14 + assert abs((cot(pi*Rational(4, 15))*sin(pi*Rational(4, 15))/cos(pi*Rational(4, 15)) - 1).evalf()) < 1e-14 + +@XFAIL +def test_tan_cot_sin_cos_ratsimp(): + assert 1 == (tan(pi*Rational(8, 15))*cos(pi*Rational(8, 15))/sin(pi*Rational(8, 15))).ratsimp() + assert 1 == (cot(pi*Rational(4, 15))*sin(pi*Rational(4, 15))/cos(pi*Rational(4, 15))).ratsimp() + + +def test_cot_series(): + assert cot(x).series(x, 0, 9) == \ + 1/x - x/3 - x**3/45 - 2*x**5/945 - x**7/4725 + O(x**9) + # issue 6210 + assert cot(x**4 + x**5).series(x, 0, 1) == \ + x**(-4) - 1/x**3 + x**(-2) - 1/x + 1 + O(x) + assert cot(pi*(1-x)).series(x, 0, 3) == -1/(pi*x) + pi*x/3 + O(x**3) + assert cot(x).taylor_term(0, x) == 1/x + assert cot(x).taylor_term(2, x) is S.Zero + assert cot(x).taylor_term(3, x) == -x**3/45 + + +def test_cot_rewrite(): + neg_exp, pos_exp = exp(-x*I), exp(x*I) + assert cot(x).rewrite(exp) == I*(pos_exp + neg_exp)/(pos_exp - neg_exp) + assert cot(x).rewrite(sin) == sin(2*x)/(2*(sin(x)**2)) + assert cot(x).rewrite(cos) == cos(x)/cos(x - pi/2, evaluate=False) + assert cot(x).rewrite(tan) == 1/tan(x) + def check(func): + z = cot(func(x)).rewrite(exp) - cot(x).rewrite(exp).subs(x, func(x)) + assert z.rewrite(exp).expand() == 0 + check(sinh) + check(cosh) + check(tanh) + check(coth) + check(sin) + check(cos) + check(tan) + assert cot(log(x)).rewrite(Pow) == -I*(x**-I + x**I)/(x**-I - x**I) + assert cot(x).rewrite(sec) == sec(x - pi / 2, evaluate=False) / sec(x) + assert cot(x).rewrite(csc) == csc(x) / csc(- x + pi / 2, evaluate=False) + assert cot(sin(x)).rewrite(Pow) == cot(sin(x)) + assert cot(pi*Rational(2, 5), evaluate=False).rewrite(sqrt) == (Rational(-1, 4) + sqrt(5)/4)/\ + sqrt(sqrt(5)/8 + Rational(5, 8)) + assert cot(x).rewrite(besselj) == besselj(-S.Half, x)/besselj(S.Half, x) + assert cot(x).rewrite(besselj).subs(x, 0) == cot(0) + + +@slow +def test_cot_rewrite_slow(): + assert cot(pi*Rational(4, 34)).rewrite(pow).ratsimp() == \ + (cos(pi*Rational(4, 34))/sin(pi*Rational(4, 34))).rewrite(pow).ratsimp() + assert cot(pi*Rational(4, 17)).rewrite(pow) == \ + (cos(pi*Rational(4, 17))/sin(pi*Rational(4, 17))).rewrite(pow) + assert cot(pi/19).rewrite(pow) == cot(pi/19) + assert cot(pi/19).rewrite(sqrt) == cot(pi/19) + assert cot(pi*Rational(2, 5), evaluate=False).rewrite(sqrt) == \ + (Rational(-1, 4) + sqrt(5)/4) / sqrt(sqrt(5)/8 + Rational(5, 8)) + + +def test_cot_subs(): + assert cot(x).subs(cot(x), y) == y + assert cot(x).subs(x, y) == cot(y) + assert cot(x).subs(x, 0) is zoo + assert cot(x).subs(x, S.Pi) is zoo + + +def test_cot_expansion(): + assert cot(x + y).expand(trig=True).together() == ( + (cot(x)*cot(y) - 1)/(cot(x) + cot(y))) + assert cot(x - y).expand(trig=True).together() == ( + cot(x)*cot(-y) - 1)/(cot(x) + cot(-y)) + assert cot(x + y + z).expand(trig=True).together() == ( + (cot(x)*cot(y)*cot(z) - cot(x) - cot(y) - cot(z))/ + (-1 + cot(x)*cot(y) + cot(x)*cot(z) + cot(y)*cot(z))) + assert cot(3*x).expand(trig=True).together() == ( + (cot(x)**2 - 3)*cot(x)/(3*cot(x)**2 - 1)) + assert cot(2*x).expand(trig=True) == cot(x)/2 - 1/(2*cot(x)) + assert cot(3*x).expand(trig=True).together() == ( + cot(x)**2 - 3)*cot(x)/(3*cot(x)**2 - 1) + assert cot(4*x - pi/4).expand(trig=True).cancel() == ( + -tan(x)**4 + 4*tan(x)**3 + 6*tan(x)**2 - 4*tan(x) - 1 + )/(tan(x)**4 + 4*tan(x)**3 - 6*tan(x)**2 - 4*tan(x) + 1) + _test_extrig(cot, 2, (-1 + cot(1)**2)/(2*cot(1))) + _test_extrig(cot, 3, (-3*cot(1) + cot(1)**3)/(-1 + 3*cot(1)**2)) + + +def test_cot_AccumBounds(): + assert cot(AccumBounds(-oo, oo)) == AccumBounds(-oo, oo) + assert cot(AccumBounds(-S.Pi/3, S.Pi/3)) == AccumBounds(-oo, oo) + assert cot(AccumBounds(S.Pi/6, S.Pi/3)) == AccumBounds(cot(S.Pi/3), cot(S.Pi/6)) + + +def test_cot_fdiff(): + assert cot(x).fdiff() == -cot(x)**2 - 1 + raises(ArgumentIndexError, lambda: cot(x).fdiff(2)) + + +def test_sinc(): + assert isinstance(sinc(x), sinc) + + s = Symbol('s', zero=True) + assert sinc(s) is S.One + assert sinc(S.Infinity) is S.Zero + assert sinc(S.NegativeInfinity) is S.Zero + assert sinc(S.NaN) is S.NaN + assert sinc(S.ComplexInfinity) is S.NaN + + n = Symbol('n', integer=True, nonzero=True) + assert sinc(n*pi) is S.Zero + assert sinc(-n*pi) is S.Zero + assert sinc(pi/2) == 2 / pi + assert sinc(-pi/2) == 2 / pi + assert sinc(pi*Rational(5, 2)) == 2 / (5*pi) + assert sinc(pi*Rational(7, 2)) == -2 / (7*pi) + + assert sinc(-x) == sinc(x) + + assert sinc(x).diff(x) == cos(x)/x - sin(x)/x**2 + assert sinc(x).diff(x) == (sin(x)/x).diff(x) + assert sinc(x).diff(x, x) == (-sin(x) - 2*cos(x)/x + 2*sin(x)/x**2)/x + assert sinc(x).diff(x, x) == (sin(x)/x).diff(x, x) + assert limit(sinc(x).diff(x), x, 0) == 0 + assert limit(sinc(x).diff(x, x), x, 0) == -S(1)/3 + + # https://github.com/sympy/sympy/issues/11402 + # + # assert sinc(x).diff(x) == Piecewise(((x*cos(x) - sin(x)) / x**2, Ne(x, 0)), (0, True)) + # + # assert sinc(x).diff(x).equals(sinc(x).rewrite(sin).diff(x)) + # + # assert sinc(x).diff(x).subs(x, 0) is S.Zero + + assert sinc(x).series() == 1 - x**2/6 + x**4/120 + O(x**6) + + assert sinc(x).rewrite(jn) == jn(0, x) + assert sinc(x).rewrite(sin) == Piecewise((sin(x)/x, Ne(x, 0)), (1, True)) + assert sinc(pi, evaluate=False).is_zero is True + assert sinc(0, evaluate=False).is_zero is False + assert sinc(n*pi, evaluate=False).is_zero is True + assert sinc(x).is_zero is None + xr = Symbol('xr', real=True, nonzero=True) + assert sinc(x).is_real is None + assert sinc(xr).is_real is True + assert sinc(I*xr).is_real is True + assert sinc(I*100).is_real is True + assert sinc(x).is_finite is None + assert sinc(xr).is_finite is True + + +def test_asin(): + assert asin(nan) is nan + + assert asin.nargs == FiniteSet(1) + assert asin(oo) == -I*oo + assert asin(-oo) == I*oo + assert asin(zoo) is zoo + + # Note: asin(-x) = - asin(x) + assert asin(0) == 0 + assert asin(1) == pi/2 + assert asin(-1) == -pi/2 + assert asin(sqrt(3)/2) == pi/3 + assert asin(-sqrt(3)/2) == -pi/3 + assert asin(sqrt(2)/2) == pi/4 + assert asin(-sqrt(2)/2) == -pi/4 + assert asin(sqrt((5 - sqrt(5))/8)) == pi/5 + assert asin(-sqrt((5 - sqrt(5))/8)) == -pi/5 + assert asin(S.Half) == pi/6 + assert asin(Rational(-1, 2)) == -pi/6 + assert asin((sqrt(2 - sqrt(2)))/2) == pi/8 + assert asin(-(sqrt(2 - sqrt(2)))/2) == -pi/8 + assert asin((sqrt(5) - 1)/4) == pi/10 + assert asin(-(sqrt(5) - 1)/4) == -pi/10 + assert asin((sqrt(3) - 1)/sqrt(2**3)) == pi/12 + assert asin(-(sqrt(3) - 1)/sqrt(2**3)) == -pi/12 + + # check round-trip for exact values: + for d in [5, 6, 8, 10, 12]: + for n in range(-(d//2), d//2 + 1): + if gcd(n, d) == 1: + assert asin(sin(n*pi/d)) == n*pi/d + + assert asin(x).diff(x) == 1/sqrt(1 - x**2) + + assert asin(0.2, evaluate=False).is_real is True + assert asin(-2).is_real is False + assert asin(r).is_real is None + + assert asin(-2*I) == -I*asinh(2) + + assert asin(Rational(1, 7), evaluate=False).is_positive is True + assert asin(Rational(-1, 7), evaluate=False).is_positive is False + assert asin(p).is_positive is None + assert asin(sin(Rational(7, 2))) == Rational(-7, 2) + pi + assert asin(sin(Rational(-7, 4))) == Rational(7, 4) - pi + assert unchanged(asin, cos(x)) + + +def test_asin_series(): + assert asin(x).series(x, 0, 9) == \ + x + x**3/6 + 3*x**5/40 + 5*x**7/112 + O(x**9) + t5 = asin(x).taylor_term(5, x) + assert t5 == 3*x**5/40 + assert asin(x).taylor_term(7, x, t5, 0) == 5*x**7/112 + + +def test_asin_leading_term(): + assert asin(x).as_leading_term(x) == x + # Tests concerning branch points + assert asin(x + 1).as_leading_term(x) == pi/2 + assert asin(x - 1).as_leading_term(x) == -pi/2 + assert asin(1/x).as_leading_term(x, cdir=1) == I*log(x) + pi/2 - I*log(2) + assert asin(1/x).as_leading_term(x, cdir=-1) == -I*log(x) - 3*pi/2 + I*log(2) + # Tests concerning points lying on branch cuts + assert asin(I*x + 2).as_leading_term(x, cdir=1) == pi - asin(2) + assert asin(-I*x + 2).as_leading_term(x, cdir=1) == asin(2) + assert asin(I*x - 2).as_leading_term(x, cdir=1) == -asin(2) + assert asin(-I*x - 2).as_leading_term(x, cdir=1) == -pi + asin(2) + # Tests concerning im(ndir) == 0 + assert asin(-I*x**2 + x - 2).as_leading_term(x, cdir=1) == -pi/2 + I*log(2 - sqrt(3)) + assert asin(-I*x**2 + x - 2).as_leading_term(x, cdir=-1) == -pi/2 + I*log(2 - sqrt(3)) + + +def test_asin_rewrite(): + assert asin(x).rewrite(log) == -I*log(I*x + sqrt(1 - x**2)) + assert asin(x).rewrite(atan) == 2*atan(x/(1 + sqrt(1 - x**2))) + assert asin(x).rewrite(acos) == S.Pi/2 - acos(x) + assert asin(x).rewrite(acot) == 2*acot((sqrt(-x**2 + 1) + 1)/x) + assert asin(x).rewrite(asec) == -asec(1/x) + pi/2 + assert asin(x).rewrite(acsc) == acsc(1/x) + + +def test_asin_fdiff(): + assert asin(x).fdiff() == 1/sqrt(1 - x**2) + raises(ArgumentIndexError, lambda: asin(x).fdiff(2)) + + +def test_acos(): + assert acos(nan) is nan + assert acos(zoo) is zoo + + assert acos.nargs == FiniteSet(1) + assert acos(oo) == I*oo + assert acos(-oo) == -I*oo + + # Note: acos(-x) = pi - acos(x) + assert acos(0) == pi/2 + assert acos(S.Half) == pi/3 + assert acos(Rational(-1, 2)) == pi*Rational(2, 3) + assert acos(1) == 0 + assert acos(-1) == pi + assert acos(sqrt(2)/2) == pi/4 + assert acos(-sqrt(2)/2) == pi*Rational(3, 4) + + # check round-trip for exact values: + for d in range(5, 13): + for num in range(d): + if gcd(num, d) == 1: + assert acos(cos(num*pi/d)) == num*pi/d + assert acos(-cos(num*pi/d)) == pi - num*pi/d + assert acos(sin(num*pi/d)) == pi/2 - asin(sin(num*pi/d)) + assert acos(-sin(num*pi/d)) == pi/2 - asin(-sin(num*pi/d)) + + assert acos(2*I) == pi/2 - asin(2*I) + + assert acos(x).diff(x) == -1/sqrt(1 - x**2) + + assert acos(0.2).is_real is True + assert acos(-2).is_real is False + assert acos(r).is_real is None + + assert acos(Rational(1, 7), evaluate=False).is_positive is True + assert acos(Rational(-1, 7), evaluate=False).is_positive is True + assert acos(Rational(3, 2), evaluate=False).is_positive is False + assert acos(p).is_positive is None + + assert acos(2 + p).conjugate() != acos(10 + p) + assert acos(-3 + n).conjugate() != acos(-3 + n) + assert acos(Rational(1, 3)).conjugate() == acos(Rational(1, 3)) + assert acos(Rational(-1, 3)).conjugate() == acos(Rational(-1, 3)) + assert acos(p + n*I).conjugate() == acos(p - n*I) + assert acos(z).conjugate() != acos(conjugate(z)) + + +def test_acos_leading_term(): + assert acos(x).as_leading_term(x) == pi/2 + # Tests concerning branch points + assert acos(x + 1).as_leading_term(x) == sqrt(2)*sqrt(-x) + assert acos(x - 1).as_leading_term(x) == pi + assert acos(1/x).as_leading_term(x, cdir=1) == -I*log(x) + I*log(2) + assert acos(1/x).as_leading_term(x, cdir=-1) == I*log(x) + 2*pi - I*log(2) + # Tests concerning points lying on branch cuts + assert acos(I*x + 2).as_leading_term(x, cdir=1) == -acos(2) + assert acos(-I*x + 2).as_leading_term(x, cdir=1) == acos(2) + assert acos(I*x - 2).as_leading_term(x, cdir=1) == acos(-2) + assert acos(-I*x - 2).as_leading_term(x, cdir=1) == 2*pi - acos(-2) + # Tests concerning im(ndir) == 0 + assert acos(-I*x**2 + x - 2).as_leading_term(x, cdir=1) == pi + I*log(sqrt(3) + 2) + assert acos(-I*x**2 + x - 2).as_leading_term(x, cdir=-1) == pi + I*log(sqrt(3) + 2) + + +def test_acos_series(): + assert acos(x).series(x, 0, 8) == \ + pi/2 - x - x**3/6 - 3*x**5/40 - 5*x**7/112 + O(x**8) + assert acos(x).series(x, 0, 8) == pi/2 - asin(x).series(x, 0, 8) + t5 = acos(x).taylor_term(5, x) + assert t5 == -3*x**5/40 + assert acos(x).taylor_term(7, x, t5, 0) == -5*x**7/112 + assert acos(x).taylor_term(0, x) == pi/2 + assert acos(x).taylor_term(2, x) is S.Zero + + +def test_acos_rewrite(): + assert acos(x).rewrite(log) == pi/2 + I*log(I*x + sqrt(1 - x**2)) + assert acos(x).rewrite(atan) == pi*(-x*sqrt(x**(-2)) + 1)/2 + atan(sqrt(1 - x**2)/x) + assert acos(0).rewrite(atan) == S.Pi/2 + assert acos(0.5).rewrite(atan) == acos(0.5).rewrite(log) + assert acos(x).rewrite(asin) == S.Pi/2 - asin(x) + assert acos(x).rewrite(acot) == -2*acot((sqrt(-x**2 + 1) + 1)/x) + pi/2 + assert acos(x).rewrite(asec) == asec(1/x) + assert acos(x).rewrite(acsc) == -acsc(1/x) + pi/2 + + +def test_acos_fdiff(): + assert acos(x).fdiff() == -1/sqrt(1 - x**2) + raises(ArgumentIndexError, lambda: acos(x).fdiff(2)) + + +def test_atan(): + assert atan(nan) is nan + + assert atan.nargs == FiniteSet(1) + assert atan(oo) == pi/2 + assert atan(-oo) == -pi/2 + assert atan(zoo) == AccumBounds(-pi/2, pi/2) + + assert atan(0) == 0 + assert atan(1) == pi/4 + assert atan(sqrt(3)) == pi/3 + assert atan(-(1 + sqrt(2))) == pi*Rational(-3, 8) + assert atan(sqrt(5 - 2 * sqrt(5))) == pi/5 + assert atan(-sqrt(1 - 2 * sqrt(5)/ 5)) == -pi/10 + assert atan(sqrt(1 + 2 * sqrt(5) / 5)) == pi*Rational(3, 10) + assert atan(-2 + sqrt(3)) == -pi/12 + assert atan(2 + sqrt(3)) == pi*Rational(5, 12) + assert atan(-2 - sqrt(3)) == pi*Rational(-5, 12) + + # check round-trip for exact values: + for d in [5, 6, 8, 10, 12]: + for num in range(-(d//2), d//2 + 1): + if gcd(num, d) == 1: + assert atan(tan(num*pi/d)) == num*pi/d + + assert atan(oo) == pi/2 + assert atan(x).diff(x) == 1/(1 + x**2) + + assert atan(r).is_real is True + + assert atan(-2*I) == -I*atanh(2) + assert unchanged(atan, cot(x)) + assert atan(cot(Rational(1, 4))) == Rational(-1, 4) + pi/2 + assert acot(Rational(1, 4)).is_rational is False + + for s in (x, p, n, np, nn, nz, ep, en, enp, enn, enz): + if s.is_real or s.is_extended_real is None: + assert s.is_nonzero is atan(s).is_nonzero + assert s.is_positive is atan(s).is_positive + assert s.is_negative is atan(s).is_negative + assert s.is_nonpositive is atan(s).is_nonpositive + assert s.is_nonnegative is atan(s).is_nonnegative + else: + assert s.is_extended_nonzero is atan(s).is_nonzero + assert s.is_extended_positive is atan(s).is_positive + assert s.is_extended_negative is atan(s).is_negative + assert s.is_extended_nonpositive is atan(s).is_nonpositive + assert s.is_extended_nonnegative is atan(s).is_nonnegative + assert s.is_extended_nonzero is atan(s).is_extended_nonzero + assert s.is_extended_positive is atan(s).is_extended_positive + assert s.is_extended_negative is atan(s).is_extended_negative + assert s.is_extended_nonpositive is atan(s).is_extended_nonpositive + assert s.is_extended_nonnegative is atan(s).is_extended_nonnegative + + +def test_atan_rewrite(): + assert atan(x).rewrite(log) == I*(log(1 - I*x)-log(1 + I*x))/2 + assert atan(x).rewrite(asin) == (-asin(1/sqrt(x**2 + 1)) + pi/2)*sqrt(x**2)/x + assert atan(x).rewrite(acos) == sqrt(x**2)*acos(1/sqrt(x**2 + 1))/x + assert atan(x).rewrite(acot) == acot(1/x) + assert atan(x).rewrite(asec) == sqrt(x**2)*asec(sqrt(x**2 + 1))/x + assert atan(x).rewrite(acsc) == (-acsc(sqrt(x**2 + 1)) + pi/2)*sqrt(x**2)/x + + assert atan(-5*I).evalf() == atan(x).rewrite(log).evalf(subs={x:-5*I}) + assert atan(5*I).evalf() == atan(x).rewrite(log).evalf(subs={x:5*I}) + + +def test_atan_fdiff(): + assert atan(x).fdiff() == 1/(x**2 + 1) + raises(ArgumentIndexError, lambda: atan(x).fdiff(2)) + + +def test_atan_leading_term(): + assert atan(x).as_leading_term(x) == x + assert atan(1/x).as_leading_term(x, cdir=1) == pi/2 + assert atan(1/x).as_leading_term(x, cdir=-1) == -pi/2 + # Tests concerning branch points + assert atan(x + I).as_leading_term(x, cdir=1) == -I*log(x)/2 + pi/4 + I*log(2)/2 + assert atan(x + I).as_leading_term(x, cdir=-1) == -I*log(x)/2 - 3*pi/4 + I*log(2)/2 + assert atan(x - I).as_leading_term(x, cdir=1) == I*log(x)/2 + pi/4 - I*log(2)/2 + assert atan(x - I).as_leading_term(x, cdir=-1) == I*log(x)/2 + pi/4 - I*log(2)/2 + # Tests concerning points lying on branch cuts + assert atan(x + 2*I).as_leading_term(x, cdir=1) == I*atanh(2) + assert atan(x + 2*I).as_leading_term(x, cdir=-1) == -pi + I*atanh(2) + assert atan(x - 2*I).as_leading_term(x, cdir=1) == pi - I*atanh(2) + assert atan(x - 2*I).as_leading_term(x, cdir=-1) == -I*atanh(2) + # Tests concerning re(ndir) == 0 + assert atan(2*I - I*x - x**2).as_leading_term(x, cdir=1) == -pi/2 + I*log(3)/2 + assert atan(2*I - I*x - x**2).as_leading_term(x, cdir=-1) == -pi/2 + I*log(3)/2 + + +def test_atan2(): + assert atan2.nargs == FiniteSet(2) + assert atan2(0, 0) is S.NaN + assert atan2(0, 1) == 0 + assert atan2(1, 1) == pi/4 + assert atan2(1, 0) == pi/2 + assert atan2(1, -1) == pi*Rational(3, 4) + assert atan2(0, -1) == pi + assert atan2(-1, -1) == pi*Rational(-3, 4) + assert atan2(-1, 0) == -pi/2 + assert atan2(-1, 1) == -pi/4 + i = symbols('i', imaginary=True) + r = symbols('r', real=True) + eq = atan2(r, i) + ans = -I*log((i + I*r)/sqrt(i**2 + r**2)) + reps = ((r, 2), (i, I)) + assert eq.subs(reps) == ans.subs(reps) + + x = Symbol('x', negative=True) + y = Symbol('y', negative=True) + assert atan2(y, x) == atan(y/x) - pi + y = Symbol('y', nonnegative=True) + assert atan2(y, x) == atan(y/x) + pi + y = Symbol('y') + assert atan2(y, x) == atan2(y, x, evaluate=False) + + u = Symbol("u", positive=True) + assert atan2(0, u) == 0 + u = Symbol("u", negative=True) + assert atan2(0, u) == pi + + assert atan2(y, oo) == 0 + assert atan2(y, -oo)== 2*pi*Heaviside(re(y), S.Half) - pi + + assert atan2(y, x).rewrite(log) == -I*log((x + I*y)/sqrt(x**2 + y**2)) + assert atan2(0, 0) is S.NaN + + ex = atan2(y, x) - arg(x + I*y) + assert ex.subs({x:2, y:3}).rewrite(arg) == 0 + assert ex.subs({x:2, y:3*I}).rewrite(arg) == -pi - I*log(sqrt(5)*I/5) + assert ex.subs({x:2*I, y:3}).rewrite(arg) == -pi/2 - I*log(sqrt(5)*I) + assert ex.subs({x:2*I, y:3*I}).rewrite(arg) == -pi + atan(Rational(2, 3)) + atan(Rational(3, 2)) + i = symbols('i', imaginary=True) + r = symbols('r', real=True) + e = atan2(i, r) + rewrite = e.rewrite(arg) + reps = {i: I, r: -2} + assert rewrite == -I*log(abs(I*i + r)/sqrt(abs(i**2 + r**2))) + arg((I*i + r)/sqrt(i**2 + r**2)) + assert (e - rewrite).subs(reps).equals(0) + + assert atan2(0, x).rewrite(atan) == Piecewise((pi, re(x) < 0), + (0, Ne(x, 0)), + (nan, True)) + assert atan2(0, r).rewrite(atan) == Piecewise((pi, r < 0), (0, Ne(r, 0)), (S.NaN, True)) + assert atan2(0, i),rewrite(atan) == 0 + assert atan2(0, r + i).rewrite(atan) == Piecewise((pi, r < 0), (0, True)) + + assert atan2(y, x).rewrite(atan) == Piecewise( + (2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)), + (pi, re(x) < 0), + (0, (re(x) > 0) | Ne(im(x), 0)), + (nan, True)) + assert conjugate(atan2(x, y)) == atan2(conjugate(x), conjugate(y)) + + assert diff(atan2(y, x), x) == -y/(x**2 + y**2) + assert diff(atan2(y, x), y) == x/(x**2 + y**2) + + assert simplify(diff(atan2(y, x).rewrite(log), x)) == -y/(x**2 + y**2) + assert simplify(diff(atan2(y, x).rewrite(log), y)) == x/(x**2 + y**2) + + assert str(atan2(1, 2).evalf(5)) == '0.46365' + raises(ArgumentIndexError, lambda: atan2(x, y).fdiff(3)) + +def test_issue_17461(): + class A(Symbol): + is_extended_real = True + + def _eval_evalf(self, prec): + return Float(5.0) + + x = A('X') + y = A('Y') + assert abs(atan2(x, y).evalf() - 0.785398163397448) <= 1e-10 + +def test_acot(): + assert acot(nan) is nan + + assert acot.nargs == FiniteSet(1) + assert acot(-oo) == 0 + assert acot(oo) == 0 + assert acot(zoo) == 0 + assert acot(1) == pi/4 + assert acot(0) == pi/2 + assert acot(sqrt(3)/3) == pi/3 + assert acot(1/sqrt(3)) == pi/3 + assert acot(-1/sqrt(3)) == -pi/3 + assert acot(x).diff(x) == -1/(1 + x**2) + + assert acot(r).is_extended_real is True + + assert acot(I*pi) == -I*acoth(pi) + assert acot(-2*I) == I*acoth(2) + assert acot(x).is_positive is None + assert acot(n).is_positive is False + assert acot(p).is_positive is True + assert acot(I).is_positive is False + assert acot(Rational(1, 4)).is_rational is False + assert unchanged(acot, cot(x)) + assert unchanged(acot, tan(x)) + assert acot(cot(Rational(1, 4))) == Rational(1, 4) + assert acot(tan(Rational(-1, 4))) == Rational(1, 4) - pi/2 + + +def test_acot_rewrite(): + assert acot(x).rewrite(log) == I*(log(1 - I/x)-log(1 + I/x))/2 + assert acot(x).rewrite(asin) == x*(-asin(sqrt(-x**2)/sqrt(-x**2 - 1)) + pi/2)*sqrt(x**(-2)) + assert acot(x).rewrite(acos) == x*sqrt(x**(-2))*acos(sqrt(-x**2)/sqrt(-x**2 - 1)) + assert acot(x).rewrite(atan) == atan(1/x) + assert acot(x).rewrite(asec) == x*sqrt(x**(-2))*asec(sqrt((x**2 + 1)/x**2)) + assert acot(x).rewrite(acsc) == x*(-acsc(sqrt((x**2 + 1)/x**2)) + pi/2)*sqrt(x**(-2)) + + assert acot(-I/5).evalf() == acot(x).rewrite(log).evalf(subs={x:-I/5}) + assert acot(I/5).evalf() == acot(x).rewrite(log).evalf(subs={x:I/5}) + + +def test_acot_fdiff(): + assert acot(x).fdiff() == -1/(x**2 + 1) + raises(ArgumentIndexError, lambda: acot(x).fdiff(2)) + +def test_acot_leading_term(): + assert acot(1/x).as_leading_term(x) == x + # Tests concerning branch points + assert acot(x + I).as_leading_term(x, cdir=1) == I*log(x)/2 + pi/4 - I*log(2)/2 + assert acot(x + I).as_leading_term(x, cdir=-1) == I*log(x)/2 + pi/4 - I*log(2)/2 + assert acot(x - I).as_leading_term(x, cdir=1) == -I*log(x)/2 + pi/4 + I*log(2)/2 + assert acot(x - I).as_leading_term(x, cdir=-1) == -I*log(x)/2 - 3*pi/4 + I*log(2)/2 + # Tests concerning points lying on branch cuts + assert acot(x).as_leading_term(x, cdir=1) == pi/2 + assert acot(x).as_leading_term(x, cdir=-1) == -pi/2 + assert acot(x + I/2).as_leading_term(x, cdir=1) == pi - I*acoth(S(1)/2) + assert acot(x + I/2).as_leading_term(x, cdir=-1) == -I*acoth(S(1)/2) + assert acot(x - I/2).as_leading_term(x, cdir=1) == I*acoth(S(1)/2) + assert acot(x - I/2).as_leading_term(x, cdir=-1) == -pi + I*acoth(S(1)/2) + # Tests concerning re(ndir) == 0 + assert acot(I/2 - I*x - x**2).as_leading_term(x, cdir=1) == -pi/2 - I*log(3)/2 + assert acot(I/2 - I*x - x**2).as_leading_term(x, cdir=-1) == -pi/2 - I*log(3)/2 + + +def test_attributes(): + assert sin(x).args == (x,) + + +def test_sincos_rewrite(): + assert sin(pi/2 - x) == cos(x) + assert sin(pi - x) == sin(x) + assert cos(pi/2 - x) == sin(x) + assert cos(pi - x) == -cos(x) + + +def _check_even_rewrite(func, arg): + """Checks that the expr has been rewritten using f(-x) -> f(x) + arg : -x + """ + return func(arg).args[0] == -arg + + +def _check_odd_rewrite(func, arg): + """Checks that the expr has been rewritten using f(-x) -> -f(x) + arg : -x + """ + return func(arg).func.is_Mul + + +def _check_no_rewrite(func, arg): + """Checks that the expr is not rewritten""" + return func(arg).args[0] == arg + + +def test_evenodd_rewrite(): + a = cos(2) # negative + b = sin(1) # positive + even = [cos] + odd = [sin, tan, cot, asin, atan, acot] + with_minus = [-1, -2**1024 * E, -pi/105, -x*y, -x - y] + for func in even: + for expr in with_minus: + assert _check_even_rewrite(func, expr) + assert _check_no_rewrite(func, a*b) + assert func( + x - y) == func(y - x) # it doesn't matter which form is canonical + for func in odd: + for expr in with_minus: + assert _check_odd_rewrite(func, expr) + assert _check_no_rewrite(func, a*b) + assert func( + x - y) == -func(y - x) # it doesn't matter which form is canonical + + +def test_as_leading_term_issue_5272(): + assert sin(x).as_leading_term(x) == x + assert cos(x).as_leading_term(x) == 1 + assert tan(x).as_leading_term(x) == x + assert cot(x).as_leading_term(x) == 1/x + + +def test_leading_terms(): + assert sin(1/x).as_leading_term(x) == AccumBounds(-1, 1) + assert sin(S.Half).as_leading_term(x) == sin(S.Half) + assert cos(1/x).as_leading_term(x) == AccumBounds(-1, 1) + assert cos(S.Half).as_leading_term(x) == cos(S.Half) + assert sec(1/x).as_leading_term(x) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert csc(1/x).as_leading_term(x) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert tan(1/x).as_leading_term(x) == AccumBounds(S.NegativeInfinity, S.Infinity) + assert cot(1/x).as_leading_term(x) == AccumBounds(S.NegativeInfinity, S.Infinity) + + # https://github.com/sympy/sympy/issues/21038 + f = sin(pi*(x + 4))/(3*x) + assert f.as_leading_term(x) == pi/3 + + +def test_atan2_expansion(): + assert cancel(atan2(x**2, x + 1).diff(x) - atan(x**2/(x + 1)).diff(x)) == 0 + assert cancel(atan(y/x).series(y, 0, 5) - atan2(y, x).series(y, 0, 5) + + atan2(0, x) - atan(0)) == O(y**5) + assert cancel(atan(y/x).series(x, 1, 4) - atan2(y, x).series(x, 1, 4) + + atan2(y, 1) - atan(y)) == O((x - 1)**4, (x, 1)) + assert cancel(atan((y + x)/x).series(x, 1, 3) - atan2(y + x, x).series(x, 1, 3) + + atan2(1 + y, 1) - atan(1 + y)) == O((x - 1)**3, (x, 1)) + assert Matrix([atan2(y, x)]).jacobian([y, x]) == \ + Matrix([[x/(y**2 + x**2), -y/(y**2 + x**2)]]) + + +def test_aseries(): + def t(n, v, d, e): + assert abs( + n(1/v).evalf() - n(1/x).series(x, dir=d).removeO().subs(x, v)) < e + t(atan, 0.1, '+', 1e-5) + t(atan, -0.1, '-', 1e-5) + t(acot, 0.1, '+', 1e-5) + t(acot, -0.1, '-', 1e-5) + + +def test_issue_4420(): + i = Symbol('i', integer=True) + e = Symbol('e', even=True) + o = Symbol('o', odd=True) + + # unknown parity for variable + assert cos(4*i*pi) == 1 + assert sin(4*i*pi) == 0 + assert tan(4*i*pi) == 0 + assert cot(4*i*pi) is zoo + + assert cos(3*i*pi) == cos(pi*i) # +/-1 + assert sin(3*i*pi) == 0 + assert tan(3*i*pi) == 0 + assert cot(3*i*pi) is zoo + + assert cos(4.0*i*pi) == 1 + assert sin(4.0*i*pi) == 0 + assert tan(4.0*i*pi) == 0 + assert cot(4.0*i*pi) is zoo + + assert cos(3.0*i*pi) == cos(pi*i) # +/-1 + assert sin(3.0*i*pi) == 0 + assert tan(3.0*i*pi) == 0 + assert cot(3.0*i*pi) is zoo + + assert cos(4.5*i*pi) == cos(0.5*pi*i) + assert sin(4.5*i*pi) == sin(0.5*pi*i) + assert tan(4.5*i*pi) == tan(0.5*pi*i) + assert cot(4.5*i*pi) == cot(0.5*pi*i) + + # parity of variable is known + assert cos(4*e*pi) == 1 + assert sin(4*e*pi) == 0 + assert tan(4*e*pi) == 0 + assert cot(4*e*pi) is zoo + + assert cos(3*e*pi) == 1 + assert sin(3*e*pi) == 0 + assert tan(3*e*pi) == 0 + assert cot(3*e*pi) is zoo + + assert cos(4.0*e*pi) == 1 + assert sin(4.0*e*pi) == 0 + assert tan(4.0*e*pi) == 0 + assert cot(4.0*e*pi) is zoo + + assert cos(3.0*e*pi) == 1 + assert sin(3.0*e*pi) == 0 + assert tan(3.0*e*pi) == 0 + assert cot(3.0*e*pi) is zoo + + assert cos(4.5*e*pi) == cos(0.5*pi*e) + assert sin(4.5*e*pi) == sin(0.5*pi*e) + assert tan(4.5*e*pi) == tan(0.5*pi*e) + assert cot(4.5*e*pi) == cot(0.5*pi*e) + + assert cos(4*o*pi) == 1 + assert sin(4*o*pi) == 0 + assert tan(4*o*pi) == 0 + assert cot(4*o*pi) is zoo + + assert cos(3*o*pi) == -1 + assert sin(3*o*pi) == 0 + assert tan(3*o*pi) == 0 + assert cot(3*o*pi) is zoo + + assert cos(4.0*o*pi) == 1 + assert sin(4.0*o*pi) == 0 + assert tan(4.0*o*pi) == 0 + assert cot(4.0*o*pi) is zoo + + assert cos(3.0*o*pi) == -1 + assert sin(3.0*o*pi) == 0 + assert tan(3.0*o*pi) == 0 + assert cot(3.0*o*pi) is zoo + + assert cos(4.5*o*pi) == cos(0.5*pi*o) + assert sin(4.5*o*pi) == sin(0.5*pi*o) + assert tan(4.5*o*pi) == tan(0.5*pi*o) + assert cot(4.5*o*pi) == cot(0.5*pi*o) + + # x could be imaginary + assert cos(4*x*pi) == cos(4*pi*x) + assert sin(4*x*pi) == sin(4*pi*x) + assert tan(4*x*pi) == tan(4*pi*x) + assert cot(4*x*pi) == cot(4*pi*x) + + assert cos(3*x*pi) == cos(3*pi*x) + assert sin(3*x*pi) == sin(3*pi*x) + assert tan(3*x*pi) == tan(3*pi*x) + assert cot(3*x*pi) == cot(3*pi*x) + + assert cos(4.0*x*pi) == cos(4.0*pi*x) + assert sin(4.0*x*pi) == sin(4.0*pi*x) + assert tan(4.0*x*pi) == tan(4.0*pi*x) + assert cot(4.0*x*pi) == cot(4.0*pi*x) + + assert cos(3.0*x*pi) == cos(3.0*pi*x) + assert sin(3.0*x*pi) == sin(3.0*pi*x) + assert tan(3.0*x*pi) == tan(3.0*pi*x) + assert cot(3.0*x*pi) == cot(3.0*pi*x) + + assert cos(4.5*x*pi) == cos(4.5*pi*x) + assert sin(4.5*x*pi) == sin(4.5*pi*x) + assert tan(4.5*x*pi) == tan(4.5*pi*x) + assert cot(4.5*x*pi) == cot(4.5*pi*x) + + +def test_inverses(): + raises(AttributeError, lambda: sin(x).inverse()) + raises(AttributeError, lambda: cos(x).inverse()) + assert tan(x).inverse() == atan + assert cot(x).inverse() == acot + raises(AttributeError, lambda: csc(x).inverse()) + raises(AttributeError, lambda: sec(x).inverse()) + assert asin(x).inverse() == sin + assert acos(x).inverse() == cos + assert atan(x).inverse() == tan + assert acot(x).inverse() == cot + + +def test_real_imag(): + a, b = symbols('a b', real=True) + z = a + b*I + for deep in [True, False]: + assert sin( + z).as_real_imag(deep=deep) == (sin(a)*cosh(b), cos(a)*sinh(b)) + assert cos( + z).as_real_imag(deep=deep) == (cos(a)*cosh(b), -sin(a)*sinh(b)) + assert tan(z).as_real_imag(deep=deep) == (sin(2*a)/(cos(2*a) + + cosh(2*b)), sinh(2*b)/(cos(2*a) + cosh(2*b))) + assert cot(z).as_real_imag(deep=deep) == (-sin(2*a)/(cos(2*a) - + cosh(2*b)), sinh(2*b)/(cos(2*a) - cosh(2*b))) + assert sin(a).as_real_imag(deep=deep) == (sin(a), 0) + assert cos(a).as_real_imag(deep=deep) == (cos(a), 0) + assert tan(a).as_real_imag(deep=deep) == (tan(a), 0) + assert cot(a).as_real_imag(deep=deep) == (cot(a), 0) + + +@slow +def test_sincos_rewrite_sqrt(): + # equivalent to testing rewrite(pow) + for p in [1, 3, 5, 17]: + for t in [1, 8]: + n = t*p + # The vertices `exp(i*pi/n)` of a regular `n`-gon can + # be expressed by means of nested square roots if and + # only if `n` is a product of Fermat primes, `p`, and + # powers of 2, `t'. The code aims to check all vertices + # not belonging to an `m`-gon for `m < n`(`gcd(i, n) == 1`). + # For large `n` this makes the test too slow, therefore + # the vertices are limited to those of index `i < 10`. + for i in range(1, min((n + 1)//2 + 1, 10)): + if 1 == gcd(i, n): + x = i*pi/n + s1 = sin(x).rewrite(sqrt) + c1 = cos(x).rewrite(sqrt) + assert not s1.has(cos, sin), "fails for %d*pi/%d" % (i, n) + assert not c1.has(cos, sin), "fails for %d*pi/%d" % (i, n) + assert 1e-3 > abs(sin(x.evalf(5)) - s1.evalf(2)), "fails for %d*pi/%d" % (i, n) + assert 1e-3 > abs(cos(x.evalf(5)) - c1.evalf(2)), "fails for %d*pi/%d" % (i, n) + assert cos(pi/14).rewrite(sqrt) == sqrt(cos(pi/7)/2 + S.Half) + assert cos(pi*Rational(-15, 2)/11, evaluate=False).rewrite( + sqrt) == -sqrt(-cos(pi*Rational(4, 11))/2 + S.Half) + assert cos(Mul(2, pi, S.Half, evaluate=False), evaluate=False).rewrite( + sqrt) == -1 + e = cos(pi/3/17) # don't use pi/15 since that is caught at instantiation + a = ( + -3*sqrt(-sqrt(17) + 17)*sqrt(sqrt(17) + 17)/64 - + 3*sqrt(34)*sqrt(sqrt(17) + 17)/128 - sqrt(sqrt(17) + + 17)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + 17) + + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/64 - sqrt(-sqrt(17) + + 17)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/128 - Rational(1, 32) + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/64 + + 3*sqrt(2)*sqrt(sqrt(17) + 17)/128 + sqrt(34)*sqrt(-sqrt(17) + 17)/128 + + 13*sqrt(2)*sqrt(-sqrt(17) + 17)/128 + sqrt(17)*sqrt(-sqrt(17) + + 17)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + 17) + + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/128 + 5*sqrt(17)/32 + + sqrt(3)*sqrt(-sqrt(2)*sqrt(sqrt(17) + 17)*sqrt(sqrt(17)/32 + + sqrt(2)*sqrt(-sqrt(17) + 17)/32 + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/32 + Rational(15, 32))/8 - + 5*sqrt(2)*sqrt(sqrt(17)/32 + sqrt(2)*sqrt(-sqrt(17) + 17)/32 + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/32 + + Rational(15, 32))*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/64 - + 3*sqrt(2)*sqrt(-sqrt(17) + 17)*sqrt(sqrt(17)/32 + + sqrt(2)*sqrt(-sqrt(17) + 17)/32 + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/32 + Rational(15, 32))/32 + + sqrt(34)*sqrt(sqrt(17)/32 + sqrt(2)*sqrt(-sqrt(17) + 17)/32 + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/32 + + Rational(15, 32))*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/64 + + sqrt(sqrt(17)/32 + sqrt(2)*sqrt(-sqrt(17) + 17)/32 + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/32 + Rational(15, 32))/2 + + S.Half + sqrt(-sqrt(17) + 17)*sqrt(sqrt(17)/32 + sqrt(2)*sqrt(-sqrt(17) + + 17)/32 + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - + sqrt(2)*sqrt(-sqrt(17) + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + + 6*sqrt(17) + 34)/32 + Rational(15, 32))*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - + sqrt(2)*sqrt(-sqrt(17) + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + + 6*sqrt(17) + 34)/32 + sqrt(34)*sqrt(-sqrt(17) + 17)*sqrt(sqrt(17)/32 + + sqrt(2)*sqrt(-sqrt(17) + 17)/32 + + sqrt(2)*sqrt(-8*sqrt(2)*sqrt(sqrt(17) + 17) - sqrt(2)*sqrt(-sqrt(17) + + 17) + sqrt(34)*sqrt(-sqrt(17) + 17) + 6*sqrt(17) + 34)/32 + + Rational(15, 32))/32)/2) + assert e.rewrite(sqrt) == a + assert e.n() == a.n() + # coverage of fermatCoords: multiplicity > 1; the following could be + # different but that portion of the code should be tested in some way + assert cos(pi/9/17).rewrite(sqrt) == \ + sin(pi/9)*sin(pi*Rational(2, 17)) + cos(pi/9)*cos(pi*Rational(2, 17)) + + +@slow +def test_sincos_rewrite_sqrt_257(): + assert cos(pi/257).rewrite(sqrt).evalf(64) == cos(pi/257).evalf(64) + + +@slow +def test_tancot_rewrite_sqrt(): + # equivalent to testing rewrite(pow) + for p in [1, 3, 5, 17]: + for t in [1, 8]: + n = t*p + for i in range(1, min((n + 1)//2 + 1, 10)): + if 1 == gcd(i, n): + x = i*pi/n + if 2*i != n and 3*i != 2*n: + t1 = tan(x).rewrite(sqrt) + assert not t1.has(cot, tan), "fails for %d*pi/%d" % (i, n) + assert 1e-3 > abs( tan(x.evalf(7)) - t1.evalf(4) ), "fails for %d*pi/%d" % (i, n) + if i != 0 and i != n: + c1 = cot(x).rewrite(sqrt) + assert not c1.has(cot, tan), "fails for %d*pi/%d" % (i, n) + assert 1e-3 > abs( cot(x.evalf(7)) - c1.evalf(4) ), "fails for %d*pi/%d" % (i, n) + + +def test_sec(): + x = symbols('x', real=True) + z = symbols('z') + + assert sec.nargs == FiniteSet(1) + + assert sec(zoo) is nan + assert sec(0) == 1 + assert sec(pi) == -1 + assert sec(pi/2) is zoo + assert sec(-pi/2) is zoo + assert sec(pi/6) == 2*sqrt(3)/3 + assert sec(pi/3) == 2 + assert sec(pi*Rational(5, 2)) is zoo + assert sec(pi*Rational(9, 7)) == -sec(pi*Rational(2, 7)) + assert sec(pi*Rational(3, 4)) == -sqrt(2) # issue 8421 + assert sec(I) == 1/cosh(1) + assert sec(x*I) == 1/cosh(x) + assert sec(-x) == sec(x) + + assert sec(asec(x)) == x + + assert sec(z).conjugate() == sec(conjugate(z)) + + assert (sec(z).as_real_imag() == + (cos(re(z))*cosh(im(z))/(sin(re(z))**2*sinh(im(z))**2 + + cos(re(z))**2*cosh(im(z))**2), + sin(re(z))*sinh(im(z))/(sin(re(z))**2*sinh(im(z))**2 + + cos(re(z))**2*cosh(im(z))**2))) + + assert sec(x).expand(trig=True) == 1/cos(x) + assert sec(2*x).expand(trig=True) == 1/(2*cos(x)**2 - 1) + + assert sec(x).is_extended_real == True + assert sec(z).is_real == None + + assert sec(a).is_algebraic is None + assert sec(na).is_algebraic is False + + assert sec(x).as_leading_term() == sec(x) + + assert sec(0, evaluate=False).is_finite == True + assert sec(x).is_finite == None + assert sec(pi/2, evaluate=False).is_finite == False + + assert series(sec(x), x, x0=0, n=6) == 1 + x**2/2 + 5*x**4/24 + O(x**6) + + # https://github.com/sympy/sympy/issues/7166 + assert series(sqrt(sec(x))) == 1 + x**2/4 + 7*x**4/96 + O(x**6) + + # https://github.com/sympy/sympy/issues/7167 + assert (series(sqrt(sec(x)), x, x0=pi*3/2, n=4) == + 1/sqrt(x - pi*Rational(3, 2)) + (x - pi*Rational(3, 2))**Rational(3, 2)/12 + + (x - pi*Rational(3, 2))**Rational(7, 2)/160 + O((x - pi*Rational(3, 2))**4, (x, pi*Rational(3, 2)))) + + assert sec(x).diff(x) == tan(x)*sec(x) + + # Taylor Term checks + assert sec(z).taylor_term(4, z) == 5*z**4/24 + assert sec(z).taylor_term(6, z) == 61*z**6/720 + assert sec(z).taylor_term(5, z) == 0 + + +def test_sec_rewrite(): + assert sec(x).rewrite(exp) == 1/(exp(I*x)/2 + exp(-I*x)/2) + assert sec(x).rewrite(cos) == 1/cos(x) + assert sec(x).rewrite(tan) == (tan(x/2)**2 + 1)/(-tan(x/2)**2 + 1) + assert sec(x).rewrite(pow) == sec(x) + assert sec(x).rewrite(sqrt) == sec(x) + assert sec(z).rewrite(cot) == (cot(z/2)**2 + 1)/(cot(z/2)**2 - 1) + assert sec(x).rewrite(sin) == 1 / sin(x + pi / 2, evaluate=False) + assert sec(x).rewrite(tan) == (tan(x / 2)**2 + 1) / (-tan(x / 2)**2 + 1) + assert sec(x).rewrite(csc) == csc(-x + pi/2, evaluate=False) + assert sec(x).rewrite(besselj) == Piecewise( + (sqrt(2)/(sqrt(pi*x)*besselj(-S.Half, x)), Ne(x, 0)), + (1, True) + ) + assert sec(x).rewrite(besselj).subs(x, 0) == sec(0) + + +def test_sec_fdiff(): + assert sec(x).fdiff() == tan(x)*sec(x) + raises(ArgumentIndexError, lambda: sec(x).fdiff(2)) + + +def test_csc(): + x = symbols('x', real=True) + z = symbols('z') + + # https://github.com/sympy/sympy/issues/6707 + cosecant = csc('x') + alternate = 1/sin('x') + assert cosecant.equals(alternate) == True + assert alternate.equals(cosecant) == True + + assert csc.nargs == FiniteSet(1) + + assert csc(0) is zoo + assert csc(pi) is zoo + assert csc(zoo) is nan + + assert csc(pi/2) == 1 + assert csc(-pi/2) == -1 + assert csc(pi/6) == 2 + assert csc(pi/3) == 2*sqrt(3)/3 + assert csc(pi*Rational(5, 2)) == 1 + assert csc(pi*Rational(9, 7)) == -csc(pi*Rational(2, 7)) + assert csc(pi*Rational(3, 4)) == sqrt(2) # issue 8421 + assert csc(I) == -I/sinh(1) + assert csc(x*I) == -I/sinh(x) + assert csc(-x) == -csc(x) + + assert csc(acsc(x)) == x + + assert csc(z).conjugate() == csc(conjugate(z)) + + assert (csc(z).as_real_imag() == + (sin(re(z))*cosh(im(z))/(sin(re(z))**2*cosh(im(z))**2 + + cos(re(z))**2*sinh(im(z))**2), + -cos(re(z))*sinh(im(z))/(sin(re(z))**2*cosh(im(z))**2 + + cos(re(z))**2*sinh(im(z))**2))) + + assert csc(x).expand(trig=True) == 1/sin(x) + assert csc(2*x).expand(trig=True) == 1/(2*sin(x)*cos(x)) + + assert csc(x).is_extended_real == True + assert csc(z).is_real == None + + assert csc(a).is_algebraic is None + assert csc(na).is_algebraic is False + + assert csc(x).as_leading_term() == csc(x) + + assert csc(0, evaluate=False).is_finite == False + assert csc(x).is_finite == None + assert csc(pi/2, evaluate=False).is_finite == True + + assert series(csc(x), x, x0=pi/2, n=6) == \ + 1 + (x - pi/2)**2/2 + 5*(x - pi/2)**4/24 + O((x - pi/2)**6, (x, pi/2)) + assert series(csc(x), x, x0=0, n=6) == \ + 1/x + x/6 + 7*x**3/360 + 31*x**5/15120 + O(x**6) + + assert csc(x).diff(x) == -cot(x)*csc(x) + + assert csc(x).taylor_term(2, x) == 0 + assert csc(x).taylor_term(3, x) == 7*x**3/360 + assert csc(x).taylor_term(5, x) == 31*x**5/15120 + raises(ArgumentIndexError, lambda: csc(x).fdiff(2)) + + +def test_asec(): + z = Symbol('z', zero=True) + assert asec(z) is zoo + assert asec(nan) is nan + assert asec(1) == 0 + assert asec(-1) == pi + assert asec(oo) == pi/2 + assert asec(-oo) == pi/2 + assert asec(zoo) == pi/2 + + assert asec(sec(pi*Rational(13, 4))) == pi*Rational(3, 4) + assert asec(1 + sqrt(5)) == pi*Rational(2, 5) + assert asec(2/sqrt(3)) == pi/6 + assert asec(sqrt(4 - 2*sqrt(2))) == pi/8 + assert asec(-sqrt(4 + 2*sqrt(2))) == pi*Rational(5, 8) + assert asec(sqrt(2 + 2*sqrt(5)/5)) == pi*Rational(3, 10) + assert asec(-sqrt(2 + 2*sqrt(5)/5)) == pi*Rational(7, 10) + assert asec(sqrt(2) - sqrt(6)) == pi*Rational(11, 12) + + for d in [3, 4, 6]: + for num in range(d): + if gcd(num, d) == 1: + assert asec(sec(num*pi/d)) == num*pi/d + assert asec(-sec(num*pi/d)) == pi - num*pi/d + assert asec(csc(num*pi/d)) == pi/2 - acsc(csc(num*pi/d)) + assert asec(-csc(num*pi/d)) == pi/2 - acsc(-csc(num*pi/d)) + + assert asec(x).diff(x) == 1/(x**2*sqrt(1 - 1/x**2)) + + assert asec(x).rewrite(log) == I*log(sqrt(1 - 1/x**2) + I/x) + pi/2 + assert asec(x).rewrite(asin) == -asin(1/x) + pi/2 + assert asec(x).rewrite(acos) == acos(1/x) + assert asec(x).rewrite(atan) == \ + pi*(1 - sqrt(x**2)/x)/2 + sqrt(x**2)*atan(sqrt(x**2 - 1))/x + assert asec(x).rewrite(acot) == \ + pi*(1 - sqrt(x**2)/x)/2 + sqrt(x**2)*acot(1/sqrt(x**2 - 1))/x + assert asec(x).rewrite(acsc) == -acsc(x) + pi/2 + raises(ArgumentIndexError, lambda: asec(x).fdiff(2)) + + +def test_asec_is_real(): + assert asec(S.Half).is_real is False + n = Symbol('n', positive=True, integer=True) + assert asec(n).is_extended_real is True + assert asec(x).is_real is None + assert asec(r).is_real is None + t = Symbol('t', real=False, finite=True) + assert asec(t).is_real is False + + +def test_asec_leading_term(): + assert asec(1/x).as_leading_term(x) == pi/2 + # Tests concerning branch points + assert asec(x + 1).as_leading_term(x) == sqrt(2)*sqrt(x) + assert asec(x - 1).as_leading_term(x) == pi + # Tests concerning points lying on branch cuts + assert asec(x).as_leading_term(x, cdir=1) == -I*log(x) + I*log(2) + assert asec(x).as_leading_term(x, cdir=-1) == I*log(x) + 2*pi - I*log(2) + assert asec(I*x + 1/2).as_leading_term(x, cdir=1) == asec(1/2) + assert asec(-I*x + 1/2).as_leading_term(x, cdir=1) == -asec(1/2) + assert asec(I*x - 1/2).as_leading_term(x, cdir=1) == 2*pi - asec(-1/2) + assert asec(-I*x - 1/2).as_leading_term(x, cdir=1) == asec(-1/2) + # Tests concerning im(ndir) == 0 + assert asec(-I*x**2 + x - S(1)/2).as_leading_term(x, cdir=1) == pi + I*log(2 - sqrt(3)) + assert asec(-I*x**2 + x - S(1)/2).as_leading_term(x, cdir=-1) == pi + I*log(2 - sqrt(3)) + + +def test_asec_series(): + assert asec(x).series(x, 0, 9) == \ + I*log(2) - I*log(x) - I*x**2/4 - 3*I*x**4/32 \ + - 5*I*x**6/96 - 35*I*x**8/1024 + O(x**9) + t4 = asec(x).taylor_term(4, x) + assert t4 == -3*I*x**4/32 + assert asec(x).taylor_term(6, x, t4, 0) == -5*I*x**6/96 + + +def test_acsc(): + assert acsc(nan) is nan + assert acsc(1) == pi/2 + assert acsc(-1) == -pi/2 + assert acsc(oo) == 0 + assert acsc(-oo) == 0 + assert acsc(zoo) == 0 + assert acsc(0) is zoo + + assert acsc(csc(3)) == -3 + pi + assert acsc(csc(4)) == -4 + pi + assert acsc(csc(6)) == 6 - 2*pi + assert unchanged(acsc, csc(x)) + assert unchanged(acsc, sec(x)) + + assert acsc(2/sqrt(3)) == pi/3 + assert acsc(csc(pi*Rational(13, 4))) == -pi/4 + assert acsc(sqrt(2 + 2*sqrt(5)/5)) == pi/5 + assert acsc(-sqrt(2 + 2*sqrt(5)/5)) == -pi/5 + assert acsc(-2) == -pi/6 + assert acsc(-sqrt(4 + 2*sqrt(2))) == -pi/8 + assert acsc(sqrt(4 - 2*sqrt(2))) == pi*Rational(3, 8) + assert acsc(1 + sqrt(5)) == pi/10 + assert acsc(sqrt(2) - sqrt(6)) == pi*Rational(-5, 12) + + assert acsc(x).diff(x) == -1/(x**2*sqrt(1 - 1/x**2)) + + assert acsc(x).rewrite(log) == -I*log(sqrt(1 - 1/x**2) + I/x) + assert acsc(x).rewrite(asin) == asin(1/x) + assert acsc(x).rewrite(acos) == -acos(1/x) + pi/2 + assert acsc(x).rewrite(atan) == \ + (-atan(sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x + assert acsc(x).rewrite(acot) == (-acot(1/sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x + assert acsc(x).rewrite(asec) == -asec(x) + pi/2 + raises(ArgumentIndexError, lambda: acsc(x).fdiff(2)) + + +def test_csc_rewrite(): + assert csc(x).rewrite(pow) == csc(x) + assert csc(x).rewrite(sqrt) == csc(x) + + assert csc(x).rewrite(exp) == 2*I/(exp(I*x) - exp(-I*x)) + assert csc(x).rewrite(sin) == 1/sin(x) + assert csc(x).rewrite(tan) == (tan(x/2)**2 + 1)/(2*tan(x/2)) + assert csc(x).rewrite(cot) == (cot(x/2)**2 + 1)/(2*cot(x/2)) + assert csc(x).rewrite(cos) == 1/cos(x - pi/2, evaluate=False) + assert csc(x).rewrite(sec) == sec(-x + pi/2, evaluate=False) + + # issue 17349 + assert csc(1 - exp(-besselj(I, I))).rewrite(cos) == \ + -1/cos(-pi/2 - 1 + cos(I*besselj(I, I)) + + I*cos(-pi/2 + I*besselj(I, I), evaluate=False), evaluate=False) + assert csc(x).rewrite(besselj) == sqrt(2)/(sqrt(pi*x)*besselj(S.Half, x)) + assert csc(x).rewrite(besselj).subs(x, 0) == csc(0) + + +def test_acsc_leading_term(): + assert acsc(1/x).as_leading_term(x) == x + # Tests concerning branch points + assert acsc(x + 1).as_leading_term(x) == pi/2 + assert acsc(x - 1).as_leading_term(x) == -pi/2 + # Tests concerning points lying on branch cuts + assert acsc(x).as_leading_term(x, cdir=1) == I*log(x) + pi/2 - I*log(2) + assert acsc(x).as_leading_term(x, cdir=-1) == -I*log(x) - 3*pi/2 + I*log(2) + assert acsc(I*x + 1/2).as_leading_term(x, cdir=1) == acsc(1/2) + assert acsc(-I*x + 1/2).as_leading_term(x, cdir=1) == pi - acsc(1/2) + assert acsc(I*x - 1/2).as_leading_term(x, cdir=1) == -pi - acsc(-1/2) + assert acsc(-I*x - 1/2).as_leading_term(x, cdir=1) == -acsc(1/2) + # Tests concerning im(ndir) == 0 + assert acsc(-I*x**2 + x - S(1)/2).as_leading_term(x, cdir=1) == -pi/2 + I*log(sqrt(3) + 2) + assert acsc(-I*x**2 + x - S(1)/2).as_leading_term(x, cdir=-1) == -pi/2 + I*log(sqrt(3) + 2) + + +def test_acsc_series(): + assert acsc(x).series(x, 0, 9) == \ + -I*log(2) + pi/2 + I*log(x) + I*x**2/4 \ + + 3*I*x**4/32 + 5*I*x**6/96 + 35*I*x**8/1024 + O(x**9) + t6 = acsc(x).taylor_term(6, x) + assert t6 == 5*I*x**6/96 + assert acsc(x).taylor_term(8, x, t6, 0) == 35*I*x**8/1024 + + +def test_asin_nseries(): + assert asin(x + 2)._eval_nseries(x, 4, None, I) == -asin(2) + pi + \ + sqrt(3)*I*x/3 - sqrt(3)*I*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + assert asin(x + 2)._eval_nseries(x, 4, None, -I) == asin(2) - \ + sqrt(3)*I*x/3 + sqrt(3)*I*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + assert asin(x - 2)._eval_nseries(x, 4, None, I) == -asin(2) - \ + sqrt(3)*I*x/3 - sqrt(3)*I*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + assert asin(x - 2)._eval_nseries(x, 4, None, -I) == asin(2) - pi + \ + sqrt(3)*I*x/3 + sqrt(3)*I*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + # testing nseries for asin at branch points + assert asin(1 + x)._eval_nseries(x, 3, None) == pi/2 - sqrt(2)*sqrt(-x) - \ + sqrt(2)*(-x)**(S(3)/2)/12 - 3*sqrt(2)*(-x)**(S(5)/2)/160 + O(x**3) + assert asin(-1 + x)._eval_nseries(x, 3, None) == -pi/2 + sqrt(2)*sqrt(x) + \ + sqrt(2)*x**(S(3)/2)/12 + 3*sqrt(2)*x**(S(5)/2)/160 + O(x**3) + assert asin(exp(x))._eval_nseries(x, 3, None) == pi/2 - sqrt(2)*sqrt(-x) + \ + sqrt(2)*(-x)**(S(3)/2)/6 - sqrt(2)*(-x)**(S(5)/2)/120 + O(x**3) + assert asin(-exp(x))._eval_nseries(x, 3, None) == -pi/2 + sqrt(2)*sqrt(-x) - \ + sqrt(2)*(-x)**(S(3)/2)/6 + sqrt(2)*(-x)**(S(5)/2)/120 + O(x**3) + + +def test_acos_nseries(): + assert acos(x + 2)._eval_nseries(x, 4, None, I) == -acos(2) - sqrt(3)*I*x/3 + \ + sqrt(3)*I*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + assert acos(x + 2)._eval_nseries(x, 4, None, -I) == acos(2) + sqrt(3)*I*x/3 - \ + sqrt(3)*I*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + assert acos(x - 2)._eval_nseries(x, 4, None, I) == acos(-2) + sqrt(3)*I*x/3 + \ + sqrt(3)*I*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4) + assert acos(x - 2)._eval_nseries(x, 4, None, -I) == -acos(-2) + 2*pi - \ + sqrt(3)*I*x/3 - sqrt(3)*I*x**2/9 - sqrt(3)*I*x**3/18 + O(x**4) + # testing nseries for acos at branch points + assert acos(1 + x)._eval_nseries(x, 3, None) == sqrt(2)*sqrt(-x) + \ + sqrt(2)*(-x)**(S(3)/2)/12 + 3*sqrt(2)*(-x)**(S(5)/2)/160 + O(x**3) + assert acos(-1 + x)._eval_nseries(x, 3, None) == pi - sqrt(2)*sqrt(x) - \ + sqrt(2)*x**(S(3)/2)/12 - 3*sqrt(2)*x**(S(5)/2)/160 + O(x**3) + assert acos(exp(x))._eval_nseries(x, 3, None) == sqrt(2)*sqrt(-x) - \ + sqrt(2)*(-x)**(S(3)/2)/6 + sqrt(2)*(-x)**(S(5)/2)/120 + O(x**3) + assert acos(-exp(x))._eval_nseries(x, 3, None) == pi - sqrt(2)*sqrt(-x) + \ + sqrt(2)*(-x)**(S(3)/2)/6 - sqrt(2)*(-x)**(S(5)/2)/120 + O(x**3) + + +def test_atan_nseries(): + assert atan(x + 2*I)._eval_nseries(x, 4, None, 1) == I*atanh(2) - x/3 - \ + 2*I*x**2/9 + 13*x**3/81 + O(x**4) + assert atan(x + 2*I)._eval_nseries(x, 4, None, -1) == I*atanh(2) - pi - \ + x/3 - 2*I*x**2/9 + 13*x**3/81 + O(x**4) + assert atan(x - 2*I)._eval_nseries(x, 4, None, 1) == -I*atanh(2) + pi - \ + x/3 + 2*I*x**2/9 + 13*x**3/81 + O(x**4) + assert atan(x - 2*I)._eval_nseries(x, 4, None, -1) == -I*atanh(2) - x/3 + \ + 2*I*x**2/9 + 13*x**3/81 + O(x**4) + assert atan(1/x)._eval_nseries(x, 2, None, 1) == pi/2 - x + O(x**2) + assert atan(1/x)._eval_nseries(x, 2, None, -1) == -pi/2 - x + O(x**2) + # testing nseries for atan at branch points + assert atan(x + I)._eval_nseries(x, 4, None) == I*log(2)/2 + pi/4 - \ + I*log(x)/2 + x/4 + I*x**2/16 - x**3/48 + O(x**4) + assert atan(x - I)._eval_nseries(x, 4, None) == -I*log(2)/2 + pi/4 + \ + I*log(x)/2 + x/4 - I*x**2/16 - x**3/48 + O(x**4) + + +def test_acot_nseries(): + assert acot(x + S(1)/2*I)._eval_nseries(x, 4, None, 1) == -I*acoth(S(1)/2) + \ + pi - 4*x/3 + 8*I*x**2/9 + 112*x**3/81 + O(x**4) + assert acot(x + S(1)/2*I)._eval_nseries(x, 4, None, -1) == -I*acoth(S(1)/2) - \ + 4*x/3 + 8*I*x**2/9 + 112*x**3/81 + O(x**4) + assert acot(x - S(1)/2*I)._eval_nseries(x, 4, None, 1) == I*acoth(S(1)/2) - \ + 4*x/3 - 8*I*x**2/9 + 112*x**3/81 + O(x**4) + assert acot(x - S(1)/2*I)._eval_nseries(x, 4, None, -1) == I*acoth(S(1)/2) - \ + pi - 4*x/3 - 8*I*x**2/9 + 112*x**3/81 + O(x**4) + assert acot(x)._eval_nseries(x, 2, None, 1) == pi/2 - x + O(x**2) + assert acot(x)._eval_nseries(x, 2, None, -1) == -pi/2 - x + O(x**2) + # testing nseries for acot at branch points + assert acot(x + I)._eval_nseries(x, 4, None) == -I*log(2)/2 + pi/4 + \ + I*log(x)/2 - x/4 - I*x**2/16 + x**3/48 + O(x**4) + assert acot(x - I)._eval_nseries(x, 4, None) == I*log(2)/2 + pi/4 - \ + I*log(x)/2 - x/4 + I*x**2/16 + x**3/48 + O(x**4) + + +def test_asec_nseries(): + assert asec(x + S(1)/2)._eval_nseries(x, 4, None, I) == asec(S(1)/2) - \ + 4*sqrt(3)*I*x/3 + 8*sqrt(3)*I*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4) + assert asec(x + S(1)/2)._eval_nseries(x, 4, None, -I) == -asec(S(1)/2) + \ + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*I*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4) + assert asec(x - S(1)/2)._eval_nseries(x, 4, None, I) == -asec(-S(1)/2) + \ + 2*pi + 4*sqrt(3)*I*x/3 + 8*sqrt(3)*I*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4) + assert asec(x - S(1)/2)._eval_nseries(x, 4, None, -I) == asec(-S(1)/2) - \ + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*I*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4) + # testing nseries for asec at branch points + assert asec(1 + x)._eval_nseries(x, 3, None) == sqrt(2)*sqrt(x) - \ + 5*sqrt(2)*x**(S(3)/2)/12 + 43*sqrt(2)*x**(S(5)/2)/160 + O(x**3) + assert asec(-1 + x)._eval_nseries(x, 3, None) == pi - sqrt(2)*sqrt(-x) + \ + 5*sqrt(2)*(-x)**(S(3)/2)/12 - 43*sqrt(2)*(-x)**(S(5)/2)/160 + O(x**3) + assert asec(exp(x))._eval_nseries(x, 3, None) == sqrt(2)*sqrt(x) - \ + sqrt(2)*x**(S(3)/2)/6 + sqrt(2)*x**(S(5)/2)/120 + O(x**3) + assert asec(-exp(x))._eval_nseries(x, 3, None) == pi - sqrt(2)*sqrt(x) + \ + sqrt(2)*x**(S(3)/2)/6 - sqrt(2)*x**(S(5)/2)/120 + O(x**3) + + +def test_acsc_nseries(): + assert acsc(x + S(1)/2)._eval_nseries(x, 4, None, I) == acsc(S(1)/2) + \ + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*I*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4) + assert acsc(x + S(1)/2)._eval_nseries(x, 4, None, -I) == -acsc(S(1)/2) + \ + pi - 4*sqrt(3)*I*x/3 + 8*sqrt(3)*I*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4) + assert acsc(x - S(1)/2)._eval_nseries(x, 4, None, I) == acsc(S(1)/2) - pi -\ + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*I*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4) + assert acsc(x - S(1)/2)._eval_nseries(x, 4, None, -I) == -acsc(S(1)/2) + \ + 4*sqrt(3)*I*x/3 + 8*sqrt(3)*I*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4) + # testing nseries for acsc at branch points + assert acsc(1 + x)._eval_nseries(x, 3, None) == pi/2 - sqrt(2)*sqrt(x) + \ + 5*sqrt(2)*x**(S(3)/2)/12 - 43*sqrt(2)*x**(S(5)/2)/160 + O(x**3) + assert acsc(-1 + x)._eval_nseries(x, 3, None) == -pi/2 + sqrt(2)*sqrt(-x) - \ + 5*sqrt(2)*(-x)**(S(3)/2)/12 + 43*sqrt(2)*(-x)**(S(5)/2)/160 + O(x**3) + assert acsc(exp(x))._eval_nseries(x, 3, None) == pi/2 - sqrt(2)*sqrt(x) + \ + sqrt(2)*x**(S(3)/2)/6 - sqrt(2)*x**(S(5)/2)/120 + O(x**3) + assert acsc(-exp(x))._eval_nseries(x, 3, None) == -pi/2 + sqrt(2)*sqrt(x) - \ + sqrt(2)*x**(S(3)/2)/6 + sqrt(2)*x**(S(5)/2)/120 + O(x**3) + + +def test_issue_8653(): + n = Symbol('n', integer=True) + assert sin(n).is_irrational is None + assert cos(n).is_irrational is None + assert tan(n).is_irrational is None + + +def test_issue_9157(): + n = Symbol('n', integer=True, positive=True) + assert atan(n - 1).is_nonnegative is True + + +def test_trig_period(): + x, y = symbols('x, y') + + assert sin(x).period() == 2*pi + assert cos(x).period() == 2*pi + assert tan(x).period() == pi + assert cot(x).period() == pi + assert sec(x).period() == 2*pi + assert csc(x).period() == 2*pi + assert sin(2*x).period() == pi + assert cot(4*x - 6).period() == pi/4 + assert cos((-3)*x).period() == pi*Rational(2, 3) + assert cos(x*y).period(x) == 2*pi/abs(y) + assert sin(3*x*y + 2*pi).period(y) == 2*pi/abs(3*x) + assert tan(3*x).period(y) is S.Zero + raises(NotImplementedError, lambda: sin(x**2).period(x)) + + +def test_issue_7171(): + assert sin(x).rewrite(sqrt) == sin(x) + assert sin(x).rewrite(pow) == sin(x) + + +def test_issue_11864(): + w, k = symbols('w, k', real=True) + F = Piecewise((1, Eq(2*pi*k, 0)), (sin(pi*k)/(pi*k), True)) + soln = Piecewise((1, Eq(2*pi*k, 0)), (sinc(pi*k), True)) + assert F.rewrite(sinc) == soln + +def test_real_assumptions(): + z = Symbol('z', real=False, finite=True) + assert sin(z).is_real is None + assert cos(z).is_real is None + assert tan(z).is_real is False + assert sec(z).is_real is None + assert csc(z).is_real is None + assert cot(z).is_real is False + assert asin(p).is_real is None + assert asin(n).is_real is None + assert asec(p).is_real is None + assert asec(n).is_real is None + assert acos(p).is_real is None + assert acos(n).is_real is None + assert acsc(p).is_real is None + assert acsc(n).is_real is None + assert atan(p).is_positive is True + assert atan(n).is_negative is True + assert acot(p).is_positive is True + assert acot(n).is_negative is True + +def test_issue_14320(): + assert asin(sin(2)) == -2 + pi and (-pi/2 <= -2 + pi <= pi/2) and sin(2) == sin(-2 + pi) + assert asin(cos(2)) == -2 + pi/2 and (-pi/2 <= -2 + pi/2 <= pi/2) and cos(2) == sin(-2 + pi/2) + assert acos(sin(2)) == -pi/2 + 2 and (0 <= -pi/2 + 2 <= pi) and sin(2) == cos(-pi/2 + 2) + assert acos(cos(20)) == -6*pi + 20 and (0 <= -6*pi + 20 <= pi) and cos(20) == cos(-6*pi + 20) + assert acos(cos(30)) == -30 + 10*pi and (0 <= -30 + 10*pi <= pi) and cos(30) == cos(-30 + 10*pi) + + assert atan(tan(17)) == -5*pi + 17 and (-pi/2 < -5*pi + 17 < pi/2) and tan(17) == tan(-5*pi + 17) + assert atan(tan(15)) == -5*pi + 15 and (-pi/2 < -5*pi + 15 < pi/2) and tan(15) == tan(-5*pi + 15) + assert atan(cot(12)) == -12 + pi*Rational(7, 2) and (-pi/2 < -12 + pi*Rational(7, 2) < pi/2) and cot(12) == tan(-12 + pi*Rational(7, 2)) + assert acot(cot(15)) == -5*pi + 15 and (-pi/2 < -5*pi + 15 <= pi/2) and cot(15) == cot(-5*pi + 15) + assert acot(tan(19)) == -19 + pi*Rational(13, 2) and (-pi/2 < -19 + pi*Rational(13, 2) <= pi/2) and tan(19) == cot(-19 + pi*Rational(13, 2)) + + assert asec(sec(11)) == -11 + 4*pi and (0 <= -11 + 4*pi <= pi) and cos(11) == cos(-11 + 4*pi) + assert asec(csc(13)) == -13 + pi*Rational(9, 2) and (0 <= -13 + pi*Rational(9, 2) <= pi) and sin(13) == cos(-13 + pi*Rational(9, 2)) + assert acsc(csc(14)) == -4*pi + 14 and (-pi/2 <= -4*pi + 14 <= pi/2) and sin(14) == sin(-4*pi + 14) + assert acsc(sec(10)) == pi*Rational(-7, 2) + 10 and (-pi/2 <= pi*Rational(-7, 2) + 10 <= pi/2) and cos(10) == sin(pi*Rational(-7, 2) + 10) + +def test_issue_14543(): + assert sec(2*pi + 11) == sec(11) + assert sec(2*pi - 11) == sec(11) + assert sec(pi + 11) == -sec(11) + assert sec(pi - 11) == -sec(11) + + assert csc(2*pi + 17) == csc(17) + assert csc(2*pi - 17) == -csc(17) + assert csc(pi + 17) == -csc(17) + assert csc(pi - 17) == csc(17) + + x = Symbol('x') + assert csc(pi/2 + x) == sec(x) + assert csc(pi/2 - x) == sec(x) + assert csc(pi*Rational(3, 2) + x) == -sec(x) + assert csc(pi*Rational(3, 2) - x) == -sec(x) + + assert sec(pi/2 - x) == csc(x) + assert sec(pi/2 + x) == -csc(x) + assert sec(pi*Rational(3, 2) + x) == csc(x) + assert sec(pi*Rational(3, 2) - x) == -csc(x) + + +def test_as_real_imag(): + # This is for https://github.com/sympy/sympy/issues/17142 + # If it start failing again in irrelevant builds or in the master + # please open up the issue again. + expr = atan(I/(I + I*tan(1))) + assert expr.as_real_imag() == (expr, 0) + + +def test_issue_18746(): + e3 = cos(S.Pi*(x/4 + 1/4)) + assert e3.period() == 8 + + +def test_issue_25833(): + assert limit(atan(x**2), x, oo) == pi/2 + assert limit(atan(x**2 - 1), x, oo) == pi/2 + assert limit(atan(log(2**x)/log(2*x)), x, oo) == pi/2 + + +def test_issue_25847(): + #atan + assert atan(sin(x)/x).as_leading_term(x) == pi/4 + raises(PoleError, lambda: atan(exp(1/x)).as_leading_term(x)) + + #asin + assert asin(sin(x)/x).as_leading_term(x) == pi/2 + raises(PoleError, lambda: asin(exp(1/x)).as_leading_term(x)) + + #acos + assert acos(sin(x)/x).as_leading_term(x) == 0 + raises(PoleError, lambda: acos(exp(1/x)).as_leading_term(x)) + + #acot + assert acot(sin(x)/x).as_leading_term(x) == pi/4 + raises(PoleError, lambda: acot(exp(1/x)).as_leading_term(x)) + + #asec + assert asec(sin(x)/x).as_leading_term(x) == 0 + raises(PoleError, lambda: asec(exp(1/x)).as_leading_term(x)) + + #acsc + assert acsc(sin(x)/x).as_leading_term(x) == pi/2 + raises(PoleError, lambda: acsc(exp(1/x)).as_leading_term(x)) + +def test_issue_23843(): + #atan + assert atan(x + I).series(x, oo) == -16/(5*x**5) - 2*I/x**4 + 4/(3*x**3) + I/x**2 - 1/x + pi/2 + O(x**(-6), (x, oo)) + assert atan(x + I).series(x, -oo) == -16/(5*x**5) - 2*I/x**4 + 4/(3*x**3) + I/x**2 - 1/x - pi/2 + O(x**(-6), (x, -oo)) + assert atan(x - I).series(x, oo) == -16/(5*x**5) + 2*I/x**4 + 4/(3*x**3) - I/x**2 - 1/x + pi/2 + O(x**(-6), (x, oo)) + assert atan(x - I).series(x, -oo) == -16/(5*x**5) + 2*I/x**4 + 4/(3*x**3) - I/x**2 - 1/x - pi/2 + O(x**(-6), (x, -oo)) + + #acot + assert acot(x + I).series(x, oo) == 16/(5*x**5) + 2*I/x**4 - 4/(3*x**3) - I/x**2 + 1/x + O(x**(-6), (x, oo)) + assert acot(x + I).series(x, -oo) == 16/(5*x**5) + 2*I/x**4 - 4/(3*x**3) - I/x**2 + 1/x + O(x**(-6), (x, -oo)) + assert acot(x - I).series(x, oo) == 16/(5*x**5) - 2*I/x**4 - 4/(3*x**3) + I/x**2 + 1/x + O(x**(-6), (x, oo)) + assert acot(x - I).series(x, -oo) == 16/(5*x**5) - 2*I/x**4 - 4/(3*x**3) + I/x**2 + 1/x + O(x**(-6), (x, -oo)) diff --git a/phivenv/Lib/site-packages/sympy/functions/elementary/trigonometric.py b/phivenv/Lib/site-packages/sympy/functions/elementary/trigonometric.py new file mode 100644 index 0000000000000000000000000000000000000000..24e5db81f17a215f5b344291f0e9bf4752e5317d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/elementary/trigonometric.py @@ -0,0 +1,3627 @@ +from __future__ import annotations +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.expr import Expr +from sympy.core.function import DefinedFunction, ArgumentIndexError, PoleError, expand_mul +from sympy.core.logic import fuzzy_not, fuzzy_or, FuzzyBool, fuzzy_and +from sympy.core.mod import Mod +from sympy.core.numbers import Rational, pi, Integer, Float, equal_valued +from sympy.core.relational import Ne, Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, Dummy +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial, RisingFactorial +from sympy.functions.combinatorial.numbers import bernoulli, euler +from sympy.functions.elementary.complexes import arg as arg_f, im, re +from sympy.functions.elementary.exponential import log, exp +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt, Min, Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary._trigonometric_special import ( + cos_table, ipartfrac, fermat_coords) +from sympy.logic.boolalg import And +from sympy.ntheory import factorint +from sympy.polys.specialpolys import symmetric_poly +from sympy.utilities.iterables import numbered_symbols + + +############################################################################### +########################## UTILITIES ########################################## +############################################################################### + + +def _imaginary_unit_as_coefficient(arg): + """ Helper to extract symbolic coefficient for imaginary unit """ + if isinstance(arg, Float): + return None + else: + return arg.as_coefficient(S.ImaginaryUnit) + +############################################################################### +########################## TRIGONOMETRIC FUNCTIONS ############################ +############################################################################### + + +class TrigonometricFunction(DefinedFunction): + """Base class for trigonometric functions. """ + + unbranched = True + _singularities = (S.ComplexInfinity,) + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + if s.args[0].is_rational and fuzzy_not(s.args[0].is_zero): + return False + else: + return s.is_rational + + def _eval_is_algebraic(self): + s = self.func(*self.args) + if s.func == self.func: + if fuzzy_not(self.args[0].is_zero) and self.args[0].is_algebraic: + return False + pi_coeff = _pi_coeff(self.args[0]) + if pi_coeff is not None and pi_coeff.is_rational: + return True + else: + return s.is_algebraic + + def _eval_expand_complex(self, deep=True, **hints): + re_part, im_part = self.as_real_imag(deep=deep, **hints) + return re_part + im_part*S.ImaginaryUnit + + def _as_real_imag(self, deep=True, **hints): + if self.args[0].is_extended_real: + if deep: + hints['complex'] = False + return (self.args[0].expand(deep, **hints), S.Zero) + else: + return (self.args[0], S.Zero) + if deep: + re, im = self.args[0].expand(deep, **hints).as_real_imag() + else: + re, im = self.args[0].as_real_imag() + return (re, im) + + def _period(self, general_period, symbol=None): + f = expand_mul(self.args[0]) + if symbol is None: + symbol = tuple(f.free_symbols)[0] + + if not f.has(symbol): + return S.Zero + + if f == symbol: + return general_period + + if symbol in f.free_symbols: + if f.is_Mul: + g, h = f.as_independent(symbol) + if h == symbol: + return general_period/abs(g) + + if f.is_Add: + a, h = f.as_independent(symbol) + g, h = h.as_independent(symbol, as_Add=False) + if h == symbol: + return general_period/abs(g) + + raise NotImplementedError("Use the periodicity function instead.") + + +@cacheit +def _table2(): + # If nested sqrt's are worse than un-evaluation + # you can require q to be in (1, 2, 3, 4, 6, 12) + # q <= 12, q=15, q=20, q=24, q=30, q=40, q=60, q=120 return + # expressions with 2 or fewer sqrt nestings. + return { + 12: (3, 4), + 20: (4, 5), + 30: (5, 6), + 15: (6, 10), + 24: (6, 8), + 40: (8, 10), + 60: (20, 30), + 120: (40, 60) + } + + +def _peeloff_pi(arg): + r""" + Split ARG into two parts, a "rest" and a multiple of $\pi$. + This assumes ARG to be an Add. + The multiple of $\pi$ returned in the second position is always a Rational. + + Examples + ======== + + >>> from sympy.functions.elementary.trigonometric import _peeloff_pi + >>> from sympy import pi + >>> from sympy.abc import x, y + >>> _peeloff_pi(x + pi/2) + (x, 1/2) + >>> _peeloff_pi(x + 2*pi/3 + pi*y) + (x + pi*y + pi/6, 1/2) + + """ + pi_coeff = S.Zero + rest_terms = [] + for a in Add.make_args(arg): + K = a.coeff(pi) + if K and K.is_rational: + pi_coeff += K + else: + rest_terms.append(a) + + if pi_coeff is S.Zero: + return arg, S.Zero + + m1 = (pi_coeff % S.Half) + m2 = pi_coeff - m1 + if m2.is_integer or ((2*m2).is_integer and m2.is_even is False): + return Add(*(rest_terms + [m1*pi])), m2 + return arg, S.Zero + + +def _pi_coeff(arg: Expr, cycles: int = 1) -> Expr | None: + r""" + When arg is a Number times $\pi$ (e.g. $3\pi/2$) then return the Number + normalized to be in the range $[0, 2]$, else `None`. + + When an even multiple of $\pi$ is encountered, if it is multiplying + something with known parity then the multiple is returned as 0 otherwise + as 2. + + Examples + ======== + + >>> from sympy.functions.elementary.trigonometric import _pi_coeff + >>> from sympy import pi, Dummy + >>> from sympy.abc import x + >>> _pi_coeff(3*x*pi) + 3*x + >>> _pi_coeff(11*pi/7) + 11/7 + >>> _pi_coeff(-11*pi/7) + 3/7 + >>> _pi_coeff(4*pi) + 0 + >>> _pi_coeff(5*pi) + 1 + >>> _pi_coeff(5.0*pi) + 1 + >>> _pi_coeff(5.5*pi) + 3/2 + >>> _pi_coeff(2 + pi) + + >>> _pi_coeff(2*Dummy(integer=True)*pi) + 2 + >>> _pi_coeff(2*Dummy(even=True)*pi) + 0 + + """ + if arg is pi: + return S.One + elif not arg: + return S.Zero + elif arg.is_Mul: + cx = arg.coeff(pi) + if cx: + c, x = cx.as_coeff_Mul() # pi is not included as coeff + if c.is_Float: + # recast exact binary fractions to Rationals + f = abs(c) % 1 + if f != 0: + p = -int(round(log(f, 2).evalf())) + m = 2**p + cm = c*m + i = int(cm) + if equal_valued(i, cm): + c = Rational(i, m) + cx = c*x + else: + c = Rational(int(c)) + cx = c*x + if x.is_integer: + c2 = c % 2 + if c2 == 1: + return x + elif not c2: + if x.is_even is not None: # known parity + return S.Zero + return Integer(2) + else: + return c2*x + return cx + elif arg.is_zero: + return S.Zero + return None + + +class sin(TrigonometricFunction): + r""" + The sine function. + + Returns the sine of x (measured in radians). + + Explanation + =========== + + This function will evaluate automatically in the + case $x/\pi$ is some rational number [4]_. For example, + if $x$ is a multiple of $\pi$, $\pi/2$, $\pi/3$, $\pi/4$, and $\pi/6$. + + Examples + ======== + + >>> from sympy import sin, pi + >>> from sympy.abc import x + >>> sin(x**2).diff(x) + 2*x*cos(x**2) + >>> sin(1).diff(x) + 0 + >>> sin(pi) + 0 + >>> sin(pi/2) + 1 + >>> sin(pi/6) + 1/2 + >>> sin(pi/12) + -sqrt(2)/4 + sqrt(6)/4 + + + See Also + ======== + + csc, cos, sec, tan, cot + asin, acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_functions + .. [2] https://dlmf.nist.gov/4.14 + .. [3] https://functions.wolfram.com/ElementaryFunctions/Sin + .. [4] https://mathworld.wolfram.com/TrigonometryAngles.html + + """ + + def period(self, symbol=None): + return self._period(2*pi, symbol) + + def fdiff(self, argindex=1): + if argindex == 1: + return cos(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.sets.setexpr import SetExpr + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg.is_zero: + return S.Zero + elif arg in (S.Infinity, S.NegativeInfinity): + return AccumBounds(-1, 1) + + if arg is S.ComplexInfinity: + return S.NaN + + if isinstance(arg, AccumBounds): + from sympy.sets.sets import FiniteSet + min, max = arg.min, arg.max + d = floor(min/(2*pi)) + if min is not S.NegativeInfinity: + min = min - d*2*pi + if max is not S.Infinity: + max = max - d*2*pi + if AccumBounds(min, max).intersection(FiniteSet(pi/2, pi*Rational(5, 2))) \ + is not S.EmptySet and \ + AccumBounds(min, max).intersection(FiniteSet(pi*Rational(3, 2), + pi*Rational(7, 2))) is not S.EmptySet: + return AccumBounds(-1, 1) + elif AccumBounds(min, max).intersection(FiniteSet(pi/2, pi*Rational(5, 2))) \ + is not S.EmptySet: + return AccumBounds(Min(sin(min), sin(max)), 1) + elif AccumBounds(min, max).intersection(FiniteSet(pi*Rational(3, 2), pi*Rational(8, 2))) \ + is not S.EmptySet: + return AccumBounds(-1, Max(sin(min), sin(max))) + else: + return AccumBounds(Min(sin(min), sin(max)), + Max(sin(min), sin(max))) + elif isinstance(arg, SetExpr): + return arg._eval_func(cls) + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import sinh + return S.ImaginaryUnit*sinh(i_coeff) + + pi_coeff = _pi_coeff(arg) + if pi_coeff is not None: + if pi_coeff.is_integer: + return S.Zero + + if (2*pi_coeff).is_integer: + # is_even-case handled above as then pi_coeff.is_integer, + # so check if known to be not even + if pi_coeff.is_even is False: + return S.NegativeOne**(pi_coeff - S.Half) + + if not pi_coeff.is_Rational: + narg = pi_coeff*pi + if narg != arg: + return cls(narg) + return None + + # https://github.com/sympy/sympy/issues/6048 + # transform a sine to a cosine, to avoid redundant code + if pi_coeff.is_Rational: + x = pi_coeff % 2 + if x > 1: + return -cls((x % 1)*pi) + if 2*x > 1: + return cls((1 - x)*pi) + narg = ((pi_coeff + Rational(3, 2)) % 2)*pi + result = cos(narg) + if not isinstance(result, cos): + return result + if pi_coeff*pi != arg: + return cls(pi_coeff*pi) + return None + + if arg.is_Add: + x, m = _peeloff_pi(arg) + if m: + m = m*pi + return sin(m)*cos(x) + cos(m)*sin(x) + + if arg.is_zero: + return S.Zero + + if isinstance(arg, asin): + return arg.args[0] + + if isinstance(arg, atan): + x = arg.args[0] + return x/sqrt(1 + x**2) + + if isinstance(arg, atan2): + y, x = arg.args + return y/sqrt(x**2 + y**2) + + if isinstance(arg, acos): + x = arg.args[0] + return sqrt(1 - x**2) + + if isinstance(arg, acot): + x = arg.args[0] + return 1/(sqrt(1 + 1/x**2)*x) + + if isinstance(arg, acsc): + x = arg.args[0] + return 1/x + + if isinstance(arg, asec): + x = arg.args[0] + return sqrt(1 - 1/x**2) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + if len(previous_terms) > 2: + p = previous_terms[-2] + return -p*x**2/(n*(n - 1)) + else: + return S.NegativeOne**(n//2)*x**n/factorial(n) + + def _eval_nseries(self, x, n, logx, cdir=0): + arg = self.args[0] + if logx is not None: + arg = arg.subs(log(x), logx) + if arg.subs(x, 0).has(S.NaN, S.ComplexInfinity): + raise PoleError("Cannot expand %s around 0" % (self)) + return super()._eval_nseries(x, n=n, logx=logx, cdir=cdir) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + from sympy.functions.elementary.hyperbolic import HyperbolicFunction + I = S.ImaginaryUnit + if isinstance(arg, (TrigonometricFunction, HyperbolicFunction)): + arg = arg.func(arg.args[0]).rewrite(exp) + return (exp(arg*I) - exp(-arg*I))/(2*I) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + if isinstance(arg, log): + I = S.ImaginaryUnit + x = arg.args[0] + return I*x**-I/2 - I*x**I /2 + + def _eval_rewrite_as_cos(self, arg, **kwargs): + return cos(arg - pi/2, evaluate=False) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + tan_half = tan(S.Half*arg) + return 2*tan_half/(1 + tan_half**2) + + def _eval_rewrite_as_sincos(self, arg, **kwargs): + return sin(arg)*cos(arg)/cos(arg) + + def _eval_rewrite_as_cot(self, arg, **kwargs): + cot_half = cot(S.Half*arg) + return Piecewise((0, And(Eq(im(arg), 0), Eq(Mod(arg, pi), 0))), + (2*cot_half/(1 + cot_half**2), True)) + + def _eval_rewrite_as_pow(self, arg, **kwargs): + return self.rewrite(cos, **kwargs).rewrite(pow, **kwargs) + + def _eval_rewrite_as_sqrt(self, arg, **kwargs): + return self.rewrite(cos, **kwargs).rewrite(sqrt, **kwargs) + + def _eval_rewrite_as_csc(self, arg, **kwargs): + return 1/csc(arg) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + return 1/sec(arg - pi/2, evaluate=False) + + def _eval_rewrite_as_sinc(self, arg, **kwargs): + return arg*sinc(arg) + + def _eval_rewrite_as_besselj(self, arg, **kwargs): + from sympy.functions.special.bessel import besselj + return sqrt(pi*arg/2)*besselj(S.Half, arg) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + from sympy.functions.elementary.hyperbolic import cosh, sinh + re, im = self._as_real_imag(deep=deep, **hints) + return (sin(re)*cosh(im), cos(re)*sinh(im)) + + def _eval_expand_trig(self, **hints): + from sympy.functions.special.polynomials import chebyshevt, chebyshevu + arg = self.args[0] + x = None + if arg.is_Add: # TODO, implement more if deep stuff here + # TODO: Do this more efficiently for more than two terms + x, y = arg.as_two_terms() + sx = sin(x, evaluate=False)._eval_expand_trig() + sy = sin(y, evaluate=False)._eval_expand_trig() + cx = cos(x, evaluate=False)._eval_expand_trig() + cy = cos(y, evaluate=False)._eval_expand_trig() + return sx*cy + sy*cx + elif arg.is_Mul: + n, x = arg.as_coeff_Mul(rational=True) + if n.is_Integer: # n will be positive because of .eval + # canonicalization + + # See https://mathworld.wolfram.com/Multiple-AngleFormulas.html + if n.is_odd: + return S.NegativeOne**((n - 1)/2)*chebyshevt(n, sin(x)) + else: + return expand_mul(S.NegativeOne**(n/2 - 1)*cos(x)* + chebyshevu(n - 1, sin(x)), deep=False) + return sin(arg) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + n = x0/pi + if n.is_integer: + lt = (arg - n*pi).as_leading_term(x) + return (S.NegativeOne**n)*lt + if x0 is S.ComplexInfinity: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if x0 in [S.Infinity, S.NegativeInfinity]: + return AccumBounds(-1, 1) + return self.func(x0) if x0.is_finite else self + + def _eval_is_extended_real(self): + if self.args[0].is_extended_real: + return True + + def _eval_is_finite(self): + arg = self.args[0] + if arg.is_extended_real: + return True + + def _eval_is_zero(self): + rest, pi_mult = _peeloff_pi(self.args[0]) + if rest.is_zero: + return pi_mult.is_integer + + def _eval_is_complex(self): + if self.args[0].is_extended_real \ + or self.args[0].is_complex: + return True + + +class cos(TrigonometricFunction): + """ + The cosine function. + + Returns the cosine of x (measured in radians). + + Explanation + =========== + + See :func:`sin` for notes about automatic evaluation. + + Examples + ======== + + >>> from sympy import cos, pi + >>> from sympy.abc import x + >>> cos(x**2).diff(x) + -2*x*sin(x**2) + >>> cos(1).diff(x) + 0 + >>> cos(pi) + -1 + >>> cos(pi/2) + 0 + >>> cos(2*pi/3) + -1/2 + >>> cos(pi/12) + sqrt(2)/4 + sqrt(6)/4 + + See Also + ======== + + sin, csc, sec, tan, cot + asin, acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_functions + .. [2] https://dlmf.nist.gov/4.14 + .. [3] https://functions.wolfram.com/ElementaryFunctions/Cos + + """ + + def period(self, symbol=None): + return self._period(2*pi, symbol) + + def fdiff(self, argindex=1): + if argindex == 1: + return -sin(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + from sympy.functions.special.polynomials import chebyshevt + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.sets.setexpr import SetExpr + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg.is_zero: + return S.One + elif arg in (S.Infinity, S.NegativeInfinity): + # In this case it is better to return AccumBounds(-1, 1) + # rather than returning S.NaN, since AccumBounds(-1, 1) + # preserves the information that sin(oo) is between + # -1 and 1, where S.NaN does not do that. + return AccumBounds(-1, 1) + + if arg is S.ComplexInfinity: + return S.NaN + + if isinstance(arg, AccumBounds): + return sin(arg + pi/2) + elif isinstance(arg, SetExpr): + return arg._eval_func(cls) + + if arg.is_extended_real and arg.is_finite is False: + return AccumBounds(-1, 1) + + if arg.could_extract_minus_sign(): + return cls(-arg) + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import cosh + return cosh(i_coeff) + + pi_coeff = _pi_coeff(arg) + if pi_coeff is not None: + if pi_coeff.is_integer: + return (S.NegativeOne)**pi_coeff + + if (2*pi_coeff).is_integer: + # is_even-case handled above as then pi_coeff.is_integer, + # so check if known to be not even + if pi_coeff.is_even is False: + return S.Zero + + if not pi_coeff.is_Rational: + narg = pi_coeff*pi + if narg != arg: + return cls(narg) + return None + + # cosine formula ##################### + # https://github.com/sympy/sympy/issues/6048 + # explicit calculations are performed for + # cos(k pi/n) for n = 8,10,12,15,20,24,30,40,60,120 + # Some other exact values like cos(k pi/240) can be + # calculated using a partial-fraction decomposition + # by calling cos( X ).rewrite(sqrt) + if pi_coeff.is_Rational: + q = pi_coeff.q + p = pi_coeff.p % (2*q) + if p > q: + narg = (pi_coeff - 1)*pi + return -cls(narg) + if 2*p > q: + narg = (1 - pi_coeff)*pi + return -cls(narg) + + # If nested sqrt's are worse than un-evaluation + # you can require q to be in (1, 2, 3, 4, 6, 12) + # q <= 12, q=15, q=20, q=24, q=30, q=40, q=60, q=120 return + # expressions with 2 or fewer sqrt nestings. + table2 = _table2() + if q in table2: + a, b = table2[q] + a, b = p*pi/a, p*pi/b + nvala, nvalb = cls(a), cls(b) + if None in (nvala, nvalb): + return None + return nvala*nvalb + cls(pi/2 - a)*cls(pi/2 - b) + + if q > 12: + return None + + cst_table_some = { + 3: S.Half, + 5: (sqrt(5) + 1) / 4, + } + if q in cst_table_some: + cts = cst_table_some[pi_coeff.q] + return chebyshevt(pi_coeff.p, cts).expand() + + if 0 == q % 2: + narg = (pi_coeff*2)*pi + nval = cls(narg) + if None == nval: + return None + x = (2*pi_coeff + 1)/2 + sign_cos = (-1)**((-1 if x < 0 else 1)*int(abs(x))) + return sign_cos*sqrt( (1 + nval)/2 ) + return None + + if arg.is_Add: + x, m = _peeloff_pi(arg) + if m: + m = m*pi + return cos(m)*cos(x) - sin(m)*sin(x) + + if arg.is_zero: + return S.One + + if isinstance(arg, acos): + return arg.args[0] + + if isinstance(arg, atan): + x = arg.args[0] + return 1/sqrt(1 + x**2) + + if isinstance(arg, atan2): + y, x = arg.args + return x/sqrt(x**2 + y**2) + + if isinstance(arg, asin): + x = arg.args[0] + return sqrt(1 - x ** 2) + + if isinstance(arg, acot): + x = arg.args[0] + return 1/sqrt(1 + 1/x**2) + + if isinstance(arg, acsc): + x = arg.args[0] + return sqrt(1 - 1/x**2) + + if isinstance(arg, asec): + x = arg.args[0] + return 1/x + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + + if len(previous_terms) > 2: + p = previous_terms[-2] + return -p*x**2/(n*(n - 1)) + else: + return S.NegativeOne**(n//2)*x**n/factorial(n) + + def _eval_nseries(self, x, n, logx, cdir=0): + arg = self.args[0] + if logx is not None: + arg = arg.subs(log(x), logx) + if arg.subs(x, 0).has(S.NaN, S.ComplexInfinity): + raise PoleError("Cannot expand %s around 0" % (self)) + return super()._eval_nseries(x, n=n, logx=logx, cdir=cdir) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + I = S.ImaginaryUnit + from sympy.functions.elementary.hyperbolic import HyperbolicFunction + if isinstance(arg, (TrigonometricFunction, HyperbolicFunction)): + arg = arg.func(arg.args[0]).rewrite(exp, **kwargs) + return (exp(arg*I) + exp(-arg*I))/2 + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + if isinstance(arg, log): + I = S.ImaginaryUnit + x = arg.args[0] + return x**I/2 + x**-I/2 + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return sin(arg + pi/2, evaluate=False) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + tan_half = tan(S.Half*arg)**2 + return (1 - tan_half)/(1 + tan_half) + + def _eval_rewrite_as_sincos(self, arg, **kwargs): + return sin(arg)*cos(arg)/sin(arg) + + def _eval_rewrite_as_cot(self, arg, **kwargs): + cot_half = cot(S.Half*arg)**2 + return Piecewise((1, And(Eq(im(arg), 0), Eq(Mod(arg, 2*pi), 0))), + ((cot_half - 1)/(cot_half + 1), True)) + + def _eval_rewrite_as_pow(self, arg, **kwargs): + return self._eval_rewrite_as_sqrt(arg, **kwargs) + + def _eval_rewrite_as_sqrt(self, arg: Expr, **kwargs): + from sympy.functions.special.polynomials import chebyshevt + + pi_coeff = _pi_coeff(arg) + if pi_coeff is None: + return None + + if isinstance(pi_coeff, Integer): + return None + + if not isinstance(pi_coeff, Rational): + return None + + cst_table_some = cos_table() + + if pi_coeff.q in cst_table_some: + rv = chebyshevt(pi_coeff.p, cst_table_some[pi_coeff.q]()) + if pi_coeff.q < 257: + rv = rv.expand() + return rv + + if not pi_coeff.q % 2: # recursively remove factors of 2 + pico2 = pi_coeff * 2 + nval = cos(pico2 * pi).rewrite(sqrt, **kwargs) + x = (pico2 + 1) / 2 + sign_cos = -1 if int(x) % 2 else 1 + return sign_cos * sqrt((1 + nval) / 2) + + FC = fermat_coords(pi_coeff.q) + if FC: + denoms = FC + else: + denoms = [b**e for b, e in factorint(pi_coeff.q).items()] + + apart = ipartfrac(*denoms) + decomp = (pi_coeff.p * Rational(n, d) for n, d in zip(apart, denoms)) + X = [(x[1], x[0]*pi) for x in zip(decomp, numbered_symbols('z'))] + pcls = cos(sum(x[0] for x in X))._eval_expand_trig().subs(X) + + if not FC or len(FC) == 1: + return pcls + return pcls.rewrite(sqrt, **kwargs) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + return 1/sec(arg) + + def _eval_rewrite_as_csc(self, arg, **kwargs): + return 1/sec(arg).rewrite(csc, **kwargs) + + def _eval_rewrite_as_besselj(self, arg, **kwargs): + from sympy.functions.special.bessel import besselj + return Piecewise( + (sqrt(pi*arg/2)*besselj(-S.Half, arg), Ne(arg, 0)), + (1, True) + ) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + from sympy.functions.elementary.hyperbolic import cosh, sinh + re, im = self._as_real_imag(deep=deep, **hints) + return (cos(re)*cosh(im), -sin(re)*sinh(im)) + + def _eval_expand_trig(self, **hints): + from sympy.functions.special.polynomials import chebyshevt + arg = self.args[0] + x = None + if arg.is_Add: # TODO: Do this more efficiently for more than two terms + x, y = arg.as_two_terms() + sx = sin(x, evaluate=False)._eval_expand_trig() + sy = sin(y, evaluate=False)._eval_expand_trig() + cx = cos(x, evaluate=False)._eval_expand_trig() + cy = cos(y, evaluate=False)._eval_expand_trig() + return cx*cy - sx*sy + elif arg.is_Mul: + coeff, terms = arg.as_coeff_Mul(rational=True) + if coeff.is_Integer: + return chebyshevt(coeff, cos(terms)) + return cos(arg) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + n = (x0 + pi/2)/pi + if n.is_integer: + lt = (arg - n*pi + pi/2).as_leading_term(x) + return (S.NegativeOne**n)*lt + if x0 is S.ComplexInfinity: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if x0 in [S.Infinity, S.NegativeInfinity]: + return AccumBounds(-1, 1) + return self.func(x0) if x0.is_finite else self + + def _eval_is_extended_real(self): + if self.args[0].is_extended_real: + return True + + def _eval_is_finite(self): + arg = self.args[0] + + if arg.is_extended_real: + return True + + def _eval_is_complex(self): + if self.args[0].is_extended_real \ + or self.args[0].is_complex: + return True + + def _eval_is_zero(self): + rest, pi_mult = _peeloff_pi(self.args[0]) + if rest.is_zero and pi_mult: + return (pi_mult - S.Half).is_integer + + +class tan(TrigonometricFunction): + """ + The tangent function. + + Returns the tangent of x (measured in radians). + + Explanation + =========== + + See :class:`sin` for notes about automatic evaluation. + + Examples + ======== + + >>> from sympy import tan, pi + >>> from sympy.abc import x + >>> tan(x**2).diff(x) + 2*x*(tan(x**2)**2 + 1) + >>> tan(1).diff(x) + 0 + >>> tan(pi/8).expand() + -1 + sqrt(2) + + See Also + ======== + + sin, csc, cos, sec, cot + asin, acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_functions + .. [2] https://dlmf.nist.gov/4.14 + .. [3] https://functions.wolfram.com/ElementaryFunctions/Tan + + """ + + def period(self, symbol=None): + return self._period(pi, symbol) + + def fdiff(self, argindex=1): + if argindex == 1: + return S.One + self**2 + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return atan + + @classmethod + def eval(cls, arg): + from sympy.calculus.accumulationbounds import AccumBounds + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg.is_zero: + return S.Zero + elif arg in (S.Infinity, S.NegativeInfinity): + return AccumBounds(S.NegativeInfinity, S.Infinity) + + if arg is S.ComplexInfinity: + return S.NaN + + if isinstance(arg, AccumBounds): + min, max = arg.min, arg.max + d = floor(min/pi) + if min is not S.NegativeInfinity: + min = min - d*pi + if max is not S.Infinity: + max = max - d*pi + from sympy.sets.sets import FiniteSet + if AccumBounds(min, max).intersection(FiniteSet(pi/2, pi*Rational(3, 2))): + return AccumBounds(S.NegativeInfinity, S.Infinity) + else: + return AccumBounds(tan(min), tan(max)) + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import tanh + return S.ImaginaryUnit*tanh(i_coeff) + + pi_coeff = _pi_coeff(arg, 2) + if pi_coeff is not None: + if pi_coeff.is_integer: + return S.Zero + + if not pi_coeff.is_Rational: + narg = pi_coeff*pi + if narg != arg: + return cls(narg) + return None + + if pi_coeff.is_Rational: + q = pi_coeff.q + p = pi_coeff.p % q + # ensure simplified results are returned for n*pi/5, n*pi/10 + table10 = { + 1: sqrt(1 - 2*sqrt(5)/5), + 2: sqrt(5 - 2*sqrt(5)), + 3: sqrt(1 + 2*sqrt(5)/5), + 4: sqrt(5 + 2*sqrt(5)) + } + if q in (5, 10): + n = 10*p/q + if n > 5: + n = 10 - n + return -table10[n] + else: + return table10[n] + if not pi_coeff.q % 2: + narg = pi_coeff*pi*2 + cresult, sresult = cos(narg), cos(narg - pi/2) + if not isinstance(cresult, cos) \ + and not isinstance(sresult, cos): + if sresult == 0: + return S.ComplexInfinity + return 1/sresult - cresult/sresult + + table2 = _table2() + if q in table2: + a, b = table2[q] + nvala, nvalb = cls(p*pi/a), cls(p*pi/b) + if None in (nvala, nvalb): + return None + return (nvala - nvalb)/(1 + nvala*nvalb) + narg = ((pi_coeff + S.Half) % 1 - S.Half)*pi + # see cos() to specify which expressions should be + # expanded automatically in terms of radicals + cresult, sresult = cos(narg), cos(narg - pi/2) + if not isinstance(cresult, cos) \ + and not isinstance(sresult, cos): + if cresult == 0: + return S.ComplexInfinity + return (sresult/cresult) + if narg != arg: + return cls(narg) + + if arg.is_Add: + x, m = _peeloff_pi(arg) + if m: + tanm = tan(m*pi) + if tanm is S.ComplexInfinity: + return -cot(x) + else: # tanm == 0 + return tan(x) + + if arg.is_zero: + return S.Zero + + if isinstance(arg, atan): + return arg.args[0] + + if isinstance(arg, atan2): + y, x = arg.args + return y/x + + if isinstance(arg, asin): + x = arg.args[0] + return x/sqrt(1 - x**2) + + if isinstance(arg, acos): + x = arg.args[0] + return sqrt(1 - x**2)/x + + if isinstance(arg, acot): + x = arg.args[0] + return 1/x + + if isinstance(arg, acsc): + x = arg.args[0] + return 1/(sqrt(1 - 1/x**2)*x) + + if isinstance(arg, asec): + x = arg.args[0] + return sqrt(1 - 1/x**2)*x + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + a, b = ((n - 1)//2), 2**(n + 1) + + B = bernoulli(n + 1) + F = factorial(n + 1) + + return S.NegativeOne**a*b*(b - 1)*B/F*x**n + + def _eval_nseries(self, x, n, logx, cdir=0): + i = self.args[0].limit(x, 0)*2/pi + if i and i.is_Integer: + return self.rewrite(cos)._eval_nseries(x, n=n, logx=logx) + return super()._eval_nseries(x, n=n, logx=logx) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + if isinstance(arg, log): + I = S.ImaginaryUnit + x = arg.args[0] + return I*(x**-I - x**I)/(x**-I + x**I) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + re, im = self._as_real_imag(deep=deep, **hints) + if im: + from sympy.functions.elementary.hyperbolic import cosh, sinh + denom = cos(2*re) + cosh(2*im) + return (sin(2*re)/denom, sinh(2*im)/denom) + else: + return (self.func(re), S.Zero) + + def _eval_expand_trig(self, **hints): + arg = self.args[0] + x = None + if arg.is_Add: + n = len(arg.args) + TX = [] + for x in arg.args: + tx = tan(x, evaluate=False)._eval_expand_trig() + TX.append(tx) + + Yg = numbered_symbols('Y') + Y = [ next(Yg) for i in range(n) ] + + p = [0, 0] + for i in range(n + 1): + p[1 - i % 2] += symmetric_poly(i, Y)*(-1)**((i % 4)//2) + return (p[0]/p[1]).subs(list(zip(Y, TX))) + + elif arg.is_Mul: + coeff, terms = arg.as_coeff_Mul(rational=True) + if coeff.is_Integer and coeff > 1: + I = S.ImaginaryUnit + z = Symbol('dummy', real=True) + P = ((1 + I*z)**coeff).expand() + return (im(P)/re(P)).subs([(z, tan(terms))]) + return tan(arg) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + I = S.ImaginaryUnit + from sympy.functions.elementary.hyperbolic import HyperbolicFunction + if isinstance(arg, (TrigonometricFunction, HyperbolicFunction)): + arg = arg.func(arg.args[0]).rewrite(exp) + neg_exp, pos_exp = exp(-arg*I), exp(arg*I) + return I*(neg_exp - pos_exp)/(neg_exp + pos_exp) + + def _eval_rewrite_as_sin(self, x, **kwargs): + return 2*sin(x)**2/sin(2*x) + + def _eval_rewrite_as_cos(self, x, **kwargs): + return cos(x - pi/2, evaluate=False)/cos(x) + + def _eval_rewrite_as_sincos(self, arg, **kwargs): + return sin(arg)/cos(arg) + + def _eval_rewrite_as_cot(self, arg, **kwargs): + return 1/cot(arg) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + sin_in_sec_form = sin(arg).rewrite(sec, **kwargs) + cos_in_sec_form = cos(arg).rewrite(sec, **kwargs) + return sin_in_sec_form/cos_in_sec_form + + def _eval_rewrite_as_csc(self, arg, **kwargs): + sin_in_csc_form = sin(arg).rewrite(csc, **kwargs) + cos_in_csc_form = cos(arg).rewrite(csc, **kwargs) + return sin_in_csc_form/cos_in_csc_form + + def _eval_rewrite_as_pow(self, arg, **kwargs): + y = self.rewrite(cos, **kwargs).rewrite(pow, **kwargs) + if y.has(cos): + return None + return y + + def _eval_rewrite_as_sqrt(self, arg, **kwargs): + y = self.rewrite(cos, **kwargs).rewrite(sqrt, **kwargs) + if y.has(cos): + return None + return y + + def _eval_rewrite_as_besselj(self, arg, **kwargs): + from sympy.functions.special.bessel import besselj + return besselj(S.Half, arg)/besselj(-S.Half, arg) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.functions.elementary.complexes import re + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + n = 2*x0/pi + if n.is_integer: + lt = (arg - n*pi/2).as_leading_term(x) + return lt if n.is_even else -1/lt + if x0 is S.ComplexInfinity: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if x0 in (S.Infinity, S.NegativeInfinity): + return AccumBounds(S.NegativeInfinity, S.Infinity) + return self.func(x0) if x0.is_finite else self + + def _eval_is_extended_real(self): + # FIXME: currently tan(pi/2) return zoo + return self.args[0].is_extended_real + + def _eval_is_real(self): + arg = self.args[0] + if arg.is_real and (arg/pi - S.Half).is_integer is False: + return True + + def _eval_is_finite(self): + arg = self.args[0] + + if arg.is_real and (arg/pi - S.Half).is_integer is False: + return True + + if arg.is_imaginary: + return True + + def _eval_is_zero(self): + rest, pi_mult = _peeloff_pi(self.args[0]) + if rest.is_zero: + return pi_mult.is_integer + + def _eval_is_complex(self): + arg = self.args[0] + + if arg.is_real and (arg/pi - S.Half).is_integer is False: + return True + + +class cot(TrigonometricFunction): + """ + The cotangent function. + + Returns the cotangent of x (measured in radians). + + Explanation + =========== + + See :class:`sin` for notes about automatic evaluation. + + Examples + ======== + + >>> from sympy import cot, pi + >>> from sympy.abc import x + >>> cot(x**2).diff(x) + 2*x*(-cot(x**2)**2 - 1) + >>> cot(1).diff(x) + 0 + >>> cot(pi/12) + sqrt(3) + 2 + + See Also + ======== + + sin, csc, cos, sec, tan + asin, acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_functions + .. [2] https://dlmf.nist.gov/4.14 + .. [3] https://functions.wolfram.com/ElementaryFunctions/Cot + + """ + + def period(self, symbol=None): + return self._period(pi, symbol) + + def fdiff(self, argindex=1): + if argindex == 1: + return S.NegativeOne - self**2 + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return acot + + @classmethod + def eval(cls, arg): + from sympy.calculus.accumulationbounds import AccumBounds + if arg.is_Number: + if arg is S.NaN: + return S.NaN + if arg.is_zero: + return S.ComplexInfinity + elif arg in (S.Infinity, S.NegativeInfinity): + return AccumBounds(S.NegativeInfinity, S.Infinity) + + if arg is S.ComplexInfinity: + return S.NaN + + if isinstance(arg, AccumBounds): + return -tan(arg + pi/2) + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import coth + return -S.ImaginaryUnit*coth(i_coeff) + + pi_coeff = _pi_coeff(arg, 2) + if pi_coeff is not None: + if pi_coeff.is_integer: + return S.ComplexInfinity + + if not pi_coeff.is_Rational: + narg = pi_coeff*pi + if narg != arg: + return cls(narg) + return None + + if pi_coeff.is_Rational: + if pi_coeff.q in (5, 10): + return tan(pi/2 - arg) + if pi_coeff.q > 2 and not pi_coeff.q % 2: + narg = pi_coeff*pi*2 + cresult, sresult = cos(narg), cos(narg - pi/2) + if not isinstance(cresult, cos) \ + and not isinstance(sresult, cos): + return 1/sresult + cresult/sresult + q = pi_coeff.q + p = pi_coeff.p % q + table2 = _table2() + if q in table2: + a, b = table2[q] + nvala, nvalb = cls(p*pi/a), cls(p*pi/b) + if None in (nvala, nvalb): + return None + return (1 + nvala*nvalb)/(nvalb - nvala) + narg = (((pi_coeff + S.Half) % 1) - S.Half)*pi + # see cos() to specify which expressions should be + # expanded automatically in terms of radicals + cresult, sresult = cos(narg), cos(narg - pi/2) + if not isinstance(cresult, cos) \ + and not isinstance(sresult, cos): + if sresult == 0: + return S.ComplexInfinity + return cresult/sresult + if narg != arg: + return cls(narg) + + if arg.is_Add: + x, m = _peeloff_pi(arg) + if m: + cotm = cot(m*pi) + if cotm is S.ComplexInfinity: + return cot(x) + else: # cotm == 0 + return -tan(x) + + if arg.is_zero: + return S.ComplexInfinity + + if isinstance(arg, acot): + return arg.args[0] + + if isinstance(arg, atan): + x = arg.args[0] + return 1/x + + if isinstance(arg, atan2): + y, x = arg.args + return x/y + + if isinstance(arg, asin): + x = arg.args[0] + return sqrt(1 - x**2)/x + + if isinstance(arg, acos): + x = arg.args[0] + return x/sqrt(1 - x**2) + + if isinstance(arg, acsc): + x = arg.args[0] + return sqrt(1 - 1/x**2)*x + + if isinstance(arg, asec): + x = arg.args[0] + return 1/(sqrt(1 - 1/x**2)*x) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return 1/sympify(x) + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + + B = bernoulli(n + 1) + F = factorial(n + 1) + + return S.NegativeOne**((n + 1)//2)*2**(n + 1)*B/F*x**n + + def _eval_nseries(self, x, n, logx, cdir=0): + i = self.args[0].limit(x, 0)/pi + if i and i.is_Integer: + return self.rewrite(cos)._eval_nseries(x, n=n, logx=logx) + return self.rewrite(tan)._eval_nseries(x, n=n, logx=logx) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + re, im = self._as_real_imag(deep=deep, **hints) + if im: + from sympy.functions.elementary.hyperbolic import cosh, sinh + denom = cos(2*re) - cosh(2*im) + return (-sin(2*re)/denom, sinh(2*im)/denom) + else: + return (self.func(re), S.Zero) + + def _eval_rewrite_as_exp(self, arg, **kwargs): + from sympy.functions.elementary.hyperbolic import HyperbolicFunction + I = S.ImaginaryUnit + if isinstance(arg, (TrigonometricFunction, HyperbolicFunction)): + arg = arg.func(arg.args[0]).rewrite(exp, **kwargs) + neg_exp, pos_exp = exp(-arg*I), exp(arg*I) + return I*(pos_exp + neg_exp)/(pos_exp - neg_exp) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + if isinstance(arg, log): + I = S.ImaginaryUnit + x = arg.args[0] + return -I*(x**-I + x**I)/(x**-I - x**I) + + def _eval_rewrite_as_sin(self, x, **kwargs): + return sin(2*x)/(2*(sin(x)**2)) + + def _eval_rewrite_as_cos(self, x, **kwargs): + return cos(x)/cos(x - pi/2, evaluate=False) + + def _eval_rewrite_as_sincos(self, arg, **kwargs): + return cos(arg)/sin(arg) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + return 1/tan(arg) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + cos_in_sec_form = cos(arg).rewrite(sec, **kwargs) + sin_in_sec_form = sin(arg).rewrite(sec, **kwargs) + return cos_in_sec_form/sin_in_sec_form + + def _eval_rewrite_as_csc(self, arg, **kwargs): + cos_in_csc_form = cos(arg).rewrite(csc, **kwargs) + sin_in_csc_form = sin(arg).rewrite(csc, **kwargs) + return cos_in_csc_form/sin_in_csc_form + + def _eval_rewrite_as_pow(self, arg, **kwargs): + y = self.rewrite(cos, **kwargs).rewrite(pow, **kwargs) + if y.has(cos): + return None + return y + + def _eval_rewrite_as_sqrt(self, arg, **kwargs): + y = self.rewrite(cos, **kwargs).rewrite(sqrt, **kwargs) + if y.has(cos): + return None + return y + + def _eval_rewrite_as_besselj(self, arg, **kwargs): + from sympy.functions.special.bessel import besselj + return besselj(-S.Half, arg)/besselj(S.Half, arg) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.functions.elementary.complexes import re + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + n = 2*x0/pi + if n.is_integer: + lt = (arg - n*pi/2).as_leading_term(x) + return 1/lt if n.is_even else -lt + if x0 is S.ComplexInfinity: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if x0 in (S.Infinity, S.NegativeInfinity): + return AccumBounds(S.NegativeInfinity, S.Infinity) + return self.func(x0) if x0.is_finite else self + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + def _eval_expand_trig(self, **hints): + arg = self.args[0] + x = None + if arg.is_Add: + n = len(arg.args) + CX = [] + for x in arg.args: + cx = cot(x, evaluate=False)._eval_expand_trig() + CX.append(cx) + + Yg = numbered_symbols('Y') + Y = [ next(Yg) for i in range(n) ] + + p = [0, 0] + for i in range(n, -1, -1): + p[(n - i) % 2] += symmetric_poly(i, Y)*(-1)**(((n - i) % 4)//2) + return (p[0]/p[1]).subs(list(zip(Y, CX))) + elif arg.is_Mul: + coeff, terms = arg.as_coeff_Mul(rational=True) + if coeff.is_Integer and coeff > 1: + I = S.ImaginaryUnit + z = Symbol('dummy', real=True) + P = ((z + I)**coeff).expand() + return (re(P)/im(P)).subs([(z, cot(terms))]) + return cot(arg) # XXX sec and csc return 1/cos and 1/sin + + def _eval_is_finite(self): + arg = self.args[0] + if arg.is_real and (arg/pi).is_integer is False: + return True + if arg.is_imaginary: + return True + + def _eval_is_real(self): + arg = self.args[0] + if arg.is_real and (arg/pi).is_integer is False: + return True + + def _eval_is_complex(self): + arg = self.args[0] + if arg.is_real and (arg/pi).is_integer is False: + return True + + def _eval_is_zero(self): + rest, pimult = _peeloff_pi(self.args[0]) + if pimult and rest.is_zero: + return (pimult - S.Half).is_integer + + def _eval_subs(self, old, new): + arg = self.args[0] + argnew = arg.subs(old, new) + if arg != argnew and (argnew/pi).is_integer: + return S.ComplexInfinity + return cot(argnew) + + +class ReciprocalTrigonometricFunction(TrigonometricFunction): + """Base class for reciprocal functions of trigonometric functions. """ + + _reciprocal_of = None # mandatory, to be defined in subclass + _singularities = (S.ComplexInfinity,) + + # _is_even and _is_odd are used for correct evaluation of csc(-x), sec(-x) + # TODO refactor into TrigonometricFunction common parts of + # trigonometric functions eval() like even/odd, func(x+2*k*pi), etc. + + # optional, to be defined in subclasses: + _is_even: FuzzyBool = None + _is_odd: FuzzyBool = None + + @classmethod + def eval(cls, arg): + if arg.could_extract_minus_sign(): + if cls._is_even: + return cls(-arg) + if cls._is_odd: + return -cls(-arg) + + pi_coeff = _pi_coeff(arg) + if (pi_coeff is not None + and not (2*pi_coeff).is_integer + and pi_coeff.is_Rational): + q = pi_coeff.q + p = pi_coeff.p % (2*q) + if p > q: + narg = (pi_coeff - 1)*pi + return -cls(narg) + if 2*p > q: + narg = (1 - pi_coeff)*pi + if cls._is_odd: + return cls(narg) + elif cls._is_even: + return -cls(narg) + + if hasattr(arg, 'inverse') and arg.inverse() == cls: + return arg.args[0] + + t = cls._reciprocal_of.eval(arg) + if t is None: + return t + elif any(isinstance(i, cos) for i in (t, -t)): + return (1/t).rewrite(sec) + elif any(isinstance(i, sin) for i in (t, -t)): + return (1/t).rewrite(csc) + else: + return 1/t + + def _call_reciprocal(self, method_name, *args, **kwargs): + # Calls method_name on _reciprocal_of + o = self._reciprocal_of(self.args[0]) + return getattr(o, method_name)(*args, **kwargs) + + def _calculate_reciprocal(self, method_name, *args, **kwargs): + # If calling method_name on _reciprocal_of returns a value != None + # then return the reciprocal of that value + t = self._call_reciprocal(method_name, *args, **kwargs) + return 1/t if t is not None else t + + def _rewrite_reciprocal(self, method_name, arg): + # Special handling for rewrite functions. If reciprocal rewrite returns + # unmodified expression, then return None + t = self._call_reciprocal(method_name, arg) + if t is not None and t != self._reciprocal_of(arg): + return 1/t + + def _period(self, symbol): + f = expand_mul(self.args[0]) + return self._reciprocal_of(f).period(symbol) + + def fdiff(self, argindex=1): + return -self._calculate_reciprocal("fdiff", argindex)/self**2 + + def _eval_rewrite_as_exp(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_exp", arg) + + def _eval_rewrite_as_Pow(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_Pow", arg) + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_sin", arg) + + def _eval_rewrite_as_cos(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_cos", arg) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_tan", arg) + + def _eval_rewrite_as_pow(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_pow", arg) + + def _eval_rewrite_as_sqrt(self, arg, **kwargs): + return self._rewrite_reciprocal("_eval_rewrite_as_sqrt", arg) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def as_real_imag(self, deep=True, **hints): + return (1/self._reciprocal_of(self.args[0])).as_real_imag(deep, + **hints) + + def _eval_expand_trig(self, **hints): + return self._calculate_reciprocal("_eval_expand_trig", **hints) + + def _eval_is_extended_real(self): + return self._reciprocal_of(self.args[0])._eval_is_extended_real() + + def _eval_as_leading_term(self, x, logx, cdir): + return (1/self._reciprocal_of(self.args[0]))._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_is_finite(self): + return (1/self._reciprocal_of(self.args[0])).is_finite + + def _eval_nseries(self, x, n, logx, cdir=0): + return (1/self._reciprocal_of(self.args[0]))._eval_nseries(x, n, logx) + + +class sec(ReciprocalTrigonometricFunction): + """ + The secant function. + + Returns the secant of x (measured in radians). + + Explanation + =========== + + See :class:`sin` for notes about automatic evaluation. + + Examples + ======== + + >>> from sympy import sec + >>> from sympy.abc import x + >>> sec(x**2).diff(x) + 2*x*tan(x**2)*sec(x**2) + >>> sec(1).diff(x) + 0 + + See Also + ======== + + sin, csc, cos, tan, cot + asin, acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_functions + .. [2] https://dlmf.nist.gov/4.14 + .. [3] https://functions.wolfram.com/ElementaryFunctions/Sec + + """ + + _reciprocal_of = cos + _is_even = True + + def period(self, symbol=None): + return self._period(symbol) + + def _eval_rewrite_as_cot(self, arg, **kwargs): + cot_half_sq = cot(arg/2)**2 + return (cot_half_sq + 1)/(cot_half_sq - 1) + + def _eval_rewrite_as_cos(self, arg, **kwargs): + return (1/cos(arg)) + + def _eval_rewrite_as_sincos(self, arg, **kwargs): + return sin(arg)/(cos(arg)*sin(arg)) + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return (1/cos(arg).rewrite(sin, **kwargs)) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + return (1/cos(arg).rewrite(tan, **kwargs)) + + def _eval_rewrite_as_csc(self, arg, **kwargs): + return csc(pi/2 - arg, evaluate=False) + + def fdiff(self, argindex=1): + if argindex == 1: + return tan(self.args[0])*sec(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_besselj(self, arg, **kwargs): + from sympy.functions.special.bessel import besselj + return Piecewise( + (1/(sqrt(pi*arg)/(sqrt(2))*besselj(-S.Half, arg)), Ne(arg, 0)), + (1, True) + ) + + def _eval_is_complex(self): + arg = self.args[0] + + if arg.is_complex and (arg/pi - S.Half).is_integer is False: + return True + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + # Reference Formula: + # https://functions.wolfram.com/ElementaryFunctions/Sec/06/01/02/01/ + if n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + k = n//2 + return S.NegativeOne**k*euler(2*k)/factorial(2*k)*x**(2*k) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.functions.elementary.complexes import re + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + n = (x0 + pi/2)/pi + if n.is_integer: + lt = (arg - n*pi + pi/2).as_leading_term(x) + return (S.NegativeOne**n)/lt + if x0 is S.ComplexInfinity: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if x0 in (S.Infinity, S.NegativeInfinity): + return AccumBounds(S.NegativeInfinity, S.Infinity) + return self.func(x0) if x0.is_finite else self + + +class csc(ReciprocalTrigonometricFunction): + """ + The cosecant function. + + Returns the cosecant of x (measured in radians). + + Explanation + =========== + + See :func:`sin` for notes about automatic evaluation. + + Examples + ======== + + >>> from sympy import csc + >>> from sympy.abc import x + >>> csc(x**2).diff(x) + -2*x*cot(x**2)*csc(x**2) + >>> csc(1).diff(x) + 0 + + See Also + ======== + + sin, cos, sec, tan, cot + asin, acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_functions + .. [2] https://dlmf.nist.gov/4.14 + .. [3] https://functions.wolfram.com/ElementaryFunctions/Csc + + """ + + _reciprocal_of = sin + _is_odd = True + + def period(self, symbol=None): + return self._period(symbol) + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return (1/sin(arg)) + + def _eval_rewrite_as_sincos(self, arg, **kwargs): + return cos(arg)/(sin(arg)*cos(arg)) + + def _eval_rewrite_as_cot(self, arg, **kwargs): + cot_half = cot(arg/2) + return (1 + cot_half**2)/(2*cot_half) + + def _eval_rewrite_as_cos(self, arg, **kwargs): + return 1/sin(arg).rewrite(cos, **kwargs) + + def _eval_rewrite_as_sec(self, arg, **kwargs): + return sec(pi/2 - arg, evaluate=False) + + def _eval_rewrite_as_tan(self, arg, **kwargs): + return (1/sin(arg).rewrite(tan, **kwargs)) + + def _eval_rewrite_as_besselj(self, arg, **kwargs): + from sympy.functions.special.bessel import besselj + return sqrt(2/pi)*(1/(sqrt(arg)*besselj(S.Half, arg))) + + def fdiff(self, argindex=1): + if argindex == 1: + return -cot(self.args[0])*csc(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_complex(self): + arg = self.args[0] + if arg.is_real and (arg/pi).is_integer is False: + return True + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return 1/sympify(x) + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + k = n//2 + 1 + return (S.NegativeOne**(k - 1)*2*(2**(2*k - 1) - 1)* + bernoulli(2*k)*x**(2*k - 1)/factorial(2*k)) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.calculus.accumulationbounds import AccumBounds + from sympy.functions.elementary.complexes import re + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + n = x0/pi + if n.is_integer: + lt = (arg - n*pi).as_leading_term(x) + return (S.NegativeOne**n)/lt + if x0 is S.ComplexInfinity: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if x0 in (S.Infinity, S.NegativeInfinity): + return AccumBounds(S.NegativeInfinity, S.Infinity) + return self.func(x0) if x0.is_finite else self + + +class sinc(DefinedFunction): + r""" + Represents an unnormalized sinc function: + + .. math:: + + \operatorname{sinc}(x) = + \begin{cases} + \frac{\sin x}{x} & \qquad x \neq 0 \\ + 1 & \qquad x = 0 + \end{cases} + + Examples + ======== + + >>> from sympy import sinc, oo, jn + >>> from sympy.abc import x + >>> sinc(x) + sinc(x) + + * Automated Evaluation + + >>> sinc(0) + 1 + >>> sinc(oo) + 0 + + * Differentiation + + >>> sinc(x).diff() + cos(x)/x - sin(x)/x**2 + + * Series Expansion + + >>> sinc(x).series() + 1 - x**2/6 + x**4/120 + O(x**6) + + * As zero'th order spherical Bessel Function + + >>> sinc(x).rewrite(jn) + jn(0, x) + + See also + ======== + + sin + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Sinc_function + + """ + _singularities = (S.ComplexInfinity,) + + def fdiff(self, argindex=1): + x = self.args[0] + if argindex == 1: + # We would like to return the Piecewise here, but Piecewise.diff + # currently can't handle removable singularities, meaning things + # like sinc(x).diff(x, 2) give the wrong answer at x = 0. See + # https://github.com/sympy/sympy/issues/11402. + # + # return Piecewise(((x*cos(x) - sin(x))/x**2, Ne(x, S.Zero)), (S.Zero, S.true)) + return cos(x)/x - sin(x)/x**2 + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_zero: + return S.One + if arg.is_Number: + if arg in [S.Infinity, S.NegativeInfinity]: + return S.Zero + elif arg is S.NaN: + return S.NaN + + if arg is S.ComplexInfinity: + return S.NaN + + if arg.could_extract_minus_sign(): + return cls(-arg) + + pi_coeff = _pi_coeff(arg) + if pi_coeff is not None: + if pi_coeff.is_integer: + if fuzzy_not(arg.is_zero): + return S.Zero + elif (2*pi_coeff).is_integer: + return S.NegativeOne**(pi_coeff - S.Half)/arg + + def _eval_nseries(self, x, n, logx, cdir=0): + x = self.args[0] + return (sin(x)/x)._eval_nseries(x, n, logx) + + def _eval_rewrite_as_jn(self, arg, **kwargs): + from sympy.functions.special.bessel import jn + return jn(0, arg) + + def _eval_rewrite_as_sin(self, arg, **kwargs): + return Piecewise((sin(arg)/arg, Ne(arg, S.Zero)), (S.One, S.true)) + + def _eval_is_zero(self): + if self.args[0].is_infinite: + return True + rest, pi_mult = _peeloff_pi(self.args[0]) + if rest.is_zero: + return fuzzy_and([pi_mult.is_integer, pi_mult.is_nonzero]) + if rest.is_Number and pi_mult.is_integer: + return False + + def _eval_is_real(self): + if self.args[0].is_extended_real or self.args[0].is_imaginary: + return True + + _eval_is_finite = _eval_is_real + + +############################################################################### +########################### TRIGONOMETRIC INVERSES ############################ +############################################################################### + + +class InverseTrigonometricFunction(DefinedFunction): + """Base class for inverse trigonometric functions.""" + _singularities: tuple[Expr, ...] = (S.One, S.NegativeOne, S.Zero, S.ComplexInfinity) + + @staticmethod + @cacheit + def _asin_table(): + # Only keys with could_extract_minus_sign() == False + # are actually needed. + return { + sqrt(3)/2: pi/3, + sqrt(2)/2: pi/4, + 1/sqrt(2): pi/4, + sqrt((5 - sqrt(5))/8): pi/5, + sqrt(2)*sqrt(5 - sqrt(5))/4: pi/5, + sqrt((5 + sqrt(5))/8): pi*Rational(2, 5), + sqrt(2)*sqrt(5 + sqrt(5))/4: pi*Rational(2, 5), + S.Half: pi/6, + sqrt(2 - sqrt(2))/2: pi/8, + sqrt(S.Half - sqrt(2)/4): pi/8, + sqrt(2 + sqrt(2))/2: pi*Rational(3, 8), + sqrt(S.Half + sqrt(2)/4): pi*Rational(3, 8), + (sqrt(5) - 1)/4: pi/10, + (1 - sqrt(5))/4: -pi/10, + (sqrt(5) + 1)/4: pi*Rational(3, 10), + sqrt(6)/4 - sqrt(2)/4: pi/12, + -sqrt(6)/4 + sqrt(2)/4: -pi/12, + (sqrt(3) - 1)/sqrt(8): pi/12, + (1 - sqrt(3))/sqrt(8): -pi/12, + sqrt(6)/4 + sqrt(2)/4: pi*Rational(5, 12), + (1 + sqrt(3))/sqrt(8): pi*Rational(5, 12) + } + + + @staticmethod + @cacheit + def _atan_table(): + # Only keys with could_extract_minus_sign() == False + # are actually needed. + return { + sqrt(3)/3: pi/6, + 1/sqrt(3): pi/6, + sqrt(3): pi/3, + sqrt(2) - 1: pi/8, + 1 - sqrt(2): -pi/8, + 1 + sqrt(2): pi*Rational(3, 8), + sqrt(5 - 2*sqrt(5)): pi/5, + sqrt(5 + 2*sqrt(5)): pi*Rational(2, 5), + sqrt(1 - 2*sqrt(5)/5): pi/10, + sqrt(1 + 2*sqrt(5)/5): pi*Rational(3, 10), + 2 - sqrt(3): pi/12, + -2 + sqrt(3): -pi/12, + 2 + sqrt(3): pi*Rational(5, 12) + } + + @staticmethod + @cacheit + def _acsc_table(): + # Keys for which could_extract_minus_sign() + # will obviously return True are omitted. + return { + 2*sqrt(3)/3: pi/3, + sqrt(2): pi/4, + sqrt(2 + 2*sqrt(5)/5): pi/5, + 1/sqrt(Rational(5, 8) - sqrt(5)/8): pi/5, + sqrt(2 - 2*sqrt(5)/5): pi*Rational(2, 5), + 1/sqrt(Rational(5, 8) + sqrt(5)/8): pi*Rational(2, 5), + 2: pi/6, + sqrt(4 + 2*sqrt(2)): pi/8, + 2/sqrt(2 - sqrt(2)): pi/8, + sqrt(4 - 2*sqrt(2)): pi*Rational(3, 8), + 2/sqrt(2 + sqrt(2)): pi*Rational(3, 8), + 1 + sqrt(5): pi/10, + sqrt(5) - 1: pi*Rational(3, 10), + -(sqrt(5) - 1): pi*Rational(-3, 10), + sqrt(6) + sqrt(2): pi/12, + sqrt(6) - sqrt(2): pi*Rational(5, 12), + -(sqrt(6) - sqrt(2)): pi*Rational(-5, 12) + } + + +class asin(InverseTrigonometricFunction): + r""" + The inverse sine function. + + Returns the arcsine of x in radians. + + Explanation + =========== + + ``asin(x)`` will evaluate automatically in the cases + $x \in \{\infty, -\infty, 0, 1, -1\}$ and for some instances when the + result is a rational multiple of $\pi$ (see the ``eval`` class method). + + A purely imaginary argument will lead to an asinh expression. + + Examples + ======== + + >>> from sympy import asin, oo + >>> asin(1) + pi/2 + >>> asin(-1) + -pi/2 + >>> asin(-oo) + oo*I + >>> asin(oo) + -oo*I + + See Also + ======== + + sin, csc, cos, sec, tan, cot + acsc, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_trigonometric_functions + .. [2] https://dlmf.nist.gov/4.23 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcSin + + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return 1/sqrt(1 - self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + if s.args[0].is_rational: + return False + else: + return s.is_rational + + def _eval_is_positive(self): + return self._eval_is_extended_real() and self.args[0].is_positive + + def _eval_is_negative(self): + return self._eval_is_extended_real() and self.args[0].is_negative + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.NegativeInfinity*S.ImaginaryUnit + elif arg is S.NegativeInfinity: + return S.Infinity*S.ImaginaryUnit + elif arg.is_zero: + return S.Zero + elif arg is S.One: + return pi/2 + elif arg is S.NegativeOne: + return -pi/2 + + if arg is S.ComplexInfinity: + return S.ComplexInfinity + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_number: + asin_table = cls._asin_table() + if arg in asin_table: + return asin_table[arg] + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import asinh + return S.ImaginaryUnit*asinh(i_coeff) + + if arg.is_zero: + return S.Zero + + if isinstance(arg, sin): + ang = arg.args[0] + if ang.is_comparable: + ang %= 2*pi # restrict to [0,2*pi) + if ang > pi: # restrict to (-pi,pi] + ang = pi - ang + + # restrict to [-pi/2,pi/2] + if ang > pi/2: + ang = pi - ang + if ang < -pi/2: + ang = -pi - ang + + return ang + + if isinstance(arg, cos): # acos(x) + asin(x) = pi/2 + ang = arg.args[0] + if ang.is_comparable: + return pi/2 - acos(arg) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) >= 2 and n > 2: + p = previous_terms[-2] + return p*(n - 2)**2/(n*(n - 1))*x**2 + else: + k = (n - 1) // 2 + R = RisingFactorial(S.Half, k) + F = factorial(k) + return R/F*x**n/n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.NaN: + return self.func(arg.as_leading_term(x)) + if x0.is_zero: + return arg.as_leading_term(x) + + # Handling branch points + if x0 in (-S.One, S.One, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + # Handling points lying on branch cuts (-oo, -1) U (1, oo) + if (1 - x0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if x0.is_negative: + return -pi - self.func(x0) + elif im(ndir).is_positive: + if x0.is_positive: + return pi - self.func(x0) + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # asin + from sympy.series.order import O + arg0 = self.args[0].subs(x, 0) + # Handling branch points + if arg0 is S.One: + t = Dummy('t', positive=True) + ser = asin(S.One - t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.One - self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else pi/2 + O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + if arg0 is S.NegativeOne: + t = Dummy('t', positive=True) + ser = asin(S.NegativeOne + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.One + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else -pi/2 + O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + # Handling points lying on branch cuts (-oo, -1) U (1, oo) + if (1 - arg0**2).is_negative: + ndir = self.args[0].dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if arg0.is_negative: + return -pi - res + elif im(ndir).is_positive: + if arg0.is_positive: + return pi - res + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_acos(self, x, **kwargs): + return pi/2 - acos(x) + + def _eval_rewrite_as_atan(self, x, **kwargs): + return 2*atan(x/(1 + sqrt(1 - x**2))) + + def _eval_rewrite_as_log(self, x, **kwargs): + return -S.ImaginaryUnit*log(S.ImaginaryUnit*x + sqrt(1 - x**2)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_acot(self, arg, **kwargs): + return 2*acot((1 + sqrt(1 - arg**2))/arg) + + def _eval_rewrite_as_asec(self, arg, **kwargs): + return pi/2 - asec(1/arg) + + def _eval_rewrite_as_acsc(self, arg, **kwargs): + return acsc(1/arg) + + def _eval_is_extended_real(self): + x = self.args[0] + return x.is_extended_real and (1 - abs(x)).is_nonnegative + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return sin + + +class acos(InverseTrigonometricFunction): + r""" + The inverse cosine function. + + Explanation + =========== + + Returns the arc cosine of x (measured in radians). + + ``acos(x)`` will evaluate automatically in the cases + $x \in \{\infty, -\infty, 0, 1, -1\}$ and for some instances when + the result is a rational multiple of $\pi$ (see the eval class method). + + ``acos(zoo)`` evaluates to ``zoo`` + (see note in :class:`sympy.functions.elementary.trigonometric.asec`) + + A purely imaginary argument will be rewritten to asinh. + + Examples + ======== + + >>> from sympy import acos, oo + >>> acos(1) + 0 + >>> acos(0) + pi/2 + >>> acos(oo) + oo*I + + See Also + ======== + + sin, csc, cos, sec, tan, cot + asin, acsc, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_trigonometric_functions + .. [2] https://dlmf.nist.gov/4.23 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcCos + + """ + + def fdiff(self, argindex=1): + if argindex == 1: + return -1/sqrt(1 - self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + if s.args[0].is_rational: + return False + else: + return s.is_rational + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity*S.ImaginaryUnit + elif arg is S.NegativeInfinity: + return S.NegativeInfinity*S.ImaginaryUnit + elif arg.is_zero: + return pi/2 + elif arg is S.One: + return S.Zero + elif arg is S.NegativeOne: + return pi + + if arg is S.ComplexInfinity: + return S.ComplexInfinity + + if arg.is_number: + asin_table = cls._asin_table() + if arg in asin_table: + return pi/2 - asin_table[arg] + elif -arg in asin_table: + return pi/2 + asin_table[-arg] + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + return pi/2 - asin(arg) + + if arg.is_Mul and len(arg.args) == 2 and arg.args[0] == -1: + narg = arg.args[1] + minus = True + else: + narg = arg + minus = False + + if isinstance(narg, cos): + # acos(cos(x)) = x or acos(-cos(x)) = pi - x + ang = narg.args[0] + if ang.is_comparable: + if minus: + ang = pi - ang + ang %= 2*pi # restrict to [0,2*pi) + if ang > pi: # restrict to [0,pi] + ang = 2*pi - ang + return ang + + if isinstance(narg, sin): # acos(x) + asin(x) = pi/2 + ang = narg.args[0] + if ang.is_comparable: + if minus: + return pi/2 + asin(narg) + return pi/2 - asin(narg) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return pi/2 + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) >= 2 and n > 2: + p = previous_terms[-2] + return p*(n - 2)**2/(n*(n - 1))*x**2 + else: + k = (n - 1) // 2 + R = RisingFactorial(S.Half, k) + F = factorial(k) + return -R/F*x**n/n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.NaN: + return self.func(arg.as_leading_term(x)) + # Handling branch points + if x0 == 1: + return sqrt(2)*sqrt((S.One - arg).as_leading_term(x)) + if x0 in (-S.One, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + # Handling points lying on branch cuts (-oo, -1) U (1, oo) + if (1 - x0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if x0.is_negative: + return 2*pi - self.func(x0) + elif im(ndir).is_positive: + if x0.is_positive: + return -self.func(x0) + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + return self.func(x0) + + def _eval_is_extended_real(self): + x = self.args[0] + return x.is_extended_real and (1 - abs(x)).is_nonnegative + + def _eval_is_nonnegative(self): + return self._eval_is_extended_real() + + def _eval_nseries(self, x, n, logx, cdir=0): # acos + from sympy.series.order import O + arg0 = self.args[0].subs(x, 0) + # Handling branch points + if arg0 is S.One: + t = Dummy('t', positive=True) + ser = acos(S.One - t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.One - self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + if arg0 is S.NegativeOne: + t = Dummy('t', positive=True) + ser = acos(S.NegativeOne + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.One + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + if not g.is_meromorphic(x, 0): # cannot be expanded + return O(1) if n == 0 else pi + O(sqrt(x)) + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + # Handling points lying on branch cuts (-oo, -1) U (1, oo) + if (1 - arg0**2).is_negative: + ndir = self.args[0].dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if arg0.is_negative: + return 2*pi - res + elif im(ndir).is_positive: + if arg0.is_positive: + return -res + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, x, **kwargs): + return pi/2 + S.ImaginaryUnit*\ + log(S.ImaginaryUnit*x + sqrt(1 - x**2)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_asin(self, x, **kwargs): + return pi/2 - asin(x) + + def _eval_rewrite_as_atan(self, x, **kwargs): + return atan(sqrt(1 - x**2)/x) + (pi/2)*(1 - x*sqrt(1/x**2)) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return cos + + def _eval_rewrite_as_acot(self, arg, **kwargs): + return pi/2 - 2*acot((1 + sqrt(1 - arg**2))/arg) + + def _eval_rewrite_as_asec(self, arg, **kwargs): + return asec(1/arg) + + def _eval_rewrite_as_acsc(self, arg, **kwargs): + return pi/2 - acsc(1/arg) + + def _eval_conjugate(self): + z = self.args[0] + r = self.func(self.args[0].conjugate()) + if z.is_extended_real is False: + return r + elif z.is_extended_real and (z + 1).is_nonnegative and (z - 1).is_nonpositive: + return r + + +class atan(InverseTrigonometricFunction): + r""" + The inverse tangent function. + + Returns the arc tangent of x (measured in radians). + + Explanation + =========== + + ``atan(x)`` will evaluate automatically in the cases + $x \in \{\infty, -\infty, 0, 1, -1\}$ and for some instances when the + result is a rational multiple of $\pi$ (see the eval class method). + + Examples + ======== + + >>> from sympy import atan, oo + >>> atan(0) + 0 + >>> atan(1) + pi/4 + >>> atan(oo) + pi/2 + + See Also + ======== + + sin, csc, cos, sec, tan, cot + asin, acsc, acos, asec, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_trigonometric_functions + .. [2] https://dlmf.nist.gov/4.23 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcTan + + """ + + args: tuple[Expr] + + _singularities = (S.ImaginaryUnit, -S.ImaginaryUnit) + + def fdiff(self, argindex=1): + if argindex == 1: + return 1/(1 + self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + if s.args[0].is_rational: + return False + else: + return s.is_rational + + def _eval_is_positive(self): + return self.args[0].is_extended_positive + + def _eval_is_nonnegative(self): + return self.args[0].is_extended_nonnegative + + def _eval_is_zero(self): + return self.args[0].is_zero + + def _eval_is_real(self): + return self.args[0].is_extended_real + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return pi/2 + elif arg is S.NegativeInfinity: + return -pi/2 + elif arg.is_zero: + return S.Zero + elif arg is S.One: + return pi/4 + elif arg is S.NegativeOne: + return -pi/4 + + if arg is S.ComplexInfinity: + from sympy.calculus.accumulationbounds import AccumBounds + return AccumBounds(-pi/2, pi/2) + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_number: + atan_table = cls._atan_table() + if arg in atan_table: + return atan_table[arg] + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import atanh + return S.ImaginaryUnit*atanh(i_coeff) + + if arg.is_zero: + return S.Zero + + if isinstance(arg, tan): + ang = arg.args[0] + if ang.is_comparable: + ang %= pi # restrict to [0,pi) + if ang > pi/2: # restrict to [-pi/2,pi/2] + ang -= pi + + return ang + + if isinstance(arg, cot): # atan(x) + acot(x) = pi/2 + ang = arg.args[0] + if ang.is_comparable: + ang = pi/2 - acot(arg) + if ang > pi/2: # restrict to [-pi/2,pi/2] + ang -= pi + return ang + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + return S.NegativeOne**((n - 1)//2)*x**n/n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.NaN: + return self.func(arg.as_leading_term(x)) + if x0.is_zero: + return arg.as_leading_term(x) + # Handling branch points + if x0 in (-S.ImaginaryUnit, S.ImaginaryUnit, S.ComplexInfinity): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + # Handling points lying on branch cuts (-I*oo, -I) U (I, I*oo) + if (1 + x0**2).is_negative: + ndir = arg.dir(x, cdir if cdir else 1) + if re(ndir).is_negative: + if im(x0).is_positive: + return self.func(x0) - pi + elif re(ndir).is_positive: + if im(x0).is_negative: + return self.func(x0) + pi + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # atan + arg0 = self.args[0].subs(x, 0) + + # Handling branch points + if arg0 in (S.ImaginaryUnit, S.NegativeOne*S.ImaginaryUnit): + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + + res = super()._eval_nseries(x, n=n, logx=logx) + ndir = self.args[0].dir(x, cdir if cdir else 1) + if arg0 is S.ComplexInfinity: + if re(ndir) > 0: + return res - pi + return res + # Handling points lying on branch cuts (-I*oo, -I) U (I, I*oo) + if (1 + arg0**2).is_negative: + if re(ndir).is_negative: + if im(arg0).is_positive: + return res - pi + elif re(ndir).is_positive: + if im(arg0).is_negative: + return res + pi + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, x, **kwargs): + return S.ImaginaryUnit/2*(log(S.One - S.ImaginaryUnit*x) + - log(S.One + S.ImaginaryUnit*x)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_aseries(self, n, args0, x, logx): + if args0[0] in [S.Infinity, S.NegativeInfinity]: + return (pi/2 - atan(1/self.args[0]))._eval_nseries(x, n, logx) + else: + return super()._eval_aseries(n, args0, x, logx) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return tan + + def _eval_rewrite_as_asin(self, arg, **kwargs): + return sqrt(arg**2)/arg*(pi/2 - asin(1/sqrt(1 + arg**2))) + + def _eval_rewrite_as_acos(self, arg, **kwargs): + return sqrt(arg**2)/arg*acos(1/sqrt(1 + arg**2)) + + def _eval_rewrite_as_acot(self, arg, **kwargs): + return acot(1/arg) + + def _eval_rewrite_as_asec(self, arg, **kwargs): + return sqrt(arg**2)/arg*asec(sqrt(1 + arg**2)) + + def _eval_rewrite_as_acsc(self, arg, **kwargs): + return sqrt(arg**2)/arg*(pi/2 - acsc(sqrt(1 + arg**2))) + + +class acot(InverseTrigonometricFunction): + r""" + The inverse cotangent function. + + Returns the arc cotangent of x (measured in radians). + + Explanation + =========== + + ``acot(x)`` will evaluate automatically in the cases + $x \in \{\infty, -\infty, \tilde{\infty}, 0, 1, -1\}$ + and for some instances when the result is a rational multiple of $\pi$ + (see the eval class method). + + A purely imaginary argument will lead to an ``acoth`` expression. + + ``acot(x)`` has a branch cut along $(-i, i)$, hence it is discontinuous + at 0. Its range for real $x$ is $(-\frac{\pi}{2}, \frac{\pi}{2}]$. + + Examples + ======== + + >>> from sympy import acot, sqrt + >>> acot(0) + pi/2 + >>> acot(1) + pi/4 + >>> acot(sqrt(3) - 2) + -5*pi/12 + + See Also + ======== + + sin, csc, cos, sec, tan, cot + asin, acsc, acos, asec, atan, atan2 + + References + ========== + + .. [1] https://dlmf.nist.gov/4.23 + .. [2] https://functions.wolfram.com/ElementaryFunctions/ArcCot + + """ + _singularities = (S.ImaginaryUnit, -S.ImaginaryUnit) + + def fdiff(self, argindex=1): + if argindex == 1: + return -1/(1 + self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_rational(self): + s = self.func(*self.args) + if s.func == self.func: + if s.args[0].is_rational: + return False + else: + return s.is_rational + + def _eval_is_positive(self): + return self.args[0].is_nonnegative + + def _eval_is_negative(self): + return self.args[0].is_negative + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Zero + elif arg is S.NegativeInfinity: + return S.Zero + elif arg.is_zero: + return pi/ 2 + elif arg is S.One: + return pi/4 + elif arg is S.NegativeOne: + return -pi/4 + + if arg is S.ComplexInfinity: + return S.Zero + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_number: + atan_table = cls._atan_table() + if arg in atan_table: + ang = pi/2 - atan_table[arg] + if ang > pi/2: # restrict to (-pi/2,pi/2] + ang -= pi + return ang + + i_coeff = _imaginary_unit_as_coefficient(arg) + if i_coeff is not None: + from sympy.functions.elementary.hyperbolic import acoth + return -S.ImaginaryUnit*acoth(i_coeff) + + if arg.is_zero: + return pi*S.Half + + if isinstance(arg, cot): + ang = arg.args[0] + if ang.is_comparable: + ang %= pi # restrict to [0,pi) + if ang > pi/2: # restrict to (-pi/2,pi/2] + ang -= pi + return ang + + if isinstance(arg, tan): # atan(x) + acot(x) = pi/2 + ang = arg.args[0] + if ang.is_comparable: + ang = pi/2 - atan(arg) + if ang > pi/2: # restrict to (-pi/2,pi/2] + ang -= pi + return ang + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return pi/2 # FIX THIS + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + return S.NegativeOne**((n + 1)//2)*x**n/n + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.NaN: + return self.func(arg.as_leading_term(x)) + if x0 is S.ComplexInfinity: + return (1/arg).as_leading_term(x) + # Handling branch points + if x0 in (-S.ImaginaryUnit, S.ImaginaryUnit, S.Zero): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + # Handling points lying on branch cuts [-I, I] + if x0.is_imaginary and (1 + x0**2).is_positive: + ndir = arg.dir(x, cdir if cdir else 1) + if re(ndir).is_positive: + if im(x0).is_positive: + return self.func(x0) + pi + elif re(ndir).is_negative: + if im(x0).is_negative: + return self.func(x0) - pi + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # acot + arg0 = self.args[0].subs(x, 0) + + # Handling branch points + if arg0 in (S.ImaginaryUnit, S.NegativeOne*S.ImaginaryUnit): + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + ndir = self.args[0].dir(x, cdir if cdir else 1) + if arg0.is_zero: + if re(ndir) < 0: + return res - pi + return res + # Handling points lying on branch cuts [-I, I] + if arg0.is_imaginary and (1 + arg0**2).is_positive: + if re(ndir).is_positive: + if im(arg0).is_positive: + return res + pi + elif re(ndir).is_negative: + if im(arg0).is_negative: + return res - pi + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_aseries(self, n, args0, x, logx): + if args0[0] in [S.Infinity, S.NegativeInfinity]: + return atan(1/self.args[0])._eval_nseries(x, n, logx) + else: + return super()._eval_aseries(n, args0, x, logx) + + def _eval_rewrite_as_log(self, x, **kwargs): + return S.ImaginaryUnit/2*(log(1 - S.ImaginaryUnit/x) + - log(1 + S.ImaginaryUnit/x)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return cot + + def _eval_rewrite_as_asin(self, arg, **kwargs): + return (arg*sqrt(1/arg**2)* + (pi/2 - asin(sqrt(-arg**2)/sqrt(-arg**2 - 1)))) + + def _eval_rewrite_as_acos(self, arg, **kwargs): + return arg*sqrt(1/arg**2)*acos(sqrt(-arg**2)/sqrt(-arg**2 - 1)) + + def _eval_rewrite_as_atan(self, arg, **kwargs): + return atan(1/arg) + + def _eval_rewrite_as_asec(self, arg, **kwargs): + return arg*sqrt(1/arg**2)*asec(sqrt((1 + arg**2)/arg**2)) + + def _eval_rewrite_as_acsc(self, arg, **kwargs): + return arg*sqrt(1/arg**2)*(pi/2 - acsc(sqrt((1 + arg**2)/arg**2))) + + +class asec(InverseTrigonometricFunction): + r""" + The inverse secant function. + + Returns the arc secant of x (measured in radians). + + Explanation + =========== + + ``asec(x)`` will evaluate automatically in the cases + $x \in \{\infty, -\infty, 0, 1, -1\}$ and for some instances when the + result is a rational multiple of $\pi$ (see the eval class method). + + ``asec(x)`` has branch cut in the interval $[-1, 1]$. For complex arguments, + it can be defined [4]_ as + + .. math:: + \operatorname{sec^{-1}}(z) = -i\frac{\log\left(\sqrt{1 - z^2} + 1\right)}{z} + + At ``x = 0``, for positive branch cut, the limit evaluates to ``zoo``. For + negative branch cut, the limit + + .. math:: + \lim_{z \to 0}-i\frac{\log\left(-\sqrt{1 - z^2} + 1\right)}{z} + + simplifies to :math:`-i\log\left(z/2 + O\left(z^3\right)\right)` which + ultimately evaluates to ``zoo``. + + As ``acos(x) = asec(1/x)``, a similar argument can be given for + ``acos(x)``. + + Examples + ======== + + >>> from sympy import asec, oo + >>> asec(1) + 0 + >>> asec(-1) + pi + >>> asec(0) + zoo + >>> asec(-oo) + pi/2 + + See Also + ======== + + sin, csc, cos, sec, tan, cot + asin, acsc, acos, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_trigonometric_functions + .. [2] https://dlmf.nist.gov/4.23 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcSec + .. [4] https://reference.wolfram.com/language/ref/ArcSec.html + + """ + + @classmethod + def eval(cls, arg): + if arg.is_zero: + return S.ComplexInfinity + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.One: + return S.Zero + elif arg is S.NegativeOne: + return pi + if arg in [S.Infinity, S.NegativeInfinity, S.ComplexInfinity]: + return pi/2 + + if arg.is_number: + acsc_table = cls._acsc_table() + if arg in acsc_table: + return pi/2 - acsc_table[arg] + elif -arg in acsc_table: + return pi/2 + acsc_table[-arg] + + if arg.is_infinite: + return pi/2 + + if arg.is_Mul and len(arg.args) == 2 and arg.args[0] == -1: + narg = arg.args[1] + minus = True + else: + narg = arg + minus = False + + if isinstance(narg, sec): + # asec(sec(x)) = x or asec(-sec(x)) = pi - x + ang = narg.args[0] + if ang.is_comparable: + if minus: + ang = pi - ang + ang %= 2*pi # restrict to [0,2*pi) + if ang > pi: # restrict to [0,pi] + ang = 2*pi - ang + return ang + + if isinstance(narg, csc): # asec(x) + acsc(x) = pi/2 + ang = narg.args[0] + if ang.is_comparable: + if minus: + pi/2 + acsc(narg) + return pi/2 - acsc(narg) + + def fdiff(self, argindex=1): + if argindex == 1: + return 1/(self.args[0]**2*sqrt(1 - 1/self.args[0]**2)) + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return sec + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return S.ImaginaryUnit*log(2 / x) + elif n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 2 and n > 2: + p = previous_terms[-2] + return p * ((n - 1)*(n-2)) * x**2/(4 * (n//2)**2) + else: + k = n // 2 + R = RisingFactorial(S.Half, k) * n + F = factorial(k) * n // 2 * n // 2 + return -S.ImaginaryUnit * R / F * x**n / 4 + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.NaN: + return self.func(arg.as_leading_term(x)) + # Handling branch points + if x0 == 1: + return sqrt(2)*sqrt((arg - S.One).as_leading_term(x)) + if x0 in (-S.One, S.Zero): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir) + # Handling points lying on branch cuts (-1, 1) + if x0.is_real and (1 - x0**2).is_positive: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if x0.is_positive: + return -self.func(x0) + elif im(ndir).is_positive: + if x0.is_negative: + return 2*pi - self.func(x0) + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # asec + from sympy.series.order import O + arg0 = self.args[0].subs(x, 0) + # Handling branch points + if arg0 is S.One: + t = Dummy('t', positive=True) + ser = asec(S.One + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.NegativeOne + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + if arg0 is S.NegativeOne: + t = Dummy('t', positive=True) + ser = asec(S.NegativeOne - t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.NegativeOne - self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + # Handling points lying on branch cuts (-1, 1) + if arg0.is_real and (1 - arg0**2).is_positive: + ndir = self.args[0].dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if arg0.is_positive: + return -res + elif im(ndir).is_positive: + if arg0.is_negative: + return 2*pi - res + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_is_extended_real(self): + x = self.args[0] + if x.is_extended_real is False: + return False + return fuzzy_or(((x - 1).is_nonnegative, (-x - 1).is_nonnegative)) + + def _eval_rewrite_as_log(self, arg, **kwargs): + return pi/2 + S.ImaginaryUnit*log(S.ImaginaryUnit/arg + sqrt(1 - 1/arg**2)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_asin(self, arg, **kwargs): + return pi/2 - asin(1/arg) + + def _eval_rewrite_as_acos(self, arg, **kwargs): + return acos(1/arg) + + def _eval_rewrite_as_atan(self, x, **kwargs): + sx2x = sqrt(x**2)/x + return pi/2*(1 - sx2x) + sx2x*atan(sqrt(x**2 - 1)) + + def _eval_rewrite_as_acot(self, x, **kwargs): + sx2x = sqrt(x**2)/x + return pi/2*(1 - sx2x) + sx2x*acot(1/sqrt(x**2 - 1)) + + def _eval_rewrite_as_acsc(self, arg, **kwargs): + return pi/2 - acsc(arg) + + +class acsc(InverseTrigonometricFunction): + r""" + The inverse cosecant function. + + Returns the arc cosecant of x (measured in radians). + + Explanation + =========== + + ``acsc(x)`` will evaluate automatically in the cases + $x \in \{\infty, -\infty, 0, 1, -1\}$` and for some instances when the + result is a rational multiple of $\pi$ (see the ``eval`` class method). + + Examples + ======== + + >>> from sympy import acsc, oo + >>> acsc(1) + pi/2 + >>> acsc(-1) + -pi/2 + >>> acsc(oo) + 0 + >>> acsc(-oo) == acsc(oo) + True + >>> acsc(0) + zoo + + See Also + ======== + + sin, csc, cos, sec, tan, cot + asin, acos, asec, atan, acot, atan2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_trigonometric_functions + .. [2] https://dlmf.nist.gov/4.23 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcCsc + + """ + + @classmethod + def eval(cls, arg): + if arg.is_zero: + return S.ComplexInfinity + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.One: + return pi/2 + elif arg is S.NegativeOne: + return -pi/2 + if arg in [S.Infinity, S.NegativeInfinity, S.ComplexInfinity]: + return S.Zero + + if arg.could_extract_minus_sign(): + return -cls(-arg) + + if arg.is_infinite: + return S.Zero + + if arg.is_number: + acsc_table = cls._acsc_table() + if arg in acsc_table: + return acsc_table[arg] + + if isinstance(arg, csc): + ang = arg.args[0] + if ang.is_comparable: + ang %= 2*pi # restrict to [0,2*pi) + if ang > pi: # restrict to (-pi,pi] + ang = pi - ang + + # restrict to [-pi/2,pi/2] + if ang > pi/2: + ang = pi - ang + if ang < -pi/2: + ang = -pi - ang + + return ang + + if isinstance(arg, sec): # asec(x) + acsc(x) = pi/2 + ang = arg.args[0] + if ang.is_comparable: + return pi/2 - asec(arg) + + def fdiff(self, argindex=1): + if argindex == 1: + return -1/(self.args[0]**2*sqrt(1 - 1/self.args[0]**2)) + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + """ + return csc + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return pi/2 - S.ImaginaryUnit*log(2) + S.ImaginaryUnit*log(x) + elif n < 0 or n % 2 == 1: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 2 and n > 2: + p = previous_terms[-2] + return p * ((n - 1)*(n-2)) * x**2/(4 * (n//2)**2) + else: + k = n // 2 + R = RisingFactorial(S.Half, k) * n + F = factorial(k) * n // 2 * n // 2 + return S.ImaginaryUnit * R / F * x**n / 4 + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0).cancel() + if x0 is S.NaN: + return self.func(arg.as_leading_term(x)) + # Handling branch points + if x0 in (-S.One, S.One, S.Zero): + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + if x0 is S.ComplexInfinity: + return (1/arg).as_leading_term(x) + # Handling points lying on branch cuts (-1, 1) + if x0.is_real and (1 - x0**2).is_positive: + ndir = arg.dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if x0.is_positive: + return pi - self.func(x0) + elif im(ndir).is_positive: + if x0.is_negative: + return -pi - self.func(x0) + else: + return self.rewrite(log)._eval_as_leading_term(x, logx=logx, cdir=cdir).expand() + return self.func(x0) + + def _eval_nseries(self, x, n, logx, cdir=0): # acsc + from sympy.series.order import O + arg0 = self.args[0].subs(x, 0) + # Handling branch points + if arg0 is S.One: + t = Dummy('t', positive=True) + ser = acsc(S.One + t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.NegativeOne + self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + if arg0 is S.NegativeOne: + t = Dummy('t', positive=True) + ser = acsc(S.NegativeOne - t**2).rewrite(log).nseries(t, 0, 2*n) + arg1 = S.NegativeOne - self.args[0] + f = arg1.as_leading_term(x) + g = (arg1 - f)/ f + res1 = sqrt(S.One + g)._eval_nseries(x, n=n, logx=logx) + res = (res1.removeO()*sqrt(f)).expand() + return ser.removeO().subs(t, res).expand().powsimp() + O(x**n, x) + + res = super()._eval_nseries(x, n=n, logx=logx) + if arg0 is S.ComplexInfinity: + return res + # Handling points lying on branch cuts (-1, 1) + if arg0.is_real and (1 - arg0**2).is_positive: + ndir = self.args[0].dir(x, cdir if cdir else 1) + if im(ndir).is_negative: + if arg0.is_positive: + return pi - res + elif im(ndir).is_positive: + if arg0.is_negative: + return -pi - res + else: + return self.rewrite(log)._eval_nseries(x, n, logx=logx, cdir=cdir) + return res + + def _eval_rewrite_as_log(self, arg, **kwargs): + return -S.ImaginaryUnit*log(S.ImaginaryUnit/arg + sqrt(1 - 1/arg**2)) + + _eval_rewrite_as_tractable = _eval_rewrite_as_log + + def _eval_rewrite_as_asin(self, arg, **kwargs): + return asin(1/arg) + + def _eval_rewrite_as_acos(self, arg, **kwargs): + return pi/2 - acos(1/arg) + + def _eval_rewrite_as_atan(self, x, **kwargs): + return sqrt(x**2)/x*(pi/2 - atan(sqrt(x**2 - 1))) + + def _eval_rewrite_as_acot(self, arg, **kwargs): + return sqrt(arg**2)/arg*(pi/2 - acot(1/sqrt(arg**2 - 1))) + + def _eval_rewrite_as_asec(self, arg, **kwargs): + return pi/2 - asec(arg) + + +class atan2(InverseTrigonometricFunction): + r""" + The function ``atan2(y, x)`` computes `\operatorname{atan}(y/x)` taking + two arguments `y` and `x`. Signs of both `y` and `x` are considered to + determine the appropriate quadrant of `\operatorname{atan}(y/x)`. + The range is `(-\pi, \pi]`. The complete definition reads as follows: + + .. math:: + + \operatorname{atan2}(y, x) = + \begin{cases} + \arctan\left(\frac y x\right) & \qquad x > 0 \\ + \arctan\left(\frac y x\right) + \pi& \qquad y \ge 0, x < 0 \\ + \arctan\left(\frac y x\right) - \pi& \qquad y < 0, x < 0 \\ + +\frac{\pi}{2} & \qquad y > 0, x = 0 \\ + -\frac{\pi}{2} & \qquad y < 0, x = 0 \\ + \text{undefined} & \qquad y = 0, x = 0 + \end{cases} + + Attention: Note the role reversal of both arguments. The `y`-coordinate + is the first argument and the `x`-coordinate the second. + + If either `x` or `y` is complex: + + .. math:: + + \operatorname{atan2}(y, x) = + -i\log\left(\frac{x + iy}{\sqrt{x^2 + y^2}}\right) + + Examples + ======== + + Going counter-clock wise around the origin we find the + following angles: + + >>> from sympy import atan2 + >>> atan2(0, 1) + 0 + >>> atan2(1, 1) + pi/4 + >>> atan2(1, 0) + pi/2 + >>> atan2(1, -1) + 3*pi/4 + >>> atan2(0, -1) + pi + >>> atan2(-1, -1) + -3*pi/4 + >>> atan2(-1, 0) + -pi/2 + >>> atan2(-1, 1) + -pi/4 + + which are all correct. Compare this to the results of the ordinary + `\operatorname{atan}` function for the point `(x, y) = (-1, 1)` + + >>> from sympy import atan, S + >>> atan(S(1)/-1) + -pi/4 + >>> atan2(1, -1) + 3*pi/4 + + where only the `\operatorname{atan2}` function returns what we expect. + We can differentiate the function with respect to both arguments: + + >>> from sympy import diff + >>> from sympy.abc import x, y + >>> diff(atan2(y, x), x) + -y/(x**2 + y**2) + + >>> diff(atan2(y, x), y) + x/(x**2 + y**2) + + We can express the `\operatorname{atan2}` function in terms of + complex logarithms: + + >>> from sympy import log + >>> atan2(y, x).rewrite(log) + -I*log((x + I*y)/sqrt(x**2 + y**2)) + + and in terms of `\operatorname(atan)`: + + >>> from sympy import atan + >>> atan2(y, x).rewrite(atan) + Piecewise((2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)), (pi, re(x) < 0), (0, Ne(x, 0)), (nan, True)) + + but note that this form is undefined on the negative real axis. + + See Also + ======== + + sin, csc, cos, sec, tan, cot + asin, acsc, acos, asec, atan, acot + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Inverse_trigonometric_functions + .. [2] https://en.wikipedia.org/wiki/Atan2 + .. [3] https://functions.wolfram.com/ElementaryFunctions/ArcTan2 + + """ + + @classmethod + def eval(cls, y, x): + from sympy.functions.special.delta_functions import Heaviside + if x is S.NegativeInfinity: + if y.is_zero: + # Special case y = 0 because we define Heaviside(0) = 1/2 + return pi + return 2*pi*(Heaviside(re(y))) - pi + elif x is S.Infinity: + return S.Zero + elif x.is_imaginary and y.is_imaginary and x.is_number and y.is_number: + x = im(x) + y = im(y) + + if x.is_extended_real and y.is_extended_real: + if x.is_positive: + return atan(y/x) + elif x.is_negative: + if y.is_negative: + return atan(y/x) - pi + elif y.is_nonnegative: + return atan(y/x) + pi + elif x.is_zero: + if y.is_positive: + return pi/2 + elif y.is_negative: + return -pi/2 + elif y.is_zero: + return S.NaN + if y.is_zero: + if x.is_extended_nonzero: + return pi*(S.One - Heaviside(x)) + if x.is_number: + return Piecewise((pi, re(x) < 0), + (0, Ne(x, 0)), + (S.NaN, True)) + if x.is_number and y.is_number: + return -S.ImaginaryUnit*log( + (x + S.ImaginaryUnit*y)/sqrt(x**2 + y**2)) + + def _eval_rewrite_as_log(self, y, x, **kwargs): + return -S.ImaginaryUnit*log((x + S.ImaginaryUnit*y)/sqrt(x**2 + y**2)) + + def _eval_rewrite_as_atan(self, y, x, **kwargs): + return Piecewise((2*atan(y/(x + sqrt(x**2 + y**2))), Ne(y, 0)), + (pi, re(x) < 0), + (0, Ne(x, 0)), + (S.NaN, True)) + + def _eval_rewrite_as_arg(self, y, x, **kwargs): + if x.is_extended_real and y.is_extended_real: + return arg_f(x + y*S.ImaginaryUnit) + n = x + S.ImaginaryUnit*y + d = x**2 + y**2 + return arg_f(n/sqrt(d)) - S.ImaginaryUnit*log(abs(n)/sqrt(abs(d))) + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real and self.args[1].is_extended_real + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate(), self.args[1].conjugate()) + + def fdiff(self, argindex): + y, x = self.args + if argindex == 1: + # Diff wrt y + return x/(x**2 + y**2) + elif argindex == 2: + # Diff wrt x + return -y/(x**2 + y**2) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_evalf(self, prec): + y, x = self.args + if x.is_extended_real and y.is_extended_real: + return super()._eval_evalf(prec) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__init__.py b/phivenv/Lib/site-packages/sympy/functions/special/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ab52ace36a8dfbe73179dbf4419a54f7fa1af5fa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/__init__.py @@ -0,0 +1 @@ +# Stub __init__.py for the sympy.functions.special package diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7c42864aa9bf4b912c4416d50ccc3bf51c2490d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/bessel.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/bessel.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99680a7912e45cb27ab609e2159cb07d9c9862fa Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/bessel.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/beta_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/beta_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74725f4dd20a3b5a8b75f227ece3f605ffaab8ea Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/beta_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/bsplines.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/bsplines.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12d2ca9085c5f7c426fbe27158b9f126ae34aacc Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/bsplines.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/delta_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/delta_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8887478a83dfe675328a30d17ac75ef397b72ba Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/delta_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/elliptic_integrals.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/elliptic_integrals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..562058dec7d0309dfe5c606e26e2adb572d3dfd8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/elliptic_integrals.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/error_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/error_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e62fa62963e91ce32bbf316939bb81784636bb52 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/error_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/gamma_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/gamma_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0e8d80eefad4ff7b7391b2da7eea3fefa54a552 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/gamma_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/hyper.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/hyper.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20d77f60e38c314e4dafdd04351eedd8010cca25 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/hyper.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/mathieu_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/mathieu_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ababb523773b52592695f0ec46d2e4575de004be Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/mathieu_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/polynomials.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/polynomials.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9917b1130882af8f3fe662a8e8cc837b0921319d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/polynomials.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/singularity_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/singularity_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e4c1b1f2f895491def9dcf464f89b35fad2d77f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/singularity_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/spherical_harmonics.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/spherical_harmonics.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca349eb059547a866639934dee4fd6ac70820318 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/spherical_harmonics.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/tensor_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/tensor_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..becc789b8383c3f18f993d85a3586715932552e9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/tensor_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/zeta_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/zeta_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc4ee883230de62f1859bca2c44d8d442c616fba Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/__pycache__/zeta_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__init__.py b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e53395c852e7aaa0faa00bed63cc9aa792150714 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__pycache__/bench_special.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__pycache__/bench_special.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a660cea6422f46aada9c9f1295e116747cead41 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/__pycache__/bench_special.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/bench_special.py b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/bench_special.py new file mode 100644 index 0000000000000000000000000000000000000000..25d7280c2cf31dcbff08065a78847ed03e0ebb05 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/benchmarks/bench_special.py @@ -0,0 +1,8 @@ +from sympy.core.symbol import symbols +from sympy.functions.special.spherical_harmonics import Ynm + +x, y = symbols('x,y') + + +def timeit_Ynm_xy(): + Ynm(1, 1, x, y) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/bessel.py b/phivenv/Lib/site-packages/sympy/functions/special/bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..a24e7dc442d2a5a9bf7047113fd81b36c6b6ba36 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/bessel.py @@ -0,0 +1,2208 @@ +from functools import wraps + +from sympy.core import S +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.expr import Expr +from sympy.core.function import DefinedFunction, ArgumentIndexError, _mexpand +from sympy.core.logic import fuzzy_or, fuzzy_not +from sympy.core.numbers import Rational, pi, I +from sympy.core.power import Pow +from sympy.core.symbol import Dummy, uniquely_named_symbol, Wild +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial, RisingFactorial +from sympy.functions.elementary.trigonometric import sin, cos, csc, cot +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.miscellaneous import cbrt, sqrt, root +from sympy.functions.elementary.complexes import (Abs, re, im, polar_lift, unpolarify) +from sympy.functions.special.gamma_functions import gamma, digamma, uppergamma +from sympy.functions.special.hyper import hyper +from sympy.polys.orthopolys import spherical_bessel_fn + +from mpmath import mp, workprec + +# TODO +# o Scorer functions G1 and G2 +# o Asymptotic expansions +# These are possible, e.g. for fixed order, but since the bessel type +# functions are oscillatory they are not actually tractable at +# infinity, so this is not particularly useful right now. +# o Nicer series expansions. +# o More rewriting. +# o Add solvers to ode.py (or rather add solvers for the hypergeometric equation). + + +class BesselBase(DefinedFunction): + """ + Abstract base class for Bessel-type functions. + + This class is meant to reduce code duplication. + All Bessel-type functions can 1) be differentiated, with the derivatives + expressed in terms of similar functions, and 2) be rewritten in terms + of other Bessel-type functions. + + Here, Bessel-type functions are assumed to have one complex parameter. + + To use this base class, define class attributes ``_a`` and ``_b`` such that + ``2*F_n' = -_a*F_{n+1} + b*F_{n-1}``. + + """ + + @property + def order(self): + """ The order of the Bessel-type function. """ + return self.args[0] + + @property + def argument(self): + """ The argument of the Bessel-type function. """ + return self.args[1] + + @classmethod + def eval(cls, nu, z): + return + + def fdiff(self, argindex=2): + if argindex != 2: + raise ArgumentIndexError(self, argindex) + return (self._b/2 * self.__class__(self.order - 1, self.argument) - + self._a/2 * self.__class__(self.order + 1, self.argument)) + + def _eval_conjugate(self): + z = self.argument + if z.is_extended_negative is False: + return self.__class__(self.order.conjugate(), z.conjugate()) + + def _eval_is_meromorphic(self, x, a): + nu, z = self.order, self.argument + + if nu.has(x): + return False + if not z._eval_is_meromorphic(x, a): + return None + z0 = z.subs(x, a) + if nu.is_integer: + if isinstance(self, (besselj, besseli, hn1, hn2, jn, yn)) or not nu.is_zero: + return fuzzy_not(z0.is_infinite) + return fuzzy_not(fuzzy_or([z0.is_zero, z0.is_infinite])) + + def _eval_expand_func(self, **hints): + nu, z, f = self.order, self.argument, self.__class__ + if nu.is_real: + if (nu - 1).is_positive: + return (-self._a*self._b*f(nu - 2, z)._eval_expand_func() + + 2*self._a*(nu - 1)*f(nu - 1, z)._eval_expand_func()/z) + elif (nu + 1).is_negative: + return (2*self._b*(nu + 1)*f(nu + 1, z)._eval_expand_func()/z - + self._a*self._b*f(nu + 2, z)._eval_expand_func()) + return self + + def _eval_simplify(self, **kwargs): + from sympy.simplify.simplify import besselsimp + return besselsimp(self) + + +class besselj(BesselBase): + r""" + Bessel function of the first kind. + + Explanation + =========== + + The Bessel $J$ function of order $\nu$ is defined to be the function + satisfying Bessel's differential equation + + .. math :: + z^2 \frac{\mathrm{d}^2 w}{\mathrm{d}z^2} + + z \frac{\mathrm{d}w}{\mathrm{d}z} + (z^2 - \nu^2) w = 0, + + with Laurent expansion + + .. math :: + J_\nu(z) = z^\nu \left(\frac{1}{\Gamma(\nu + 1) 2^\nu} + O(z^2) \right), + + if $\nu$ is not a negative integer. If $\nu=-n \in \mathbb{Z}_{<0}$ + *is* a negative integer, then the definition is + + .. math :: + J_{-n}(z) = (-1)^n J_n(z). + + Examples + ======== + + Create a Bessel function object: + + >>> from sympy import besselj, jn + >>> from sympy.abc import z, n + >>> b = besselj(n, z) + + Differentiate it: + + >>> b.diff(z) + besselj(n - 1, z)/2 - besselj(n + 1, z)/2 + + Rewrite in terms of spherical Bessel functions: + + >>> b.rewrite(jn) + sqrt(2)*sqrt(z)*jn(n - 1/2, z)/sqrt(pi) + + Access the parameter and argument: + + >>> b.order + n + >>> b.argument + z + + See Also + ======== + + bessely, besseli, besselk + + References + ========== + + .. [1] Abramowitz, Milton; Stegun, Irene A., eds. (1965), "Chapter 9", + Handbook of Mathematical Functions with Formulas, Graphs, and + Mathematical Tables + .. [2] Luke, Y. L. (1969), The Special Functions and Their + Approximations, Volume 1 + .. [3] https://en.wikipedia.org/wiki/Bessel_function + .. [4] https://functions.wolfram.com/Bessel-TypeFunctions/BesselJ/ + + """ + + _a = S.One + _b = S.One + + @classmethod + def eval(cls, nu, z): + if z.is_zero: + if nu.is_zero: + return S.One + elif (nu.is_integer and nu.is_zero is False) or re(nu).is_positive: + return S.Zero + elif re(nu).is_negative and not (nu.is_integer is True): + return S.ComplexInfinity + elif nu.is_imaginary: + return S.NaN + if z in (S.Infinity, S.NegativeInfinity): + return S.Zero + + if z.could_extract_minus_sign(): + return (z)**nu*(-z)**(-nu)*besselj(nu, -z) + if nu.is_integer: + if nu.could_extract_minus_sign(): + return S.NegativeOne**(-nu)*besselj(-nu, z) + newz = z.extract_multiplicatively(I) + if newz: # NOTE we don't want to change the function if z==0 + return I**(nu)*besseli(nu, newz) + + # branch handling: + if nu.is_integer: + newz = unpolarify(z) + if newz != z: + return besselj(nu, newz) + else: + newz, n = z.extract_branch_factor() + if n != 0: + return exp(2*n*pi*nu*I)*besselj(nu, newz) + nnu = unpolarify(nu) + if nu != nnu: + return besselj(nnu, z) + + def _eval_rewrite_as_besseli(self, nu, z, **kwargs): + return exp(I*pi*nu/2)*besseli(nu, polar_lift(-I)*z) + + def _eval_rewrite_as_bessely(self, nu, z, **kwargs): + if nu.is_integer is False: + return csc(pi*nu)*bessely(-nu, z) - cot(pi*nu)*bessely(nu, z) + + def _eval_rewrite_as_jn(self, nu, z, **kwargs): + return sqrt(2*z/pi)*jn(nu - S.Half, self.argument) + + def _eval_as_leading_term(self, x, logx, cdir): + nu, z = self.args + try: + arg = z.as_leading_term(x) + except NotImplementedError: + return self + c, e = arg.as_coeff_exponent(x) + + if e.is_positive: + return arg**nu/(2**nu*gamma(nu + 1)) + elif e.is_negative: + cdir = 1 if cdir == 0 else cdir + sign = c*cdir**e + if not sign.is_negative: + # Refer Abramowitz and Stegun 1965, p. 364 for more information on + # asymptotic approximation of besselj function. + return sqrt(2)*cos(z - pi*(2*nu + 1)/4)/sqrt(pi*z) + return self + + return super(besselj, self)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_is_extended_real(self): + nu, z = self.args + if nu.is_integer and z.is_extended_real: + return True + + def _eval_nseries(self, x, n, logx, cdir=0): + # Refer https://functions.wolfram.com/Bessel-TypeFunctions/BesselJ/06/01/04/01/01/0003/ + # for more information on nseries expansion of besselj function. + from sympy.series.order import Order + nu, z = self.args + + # In case of powers less than 1, number of terms need to be computed + # separately to avoid repeated callings of _eval_nseries with wrong n + try: + _, exp = z.leadterm(x) + except (ValueError, NotImplementedError): + return self + + if exp.is_positive: + newn = ceiling(n/exp) + o = Order(x**n, x) + r = (z/2)._eval_nseries(x, n, logx, cdir).removeO() + if r is S.Zero: + return o + t = (_mexpand(r**2) + o).removeO() + + term = r**nu/gamma(nu + 1) + s = [term] + for k in range(1, (newn + 1)//2): + term *= -t/(k*(nu + k)) + term = (_mexpand(term) + o).removeO() + s.append(term) + return Add(*s) + o + + return super(besselj, self)._eval_nseries(x, n, logx, cdir) + + +class bessely(BesselBase): + r""" + Bessel function of the second kind. + + Explanation + =========== + + The Bessel $Y$ function of order $\nu$ is defined as + + .. math :: + Y_\nu(z) = \lim_{\mu \to \nu} \frac{J_\mu(z) \cos(\pi \mu) + - J_{-\mu}(z)}{\sin(\pi \mu)}, + + where $J_\mu(z)$ is the Bessel function of the first kind. + + It is a solution to Bessel's equation, and linearly independent from + $J_\nu$. + + Examples + ======== + + >>> from sympy import bessely, yn + >>> from sympy.abc import z, n + >>> b = bessely(n, z) + >>> b.diff(z) + bessely(n - 1, z)/2 - bessely(n + 1, z)/2 + >>> b.rewrite(yn) + sqrt(2)*sqrt(z)*yn(n - 1/2, z)/sqrt(pi) + + See Also + ======== + + besselj, besseli, besselk + + References + ========== + + .. [1] https://functions.wolfram.com/Bessel-TypeFunctions/BesselY/ + + """ + + _a = S.One + _b = S.One + + @classmethod + def eval(cls, nu, z): + if z.is_zero: + if nu.is_zero: + return S.NegativeInfinity + elif re(nu).is_zero is False: + return S.ComplexInfinity + elif re(nu).is_zero: + return S.NaN + if z in (S.Infinity, S.NegativeInfinity): + return S.Zero + if z == I*S.Infinity: + return exp(I*pi*(nu + 1)/2) * S.Infinity + if z == I*S.NegativeInfinity: + return exp(-I*pi*(nu + 1)/2) * S.Infinity + + if nu.is_integer: + if nu.could_extract_minus_sign(): + return S.NegativeOne**(-nu)*bessely(-nu, z) + + def _eval_rewrite_as_besselj(self, nu, z, **kwargs): + if nu.is_integer is False: + return csc(pi*nu)*(cos(pi*nu)*besselj(nu, z) - besselj(-nu, z)) + + def _eval_rewrite_as_besseli(self, nu, z, **kwargs): + aj = self._eval_rewrite_as_besselj(*self.args) + if aj: + return aj.rewrite(besseli) + + def _eval_rewrite_as_yn(self, nu, z, **kwargs): + return sqrt(2*z/pi) * yn(nu - S.Half, self.argument) + + def _eval_as_leading_term(self, x, logx, cdir): + nu, z = self.args + try: + arg = z.as_leading_term(x) + except NotImplementedError: + return self + c, e = arg.as_coeff_exponent(x) + + if e.is_positive: + term_one = ((2/pi)*log(z/2)*besselj(nu, z)) + term_two = -(z/2)**(-nu)*factorial(nu - 1)/pi if (nu).is_positive else S.Zero + term_three = -(z/2)**nu/(pi*factorial(nu))*(digamma(nu + 1) - S.EulerGamma) + arg = Add(*[term_one, term_two, term_three]).as_leading_term(x, logx=logx) + return arg + elif e.is_negative: + cdir = 1 if cdir == 0 else cdir + sign = c*cdir**e + if not sign.is_negative: + # Refer Abramowitz and Stegun 1965, p. 364 for more information on + # asymptotic approximation of bessely function. + return sqrt(2)*(-sin(pi*nu/2 - z + pi/4) + 3*cos(pi*nu/2 - z + pi/4)/(8*z))*sqrt(1/z)/sqrt(pi) + return self + + return super(bessely, self)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_is_extended_real(self): + nu, z = self.args + if nu.is_integer and z.is_positive: + return True + + def _eval_nseries(self, x, n, logx, cdir=0): + # Refer https://functions.wolfram.com/Bessel-TypeFunctions/BesselY/06/01/04/01/02/0008/ + # for more information on nseries expansion of bessely function. + from sympy.series.order import Order + nu, z = self.args + + # In case of powers less than 1, number of terms need to be computed + # separately to avoid repeated callings of _eval_nseries with wrong n + try: + _, exp = z.leadterm(x) + except (ValueError, NotImplementedError): + return self + + if exp.is_positive and nu.is_integer: + newn = ceiling(n/exp) + bn = besselj(nu, z) + a = ((2/pi)*log(z/2)*bn)._eval_nseries(x, n, logx, cdir) + + b, c = [], [] + o = Order(x**n, x) + r = (z/2)._eval_nseries(x, n, logx, cdir).removeO() + if r is S.Zero: + return o + t = (_mexpand(r**2) + o).removeO() + + if nu > S.Zero: + term = r**(-nu)*factorial(nu - 1)/pi + b.append(term) + for k in range(1, nu): + denom = (nu - k)*k + if denom == S.Zero: + term *= t/k + else: + term *= t/denom + term = (_mexpand(term) + o).removeO() + b.append(term) + + p = r**nu/(pi*factorial(nu)) + term = p*(digamma(nu + 1) - S.EulerGamma) + c.append(term) + for k in range(1, (newn + 1)//2): + p *= -t/(k*(k + nu)) + p = (_mexpand(p) + o).removeO() + term = p*(digamma(k + nu + 1) + digamma(k + 1)) + c.append(term) + return a - Add(*b) - Add(*c) # Order term comes from a + + return super(bessely, self)._eval_nseries(x, n, logx, cdir) + + +class besseli(BesselBase): + r""" + Modified Bessel function of the first kind. + + Explanation + =========== + + The Bessel $I$ function is a solution to the modified Bessel equation + + .. math :: + z^2 \frac{\mathrm{d}^2 w}{\mathrm{d}z^2} + + z \frac{\mathrm{d}w}{\mathrm{d}z} + (z^2 + \nu^2)^2 w = 0. + + It can be defined as + + .. math :: + I_\nu(z) = i^{-\nu} J_\nu(iz), + + where $J_\nu(z)$ is the Bessel function of the first kind. + + Examples + ======== + + >>> from sympy import besseli + >>> from sympy.abc import z, n + >>> besseli(n, z).diff(z) + besseli(n - 1, z)/2 + besseli(n + 1, z)/2 + + See Also + ======== + + besselj, bessely, besselk + + References + ========== + + .. [1] https://functions.wolfram.com/Bessel-TypeFunctions/BesselI/ + + """ + + _a = -S.One + _b = S.One + + @classmethod + def eval(cls, nu, z): + if z.is_zero: + if nu.is_zero: + return S.One + elif (nu.is_integer and nu.is_zero is False) or re(nu).is_positive: + return S.Zero + elif re(nu).is_negative and not (nu.is_integer is True): + return S.ComplexInfinity + elif nu.is_imaginary: + return S.NaN + if im(z) in (S.Infinity, S.NegativeInfinity): + return S.Zero + if z is S.Infinity: + return S.Infinity + if z is S.NegativeInfinity: + return (-1)**nu*S.Infinity + + if z.could_extract_minus_sign(): + return (z)**nu*(-z)**(-nu)*besseli(nu, -z) + if nu.is_integer: + if nu.could_extract_minus_sign(): + return besseli(-nu, z) + newz = z.extract_multiplicatively(I) + if newz: # NOTE we don't want to change the function if z==0 + return I**(-nu)*besselj(nu, -newz) + + # branch handling: + if nu.is_integer: + newz = unpolarify(z) + if newz != z: + return besseli(nu, newz) + else: + newz, n = z.extract_branch_factor() + if n != 0: + return exp(2*n*pi*nu*I)*besseli(nu, newz) + nnu = unpolarify(nu) + if nu != nnu: + return besseli(nnu, z) + + def _eval_rewrite_as_tractable(self, nu, z, limitvar=None, **kwargs): + if z.is_extended_real: + return exp(z)*_besseli(nu, z) + + def _eval_rewrite_as_besselj(self, nu, z, **kwargs): + return exp(-I*pi*nu/2)*besselj(nu, polar_lift(I)*z) + + def _eval_rewrite_as_bessely(self, nu, z, **kwargs): + aj = self._eval_rewrite_as_besselj(*self.args) + if aj: + return aj.rewrite(bessely) + + def _eval_rewrite_as_jn(self, nu, z, **kwargs): + return self._eval_rewrite_as_besselj(*self.args).rewrite(jn) + + def _eval_is_extended_real(self): + nu, z = self.args + if nu.is_integer and z.is_extended_real: + return True + + def _eval_as_leading_term(self, x, logx, cdir): + nu, z = self.args + try: + arg = z.as_leading_term(x) + except NotImplementedError: + return self + c, e = arg.as_coeff_exponent(x) + + if e.is_positive: + return arg**nu/(2**nu*gamma(nu + 1)) + elif e.is_negative: + cdir = 1 if cdir == 0 else cdir + sign = c*cdir**e + if not sign.is_negative: + # Refer Abramowitz and Stegun 1965, p. 377 for more information on + # asymptotic approximation of besseli function. + return exp(z)/sqrt(2*pi*z) + return self + + return super(besseli, self)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + # Refer https://functions.wolfram.com/Bessel-TypeFunctions/BesselI/06/01/04/01/01/0003/ + # for more information on nseries expansion of besseli function. + from sympy.series.order import Order + nu, z = self.args + + # In case of powers less than 1, number of terms need to be computed + # separately to avoid repeated callings of _eval_nseries with wrong n + try: + _, exp = z.leadterm(x) + except (ValueError, NotImplementedError): + return self + + if exp.is_positive: + newn = ceiling(n/exp) + o = Order(x**n, x) + r = (z/2)._eval_nseries(x, n, logx, cdir).removeO() + if r is S.Zero: + return o + t = (_mexpand(r**2) + o).removeO() + + term = r**nu/gamma(nu + 1) + s = [term] + for k in range(1, (newn + 1)//2): + term *= t/(k*(nu + k)) + term = (_mexpand(term) + o).removeO() + s.append(term) + return Add(*s) + o + + return super(besseli, self)._eval_nseries(x, n, logx, cdir) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.functions.combinatorial.factorials import RisingFactorial + from sympy.series.order import Order + point = args0[1] + + if point in [S.Infinity, S.NegativeInfinity]: + nu, z = self.args + s = [(RisingFactorial(Rational(2*nu - 1, 2), k)*RisingFactorial(Rational(2*nu + 1, 2), k))/\ + ((2)**(k)*z**(Rational(2*k + 1, 2))*factorial(k)) for k in range(n)] + [Order(1/z**(Rational(2*n + 1, 2)), x)] + return exp(z)/sqrt(2*pi) * (Add(*s)) + + return super()._eval_aseries(n, args0, x, logx) + + +class besselk(BesselBase): + r""" + Modified Bessel function of the second kind. + + Explanation + =========== + + The Bessel $K$ function of order $\nu$ is defined as + + .. math :: + K_\nu(z) = \lim_{\mu \to \nu} \frac{\pi}{2} + \frac{I_{-\mu}(z) -I_\mu(z)}{\sin(\pi \mu)}, + + where $I_\mu(z)$ is the modified Bessel function of the first kind. + + It is a solution of the modified Bessel equation, and linearly independent + from $Y_\nu$. + + Examples + ======== + + >>> from sympy import besselk + >>> from sympy.abc import z, n + >>> besselk(n, z).diff(z) + -besselk(n - 1, z)/2 - besselk(n + 1, z)/2 + + See Also + ======== + + besselj, besseli, bessely + + References + ========== + + .. [1] https://functions.wolfram.com/Bessel-TypeFunctions/BesselK/ + + """ + + _a = S.One + _b = -S.One + + @classmethod + def eval(cls, nu, z): + if z.is_zero: + if nu.is_zero: + return S.Infinity + elif re(nu).is_zero is False: + return S.ComplexInfinity + elif re(nu).is_zero: + return S.NaN + if z in (S.Infinity, I*S.Infinity, I*S.NegativeInfinity): + return S.Zero + + if nu.is_integer: + if nu.could_extract_minus_sign(): + return besselk(-nu, z) + + def _eval_rewrite_as_besseli(self, nu, z, **kwargs): + if nu.is_integer is False: + return pi*csc(pi*nu)*(besseli(-nu, z) - besseli(nu, z))/2 + + def _eval_rewrite_as_besselj(self, nu, z, **kwargs): + ai = self._eval_rewrite_as_besseli(*self.args) + if ai: + return ai.rewrite(besselj) + + def _eval_rewrite_as_bessely(self, nu, z, **kwargs): + aj = self._eval_rewrite_as_besselj(*self.args) + if aj: + return aj.rewrite(bessely) + + def _eval_rewrite_as_yn(self, nu, z, **kwargs): + ay = self._eval_rewrite_as_bessely(*self.args) + if ay: + return ay.rewrite(yn) + + def _eval_is_extended_real(self): + nu, z = self.args + if nu.is_integer and z.is_positive: + return True + + def _eval_rewrite_as_tractable(self, nu, z, limitvar=None, **kwargs): + if z.is_extended_real: + return exp(-z)*_besselk(nu, z) + + def _eval_as_leading_term(self, x, logx, cdir): + nu, z = self.args + try: + arg = z.as_leading_term(x) + except NotImplementedError: + return self + _, e = arg.as_coeff_exponent(x) + + if e.is_positive: + if nu.is_zero: + # Equation 9.6.8 of Abramowitz and Stegun (10th ed, 1972). + term = -log(z) - S.EulerGamma + log(2) + elif nu.is_nonzero: + # Equation 9.6.9 of Abramowitz and Stegun (10th ed, 1972). + term = gamma(Abs(nu))*(z/2)**(-Abs(nu))/2 + else: + raise NotImplementedError(f"Cannot proceed without knowing if {nu} is zero or not.") + + return term.as_leading_term(x, logx=logx) + elif e.is_negative: + # Equation 9.7.2 of Abramowitz and Stegun (10th ed, 1972). + return sqrt(pi)*exp(-arg)/sqrt(2*arg) + else: + return self.func(nu, arg) + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.series.order import Order + nu, z = self.args + + try: + _, exp = z.leadterm(x) + except (ValueError, NotImplementedError): + return self + + # In case of powers less than 1, number of terms need to be computed + # separately to avoid repeated callings of _eval_nseries with wrong n + if exp.is_positive: + r = (z/2)._eval_nseries(x, n, logx, cdir).removeO() + if r is S.Zero: + return Order(z**(-nu) + z**nu, x) + + o = Order(x**n, x) + if nu.is_integer: + # Reference: https://functions.wolfram.com/Bessel-TypeFunctions/BesselK/06/01/04/01/02/0008/ (only for integer order) + newn = ceiling(n/exp) + bn = besseli(nu, z) + a = ((-1)**(nu - 1)*log(z/2)*bn)._eval_nseries(x, n, logx, cdir) + + b, c = [], [] + t = _mexpand(r**2) + + if nu > S.Zero: + term = r**(-nu)*factorial(nu - 1)/2 + b.append(term) + for k in range(1, nu): + term *= t/((k - nu)*k) + term = (_mexpand(term) + o).removeO() + b.append(term) + + p = r**nu*(-1)**nu/(2*factorial(nu)) + term = p*(digamma(nu + 1) - S.EulerGamma) + c.append(term) + for k in range(1, (newn + 1)//2): + p *= t/(k*(k + nu)) + p = (_mexpand(p) + o).removeO() + term = p*(digamma(k + nu + 1) + digamma(k + 1)) + c.append(term) + return a + Add(*b) + Add(*c) + o + elif nu.is_noninteger: + # Reference: https://functions.wolfram.com/Bessel-TypeFunctions/BesselK/06/01/04/01/01/0003/ + # (only for non-integer order). + # While the expression in the reference above seems correct + # for non-real order as well, it would need some manipulation + # (not implemented) to be written as a power series in x with + # real exponents [e.g. Dunster 1990. "Bessel functions + # of purely imaginary order, with an application to second-order + # linear differential equations having a large parameter". + # SIAM J. Math. Anal. Vol 21, No. 4, pp 995-1018.]. + newn_a = ceiling((n+nu)/exp) + newn_b = ceiling((n-nu)/exp) + + a, b = [], [] + for k in range((newn_a+1)//2): + term = gamma(nu)*r**(2*k-nu)/(2*RisingFactorial(1-nu, k)*factorial(k)) + a.append(_mexpand(term)) + for k in range((newn_b+1)//2): + term = gamma(-nu)*r**(2*k+nu)/(2*RisingFactorial(nu+1, k)*factorial(k)) + b.append(_mexpand(term)) + return Add(*a) + Add(*b) + o + else: + raise NotImplementedError("besselk expansion is only implemented for real order") + + return super(besselk, self)._eval_nseries(x, n, logx, cdir) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.functions.combinatorial.factorials import RisingFactorial + from sympy.series.order import Order + point = args0[1] + + if point in [S.Infinity, S.NegativeInfinity]: + nu, z = self.args + s = [(RisingFactorial(Rational(2*nu - 1, 2), k)*RisingFactorial(Rational(2*nu + 1, 2), k))/\ + ((-2)**(k)*z**(Rational(2*k + 1, 2))*factorial(k)) for k in range(n)] +[Order(1/z**(Rational(2*n + 1, 2)), x)] + return (exp(-z)*sqrt(pi/2))*Add(*s) + + return super()._eval_aseries(n, args0, x, logx) + + +class hankel1(BesselBase): + r""" + Hankel function of the first kind. + + Explanation + =========== + + This function is defined as + + .. math :: + H_\nu^{(1)} = J_\nu(z) + iY_\nu(z), + + where $J_\nu(z)$ is the Bessel function of the first kind, and + $Y_\nu(z)$ is the Bessel function of the second kind. + + It is a solution to Bessel's equation. + + Examples + ======== + + >>> from sympy import hankel1 + >>> from sympy.abc import z, n + >>> hankel1(n, z).diff(z) + hankel1(n - 1, z)/2 - hankel1(n + 1, z)/2 + + See Also + ======== + + hankel2, besselj, bessely + + References + ========== + + .. [1] https://functions.wolfram.com/Bessel-TypeFunctions/HankelH1/ + + """ + + _a = S.One + _b = S.One + + def _eval_conjugate(self): + z = self.argument + if z.is_extended_negative is False: + return hankel2(self.order.conjugate(), z.conjugate()) + + +class hankel2(BesselBase): + r""" + Hankel function of the second kind. + + Explanation + =========== + + This function is defined as + + .. math :: + H_\nu^{(2)} = J_\nu(z) - iY_\nu(z), + + where $J_\nu(z)$ is the Bessel function of the first kind, and + $Y_\nu(z)$ is the Bessel function of the second kind. + + It is a solution to Bessel's equation, and linearly independent from + $H_\nu^{(1)}$. + + Examples + ======== + + >>> from sympy import hankel2 + >>> from sympy.abc import z, n + >>> hankel2(n, z).diff(z) + hankel2(n - 1, z)/2 - hankel2(n + 1, z)/2 + + See Also + ======== + + hankel1, besselj, bessely + + References + ========== + + .. [1] https://functions.wolfram.com/Bessel-TypeFunctions/HankelH2/ + + """ + + _a = S.One + _b = S.One + + def _eval_conjugate(self): + z = self.argument + if z.is_extended_negative is False: + return hankel1(self.order.conjugate(), z.conjugate()) + + +def assume_integer_order(fn): + @wraps(fn) + def g(self, nu, z): + if nu.is_integer: + return fn(self, nu, z) + return g + + +class SphericalBesselBase(BesselBase): + """ + Base class for spherical Bessel functions. + + These are thin wrappers around ordinary Bessel functions, + since spherical Bessel functions differ from the ordinary + ones just by a slight change in order. + + To use this class, define the ``_eval_evalf()`` and ``_expand()`` methods. + + """ + + def _expand(self, **hints): + """ Expand self into a polynomial. Nu is guaranteed to be Integer. """ + raise NotImplementedError('expansion') + + def _eval_expand_func(self, **hints): + if self.order.is_Integer: + return self._expand(**hints) + return self + + def fdiff(self, argindex=2): + if argindex != 2: + raise ArgumentIndexError(self, argindex) + return self.__class__(self.order - 1, self.argument) - \ + self * (self.order + 1)/self.argument + + +def _jn(n, z): + return (spherical_bessel_fn(n, z)*sin(z) + + S.NegativeOne**(n + 1)*spherical_bessel_fn(-n - 1, z)*cos(z)) + + +def _yn(n, z): + # (-1)**(n + 1) * _jn(-n - 1, z) + return (S.NegativeOne**(n + 1) * spherical_bessel_fn(-n - 1, z)*sin(z) - + spherical_bessel_fn(n, z)*cos(z)) + + +class jn(SphericalBesselBase): + r""" + Spherical Bessel function of the first kind. + + Explanation + =========== + + This function is a solution to the spherical Bessel equation + + .. math :: + z^2 \frac{\mathrm{d}^2 w}{\mathrm{d}z^2} + + 2z \frac{\mathrm{d}w}{\mathrm{d}z} + (z^2 - \nu(\nu + 1)) w = 0. + + It can be defined as + + .. math :: + j_\nu(z) = \sqrt{\frac{\pi}{2z}} J_{\nu + \frac{1}{2}}(z), + + where $J_\nu(z)$ is the Bessel function of the first kind. + + The spherical Bessel functions of integral order are + calculated using the formula: + + .. math:: j_n(z) = f_n(z) \sin{z} + (-1)^{n+1} f_{-n-1}(z) \cos{z}, + + where the coefficients $f_n(z)$ are available as + :func:`sympy.polys.orthopolys.spherical_bessel_fn`. + + Examples + ======== + + >>> from sympy import Symbol, jn, sin, cos, expand_func, besselj, bessely + >>> z = Symbol("z") + >>> nu = Symbol("nu", integer=True) + >>> print(expand_func(jn(0, z))) + sin(z)/z + >>> expand_func(jn(1, z)) == sin(z)/z**2 - cos(z)/z + True + >>> expand_func(jn(3, z)) + (-6/z**2 + 15/z**4)*sin(z) + (1/z - 15/z**3)*cos(z) + >>> jn(nu, z).rewrite(besselj) + sqrt(2)*sqrt(pi)*sqrt(1/z)*besselj(nu + 1/2, z)/2 + >>> jn(nu, z).rewrite(bessely) + (-1)**nu*sqrt(2)*sqrt(pi)*sqrt(1/z)*bessely(-nu - 1/2, z)/2 + >>> jn(2, 5.2+0.3j).evalf(20) + 0.099419756723640344491 - 0.054525080242173562897*I + + See Also + ======== + + besselj, bessely, besselk, yn + + References + ========== + + .. [1] https://dlmf.nist.gov/10.47 + + """ + @classmethod + def eval(cls, nu, z): + if z.is_zero: + if nu.is_zero: + return S.One + elif nu.is_integer: + if nu.is_positive: + return S.Zero + else: + return S.ComplexInfinity + if z in (S.NegativeInfinity, S.Infinity): + return S.Zero + + def _eval_rewrite_as_besselj(self, nu, z, **kwargs): + return sqrt(pi/(2*z)) * besselj(nu + S.Half, z) + + def _eval_rewrite_as_bessely(self, nu, z, **kwargs): + return S.NegativeOne**nu * sqrt(pi/(2*z)) * bessely(-nu - S.Half, z) + + def _eval_rewrite_as_yn(self, nu, z, **kwargs): + return S.NegativeOne**(nu) * yn(-nu - 1, z) + + def _expand(self, **hints): + return _jn(self.order, self.argument) + + def _eval_evalf(self, prec): + if self.order.is_Integer: + return self.rewrite(besselj)._eval_evalf(prec) + + +class yn(SphericalBesselBase): + r""" + Spherical Bessel function of the second kind. + + Explanation + =========== + + This function is another solution to the spherical Bessel equation, and + linearly independent from $j_n$. It can be defined as + + .. math :: + y_\nu(z) = \sqrt{\frac{\pi}{2z}} Y_{\nu + \frac{1}{2}}(z), + + where $Y_\nu(z)$ is the Bessel function of the second kind. + + For integral orders $n$, $y_n$ is calculated using the formula: + + .. math:: y_n(z) = (-1)^{n+1} j_{-n-1}(z) + + Examples + ======== + + >>> from sympy import Symbol, yn, sin, cos, expand_func, besselj, bessely + >>> z = Symbol("z") + >>> nu = Symbol("nu", integer=True) + >>> print(expand_func(yn(0, z))) + -cos(z)/z + >>> expand_func(yn(1, z)) == -cos(z)/z**2-sin(z)/z + True + >>> yn(nu, z).rewrite(besselj) + (-1)**(nu + 1)*sqrt(2)*sqrt(pi)*sqrt(1/z)*besselj(-nu - 1/2, z)/2 + >>> yn(nu, z).rewrite(bessely) + sqrt(2)*sqrt(pi)*sqrt(1/z)*bessely(nu + 1/2, z)/2 + >>> yn(2, 5.2+0.3j).evalf(20) + 0.18525034196069722536 + 0.014895573969924817587*I + + See Also + ======== + + besselj, bessely, besselk, jn + + References + ========== + + .. [1] https://dlmf.nist.gov/10.47 + + """ + @assume_integer_order + def _eval_rewrite_as_besselj(self, nu, z, **kwargs): + return S.NegativeOne**(nu+1) * sqrt(pi/(2*z)) * besselj(-nu - S.Half, z) + + @assume_integer_order + def _eval_rewrite_as_bessely(self, nu, z, **kwargs): + return sqrt(pi/(2*z)) * bessely(nu + S.Half, z) + + def _eval_rewrite_as_jn(self, nu, z, **kwargs): + return S.NegativeOne**(nu + 1) * jn(-nu - 1, z) + + def _expand(self, **hints): + return _yn(self.order, self.argument) + + def _eval_evalf(self, prec): + if self.order.is_Integer: + return self.rewrite(bessely)._eval_evalf(prec) + + +class SphericalHankelBase(SphericalBesselBase): + + @assume_integer_order + def _eval_rewrite_as_besselj(self, nu, z, **kwargs): + # jn +- I*yn + # jn as beeselj: sqrt(pi/(2*z)) * besselj(nu + S.Half, z) + # yn as besselj: (-1)**(nu+1) * sqrt(pi/(2*z)) * besselj(-nu - S.Half, z) + hks = self._hankel_kind_sign + return sqrt(pi/(2*z))*(besselj(nu + S.Half, z) + + hks*I*S.NegativeOne**(nu+1)*besselj(-nu - S.Half, z)) + + @assume_integer_order + def _eval_rewrite_as_bessely(self, nu, z, **kwargs): + # jn +- I*yn + # jn as bessely: (-1)**nu * sqrt(pi/(2*z)) * bessely(-nu - S.Half, z) + # yn as bessely: sqrt(pi/(2*z)) * bessely(nu + S.Half, z) + hks = self._hankel_kind_sign + return sqrt(pi/(2*z))*(S.NegativeOne**nu*bessely(-nu - S.Half, z) + + hks*I*bessely(nu + S.Half, z)) + + def _eval_rewrite_as_yn(self, nu, z, **kwargs): + hks = self._hankel_kind_sign + return jn(nu, z).rewrite(yn) + hks*I*yn(nu, z) + + def _eval_rewrite_as_jn(self, nu, z, **kwargs): + hks = self._hankel_kind_sign + return jn(nu, z) + hks*I*yn(nu, z).rewrite(jn) + + def _eval_expand_func(self, **hints): + if self.order.is_Integer: + return self._expand(**hints) + else: + nu = self.order + z = self.argument + hks = self._hankel_kind_sign + return jn(nu, z) + hks*I*yn(nu, z) + + def _expand(self, **hints): + n = self.order + z = self.argument + hks = self._hankel_kind_sign + + # fully expanded version + # return ((fn(n, z) * sin(z) + + # (-1)**(n + 1) * fn(-n - 1, z) * cos(z)) + # jn + # (hks * I * (-1)**(n + 1) * + # (fn(-n - 1, z) * hk * I * sin(z) + + # (-1)**(-n) * fn(n, z) * I * cos(z))) # +-I*yn + # ) + + return (_jn(n, z) + hks*I*_yn(n, z)).expand() + + def _eval_evalf(self, prec): + if self.order.is_Integer: + return self.rewrite(besselj)._eval_evalf(prec) + + +class hn1(SphericalHankelBase): + r""" + Spherical Hankel function of the first kind. + + Explanation + =========== + + This function is defined as + + .. math:: h_\nu^(1)(z) = j_\nu(z) + i y_\nu(z), + + where $j_\nu(z)$ and $y_\nu(z)$ are the spherical + Bessel function of the first and second kinds. + + For integral orders $n$, $h_n^(1)$ is calculated using the formula: + + .. math:: h_n^(1)(z) = j_{n}(z) + i (-1)^{n+1} j_{-n-1}(z) + + Examples + ======== + + >>> from sympy import Symbol, hn1, hankel1, expand_func, yn, jn + >>> z = Symbol("z") + >>> nu = Symbol("nu", integer=True) + >>> print(expand_func(hn1(nu, z))) + jn(nu, z) + I*yn(nu, z) + >>> print(expand_func(hn1(0, z))) + sin(z)/z - I*cos(z)/z + >>> print(expand_func(hn1(1, z))) + -I*sin(z)/z - cos(z)/z + sin(z)/z**2 - I*cos(z)/z**2 + >>> hn1(nu, z).rewrite(jn) + (-1)**(nu + 1)*I*jn(-nu - 1, z) + jn(nu, z) + >>> hn1(nu, z).rewrite(yn) + (-1)**nu*yn(-nu - 1, z) + I*yn(nu, z) + >>> hn1(nu, z).rewrite(hankel1) + sqrt(2)*sqrt(pi)*sqrt(1/z)*hankel1(nu, z)/2 + + See Also + ======== + + hn2, jn, yn, hankel1, hankel2 + + References + ========== + + .. [1] https://dlmf.nist.gov/10.47 + + """ + + _hankel_kind_sign = S.One + + @assume_integer_order + def _eval_rewrite_as_hankel1(self, nu, z, **kwargs): + return sqrt(pi/(2*z))*hankel1(nu, z) + + +class hn2(SphericalHankelBase): + r""" + Spherical Hankel function of the second kind. + + Explanation + =========== + + This function is defined as + + .. math:: h_\nu^(2)(z) = j_\nu(z) - i y_\nu(z), + + where $j_\nu(z)$ and $y_\nu(z)$ are the spherical + Bessel function of the first and second kinds. + + For integral orders $n$, $h_n^(2)$ is calculated using the formula: + + .. math:: h_n^(2)(z) = j_{n} - i (-1)^{n+1} j_{-n-1}(z) + + Examples + ======== + + >>> from sympy import Symbol, hn2, hankel2, expand_func, jn, yn + >>> z = Symbol("z") + >>> nu = Symbol("nu", integer=True) + >>> print(expand_func(hn2(nu, z))) + jn(nu, z) - I*yn(nu, z) + >>> print(expand_func(hn2(0, z))) + sin(z)/z + I*cos(z)/z + >>> print(expand_func(hn2(1, z))) + I*sin(z)/z - cos(z)/z + sin(z)/z**2 + I*cos(z)/z**2 + >>> hn2(nu, z).rewrite(hankel2) + sqrt(2)*sqrt(pi)*sqrt(1/z)*hankel2(nu, z)/2 + >>> hn2(nu, z).rewrite(jn) + -(-1)**(nu + 1)*I*jn(-nu - 1, z) + jn(nu, z) + >>> hn2(nu, z).rewrite(yn) + (-1)**nu*yn(-nu - 1, z) - I*yn(nu, z) + + See Also + ======== + + hn1, jn, yn, hankel1, hankel2 + + References + ========== + + .. [1] https://dlmf.nist.gov/10.47 + + """ + + _hankel_kind_sign = -S.One + + @assume_integer_order + def _eval_rewrite_as_hankel2(self, nu, z, **kwargs): + return sqrt(pi/(2*z))*hankel2(nu, z) + + +def jn_zeros(n, k, method="sympy", dps=15): + """ + Zeros of the spherical Bessel function of the first kind. + + Explanation + =========== + + This returns an array of zeros of $jn$ up to the $k$-th zero. + + * method = "sympy": uses `mpmath.besseljzero + `_ + * method = "scipy": uses the + `SciPy's sph_jn `_ + and + `newton `_ + to find all + roots, which is faster than computing the zeros using a general + numerical solver, but it requires SciPy and only works with low + precision floating point numbers. (The function used with + method="sympy" is a recent addition to mpmath; before that a general + solver was used.) + + Examples + ======== + + >>> from sympy import jn_zeros + >>> jn_zeros(2, 4, dps=5) + [5.7635, 9.095, 12.323, 15.515] + + See Also + ======== + + jn, yn, besselj, besselk, bessely + + Parameters + ========== + + n : integer + order of Bessel function + + k : integer + number of zeros to return + + + """ + from math import pi as math_pi + + if method == "sympy": + from mpmath import besseljzero + from mpmath.libmp.libmpf import dps_to_prec + prec = dps_to_prec(dps) + return [Expr._from_mpmath(besseljzero(S(n + 0.5)._to_mpmath(prec), + int(l)), prec) + for l in range(1, k + 1)] + elif method == "scipy": + from scipy.optimize import newton + try: + from scipy.special import spherical_jn + f = lambda x: spherical_jn(n, x) + except ImportError: + from scipy.special import sph_jn + f = lambda x: sph_jn(n, x)[0][-1] + else: + raise NotImplementedError("Unknown method.") + + def solver(f, x): + if method == "scipy": + root = newton(f, x) + else: + raise NotImplementedError("Unknown method.") + return root + + # we need to approximate the position of the first root: + root = n + math_pi + # determine the first root exactly: + root = solver(f, root) + roots = [root] + for i in range(k - 1): + # estimate the position of the next root using the last root + pi: + root = solver(f, root + math_pi) + roots.append(root) + return roots + + +class AiryBase(DefinedFunction): + """ + Abstract base class for Airy functions. + + This class is meant to reduce code duplication. + + """ + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + def as_real_imag(self, deep=True, **hints): + z = self.args[0] + zc = z.conjugate() + f = self.func + u = (f(z)+f(zc))/2 + v = I*(f(zc)-f(z))/2 + return u, v + + def _eval_expand_complex(self, deep=True, **hints): + re_part, im_part = self.as_real_imag(deep=deep, **hints) + return re_part + im_part*I + + +class airyai(AiryBase): + r""" + The Airy function $\operatorname{Ai}$ of the first kind. + + Explanation + =========== + + The Airy function $\operatorname{Ai}(z)$ is defined to be the function + satisfying Airy's differential equation + + .. math:: + \frac{\mathrm{d}^2 w(z)}{\mathrm{d}z^2} - z w(z) = 0. + + Equivalently, for real $z$ + + .. math:: + \operatorname{Ai}(z) := \frac{1}{\pi} + \int_0^\infty \cos\left(\frac{t^3}{3} + z t\right) \mathrm{d}t. + + Examples + ======== + + Create an Airy function object: + + >>> from sympy import airyai + >>> from sympy.abc import z + + >>> airyai(z) + airyai(z) + + Several special values are known: + + >>> airyai(0) + 3**(1/3)/(3*gamma(2/3)) + >>> from sympy import oo + >>> airyai(oo) + 0 + >>> airyai(-oo) + 0 + + The Airy function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(airyai(z)) + airyai(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(airyai(z), z) + airyaiprime(z) + >>> diff(airyai(z), z, 2) + z*airyai(z) + + Series expansion is also supported: + + >>> from sympy import series + >>> series(airyai(z), z, 0, 3) + 3**(5/6)*gamma(1/3)/(6*pi) - 3**(1/6)*z*gamma(2/3)/(2*pi) + O(z**3) + + We can numerically evaluate the Airy function to arbitrary precision + on the whole complex plane: + + >>> airyai(-2).evalf(50) + 0.22740742820168557599192443603787379946077222541710 + + Rewrite $\operatorname{Ai}(z)$ in terms of hypergeometric functions: + + >>> from sympy import hyper + >>> airyai(z).rewrite(hyper) + -3**(2/3)*z*hyper((), (4/3,), z**3/9)/(3*gamma(1/3)) + 3**(1/3)*hyper((), (2/3,), z**3/9)/(3*gamma(2/3)) + + See Also + ======== + + airybi: Airy function of the second kind. + airyaiprime: Derivative of the Airy function of the first kind. + airybiprime: Derivative of the Airy function of the second kind. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Airy_function + .. [2] https://dlmf.nist.gov/9 + .. [3] https://encyclopediaofmath.org/wiki/Airy_functions + .. [4] https://mathworld.wolfram.com/AiryFunctions.html + + """ + + nargs = 1 + unbranched = True + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Zero + elif arg is S.NegativeInfinity: + return S.Zero + elif arg.is_zero: + return S.One / (3**Rational(2, 3) * gamma(Rational(2, 3))) + if arg.is_zero: + return S.One / (3**Rational(2, 3) * gamma(Rational(2, 3))) + + def fdiff(self, argindex=1): + if argindex == 1: + return airyaiprime(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 1: + p = previous_terms[-1] + return ((cbrt(3)*x)**(-n)*(cbrt(3)*x)**(n + 1)*sin(pi*(n*Rational(2, 3) + Rational(4, 3)))*factorial(n) * + gamma(n/3 + Rational(2, 3))/(sin(pi*(n*Rational(2, 3) + Rational(2, 3)))*factorial(n + 1)*gamma(n/3 + Rational(1, 3))) * p) + else: + return (S.One/(3**Rational(2, 3)*pi) * gamma((n+S.One)/S(3)) * sin(Rational(2, 3)*pi*(n+S.One)) / + factorial(n) * (cbrt(3)*x)**n) + + def _eval_rewrite_as_besselj(self, z, **kwargs): + ot = Rational(1, 3) + tt = Rational(2, 3) + a = Pow(-z, Rational(3, 2)) + if re(z).is_negative: + return ot*sqrt(-z) * (besselj(-ot, tt*a) + besselj(ot, tt*a)) + + def _eval_rewrite_as_besseli(self, z, **kwargs): + ot = Rational(1, 3) + tt = Rational(2, 3) + a = Pow(z, Rational(3, 2)) + if re(z).is_positive: + return ot*sqrt(z) * (besseli(-ot, tt*a) - besseli(ot, tt*a)) + else: + return ot*(Pow(a, ot)*besseli(-ot, tt*a) - z*Pow(a, -ot)*besseli(ot, tt*a)) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + pf1 = S.One / (3**Rational(2, 3)*gamma(Rational(2, 3))) + pf2 = z / (root(3, 3)*gamma(Rational(1, 3))) + return pf1 * hyper([], [Rational(2, 3)], z**3/9) - pf2 * hyper([], [Rational(4, 3)], z**3/9) + + def _eval_expand_func(self, **hints): + arg = self.args[0] + symbs = arg.free_symbols + + if len(symbs) == 1: + z = symbs.pop() + c = Wild("c", exclude=[z]) + d = Wild("d", exclude=[z]) + m = Wild("m", exclude=[z]) + n = Wild("n", exclude=[z]) + M = arg.match(c*(d*z**n)**m) + if M is not None: + m = M[m] + # The transformation is given by 03.05.16.0001.01 + # https://functions.wolfram.com/Bessel-TypeFunctions/AiryAi/16/01/01/0001/ + if (3*m).is_integer: + c = M[c] + d = M[d] + n = M[n] + pf = (d * z**n)**m / (d**m * z**(m*n)) + newarg = c * d**m * z**(m*n) + return S.Half * ((pf + S.One)*airyai(newarg) - (pf - S.One)/sqrt(3)*airybi(newarg)) + + +class airybi(AiryBase): + r""" + The Airy function $\operatorname{Bi}$ of the second kind. + + Explanation + =========== + + The Airy function $\operatorname{Bi}(z)$ is defined to be the function + satisfying Airy's differential equation + + .. math:: + \frac{\mathrm{d}^2 w(z)}{\mathrm{d}z^2} - z w(z) = 0. + + Equivalently, for real $z$ + + .. math:: + \operatorname{Bi}(z) := \frac{1}{\pi} + \int_0^\infty + \exp\left(-\frac{t^3}{3} + z t\right) + + \sin\left(\frac{t^3}{3} + z t\right) \mathrm{d}t. + + Examples + ======== + + Create an Airy function object: + + >>> from sympy import airybi + >>> from sympy.abc import z + + >>> airybi(z) + airybi(z) + + Several special values are known: + + >>> airybi(0) + 3**(5/6)/(3*gamma(2/3)) + >>> from sympy import oo + >>> airybi(oo) + oo + >>> airybi(-oo) + 0 + + The Airy function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(airybi(z)) + airybi(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(airybi(z), z) + airybiprime(z) + >>> diff(airybi(z), z, 2) + z*airybi(z) + + Series expansion is also supported: + + >>> from sympy import series + >>> series(airybi(z), z, 0, 3) + 3**(1/3)*gamma(1/3)/(2*pi) + 3**(2/3)*z*gamma(2/3)/(2*pi) + O(z**3) + + We can numerically evaluate the Airy function to arbitrary precision + on the whole complex plane: + + >>> airybi(-2).evalf(50) + -0.41230258795639848808323405461146104203453483447240 + + Rewrite $\operatorname{Bi}(z)$ in terms of hypergeometric functions: + + >>> from sympy import hyper + >>> airybi(z).rewrite(hyper) + 3**(1/6)*z*hyper((), (4/3,), z**3/9)/gamma(1/3) + 3**(5/6)*hyper((), (2/3,), z**3/9)/(3*gamma(2/3)) + + See Also + ======== + + airyai: Airy function of the first kind. + airyaiprime: Derivative of the Airy function of the first kind. + airybiprime: Derivative of the Airy function of the second kind. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Airy_function + .. [2] https://dlmf.nist.gov/9 + .. [3] https://encyclopediaofmath.org/wiki/Airy_functions + .. [4] https://mathworld.wolfram.com/AiryFunctions.html + + """ + + nargs = 1 + unbranched = True + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.Zero + elif arg.is_zero: + return S.One / (3**Rational(1, 6) * gamma(Rational(2, 3))) + + if arg.is_zero: + return S.One / (3**Rational(1, 6) * gamma(Rational(2, 3))) + + def fdiff(self, argindex=1): + if argindex == 1: + return airybiprime(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 1: + p = previous_terms[-1] + return (cbrt(3)*x * Abs(sin(Rational(2, 3)*pi*(n + S.One))) * factorial((n - S.One)/S(3)) / + ((n + S.One) * Abs(cos(Rational(2, 3)*pi*(n + S.Half))) * factorial((n - 2)/S(3))) * p) + else: + return (S.One/(root(3, 6)*pi) * gamma((n + S.One)/S(3)) * Abs(sin(Rational(2, 3)*pi*(n + S.One))) / + factorial(n) * (cbrt(3)*x)**n) + + def _eval_rewrite_as_besselj(self, z, **kwargs): + ot = Rational(1, 3) + tt = Rational(2, 3) + a = Pow(-z, Rational(3, 2)) + if re(z).is_negative: + return sqrt(-z/3) * (besselj(-ot, tt*a) - besselj(ot, tt*a)) + + def _eval_rewrite_as_besseli(self, z, **kwargs): + ot = Rational(1, 3) + tt = Rational(2, 3) + a = Pow(z, Rational(3, 2)) + if re(z).is_positive: + return sqrt(z)/sqrt(3) * (besseli(-ot, tt*a) + besseli(ot, tt*a)) + else: + b = Pow(a, ot) + c = Pow(a, -ot) + return sqrt(ot)*(b*besseli(-ot, tt*a) + z*c*besseli(ot, tt*a)) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + pf1 = S.One / (root(3, 6)*gamma(Rational(2, 3))) + pf2 = z*root(3, 6) / gamma(Rational(1, 3)) + return pf1 * hyper([], [Rational(2, 3)], z**3/9) + pf2 * hyper([], [Rational(4, 3)], z**3/9) + + def _eval_expand_func(self, **hints): + arg = self.args[0] + symbs = arg.free_symbols + + if len(symbs) == 1: + z = symbs.pop() + c = Wild("c", exclude=[z]) + d = Wild("d", exclude=[z]) + m = Wild("m", exclude=[z]) + n = Wild("n", exclude=[z]) + M = arg.match(c*(d*z**n)**m) + if M is not None: + m = M[m] + # The transformation is given by 03.06.16.0001.01 + # https://functions.wolfram.com/Bessel-TypeFunctions/AiryBi/16/01/01/0001/ + if (3*m).is_integer: + c = M[c] + d = M[d] + n = M[n] + pf = (d * z**n)**m / (d**m * z**(m*n)) + newarg = c * d**m * z**(m*n) + return S.Half * (sqrt(3)*(S.One - pf)*airyai(newarg) + (S.One + pf)*airybi(newarg)) + + +class airyaiprime(AiryBase): + r""" + The derivative $\operatorname{Ai}^\prime$ of the Airy function of the first + kind. + + Explanation + =========== + + The Airy function $\operatorname{Ai}^\prime(z)$ is defined to be the + function + + .. math:: + \operatorname{Ai}^\prime(z) := \frac{\mathrm{d} \operatorname{Ai}(z)}{\mathrm{d} z}. + + Examples + ======== + + Create an Airy function object: + + >>> from sympy import airyaiprime + >>> from sympy.abc import z + + >>> airyaiprime(z) + airyaiprime(z) + + Several special values are known: + + >>> airyaiprime(0) + -3**(2/3)/(3*gamma(1/3)) + >>> from sympy import oo + >>> airyaiprime(oo) + 0 + + The Airy function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(airyaiprime(z)) + airyaiprime(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(airyaiprime(z), z) + z*airyai(z) + >>> diff(airyaiprime(z), z, 2) + z*airyaiprime(z) + airyai(z) + + Series expansion is also supported: + + >>> from sympy import series + >>> series(airyaiprime(z), z, 0, 3) + -3**(2/3)/(3*gamma(1/3)) + 3**(1/3)*z**2/(6*gamma(2/3)) + O(z**3) + + We can numerically evaluate the Airy function to arbitrary precision + on the whole complex plane: + + >>> airyaiprime(-2).evalf(50) + 0.61825902074169104140626429133247528291577794512415 + + Rewrite $\operatorname{Ai}^\prime(z)$ in terms of hypergeometric functions: + + >>> from sympy import hyper + >>> airyaiprime(z).rewrite(hyper) + 3**(1/3)*z**2*hyper((), (5/3,), z**3/9)/(6*gamma(2/3)) - 3**(2/3)*hyper((), (1/3,), z**3/9)/(3*gamma(1/3)) + + See Also + ======== + + airyai: Airy function of the first kind. + airybi: Airy function of the second kind. + airybiprime: Derivative of the Airy function of the second kind. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Airy_function + .. [2] https://dlmf.nist.gov/9 + .. [3] https://encyclopediaofmath.org/wiki/Airy_functions + .. [4] https://mathworld.wolfram.com/AiryFunctions.html + + """ + + nargs = 1 + unbranched = True + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Zero + + if arg.is_zero: + return S.NegativeOne / (3**Rational(1, 3) * gamma(Rational(1, 3))) + + def fdiff(self, argindex=1): + if argindex == 1: + return self.args[0]*airyai(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_evalf(self, prec): + z = self.args[0]._to_mpmath(prec) + with workprec(prec): + res = mp.airyai(z, derivative=1) + return Expr._from_mpmath(res, prec) + + def _eval_rewrite_as_besselj(self, z, **kwargs): + tt = Rational(2, 3) + a = Pow(-z, Rational(3, 2)) + if re(z).is_negative: + return z/3 * (besselj(-tt, tt*a) - besselj(tt, tt*a)) + + def _eval_rewrite_as_besseli(self, z, **kwargs): + ot = Rational(1, 3) + tt = Rational(2, 3) + a = tt * Pow(z, Rational(3, 2)) + if re(z).is_positive: + return z/3 * (besseli(tt, a) - besseli(-tt, a)) + else: + a = Pow(z, Rational(3, 2)) + b = Pow(a, tt) + c = Pow(a, -tt) + return ot * (z**2*c*besseli(tt, tt*a) - b*besseli(-ot, tt*a)) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + pf1 = z**2 / (2*3**Rational(2, 3)*gamma(Rational(2, 3))) + pf2 = 1 / (root(3, 3)*gamma(Rational(1, 3))) + return pf1 * hyper([], [Rational(5, 3)], z**3/9) - pf2 * hyper([], [Rational(1, 3)], z**3/9) + + def _eval_expand_func(self, **hints): + arg = self.args[0] + symbs = arg.free_symbols + + if len(symbs) == 1: + z = symbs.pop() + c = Wild("c", exclude=[z]) + d = Wild("d", exclude=[z]) + m = Wild("m", exclude=[z]) + n = Wild("n", exclude=[z]) + M = arg.match(c*(d*z**n)**m) + if M is not None: + m = M[m] + # The transformation is in principle + # given by 03.07.16.0001.01 but note + # that there is an error in this formula. + # https://functions.wolfram.com/Bessel-TypeFunctions/AiryAiPrime/16/01/01/0001/ + if (3*m).is_integer: + c = M[c] + d = M[d] + n = M[n] + pf = (d**m * z**(n*m)) / (d * z**n)**m + newarg = c * d**m * z**(n*m) + return S.Half * ((pf + S.One)*airyaiprime(newarg) + (pf - S.One)/sqrt(3)*airybiprime(newarg)) + + +class airybiprime(AiryBase): + r""" + The derivative $\operatorname{Bi}^\prime$ of the Airy function of the first + kind. + + Explanation + =========== + + The Airy function $\operatorname{Bi}^\prime(z)$ is defined to be the + function + + .. math:: + \operatorname{Bi}^\prime(z) := \frac{\mathrm{d} \operatorname{Bi}(z)}{\mathrm{d} z}. + + Examples + ======== + + Create an Airy function object: + + >>> from sympy import airybiprime + >>> from sympy.abc import z + + >>> airybiprime(z) + airybiprime(z) + + Several special values are known: + + >>> airybiprime(0) + 3**(1/6)/gamma(1/3) + >>> from sympy import oo + >>> airybiprime(oo) + oo + >>> airybiprime(-oo) + 0 + + The Airy function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(airybiprime(z)) + airybiprime(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(airybiprime(z), z) + z*airybi(z) + >>> diff(airybiprime(z), z, 2) + z*airybiprime(z) + airybi(z) + + Series expansion is also supported: + + >>> from sympy import series + >>> series(airybiprime(z), z, 0, 3) + 3**(1/6)/gamma(1/3) + 3**(5/6)*z**2/(6*gamma(2/3)) + O(z**3) + + We can numerically evaluate the Airy function to arbitrary precision + on the whole complex plane: + + >>> airybiprime(-2).evalf(50) + 0.27879516692116952268509756941098324140300059345163 + + Rewrite $\operatorname{Bi}^\prime(z)$ in terms of hypergeometric functions: + + >>> from sympy import hyper + >>> airybiprime(z).rewrite(hyper) + 3**(5/6)*z**2*hyper((), (5/3,), z**3/9)/(6*gamma(2/3)) + 3**(1/6)*hyper((), (1/3,), z**3/9)/gamma(1/3) + + See Also + ======== + + airyai: Airy function of the first kind. + airybi: Airy function of the second kind. + airyaiprime: Derivative of the Airy function of the first kind. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Airy_function + .. [2] https://dlmf.nist.gov/9 + .. [3] https://encyclopediaofmath.org/wiki/Airy_functions + .. [4] https://mathworld.wolfram.com/AiryFunctions.html + + """ + + nargs = 1 + unbranched = True + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Infinity + elif arg is S.NegativeInfinity: + return S.Zero + elif arg.is_zero: + return 3**Rational(1, 6) / gamma(Rational(1, 3)) + + if arg.is_zero: + return 3**Rational(1, 6) / gamma(Rational(1, 3)) + + + def fdiff(self, argindex=1): + if argindex == 1: + return self.args[0]*airybi(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_evalf(self, prec): + z = self.args[0]._to_mpmath(prec) + with workprec(prec): + res = mp.airybi(z, derivative=1) + return Expr._from_mpmath(res, prec) + + def _eval_rewrite_as_besselj(self, z, **kwargs): + tt = Rational(2, 3) + a = tt * Pow(-z, Rational(3, 2)) + if re(z).is_negative: + return -z/sqrt(3) * (besselj(-tt, a) + besselj(tt, a)) + + def _eval_rewrite_as_besseli(self, z, **kwargs): + ot = Rational(1, 3) + tt = Rational(2, 3) + a = tt * Pow(z, Rational(3, 2)) + if re(z).is_positive: + return z/sqrt(3) * (besseli(-tt, a) + besseli(tt, a)) + else: + a = Pow(z, Rational(3, 2)) + b = Pow(a, tt) + c = Pow(a, -tt) + return sqrt(ot) * (b*besseli(-tt, tt*a) + z**2*c*besseli(tt, tt*a)) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + pf1 = z**2 / (2*root(3, 6)*gamma(Rational(2, 3))) + pf2 = root(3, 6) / gamma(Rational(1, 3)) + return pf1 * hyper([], [Rational(5, 3)], z**3/9) + pf2 * hyper([], [Rational(1, 3)], z**3/9) + + def _eval_expand_func(self, **hints): + arg = self.args[0] + symbs = arg.free_symbols + + if len(symbs) == 1: + z = symbs.pop() + c = Wild("c", exclude=[z]) + d = Wild("d", exclude=[z]) + m = Wild("m", exclude=[z]) + n = Wild("n", exclude=[z]) + M = arg.match(c*(d*z**n)**m) + if M is not None: + m = M[m] + # The transformation is in principle + # given by 03.08.16.0001.01 but note + # that there is an error in this formula. + # https://functions.wolfram.com/Bessel-TypeFunctions/AiryBiPrime/16/01/01/0001/ + if (3*m).is_integer: + c = M[c] + d = M[d] + n = M[n] + pf = (d**m * z**(n*m)) / (d * z**n)**m + newarg = c * d**m * z**(n*m) + return S.Half * (sqrt(3)*(pf - S.One)*airyaiprime(newarg) + (pf + S.One)*airybiprime(newarg)) + + +class marcumq(DefinedFunction): + r""" + The Marcum Q-function. + + Explanation + =========== + + The Marcum Q-function is defined by the meromorphic continuation of + + .. math:: + Q_m(a, b) = a^{- m + 1} \int_{b}^{\infty} x^{m} e^{- \frac{a^{2}}{2} - \frac{x^{2}}{2}} I_{m - 1}\left(a x\right)\, dx + + Examples + ======== + + >>> from sympy import marcumq + >>> from sympy.abc import m, a, b + >>> marcumq(m, a, b) + marcumq(m, a, b) + + Special values: + + >>> marcumq(m, 0, b) + uppergamma(m, b**2/2)/gamma(m) + >>> marcumq(0, 0, 0) + 0 + >>> marcumq(0, a, 0) + 1 - exp(-a**2/2) + >>> marcumq(1, a, a) + 1/2 + exp(-a**2)*besseli(0, a**2)/2 + >>> marcumq(2, a, a) + 1/2 + exp(-a**2)*besseli(0, a**2)/2 + exp(-a**2)*besseli(1, a**2) + + Differentiation with respect to $a$ and $b$ is supported: + + >>> from sympy import diff + >>> diff(marcumq(m, a, b), a) + a*(-marcumq(m, a, b) + marcumq(m + 1, a, b)) + >>> diff(marcumq(m, a, b), b) + -a**(1 - m)*b**m*exp(-a**2/2 - b**2/2)*besseli(m - 1, a*b) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Marcum_Q-function + .. [2] https://mathworld.wolfram.com/MarcumQ-Function.html + + """ + + @classmethod + def eval(cls, m, a, b): + if a is S.Zero: + if m is S.Zero and b is S.Zero: + return S.Zero + return uppergamma(m, b**2 * S.Half) / gamma(m) + + if m is S.Zero and b is S.Zero: + return 1 - 1 / exp(a**2 * S.Half) + + if a == b: + if m is S.One: + return (1 + exp(-a**2) * besseli(0, a**2))*S.Half + if m == 2: + return S.Half + S.Half * exp(-a**2) * besseli(0, a**2) + exp(-a**2) * besseli(1, a**2) + + if a.is_zero: + if m.is_zero and b.is_zero: + return S.Zero + return uppergamma(m, b**2*S.Half) / gamma(m) + + if m.is_zero and b.is_zero: + return 1 - 1 / exp(a**2*S.Half) + + def fdiff(self, argindex=2): + m, a, b = self.args + if argindex == 2: + return a * (-marcumq(m, a, b) + marcumq(1+m, a, b)) + elif argindex == 3: + return (-b**m / a**(m-1)) * exp(-(a**2 + b**2)/2) * besseli(m-1, a*b) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Integral(self, m, a, b, **kwargs): + from sympy.integrals.integrals import Integral + x = kwargs.get('x', Dummy(uniquely_named_symbol('x').name)) + return a ** (1 - m) * \ + Integral(x**m * exp(-(x**2 + a**2)/2) * besseli(m-1, a*x), [x, b, S.Infinity]) + + def _eval_rewrite_as_Sum(self, m, a, b, **kwargs): + from sympy.concrete.summations import Sum + k = kwargs.get('k', Dummy('k')) + return exp(-(a**2 + b**2) / 2) * Sum((a/b)**k * besseli(k, a*b), [k, 1-m, S.Infinity]) + + def _eval_rewrite_as_besseli(self, m, a, b, **kwargs): + if a == b: + if m == 1: + return (1 + exp(-a**2) * besseli(0, a**2)) / 2 + if m.is_Integer and m >= 2: + s = sum(besseli(i, a**2) for i in range(1, m)) + return S.Half + exp(-a**2) * besseli(0, a**2) / 2 + exp(-a**2) * s + + def _eval_is_zero(self): + if all(arg.is_zero for arg in self.args): + return True + +class _besseli(DefinedFunction): + """ + Helper function to make the $\\mathrm{besseli}(nu, z)$ + function tractable for the Gruntz algorithm. + + """ + + def _eval_aseries(self, n, args0, x, logx): + from sympy.functions.combinatorial.factorials import RisingFactorial + from sympy.series.order import Order + point = args0[1] + + if point in [S.Infinity, S.NegativeInfinity]: + nu, z = self.args + l = [((RisingFactorial(Rational(2*nu - 1, 2), k)*RisingFactorial( + Rational(2*nu + 1, 2), k))/((2)**(k)*z**(Rational(2*k + 1, 2))*factorial(k))) for k in range(n)] + return sqrt(pi/(2))*(Add(*l)) + Order(1/z**(Rational(2*n + 1, 2)), x) + + return super()._eval_aseries(n, args0, x, logx) + + def _eval_rewrite_as_intractable(self, nu, z, **kwargs): + return exp(-z)*besseli(nu, z) + + def _eval_nseries(self, x, n, logx, cdir=0): + x0 = self.args[0].limit(x, 0) + if x0.is_zero: + f = self._eval_rewrite_as_intractable(*self.args) + return f._eval_nseries(x, n, logx) + return super()._eval_nseries(x, n, logx) + + +class _besselk(DefinedFunction): + """ + Helper function to make the $\\mathrm{besselk}(nu, z)$ + function tractable for the Gruntz algorithm. + + """ + + def _eval_aseries(self, n, args0, x, logx): + from sympy.functions.combinatorial.factorials import RisingFactorial + from sympy.series.order import Order + point = args0[1] + + if point in [S.Infinity, S.NegativeInfinity]: + nu, z = self.args + l = [((RisingFactorial(Rational(2*nu - 1, 2), k)*RisingFactorial( + Rational(2*nu + 1, 2), k))/((-2)**(k)*z**(Rational(2*k + 1, 2))*factorial(k))) for k in range(n)] + return sqrt(pi/(2))*(Add(*l)) + Order(1/z**(Rational(2*n + 1, 2)), x) + + return super()._eval_aseries(n, args0, x, logx) + + def _eval_rewrite_as_intractable(self,nu, z, **kwargs): + return exp(z)*besselk(nu, z) + + def _eval_nseries(self, x, n, logx, cdir=0): + x0 = self.args[0].limit(x, 0) + if x0.is_zero: + f = self._eval_rewrite_as_intractable(*self.args) + return f._eval_nseries(x, n, logx) + return super()._eval_nseries(x, n, logx) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/beta_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/beta_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..337ae6c71fe5a38cc0fcd819e9d489ebbbd1946b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/beta_functions.py @@ -0,0 +1,389 @@ +from sympy.core import S +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.symbol import Dummy, uniquely_named_symbol +from sympy.functions.special.gamma_functions import gamma, digamma +from sympy.functions.combinatorial.numbers import catalan +from sympy.functions.elementary.complexes import conjugate + +# See mpmath #569 and SymPy #20569 +def betainc_mpmath_fix(a, b, x1, x2, reg=0): + from mpmath import betainc, mpf + if x1 == x2: + return mpf(0) + else: + return betainc(a, b, x1, x2, reg) + +############################################################################### +############################ COMPLETE BETA FUNCTION ########################## +############################################################################### + +class beta(DefinedFunction): + r""" + The beta integral is called the Eulerian integral of the first kind by + Legendre: + + .. math:: + \mathrm{B}(x,y) \int^{1}_{0} t^{x-1} (1-t)^{y-1} \mathrm{d}t. + + Explanation + =========== + + The Beta function or Euler's first integral is closely associated + with the gamma function. The Beta function is often used in probability + theory and mathematical statistics. It satisfies properties like: + + .. math:: + \mathrm{B}(a,1) = \frac{1}{a} \\ + \mathrm{B}(a,b) = \mathrm{B}(b,a) \\ + \mathrm{B}(a,b) = \frac{\Gamma(a) \Gamma(b)}{\Gamma(a+b)} + + Therefore for integral values of $a$ and $b$: + + .. math:: + \mathrm{B} = \frac{(a-1)! (b-1)!}{(a+b-1)!} + + A special case of the Beta function when `x = y` is the + Central Beta function. It satisfies properties like: + + .. math:: + \mathrm{B}(x) = 2^{1 - 2x}\mathrm{B}(x, \frac{1}{2}) + \mathrm{B}(x) = 2^{1 - 2x} cos(\pi x) \mathrm{B}(\frac{1}{2} - x, x) + \mathrm{B}(x) = \int_{0}^{1} \frac{t^x}{(1 + t)^{2x}} dt + \mathrm{B}(x) = \frac{2}{x} \prod_{n = 1}^{\infty} \frac{n(n + 2x)}{(n + x)^2} + + Examples + ======== + + >>> from sympy import I, pi + >>> from sympy.abc import x, y + + The Beta function obeys the mirror symmetry: + + >>> from sympy import beta, conjugate + >>> conjugate(beta(x, y)) + beta(conjugate(x), conjugate(y)) + + Differentiation with respect to both $x$ and $y$ is supported: + + >>> from sympy import beta, diff + >>> diff(beta(x, y), x) + (polygamma(0, x) - polygamma(0, x + y))*beta(x, y) + + >>> diff(beta(x, y), y) + (polygamma(0, y) - polygamma(0, x + y))*beta(x, y) + + >>> diff(beta(x), x) + 2*(polygamma(0, x) - polygamma(0, 2*x))*beta(x, x) + + We can numerically evaluate the Beta function to + arbitrary precision for any complex numbers x and y: + + >>> from sympy import beta + >>> beta(pi).evalf(40) + 0.02671848900111377452242355235388489324562 + + >>> beta(1 + I).evalf(20) + -0.2112723729365330143 - 0.7655283165378005676*I + + See Also + ======== + + gamma: Gamma function. + uppergamma: Upper incomplete gamma function. + lowergamma: Lower incomplete gamma function. + polygamma: Polygamma function. + loggamma: Log Gamma function. + digamma: Digamma function. + trigamma: Trigamma function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Beta_function + .. [2] https://mathworld.wolfram.com/BetaFunction.html + .. [3] https://dlmf.nist.gov/5.12 + + """ + unbranched = True + + def fdiff(self, argindex): + x, y = self.args + if argindex == 1: + # Diff wrt x + return beta(x, y)*(digamma(x) - digamma(x + y)) + elif argindex == 2: + # Diff wrt y + return beta(x, y)*(digamma(y) - digamma(x + y)) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, x, y=None): + if y is None: + return beta(x, x) + if x.is_Number and y.is_Number: + return beta(x, y, evaluate=False).doit() + + def doit(self, **hints): + x = xold = self.args[0] + # Deal with unevaluated single argument beta + single_argument = len(self.args) == 1 + y = yold = self.args[0] if single_argument else self.args[1] + if hints.get('deep', True): + x = x.doit(**hints) + y = y.doit(**hints) + if y.is_zero or x.is_zero: + return S.ComplexInfinity + if y is S.One: + return 1/x + if x is S.One: + return 1/y + if y == x + 1: + return 1/(x*y*catalan(x)) + s = x + y + if (s.is_integer and s.is_negative and x.is_integer is False and + y.is_integer is False): + return S.Zero + if x == xold and y == yold and not single_argument: + return self + return beta(x, y) + + def _eval_expand_func(self, **hints): + x, y = self.args + return gamma(x)*gamma(y) / gamma(x + y) + + def _eval_is_real(self): + return self.args[0].is_real and self.args[1].is_real + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate(), self.args[1].conjugate()) + + def _eval_rewrite_as_gamma(self, x, y, piecewise=True, **kwargs): + return self._eval_expand_func(**kwargs) + + def _eval_rewrite_as_Integral(self, x, y, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [x, y]).name) + return Integral(t**(x - 1)*(1 - t)**(y - 1), (t, 0, 1)) + +############################################################################### +########################## INCOMPLETE BETA FUNCTION ########################### +############################################################################### + +class betainc(DefinedFunction): + r""" + The Generalized Incomplete Beta function is defined as + + .. math:: + \mathrm{B}_{(x_1, x_2)}(a, b) = \int_{x_1}^{x_2} t^{a - 1} (1 - t)^{b - 1} dt + + The Incomplete Beta function is a special case + of the Generalized Incomplete Beta function : + + .. math:: \mathrm{B}_z (a, b) = \mathrm{B}_{(0, z)}(a, b) + + The Incomplete Beta function satisfies : + + .. math:: \mathrm{B}_z (a, b) = (-1)^a \mathrm{B}_{\frac{z}{z - 1}} (a, 1 - a - b) + + The Beta function is a special case of the Incomplete Beta function : + + .. math:: \mathrm{B}(a, b) = \mathrm{B}_{1}(a, b) + + Examples + ======== + + >>> from sympy import betainc, symbols, conjugate + >>> a, b, x, x1, x2 = symbols('a b x x1 x2') + + The Generalized Incomplete Beta function is given by: + + >>> betainc(a, b, x1, x2) + betainc(a, b, x1, x2) + + The Incomplete Beta function can be obtained as follows: + + >>> betainc(a, b, 0, x) + betainc(a, b, 0, x) + + The Incomplete Beta function obeys the mirror symmetry: + + >>> conjugate(betainc(a, b, x1, x2)) + betainc(conjugate(a), conjugate(b), conjugate(x1), conjugate(x2)) + + We can numerically evaluate the Incomplete Beta function to + arbitrary precision for any complex numbers a, b, x1 and x2: + + >>> from sympy import betainc, I + >>> betainc(2, 3, 4, 5).evalf(10) + 56.08333333 + >>> betainc(0.75, 1 - 4*I, 0, 2 + 3*I).evalf(25) + 0.2241657956955709603655887 + 0.3619619242700451992411724*I + + The Generalized Incomplete Beta function can be expressed + in terms of the Generalized Hypergeometric function. + + >>> from sympy import hyper + >>> betainc(a, b, x1, x2).rewrite(hyper) + (-x1**a*hyper((a, 1 - b), (a + 1,), x1) + x2**a*hyper((a, 1 - b), (a + 1,), x2))/a + + See Also + ======== + + beta: Beta function + hyper: Generalized Hypergeometric function + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function + .. [2] https://dlmf.nist.gov/8.17 + .. [3] https://functions.wolfram.com/GammaBetaErf/Beta4/ + .. [4] https://functions.wolfram.com/GammaBetaErf/BetaRegularized4/02/ + + """ + nargs = 4 + unbranched = True + + def fdiff(self, argindex): + a, b, x1, x2 = self.args + if argindex == 3: + # Diff wrt x1 + return -(1 - x1)**(b - 1)*x1**(a - 1) + elif argindex == 4: + # Diff wrt x2 + return (1 - x2)**(b - 1)*x2**(a - 1) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_mpmath(self): + return betainc_mpmath_fix, self.args + + def _eval_is_real(self): + if all(arg.is_real for arg in self.args): + return True + + def _eval_conjugate(self): + return self.func(*map(conjugate, self.args)) + + def _eval_rewrite_as_Integral(self, a, b, x1, x2, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [a, b, x1, x2]).name) + return Integral(t**(a - 1)*(1 - t)**(b - 1), (t, x1, x2)) + + def _eval_rewrite_as_hyper(self, a, b, x1, x2, **kwargs): + from sympy.functions.special.hyper import hyper + return (x2**a * hyper((a, 1 - b), (a + 1,), x2) - x1**a * hyper((a, 1 - b), (a + 1,), x1)) / a + +############################################################################### +#################### REGULARIZED INCOMPLETE BETA FUNCTION ##################### +############################################################################### + +class betainc_regularized(DefinedFunction): + r""" + The Generalized Regularized Incomplete Beta function is given by + + .. math:: + \mathrm{I}_{(x_1, x_2)}(a, b) = \frac{\mathrm{B}_{(x_1, x_2)}(a, b)}{\mathrm{B}(a, b)} + + The Regularized Incomplete Beta function is a special case + of the Generalized Regularized Incomplete Beta function : + + .. math:: \mathrm{I}_z (a, b) = \mathrm{I}_{(0, z)}(a, b) + + The Regularized Incomplete Beta function is the cumulative distribution + function of the beta distribution. + + Examples + ======== + + >>> from sympy import betainc_regularized, symbols, conjugate + >>> a, b, x, x1, x2 = symbols('a b x x1 x2') + + The Generalized Regularized Incomplete Beta + function is given by: + + >>> betainc_regularized(a, b, x1, x2) + betainc_regularized(a, b, x1, x2) + + The Regularized Incomplete Beta function + can be obtained as follows: + + >>> betainc_regularized(a, b, 0, x) + betainc_regularized(a, b, 0, x) + + The Regularized Incomplete Beta function + obeys the mirror symmetry: + + >>> conjugate(betainc_regularized(a, b, x1, x2)) + betainc_regularized(conjugate(a), conjugate(b), conjugate(x1), conjugate(x2)) + + We can numerically evaluate the Regularized Incomplete Beta function + to arbitrary precision for any complex numbers a, b, x1 and x2: + + >>> from sympy import betainc_regularized, pi, E + >>> betainc_regularized(1, 2, 0, 0.25).evalf(10) + 0.4375000000 + >>> betainc_regularized(pi, E, 0, 1).evalf(5) + 1.00000 + + The Generalized Regularized Incomplete Beta function can be + expressed in terms of the Generalized Hypergeometric function. + + >>> from sympy import hyper + >>> betainc_regularized(a, b, x1, x2).rewrite(hyper) + (-x1**a*hyper((a, 1 - b), (a + 1,), x1) + x2**a*hyper((a, 1 - b), (a + 1,), x2))/(a*beta(a, b)) + + See Also + ======== + + beta: Beta function + hyper: Generalized Hypergeometric function + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function + .. [2] https://dlmf.nist.gov/8.17 + .. [3] https://functions.wolfram.com/GammaBetaErf/Beta4/ + .. [4] https://functions.wolfram.com/GammaBetaErf/BetaRegularized4/02/ + + """ + nargs = 4 + unbranched = True + + def __new__(cls, a, b, x1, x2): + return super().__new__(cls, a, b, x1, x2) + + def _eval_mpmath(self): + return betainc_mpmath_fix, (*self.args, S(1)) + + def fdiff(self, argindex): + a, b, x1, x2 = self.args + if argindex == 3: + # Diff wrt x1 + return -(1 - x1)**(b - 1)*x1**(a - 1) / beta(a, b) + elif argindex == 4: + # Diff wrt x2 + return (1 - x2)**(b - 1)*x2**(a - 1) / beta(a, b) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_real(self): + if all(arg.is_real for arg in self.args): + return True + + def _eval_conjugate(self): + return self.func(*map(conjugate, self.args)) + + def _eval_rewrite_as_Integral(self, a, b, x1, x2, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [a, b, x1, x2]).name) + integrand = t**(a - 1)*(1 - t)**(b - 1) + expr = Integral(integrand, (t, x1, x2)) + return expr / Integral(integrand, (t, 0, 1)) + + def _eval_rewrite_as_hyper(self, a, b, x1, x2, **kwargs): + from sympy.functions.special.hyper import hyper + expr = (x2**a * hyper((a, 1 - b), (a + 1,), x2) - x1**a * hyper((a, 1 - b), (a + 1,), x1)) / a + return expr / beta(a, b) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/bsplines.py b/phivenv/Lib/site-packages/sympy/functions/special/bsplines.py new file mode 100644 index 0000000000000000000000000000000000000000..50c9141e841288aa02457c466fc6573f9a20d09f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/bsplines.py @@ -0,0 +1,348 @@ +from sympy.core import S, sympify +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions import Piecewise, piecewise_fold +from sympy.logic.boolalg import And +from sympy.sets.sets import Interval + +from functools import lru_cache + + +def _ivl(cond, x): + """return the interval corresponding to the condition + + Conditions in spline's Piecewise give the range over + which an expression is valid like (lo <= x) & (x <= hi). + This function returns (lo, hi). + """ + if isinstance(cond, And) and len(cond.args) == 2: + a, b = cond.args + if a.lts == x: + a, b = b, a + return a.lts, b.gts + raise TypeError('unexpected cond type: %s' % cond) + + +def _add_splines(c, b1, d, b2, x): + """Construct c*b1 + d*b2.""" + + if S.Zero in (b1, c): + rv = piecewise_fold(d * b2) + elif S.Zero in (b2, d): + rv = piecewise_fold(c * b1) + else: + new_args = [] + # Just combining the Piecewise without any fancy optimization + p1 = piecewise_fold(c * b1) + p2 = piecewise_fold(d * b2) + + # Search all Piecewise arguments except (0, True) + p2args = list(p2.args[:-1]) + + # This merging algorithm assumes the conditions in + # p1 and p2 are sorted + for arg in p1.args[:-1]: + expr = arg.expr + cond = arg.cond + + lower = _ivl(cond, x)[0] + + # Check p2 for matching conditions that can be merged + for i, arg2 in enumerate(p2args): + expr2 = arg2.expr + cond2 = arg2.cond + + lower_2, upper_2 = _ivl(cond2, x) + if cond2 == cond: + # Conditions match, join expressions + expr += expr2 + # Remove matching element + del p2args[i] + # No need to check the rest + break + elif lower_2 < lower and upper_2 <= lower: + # Check if arg2 condition smaller than arg1, + # add to new_args by itself (no match expected + # in p1) + new_args.append(arg2) + del p2args[i] + break + + # Checked all, add expr and cond + new_args.append((expr, cond)) + + # Add remaining items from p2args + new_args.extend(p2args) + + # Add final (0, True) + new_args.append((0, True)) + + rv = Piecewise(*new_args, evaluate=False) + + return rv.expand() + + +@lru_cache(maxsize=128) +def bspline_basis(d, knots, n, x): + """ + The $n$-th B-spline at $x$ of degree $d$ with knots. + + Explanation + =========== + + B-Splines are piecewise polynomials of degree $d$. They are defined on a + set of knots, which is a sequence of integers or floats. + + Examples + ======== + + The 0th degree splines have a value of 1 on a single interval: + + >>> from sympy import bspline_basis + >>> from sympy.abc import x + >>> d = 0 + >>> knots = tuple(range(5)) + >>> bspline_basis(d, knots, 0, x) + Piecewise((1, (x >= 0) & (x <= 1)), (0, True)) + + For a given ``(d, knots)`` there are ``len(knots)-d-1`` B-splines + defined, that are indexed by ``n`` (starting at 0). + + Here is an example of a cubic B-spline: + + >>> bspline_basis(3, tuple(range(5)), 0, x) + Piecewise((x**3/6, (x >= 0) & (x <= 1)), + (-x**3/2 + 2*x**2 - 2*x + 2/3, + (x >= 1) & (x <= 2)), + (x**3/2 - 4*x**2 + 10*x - 22/3, + (x >= 2) & (x <= 3)), + (-x**3/6 + 2*x**2 - 8*x + 32/3, + (x >= 3) & (x <= 4)), + (0, True)) + + By repeating knot points, you can introduce discontinuities in the + B-splines and their derivatives: + + >>> d = 1 + >>> knots = (0, 0, 2, 3, 4) + >>> bspline_basis(d, knots, 0, x) + Piecewise((1 - x/2, (x >= 0) & (x <= 2)), (0, True)) + + It is quite time consuming to construct and evaluate B-splines. If + you need to evaluate a B-spline many times, it is best to lambdify them + first: + + >>> from sympy import lambdify + >>> d = 3 + >>> knots = tuple(range(10)) + >>> b0 = bspline_basis(d, knots, 0, x) + >>> f = lambdify(x, b0) + >>> y = f(0.5) + + Parameters + ========== + + d : integer + degree of bspline + + knots : list of integer values + list of knots points of bspline + + n : integer + $n$-th B-spline + + x : symbol + + See Also + ======== + + bspline_basis_set + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/B-spline + + """ + # make sure x has no assumptions so conditions don't evaluate + xvar = x + x = Dummy() + + knots = tuple(sympify(k) for k in knots) + d = int(d) + n = int(n) + n_knots = len(knots) + n_intervals = n_knots - 1 + if n + d + 1 > n_intervals: + raise ValueError("n + d + 1 must not exceed len(knots) - 1") + if d == 0: + result = Piecewise( + (S.One, Interval(knots[n], knots[n + 1]).contains(x)), (0, True) + ) + elif d > 0: + denom = knots[n + d + 1] - knots[n + 1] + if denom != S.Zero: + B = (knots[n + d + 1] - x) / denom + b2 = bspline_basis(d - 1, knots, n + 1, x) + else: + b2 = B = S.Zero + + denom = knots[n + d] - knots[n] + if denom != S.Zero: + A = (x - knots[n]) / denom + b1 = bspline_basis(d - 1, knots, n, x) + else: + b1 = A = S.Zero + + result = _add_splines(A, b1, B, b2, x) + else: + raise ValueError("degree must be non-negative: %r" % n) + + # return result with user-given x + return result.xreplace({x: xvar}) + + +def bspline_basis_set(d, knots, x): + """ + Return the ``len(knots)-d-1`` B-splines at *x* of degree *d* + with *knots*. + + Explanation + =========== + + This function returns a list of piecewise polynomials that are the + ``len(knots)-d-1`` B-splines of degree *d* for the given knots. + This function calls ``bspline_basis(d, knots, n, x)`` for different + values of *n*. + + Examples + ======== + + >>> from sympy import bspline_basis_set + >>> from sympy.abc import x + >>> d = 2 + >>> knots = range(5) + >>> splines = bspline_basis_set(d, knots, x) + >>> splines + [Piecewise((x**2/2, (x >= 0) & (x <= 1)), + (-x**2 + 3*x - 3/2, (x >= 1) & (x <= 2)), + (x**2/2 - 3*x + 9/2, (x >= 2) & (x <= 3)), + (0, True)), + Piecewise((x**2/2 - x + 1/2, (x >= 1) & (x <= 2)), + (-x**2 + 5*x - 11/2, (x >= 2) & (x <= 3)), + (x**2/2 - 4*x + 8, (x >= 3) & (x <= 4)), + (0, True))] + + Parameters + ========== + + d : integer + degree of bspline + + knots : list of integers + list of knots points of bspline + + x : symbol + + See Also + ======== + + bspline_basis + + """ + n_splines = len(knots) - d - 1 + return [bspline_basis(d, tuple(knots), i, x) for i in range(n_splines)] + + +def interpolating_spline(d, x, X, Y): + """ + Return spline of degree *d*, passing through the given *X* + and *Y* values. + + Explanation + =========== + + This function returns a piecewise function such that each part is + a polynomial of degree not greater than *d*. The value of *d* + must be 1 or greater and the values of *X* must be strictly + increasing. + + Examples + ======== + + >>> from sympy import interpolating_spline + >>> from sympy.abc import x + >>> interpolating_spline(1, x, [1, 2, 4, 7], [3, 6, 5, 7]) + Piecewise((3*x, (x >= 1) & (x <= 2)), + (7 - x/2, (x >= 2) & (x <= 4)), + (2*x/3 + 7/3, (x >= 4) & (x <= 7))) + >>> interpolating_spline(3, x, [-2, 0, 1, 3, 4], [4, 2, 1, 1, 3]) + Piecewise((7*x**3/117 + 7*x**2/117 - 131*x/117 + 2, (x >= -2) & (x <= 1)), + (10*x**3/117 - 2*x**2/117 - 122*x/117 + 77/39, (x >= 1) & (x <= 4))) + + Parameters + ========== + + d : integer + Degree of Bspline strictly greater than equal to one + + x : symbol + + X : list of strictly increasing real values + list of X coordinates through which the spline passes + + Y : list of real values + list of corresponding Y coordinates through which the spline passes + + See Also + ======== + + bspline_basis_set, interpolating_poly + + """ + from sympy.solvers.solveset import linsolve + from sympy.matrices.dense import Matrix + + # Input sanitization + d = sympify(d) + if not (d.is_Integer and d.is_positive): + raise ValueError("Spline degree must be a positive integer, not %s." % d) + if len(X) != len(Y): + raise ValueError("Number of X and Y coordinates must be the same.") + if len(X) < d + 1: + raise ValueError("Degree must be less than the number of control points.") + if not all(a < b for a, b in zip(X, X[1:])): + raise ValueError("The x-coordinates must be strictly increasing.") + X = [sympify(i) for i in X] + + # Evaluating knots value + if d.is_odd: + j = (d + 1) // 2 + interior_knots = X[j:-j] + else: + j = d // 2 + interior_knots = [ + (a + b)/2 for a, b in zip(X[j : -j - 1], X[j + 1 : -j]) + ] + + knots = [X[0]] * (d + 1) + list(interior_knots) + [X[-1]] * (d + 1) + + basis = bspline_basis_set(d, knots, x) + + A = [[b.subs(x, v) for b in basis] for v in X] + + coeff = linsolve((Matrix(A), Matrix(Y)), symbols("c0:{}".format(len(X)), cls=Dummy)) + coeff = list(coeff)[0] + intervals = {c for b in basis for (e, c) in b.args if c != True} + + # Sorting the intervals + # ival contains the end-points of each interval + intervals = sorted(intervals, key=lambda c: _ivl(c, x)) + + basis_dicts = [{c: e for (e, c) in b.args} for b in basis] + spline = [] + for i in intervals: + piece = sum( + [c * d.get(i, S.Zero) for (c, d) in zip(coeff, basis_dicts)], S.Zero + ) + spline.append((piece, i)) + return Piecewise(*spline) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/delta_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/delta_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..698d8c0c3ba5e68c2f084e83e7a9ec070ce83307 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/delta_functions.py @@ -0,0 +1,664 @@ +from sympy.core import S, diff +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.logic import fuzzy_not +from sympy.core.relational import Eq, Ne +from sympy.functions.elementary.complexes import im, sign +from sympy.functions.elementary.piecewise import Piecewise +from sympy.polys.polyerrors import PolynomialError +from sympy.polys.polyroots import roots +from sympy.utilities.misc import filldedent + + +############################################################################### +################################ DELTA FUNCTION ############################### +############################################################################### + + +class DiracDelta(DefinedFunction): + r""" + The DiracDelta function and its derivatives. + + Explanation + =========== + + DiracDelta is not an ordinary function. It can be rigorously defined either + as a distribution or as a measure. + + DiracDelta only makes sense in definite integrals, and in particular, + integrals of the form ``Integral(f(x)*DiracDelta(x - x0), (x, a, b))``, + where it equals ``f(x0)`` if ``a <= x0 <= b`` and ``0`` otherwise. Formally, + DiracDelta acts in some ways like a function that is ``0`` everywhere except + at ``0``, but in many ways it also does not. It can often be useful to treat + DiracDelta in formal ways, building up and manipulating expressions with + delta functions (which may eventually be integrated), but care must be taken + to not treat it as a real function. SymPy's ``oo`` is similar. It only + truly makes sense formally in certain contexts (such as integration limits), + but SymPy allows its use everywhere, and it tries to be consistent with + operations on it (like ``1/oo``), but it is easy to get into trouble and get + wrong results if ``oo`` is treated too much like a number. Similarly, if + DiracDelta is treated too much like a function, it is easy to get wrong or + nonsensical results. + + DiracDelta function has the following properties: + + 1) $\frac{d}{d x} \theta(x) = \delta(x)$ + 2) $\int_{-\infty}^\infty \delta(x - a)f(x)\, dx = f(a)$ and $\int_{a- + \epsilon}^{a+\epsilon} \delta(x - a)f(x)\, dx = f(a)$ + 3) $\delta(x) = 0$ for all $x \neq 0$ + 4) $\delta(g(x)) = \sum_i \frac{\delta(x - x_i)}{\|g'(x_i)\|}$ where $x_i$ + are the roots of $g$ + 5) $\delta(-x) = \delta(x)$ + + Derivatives of ``k``-th order of DiracDelta have the following properties: + + 6) $\delta(x, k) = 0$ for all $x \neq 0$ + 7) $\delta(-x, k) = -\delta(x, k)$ for odd $k$ + 8) $\delta(-x, k) = \delta(x, k)$ for even $k$ + + Examples + ======== + + >>> from sympy import DiracDelta, diff, pi + >>> from sympy.abc import x, y + + >>> DiracDelta(x) + DiracDelta(x) + >>> DiracDelta(1) + 0 + >>> DiracDelta(-1) + 0 + >>> DiracDelta(pi) + 0 + >>> DiracDelta(x - 4).subs(x, 4) + DiracDelta(0) + >>> diff(DiracDelta(x)) + DiracDelta(x, 1) + >>> diff(DiracDelta(x - 1), x, 2) + DiracDelta(x - 1, 2) + >>> diff(DiracDelta(x**2 - 1), x, 2) + 2*(2*x**2*DiracDelta(x**2 - 1, 2) + DiracDelta(x**2 - 1, 1)) + >>> DiracDelta(3*x).is_simple(x) + True + >>> DiracDelta(x**2).is_simple(x) + False + >>> DiracDelta((x**2 - 1)*y).expand(diracdelta=True, wrt=x) + DiracDelta(x - 1)/(2*Abs(y)) + DiracDelta(x + 1)/(2*Abs(y)) + + See Also + ======== + + Heaviside + sympy.simplify.simplify.simplify, is_simple + sympy.functions.special.tensor_functions.KroneckerDelta + + References + ========== + + .. [1] https://mathworld.wolfram.com/DeltaFunction.html + + """ + + is_real = True + + def fdiff(self, argindex=1): + """ + Returns the first derivative of a DiracDelta Function. + + Explanation + =========== + + The difference between ``diff()`` and ``fdiff()`` is: ``diff()`` is the + user-level function and ``fdiff()`` is an object method. ``fdiff()`` is + a convenience method available in the ``Function`` class. It returns + the derivative of the function without considering the chain rule. + ``diff(function, x)`` calls ``Function._eval_derivative`` which in turn + calls ``fdiff()`` internally to compute the derivative of the function. + + Examples + ======== + + >>> from sympy import DiracDelta, diff + >>> from sympy.abc import x + + >>> DiracDelta(x).fdiff() + DiracDelta(x, 1) + + >>> DiracDelta(x, 1).fdiff() + DiracDelta(x, 2) + + >>> DiracDelta(x**2 - 1).fdiff() + DiracDelta(x**2 - 1, 1) + + >>> diff(DiracDelta(x, 1)).fdiff() + DiracDelta(x, 3) + + Parameters + ========== + + argindex : integer + degree of derivative + + """ + if argindex == 1: + #I didn't know if there is a better way to handle default arguments + k = 0 + if len(self.args) > 1: + k = self.args[1] + return self.func(self.args[0], k + 1) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg, k=S.Zero): + """ + Returns a simplified form or a value of DiracDelta depending on the + argument passed by the DiracDelta object. + + Explanation + =========== + + The ``eval()`` method is automatically called when the ``DiracDelta`` + class is about to be instantiated and it returns either some simplified + instance or the unevaluated instance depending on the argument passed. + In other words, ``eval()`` method is not needed to be called explicitly, + it is being called and evaluated once the object is called. + + Examples + ======== + + >>> from sympy import DiracDelta, S + >>> from sympy.abc import x + + >>> DiracDelta(x) + DiracDelta(x) + + >>> DiracDelta(-x, 1) + -DiracDelta(x, 1) + + >>> DiracDelta(1) + 0 + + >>> DiracDelta(5, 1) + 0 + + >>> DiracDelta(0) + DiracDelta(0) + + >>> DiracDelta(-1) + 0 + + >>> DiracDelta(S.NaN) + nan + + >>> DiracDelta(x - 100).subs(x, 5) + 0 + + >>> DiracDelta(x - 100).subs(x, 100) + DiracDelta(0) + + Parameters + ========== + + k : integer + order of derivative + + arg : argument passed to DiracDelta + + """ + if not k.is_Integer or k.is_negative: + raise ValueError("Error: the second argument of DiracDelta must be \ + a non-negative integer, %s given instead." % (k,)) + if arg is S.NaN: + return S.NaN + if arg.is_nonzero: + return S.Zero + if fuzzy_not(im(arg).is_zero): + raise ValueError(filldedent(''' + Function defined only for Real Values. + Complex part: %s found in %s .''' % ( + repr(im(arg)), repr(arg)))) + c, nc = arg.args_cnc() + if c and c[0] is S.NegativeOne: + # keep this fast and simple instead of using + # could_extract_minus_sign + if k.is_odd: + return -cls(-arg, k) + elif k.is_even: + return cls(-arg, k) if k else cls(-arg) + elif k.is_zero: + return cls(arg, evaluate=False) + + def _eval_expand_diracdelta(self, **hints): + """ + Compute a simplified representation of the function using + property number 4. Pass ``wrt`` as a hint to expand the expression + with respect to a particular variable. + + Explanation + =========== + + ``wrt`` is: + + - a variable with respect to which a DiracDelta expression will + get expanded. + + Examples + ======== + + >>> from sympy import DiracDelta + >>> from sympy.abc import x, y + + >>> DiracDelta(x*y).expand(diracdelta=True, wrt=x) + DiracDelta(x)/Abs(y) + >>> DiracDelta(x*y).expand(diracdelta=True, wrt=y) + DiracDelta(y)/Abs(x) + + >>> DiracDelta(x**2 + x - 2).expand(diracdelta=True, wrt=x) + DiracDelta(x - 1)/3 + DiracDelta(x + 2)/3 + + See Also + ======== + + is_simple, Diracdelta + + """ + wrt = hints.get('wrt', None) + if wrt is None: + free = self.free_symbols + if len(free) == 1: + wrt = free.pop() + else: + raise TypeError(filldedent(''' + When there is more than 1 free symbol or variable in the expression, + the 'wrt' keyword is required as a hint to expand when using the + DiracDelta hint.''')) + + if not self.args[0].has(wrt) or (len(self.args) > 1 and self.args[1] != 0 ): + return self + try: + argroots = roots(self.args[0], wrt) + result = 0 + valid = True + darg = abs(diff(self.args[0], wrt)) + for r, m in argroots.items(): + if r.is_real is not False and m == 1: + result += self.func(wrt - r)/darg.subs(wrt, r) + else: + # don't handle non-real and if m != 1 then + # a polynomial will have a zero in the derivative (darg) + # at r + valid = False + break + if valid: + return result + except PolynomialError: + pass + return self + + def is_simple(self, x): + """ + Tells whether the argument(args[0]) of DiracDelta is a linear + expression in *x*. + + Examples + ======== + + >>> from sympy import DiracDelta, cos + >>> from sympy.abc import x, y + + >>> DiracDelta(x*y).is_simple(x) + True + >>> DiracDelta(x*y).is_simple(y) + True + + >>> DiracDelta(x**2 + x - 2).is_simple(x) + False + + >>> DiracDelta(cos(x)).is_simple(x) + False + + Parameters + ========== + + x : can be a symbol + + See Also + ======== + + sympy.simplify.simplify.simplify, DiracDelta + + """ + p = self.args[0].as_poly(x) + if p: + return p.degree() == 1 + return False + + def _eval_rewrite_as_Piecewise(self, *args, **kwargs): + """ + Represents DiracDelta in a piecewise form. + + Examples + ======== + + >>> from sympy import DiracDelta, Piecewise, Symbol + >>> x = Symbol('x') + + >>> DiracDelta(x).rewrite(Piecewise) + Piecewise((DiracDelta(0), Eq(x, 0)), (0, True)) + + >>> DiracDelta(x - 5).rewrite(Piecewise) + Piecewise((DiracDelta(0), Eq(x, 5)), (0, True)) + + >>> DiracDelta(x**2 - 5).rewrite(Piecewise) + Piecewise((DiracDelta(0), Eq(x**2, 5)), (0, True)) + + >>> DiracDelta(x - 5, 4).rewrite(Piecewise) + DiracDelta(x - 5, 4) + + """ + if len(args) == 1: + return Piecewise((DiracDelta(0), Eq(args[0], 0)), (0, True)) + + def _eval_rewrite_as_SingularityFunction(self, *args, **kwargs): + """ + Returns the DiracDelta expression written in the form of Singularity + Functions. + + """ + from sympy.solvers import solve + from sympy.functions.special.singularity_functions import SingularityFunction + if self == DiracDelta(0): + return SingularityFunction(0, 0, -1) + if self == DiracDelta(0, 1): + return SingularityFunction(0, 0, -2) + free = self.free_symbols + if len(free) == 1: + x = (free.pop()) + if len(args) == 1: + return SingularityFunction(x, solve(args[0], x)[0], -1) + return SingularityFunction(x, solve(args[0], x)[0], -args[1] - 1) + else: + # I don't know how to handle the case for DiracDelta expressions + # having arguments with more than one variable. + raise TypeError(filldedent(''' + rewrite(SingularityFunction) does not support + arguments with more that one variable.''')) + + +############################################################################### +############################## HEAVISIDE FUNCTION ############################# +############################################################################### + + +class Heaviside(DefinedFunction): + r""" + Heaviside step function. + + Explanation + =========== + + The Heaviside step function has the following properties: + + 1) $\frac{d}{d x} \theta(x) = \delta(x)$ + 2) $\theta(x) = \begin{cases} 0 & \text{for}\: x < 0 \\ \frac{1}{2} & + \text{for}\: x = 0 \\1 & \text{for}\: x > 0 \end{cases}$ + 3) $\frac{d}{d x} \max(x, 0) = \theta(x)$ + + Heaviside(x) is printed as $\theta(x)$ with the SymPy LaTeX printer. + + The value at 0 is set differently in different fields. SymPy uses 1/2, + which is a convention from electronics and signal processing, and is + consistent with solving improper integrals by Fourier transform and + convolution. + + To specify a different value of Heaviside at ``x=0``, a second argument + can be given. Using ``Heaviside(x, nan)`` gives an expression that will + evaluate to nan for x=0. + + .. versionchanged:: 1.9 ``Heaviside(0)`` now returns 1/2 (before: undefined) + + Examples + ======== + + >>> from sympy import Heaviside, nan + >>> from sympy.abc import x + >>> Heaviside(9) + 1 + >>> Heaviside(-9) + 0 + >>> Heaviside(0) + 1/2 + >>> Heaviside(0, nan) + nan + >>> (Heaviside(x) + 1).replace(Heaviside(x), Heaviside(x, 1)) + Heaviside(x, 1) + 1 + + See Also + ======== + + DiracDelta + + References + ========== + + .. [1] https://mathworld.wolfram.com/HeavisideStepFunction.html + .. [2] https://dlmf.nist.gov/1.16#iv + + """ + + is_real = True + + def fdiff(self, argindex=1): + """ + Returns the first derivative of a Heaviside Function. + + Examples + ======== + + >>> from sympy import Heaviside, diff + >>> from sympy.abc import x + + >>> Heaviside(x).fdiff() + DiracDelta(x) + + >>> Heaviside(x**2 - 1).fdiff() + DiracDelta(x**2 - 1) + + >>> diff(Heaviside(x)).fdiff() + DiracDelta(x, 1) + + Parameters + ========== + + argindex : integer + order of derivative + + """ + if argindex == 1: + return DiracDelta(self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + def __new__(cls, arg, H0=S.Half, **options): + if isinstance(H0, Heaviside) and len(H0.args) == 1: + H0 = S.Half + return super(cls, cls).__new__(cls, arg, H0, **options) + + @property + def pargs(self): + """Args without default S.Half""" + args = self.args + if args[1] is S.Half: + args = args[:1] + return args + + @classmethod + def eval(cls, arg, H0=S.Half): + """ + Returns a simplified form or a value of Heaviside depending on the + argument passed by the Heaviside object. + + Explanation + =========== + + The ``eval()`` method is automatically called when the ``Heaviside`` + class is about to be instantiated and it returns either some simplified + instance or the unevaluated instance depending on the argument passed. + In other words, ``eval()`` method is not needed to be called explicitly, + it is being called and evaluated once the object is called. + + Examples + ======== + + >>> from sympy import Heaviside, S + >>> from sympy.abc import x + + >>> Heaviside(x) + Heaviside(x) + + >>> Heaviside(19) + 1 + + >>> Heaviside(0) + 1/2 + + >>> Heaviside(0, 1) + 1 + + >>> Heaviside(-5) + 0 + + >>> Heaviside(S.NaN) + nan + + >>> Heaviside(x - 100).subs(x, 5) + 0 + + >>> Heaviside(x - 100).subs(x, 105) + 1 + + Parameters + ========== + + arg : argument passed by Heaviside object + + H0 : value of Heaviside(0) + + """ + if arg.is_extended_negative: + return S.Zero + elif arg.is_extended_positive: + return S.One + elif arg.is_zero: + return H0 + elif arg is S.NaN: + return S.NaN + elif fuzzy_not(im(arg).is_zero): + raise ValueError("Function defined only for Real Values. Complex part: %s found in %s ." % (repr(im(arg)), repr(arg)) ) + + def _eval_rewrite_as_Piecewise(self, arg, H0=None, **kwargs): + """ + Represents Heaviside in a Piecewise form. + + Examples + ======== + + >>> from sympy import Heaviside, Piecewise, Symbol, nan + >>> x = Symbol('x') + + >>> Heaviside(x).rewrite(Piecewise) + Piecewise((0, x < 0), (1/2, Eq(x, 0)), (1, True)) + + >>> Heaviside(x,nan).rewrite(Piecewise) + Piecewise((0, x < 0), (nan, Eq(x, 0)), (1, True)) + + >>> Heaviside(x - 5).rewrite(Piecewise) + Piecewise((0, x < 5), (1/2, Eq(x, 5)), (1, True)) + + >>> Heaviside(x**2 - 1).rewrite(Piecewise) + Piecewise((0, x**2 < 1), (1/2, Eq(x**2, 1)), (1, True)) + + """ + if H0 == 0: + return Piecewise((0, arg <= 0), (1, True)) + if H0 == 1: + return Piecewise((0, arg < 0), (1, True)) + return Piecewise((0, arg < 0), (H0, Eq(arg, 0)), (1, True)) + + def _eval_rewrite_as_sign(self, arg, H0=S.Half, **kwargs): + """ + Represents the Heaviside function in the form of sign function. + + Explanation + =========== + + The value of Heaviside(0) must be 1/2 for rewriting as sign to be + strictly equivalent. For easier usage, we also allow this rewriting + when Heaviside(0) is undefined. + + Examples + ======== + + >>> from sympy import Heaviside, Symbol, sign, nan + >>> x = Symbol('x', real=True) + >>> y = Symbol('y') + + >>> Heaviside(x).rewrite(sign) + sign(x)/2 + 1/2 + + >>> Heaviside(x, 0).rewrite(sign) + Piecewise((sign(x)/2 + 1/2, Ne(x, 0)), (0, True)) + + >>> Heaviside(x, nan).rewrite(sign) + Piecewise((sign(x)/2 + 1/2, Ne(x, 0)), (nan, True)) + + >>> Heaviside(x - 2).rewrite(sign) + sign(x - 2)/2 + 1/2 + + >>> Heaviside(x**2 - 2*x + 1).rewrite(sign) + sign(x**2 - 2*x + 1)/2 + 1/2 + + >>> Heaviside(y).rewrite(sign) + Heaviside(y) + + >>> Heaviside(y**2 - 2*y + 1).rewrite(sign) + Heaviside(y**2 - 2*y + 1) + + See Also + ======== + + sign + + """ + if arg.is_extended_real: + pw1 = Piecewise( + ((sign(arg) + 1)/2, Ne(arg, 0)), + (Heaviside(0, H0=H0), True)) + pw2 = Piecewise( + ((sign(arg) + 1)/2, Eq(Heaviside(0, H0=H0), S.Half)), + (pw1, True)) + return pw2 + + def _eval_rewrite_as_SingularityFunction(self, args, H0=S.Half, **kwargs): + """ + Returns the Heaviside expression written in the form of Singularity + Functions. + + """ + from sympy.solvers import solve + from sympy.functions.special.singularity_functions import SingularityFunction + if self == Heaviside(0): + return SingularityFunction(0, 0, 0) + free = self.free_symbols + if len(free) == 1: + x = (free.pop()) + return SingularityFunction(x, solve(args, x)[0], 0) + # TODO + # ((x - 5)**3*Heaviside(x - 5)).rewrite(SingularityFunction) should output + # SingularityFunction(x, 5, 0) instead of (x - 5)**3*SingularityFunction(x, 5, 0) + else: + # I don't know how to handle the case for Heaviside expressions + # having arguments with more than one variable. + raise TypeError(filldedent(''' + rewrite(SingularityFunction) does not + support arguments with more that one variable.''')) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/elliptic_integrals.py b/phivenv/Lib/site-packages/sympy/functions/special/elliptic_integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..a94e343c0106891db44668ae05c33e84ecd05d0b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/elliptic_integrals.py @@ -0,0 +1,445 @@ +""" Elliptic Integrals. """ + +from sympy.core import S, pi, I, Rational +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.symbol import Dummy,uniquely_named_symbol +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.hyperbolic import atanh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin, tan +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import hyper, meijerg + +class elliptic_k(DefinedFunction): + r""" + The complete elliptic integral of the first kind, defined by + + .. math:: K(m) = F\left(\tfrac{\pi}{2}\middle| m\right) + + where $F\left(z\middle| m\right)$ is the Legendre incomplete + elliptic integral of the first kind. + + Explanation + =========== + + The function $K(m)$ is a single-valued function on the complex + plane with branch cut along the interval $(1, \infty)$. + + Note that our notation defines the incomplete elliptic integral + in terms of the parameter $m$ instead of the elliptic modulus + (eccentricity) $k$. + In this case, the parameter $m$ is defined as $m=k^2$. + + Examples + ======== + + >>> from sympy import elliptic_k, I + >>> from sympy.abc import m + >>> elliptic_k(0) + pi/2 + >>> elliptic_k(1.0 + I) + 1.50923695405127 + 0.625146415202697*I + >>> elliptic_k(m).series(n=3) + pi/2 + pi*m/8 + 9*pi*m**2/128 + O(m**3) + + See Also + ======== + + elliptic_f + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Elliptic_integrals + .. [2] https://functions.wolfram.com/EllipticIntegrals/EllipticK + + """ + + @classmethod + def eval(cls, m): + if m.is_zero: + return pi*S.Half + elif m is S.Half: + return 8*pi**Rational(3, 2)/gamma(Rational(-1, 4))**2 + elif m is S.One: + return S.ComplexInfinity + elif m is S.NegativeOne: + return gamma(Rational(1, 4))**2/(4*sqrt(2*pi)) + elif m in (S.Infinity, S.NegativeInfinity, I*S.Infinity, + I*S.NegativeInfinity, S.ComplexInfinity): + return S.Zero + + def fdiff(self, argindex=1): + m = self.args[0] + return (elliptic_e(m) - (1 - m)*elliptic_k(m))/(2*m*(1 - m)) + + def _eval_conjugate(self): + m = self.args[0] + if (m.is_real and (m - 1).is_positive) is False: + return self.func(m.conjugate()) + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.simplify import hyperexpand + return hyperexpand(self.rewrite(hyper)._eval_nseries(x, n=n, logx=logx)) + + def _eval_rewrite_as_hyper(self, m, **kwargs): + return pi*S.Half*hyper((S.Half, S.Half), (S.One,), m) + + def _eval_rewrite_as_meijerg(self, m, **kwargs): + return meijerg(((S.Half, S.Half), []), ((S.Zero,), (S.Zero,)), -m)/2 + + def _eval_is_zero(self): + m = self.args[0] + if m.is_infinite: + return True + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', args).name) + m = self.args[0] + return Integral(1/sqrt(1 - m*sin(t)**2), (t, 0, pi/2)) + + +class elliptic_f(DefinedFunction): + r""" + The Legendre incomplete elliptic integral of the first + kind, defined by + + .. math:: F\left(z\middle| m\right) = + \int_0^z \frac{dt}{\sqrt{1 - m \sin^2 t}} + + Explanation + =========== + + This function reduces to a complete elliptic integral of + the first kind, $K(m)$, when $z = \pi/2$. + + Note that our notation defines the incomplete elliptic integral + in terms of the parameter $m$ instead of the elliptic modulus + (eccentricity) $k$. + In this case, the parameter $m$ is defined as $m=k^2$. + + Examples + ======== + + >>> from sympy import elliptic_f, I + >>> from sympy.abc import z, m + >>> elliptic_f(z, m).series(z) + z + z**5*(3*m**2/40 - m/30) + m*z**3/6 + O(z**6) + >>> elliptic_f(3.0 + I/2, 1.0 + I) + 2.909449841483 + 1.74720545502474*I + + See Also + ======== + + elliptic_k + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Elliptic_integrals + .. [2] https://functions.wolfram.com/EllipticIntegrals/EllipticF + + """ + + @classmethod + def eval(cls, z, m): + if z.is_zero: + return S.Zero + if m.is_zero: + return z + k = 2*z/pi + if k.is_integer: + return k*elliptic_k(m) + elif m in (S.Infinity, S.NegativeInfinity): + return S.Zero + elif z.could_extract_minus_sign(): + return -elliptic_f(-z, m) + + def fdiff(self, argindex=1): + z, m = self.args + fm = sqrt(1 - m*sin(z)**2) + if argindex == 1: + return 1/fm + elif argindex == 2: + return (elliptic_e(z, m)/(2*m*(1 - m)) - elliptic_f(z, m)/(2*m) - + sin(2*z)/(4*(1 - m)*fm)) + raise ArgumentIndexError(self, argindex) + + def _eval_conjugate(self): + z, m = self.args + if (m.is_real and (m - 1).is_positive) is False: + return self.func(z.conjugate(), m.conjugate()) + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', args).name) + z, m = self.args[0], self.args[1] + return Integral(1/(sqrt(1 - m*sin(t)**2)), (t, 0, z)) + + def _eval_is_zero(self): + z, m = self.args + if z.is_zero: + return True + if m.is_extended_real and m.is_infinite: + return True + + +class elliptic_e(DefinedFunction): + r""" + Called with two arguments $z$ and $m$, evaluates the + incomplete elliptic integral of the second kind, defined by + + .. math:: E\left(z\middle| m\right) = \int_0^z \sqrt{1 - m \sin^2 t} dt + + Called with a single argument $m$, evaluates the Legendre complete + elliptic integral of the second kind + + .. math:: E(m) = E\left(\tfrac{\pi}{2}\middle| m\right) + + Explanation + =========== + + The function $E(m)$ is a single-valued function on the complex + plane with branch cut along the interval $(1, \infty)$. + + Note that our notation defines the incomplete elliptic integral + in terms of the parameter $m$ instead of the elliptic modulus + (eccentricity) $k$. + In this case, the parameter $m$ is defined as $m=k^2$. + + Examples + ======== + + >>> from sympy import elliptic_e, I + >>> from sympy.abc import z, m + >>> elliptic_e(z, m).series(z) + z + z**5*(-m**2/40 + m/30) - m*z**3/6 + O(z**6) + >>> elliptic_e(m).series(n=4) + pi/2 - pi*m/8 - 3*pi*m**2/128 - 5*pi*m**3/512 + O(m**4) + >>> elliptic_e(1 + I, 2 - I/2).n() + 1.55203744279187 + 0.290764986058437*I + >>> elliptic_e(0) + pi/2 + >>> elliptic_e(2.0 - I) + 0.991052601328069 + 0.81879421395609*I + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Elliptic_integrals + .. [2] https://functions.wolfram.com/EllipticIntegrals/EllipticE2 + .. [3] https://functions.wolfram.com/EllipticIntegrals/EllipticE + + """ + + @classmethod + def eval(cls, m, z=None): + if z is not None: + z, m = m, z + k = 2*z/pi + if m.is_zero: + return z + if z.is_zero: + return S.Zero + elif k.is_integer: + return k*elliptic_e(m) + elif m in (S.Infinity, S.NegativeInfinity): + return S.ComplexInfinity + elif z.could_extract_minus_sign(): + return -elliptic_e(-z, m) + else: + if m.is_zero: + return pi/2 + elif m is S.One: + return S.One + elif m is S.Infinity: + return I*S.Infinity + elif m is S.NegativeInfinity: + return S.Infinity + elif m is S.ComplexInfinity: + return S.ComplexInfinity + + def fdiff(self, argindex=1): + if len(self.args) == 2: + z, m = self.args + if argindex == 1: + return sqrt(1 - m*sin(z)**2) + elif argindex == 2: + return (elliptic_e(z, m) - elliptic_f(z, m))/(2*m) + else: + m = self.args[0] + if argindex == 1: + return (elliptic_e(m) - elliptic_k(m))/(2*m) + raise ArgumentIndexError(self, argindex) + + def _eval_conjugate(self): + if len(self.args) == 2: + z, m = self.args + if (m.is_real and (m - 1).is_positive) is False: + return self.func(z.conjugate(), m.conjugate()) + else: + m = self.args[0] + if (m.is_real and (m - 1).is_positive) is False: + return self.func(m.conjugate()) + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.simplify import hyperexpand + if len(self.args) == 1: + return hyperexpand(self.rewrite(hyper)._eval_nseries(x, n=n, logx=logx)) + return super()._eval_nseries(x, n=n, logx=logx) + + def _eval_rewrite_as_hyper(self, *args, **kwargs): + if len(args) == 1: + m = args[0] + return (pi/2)*hyper((Rational(-1, 2), S.Half), (S.One,), m) + + def _eval_rewrite_as_meijerg(self, *args, **kwargs): + if len(args) == 1: + m = args[0] + return -meijerg(((S.Half, Rational(3, 2)), []), \ + ((S.Zero,), (S.Zero,)), -m)/4 + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + from sympy.integrals.integrals import Integral + z, m = (pi/2, self.args[0]) if len(self.args) == 1 else self.args + t = Dummy(uniquely_named_symbol('t', args).name) + return Integral(sqrt(1 - m*sin(t)**2), (t, 0, z)) + + +class elliptic_pi(DefinedFunction): + r""" + Called with three arguments $n$, $z$ and $m$, evaluates the + Legendre incomplete elliptic integral of the third kind, defined by + + .. math:: \Pi\left(n; z\middle| m\right) = \int_0^z \frac{dt} + {\left(1 - n \sin^2 t\right) \sqrt{1 - m \sin^2 t}} + + Called with two arguments $n$ and $m$, evaluates the complete + elliptic integral of the third kind: + + .. math:: \Pi\left(n\middle| m\right) = + \Pi\left(n; \tfrac{\pi}{2}\middle| m\right) + + Explanation + =========== + + Note that our notation defines the incomplete elliptic integral + in terms of the parameter $m$ instead of the elliptic modulus + (eccentricity) $k$. + In this case, the parameter $m$ is defined as $m=k^2$. + + Examples + ======== + + >>> from sympy import elliptic_pi, I + >>> from sympy.abc import z, n, m + >>> elliptic_pi(n, z, m).series(z, n=4) + z + z**3*(m/6 + n/3) + O(z**4) + >>> elliptic_pi(0.5 + I, 1.0 - I, 1.2) + 2.50232379629182 - 0.760939574180767*I + >>> elliptic_pi(0, 0) + pi/2 + >>> elliptic_pi(1.0 - I/3, 2.0 + I) + 3.29136443417283 + 0.32555634906645*I + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Elliptic_integrals + .. [2] https://functions.wolfram.com/EllipticIntegrals/EllipticPi3 + .. [3] https://functions.wolfram.com/EllipticIntegrals/EllipticPi + + """ + + @classmethod + def eval(cls, n, m, z=None): + if z is not None: + z, m = m, z + if n.is_zero: + return elliptic_f(z, m) + elif n is S.One: + return (elliptic_f(z, m) + + (sqrt(1 - m*sin(z)**2)*tan(z) - + elliptic_e(z, m))/(1 - m)) + k = 2*z/pi + if k.is_integer: + return k*elliptic_pi(n, m) + elif m.is_zero: + return atanh(sqrt(n - 1)*tan(z))/sqrt(n - 1) + elif n == m: + return (elliptic_f(z, n) - elliptic_pi(1, z, n) + + tan(z)/sqrt(1 - n*sin(z)**2)) + elif n in (S.Infinity, S.NegativeInfinity): + return S.Zero + elif m in (S.Infinity, S.NegativeInfinity): + return S.Zero + elif z.could_extract_minus_sign(): + return -elliptic_pi(n, -z, m) + if n.is_zero: + return elliptic_f(z, m) + if m.is_extended_real and m.is_infinite or \ + n.is_extended_real and n.is_infinite: + return S.Zero + else: + if n.is_zero: + return elliptic_k(m) + elif n is S.One: + return S.ComplexInfinity + elif m.is_zero: + return pi/(2*sqrt(1 - n)) + elif m == S.One: + return S.NegativeInfinity/sign(n - 1) + elif n == m: + return elliptic_e(n)/(1 - n) + elif n in (S.Infinity, S.NegativeInfinity): + return S.Zero + elif m in (S.Infinity, S.NegativeInfinity): + return S.Zero + if n.is_zero: + return elliptic_k(m) + if m.is_extended_real and m.is_infinite or \ + n.is_extended_real and n.is_infinite: + return S.Zero + + def _eval_conjugate(self): + if len(self.args) == 3: + n, z, m = self.args + if (n.is_real and (n - 1).is_positive) is False and \ + (m.is_real and (m - 1).is_positive) is False: + return self.func(n.conjugate(), z.conjugate(), m.conjugate()) + else: + n, m = self.args + return self.func(n.conjugate(), m.conjugate()) + + def fdiff(self, argindex=1): + if len(self.args) == 3: + n, z, m = self.args + fm, fn = sqrt(1 - m*sin(z)**2), 1 - n*sin(z)**2 + if argindex == 1: + return (elliptic_e(z, m) + (m - n)*elliptic_f(z, m)/n + + (n**2 - m)*elliptic_pi(n, z, m)/n - + n*fm*sin(2*z)/(2*fn))/(2*(m - n)*(n - 1)) + elif argindex == 2: + return 1/(fm*fn) + elif argindex == 3: + return (elliptic_e(z, m)/(m - 1) + + elliptic_pi(n, z, m) - + m*sin(2*z)/(2*(m - 1)*fm))/(2*(n - m)) + else: + n, m = self.args + if argindex == 1: + return (elliptic_e(m) + (m - n)*elliptic_k(m)/n + + (n**2 - m)*elliptic_pi(n, m)/n)/(2*(m - n)*(n - 1)) + elif argindex == 2: + return (elliptic_e(m)/(m - 1) + elliptic_pi(n, m))/(2*(n - m)) + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + from sympy.integrals.integrals import Integral + if len(self.args) == 2: + n, m, z = self.args[0], self.args[1], pi/2 + else: + n, z, m = self.args + t = Dummy(uniquely_named_symbol('t', args).name) + return Integral(1/((1 - n*sin(t)**2)*sqrt(1 - m*sin(t)**2)), (t, 0, z)) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/error_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/error_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..a778127d8b80583a892285fadbb8230f72de39f9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/error_functions.py @@ -0,0 +1,2801 @@ +""" This module contains various functions that are special cases + of incomplete gamma functions. It should probably be renamed. """ + +from sympy.core import EulerGamma # Must be imported from core, not core.numbers +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.function import DefinedFunction, ArgumentIndexError, expand_mul +from sympy.core.logic import fuzzy_or +from sympy.core.numbers import I, pi, Rational, Integer +from sympy.core.relational import is_eq +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Dummy, uniquely_named_symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial, factorial2, RisingFactorial +from sympy.functions.elementary.complexes import polar_lift, re, unpolarify +from sympy.functions.elementary.integers import ceiling, floor +from sympy.functions.elementary.miscellaneous import sqrt, root +from sympy.functions.elementary.exponential import exp, log, exp_polar +from sympy.functions.elementary.hyperbolic import cosh, sinh +from sympy.functions.elementary.trigonometric import cos, sin, sinc +from sympy.functions.special.hyper import hyper, meijerg + +# TODO series expansions +# TODO see the "Note:" in Ei + +# Helper function +def real_to_real_as_real_imag(self, deep=True, **hints): + if self.args[0].is_extended_real: + if deep: + hints['complex'] = False + return (self.expand(deep, **hints), S.Zero) + else: + return (self, S.Zero) + if deep: + x, y = self.args[0].expand(deep, **hints).as_real_imag() + else: + x, y = self.args[0].as_real_imag() + re = (self.func(x + I*y) + self.func(x - I*y))/2 + im = (self.func(x + I*y) - self.func(x - I*y))/(2*I) + return (re, im) + + +############################################################################### +################################ ERROR FUNCTION ############################### +############################################################################### + + +class erf(DefinedFunction): + r""" + The Gauss error function. + + Explanation + =========== + + This function is defined as: + + .. math :: + \mathrm{erf}(x) = \frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2} \mathrm{d}t. + + Examples + ======== + + >>> from sympy import I, oo, erf + >>> from sympy.abc import z + + Several special values are known: + + >>> erf(0) + 0 + >>> erf(oo) + 1 + >>> erf(-oo) + -1 + >>> erf(I*oo) + oo*I + >>> erf(-I*oo) + -oo*I + + In general one can pull out factors of -1 and $I$ from the argument: + + >>> erf(-z) + -erf(z) + + The error function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(erf(z)) + erf(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(erf(z), z) + 2*exp(-z**2)/sqrt(pi) + + We can numerically evaluate the error function to arbitrary precision + on the whole complex plane: + + >>> erf(4).evalf(30) + 0.999999984582742099719981147840 + + >>> erf(-4*I).evalf(30) + -1296959.73071763923152794095062*I + + See Also + ======== + + erfc: Complementary error function. + erfi: Imaginary error function. + erf2: Two-argument error function. + erfinv: Inverse error function. + erfcinv: Inverse Complementary error function. + erf2inv: Inverse two-argument error function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Error_function + .. [2] https://dlmf.nist.gov/7 + .. [3] https://mathworld.wolfram.com/Erf.html + .. [4] https://functions.wolfram.com/GammaBetaErf/Erf + + """ + + unbranched = True + + def fdiff(self, argindex=1): + if argindex == 1: + return 2*exp(-self.args[0]**2)/sqrt(pi) + else: + raise ArgumentIndexError(self, argindex) + + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + + """ + return erfinv + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.One + elif arg is S.NegativeInfinity: + return S.NegativeOne + elif arg.is_zero: + return S.Zero + + if isinstance(arg, erfinv): + return arg.args[0] + + if isinstance(arg, erfcinv): + return S.One - arg.args[0] + + if arg.is_zero: + return S.Zero + + # Only happens with unevaluated erf2inv + if isinstance(arg, erf2inv) and arg.args[0].is_zero: + return arg.args[1] + + # Try to pull out factors of I + t = arg.extract_multiplicatively(I) + if t in (S.Infinity, S.NegativeInfinity): + return arg + + # Try to pull out factors of -1 + if arg.could_extract_minus_sign(): + return -cls(-arg) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + k = floor((n - 1)/S(2)) + if len(previous_terms) > 2: + return -previous_terms[-2] * x**2 * (n - 2)/(n*k) + else: + return 2*S.NegativeOne**k * x**n/(n*factorial(k)*sqrt(pi)) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def _eval_is_real(self): + if self.args[0].is_extended_real is True: + return True + # There are cases where erf(z) becomes a real number + # even if z is a complex number + + def _eval_is_imaginary(self): + if self.args[0].is_imaginary is True: + return True + + def _eval_is_finite(self): + z = self.args[0] + return fuzzy_or([z.is_finite, z.is_extended_real]) + + def _eval_is_zero(self): + if self.args[0].is_extended_real is True: + return self.args[0].is_zero + + def _eval_is_positive(self): + if self.args[0].is_extended_real is True: + return self.args[0].is_extended_positive + + def _eval_is_negative(self): + if self.args[0].is_extended_real is True: + return self.args[0].is_extended_negative + + def _eval_rewrite_as_uppergamma(self, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return sqrt(z**2)/z*(S.One - uppergamma(S.Half, z**2)/sqrt(pi)) + + def _eval_rewrite_as_fresnels(self, z, **kwargs): + arg = (S.One - I)*z/sqrt(pi) + return (S.One + I)*(fresnelc(arg) - I*fresnels(arg)) + + def _eval_rewrite_as_fresnelc(self, z, **kwargs): + arg = (S.One - I)*z/sqrt(pi) + return (S.One + I)*(fresnelc(arg) - I*fresnels(arg)) + + def _eval_rewrite_as_meijerg(self, z, **kwargs): + return z/sqrt(pi)*meijerg([S.Half], [], [0], [Rational(-1, 2)], z**2) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + return 2*z/sqrt(pi)*hyper([S.Half], [3*S.Half], -z**2) + + def _eval_rewrite_as_expint(self, z, **kwargs): + return sqrt(z**2)/z - z*expint(S.Half, z**2)/sqrt(pi) + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + from sympy.series.limits import limit + if limitvar: + lim = limit(z, limitvar, S.Infinity) + if lim is S.NegativeInfinity: + return S.NegativeOne + _erfs(-z)*exp(-z**2) + return S.One - _erfs(z)*exp(-z**2) + + def _eval_rewrite_as_erfc(self, z, **kwargs): + return S.One - erfc(z) + + def _eval_rewrite_as_erfi(self, z, **kwargs): + return -I*erfi(I*z) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.ComplexInfinity: + arg0 = arg.limit(x, 0, dir='-' if cdir == -1 else '+') + if x in arg.free_symbols and arg0.is_zero: + return 2*arg/sqrt(pi) + else: + return self.func(arg0) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + if point in [S.Infinity, S.NegativeInfinity]: + z = self.args[0] + + try: + _, ex = z.leadterm(x) + except (ValueError, NotImplementedError): + return self + + ex = -ex # as x->1/x for aseries + if ex.is_positive: + newn = ceiling(n/ex) + s = [S.NegativeOne**k * factorial2(2*k - 1) / (z**(2*k + 1) * 2**k) + for k in range(newn)] + [Order(1/z**newn, x)] + return S.One - (exp(-z**2)/sqrt(pi)) * Add(*s) + + return super(erf, self)._eval_aseries(n, args0, x, logx) + + as_real_imag = real_to_real_as_real_imag + + +class erfc(DefinedFunction): + r""" + Complementary Error Function. + + Explanation + =========== + + The function is defined as: + + .. math :: + \mathrm{erfc}(x) = \frac{2}{\sqrt{\pi}} \int_x^\infty e^{-t^2} \mathrm{d}t + + Examples + ======== + + >>> from sympy import I, oo, erfc + >>> from sympy.abc import z + + Several special values are known: + + >>> erfc(0) + 1 + >>> erfc(oo) + 0 + >>> erfc(-oo) + 2 + >>> erfc(I*oo) + -oo*I + >>> erfc(-I*oo) + oo*I + + The error function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(erfc(z)) + erfc(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(erfc(z), z) + -2*exp(-z**2)/sqrt(pi) + + It also follows + + >>> erfc(-z) + 2 - erfc(z) + + We can numerically evaluate the complementary error function to arbitrary + precision on the whole complex plane: + + >>> erfc(4).evalf(30) + 0.0000000154172579002800188521596734869 + + >>> erfc(4*I).evalf(30) + 1.0 - 1296959.73071763923152794095062*I + + See Also + ======== + + erf: Gaussian error function. + erfi: Imaginary error function. + erf2: Two-argument error function. + erfinv: Inverse error function. + erfcinv: Inverse Complementary error function. + erf2inv: Inverse two-argument error function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Error_function + .. [2] https://dlmf.nist.gov/7 + .. [3] https://mathworld.wolfram.com/Erfc.html + .. [4] https://functions.wolfram.com/GammaBetaErf/Erfc + + """ + + unbranched = True + + def fdiff(self, argindex=1): + if argindex == 1: + return -2*exp(-self.args[0]**2)/sqrt(pi) + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + + """ + return erfcinv + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is S.Infinity: + return S.Zero + elif arg.is_zero: + return S.One + + if isinstance(arg, erfinv): + return S.One - arg.args[0] + + if isinstance(arg, erfcinv): + return arg.args[0] + + if arg.is_zero: + return S.One + + # Try to pull out factors of I + t = arg.extract_multiplicatively(I) + if t in (S.Infinity, S.NegativeInfinity): + return -arg + + # Try to pull out factors of -1 + if arg.could_extract_minus_sign(): + return 2 - cls(-arg) + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n == 0: + return S.One + elif n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + k = floor((n - 1)/S(2)) + if len(previous_terms) > 2: + return -previous_terms[-2] * x**2 * (n - 2)/(n*k) + else: + return -2*S.NegativeOne**k * x**n/(n*factorial(k)*sqrt(pi)) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def _eval_is_real(self): + if self.args[0].is_extended_real is True: + return True + if self.args[0].is_imaginary is True: + return False + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + return self.rewrite(erf).rewrite("tractable", deep=True, limitvar=limitvar) + + def _eval_rewrite_as_erf(self, z, **kwargs): + return S.One - erf(z) + + def _eval_rewrite_as_erfi(self, z, **kwargs): + return S.One + I*erfi(I*z) + + def _eval_rewrite_as_fresnels(self, z, **kwargs): + arg = (S.One - I)*z/sqrt(pi) + return S.One - (S.One + I)*(fresnelc(arg) - I*fresnels(arg)) + + def _eval_rewrite_as_fresnelc(self, z, **kwargs): + arg = (S.One-I)*z/sqrt(pi) + return S.One - (S.One + I)*(fresnelc(arg) - I*fresnels(arg)) + + def _eval_rewrite_as_meijerg(self, z, **kwargs): + return S.One - z/sqrt(pi)*meijerg([S.Half], [], [0], [Rational(-1, 2)], z**2) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + return S.One - 2*z/sqrt(pi)*hyper([S.Half], [3*S.Half], -z**2) + + def _eval_rewrite_as_uppergamma(self, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return S.One - sqrt(z**2)/z*(S.One - uppergamma(S.Half, z**2)/sqrt(pi)) + + def _eval_rewrite_as_expint(self, z, **kwargs): + return S.One - sqrt(z**2)/z + z*expint(S.Half, z**2)/sqrt(pi) + + def _eval_expand_func(self, **hints): + return self.rewrite(erf) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.ComplexInfinity: + arg0 = arg.limit(x, 0, dir='-' if cdir == -1 else '+') + if arg0.is_zero: + return S.One + else: + return self.func(arg0) + + as_real_imag = real_to_real_as_real_imag + + def _eval_aseries(self, n, args0, x, logx): + return S.One - erf(*self.args)._eval_aseries(n, args0, x, logx) + + +class erfi(DefinedFunction): + r""" + Imaginary error function. + + Explanation + =========== + + The function erfi is defined as: + + .. math :: + \mathrm{erfi}(x) = \frac{2}{\sqrt{\pi}} \int_0^x e^{t^2} \mathrm{d}t + + Examples + ======== + + >>> from sympy import I, oo, erfi + >>> from sympy.abc import z + + Several special values are known: + + >>> erfi(0) + 0 + >>> erfi(oo) + oo + >>> erfi(-oo) + -oo + >>> erfi(I*oo) + I + >>> erfi(-I*oo) + -I + + In general one can pull out factors of -1 and $I$ from the argument: + + >>> erfi(-z) + -erfi(z) + + >>> from sympy import conjugate + >>> conjugate(erfi(z)) + erfi(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(erfi(z), z) + 2*exp(z**2)/sqrt(pi) + + We can numerically evaluate the imaginary error function to arbitrary + precision on the whole complex plane: + + >>> erfi(2).evalf(30) + 18.5648024145755525987042919132 + + >>> erfi(-2*I).evalf(30) + -0.995322265018952734162069256367*I + + See Also + ======== + + erf: Gaussian error function. + erfc: Complementary error function. + erf2: Two-argument error function. + erfinv: Inverse error function. + erfcinv: Inverse Complementary error function. + erf2inv: Inverse two-argument error function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Error_function + .. [2] https://mathworld.wolfram.com/Erfi.html + .. [3] https://functions.wolfram.com/GammaBetaErf/Erfi + + """ + + unbranched = True + + def fdiff(self, argindex=1): + if argindex == 1: + return 2*exp(self.args[0]**2)/sqrt(pi) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, z): + if z.is_Number: + if z is S.NaN: + return S.NaN + elif z.is_zero: + return S.Zero + elif z is S.Infinity: + return S.Infinity + + if z.is_zero: + return S.Zero + + # Try to pull out factors of -1 + if z.could_extract_minus_sign(): + return -cls(-z) + + # Try to pull out factors of I + nz = z.extract_multiplicatively(I) + if nz is not None: + if nz is S.Infinity: + return I + if isinstance(nz, erfinv): + return I*nz.args[0] + if isinstance(nz, erfcinv): + return I*(S.One - nz.args[0]) + # Only happens with unevaluated erf2inv + if isinstance(nz, erf2inv) and nz.args[0].is_zero: + return I*nz.args[1] + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0 or n % 2 == 0: + return S.Zero + else: + x = sympify(x) + k = floor((n - 1)/S(2)) + if len(previous_terms) > 2: + return previous_terms[-2] * x**2 * (n - 2)/(n*k) + else: + return 2 * x**n/(n*factorial(k)*sqrt(pi)) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + def _eval_is_zero(self): + return self.args[0].is_zero + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + return self.rewrite(erf).rewrite("tractable", deep=True, limitvar=limitvar) + + def _eval_rewrite_as_erf(self, z, **kwargs): + return -I*erf(I*z) + + def _eval_rewrite_as_erfc(self, z, **kwargs): + return I*erfc(I*z) - I + + def _eval_rewrite_as_fresnels(self, z, **kwargs): + arg = (S.One + I)*z/sqrt(pi) + return (S.One - I)*(fresnelc(arg) - I*fresnels(arg)) + + def _eval_rewrite_as_fresnelc(self, z, **kwargs): + arg = (S.One + I)*z/sqrt(pi) + return (S.One - I)*(fresnelc(arg) - I*fresnels(arg)) + + def _eval_rewrite_as_meijerg(self, z, **kwargs): + return z/sqrt(pi)*meijerg([S.Half], [], [0], [Rational(-1, 2)], -z**2) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + return 2*z/sqrt(pi)*hyper([S.Half], [3*S.Half], z**2) + + def _eval_rewrite_as_uppergamma(self, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return sqrt(-z**2)/z*(uppergamma(S.Half, -z**2)/sqrt(pi) - S.One) + + def _eval_rewrite_as_expint(self, z, **kwargs): + return sqrt(-z**2)/z - z*expint(S.Half, -z**2)/sqrt(pi) + + def _eval_expand_func(self, **hints): + return self.rewrite(erf) + + as_real_imag = real_to_real_as_real_imag + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if x in arg.free_symbols and arg0.is_zero: + return 2*arg/sqrt(pi) + elif arg0.is_finite: + return self.func(arg0) + return self.func(arg) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + if point is S.Infinity: + z = self.args[0] + s = [factorial2(2*k - 1) / (2**k * z**(2*k + 1)) + for k in range(n)] + [Order(1/z**n, x)] + return -I + (exp(z**2)/sqrt(pi)) * Add(*s) + + return super(erfi, self)._eval_aseries(n, args0, x, logx) + + +class erf2(DefinedFunction): + r""" + Two-argument error function. + + Explanation + =========== + + This function is defined as: + + .. math :: + \mathrm{erf2}(x, y) = \frac{2}{\sqrt{\pi}} \int_x^y e^{-t^2} \mathrm{d}t + + Examples + ======== + + >>> from sympy import oo, erf2 + >>> from sympy.abc import x, y + + Several special values are known: + + >>> erf2(0, 0) + 0 + >>> erf2(x, x) + 0 + >>> erf2(x, oo) + 1 - erf(x) + >>> erf2(x, -oo) + -erf(x) - 1 + >>> erf2(oo, y) + erf(y) - 1 + >>> erf2(-oo, y) + erf(y) + 1 + + In general one can pull out factors of -1: + + >>> erf2(-x, -y) + -erf2(x, y) + + The error function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(erf2(x, y)) + erf2(conjugate(x), conjugate(y)) + + Differentiation with respect to $x$, $y$ is supported: + + >>> from sympy import diff + >>> diff(erf2(x, y), x) + -2*exp(-x**2)/sqrt(pi) + >>> diff(erf2(x, y), y) + 2*exp(-y**2)/sqrt(pi) + + See Also + ======== + + erf: Gaussian error function. + erfc: Complementary error function. + erfi: Imaginary error function. + erfinv: Inverse error function. + erfcinv: Inverse Complementary error function. + erf2inv: Inverse two-argument error function. + + References + ========== + + .. [1] https://functions.wolfram.com/GammaBetaErf/Erf2/ + + """ + + + def fdiff(self, argindex): + x, y = self.args + if argindex == 1: + return -2*exp(-x**2)/sqrt(pi) + elif argindex == 2: + return 2*exp(-y**2)/sqrt(pi) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, x, y): + chk = (S.Infinity, S.NegativeInfinity, S.Zero) + if x is S.NaN or y is S.NaN: + return S.NaN + elif x == y: + return S.Zero + elif x in chk or y in chk: + return erf(y) - erf(x) + + if isinstance(y, erf2inv) and y.args[0] == x: + return y.args[1] + + if x.is_zero or y.is_zero or x.is_extended_real and x.is_infinite or \ + y.is_extended_real and y.is_infinite: + return erf(y) - erf(x) + + #Try to pull out -1 factor + sign_x = x.could_extract_minus_sign() + sign_y = y.could_extract_minus_sign() + if (sign_x and sign_y): + return -cls(-x, -y) + elif (sign_x or sign_y): + return erf(y)-erf(x) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate(), self.args[1].conjugate()) + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real and self.args[1].is_extended_real + + def _eval_rewrite_as_erf(self, x, y, **kwargs): + return erf(y) - erf(x) + + def _eval_rewrite_as_erfc(self, x, y, **kwargs): + return erfc(x) - erfc(y) + + def _eval_rewrite_as_erfi(self, x, y, **kwargs): + return I*(erfi(I*x)-erfi(I*y)) + + def _eval_rewrite_as_fresnels(self, x, y, **kwargs): + return erf(y).rewrite(fresnels) - erf(x).rewrite(fresnels) + + def _eval_rewrite_as_fresnelc(self, x, y, **kwargs): + return erf(y).rewrite(fresnelc) - erf(x).rewrite(fresnelc) + + def _eval_rewrite_as_meijerg(self, x, y, **kwargs): + return erf(y).rewrite(meijerg) - erf(x).rewrite(meijerg) + + def _eval_rewrite_as_hyper(self, x, y, **kwargs): + return erf(y).rewrite(hyper) - erf(x).rewrite(hyper) + + def _eval_rewrite_as_uppergamma(self, x, y, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return (sqrt(y**2)/y*(S.One - uppergamma(S.Half, y**2)/sqrt(pi)) - + sqrt(x**2)/x*(S.One - uppergamma(S.Half, x**2)/sqrt(pi))) + + def _eval_rewrite_as_expint(self, x, y, **kwargs): + return erf(y).rewrite(expint) - erf(x).rewrite(expint) + + def _eval_expand_func(self, **hints): + return self.rewrite(erf) + + def _eval_is_zero(self): + return is_eq(*self.args) + +class erfinv(DefinedFunction): + r""" + Inverse Error Function. The erfinv function is defined as: + + .. math :: + \mathrm{erf}(x) = y \quad \Rightarrow \quad \mathrm{erfinv}(y) = x + + Examples + ======== + + >>> from sympy import erfinv + >>> from sympy.abc import x + + Several special values are known: + + >>> erfinv(0) + 0 + >>> erfinv(1) + oo + + Differentiation with respect to $x$ is supported: + + >>> from sympy import diff + >>> diff(erfinv(x), x) + sqrt(pi)*exp(erfinv(x)**2)/2 + + We can numerically evaluate the inverse error function to arbitrary + precision on [-1, 1]: + + >>> erfinv(0.2).evalf(30) + 0.179143454621291692285822705344 + + See Also + ======== + + erf: Gaussian error function. + erfc: Complementary error function. + erfi: Imaginary error function. + erf2: Two-argument error function. + erfcinv: Inverse Complementary error function. + erf2inv: Inverse two-argument error function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Error_function#Inverse_functions + .. [2] https://functions.wolfram.com/GammaBetaErf/InverseErf/ + + """ + + + def fdiff(self, argindex =1): + if argindex == 1: + return sqrt(pi)*exp(self.func(self.args[0])**2)*S.Half + else : + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + + """ + return erf + + @classmethod + def eval(cls, z): + if z is S.NaN: + return S.NaN + elif z is S.NegativeOne: + return S.NegativeInfinity + elif z.is_zero: + return S.Zero + elif z is S.One: + return S.Infinity + + if isinstance(z, erf) and z.args[0].is_extended_real: + return z.args[0] + + if z.is_zero: + return S.Zero + + # Try to pull out factors of -1 + nz = z.extract_multiplicatively(-1) + if nz is not None and (isinstance(nz, erf) and (nz.args[0]).is_extended_real): + return -nz.args[0] + + def _eval_rewrite_as_erfcinv(self, z, **kwargs): + return erfcinv(1-z) + + def _eval_is_zero(self): + return self.args[0].is_zero + + +class erfcinv (DefinedFunction): + r""" + Inverse Complementary Error Function. The erfcinv function is defined as: + + .. math :: + \mathrm{erfc}(x) = y \quad \Rightarrow \quad \mathrm{erfcinv}(y) = x + + Examples + ======== + + >>> from sympy import erfcinv + >>> from sympy.abc import x + + Several special values are known: + + >>> erfcinv(1) + 0 + >>> erfcinv(0) + oo + + Differentiation with respect to $x$ is supported: + + >>> from sympy import diff + >>> diff(erfcinv(x), x) + -sqrt(pi)*exp(erfcinv(x)**2)/2 + + See Also + ======== + + erf: Gaussian error function. + erfc: Complementary error function. + erfi: Imaginary error function. + erf2: Two-argument error function. + erfinv: Inverse error function. + erf2inv: Inverse two-argument error function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Error_function#Inverse_functions + .. [2] https://functions.wolfram.com/GammaBetaErf/InverseErfc/ + + """ + + + def fdiff(self, argindex =1): + if argindex == 1: + return -sqrt(pi)*exp(self.func(self.args[0])**2)*S.Half + else: + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """ + Returns the inverse of this function. + + """ + return erfc + + @classmethod + def eval(cls, z): + if z is S.NaN: + return S.NaN + elif z.is_zero: + return S.Infinity + elif z is S.One: + return S.Zero + elif z == 2: + return S.NegativeInfinity + + if z.is_zero: + return S.Infinity + + def _eval_rewrite_as_erfinv(self, z, **kwargs): + return erfinv(1-z) + + def _eval_is_zero(self): + return (self.args[0] - 1).is_zero + + def _eval_is_infinite(self): + z = self.args[0] + return fuzzy_or([z.is_zero, is_eq(z, Integer(2))]) + + +class erf2inv(DefinedFunction): + r""" + Two-argument Inverse error function. The erf2inv function is defined as: + + .. math :: + \mathrm{erf2}(x, w) = y \quad \Rightarrow \quad \mathrm{erf2inv}(x, y) = w + + Examples + ======== + + >>> from sympy import erf2inv, oo + >>> from sympy.abc import x, y + + Several special values are known: + + >>> erf2inv(0, 0) + 0 + >>> erf2inv(1, 0) + 1 + >>> erf2inv(0, 1) + oo + >>> erf2inv(0, y) + erfinv(y) + >>> erf2inv(oo, y) + erfcinv(-y) + + Differentiation with respect to $x$ and $y$ is supported: + + >>> from sympy import diff + >>> diff(erf2inv(x, y), x) + exp(-x**2 + erf2inv(x, y)**2) + >>> diff(erf2inv(x, y), y) + sqrt(pi)*exp(erf2inv(x, y)**2)/2 + + See Also + ======== + + erf: Gaussian error function. + erfc: Complementary error function. + erfi: Imaginary error function. + erf2: Two-argument error function. + erfinv: Inverse error function. + erfcinv: Inverse complementary error function. + + References + ========== + + .. [1] https://functions.wolfram.com/GammaBetaErf/InverseErf2/ + + """ + + + def fdiff(self, argindex): + x, y = self.args + if argindex == 1: + return exp(self.func(x,y)**2-x**2) + elif argindex == 2: + return sqrt(pi)*S.Half*exp(self.func(x,y)**2) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, x, y): + if x is S.NaN or y is S.NaN: + return S.NaN + elif x.is_zero and y.is_zero: + return S.Zero + elif x.is_zero and y is S.One: + return S.Infinity + elif x is S.One and y.is_zero: + return S.One + elif x.is_zero: + return erfinv(y) + elif x is S.Infinity: + return erfcinv(-y) + elif y.is_zero: + return x + elif y is S.Infinity: + return erfinv(x) + + if x.is_zero: + if y.is_zero: + return S.Zero + else: + return erfinv(y) + if y.is_zero: + return x + + def _eval_is_zero(self): + x, y = self.args + if x.is_zero and y.is_zero: + return True + +############################################################################### +#################### EXPONENTIAL INTEGRALS #################################### +############################################################################### + +class Ei(DefinedFunction): + r""" + The classical exponential integral. + + Explanation + =========== + + For use in SymPy, this function is defined as + + .. math:: \operatorname{Ei}(x) = \sum_{n=1}^\infty \frac{x^n}{n\, n!} + + \log(x) + \gamma, + + where $\gamma$ is the Euler-Mascheroni constant. + + If $x$ is a polar number, this defines an analytic function on the + Riemann surface of the logarithm. Otherwise this defines an analytic + function in the cut plane $\mathbb{C} \setminus (-\infty, 0]$. + + **Background** + + The name exponential integral comes from the following statement: + + .. math:: \operatorname{Ei}(x) = \int_{-\infty}^x \frac{e^t}{t} \mathrm{d}t + + If the integral is interpreted as a Cauchy principal value, this statement + holds for $x > 0$ and $\operatorname{Ei}(x)$ as defined above. + + Examples + ======== + + >>> from sympy import Ei, polar_lift, exp_polar, I, pi + >>> from sympy.abc import x + + >>> Ei(-1) + Ei(-1) + + This yields a real value: + + >>> Ei(-1).n(chop=True) + -0.219383934395520 + + On the other hand the analytic continuation is not real: + + >>> Ei(polar_lift(-1)).n(chop=True) + -0.21938393439552 + 3.14159265358979*I + + The exponential integral has a logarithmic branch point at the origin: + + >>> Ei(x*exp_polar(2*I*pi)) + Ei(x) + 2*I*pi + + Differentiation is supported: + + >>> Ei(x).diff(x) + exp(x)/x + + The exponential integral is related to many other special functions. + For example: + + >>> from sympy import expint, Shi + >>> Ei(x).rewrite(expint) + -expint(1, x*exp_polar(I*pi)) - I*pi + >>> Ei(x).rewrite(Shi) + Chi(x) + Shi(x) + + See Also + ======== + + expint: Generalised exponential integral. + E1: Special case of the generalised exponential integral. + li: Logarithmic integral. + Li: Offset logarithmic integral. + Si: Sine integral. + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + uppergamma: Upper incomplete gamma function. + + References + ========== + + .. [1] https://dlmf.nist.gov/6.6 + .. [2] https://en.wikipedia.org/wiki/Exponential_integral + .. [3] Abramowitz & Stegun, section 5: https://web.archive.org/web/20201128173312/http://people.math.sfu.ca/~cbm/aands/page_228.htm + + """ + + + @classmethod + def eval(cls, z): + if z.is_zero: + return S.NegativeInfinity + elif z is S.Infinity: + return S.Infinity + elif z is S.NegativeInfinity: + return S.Zero + + if z.is_zero: + return S.NegativeInfinity + + nz, n = z.extract_branch_factor() + if n: + return Ei(nz) + 2*I*pi*n + + def fdiff(self, argindex=1): + arg = unpolarify(self.args[0]) + if argindex == 1: + return exp(arg)/arg + else: + raise ArgumentIndexError(self, argindex) + + def _eval_evalf(self, prec): + if (self.args[0]/polar_lift(-1)).is_positive: + return super()._eval_evalf(prec) + (I*pi)._eval_evalf(prec) + return super()._eval_evalf(prec) + + def _eval_rewrite_as_uppergamma(self, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + # XXX this does not currently work usefully because uppergamma + # immediately turns into expint + return -uppergamma(0, polar_lift(-1)*z) - I*pi + + def _eval_rewrite_as_expint(self, z, **kwargs): + return -expint(1, polar_lift(-1)*z) - I*pi + + def _eval_rewrite_as_li(self, z, **kwargs): + if isinstance(z, log): + return li(z.args[0]) + # TODO: + # Actually it only holds that: + # Ei(z) = li(exp(z)) + # for -pi < imag(z) <= pi + return li(exp(z)) + + def _eval_rewrite_as_Si(self, z, **kwargs): + if z.is_negative: + return Shi(z) + Chi(z) - I*pi + else: + return Shi(z) + Chi(z) + _eval_rewrite_as_Ci = _eval_rewrite_as_Si + _eval_rewrite_as_Chi = _eval_rewrite_as_Si + _eval_rewrite_as_Shi = _eval_rewrite_as_Si + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + return exp(z) * _eis(z) + + def _eval_rewrite_as_Integral(self, z, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [z]).name) + return Integral(S.Exp1**t/t, (t, S.NegativeInfinity, z)) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy import re + x0 = self.args[0].limit(x, 0) + arg = self.args[0].as_leading_term(x, cdir=cdir) + cdir = arg.dir(x, cdir) + if x0.is_zero: + c, e = arg.as_coeff_exponent(x) + logx = log(x) if logx is None else logx + return log(c) + e*logx + EulerGamma - ( + I*pi if re(cdir).is_negative else S.Zero) + return super()._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + x0 = self.args[0].limit(x, 0) + if x0.is_zero: + f = self._eval_rewrite_as_Si(*self.args) + return f._eval_nseries(x, n, logx) + return super()._eval_nseries(x, n, logx) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + if point in (S.Infinity, S.NegativeInfinity): + z = self.args[0] + s = [factorial(k) / (z)**k for k in range(n)] + \ + [Order(1/z**n, x)] + return (exp(z)/z) * Add(*s) + + return super(Ei, self)._eval_aseries(n, args0, x, logx) + + +class expint(DefinedFunction): + r""" + Generalized exponential integral. + + Explanation + =========== + + This function is defined as + + .. math:: \operatorname{E}_\nu(z) = z^{\nu - 1} \Gamma(1 - \nu, z), + + where $\Gamma(1 - \nu, z)$ is the upper incomplete gamma function + (``uppergamma``). + + Hence for $z$ with positive real part we have + + .. math:: \operatorname{E}_\nu(z) + = \int_1^\infty \frac{e^{-zt}}{t^\nu} \mathrm{d}t, + + which explains the name. + + The representation as an incomplete gamma function provides an analytic + continuation for $\operatorname{E}_\nu(z)$. If $\nu$ is a + non-positive integer, the exponential integral is thus an unbranched + function of $z$, otherwise there is a branch point at the origin. + Refer to the incomplete gamma function documentation for details of the + branching behavior. + + Examples + ======== + + >>> from sympy import expint, S + >>> from sympy.abc import nu, z + + Differentiation is supported. Differentiation with respect to $z$ further + explains the name: for integral orders, the exponential integral is an + iterated integral of the exponential function. + + >>> expint(nu, z).diff(z) + -expint(nu - 1, z) + + Differentiation with respect to $\nu$ has no classical expression: + + >>> expint(nu, z).diff(nu) + -z**(nu - 1)*meijerg(((), (1, 1)), ((0, 0, 1 - nu), ()), z) + + At non-postive integer orders, the exponential integral reduces to the + exponential function: + + >>> expint(0, z) + exp(-z)/z + >>> expint(-1, z) + exp(-z)/z + exp(-z)/z**2 + + At half-integers it reduces to error functions: + + >>> expint(S(1)/2, z) + sqrt(pi)*erfc(sqrt(z))/sqrt(z) + + At positive integer orders it can be rewritten in terms of exponentials + and ``expint(1, z)``. Use ``expand_func()`` to do this: + + >>> from sympy import expand_func + >>> expand_func(expint(5, z)) + z**4*expint(1, z)/24 + (-z**3 + z**2 - 2*z + 6)*exp(-z)/24 + + The generalised exponential integral is essentially equivalent to the + incomplete gamma function: + + >>> from sympy import uppergamma + >>> expint(nu, z).rewrite(uppergamma) + z**(nu - 1)*uppergamma(1 - nu, z) + + As such it is branched at the origin: + + >>> from sympy import exp_polar, pi, I + >>> expint(4, z*exp_polar(2*pi*I)) + I*pi*z**3/3 + expint(4, z) + >>> expint(nu, z*exp_polar(2*pi*I)) + z**(nu - 1)*(exp(2*I*pi*nu) - 1)*gamma(1 - nu) + expint(nu, z) + + See Also + ======== + + Ei: Another related function called exponential integral. + E1: The classical case, returns expint(1, z). + li: Logarithmic integral. + Li: Offset logarithmic integral. + Si: Sine integral. + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + uppergamma + + References + ========== + + .. [1] https://dlmf.nist.gov/8.19 + .. [2] https://functions.wolfram.com/GammaBetaErf/ExpIntegralE/ + .. [3] https://en.wikipedia.org/wiki/Exponential_integral + + """ + + + @classmethod + def eval(cls, nu, z): + from sympy.functions.special.gamma_functions import (gamma, uppergamma) + nu2 = unpolarify(nu) + if nu != nu2: + return expint(nu2, z) + if nu.is_Integer and nu <= 0 or (not nu.is_Integer and (2*nu).is_Integer): + return unpolarify(expand_mul(z**(nu - 1)*uppergamma(1 - nu, z))) + + # Extract branching information. This can be deduced from what is + # explained in lowergamma.eval(). + z, n = z.extract_branch_factor() + if n is S.Zero: + return + if nu.is_integer: + if not nu > 0: + return + return expint(nu, z) \ + - 2*pi*I*n*S.NegativeOne**(nu - 1)/factorial(nu - 1)*unpolarify(z)**(nu - 1) + else: + return (exp(2*I*pi*nu*n) - 1)*z**(nu - 1)*gamma(1 - nu) + expint(nu, z) + + def fdiff(self, argindex): + nu, z = self.args + if argindex == 1: + return -z**(nu - 1)*meijerg([], [1, 1], [0, 0, 1 - nu], [], z) + elif argindex == 2: + return -expint(nu - 1, z) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_uppergamma(self, nu, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return z**(nu - 1)*uppergamma(1 - nu, z) + + def _eval_rewrite_as_Ei(self, nu, z, **kwargs): + if nu == 1: + return -Ei(z*exp_polar(-I*pi)) - I*pi + elif nu.is_Integer and nu > 1: + # DLMF, 8.19.7 + x = -unpolarify(z) + return x**(nu - 1)/factorial(nu - 1)*E1(z).rewrite(Ei) + \ + exp(x)/factorial(nu - 1) * \ + Add(*[factorial(nu - k - 2)*x**k for k in range(nu - 1)]) + else: + return self + + def _eval_expand_func(self, **hints): + return self.rewrite(Ei).rewrite(expint, **hints) + + def _eval_rewrite_as_Si(self, nu, z, **kwargs): + if nu != 1: + return self + return Shi(z) - Chi(z) + _eval_rewrite_as_Ci = _eval_rewrite_as_Si + _eval_rewrite_as_Chi = _eval_rewrite_as_Si + _eval_rewrite_as_Shi = _eval_rewrite_as_Si + + def _eval_nseries(self, x, n, logx, cdir=0): + if not self.args[0].has(x): + nu = self.args[0] + if nu == 1: + f = self._eval_rewrite_as_Si(*self.args) + return f._eval_nseries(x, n, logx) + elif nu.is_Integer and nu > 1: + f = self._eval_rewrite_as_Ei(*self.args) + return f._eval_nseries(x, n, logx) + return super()._eval_nseries(x, n, logx) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[1] + nu = self.args[0] + + if point is S.Infinity: + z = self.args[1] + s = [S.NegativeOne**k * RisingFactorial(nu, k) / z**k for k in range(n)] + [Order(1/z**n, x)] + return (exp(-z)/z) * Add(*s) + + return super(expint, self)._eval_aseries(n, args0, x, logx) + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + from sympy.integrals.integrals import Integral + n, x = self.args + t = Dummy(uniquely_named_symbol('t', args).name) + return Integral(t**-n * exp(-t*x), (t, 1, S.Infinity)) + + +def E1(z): + """ + Classical case of the generalized exponential integral. + + Explanation + =========== + + This is equivalent to ``expint(1, z)``. + + Examples + ======== + + >>> from sympy import E1 + >>> E1(0) + expint(1, 0) + + >>> E1(5) + expint(1, 5) + + See Also + ======== + + Ei: Exponential integral. + expint: Generalised exponential integral. + li: Logarithmic integral. + Li: Offset logarithmic integral. + Si: Sine integral. + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + + """ + return expint(1, z) + + +class li(DefinedFunction): + r""" + The classical logarithmic integral. + + Explanation + =========== + + For use in SymPy, this function is defined as + + .. math:: \operatorname{li}(x) = \int_0^x \frac{1}{\log(t)} \mathrm{d}t \,. + + Examples + ======== + + >>> from sympy import I, oo, li + >>> from sympy.abc import z + + Several special values are known: + + >>> li(0) + 0 + >>> li(1) + -oo + >>> li(oo) + oo + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(li(z), z) + 1/log(z) + + Defining the ``li`` function via an integral: + >>> from sympy import integrate + >>> integrate(li(z)) + z*li(z) - Ei(2*log(z)) + + >>> integrate(li(z),z) + z*li(z) - Ei(2*log(z)) + + + The logarithmic integral can also be defined in terms of ``Ei``: + + >>> from sympy import Ei + >>> li(z).rewrite(Ei) + Ei(log(z)) + >>> diff(li(z).rewrite(Ei), z) + 1/log(z) + + We can numerically evaluate the logarithmic integral to arbitrary precision + on the whole complex plane (except the singular points): + + >>> li(2).evalf(30) + 1.04516378011749278484458888919 + + >>> li(2*I).evalf(30) + 1.0652795784357498247001125598 + 3.08346052231061726610939702133*I + + We can even compute Soldner's constant by the help of mpmath: + + >>> from mpmath import findroot + >>> findroot(li, 2) + 1.45136923488338 + + Further transformations include rewriting ``li`` in terms of + the trigonometric integrals ``Si``, ``Ci``, ``Shi`` and ``Chi``: + + >>> from sympy import Si, Ci, Shi, Chi + >>> li(z).rewrite(Si) + -log(I*log(z)) - log(1/log(z))/2 + log(log(z))/2 + Ci(I*log(z)) + Shi(log(z)) + >>> li(z).rewrite(Ci) + -log(I*log(z)) - log(1/log(z))/2 + log(log(z))/2 + Ci(I*log(z)) + Shi(log(z)) + >>> li(z).rewrite(Shi) + -log(1/log(z))/2 + log(log(z))/2 + Chi(log(z)) - Shi(log(z)) + >>> li(z).rewrite(Chi) + -log(1/log(z))/2 + log(log(z))/2 + Chi(log(z)) - Shi(log(z)) + + See Also + ======== + + Li: Offset logarithmic integral. + Ei: Exponential integral. + expint: Generalised exponential integral. + E1: Special case of the generalised exponential integral. + Si: Sine integral. + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Logarithmic_integral + .. [2] https://mathworld.wolfram.com/LogarithmicIntegral.html + .. [3] https://dlmf.nist.gov/6 + .. [4] https://mathworld.wolfram.com/SoldnersConstant.html + + """ + + + @classmethod + def eval(cls, z): + if z.is_zero: + return S.Zero + elif z is S.One: + return S.NegativeInfinity + elif z is S.Infinity: + return S.Infinity + if z.is_zero: + return S.Zero + + def fdiff(self, argindex=1): + arg = self.args[0] + if argindex == 1: + return S.One / log(arg) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_conjugate(self): + z = self.args[0] + # Exclude values on the branch cut (-oo, 0) + if not z.is_extended_negative: + return self.func(z.conjugate()) + + def _eval_rewrite_as_Li(self, z, **kwargs): + return Li(z) + li(2) + + def _eval_rewrite_as_Ei(self, z, **kwargs): + return Ei(log(z)) + + def _eval_rewrite_as_uppergamma(self, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return (-uppergamma(0, -log(z)) + + S.Half*(log(log(z)) - log(S.One/log(z))) - log(-log(z))) + + def _eval_rewrite_as_Si(self, z, **kwargs): + return (Ci(I*log(z)) - I*Si(I*log(z)) - + S.Half*(log(S.One/log(z)) - log(log(z))) - log(I*log(z))) + + _eval_rewrite_as_Ci = _eval_rewrite_as_Si + + def _eval_rewrite_as_Shi(self, z, **kwargs): + return (Chi(log(z)) - Shi(log(z)) - S.Half*(log(S.One/log(z)) - log(log(z)))) + + _eval_rewrite_as_Chi = _eval_rewrite_as_Shi + + def _eval_rewrite_as_hyper(self, z, **kwargs): + return (log(z)*hyper((1, 1), (2, 2), log(z)) + + S.Half*(log(log(z)) - log(S.One/log(z))) + EulerGamma) + + def _eval_rewrite_as_meijerg(self, z, **kwargs): + return (-log(-log(z)) - S.Half*(log(S.One/log(z)) - log(log(z))) + - meijerg(((), (1,)), ((0, 0), ()), -log(z))) + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + return z * _eis(log(z)) + + def _eval_nseries(self, x, n, logx, cdir=0): + z = self.args[0] + s = [(log(z))**k / (factorial(k) * k) for k in range(1, n)] + return EulerGamma + log(log(z)) + Add(*s) + + def _eval_is_zero(self): + z = self.args[0] + if z.is_zero: + return True + +class Li(DefinedFunction): + r""" + The offset logarithmic integral. + + Explanation + =========== + + For use in SymPy, this function is defined as + + .. math:: \operatorname{Li}(x) = \operatorname{li}(x) - \operatorname{li}(2) + + Examples + ======== + + >>> from sympy import Li + >>> from sympy.abc import z + + The following special value is known: + + >>> Li(2) + 0 + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(Li(z), z) + 1/log(z) + + The shifted logarithmic integral can be written in terms of $li(z)$: + + >>> from sympy import li + >>> Li(z).rewrite(li) + li(z) - li(2) + + We can numerically evaluate the logarithmic integral to arbitrary precision + on the whole complex plane (except the singular points): + + >>> Li(2).evalf(30) + 0 + + >>> Li(4).evalf(30) + 1.92242131492155809316615998938 + + See Also + ======== + + li: Logarithmic integral. + Ei: Exponential integral. + expint: Generalised exponential integral. + E1: Special case of the generalised exponential integral. + Si: Sine integral. + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Logarithmic_integral + .. [2] https://mathworld.wolfram.com/LogarithmicIntegral.html + .. [3] https://dlmf.nist.gov/6 + + """ + + + @classmethod + def eval(cls, z): + if z is S.Infinity: + return S.Infinity + elif z == S(2): + return S.Zero + + def fdiff(self, argindex=1): + arg = self.args[0] + if argindex == 1: + return S.One / log(arg) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_evalf(self, prec): + return self.rewrite(li).evalf(prec) + + def _eval_rewrite_as_li(self, z, **kwargs): + return li(z) - li(2) + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + return self.rewrite(li).rewrite("tractable", deep=True) + + def _eval_nseries(self, x, n, logx, cdir=0): + f = self._eval_rewrite_as_li(*self.args) + return f._eval_nseries(x, n, logx) + +############################################################################### +#################### TRIGONOMETRIC INTEGRALS ################################## +############################################################################### + +class TrigonometricIntegral(DefinedFunction): + """ Base class for trigonometric integrals. """ + + + @classmethod + def eval(cls, z): + if z is S.Zero: + return cls._atzero + elif z is S.Infinity: + return cls._atinf() + elif z is S.NegativeInfinity: + return cls._atneginf() + + if z.is_zero: + return cls._atzero + + nz = z.extract_multiplicatively(polar_lift(I)) + if nz is None and cls._trigfunc(0) == 0: + nz = z.extract_multiplicatively(I) + if nz is not None: + return cls._Ifactor(nz, 1) + nz = z.extract_multiplicatively(polar_lift(-I)) + if nz is not None: + return cls._Ifactor(nz, -1) + + nz = z.extract_multiplicatively(polar_lift(-1)) + if nz is None and cls._trigfunc(0) == 0: + nz = z.extract_multiplicatively(-1) + if nz is not None: + return cls._minusfactor(nz) + + nz, n = z.extract_branch_factor() + if n == 0 and nz == z: + return + return 2*pi*I*n*cls._trigfunc(0) + cls(nz) + + def fdiff(self, argindex=1): + arg = unpolarify(self.args[0]) + if argindex == 1: + return self._trigfunc(arg)/arg + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Ei(self, z, **kwargs): + return self._eval_rewrite_as_expint(z).rewrite(Ei) + + def _eval_rewrite_as_uppergamma(self, z, **kwargs): + from sympy.functions.special.gamma_functions import uppergamma + return self._eval_rewrite_as_expint(z).rewrite(uppergamma) + + def _eval_nseries(self, x, n, logx, cdir=0): + # NOTE this is fairly inefficient + if self.args[0].subs(x, 0) != 0: + return super()._eval_nseries(x, n, logx) + baseseries = self._trigfunc(x)._eval_nseries(x, n, logx) + if self._trigfunc(0) != 0: + baseseries -= 1 + baseseries = baseseries.replace(Pow, lambda t, n: t**n/n, simultaneous=False) + if self._trigfunc(0) != 0: + baseseries += EulerGamma + log(x) + return baseseries.subs(x, self.args[0])._eval_nseries(x, n, logx) + + +class Si(TrigonometricIntegral): + r""" + Sine integral. + + Explanation + =========== + + This function is defined by + + .. math:: \operatorname{Si}(z) = \int_0^z \frac{\sin{t}}{t} \mathrm{d}t. + + It is an entire function. + + Examples + ======== + + >>> from sympy import Si + >>> from sympy.abc import z + + The sine integral is an antiderivative of $sin(z)/z$: + + >>> Si(z).diff(z) + sin(z)/z + + It is unbranched: + + >>> from sympy import exp_polar, I, pi + >>> Si(z*exp_polar(2*I*pi)) + Si(z) + + Sine integral behaves much like ordinary sine under multiplication by ``I``: + + >>> Si(I*z) + I*Shi(z) + >>> Si(-z) + -Si(z) + + It can also be expressed in terms of exponential integrals, but beware + that the latter is branched: + + >>> from sympy import expint + >>> Si(z).rewrite(expint) + -I*(-expint(1, z*exp_polar(-I*pi/2))/2 + + expint(1, z*exp_polar(I*pi/2))/2) + pi/2 + + It can be rewritten in the form of sinc function (by definition): + + >>> from sympy import sinc + >>> Si(z).rewrite(sinc) + Integral(sinc(_t), (_t, 0, z)) + + See Also + ======== + + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + Ei: Exponential integral. + expint: Generalised exponential integral. + sinc: unnormalized sinc function + E1: Special case of the generalised exponential integral. + li: Logarithmic integral. + Li: Offset logarithmic integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_integral + + """ + + _trigfunc = sin + _atzero = S.Zero + + @classmethod + def _atinf(cls): + return pi*S.Half + + @classmethod + def _atneginf(cls): + return -pi*S.Half + + @classmethod + def _minusfactor(cls, z): + return -Si(z) + + @classmethod + def _Ifactor(cls, z, sign): + return I*Shi(z)*sign + + def _eval_rewrite_as_expint(self, z, **kwargs): + # XXX should we polarify z? + return pi/2 + (E1(polar_lift(I)*z) - E1(polar_lift(-I)*z))/2/I + + def _eval_rewrite_as_Integral(self, z, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [z]).name) + return Integral(sinc(t), (t, 0, z)) + + _eval_rewrite_as_sinc = _eval_rewrite_as_Integral + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if arg0.is_zero: + return arg + elif not arg0.is_infinite: + return self.func(arg0) + else: + return self + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + # Expansion at oo + if point is S.Infinity: + z = self.args[0] + p = [S.NegativeOne**k * factorial(2*k) / z**(2*k + 1) + for k in range(n//2 + 1)] + [Order(1/z**n, x)] + q = [S.NegativeOne**k * factorial(2*k + 1) / z**(2*(k + 1)) + for k in range(n//2)] + [Order(1/z**n, x)] + return pi/2 - cos(z)*Add(*p) - sin(z)*Add(*q) + + # All other points are not handled + return super(Si, self)._eval_aseries(n, args0, x, logx) + + def _eval_is_zero(self): + z = self.args[0] + if z.is_zero: + return True + + +class Ci(TrigonometricIntegral): + r""" + Cosine integral. + + Explanation + =========== + + This function is defined for positive $x$ by + + .. math:: \operatorname{Ci}(x) = \gamma + \log{x} + + \int_0^x \frac{\cos{t} - 1}{t} \mathrm{d}t + = -\int_x^\infty \frac{\cos{t}}{t} \mathrm{d}t, + + where $\gamma$ is the Euler-Mascheroni constant. + + We have + + .. math:: \operatorname{Ci}(z) = + -\frac{\operatorname{E}_1\left(e^{i\pi/2} z\right) + + \operatorname{E}_1\left(e^{-i \pi/2} z\right)}{2} + + which holds for all polar $z$ and thus provides an analytic + continuation to the Riemann surface of the logarithm. + + The formula also holds as stated + for $z \in \mathbb{C}$ with $\Re(z) > 0$. + By lifting to the principal branch, we obtain an analytic function on the + cut complex plane. + + Examples + ======== + + >>> from sympy import Ci + >>> from sympy.abc import z + + The cosine integral is a primitive of $\cos(z)/z$: + + >>> Ci(z).diff(z) + cos(z)/z + + It has a logarithmic branch point at the origin: + + >>> from sympy import exp_polar, I, pi + >>> Ci(z*exp_polar(2*I*pi)) + Ci(z) + 2*I*pi + + The cosine integral behaves somewhat like ordinary $\cos$ under + multiplication by $i$: + + >>> from sympy import polar_lift + >>> Ci(polar_lift(I)*z) + Chi(z) + I*pi/2 + >>> Ci(polar_lift(-1)*z) + Ci(z) + I*pi + + It can also be expressed in terms of exponential integrals: + + >>> from sympy import expint + >>> Ci(z).rewrite(expint) + -expint(1, z*exp_polar(-I*pi/2))/2 - expint(1, z*exp_polar(I*pi/2))/2 + + See Also + ======== + + Si: Sine integral. + Shi: Hyperbolic sine integral. + Chi: Hyperbolic cosine integral. + Ei: Exponential integral. + expint: Generalised exponential integral. + E1: Special case of the generalised exponential integral. + li: Logarithmic integral. + Li: Offset logarithmic integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_integral + + """ + + _trigfunc = cos + _atzero = S.ComplexInfinity + + @classmethod + def _atinf(cls): + return S.Zero + + @classmethod + def _atneginf(cls): + return I*pi + + @classmethod + def _minusfactor(cls, z): + return Ci(z) + I*pi + + @classmethod + def _Ifactor(cls, z, sign): + return Chi(z) + I*pi/2*sign + + def _eval_rewrite_as_expint(self, z, **kwargs): + return -(E1(polar_lift(I)*z) + E1(polar_lift(-I)*z))/2 + + def _eval_rewrite_as_Integral(self, z, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [z]).name) + return S.EulerGamma + log(z) - Integral((1-cos(t))/t, (t, 0, z)) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if arg0.is_zero: + c, e = arg.as_coeff_exponent(x) + logx = log(x) if logx is None else logx + return log(c) + e*logx + EulerGamma + elif arg0.is_finite: + return self.func(arg0) + else: + return self + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + if point in (S.Infinity, S.NegativeInfinity): + z = self.args[0] + p = [S.NegativeOne**k * factorial(2*k) / z**(2*k + 1) + for k in range(n//2 + 1)] + [Order(1/z**n, x)] + q = [S.NegativeOne**k * factorial(2*k + 1) / z**(2*(k + 1)) + for k in range(n//2)] + [Order(1/z**n, x)] + result = sin(z)*(Add(*p)) - cos(z)*(Add(*q)) + + if point is S.NegativeInfinity: + result += I*pi + return result + + return super(Ci, self)._eval_aseries(n, args0, x, logx) + +class Shi(TrigonometricIntegral): + r""" + Sinh integral. + + Explanation + =========== + + This function is defined by + + .. math:: \operatorname{Shi}(z) = \int_0^z \frac{\sinh{t}}{t} \mathrm{d}t. + + It is an entire function. + + Examples + ======== + + >>> from sympy import Shi + >>> from sympy.abc import z + + The Sinh integral is a primitive of $\sinh(z)/z$: + + >>> Shi(z).diff(z) + sinh(z)/z + + It is unbranched: + + >>> from sympy import exp_polar, I, pi + >>> Shi(z*exp_polar(2*I*pi)) + Shi(z) + + The $\sinh$ integral behaves much like ordinary $\sinh$ under + multiplication by $i$: + + >>> Shi(I*z) + I*Si(z) + >>> Shi(-z) + -Shi(z) + + It can also be expressed in terms of exponential integrals, but beware + that the latter is branched: + + >>> from sympy import expint + >>> Shi(z).rewrite(expint) + expint(1, z)/2 - expint(1, z*exp_polar(I*pi))/2 - I*pi/2 + + See Also + ======== + + Si: Sine integral. + Ci: Cosine integral. + Chi: Hyperbolic cosine integral. + Ei: Exponential integral. + expint: Generalised exponential integral. + E1: Special case of the generalised exponential integral. + li: Logarithmic integral. + Li: Offset logarithmic integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_integral + + """ + + _trigfunc = sinh + _atzero = S.Zero + + @classmethod + def _atinf(cls): + return S.Infinity + + @classmethod + def _atneginf(cls): + return S.NegativeInfinity + + @classmethod + def _minusfactor(cls, z): + return -Shi(z) + + @classmethod + def _Ifactor(cls, z, sign): + return I*Si(z)*sign + + def _eval_rewrite_as_expint(self, z, **kwargs): + # XXX should we polarify z? + return (E1(z) - E1(exp_polar(I*pi)*z))/2 - I*pi/2 + + def _eval_is_zero(self): + z = self.args[0] + if z.is_zero: + return True + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x) + arg0 = arg.subs(x, 0) + + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if arg0.is_zero: + return arg + elif not arg0.is_infinite: + return self.func(arg0) + else: + return self + + +class Chi(TrigonometricIntegral): + r""" + Cosh integral. + + Explanation + =========== + + This function is defined for positive $x$ by + + .. math:: \operatorname{Chi}(x) = \gamma + \log{x} + + \int_0^x \frac{\cosh{t} - 1}{t} \mathrm{d}t, + + where $\gamma$ is the Euler-Mascheroni constant. + + We have + + .. math:: \operatorname{Chi}(z) = \operatorname{Ci}\left(e^{i \pi/2}z\right) + - i\frac{\pi}{2}, + + which holds for all polar $z$ and thus provides an analytic + continuation to the Riemann surface of the logarithm. + By lifting to the principal branch we obtain an analytic function on the + cut complex plane. + + Examples + ======== + + >>> from sympy import Chi + >>> from sympy.abc import z + + The $\cosh$ integral is a primitive of $\cosh(z)/z$: + + >>> Chi(z).diff(z) + cosh(z)/z + + It has a logarithmic branch point at the origin: + + >>> from sympy import exp_polar, I, pi + >>> Chi(z*exp_polar(2*I*pi)) + Chi(z) + 2*I*pi + + The $\cosh$ integral behaves somewhat like ordinary $\cosh$ under + multiplication by $i$: + + >>> from sympy import polar_lift + >>> Chi(polar_lift(I)*z) + Ci(z) + I*pi/2 + >>> Chi(polar_lift(-1)*z) + Chi(z) + I*pi + + It can also be expressed in terms of exponential integrals: + + >>> from sympy import expint + >>> Chi(z).rewrite(expint) + -expint(1, z)/2 - expint(1, z*exp_polar(I*pi))/2 - I*pi/2 + + See Also + ======== + + Si: Sine integral. + Ci: Cosine integral. + Shi: Hyperbolic sine integral. + Ei: Exponential integral. + expint: Generalised exponential integral. + E1: Special case of the generalised exponential integral. + li: Logarithmic integral. + Li: Offset logarithmic integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigonometric_integral + + """ + + _trigfunc = cosh + _atzero = S.ComplexInfinity + + @classmethod + def _atinf(cls): + return S.Infinity + + @classmethod + def _atneginf(cls): + return S.Infinity + + @classmethod + def _minusfactor(cls, z): + return Chi(z) + I*pi + + @classmethod + def _Ifactor(cls, z, sign): + return Ci(z) + I*pi/2*sign + + def _eval_rewrite_as_expint(self, z, **kwargs): + return -I*pi/2 - (E1(z) + E1(exp_polar(I*pi)*z))/2 + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.NaN: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if arg0.is_zero: + c, e = arg.as_coeff_exponent(x) + logx = log(x) if logx is None else logx + return log(c) + e*logx + EulerGamma + elif arg0.is_finite: + return self.func(arg0) + else: + return self + + +############################################################################### +#################### FRESNEL INTEGRALS ######################################## +############################################################################### + +class FresnelIntegral(DefinedFunction): + """ Base class for the Fresnel integrals.""" + + unbranched = True + + @classmethod + def eval(cls, z): + # Values at positive infinities signs + # if any were extracted automatically + if z is S.Infinity: + return S.Half + + # Value at zero + if z.is_zero: + return S.Zero + + # Try to pull out factors of -1 and I + prefact = S.One + newarg = z + changed = False + + nz = newarg.extract_multiplicatively(-1) + if nz is not None: + prefact = -prefact + newarg = nz + changed = True + + nz = newarg.extract_multiplicatively(I) + if nz is not None: + prefact = cls._sign*I*prefact + newarg = nz + changed = True + + if changed: + return prefact*cls(newarg) + + def fdiff(self, argindex=1): + if argindex == 1: + return self._trigfunc(S.Half*pi*self.args[0]**2) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_is_extended_real(self): + return self.args[0].is_extended_real + + _eval_is_finite = _eval_is_extended_real + + def _eval_is_zero(self): + return self.args[0].is_zero + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + as_real_imag = real_to_real_as_real_imag + + +class fresnels(FresnelIntegral): + r""" + Fresnel integral S. + + Explanation + =========== + + This function is defined by + + .. math:: \operatorname{S}(z) = \int_0^z \sin{\frac{\pi}{2} t^2} \mathrm{d}t. + + It is an entire function. + + Examples + ======== + + >>> from sympy import I, oo, fresnels + >>> from sympy.abc import z + + Several special values are known: + + >>> fresnels(0) + 0 + >>> fresnels(oo) + 1/2 + >>> fresnels(-oo) + -1/2 + >>> fresnels(I*oo) + -I/2 + >>> fresnels(-I*oo) + I/2 + + In general one can pull out factors of -1 and $i$ from the argument: + + >>> fresnels(-z) + -fresnels(z) + >>> fresnels(I*z) + -I*fresnels(z) + + The Fresnel S integral obeys the mirror symmetry + $\overline{S(z)} = S(\bar{z})$: + + >>> from sympy import conjugate + >>> conjugate(fresnels(z)) + fresnels(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(fresnels(z), z) + sin(pi*z**2/2) + + Defining the Fresnel functions via an integral: + + >>> from sympy import integrate, pi, sin, expand_func + >>> integrate(sin(pi*z**2/2), z) + 3*fresnels(z)*gamma(3/4)/(4*gamma(7/4)) + >>> expand_func(integrate(sin(pi*z**2/2), z)) + fresnels(z) + + We can numerically evaluate the Fresnel integral to arbitrary precision + on the whole complex plane: + + >>> fresnels(2).evalf(30) + 0.343415678363698242195300815958 + + >>> fresnels(-2*I).evalf(30) + 0.343415678363698242195300815958*I + + See Also + ======== + + fresnelc: Fresnel cosine integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Fresnel_integral + .. [2] https://dlmf.nist.gov/7 + .. [3] https://mathworld.wolfram.com/FresnelIntegrals.html + .. [4] https://functions.wolfram.com/GammaBetaErf/FresnelS + .. [5] The converging factors for the fresnel integrals + by John W. Wrench Jr. and Vicki Alley + + """ + _trigfunc = sin + _sign = -S.One + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 1: + p = previous_terms[-1] + return (-pi**2*x**4*(4*n - 1)/(8*n*(2*n + 1)*(4*n + 3))) * p + else: + return x**3 * (-x**4)**n * (S(2)**(-2*n - 1)*pi**(2*n + 1)) / ((4*n + 3)*factorial(2*n + 1)) + + def _eval_rewrite_as_erf(self, z, **kwargs): + return (S.One + I)/4 * (erf((S.One + I)/2*sqrt(pi)*z) - I*erf((S.One - I)/2*sqrt(pi)*z)) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + return pi*z**3/6 * hyper([Rational(3, 4)], [Rational(3, 2), Rational(7, 4)], -pi**2*z**4/16) + + def _eval_rewrite_as_meijerg(self, z, **kwargs): + return (pi*z**Rational(9, 4) / (sqrt(2)*(z**2)**Rational(3, 4)*(-z)**Rational(3, 4)) + * meijerg([], [1], [Rational(3, 4)], [Rational(1, 4), 0], -pi**2*z**4/16)) + + def _eval_rewrite_as_Integral(self, z, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [z]).name) + return Integral(sin(pi*t**2/2), (t, 0, z)) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.series.order import Order + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.ComplexInfinity: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if arg0.is_zero: + return pi*arg**3/6 + elif arg0 in [S.Infinity, S.NegativeInfinity]: + s = 1 if arg0 is S.Infinity else -1 + return s*S.Half + Order(x, x) + else: + return self.func(arg0) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + # Expansion at oo and -oo + if point in [S.Infinity, -S.Infinity]: + z = self.args[0] + + # expansion of S(x) = S1(x*sqrt(pi/2)), see reference[5] page 1-8 + # as only real infinities are dealt with, sin and cos are O(1) + p = [S.NegativeOne**k * factorial(4*k + 1) / + (2**(2*k + 2) * z**(4*k + 3) * 2**(2*k)*factorial(2*k)) + for k in range(0, n) if 4*k + 3 < n] + q = [1/(2*z)] + [S.NegativeOne**k * factorial(4*k - 1) / + (2**(2*k + 1) * z**(4*k + 1) * 2**(2*k - 1)*factorial(2*k - 1)) + for k in range(1, n) if 4*k + 1 < n] + + p = [-sqrt(2/pi)*t for t in p] + q = [-sqrt(2/pi)*t for t in q] + s = 1 if point is S.Infinity else -1 + # The expansion at oo is 1/2 + some odd powers of z + # To get the expansion at -oo, replace z by -z and flip the sign + # The result -1/2 + the same odd powers of z as before. + return s*S.Half + (sin(z**2)*Add(*p) + cos(z**2)*Add(*q) + ).subs(x, sqrt(2/pi)*x) + Order(1/z**n, x) + + # All other points are not handled + return super()._eval_aseries(n, args0, x, logx) + + +class fresnelc(FresnelIntegral): + r""" + Fresnel integral C. + + Explanation + =========== + + This function is defined by + + .. math:: \operatorname{C}(z) = \int_0^z \cos{\frac{\pi}{2} t^2} \mathrm{d}t. + + It is an entire function. + + Examples + ======== + + >>> from sympy import I, oo, fresnelc + >>> from sympy.abc import z + + Several special values are known: + + >>> fresnelc(0) + 0 + >>> fresnelc(oo) + 1/2 + >>> fresnelc(-oo) + -1/2 + >>> fresnelc(I*oo) + I/2 + >>> fresnelc(-I*oo) + -I/2 + + In general one can pull out factors of -1 and $i$ from the argument: + + >>> fresnelc(-z) + -fresnelc(z) + >>> fresnelc(I*z) + I*fresnelc(z) + + The Fresnel C integral obeys the mirror symmetry + $\overline{C(z)} = C(\bar{z})$: + + >>> from sympy import conjugate + >>> conjugate(fresnelc(z)) + fresnelc(conjugate(z)) + + Differentiation with respect to $z$ is supported: + + >>> from sympy import diff + >>> diff(fresnelc(z), z) + cos(pi*z**2/2) + + Defining the Fresnel functions via an integral: + + >>> from sympy import integrate, pi, cos, expand_func + >>> integrate(cos(pi*z**2/2), z) + fresnelc(z)*gamma(1/4)/(4*gamma(5/4)) + >>> expand_func(integrate(cos(pi*z**2/2), z)) + fresnelc(z) + + We can numerically evaluate the Fresnel integral to arbitrary precision + on the whole complex plane: + + >>> fresnelc(2).evalf(30) + 0.488253406075340754500223503357 + + >>> fresnelc(-2*I).evalf(30) + -0.488253406075340754500223503357*I + + See Also + ======== + + fresnels: Fresnel sine integral. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Fresnel_integral + .. [2] https://dlmf.nist.gov/7 + .. [3] https://mathworld.wolfram.com/FresnelIntegrals.html + .. [4] https://functions.wolfram.com/GammaBetaErf/FresnelC + .. [5] The converging factors for the fresnel integrals + by John W. Wrench Jr. and Vicki Alley + + """ + _trigfunc = cos + _sign = S.One + + @staticmethod + @cacheit + def taylor_term(n, x, *previous_terms): + if n < 0: + return S.Zero + else: + x = sympify(x) + if len(previous_terms) > 1: + p = previous_terms[-1] + return (-pi**2*x**4*(4*n - 3)/(8*n*(2*n - 1)*(4*n + 1))) * p + else: + return x * (-x**4)**n * (S(2)**(-2*n)*pi**(2*n)) / ((4*n + 1)*factorial(2*n)) + + def _eval_rewrite_as_erf(self, z, **kwargs): + return (S.One - I)/4 * (erf((S.One + I)/2*sqrt(pi)*z) + I*erf((S.One - I)/2*sqrt(pi)*z)) + + def _eval_rewrite_as_hyper(self, z, **kwargs): + return z * hyper([Rational(1, 4)], [S.Half, Rational(5, 4)], -pi**2*z**4/16) + + def _eval_rewrite_as_meijerg(self, z, **kwargs): + return (pi*z**Rational(3, 4) / (sqrt(2)*root(z**2, 4)*root(-z, 4)) + * meijerg([], [1], [Rational(1, 4)], [Rational(3, 4), 0], -pi**2*z**4/16)) + + def _eval_rewrite_as_Integral(self, z, **kwargs): + from sympy.integrals.integrals import Integral + t = Dummy(uniquely_named_symbol('t', [z]).name) + return Integral(cos(pi*t**2/2), (t, 0, z)) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.series.order import Order + arg = self.args[0].as_leading_term(x, logx=logx, cdir=cdir) + arg0 = arg.subs(x, 0) + + if arg0 is S.ComplexInfinity: + arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + if arg0.is_zero: + return arg + elif arg0 in [S.Infinity, S.NegativeInfinity]: + s = 1 if arg0 is S.Infinity else -1 + return s*S.Half + Order(x, x) + else: + return self.func(arg0) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + # Expansion at oo + if point in [S.Infinity, -S.Infinity]: + z = self.args[0] + + # expansion of C(x) = C1(x*sqrt(pi/2)), see reference[5] page 1-8 + # as only real infinities are dealt with, sin and cos are O(1) + p = [S.NegativeOne**k * factorial(4*k + 1) / + (2**(2*k + 2) * z**(4*k + 3) * 2**(2*k)*factorial(2*k)) + for k in range(n) if 4*k + 3 < n] + q = [1/(2*z)] + [S.NegativeOne**k * factorial(4*k - 1) / + (2**(2*k + 1) * z**(4*k + 1) * 2**(2*k - 1)*factorial(2*k - 1)) + for k in range(1, n) if 4*k + 1 < n] + + p = [-sqrt(2/pi)*t for t in p] + q = [ sqrt(2/pi)*t for t in q] + s = 1 if point is S.Infinity else -1 + # The expansion at oo is 1/2 + some odd powers of z + # To get the expansion at -oo, replace z by -z and flip the sign + # The result -1/2 + the same odd powers of z as before. + return s*S.Half + (cos(z**2)*Add(*p) + sin(z**2)*Add(*q) + ).subs(x, sqrt(2/pi)*x) + Order(1/z**n, x) + + # All other points are not handled + return super()._eval_aseries(n, args0, x, logx) + + +############################################################################### +#################### HELPER FUNCTIONS ######################################### +############################################################################### + + +class _erfs(DefinedFunction): + """ + Helper function to make the $\\mathrm{erf}(z)$ function + tractable for the Gruntz algorithm. + + """ + @classmethod + def eval(cls, arg): + if arg.is_zero: + return S.One + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + point = args0[0] + + # Expansion at oo + if point is S.Infinity: + z = self.args[0] + l = [1/sqrt(pi) * factorial(2*k)*(-S( + 4))**(-k)/factorial(k) * (1/z)**(2*k + 1) for k in range(n)] + o = Order(1/z**(2*n + 1), x) + # It is very inefficient to first add the order and then do the nseries + return (Add(*l))._eval_nseries(x, n, logx) + o + + # Expansion at I*oo + t = point.extract_multiplicatively(I) + if t is S.Infinity: + z = self.args[0] + # TODO: is the series really correct? + l = [1/sqrt(pi) * factorial(2*k)*(-S( + 4))**(-k)/factorial(k) * (1/z)**(2*k + 1) for k in range(n)] + o = Order(1/z**(2*n + 1), x) + # It is very inefficient to first add the order and then do the nseries + return (Add(*l))._eval_nseries(x, n, logx) + o + + # All other points are not handled + return super()._eval_aseries(n, args0, x, logx) + + def fdiff(self, argindex=1): + if argindex == 1: + z = self.args[0] + return -2/sqrt(pi) + 2*z*_erfs(z) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_intractable(self, z, **kwargs): + return (S.One - erf(z))*exp(z**2) + + +class _eis(DefinedFunction): + """ + Helper function to make the $\\mathrm{Ei}(z)$ and $\\mathrm{li}(z)$ + functions tractable for the Gruntz algorithm. + + """ + + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + if args0[0] not in (S.Infinity, S.NegativeInfinity): + return super()._eval_aseries(n, args0, x, logx) + + z = self.args[0] + l = [factorial(k) * (1/z)**(k + 1) for k in range(n)] + o = Order(1/z**(n + 1), x) + # It is very inefficient to first add the order and then do the nseries + return (Add(*l))._eval_nseries(x, n, logx) + o + + + def fdiff(self, argindex=1): + if argindex == 1: + z = self.args[0] + return S.One / z - _eis(z) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_intractable(self, z, **kwargs): + return exp(-z)*Ei(z) + + def _eval_as_leading_term(self, x, logx, cdir): + x0 = self.args[0].limit(x, 0) + if x0.is_zero: + f = self._eval_rewrite_as_intractable(*self.args) + return f._eval_as_leading_term(x, logx=logx, cdir=cdir) + return super()._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + x0 = self.args[0].limit(x, 0) + if x0.is_zero: + f = self._eval_rewrite_as_intractable(*self.args) + return f._eval_nseries(x, n, logx) + return super()._eval_nseries(x, n, logx) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/gamma_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/gamma_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..73a5a1585154c603e9c510b3c61144039e0e5502 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/gamma_functions.py @@ -0,0 +1,1344 @@ +from math import prod + +from sympy.core import Add, S, Dummy, expand_func +from sympy.core.expr import Expr +from sympy.core.function import DefinedFunction, ArgumentIndexError, PoleError +from sympy.core.logic import fuzzy_and, fuzzy_not +from sympy.core.numbers import Rational, pi, oo, I +from sympy.core.power import Pow +from sympy.functions.special.zeta_functions import zeta +from sympy.functions.special.error_functions import erf, erfc, Ei +from sympy.functions.elementary.complexes import re, unpolarify +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.integers import ceiling, floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin, cos, cot +from sympy.functions.combinatorial.numbers import bernoulli, harmonic +from sympy.functions.combinatorial.factorials import factorial, rf, RisingFactorial +from sympy.utilities.misc import as_int + +from mpmath import mp, workprec +from mpmath.libmp.libmpf import prec_to_dps + +def intlike(n): + try: + as_int(n, strict=False) + return True + except ValueError: + return False + +############################################################################### +############################ COMPLETE GAMMA FUNCTION ########################## +############################################################################### + +class gamma(DefinedFunction): + r""" + The gamma function + + .. math:: + \Gamma(x) := \int^{\infty}_{0} t^{x-1} e^{-t} \mathrm{d}t. + + Explanation + =========== + + The ``gamma`` function implements the function which passes through the + values of the factorial function (i.e., $\Gamma(n) = (n - 1)!$ when n is + an integer). More generally, $\Gamma(z)$ is defined in the whole complex + plane except at the negative integers where there are simple poles. + + Examples + ======== + + >>> from sympy import S, I, pi, gamma + >>> from sympy.abc import x + + Several special values are known: + + >>> gamma(1) + 1 + >>> gamma(4) + 6 + >>> gamma(S(3)/2) + sqrt(pi)/2 + + The ``gamma`` function obeys the mirror symmetry: + + >>> from sympy import conjugate + >>> conjugate(gamma(x)) + gamma(conjugate(x)) + + Differentiation with respect to $x$ is supported: + + >>> from sympy import diff + >>> diff(gamma(x), x) + gamma(x)*polygamma(0, x) + + Series expansion is also supported: + + >>> from sympy import series + >>> series(gamma(x), x, 0, 3) + 1/x - EulerGamma + x*(EulerGamma**2/2 + pi**2/12) + x**2*(-EulerGamma*pi**2/12 - zeta(3)/3 - EulerGamma**3/6) + O(x**3) + + We can numerically evaluate the ``gamma`` function to arbitrary precision + on the whole complex plane: + + >>> gamma(pi).evalf(40) + 2.288037795340032417959588909060233922890 + >>> gamma(1+I).evalf(20) + 0.49801566811835604271 - 0.15494982830181068512*I + + See Also + ======== + + lowergamma: Lower incomplete gamma function. + uppergamma: Upper incomplete gamma function. + polygamma: Polygamma function. + loggamma: Log Gamma function. + digamma: Digamma function. + trigamma: Trigamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gamma_function + .. [2] https://dlmf.nist.gov/5 + .. [3] https://mathworld.wolfram.com/GammaFunction.html + .. [4] https://functions.wolfram.com/GammaBetaErf/Gamma/ + + """ + + unbranched = True + _singularities = (S.ComplexInfinity,) + + def fdiff(self, argindex=1): + if argindex == 1: + return self.func(self.args[0])*polygamma(0, self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, arg): + if arg.is_Number: + if arg is S.NaN: + return S.NaN + elif arg is oo: + return oo + elif intlike(arg): + if arg.is_positive: + return factorial(arg - 1) + else: + return S.ComplexInfinity + elif arg.is_Rational: + if arg.q == 2: + n = abs(arg.p) // arg.q + + if arg.is_positive: + k, coeff = n, S.One + else: + n = k = n + 1 + + if n & 1 == 0: + coeff = S.One + else: + coeff = S.NegativeOne + + coeff *= prod(range(3, 2*k, 2)) + + if arg.is_positive: + return coeff*sqrt(pi) / 2**n + else: + return 2**n*sqrt(pi) / coeff + + def _eval_expand_func(self, **hints): + arg = self.args[0] + if arg.is_Rational: + if abs(arg.p) > arg.q: + x = Dummy('x') + n = arg.p // arg.q + p = arg.p - n*arg.q + return self.func(x + n)._eval_expand_func().subs(x, Rational(p, arg.q)) + + if arg.is_Add: + coeff, tail = arg.as_coeff_add() + if coeff and coeff.q != 1: + intpart = floor(coeff) + tail = (coeff - intpart,) + tail + coeff = intpart + tail = arg._new_rawargs(*tail, reeval=False) + return self.func(tail)*RisingFactorial(tail, coeff) + + return self.func(*self.args) + + def _eval_conjugate(self): + return self.func(self.args[0].conjugate()) + + def _eval_is_real(self): + x = self.args[0] + if x.is_nonpositive and x.is_integer: + return False + if intlike(x) and x <= 0: + return False + if x.is_positive or x.is_noninteger: + return True + + def _eval_is_positive(self): + x = self.args[0] + if x.is_positive: + return True + elif x.is_noninteger: + return floor(x).is_even + + def _eval_rewrite_as_tractable(self, z, limitvar=None, **kwargs): + return exp(loggamma(z)) + + def _eval_rewrite_as_factorial(self, z, **kwargs): + return factorial(z - 1) + + def _eval_nseries(self, x, n, logx, cdir=0): + x0 = self.args[0].limit(x, 0) + if not (x0.is_Integer and x0 <= 0): + return super()._eval_nseries(x, n, logx) + t = self.args[0] - x0 + return (self.func(t + 1)/rf(self.args[0], -x0 + 1))._eval_nseries(x, n, logx) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[0] + x0 = arg.subs(x, 0) + + if x0.is_integer and x0.is_nonpositive: + n = -x0 + res = S.NegativeOne**n/self.func(n + 1) + return res/(arg + n).as_leading_term(x) + elif not x0.is_infinite: + return self.func(x0) + raise PoleError() + + +############################################################################### +################## LOWER and UPPER INCOMPLETE GAMMA FUNCTIONS ################# +############################################################################### + +class lowergamma(DefinedFunction): + r""" + The lower incomplete gamma function. + + Explanation + =========== + + It can be defined as the meromorphic continuation of + + .. math:: + \gamma(s, x) := \int_0^x t^{s-1} e^{-t} \mathrm{d}t = \Gamma(s) - \Gamma(s, x). + + This can be shown to be the same as + + .. math:: + \gamma(s, x) = \frac{x^s}{s} {}_1F_1\left({s \atop s+1} \middle| -x\right), + + where ${}_1F_1$ is the (confluent) hypergeometric function. + + Examples + ======== + + >>> from sympy import lowergamma, S + >>> from sympy.abc import s, x + >>> lowergamma(s, x) + lowergamma(s, x) + >>> lowergamma(3, x) + -2*(x**2/2 + x + 1)*exp(-x) + 2 + >>> lowergamma(-S(1)/2, x) + -2*sqrt(pi)*erf(sqrt(x)) - 2*exp(-x)/sqrt(x) + + See Also + ======== + + gamma: Gamma function. + uppergamma: Upper incomplete gamma function. + polygamma: Polygamma function. + loggamma: Log Gamma function. + digamma: Digamma function. + trigamma: Trigamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Incomplete_gamma_function#Lower_incomplete_gamma_function + .. [2] Abramowitz, Milton; Stegun, Irene A., eds. (1965), Chapter 6, + Section 5, Handbook of Mathematical Functions with Formulas, Graphs, + and Mathematical Tables + .. [3] https://dlmf.nist.gov/8 + .. [4] https://functions.wolfram.com/GammaBetaErf/Gamma2/ + .. [5] https://functions.wolfram.com/GammaBetaErf/Gamma3/ + + """ + + + def fdiff(self, argindex=2): + from sympy.functions.special.hyper import meijerg + if argindex == 2: + a, z = self.args + return exp(-unpolarify(z))*z**(a - 1) + elif argindex == 1: + a, z = self.args + return gamma(a)*digamma(a) - log(z)*uppergamma(a, z) \ + - meijerg([], [1, 1], [0, 0, a], [], z) + + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, a, x): + # For lack of a better place, we use this one to extract branching + # information. The following can be + # found in the literature (c/f references given above), albeit scattered: + # 1) For fixed x != 0, lowergamma(s, x) is an entire function of s + # 2) For fixed positive integers s, lowergamma(s, x) is an entire + # function of x. + # 3) For fixed non-positive integers s, + # lowergamma(s, exp(I*2*pi*n)*x) = + # 2*pi*I*n*(-1)**(-s)/factorial(-s) + lowergamma(s, x) + # (this follows from lowergamma(s, x).diff(x) = x**(s-1)*exp(-x)). + # 4) For fixed non-integral s, + # lowergamma(s, x) = x**s*gamma(s)*lowergamma_unbranched(s, x), + # where lowergamma_unbranched(s, x) is an entire function (in fact + # of both s and x), i.e. + # lowergamma(s, exp(2*I*pi*n)*x) = exp(2*pi*I*n*a)*lowergamma(a, x) + if x is S.Zero: + return S.Zero + nx, n = x.extract_branch_factor() + if a.is_integer and a.is_positive: + nx = unpolarify(x) + if nx != x: + return lowergamma(a, nx) + elif a.is_integer and a.is_nonpositive: + if n != 0: + return 2*pi*I*n*S.NegativeOne**(-a)/factorial(-a) + lowergamma(a, nx) + elif n != 0: + return exp(2*pi*I*n*a)*lowergamma(a, nx) + + # Special values. + if a.is_Number: + if a is S.One: + return S.One - exp(-x) + elif a is S.Half: + return sqrt(pi)*erf(sqrt(x)) + elif a.is_Integer or (2*a).is_Integer: + b = a - 1 + if b.is_positive: + if a.is_integer: + return factorial(b) - exp(-x) * factorial(b) * Add(*[x ** k / factorial(k) for k in range(a)]) + else: + return gamma(a)*(lowergamma(S.Half, x)/sqrt(pi) - exp(-x)*Add(*[x**(k - S.Half)/gamma(S.Half + k) for k in range(1, a + S.Half)])) + + if not a.is_Integer: + return S.NegativeOne**(S.Half - a)*pi*erf(sqrt(x))/gamma(1 - a) + exp(-x)*Add(*[x**(k + a - 1)*gamma(a)/gamma(a + k) for k in range(1, Rational(3, 2) - a)]) + + if x.is_zero: + return S.Zero + + def _eval_evalf(self, prec): + if all(x.is_number for x in self.args): + a = self.args[0]._to_mpmath(prec) + z = self.args[1]._to_mpmath(prec) + with workprec(prec): + res = mp.gammainc(a, 0, z) + return Expr._from_mpmath(res, prec) + else: + return self + + def _eval_conjugate(self): + x = self.args[1] + if x not in (S.Zero, S.NegativeInfinity): + return self.func(self.args[0].conjugate(), x.conjugate()) + + def _eval_is_meromorphic(self, x, a): + # By https://en.wikipedia.org/wiki/Incomplete_gamma_function#Holomorphic_extension, + # lowergamma(s, z) = z**s*gamma(s)*gammastar(s, z), + # where gammastar(s, z) is holomorphic for all s and z. + # Hence the singularities of lowergamma are z = 0 (branch + # point) and nonpositive integer values of s (poles of gamma(s)). + s, z = self.args + args_merom = fuzzy_and([z._eval_is_meromorphic(x, a), + s._eval_is_meromorphic(x, a)]) + if not args_merom: + return args_merom + z0 = z.subs(x, a) + if s.is_integer: + return fuzzy_and([s.is_positive, z0.is_finite]) + s0 = s.subs(x, a) + return fuzzy_and([s0.is_finite, z0.is_finite, fuzzy_not(z0.is_zero)]) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import O + s, z = self.args + if args0[0] is oo and not z.has(x): + coeff = z**s*exp(-z) + sum_expr = sum(z**k/rf(s, k + 1) for k in range(n - 1)) + o = O(z**s*s**(-n)) + return coeff*sum_expr + o + return super()._eval_aseries(n, args0, x, logx) + + def _eval_rewrite_as_uppergamma(self, s, x, **kwargs): + return gamma(s) - uppergamma(s, x) + + def _eval_rewrite_as_expint(self, s, x, **kwargs): + from sympy.functions.special.error_functions import expint + if s.is_integer and s.is_nonpositive: + return self + return self.rewrite(uppergamma).rewrite(expint) + + def _eval_is_zero(self): + x = self.args[1] + if x.is_zero: + return True + + +class uppergamma(DefinedFunction): + r""" + The upper incomplete gamma function. + + Explanation + =========== + + It can be defined as the meromorphic continuation of + + .. math:: + \Gamma(s, x) := \int_x^\infty t^{s-1} e^{-t} \mathrm{d}t = \Gamma(s) - \gamma(s, x). + + where $\gamma(s, x)$ is the lower incomplete gamma function, + :class:`lowergamma`. This can be shown to be the same as + + .. math:: + \Gamma(s, x) = \Gamma(s) - \frac{x^s}{s} {}_1F_1\left({s \atop s+1} \middle| -x\right), + + where ${}_1F_1$ is the (confluent) hypergeometric function. + + The upper incomplete gamma function is also essentially equivalent to the + generalized exponential integral: + + .. math:: + \operatorname{E}_{n}(x) = \int_{1}^{\infty}{\frac{e^{-xt}}{t^n} \, dt} = x^{n-1}\Gamma(1-n,x). + + Examples + ======== + + >>> from sympy import uppergamma, S + >>> from sympy.abc import s, x + >>> uppergamma(s, x) + uppergamma(s, x) + >>> uppergamma(3, x) + 2*(x**2/2 + x + 1)*exp(-x) + >>> uppergamma(-S(1)/2, x) + -2*sqrt(pi)*erfc(sqrt(x)) + 2*exp(-x)/sqrt(x) + >>> uppergamma(-2, x) + expint(3, x)/x**2 + + See Also + ======== + + gamma: Gamma function. + lowergamma: Lower incomplete gamma function. + polygamma: Polygamma function. + loggamma: Log Gamma function. + digamma: Digamma function. + trigamma: Trigamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Incomplete_gamma_function#Upper_incomplete_gamma_function + .. [2] Abramowitz, Milton; Stegun, Irene A., eds. (1965), Chapter 6, + Section 5, Handbook of Mathematical Functions with Formulas, Graphs, + and Mathematical Tables + .. [3] https://dlmf.nist.gov/8 + .. [4] https://functions.wolfram.com/GammaBetaErf/Gamma2/ + .. [5] https://functions.wolfram.com/GammaBetaErf/Gamma3/ + .. [6] https://en.wikipedia.org/wiki/Exponential_integral#Relation_with_other_functions + + """ + + + def fdiff(self, argindex=2): + from sympy.functions.special.hyper import meijerg + if argindex == 2: + a, z = self.args + return -exp(-unpolarify(z))*z**(a - 1) + elif argindex == 1: + a, z = self.args + return uppergamma(a, z)*log(z) + meijerg([], [1, 1], [0, 0, a], [], z) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_evalf(self, prec): + if all(x.is_number for x in self.args): + a = self.args[0]._to_mpmath(prec) + z = self.args[1]._to_mpmath(prec) + with workprec(prec): + res = mp.gammainc(a, z, mp.inf) + return Expr._from_mpmath(res, prec) + return self + + @classmethod + def eval(cls, a, z): + from sympy.functions.special.error_functions import expint + if z.is_Number: + if z is S.NaN: + return S.NaN + elif z is oo: + return S.Zero + elif z.is_zero: + if re(a).is_positive: + return gamma(a) + + # We extract branching information here. C/f lowergamma. + nx, n = z.extract_branch_factor() + if a.is_integer and a.is_positive: + nx = unpolarify(z) + if z != nx: + return uppergamma(a, nx) + elif a.is_integer and a.is_nonpositive: + if n != 0: + return -2*pi*I*n*S.NegativeOne**(-a)/factorial(-a) + uppergamma(a, nx) + elif n != 0: + return gamma(a)*(1 - exp(2*pi*I*n*a)) + exp(2*pi*I*n*a)*uppergamma(a, nx) + + # Special values. + if a.is_Number: + if a is S.Zero and z.is_positive: + return -Ei(-z) + elif a is S.One: + return exp(-z) + elif a is S.Half: + return sqrt(pi)*erfc(sqrt(z)) + elif a.is_Integer or (2*a).is_Integer: + b = a - 1 + if b.is_positive: + if a.is_integer: + return exp(-z) * factorial(b) * Add(*[z**k / factorial(k) + for k in range(a)]) + else: + return (gamma(a) * erfc(sqrt(z)) + + S.NegativeOne**(a - S(3)/2) * exp(-z) * sqrt(z) + * Add(*[gamma(-S.Half - k) * (-z)**k / gamma(1-a) + for k in range(a - S.Half)])) + elif b.is_Integer: + return expint(-b, z)*unpolarify(z)**(b + 1) + + if not a.is_Integer: + return (S.NegativeOne**(S.Half - a) * pi*erfc(sqrt(z))/gamma(1-a) + - z**a * exp(-z) * Add(*[z**k * gamma(a) / gamma(a+k+1) + for k in range(S.Half - a)])) + + if a.is_zero and z.is_positive: + return -Ei(-z) + + if z.is_zero and re(a).is_positive: + return gamma(a) + + def _eval_conjugate(self): + z = self.args[1] + if z not in (S.Zero, S.NegativeInfinity): + return self.func(self.args[0].conjugate(), z.conjugate()) + + def _eval_is_meromorphic(self, x, a): + return lowergamma._eval_is_meromorphic(self, x, a) + + def _eval_rewrite_as_lowergamma(self, s, x, **kwargs): + return gamma(s) - lowergamma(s, x) + + def _eval_rewrite_as_tractable(self, s, x, **kwargs): + return exp(loggamma(s)) - lowergamma(s, x) + + def _eval_rewrite_as_expint(self, s, x, **kwargs): + from sympy.functions.special.error_functions import expint + return expint(1 - s, x)*x**s + + +############################################################################### +###################### POLYGAMMA and LOGGAMMA FUNCTIONS ####################### +############################################################################### + +class polygamma(DefinedFunction): + r""" + The function ``polygamma(n, z)`` returns ``log(gamma(z)).diff(n + 1)``. + + Explanation + =========== + + It is a meromorphic function on $\mathbb{C}$ and defined as the $(n+1)$-th + derivative of the logarithm of the gamma function: + + .. math:: + \psi^{(n)} (z) := \frac{\mathrm{d}^{n+1}}{\mathrm{d} z^{n+1}} \log\Gamma(z). + + For `n` not a nonnegative integer the generalization by Espinosa and Moll [5]_ + is used: + + .. math:: \psi(s,z) = \frac{\zeta'(s+1, z) + (\gamma + \psi(-s)) \zeta(s+1, z)} + {\Gamma(-s)} + + Examples + ======== + + Several special values are known: + + >>> from sympy import S, polygamma + >>> polygamma(0, 1) + -EulerGamma + >>> polygamma(0, 1/S(2)) + -2*log(2) - EulerGamma + >>> polygamma(0, 1/S(3)) + -log(3) - sqrt(3)*pi/6 - EulerGamma - log(sqrt(3)) + >>> polygamma(0, 1/S(4)) + -pi/2 - log(4) - log(2) - EulerGamma + >>> polygamma(0, 2) + 1 - EulerGamma + >>> polygamma(0, 23) + 19093197/5173168 - EulerGamma + + >>> from sympy import oo, I + >>> polygamma(0, oo) + oo + >>> polygamma(0, -oo) + oo + >>> polygamma(0, I*oo) + oo + >>> polygamma(0, -I*oo) + oo + + Differentiation with respect to $x$ is supported: + + >>> from sympy import Symbol, diff + >>> x = Symbol("x") + >>> diff(polygamma(0, x), x) + polygamma(1, x) + >>> diff(polygamma(0, x), x, 2) + polygamma(2, x) + >>> diff(polygamma(0, x), x, 3) + polygamma(3, x) + >>> diff(polygamma(1, x), x) + polygamma(2, x) + >>> diff(polygamma(1, x), x, 2) + polygamma(3, x) + >>> diff(polygamma(2, x), x) + polygamma(3, x) + >>> diff(polygamma(2, x), x, 2) + polygamma(4, x) + + >>> n = Symbol("n") + >>> diff(polygamma(n, x), x) + polygamma(n + 1, x) + >>> diff(polygamma(n, x), x, 2) + polygamma(n + 2, x) + + We can rewrite ``polygamma`` functions in terms of harmonic numbers: + + >>> from sympy import harmonic + >>> polygamma(0, x).rewrite(harmonic) + harmonic(x - 1) - EulerGamma + >>> polygamma(2, x).rewrite(harmonic) + 2*harmonic(x - 1, 3) - 2*zeta(3) + >>> ni = Symbol("n", integer=True) + >>> polygamma(ni, x).rewrite(harmonic) + (-1)**(n + 1)*(-harmonic(x - 1, n + 1) + zeta(n + 1))*factorial(n) + + See Also + ======== + + gamma: Gamma function. + lowergamma: Lower incomplete gamma function. + uppergamma: Upper incomplete gamma function. + loggamma: Log Gamma function. + digamma: Digamma function. + trigamma: Trigamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Polygamma_function + .. [2] https://mathworld.wolfram.com/PolygammaFunction.html + .. [3] https://functions.wolfram.com/GammaBetaErf/PolyGamma/ + .. [4] https://functions.wolfram.com/GammaBetaErf/PolyGamma2/ + .. [5] O. Espinosa and V. Moll, "A generalized polygamma function", + *Integral Transforms and Special Functions* (2004), 101-115. + + """ + + @classmethod + def eval(cls, n, z): + if n is S.NaN or z is S.NaN: + return S.NaN + elif z is oo: + return oo if n.is_zero else S.Zero + elif z.is_Integer and z.is_nonpositive: + return S.ComplexInfinity + elif n is S.NegativeOne: + return loggamma(z) - log(2*pi) / 2 + elif n.is_zero: + if z is -oo or z.extract_multiplicatively(I) in (oo, -oo): + return oo + elif z.is_Integer: + return harmonic(z-1) - S.EulerGamma + elif z.is_Rational: + # TODO n == 1 also can do some rational z + p, q = z.as_numer_denom() + # only expand for small denominators to avoid creating long expressions + if q <= 6: + return expand_func(polygamma(S.Zero, z, evaluate=False)) + elif n.is_integer and n.is_nonnegative: + nz = unpolarify(z) + if z != nz: + return polygamma(n, nz) + if z.is_Integer: + return S.NegativeOne**(n+1) * factorial(n) * zeta(n+1, z) + elif z is S.Half: + return S.NegativeOne**(n+1) * factorial(n) * (2**(n+1)-1) * zeta(n+1) + + def _eval_is_real(self): + if self.args[0].is_positive and self.args[1].is_positive: + return True + + def _eval_is_complex(self): + z = self.args[1] + is_negative_integer = fuzzy_and([z.is_negative, z.is_integer]) + return fuzzy_and([z.is_complex, fuzzy_not(is_negative_integer)]) + + def _eval_is_positive(self): + n, z = self.args + if n.is_positive: + if n.is_odd and z.is_real: + return True + if n.is_even and z.is_positive: + return False + + def _eval_is_negative(self): + n, z = self.args + if n.is_positive: + if n.is_even and z.is_positive: + return True + if n.is_odd and z.is_real: + return False + + def _eval_expand_func(self, **hints): + n, z = self.args + + if n.is_Integer and n.is_nonnegative: + if z.is_Add: + coeff = z.args[0] + if coeff.is_Integer: + e = -(n + 1) + if coeff > 0: + tail = Add(*[Pow( + z - i, e) for i in range(1, int(coeff) + 1)]) + else: + tail = -Add(*[Pow( + z + i, e) for i in range(int(-coeff))]) + return polygamma(n, z - coeff) + S.NegativeOne**n*factorial(n)*tail + + elif z.is_Mul: + coeff, z = z.as_two_terms() + if coeff.is_Integer and coeff.is_positive: + tail = [polygamma(n, z + Rational( + i, coeff)) for i in range(int(coeff))] + if n == 0: + return Add(*tail)/coeff + log(coeff) + else: + return Add(*tail)/coeff**(n + 1) + z *= coeff + + if n == 0 and z.is_Rational: + p, q = z.as_numer_denom() + + # Reference: + # Values of the polygamma functions at rational arguments, J. Choi, 2007 + part_1 = -S.EulerGamma - pi * cot(p * pi / q) / 2 - log(q) + Add( + *[cos(2 * k * pi * p / q) * log(2 * sin(k * pi / q)) for k in range(1, q)]) + + if z > 0: + n = floor(z) + z0 = z - n + return part_1 + Add(*[1 / (z0 + k) for k in range(n)]) + elif z < 0: + n = floor(1 - z) + z0 = z + n + return part_1 - Add(*[1 / (z0 - 1 - k) for k in range(n)]) + + if n == -1: + return loggamma(z) - log(2*pi) / 2 + if n.is_integer is False or n.is_nonnegative is False: + s = Dummy("s") + dzt = zeta(s, z).diff(s).subs(s, n+1) + return (dzt + (S.EulerGamma + digamma(-n)) * zeta(n+1, z)) / gamma(-n) + + return polygamma(n, z) + + def _eval_rewrite_as_zeta(self, n, z, **kwargs): + if n.is_integer and n.is_positive: + return S.NegativeOne**(n + 1)*factorial(n)*zeta(n + 1, z) + + def _eval_rewrite_as_harmonic(self, n, z, **kwargs): + if n.is_integer: + if n.is_zero: + return harmonic(z - 1) - S.EulerGamma + else: + return S.NegativeOne**(n+1) * factorial(n) * (zeta(n+1) - harmonic(z-1, n+1)) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.series.order import Order + n, z = [a.as_leading_term(x) for a in self.args] + o = Order(z, x) + if n == 0 and o.contains(1/x): + logx = log(x) if logx is None else logx + return o.getn() * logx + else: + return self.func(n, z) + + def fdiff(self, argindex=2): + if argindex == 2: + n, z = self.args[:2] + return polygamma(n + 1, z) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + if args0[1] != oo or not \ + (self.args[0].is_Integer and self.args[0].is_nonnegative): + return super()._eval_aseries(n, args0, x, logx) + z = self.args[1] + N = self.args[0] + + if N == 0: + # digamma function series + # Abramowitz & Stegun, p. 259, 6.3.18 + r = log(z) - 1/(2*z) + o = None + if n < 2: + o = Order(1/z, x) + else: + m = ceiling((n + 1)//2) + l = [bernoulli(2*k) / (2*k*z**(2*k)) for k in range(1, m)] + r -= Add(*l) + o = Order(1/z**n, x) + return r._eval_nseries(x, n, logx) + o + else: + # proper polygamma function + # Abramowitz & Stegun, p. 260, 6.4.10 + # We return terms to order higher than O(x**n) on purpose + # -- otherwise we would not be able to return any terms for + # quite a long time! + fac = gamma(N) + e0 = fac + N*fac/(2*z) + m = ceiling((n + 1)//2) + for k in range(1, m): + fac = fac*(2*k + N - 1)*(2*k + N - 2) / ((2*k)*(2*k - 1)) + e0 += bernoulli(2*k)*fac/z**(2*k) + o = Order(1/z**(2*m), x) + if n == 0: + o = Order(1/z, x) + elif n == 1: + o = Order(1/z**2, x) + r = e0._eval_nseries(z, n, logx) + o + return (-1 * (-1/z)**N * r)._eval_nseries(x, n, logx) + + def _eval_evalf(self, prec): + if not all(i.is_number for i in self.args): + return + s = self.args[0]._to_mpmath(prec+12) + z = self.args[1]._to_mpmath(prec+12) + if mp.isint(z) and z <= 0: + return S.ComplexInfinity + with workprec(prec+12): + if mp.isint(s) and s >= 0: + res = mp.polygamma(s, z) + else: + zt = mp.zeta(s+1, z) + dzt = mp.zeta(s+1, z, 1) + res = (dzt + (mp.euler + mp.digamma(-s)) * zt) * mp.rgamma(-s) + return Expr._from_mpmath(res, prec) + + +class loggamma(DefinedFunction): + r""" + The ``loggamma`` function implements the logarithm of the + gamma function (i.e., $\log\Gamma(x)$). + + Examples + ======== + + Several special values are known. For numerical integral + arguments we have: + + >>> from sympy import loggamma + >>> loggamma(-2) + oo + >>> loggamma(0) + oo + >>> loggamma(1) + 0 + >>> loggamma(2) + 0 + >>> loggamma(3) + log(2) + + And for symbolic values: + + >>> from sympy import Symbol + >>> n = Symbol("n", integer=True, positive=True) + >>> loggamma(n) + log(gamma(n)) + >>> loggamma(-n) + oo + + For half-integral values: + + >>> from sympy import S + >>> loggamma(S(5)/2) + log(3*sqrt(pi)/4) + >>> loggamma(n/2) + log(2**(1 - n)*sqrt(pi)*gamma(n)/gamma(n/2 + 1/2)) + + And general rational arguments: + + >>> from sympy import expand_func + >>> L = loggamma(S(16)/3) + >>> expand_func(L).doit() + -5*log(3) + loggamma(1/3) + log(4) + log(7) + log(10) + log(13) + >>> L = loggamma(S(19)/4) + >>> expand_func(L).doit() + -4*log(4) + loggamma(3/4) + log(3) + log(7) + log(11) + log(15) + >>> L = loggamma(S(23)/7) + >>> expand_func(L).doit() + -3*log(7) + log(2) + loggamma(2/7) + log(9) + log(16) + + The ``loggamma`` function has the following limits towards infinity: + + >>> from sympy import oo + >>> loggamma(oo) + oo + >>> loggamma(-oo) + zoo + + The ``loggamma`` function obeys the mirror symmetry + if $x \in \mathbb{C} \setminus \{-\infty, 0\}$: + + >>> from sympy.abc import x + >>> from sympy import conjugate + >>> conjugate(loggamma(x)) + loggamma(conjugate(x)) + + Differentiation with respect to $x$ is supported: + + >>> from sympy import diff + >>> diff(loggamma(x), x) + polygamma(0, x) + + Series expansion is also supported: + + >>> from sympy import series + >>> series(loggamma(x), x, 0, 4).cancel() + -log(x) - EulerGamma*x + pi**2*x**2/12 - x**3*zeta(3)/3 + O(x**4) + + We can numerically evaluate the ``loggamma`` function + to arbitrary precision on the whole complex plane: + + >>> from sympy import I + >>> loggamma(5).evalf(30) + 3.17805383034794561964694160130 + >>> loggamma(I).evalf(20) + -0.65092319930185633889 - 1.8724366472624298171*I + + See Also + ======== + + gamma: Gamma function. + lowergamma: Lower incomplete gamma function. + uppergamma: Upper incomplete gamma function. + polygamma: Polygamma function. + digamma: Digamma function. + trigamma: Trigamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gamma_function + .. [2] https://dlmf.nist.gov/5 + .. [3] https://mathworld.wolfram.com/LogGammaFunction.html + .. [4] https://functions.wolfram.com/GammaBetaErf/LogGamma/ + + """ + @classmethod + def eval(cls, z): + if z.is_integer: + if z.is_nonpositive: + return oo + elif z.is_positive: + return log(gamma(z)) + elif z.is_rational: + p, q = z.as_numer_denom() + # Half-integral values: + if p.is_positive and q == 2: + return log(sqrt(pi) * 2**(1 - p) * gamma(p) / gamma((p + 1)*S.Half)) + + if z is oo: + return oo + elif abs(z) is oo: + return S.ComplexInfinity + if z is S.NaN: + return S.NaN + + def _eval_expand_func(self, **hints): + from sympy.concrete.summations import Sum + z = self.args[0] + + if z.is_Rational: + p, q = z.as_numer_denom() + # General rational arguments (u + p/q) + # Split z as n + p/q with p < q + n = p // q + p = p - n*q + if p.is_positive and q.is_positive and p < q: + k = Dummy("k") + if n.is_positive: + return loggamma(p / q) - n*log(q) + Sum(log((k - 1)*q + p), (k, 1, n)) + elif n.is_negative: + return loggamma(p / q) - n*log(q) + pi*I*n - Sum(log(k*q - p), (k, 1, -n)) + elif n.is_zero: + return loggamma(p / q) + + return self + + def _eval_nseries(self, x, n, logx=None, cdir=0): + x0 = self.args[0].limit(x, 0) + if x0.is_zero: + f = self._eval_rewrite_as_intractable(*self.args) + return f._eval_nseries(x, n, logx) + return super()._eval_nseries(x, n, logx) + + def _eval_aseries(self, n, args0, x, logx): + from sympy.series.order import Order + if args0[0] != oo: + return super()._eval_aseries(n, args0, x, logx) + z = self.args[0] + r = log(z)*(z - S.Half) - z + log(2*pi)/2 + l = [bernoulli(2*k) / (2*k*(2*k - 1)*z**(2*k - 1)) for k in range(1, n)] + o = None + if n == 0: + o = Order(1, x) + else: + o = Order(1/z**n, x) + # It is very inefficient to first add the order and then do the nseries + return (r + Add(*l))._eval_nseries(x, n, logx) + o + + def _eval_rewrite_as_intractable(self, z, **kwargs): + return log(gamma(z)) + + def _eval_is_real(self): + z = self.args[0] + if z.is_positive: + return True + elif z.is_nonpositive: + return False + + def _eval_conjugate(self): + z = self.args[0] + if z not in (S.Zero, S.NegativeInfinity): + return self.func(z.conjugate()) + + def fdiff(self, argindex=1): + if argindex == 1: + return polygamma(0, self.args[0]) + else: + raise ArgumentIndexError(self, argindex) + + +class digamma(DefinedFunction): + r""" + The ``digamma`` function is the first derivative of the ``loggamma`` + function + + .. math:: + \psi(x) := \frac{\mathrm{d}}{\mathrm{d} z} \log\Gamma(z) + = \frac{\Gamma'(z)}{\Gamma(z) }. + + In this case, ``digamma(z) = polygamma(0, z)``. + + Examples + ======== + + >>> from sympy import digamma + >>> digamma(0) + zoo + >>> from sympy import Symbol + >>> z = Symbol('z') + >>> digamma(z) + polygamma(0, z) + + To retain ``digamma`` as it is: + + >>> digamma(0, evaluate=False) + digamma(0) + >>> digamma(z, evaluate=False) + digamma(z) + + See Also + ======== + + gamma: Gamma function. + lowergamma: Lower incomplete gamma function. + uppergamma: Upper incomplete gamma function. + polygamma: Polygamma function. + loggamma: Log Gamma function. + trigamma: Trigamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Digamma_function + .. [2] https://mathworld.wolfram.com/DigammaFunction.html + .. [3] https://functions.wolfram.com/GammaBetaErf/PolyGamma2/ + + """ + def _eval_evalf(self, prec): + z = self.args[0] + nprec = prec_to_dps(prec) + return polygamma(0, z).evalf(n=nprec) + + def fdiff(self, argindex=1): + z = self.args[0] + return polygamma(0, z).fdiff() + + def _eval_is_real(self): + z = self.args[0] + return polygamma(0, z).is_real + + def _eval_is_positive(self): + z = self.args[0] + return polygamma(0, z).is_positive + + def _eval_is_negative(self): + z = self.args[0] + return polygamma(0, z).is_negative + + def _eval_aseries(self, n, args0, x, logx): + as_polygamma = self.rewrite(polygamma) + args0 = [S.Zero,] + args0 + return as_polygamma._eval_aseries(n, args0, x, logx) + + @classmethod + def eval(cls, z): + return polygamma(0, z) + + def _eval_expand_func(self, **hints): + z = self.args[0] + return polygamma(0, z).expand(func=True) + + def _eval_rewrite_as_harmonic(self, z, **kwargs): + return harmonic(z - 1) - S.EulerGamma + + def _eval_rewrite_as_polygamma(self, z, **kwargs): + return polygamma(0, z) + + def _eval_as_leading_term(self, x, logx, cdir): + z = self.args[0] + return polygamma(0, z).as_leading_term(x) + + + +class trigamma(DefinedFunction): + r""" + The ``trigamma`` function is the second derivative of the ``loggamma`` + function + + .. math:: + \psi^{(1)}(z) := \frac{\mathrm{d}^{2}}{\mathrm{d} z^{2}} \log\Gamma(z). + + In this case, ``trigamma(z) = polygamma(1, z)``. + + Examples + ======== + + >>> from sympy import trigamma + >>> trigamma(0) + zoo + >>> from sympy import Symbol + >>> z = Symbol('z') + >>> trigamma(z) + polygamma(1, z) + + To retain ``trigamma`` as it is: + + >>> trigamma(0, evaluate=False) + trigamma(0) + >>> trigamma(z, evaluate=False) + trigamma(z) + + + See Also + ======== + + gamma: Gamma function. + lowergamma: Lower incomplete gamma function. + uppergamma: Upper incomplete gamma function. + polygamma: Polygamma function. + loggamma: Log Gamma function. + digamma: Digamma function. + sympy.functions.special.beta_functions.beta: Euler Beta function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Trigamma_function + .. [2] https://mathworld.wolfram.com/TrigammaFunction.html + .. [3] https://functions.wolfram.com/GammaBetaErf/PolyGamma2/ + + """ + def _eval_evalf(self, prec): + z = self.args[0] + nprec = prec_to_dps(prec) + return polygamma(1, z).evalf(n=nprec) + + def fdiff(self, argindex=1): + z = self.args[0] + return polygamma(1, z).fdiff() + + def _eval_is_real(self): + z = self.args[0] + return polygamma(1, z).is_real + + def _eval_is_positive(self): + z = self.args[0] + return polygamma(1, z).is_positive + + def _eval_is_negative(self): + z = self.args[0] + return polygamma(1, z).is_negative + + def _eval_aseries(self, n, args0, x, logx): + as_polygamma = self.rewrite(polygamma) + args0 = [S.One,] + args0 + return as_polygamma._eval_aseries(n, args0, x, logx) + + @classmethod + def eval(cls, z): + return polygamma(1, z) + + def _eval_expand_func(self, **hints): + z = self.args[0] + return polygamma(1, z).expand(func=True) + + def _eval_rewrite_as_zeta(self, z, **kwargs): + return zeta(2, z) + + def _eval_rewrite_as_polygamma(self, z, **kwargs): + return polygamma(1, z) + + def _eval_rewrite_as_harmonic(self, z, **kwargs): + return -harmonic(z - 1, 2) + pi**2 / 6 + + def _eval_as_leading_term(self, x, logx, cdir): + z = self.args[0] + return polygamma(1, z).as_leading_term(x) + + +############################################################################### +##################### COMPLETE MULTIVARIATE GAMMA FUNCTION #################### +############################################################################### + + +class multigamma(DefinedFunction): + r""" + The multivariate gamma function is a generalization of the gamma function + + .. math:: + \Gamma_p(z) = \pi^{p(p-1)/4}\prod_{k=1}^p \Gamma[z + (1 - k)/2]. + + In a special case, ``multigamma(x, 1) = gamma(x)``. + + Examples + ======== + + >>> from sympy import S, multigamma + >>> from sympy import Symbol + >>> x = Symbol('x') + >>> p = Symbol('p', positive=True, integer=True) + + >>> multigamma(x, p) + pi**(p*(p - 1)/4)*Product(gamma(-_k/2 + x + 1/2), (_k, 1, p)) + + Several special values are known: + + >>> multigamma(1, 1) + 1 + >>> multigamma(4, 1) + 6 + >>> multigamma(S(3)/2, 1) + sqrt(pi)/2 + + Writing ``multigamma`` in terms of the ``gamma`` function: + + >>> multigamma(x, 1) + gamma(x) + + >>> multigamma(x, 2) + sqrt(pi)*gamma(x)*gamma(x - 1/2) + + >>> multigamma(x, 3) + pi**(3/2)*gamma(x)*gamma(x - 1)*gamma(x - 1/2) + + Parameters + ========== + + p : order or dimension of the multivariate gamma function + + See Also + ======== + + gamma, lowergamma, uppergamma, polygamma, loggamma, digamma, trigamma, + sympy.functions.special.beta_functions.beta + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Multivariate_gamma_function + + """ + unbranched = True + + def fdiff(self, argindex=2): + from sympy.concrete.summations import Sum + if argindex == 2: + x, p = self.args + k = Dummy("k") + return self.func(x, p)*Sum(polygamma(0, x + (1 - k)/2), (k, 1, p)) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, x, p): + from sympy.concrete.products import Product + if p.is_positive is False or p.is_integer is False: + raise ValueError('Order parameter p must be positive integer.') + k = Dummy("k") + return (pi**(p*(p - 1)/4)*Product(gamma(x + (1 - k)/2), + (k, 1, p))).doit() + + def _eval_conjugate(self): + x, p = self.args + return self.func(x.conjugate(), p) + + def _eval_is_real(self): + x, p = self.args + y = 2*x + if y.is_integer and (y <= (p - 1)) is True: + return False + if intlike(y) and (y <= (p - 1)): + return False + if y > (p - 1) or y.is_noninteger: + return True diff --git a/phivenv/Lib/site-packages/sympy/functions/special/hyper.py b/phivenv/Lib/site-packages/sympy/functions/special/hyper.py new file mode 100644 index 0000000000000000000000000000000000000000..3943e140821222a510c609a071b5dbbf08883745 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/hyper.py @@ -0,0 +1,1185 @@ +"""Hypergeometric and Meijer G-functions""" +from collections import Counter + +from sympy.core import S, Mod +from sympy.core.add import Add +from sympy.core.expr import Expr +from sympy.core.function import DefinedFunction, Derivative, ArgumentIndexError + +from sympy.core.containers import Tuple +from sympy.core.mul import Mul +from sympy.core.numbers import I, pi, oo, zoo +from sympy.core.parameters import global_parameters +from sympy.core.relational import Ne +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Dummy + +from sympy.external.gmpy import lcm +from sympy.functions import (sqrt, exp, log, sin, cos, asin, atan, + sinh, cosh, asinh, acosh, atanh, acoth) +from sympy.functions import factorial, RisingFactorial +from sympy.functions.elementary.complexes import Abs, re, unpolarify +from sympy.functions.elementary.exponential import exp_polar +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.piecewise import Piecewise +from sympy.logic.boolalg import (And, Or) +from sympy import ordered + + +class TupleArg(Tuple): + + # This method is only needed because hyper._eval_as_leading_term falls back + # (via super()) on using Function._eval_as_leading_term, which in turn + # calls as_leading_term on the args of the hyper. Ideally hyper should just + # have an _eval_as_leading_term method that handles all cases and this + # method should be removed because leading terms of tuples don't make + # sense. + def as_leading_term(self, *x, logx=None, cdir=0): + return TupleArg(*[f.as_leading_term(*x, logx=logx, cdir=cdir) for f in self.args]) + + def limit(self, x, xlim, dir='+'): + """ Compute limit x->xlim. + """ + from sympy.series.limits import limit + return TupleArg(*[limit(f, x, xlim, dir) for f in self.args]) + + +# TODO should __new__ accept **options? +# TODO should constructors should check if parameters are sensible? + + +def _prep_tuple(v): + """ + Turn an iterable argument *v* into a tuple and unpolarify, since both + hypergeometric and meijer g-functions are unbranched in their parameters. + + Examples + ======== + + >>> from sympy.functions.special.hyper import _prep_tuple + >>> _prep_tuple([1, 2, 3]) + (1, 2, 3) + >>> _prep_tuple((4, 5)) + (4, 5) + >>> _prep_tuple((7, 8, 9)) + (7, 8, 9) + + """ + return TupleArg(*[unpolarify(x) for x in v]) + + +class TupleParametersBase(DefinedFunction): + """ Base class that takes care of differentiation, when some of + the arguments are actually tuples. """ + # This is not deduced automatically since there are Tuples as arguments. + is_commutative = True + + def _eval_derivative(self, s): + try: + res = 0 + if self.args[0].has(s) or self.args[1].has(s): + for i, p in enumerate(self._diffargs): + m = self._diffargs[i].diff(s) + if m != 0: + res += self.fdiff((1, i))*m + return res + self.fdiff(3)*self.args[2].diff(s) + except (ArgumentIndexError, NotImplementedError): + return Derivative(self, s) + + +class hyper(TupleParametersBase): + r""" + The generalized hypergeometric function is defined by a series where + the ratios of successive terms are a rational function of the summation + index. When convergent, it is continued analytically to the largest + possible domain. + + Explanation + =========== + + The hypergeometric function depends on two vectors of parameters, called + the numerator parameters $a_p$, and the denominator parameters + $b_q$. It also has an argument $z$. The series definition is + + .. math :: + {}_pF_q\left(\begin{matrix} a_1, \cdots, a_p \\ b_1, \cdots, b_q \end{matrix} + \middle| z \right) + = \sum_{n=0}^\infty \frac{(a_1)_n \cdots (a_p)_n}{(b_1)_n \cdots (b_q)_n} + \frac{z^n}{n!}, + + where $(a)_n = (a)(a+1)\cdots(a+n-1)$ denotes the rising factorial. + + If one of the $b_q$ is a non-positive integer then the series is + undefined unless one of the $a_p$ is a larger (i.e., smaller in + magnitude) non-positive integer. If none of the $b_q$ is a + non-positive integer and one of the $a_p$ is a non-positive + integer, then the series reduces to a polynomial. To simplify the + following discussion, we assume that none of the $a_p$ or + $b_q$ is a non-positive integer. For more details, see the + references. + + The series converges for all $z$ if $p \le q$, and thus + defines an entire single-valued function in this case. If $p = + q+1$ the series converges for $|z| < 1$, and can be continued + analytically into a half-plane. If $p > q+1$ the series is + divergent for all $z$. + + Please note the hypergeometric function constructor currently does *not* + check if the parameters actually yield a well-defined function. + + Examples + ======== + + The parameters $a_p$ and $b_q$ can be passed as arbitrary + iterables, for example: + + >>> from sympy import hyper + >>> from sympy.abc import x, n, a + >>> h = hyper((1, 2, 3), [3, 4], x); h + hyper((1, 2), (4,), x) + >>> hyper((3, 1, 2), [3, 4], x, evaluate=False) # don't remove duplicates + hyper((1, 2, 3), (3, 4), x) + + There is also pretty printing (it looks better using Unicode): + + >>> from sympy import pprint + >>> pprint(h, use_unicode=False) + _ + |_ /1, 2 | \ + | | | x| + 2 1 \ 4 | / + + The parameters must always be iterables, even if they are vectors of + length one or zero: + + >>> hyper((1, ), [], x) + hyper((1,), (), x) + + But of course they may be variables (but if they depend on $x$ then you + should not expect much implemented functionality): + + >>> hyper((n, a), (n**2,), x) + hyper((a, n), (n**2,), x) + + The hypergeometric function generalizes many named special functions. + The function ``hyperexpand()`` tries to express a hypergeometric function + using named special functions. For example: + + >>> from sympy import hyperexpand + >>> hyperexpand(hyper([], [], x)) + exp(x) + + You can also use ``expand_func()``: + + >>> from sympy import expand_func + >>> expand_func(x*hyper([1, 1], [2], -x)) + log(x + 1) + + More examples: + + >>> from sympy import S + >>> hyperexpand(hyper([], [S(1)/2], -x**2/4)) + cos(x) + >>> hyperexpand(x*hyper([S(1)/2, S(1)/2], [S(3)/2], x**2)) + asin(x) + + We can also sometimes ``hyperexpand()`` parametric functions: + + >>> from sympy.abc import a + >>> hyperexpand(hyper([-a], [], x)) + (1 - x)**a + + See Also + ======== + + sympy.simplify.hyperexpand + gamma + meijerg + + References + ========== + + .. [1] Luke, Y. L. (1969), The Special Functions and Their Approximations, + Volume 1 + .. [2] https://en.wikipedia.org/wiki/Generalized_hypergeometric_function + + """ + + + def __new__(cls, ap, bq, z, **kwargs): + # TODO should we check convergence conditions? + if kwargs.pop('evaluate', global_parameters.evaluate): + ca = Counter(Tuple(*ap)) + cb = Counter(Tuple(*bq)) + common = ca & cb + arg = ap, bq = [], [] + for i, c in enumerate((ca, cb)): + c -= common + for k in ordered(c): + arg[i].extend([k]*c[k]) + else: + ap = list(ordered(ap)) + bq = list(ordered(bq)) + return super().__new__(cls, _prep_tuple(ap), _prep_tuple(bq), z, **kwargs) + + @classmethod + def eval(cls, ap, bq, z): + if len(ap) <= len(bq) or (len(ap) == len(bq) + 1 and (Abs(z) <= 1) == True): + nz = unpolarify(z) + if z != nz: + return hyper(ap, bq, nz) + + def fdiff(self, argindex=3): + if argindex != 3: + raise ArgumentIndexError(self, argindex) + nap = Tuple(*[a + 1 for a in self.ap]) + nbq = Tuple(*[b + 1 for b in self.bq]) + fac = Mul(*self.ap)/Mul(*self.bq) + return fac*hyper(nap, nbq, self.argument) + + def _eval_expand_func(self, **hints): + from sympy.functions.special.gamma_functions import gamma + from sympy.simplify.hyperexpand import hyperexpand + if len(self.ap) == 2 and len(self.bq) == 1 and self.argument == 1: + a, b = self.ap + c = self.bq[0] + return gamma(c)*gamma(c - a - b)/gamma(c - a)/gamma(c - b) + return hyperexpand(self) + + def _eval_rewrite_as_Sum(self, ap, bq, z, **kwargs): + from sympy.concrete.summations import Sum + n = Dummy("n", integer=True) + rfap = [RisingFactorial(a, n) for a in ap] + rfbq = [RisingFactorial(b, n) for b in bq] + coeff = Mul(*rfap) / Mul(*rfbq) + return Piecewise((Sum(coeff * z**n / factorial(n), (n, 0, oo)), + self.convergence_statement), (self, True)) + + def _eval_as_leading_term(self, x, logx, cdir): + arg = self.args[2] + x0 = arg.subs(x, 0) + if x0 is S.NaN: + x0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + + if x0 is S.Zero: + return S.One + return super()._eval_as_leading_term(x, logx=logx, cdir=cdir) + + def _eval_nseries(self, x, n, logx, cdir=0): + + from sympy.series.order import Order + + arg = self.args[2] + x0 = arg.limit(x, 0) + ap = self.args[0] + bq = self.args[1] + + if not (arg == x and x0 == 0): + # It would be better to do something with arg.nseries here, rather + # than falling back on Function._eval_nseries. The code below + # though is not sufficient if arg is something like x/(x+1). + from sympy.simplify.hyperexpand import hyperexpand + return hyperexpand(super()._eval_nseries(x, n, logx)) + + terms = [] + + for i in range(n): + num = Mul(*[RisingFactorial(a, i) for a in ap]) + den = Mul(*[RisingFactorial(b, i) for b in bq]) + terms.append(((num/den) * (arg**i)) / factorial(i)) + + return (Add(*terms) + Order(x**n,x)) + + @property + def argument(self): + """ Argument of the hypergeometric function. """ + return self.args[2] + + @property + def ap(self): + """ Numerator parameters of the hypergeometric function. """ + return Tuple(*self.args[0]) + + @property + def bq(self): + """ Denominator parameters of the hypergeometric function. """ + return Tuple(*self.args[1]) + + @property + def _diffargs(self): + return self.ap + self.bq + + @property + def eta(self): + """ A quantity related to the convergence of the series. """ + return sum(self.ap) - sum(self.bq) + + @property + def radius_of_convergence(self): + """ + Compute the radius of convergence of the defining series. + + Explanation + =========== + + Note that even if this is not ``oo``, the function may still be + evaluated outside of the radius of convergence by analytic + continuation. But if this is zero, then the function is not actually + defined anywhere else. + + Examples + ======== + + >>> from sympy import hyper + >>> from sympy.abc import z + >>> hyper((1, 2), [3], z).radius_of_convergence + 1 + >>> hyper((1, 2, 3), [4], z).radius_of_convergence + 0 + >>> hyper((1, 2), (3, 4), z).radius_of_convergence + oo + + """ + if any(a.is_integer and (a <= 0) == True for a in self.ap + self.bq): + aints = [a for a in self.ap if a.is_Integer and (a <= 0) == True] + bints = [a for a in self.bq if a.is_Integer and (a <= 0) == True] + if len(aints) < len(bints): + return S.Zero + popped = False + for b in bints: + cancelled = False + while aints: + a = aints.pop() + if a >= b: + cancelled = True + break + popped = True + if not cancelled: + return S.Zero + if aints or popped: + # There are still non-positive numerator parameters. + # This is a polynomial. + return oo + if len(self.ap) == len(self.bq) + 1: + return S.One + elif len(self.ap) <= len(self.bq): + return oo + else: + return S.Zero + + @property + def convergence_statement(self): + """ Return a condition on z under which the series converges. """ + R = self.radius_of_convergence + if R == 0: + return False + if R == oo: + return True + # The special functions and their approximations, page 44 + e = self.eta + z = self.argument + c1 = And(re(e) < 0, abs(z) <= 1) + c2 = And(0 <= re(e), re(e) < 1, abs(z) <= 1, Ne(z, 1)) + c3 = And(re(e) >= 1, abs(z) < 1) + return Or(c1, c2, c3) + + def _eval_simplify(self, **kwargs): + from sympy.simplify.hyperexpand import hyperexpand + return hyperexpand(self) + + +class meijerg(TupleParametersBase): + r""" + The Meijer G-function is defined by a Mellin-Barnes type integral that + resembles an inverse Mellin transform. It generalizes the hypergeometric + functions. + + Explanation + =========== + + The Meijer G-function depends on four sets of parameters. There are + "*numerator parameters*" + $a_1, \ldots, a_n$ and $a_{n+1}, \ldots, a_p$, and there are + "*denominator parameters*" + $b_1, \ldots, b_m$ and $b_{m+1}, \ldots, b_q$. + Confusingly, it is traditionally denoted as follows (note the position + of $m$, $n$, $p$, $q$, and how they relate to the lengths of the four + parameter vectors): + + .. math :: + G_{p,q}^{m,n} \left(\begin{matrix}a_1, \cdots, a_n & a_{n+1}, \cdots, a_p \\ + b_1, \cdots, b_m & b_{m+1}, \cdots, b_q + \end{matrix} \middle| z \right). + + However, in SymPy the four parameter vectors are always available + separately (see examples), so that there is no need to keep track of the + decorating sub- and super-scripts on the G symbol. + + The G function is defined as the following integral: + + .. math :: + \frac{1}{2 \pi i} \int_L \frac{\prod_{j=1}^m \Gamma(b_j - s) + \prod_{j=1}^n \Gamma(1 - a_j + s)}{\prod_{j=m+1}^q \Gamma(1- b_j +s) + \prod_{j=n+1}^p \Gamma(a_j - s)} z^s \mathrm{d}s, + + where $\Gamma(z)$ is the gamma function. There are three possible + contours which we will not describe in detail here (see the references). + If the integral converges along more than one of them, the definitions + agree. The contours all separate the poles of $\Gamma(1-a_j+s)$ + from the poles of $\Gamma(b_k-s)$, so in particular the G function + is undefined if $a_j - b_k \in \mathbb{Z}_{>0}$ for some + $j \le n$ and $k \le m$. + + The conditions under which one of the contours yields a convergent integral + are complicated and we do not state them here, see the references. + + Please note currently the Meijer G-function constructor does *not* check any + convergence conditions. + + Examples + ======== + + You can pass the parameters either as four separate vectors: + + >>> from sympy import meijerg, Tuple, pprint + >>> from sympy.abc import x, a + >>> pprint(meijerg((1, 2), (a, 4), (5,), [], x), use_unicode=False) + __1, 2 /1, 2 4, a | \ + /__ | | x| + \_|4, 1 \ 5 | / + + Or as two nested vectors: + + >>> pprint(meijerg([(1, 2), (3, 4)], ([5], Tuple()), x), use_unicode=False) + __1, 2 /1, 2 3, 4 | \ + /__ | | x| + \_|4, 1 \ 5 | / + + As with the hypergeometric function, the parameters may be passed as + arbitrary iterables. Vectors of length zero and one also have to be + passed as iterables. The parameters need not be constants, but if they + depend on the argument then not much implemented functionality should be + expected. + + All the subvectors of parameters are available: + + >>> from sympy import pprint + >>> g = meijerg([1], [2], [3], [4], x) + >>> pprint(g, use_unicode=False) + __1, 1 /1 2 | \ + /__ | | x| + \_|2, 2 \3 4 | / + >>> g.an + (1,) + >>> g.ap + (1, 2) + >>> g.aother + (2,) + >>> g.bm + (3,) + >>> g.bq + (3, 4) + >>> g.bother + (4,) + + The Meijer G-function generalizes the hypergeometric functions. + In some cases it can be expressed in terms of hypergeometric functions, + using Slater's theorem. For example: + + >>> from sympy import hyperexpand + >>> from sympy.abc import a, b, c + >>> hyperexpand(meijerg([a], [], [c], [b], x), allow_hyper=True) + x**c*gamma(-a + c + 1)*hyper((-a + c + 1,), + (-b + c + 1,), -x)/gamma(-b + c + 1) + + Thus the Meijer G-function also subsumes many named functions as special + cases. You can use ``expand_func()`` or ``hyperexpand()`` to (try to) + rewrite a Meijer G-function in terms of named special functions. For + example: + + >>> from sympy import expand_func, S + >>> expand_func(meijerg([[],[]], [[0],[]], -x)) + exp(x) + >>> hyperexpand(meijerg([[],[]], [[S(1)/2],[0]], (x/2)**2)) + sin(x)/sqrt(pi) + + See Also + ======== + + hyper + sympy.simplify.hyperexpand + + References + ========== + + .. [1] Luke, Y. L. (1969), The Special Functions and Their Approximations, + Volume 1 + .. [2] https://en.wikipedia.org/wiki/Meijer_G-function + + """ + + + def __new__(cls, *args, **kwargs): + if len(args) == 5: + args = [(args[0], args[1]), (args[2], args[3]), args[4]] + if len(args) != 3: + raise TypeError("args must be either as, as', bs, bs', z or " + "as, bs, z") + + def tr(p): + if len(p) != 2: + raise TypeError("wrong argument") + p = [list(ordered(i)) for i in p] + return TupleArg(_prep_tuple(p[0]), _prep_tuple(p[1])) + + arg0, arg1 = tr(args[0]), tr(args[1]) + if Tuple(arg0, arg1).has(oo, zoo, -oo): + raise ValueError("G-function parameters must be finite") + if any((a - b).is_Integer and a - b > 0 + for a in arg0[0] for b in arg1[0]): + raise ValueError("no parameter a1, ..., an may differ from " + "any b1, ..., bm by a positive integer") + + # TODO should we check convergence conditions? + return super().__new__(cls, arg0, arg1, args[2], **kwargs) + + def fdiff(self, argindex=3): + if argindex != 3: + return self._diff_wrt_parameter(argindex[1]) + if len(self.an) >= 1: + a = list(self.an) + a[0] -= 1 + G = meijerg(a, self.aother, self.bm, self.bother, self.argument) + return 1/self.argument * ((self.an[0] - 1)*self + G) + elif len(self.bm) >= 1: + b = list(self.bm) + b[0] += 1 + G = meijerg(self.an, self.aother, b, self.bother, self.argument) + return 1/self.argument * (self.bm[0]*self - G) + else: + return S.Zero + + def _diff_wrt_parameter(self, idx): + # Differentiation wrt a parameter can only be done in very special + # cases. In particular, if we want to differentiate with respect to + # `a`, all other gamma factors have to reduce to rational functions. + # + # Let MT denote mellin transform. Suppose T(-s) is the gamma factor + # appearing in the definition of G. Then + # + # MT(log(z)G(z)) = d/ds T(s) = d/da T(s) + ... + # + # Thus d/da G(z) = log(z)G(z) - ... + # The ... can be evaluated as a G function under the above conditions, + # the formula being most easily derived by using + # + # d Gamma(s + n) Gamma(s + n) / 1 1 1 \ + # -- ------------ = ------------ | - + ---- + ... + --------- | + # ds Gamma(s) Gamma(s) \ s s + 1 s + n - 1 / + # + # which follows from the difference equation of the digamma function. + # (There is a similar equation for -n instead of +n). + + # We first figure out how to pair the parameters. + an = list(self.an) + ap = list(self.aother) + bm = list(self.bm) + bq = list(self.bother) + if idx < len(an): + an.pop(idx) + else: + idx -= len(an) + if idx < len(ap): + ap.pop(idx) + else: + idx -= len(ap) + if idx < len(bm): + bm.pop(idx) + else: + bq.pop(idx - len(bm)) + pairs1 = [] + pairs2 = [] + for l1, l2, pairs in [(an, bq, pairs1), (ap, bm, pairs2)]: + while l1: + x = l1.pop() + found = None + for i, y in enumerate(l2): + if not Mod((x - y).simplify(), 1): + found = i + break + if found is None: + raise NotImplementedError('Derivative not expressible ' + 'as G-function?') + y = l2[i] + l2.pop(i) + pairs.append((x, y)) + + # Now build the result. + res = log(self.argument)*self + + for a, b in pairs1: + sign = 1 + n = a - b + base = b + if n < 0: + sign = -1 + n = b - a + base = a + for k in range(n): + res -= sign*meijerg(self.an + (base + k + 1,), self.aother, + self.bm, self.bother + (base + k + 0,), + self.argument) + + for a, b in pairs2: + sign = 1 + n = b - a + base = a + if n < 0: + sign = -1 + n = a - b + base = b + for k in range(n): + res -= sign*meijerg(self.an, self.aother + (base + k + 1,), + self.bm + (base + k + 0,), self.bother, + self.argument) + + return res + + def get_period(self): + """ + Return a number $P$ such that $G(x*exp(I*P)) == G(x)$. + + Examples + ======== + + >>> from sympy import meijerg, pi, S + >>> from sympy.abc import z + + >>> meijerg([1], [], [], [], z).get_period() + 2*pi + >>> meijerg([pi], [], [], [], z).get_period() + oo + >>> meijerg([1, 2], [], [], [], z).get_period() + oo + >>> meijerg([1,1], [2], [1, S(1)/2, S(1)/3], [1], z).get_period() + 12*pi + + """ + # This follows from slater's theorem. + def compute(l): + # first check that no two differ by an integer + for i, b in enumerate(l): + if not b.is_Rational: + return oo + for j in range(i + 1, len(l)): + if not Mod((b - l[j]).simplify(), 1): + return oo + return lcm(*(x.q for x in l)) + beta = compute(self.bm) + alpha = compute(self.an) + p, q = len(self.ap), len(self.bq) + if p == q: + if oo in (alpha, beta): + return oo + return 2*pi*lcm(alpha, beta) + elif p < q: + return 2*pi*beta + else: + return 2*pi*alpha + + def _eval_expand_func(self, **hints): + from sympy.simplify.hyperexpand import hyperexpand + return hyperexpand(self) + + def _eval_evalf(self, prec): + # The default code is insufficient for polar arguments. + # mpmath provides an optional argument "r", which evaluates + # G(z**(1/r)). I am not sure what its intended use is, but we hijack it + # here in the following way: to evaluate at a number z of |argument| + # less than (say) n*pi, we put r=1/n, compute z' = root(z, n) + # (carefully so as not to loose the branch information), and evaluate + # G(z'**(1/r)) = G(z'**n) = G(z). + import mpmath + znum = self.argument._eval_evalf(prec) + if znum.has(exp_polar): + znum, branch = znum.as_coeff_mul(exp_polar) + if len(branch) != 1: + return + branch = branch[0].args[0]/I + else: + branch = S.Zero + n = ceiling(abs(branch/pi)) + 1 + znum = znum**(S.One/n)*exp(I*branch / n) + + # Convert all args to mpf or mpc + try: + [z, r, ap, bq] = [arg._to_mpmath(prec) + for arg in [znum, 1/n, self.args[0], self.args[1]]] + except ValueError: + return + + with mpmath.workprec(prec): + v = mpmath.meijerg(ap, bq, z, r) + + return Expr._from_mpmath(v, prec) + + def _eval_as_leading_term(self, x, logx, cdir): + from sympy.simplify.hyperexpand import hyperexpand + return hyperexpand(self).as_leading_term(x, logx=logx, cdir=cdir) + + def integrand(self, s): + """ Get the defining integrand D(s). """ + from sympy.functions.special.gamma_functions import gamma + return self.argument**s \ + * Mul(*(gamma(b - s) for b in self.bm)) \ + * Mul(*(gamma(1 - a + s) for a in self.an)) \ + / Mul(*(gamma(1 - b + s) for b in self.bother)) \ + / Mul(*(gamma(a - s) for a in self.aother)) + + @property + def argument(self): + """ Argument of the Meijer G-function. """ + return self.args[2] + + @property + def an(self): + """ First set of numerator parameters. """ + return Tuple(*self.args[0][0]) + + @property + def ap(self): + """ Combined numerator parameters. """ + return Tuple(*(self.args[0][0] + self.args[0][1])) + + @property + def aother(self): + """ Second set of numerator parameters. """ + return Tuple(*self.args[0][1]) + + @property + def bm(self): + """ First set of denominator parameters. """ + return Tuple(*self.args[1][0]) + + @property + def bq(self): + """ Combined denominator parameters. """ + return Tuple(*(self.args[1][0] + self.args[1][1])) + + @property + def bother(self): + """ Second set of denominator parameters. """ + return Tuple(*self.args[1][1]) + + @property + def _diffargs(self): + return self.ap + self.bq + + @property + def nu(self): + """ A quantity related to the convergence region of the integral, + c.f. references. """ + return sum(self.bq) - sum(self.ap) + + @property + def delta(self): + """ A quantity related to the convergence region of the integral, + c.f. references. """ + return len(self.bm) + len(self.an) - S(len(self.ap) + len(self.bq))/2 + + @property + def is_number(self): + """ Returns true if expression has numeric data only. """ + return not self.free_symbols + + +class HyperRep(DefinedFunction): + """ + A base class for "hyper representation functions". + + This is used exclusively in ``hyperexpand()``, but fits more logically here. + + pFq is branched at 1 if p == q+1. For use with slater-expansion, we want + define an "analytic continuation" to all polar numbers, which is + continuous on circles and on the ray t*exp_polar(I*pi). Moreover, we want + a "nice" expression for the various cases. + + This base class contains the core logic, concrete derived classes only + supply the actual functions. + + """ + + + @classmethod + def eval(cls, *args): + newargs = tuple(map(unpolarify, args[:-1])) + args[-1:] + if args != newargs: + return cls(*newargs) + + @classmethod + def _expr_small(cls, x): + """ An expression for F(x) which holds for |x| < 1. """ + raise NotImplementedError + + @classmethod + def _expr_small_minus(cls, x): + """ An expression for F(-x) which holds for |x| < 1. """ + raise NotImplementedError + + @classmethod + def _expr_big(cls, x, n): + """ An expression for F(exp_polar(2*I*pi*n)*x), |x| > 1. """ + raise NotImplementedError + + @classmethod + def _expr_big_minus(cls, x, n): + """ An expression for F(exp_polar(2*I*pi*n + pi*I)*x), |x| > 1. """ + raise NotImplementedError + + def _eval_rewrite_as_nonrep(self, *args, **kwargs): + x, n = self.args[-1].extract_branch_factor(allow_half=True) + minus = False + newargs = self.args[:-1] + (x,) + if not n.is_Integer: + minus = True + n -= S.Half + newerargs = newargs + (n,) + if minus: + small = self._expr_small_minus(*newargs) + big = self._expr_big_minus(*newerargs) + else: + small = self._expr_small(*newargs) + big = self._expr_big(*newerargs) + + if big == small: + return small + return Piecewise((big, abs(x) > 1), (small, True)) + + def _eval_rewrite_as_nonrepsmall(self, *args, **kwargs): + x, n = self.args[-1].extract_branch_factor(allow_half=True) + args = self.args[:-1] + (x,) + if not n.is_Integer: + return self._expr_small_minus(*args) + return self._expr_small(*args) + + +class HyperRep_power1(HyperRep): + """ Return a representative for hyper([-a], [], z) == (1 - z)**a. """ + + @classmethod + def _expr_small(cls, a, x): + return (1 - x)**a + + @classmethod + def _expr_small_minus(cls, a, x): + return (1 + x)**a + + @classmethod + def _expr_big(cls, a, x, n): + if a.is_integer: + return cls._expr_small(a, x) + return (x - 1)**a*exp((2*n - 1)*pi*I*a) + + @classmethod + def _expr_big_minus(cls, a, x, n): + if a.is_integer: + return cls._expr_small_minus(a, x) + return (1 + x)**a*exp(2*n*pi*I*a) + + +class HyperRep_power2(HyperRep): + """ Return a representative for hyper([a, a - 1/2], [2*a], z). """ + + @classmethod + def _expr_small(cls, a, x): + return 2**(2*a - 1)*(1 + sqrt(1 - x))**(1 - 2*a) + + @classmethod + def _expr_small_minus(cls, a, x): + return 2**(2*a - 1)*(1 + sqrt(1 + x))**(1 - 2*a) + + @classmethod + def _expr_big(cls, a, x, n): + sgn = -1 + if n.is_odd: + sgn = 1 + n -= 1 + return 2**(2*a - 1)*(1 + sgn*I*sqrt(x - 1))**(1 - 2*a) \ + *exp(-2*n*pi*I*a) + + @classmethod + def _expr_big_minus(cls, a, x, n): + sgn = 1 + if n.is_odd: + sgn = -1 + return sgn*2**(2*a - 1)*(sqrt(1 + x) + sgn)**(1 - 2*a)*exp(-2*pi*I*a*n) + + +class HyperRep_log1(HyperRep): + """ Represent -z*hyper([1, 1], [2], z) == log(1 - z). """ + @classmethod + def _expr_small(cls, x): + return log(1 - x) + + @classmethod + def _expr_small_minus(cls, x): + return log(1 + x) + + @classmethod + def _expr_big(cls, x, n): + return log(x - 1) + (2*n - 1)*pi*I + + @classmethod + def _expr_big_minus(cls, x, n): + return log(1 + x) + 2*n*pi*I + + +class HyperRep_atanh(HyperRep): + """ Represent hyper([1/2, 1], [3/2], z) == atanh(sqrt(z))/sqrt(z). """ + @classmethod + def _expr_small(cls, x): + return atanh(sqrt(x))/sqrt(x) + + def _expr_small_minus(cls, x): + return atan(sqrt(x))/sqrt(x) + + def _expr_big(cls, x, n): + if n.is_even: + return (acoth(sqrt(x)) + I*pi/2)/sqrt(x) + else: + return (acoth(sqrt(x)) - I*pi/2)/sqrt(x) + + def _expr_big_minus(cls, x, n): + if n.is_even: + return atan(sqrt(x))/sqrt(x) + else: + return (atan(sqrt(x)) - pi)/sqrt(x) + + +class HyperRep_asin1(HyperRep): + """ Represent hyper([1/2, 1/2], [3/2], z) == asin(sqrt(z))/sqrt(z). """ + @classmethod + def _expr_small(cls, z): + return asin(sqrt(z))/sqrt(z) + + @classmethod + def _expr_small_minus(cls, z): + return asinh(sqrt(z))/sqrt(z) + + @classmethod + def _expr_big(cls, z, n): + return S.NegativeOne**n*((S.Half - n)*pi/sqrt(z) + I*acosh(sqrt(z))/sqrt(z)) + + @classmethod + def _expr_big_minus(cls, z, n): + return S.NegativeOne**n*(asinh(sqrt(z))/sqrt(z) + n*pi*I/sqrt(z)) + + +class HyperRep_asin2(HyperRep): + """ Represent hyper([1, 1], [3/2], z) == asin(sqrt(z))/sqrt(z)/sqrt(1-z). """ + # TODO this can be nicer + @classmethod + def _expr_small(cls, z): + return HyperRep_asin1._expr_small(z) \ + /HyperRep_power1._expr_small(S.Half, z) + + @classmethod + def _expr_small_minus(cls, z): + return HyperRep_asin1._expr_small_minus(z) \ + /HyperRep_power1._expr_small_minus(S.Half, z) + + @classmethod + def _expr_big(cls, z, n): + return HyperRep_asin1._expr_big(z, n) \ + /HyperRep_power1._expr_big(S.Half, z, n) + + @classmethod + def _expr_big_minus(cls, z, n): + return HyperRep_asin1._expr_big_minus(z, n) \ + /HyperRep_power1._expr_big_minus(S.Half, z, n) + + +class HyperRep_sqrts1(HyperRep): + """ Return a representative for hyper([-a, 1/2 - a], [1/2], z). """ + + @classmethod + def _expr_small(cls, a, z): + return ((1 - sqrt(z))**(2*a) + (1 + sqrt(z))**(2*a))/2 + + @classmethod + def _expr_small_minus(cls, a, z): + return (1 + z)**a*cos(2*a*atan(sqrt(z))) + + @classmethod + def _expr_big(cls, a, z, n): + if n.is_even: + return ((sqrt(z) + 1)**(2*a)*exp(2*pi*I*n*a) + + (sqrt(z) - 1)**(2*a)*exp(2*pi*I*(n - 1)*a))/2 + else: + n -= 1 + return ((sqrt(z) - 1)**(2*a)*exp(2*pi*I*a*(n + 1)) + + (sqrt(z) + 1)**(2*a)*exp(2*pi*I*a*n))/2 + + @classmethod + def _expr_big_minus(cls, a, z, n): + if n.is_even: + return (1 + z)**a*exp(2*pi*I*n*a)*cos(2*a*atan(sqrt(z))) + else: + return (1 + z)**a*exp(2*pi*I*n*a)*cos(2*a*atan(sqrt(z)) - 2*pi*a) + + +class HyperRep_sqrts2(HyperRep): + """ Return a representative for + sqrt(z)/2*[(1-sqrt(z))**2a - (1 + sqrt(z))**2a] + == -2*z/(2*a+1) d/dz hyper([-a - 1/2, -a], [1/2], z)""" + + @classmethod + def _expr_small(cls, a, z): + return sqrt(z)*((1 - sqrt(z))**(2*a) - (1 + sqrt(z))**(2*a))/2 + + @classmethod + def _expr_small_minus(cls, a, z): + return sqrt(z)*(1 + z)**a*sin(2*a*atan(sqrt(z))) + + @classmethod + def _expr_big(cls, a, z, n): + if n.is_even: + return sqrt(z)/2*((sqrt(z) - 1)**(2*a)*exp(2*pi*I*a*(n - 1)) - + (sqrt(z) + 1)**(2*a)*exp(2*pi*I*a*n)) + else: + n -= 1 + return sqrt(z)/2*((sqrt(z) - 1)**(2*a)*exp(2*pi*I*a*(n + 1)) - + (sqrt(z) + 1)**(2*a)*exp(2*pi*I*a*n)) + + def _expr_big_minus(cls, a, z, n): + if n.is_even: + return (1 + z)**a*exp(2*pi*I*n*a)*sqrt(z)*sin(2*a*atan(sqrt(z))) + else: + return (1 + z)**a*exp(2*pi*I*n*a)*sqrt(z) \ + *sin(2*a*atan(sqrt(z)) - 2*pi*a) + + +class HyperRep_log2(HyperRep): + """ Represent log(1/2 + sqrt(1 - z)/2) == -z/4*hyper([3/2, 1, 1], [2, 2], z) """ + + @classmethod + def _expr_small(cls, z): + return log(S.Half + sqrt(1 - z)/2) + + @classmethod + def _expr_small_minus(cls, z): + return log(S.Half + sqrt(1 + z)/2) + + @classmethod + def _expr_big(cls, z, n): + if n.is_even: + return (n - S.Half)*pi*I + log(sqrt(z)/2) + I*asin(1/sqrt(z)) + else: + return (n - S.Half)*pi*I + log(sqrt(z)/2) - I*asin(1/sqrt(z)) + + def _expr_big_minus(cls, z, n): + if n.is_even: + return pi*I*n + log(S.Half + sqrt(1 + z)/2) + else: + return pi*I*n + log(sqrt(1 + z)/2 - S.Half) + + +class HyperRep_cosasin(HyperRep): + """ Represent hyper([a, -a], [1/2], z) == cos(2*a*asin(sqrt(z))). """ + # Note there are many alternative expressions, e.g. as powers of a sum of + # square roots. + + @classmethod + def _expr_small(cls, a, z): + return cos(2*a*asin(sqrt(z))) + + @classmethod + def _expr_small_minus(cls, a, z): + return cosh(2*a*asinh(sqrt(z))) + + @classmethod + def _expr_big(cls, a, z, n): + return cosh(2*a*acosh(sqrt(z)) + a*pi*I*(2*n - 1)) + + @classmethod + def _expr_big_minus(cls, a, z, n): + return cosh(2*a*asinh(sqrt(z)) + 2*a*pi*I*n) + + +class HyperRep_sinasin(HyperRep): + """ Represent 2*a*z*hyper([1 - a, 1 + a], [3/2], z) + == sqrt(z)/sqrt(1-z)*sin(2*a*asin(sqrt(z))) """ + + @classmethod + def _expr_small(cls, a, z): + return sqrt(z)/sqrt(1 - z)*sin(2*a*asin(sqrt(z))) + + @classmethod + def _expr_small_minus(cls, a, z): + return -sqrt(z)/sqrt(1 + z)*sinh(2*a*asinh(sqrt(z))) + + @classmethod + def _expr_big(cls, a, z, n): + return -1/sqrt(1 - 1/z)*sinh(2*a*acosh(sqrt(z)) + a*pi*I*(2*n - 1)) + + @classmethod + def _expr_big_minus(cls, a, z, n): + return -1/sqrt(1 + 1/z)*sinh(2*a*asinh(sqrt(z)) + 2*a*pi*I*n) + +class appellf1(DefinedFunction): + r""" + This is the Appell hypergeometric function of two variables as: + + .. math :: + F_1(a,b_1,b_2,c,x,y) = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b_1)_m (b_2)_n}{(c)_{m+n}} + \frac{x^m y^n}{m! n!}. + + Examples + ======== + + >>> from sympy import appellf1, symbols + >>> x, y, a, b1, b2, c = symbols('x y a b1 b2 c') + >>> appellf1(2., 1., 6., 4., 5., 6.) + 0.0063339426292673 + >>> appellf1(12., 12., 6., 4., 0.5, 0.12) + 172870711.659936 + >>> appellf1(40, 2, 6, 4, 15, 60) + appellf1(40, 2, 6, 4, 15, 60) + >>> appellf1(20., 12., 10., 3., 0.5, 0.12) + 15605338197184.4 + >>> appellf1(40, 2, 6, 4, x, y) + appellf1(40, 2, 6, 4, x, y) + >>> appellf1(a, b1, b2, c, x, y) + appellf1(a, b1, b2, c, x, y) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Appell_series + .. [2] https://functions.wolfram.com/HypergeometricFunctions/AppellF1/ + + """ + + @classmethod + def eval(cls, a, b1, b2, c, x, y): + if default_sort_key(b1) > default_sort_key(b2): + b1, b2 = b2, b1 + x, y = y, x + return cls(a, b1, b2, c, x, y) + elif b1 == b2 and default_sort_key(x) > default_sort_key(y): + x, y = y, x + return cls(a, b1, b2, c, x, y) + if x == 0 and y == 0: + return S.One + + def fdiff(self, argindex=5): + a, b1, b2, c, x, y = self.args + if argindex == 5: + return (a*b1/c)*appellf1(a + 1, b1 + 1, b2, c + 1, x, y) + elif argindex == 6: + return (a*b2/c)*appellf1(a + 1, b1, b2 + 1, c + 1, x, y) + elif argindex in (1, 2, 3, 4): + return Derivative(self, self.args[argindex-1]) + else: + raise ArgumentIndexError(self, argindex) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/mathieu_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/mathieu_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..66bccd8d3e6dd357e1e0b93fb5cb5ad4c5f1367f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/mathieu_functions.py @@ -0,0 +1,269 @@ +""" This module contains the Mathieu functions. +""" + +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin, cos + + +class MathieuBase(DefinedFunction): + """ + Abstract base class for Mathieu functions. + + This class is meant to reduce code duplication. + + """ + + unbranched = True + + def _eval_conjugate(self): + a, q, z = self.args + return self.func(a.conjugate(), q.conjugate(), z.conjugate()) + + +class mathieus(MathieuBase): + r""" + The Mathieu Sine function $S(a,q,z)$. + + Explanation + =========== + + This function is one solution of the Mathieu differential equation: + + .. math :: + y(x)^{\prime\prime} + (a - 2 q \cos(2 x)) y(x) = 0 + + The other solution is the Mathieu Cosine function. + + Examples + ======== + + >>> from sympy import diff, mathieus + >>> from sympy.abc import a, q, z + + >>> mathieus(a, q, z) + mathieus(a, q, z) + + >>> mathieus(a, 0, z) + sin(sqrt(a)*z) + + >>> diff(mathieus(a, q, z), z) + mathieusprime(a, q, z) + + See Also + ======== + + mathieuc: Mathieu cosine function. + mathieusprime: Derivative of Mathieu sine function. + mathieucprime: Derivative of Mathieu cosine function. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Mathieu_function + .. [2] https://dlmf.nist.gov/28 + .. [3] https://mathworld.wolfram.com/MathieuFunction.html + .. [4] https://functions.wolfram.com/MathieuandSpheroidalFunctions/MathieuS/ + + """ + + def fdiff(self, argindex=1): + if argindex == 3: + a, q, z = self.args + return mathieusprime(a, q, z) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, a, q, z): + if q.is_Number and q.is_zero: + return sin(sqrt(a)*z) + # Try to pull out factors of -1 + if z.could_extract_minus_sign(): + return -cls(a, q, -z) + + +class mathieuc(MathieuBase): + r""" + The Mathieu Cosine function $C(a,q,z)$. + + Explanation + =========== + + This function is one solution of the Mathieu differential equation: + + .. math :: + y(x)^{\prime\prime} + (a - 2 q \cos(2 x)) y(x) = 0 + + The other solution is the Mathieu Sine function. + + Examples + ======== + + >>> from sympy import diff, mathieuc + >>> from sympy.abc import a, q, z + + >>> mathieuc(a, q, z) + mathieuc(a, q, z) + + >>> mathieuc(a, 0, z) + cos(sqrt(a)*z) + + >>> diff(mathieuc(a, q, z), z) + mathieucprime(a, q, z) + + See Also + ======== + + mathieus: Mathieu sine function + mathieusprime: Derivative of Mathieu sine function + mathieucprime: Derivative of Mathieu cosine function + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Mathieu_function + .. [2] https://dlmf.nist.gov/28 + .. [3] https://mathworld.wolfram.com/MathieuFunction.html + .. [4] https://functions.wolfram.com/MathieuandSpheroidalFunctions/MathieuC/ + + """ + + def fdiff(self, argindex=1): + if argindex == 3: + a, q, z = self.args + return mathieucprime(a, q, z) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, a, q, z): + if q.is_Number and q.is_zero: + return cos(sqrt(a)*z) + # Try to pull out factors of -1 + if z.could_extract_minus_sign(): + return cls(a, q, -z) + + +class mathieusprime(MathieuBase): + r""" + The derivative $S^{\prime}(a,q,z)$ of the Mathieu Sine function. + + Explanation + =========== + + This function is one solution of the Mathieu differential equation: + + .. math :: + y(x)^{\prime\prime} + (a - 2 q \cos(2 x)) y(x) = 0 + + The other solution is the Mathieu Cosine function. + + Examples + ======== + + >>> from sympy import diff, mathieusprime + >>> from sympy.abc import a, q, z + + >>> mathieusprime(a, q, z) + mathieusprime(a, q, z) + + >>> mathieusprime(a, 0, z) + sqrt(a)*cos(sqrt(a)*z) + + >>> diff(mathieusprime(a, q, z), z) + (-a + 2*q*cos(2*z))*mathieus(a, q, z) + + See Also + ======== + + mathieus: Mathieu sine function + mathieuc: Mathieu cosine function + mathieucprime: Derivative of Mathieu cosine function + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Mathieu_function + .. [2] https://dlmf.nist.gov/28 + .. [3] https://mathworld.wolfram.com/MathieuFunction.html + .. [4] https://functions.wolfram.com/MathieuandSpheroidalFunctions/MathieuSPrime/ + + """ + + def fdiff(self, argindex=1): + if argindex == 3: + a, q, z = self.args + return (2*q*cos(2*z) - a)*mathieus(a, q, z) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, a, q, z): + if q.is_Number and q.is_zero: + return sqrt(a)*cos(sqrt(a)*z) + # Try to pull out factors of -1 + if z.could_extract_minus_sign(): + return cls(a, q, -z) + + +class mathieucprime(MathieuBase): + r""" + The derivative $C^{\prime}(a,q,z)$ of the Mathieu Cosine function. + + Explanation + =========== + + This function is one solution of the Mathieu differential equation: + + .. math :: + y(x)^{\prime\prime} + (a - 2 q \cos(2 x)) y(x) = 0 + + The other solution is the Mathieu Sine function. + + Examples + ======== + + >>> from sympy import diff, mathieucprime + >>> from sympy.abc import a, q, z + + >>> mathieucprime(a, q, z) + mathieucprime(a, q, z) + + >>> mathieucprime(a, 0, z) + -sqrt(a)*sin(sqrt(a)*z) + + >>> diff(mathieucprime(a, q, z), z) + (-a + 2*q*cos(2*z))*mathieuc(a, q, z) + + See Also + ======== + + mathieus: Mathieu sine function + mathieuc: Mathieu cosine function + mathieusprime: Derivative of Mathieu sine function + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Mathieu_function + .. [2] https://dlmf.nist.gov/28 + .. [3] https://mathworld.wolfram.com/MathieuFunction.html + .. [4] https://functions.wolfram.com/MathieuandSpheroidalFunctions/MathieuCPrime/ + + """ + + def fdiff(self, argindex=1): + if argindex == 3: + a, q, z = self.args + return (2*q*cos(2*z) - a)*mathieuc(a, q, z) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, a, q, z): + if q.is_Number and q.is_zero: + return -sqrt(a)*sin(sqrt(a)*z) + # Try to pull out factors of -1 + if z.could_extract_minus_sign(): + return -cls(a, q, -z) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/polynomials.py b/phivenv/Lib/site-packages/sympy/functions/special/polynomials.py new file mode 100644 index 0000000000000000000000000000000000000000..5816baef600baf957c31a9dddaa5571da86d754a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/polynomials.py @@ -0,0 +1,1447 @@ +""" +This module mainly implements special orthogonal polynomials. + +See also functions.combinatorial.numbers which contains some +combinatorial polynomials. + +""" + +from sympy.core import Rational +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.functions.combinatorial.factorials import binomial, factorial, RisingFactorial +from sympy.functions.elementary.complexes import re +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import cos, sec +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import hyper +from sympy.polys.orthopolys import (chebyshevt_poly, chebyshevu_poly, + gegenbauer_poly, hermite_poly, hermite_prob_poly, + jacobi_poly, laguerre_poly, legendre_poly) + +_x = Dummy('x') + + +class OrthogonalPolynomial(DefinedFunction): + """Base class for orthogonal polynomials. + """ + + @classmethod + def _eval_at_order(cls, n, x): + if n.is_integer and n >= 0: + return cls._ortho_poly(int(n), _x).subs(_x, x) + + def _eval_conjugate(self): + return self.func(self.args[0], self.args[1].conjugate()) + +#---------------------------------------------------------------------------- +# Jacobi polynomials +# + + +class jacobi(OrthogonalPolynomial): + r""" + Jacobi polynomial $P_n^{\left(\alpha, \beta\right)}(x)$. + + Explanation + =========== + + ``jacobi(n, alpha, beta, x)`` gives the $n$th Jacobi polynomial + in $x$, $P_n^{\left(\alpha, \beta\right)}(x)$. + + The Jacobi polynomials are orthogonal on $[-1, 1]$ with respect + to the weight $\left(1-x\right)^\alpha \left(1+x\right)^\beta$. + + Examples + ======== + + >>> from sympy import jacobi, S, conjugate, diff + >>> from sympy.abc import a, b, n, x + + >>> jacobi(0, a, b, x) + 1 + >>> jacobi(1, a, b, x) + a/2 - b/2 + x*(a/2 + b/2 + 1) + >>> jacobi(2, a, b, x) + a**2/8 - a*b/4 - a/8 + b**2/8 - b/8 + x**2*(a**2/8 + a*b/4 + 7*a/8 + b**2/8 + 7*b/8 + 3/2) + x*(a**2/4 + 3*a/4 - b**2/4 - 3*b/4) - 1/2 + + >>> jacobi(n, a, b, x) + jacobi(n, a, b, x) + + >>> jacobi(n, a, a, x) + RisingFactorial(a + 1, n)*gegenbauer(n, + a + 1/2, x)/RisingFactorial(2*a + 1, n) + + >>> jacobi(n, 0, 0, x) + legendre(n, x) + + >>> jacobi(n, S(1)/2, S(1)/2, x) + RisingFactorial(3/2, n)*chebyshevu(n, x)/factorial(n + 1) + + >>> jacobi(n, -S(1)/2, -S(1)/2, x) + RisingFactorial(1/2, n)*chebyshevt(n, x)/factorial(n) + + >>> jacobi(n, a, b, -x) + (-1)**n*jacobi(n, b, a, x) + + >>> jacobi(n, a, b, 0) + gamma(a + n + 1)*hyper((-n, -b - n), (a + 1,), -1)/(2**n*factorial(n)*gamma(a + 1)) + >>> jacobi(n, a, b, 1) + RisingFactorial(a + 1, n)/factorial(n) + + >>> conjugate(jacobi(n, a, b, x)) + jacobi(n, conjugate(a), conjugate(b), conjugate(x)) + + >>> diff(jacobi(n,a,b,x), x) + (a/2 + b/2 + n/2 + 1/2)*jacobi(n - 1, a + 1, b + 1, x) + + See Also + ======== + + gegenbauer, + chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly, + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Jacobi_polynomials + .. [2] https://mathworld.wolfram.com/JacobiPolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/JacobiP/ + + """ + + @classmethod + def eval(cls, n, a, b, x): + # Simplify to other polynomials + # P^{a, a}_n(x) + if a == b: + if a == Rational(-1, 2): + return RisingFactorial(S.Half, n) / factorial(n) * chebyshevt(n, x) + elif a.is_zero: + return legendre(n, x) + elif a == S.Half: + return RisingFactorial(3*S.Half, n) / factorial(n + 1) * chebyshevu(n, x) + else: + return RisingFactorial(a + 1, n) / RisingFactorial(2*a + 1, n) * gegenbauer(n, a + S.Half, x) + elif b == -a: + # P^{a, -a}_n(x) + return gamma(n + a + 1) / gamma(n + 1) * (1 + x)**(a/2) / (1 - x)**(a/2) * assoc_legendre(n, -a, x) + + if not n.is_Number: + # Symbolic result P^{a,b}_n(x) + # P^{a,b}_n(-x) ---> (-1)**n * P^{b,a}_n(-x) + if x.could_extract_minus_sign(): + return S.NegativeOne**n * jacobi(n, b, a, -x) + # We can evaluate for some special values of x + if x.is_zero: + return (2**(-n) * gamma(a + n + 1) / (gamma(a + 1) * factorial(n)) * + hyper([-b - n, -n], [a + 1], -1)) + if x == S.One: + return RisingFactorial(a + 1, n) / factorial(n) + elif x is S.Infinity: + if n.is_positive: + # Make sure a+b+2*n \notin Z + if (a + b + 2*n).is_integer: + raise ValueError("Error. a + b + 2*n should not be an integer.") + return RisingFactorial(a + b + n + 1, n) * S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial + return jacobi_poly(n, a, b, x) + + def fdiff(self, argindex=4): + from sympy.concrete.summations import Sum + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt a + n, a, b, x = self.args + k = Dummy("k") + f1 = 1 / (a + b + n + k + 1) + f2 = ((a + b + 2*k + 1) * RisingFactorial(b + k + 1, n - k) / + ((n - k) * RisingFactorial(a + b + k + 1, n - k))) + return Sum(f1 * (jacobi(n, a, b, x) + f2*jacobi(k, a, b, x)), (k, 0, n - 1)) + elif argindex == 3: + # Diff wrt b + n, a, b, x = self.args + k = Dummy("k") + f1 = 1 / (a + b + n + k + 1) + f2 = (-1)**(n - k) * ((a + b + 2*k + 1) * RisingFactorial(a + k + 1, n - k) / + ((n - k) * RisingFactorial(a + b + k + 1, n - k))) + return Sum(f1 * (jacobi(n, a, b, x) + f2*jacobi(k, a, b, x)), (k, 0, n - 1)) + elif argindex == 4: + # Diff wrt x + n, a, b, x = self.args + return S.Half * (a + b + n + 1) * jacobi(n - 1, a + 1, b + 1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, a, b, x, **kwargs): + from sympy.concrete.summations import Sum + # Make sure n \in N + if n.is_negative or n.is_integer is False: + raise ValueError("Error: n should be a non-negative integer.") + k = Dummy("k") + kern = (RisingFactorial(-n, k) * RisingFactorial(a + b + n + 1, k) * RisingFactorial(a + k + 1, n - k) / + factorial(k) * ((1 - x)/2)**k) + return 1 / factorial(n) * Sum(kern, (k, 0, n)) + + def _eval_rewrite_as_polynomial(self, n, a, b, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, a, b, x, **kwargs) + + def _eval_conjugate(self): + n, a, b, x = self.args + return self.func(n, a.conjugate(), b.conjugate(), x.conjugate()) + + +def jacobi_normalized(n, a, b, x): + r""" + Jacobi polynomial $P_n^{\left(\alpha, \beta\right)}(x)$. + + Explanation + =========== + + ``jacobi_normalized(n, alpha, beta, x)`` gives the $n$th + Jacobi polynomial in $x$, $P_n^{\left(\alpha, \beta\right)}(x)$. + + The Jacobi polynomials are orthogonal on $[-1, 1]$ with respect + to the weight $\left(1-x\right)^\alpha \left(1+x\right)^\beta$. + + This functions returns the polynomials normilzed: + + .. math:: + + \int_{-1}^{1} + P_m^{\left(\alpha, \beta\right)}(x) + P_n^{\left(\alpha, \beta\right)}(x) + (1-x)^{\alpha} (1+x)^{\beta} \mathrm{d}x + = \delta_{m,n} + + Examples + ======== + + >>> from sympy import jacobi_normalized + >>> from sympy.abc import n,a,b,x + + >>> jacobi_normalized(n, a, b, x) + jacobi(n, a, b, x)/sqrt(2**(a + b + 1)*gamma(a + n + 1)*gamma(b + n + 1)/((a + b + 2*n + 1)*factorial(n)*gamma(a + b + n + 1))) + + Parameters + ========== + + n : integer degree of polynomial + + a : alpha value + + b : beta value + + x : symbol + + See Also + ======== + + gegenbauer, + chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly, + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Jacobi_polynomials + .. [2] https://mathworld.wolfram.com/JacobiPolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/JacobiP/ + + """ + nfactor = (S(2)**(a + b + 1) * (gamma(n + a + 1) * gamma(n + b + 1)) + / (2*n + a + b + 1) / (factorial(n) * gamma(n + a + b + 1))) + + return jacobi(n, a, b, x) / sqrt(nfactor) + + +#---------------------------------------------------------------------------- +# Gegenbauer polynomials +# + + +class gegenbauer(OrthogonalPolynomial): + r""" + Gegenbauer polynomial $C_n^{\left(\alpha\right)}(x)$. + + Explanation + =========== + + ``gegenbauer(n, alpha, x)`` gives the $n$th Gegenbauer polynomial + in $x$, $C_n^{\left(\alpha\right)}(x)$. + + The Gegenbauer polynomials are orthogonal on $[-1, 1]$ with + respect to the weight $\left(1-x^2\right)^{\alpha-\frac{1}{2}}$. + + Examples + ======== + + >>> from sympy import gegenbauer, conjugate, diff + >>> from sympy.abc import n,a,x + >>> gegenbauer(0, a, x) + 1 + >>> gegenbauer(1, a, x) + 2*a*x + >>> gegenbauer(2, a, x) + -a + x**2*(2*a**2 + 2*a) + >>> gegenbauer(3, a, x) + x**3*(4*a**3/3 + 4*a**2 + 8*a/3) + x*(-2*a**2 - 2*a) + + >>> gegenbauer(n, a, x) + gegenbauer(n, a, x) + >>> gegenbauer(n, a, -x) + (-1)**n*gegenbauer(n, a, x) + + >>> gegenbauer(n, a, 0) + 2**n*sqrt(pi)*gamma(a + n/2)/(gamma(a)*gamma(1/2 - n/2)*gamma(n + 1)) + >>> gegenbauer(n, a, 1) + gamma(2*a + n)/(gamma(2*a)*gamma(n + 1)) + + >>> conjugate(gegenbauer(n, a, x)) + gegenbauer(n, conjugate(a), conjugate(x)) + + >>> diff(gegenbauer(n, a, x), x) + 2*a*gegenbauer(n - 1, a + 1, x) + + See Also + ======== + + jacobi, + chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gegenbauer_polynomials + .. [2] https://mathworld.wolfram.com/GegenbauerPolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/GegenbauerC3/ + + """ + + @classmethod + def eval(cls, n, a, x): + # For negative n the polynomials vanish + # See https://functions.wolfram.com/Polynomials/GegenbauerC3/03/01/03/0012/ + if n.is_negative: + return S.Zero + + # Some special values for fixed a + if a == S.Half: + return legendre(n, x) + elif a == S.One: + return chebyshevu(n, x) + elif a == S.NegativeOne: + return S.Zero + + if not n.is_Number: + # Handle this before the general sign extraction rule + if x == S.NegativeOne: + if (re(a) > S.Half) == True: + return S.ComplexInfinity + else: + return (cos(S.Pi*(a+n)) * sec(S.Pi*a) * gamma(2*a+n) / + (gamma(2*a) * gamma(n+1))) + + # Symbolic result C^a_n(x) + # C^a_n(-x) ---> (-1)**n * C^a_n(x) + if x.could_extract_minus_sign(): + return S.NegativeOne**n * gegenbauer(n, a, -x) + # We can evaluate for some special values of x + if x.is_zero: + return (2**n * sqrt(S.Pi) * gamma(a + S.Half*n) / + (gamma((1 - n)/2) * gamma(n + 1) * gamma(a)) ) + if x == S.One: + return gamma(2*a + n) / (gamma(2*a) * gamma(n + 1)) + elif x is S.Infinity: + if n.is_positive: + return RisingFactorial(a, n) * S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial + return gegenbauer_poly(n, a, x) + + def fdiff(self, argindex=3): + from sympy.concrete.summations import Sum + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt a + n, a, x = self.args + k = Dummy("k") + factor1 = 2 * (1 + (-1)**(n - k)) * (k + a) / ((k + + n + 2*a) * (n - k)) + factor2 = 2*(k + 1) / ((k + 2*a) * (2*k + 2*a + 1)) + \ + 2 / (k + n + 2*a) + kern = factor1*gegenbauer(k, a, x) + factor2*gegenbauer(n, a, x) + return Sum(kern, (k, 0, n - 1)) + elif argindex == 3: + # Diff wrt x + n, a, x = self.args + return 2*a*gegenbauer(n - 1, a + 1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, a, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = ((-1)**k * RisingFactorial(a, n - k) * (2*x)**(n - 2*k) / + (factorial(k) * factorial(n - 2*k))) + return Sum(kern, (k, 0, floor(n/2))) + + def _eval_rewrite_as_polynomial(self, n, a, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, a, x, **kwargs) + + def _eval_conjugate(self): + n, a, x = self.args + return self.func(n, a.conjugate(), x.conjugate()) + +#---------------------------------------------------------------------------- +# Chebyshev polynomials of first and second kind +# + + +class chebyshevt(OrthogonalPolynomial): + r""" + Chebyshev polynomial of the first kind, $T_n(x)$. + + Explanation + =========== + + ``chebyshevt(n, x)`` gives the $n$th Chebyshev polynomial (of the first + kind) in $x$, $T_n(x)$. + + The Chebyshev polynomials of the first kind are orthogonal on + $[-1, 1]$ with respect to the weight $\frac{1}{\sqrt{1-x^2}}$. + + Examples + ======== + + >>> from sympy import chebyshevt, diff + >>> from sympy.abc import n,x + >>> chebyshevt(0, x) + 1 + >>> chebyshevt(1, x) + x + >>> chebyshevt(2, x) + 2*x**2 - 1 + + >>> chebyshevt(n, x) + chebyshevt(n, x) + >>> chebyshevt(n, -x) + (-1)**n*chebyshevt(n, x) + >>> chebyshevt(-n, x) + chebyshevt(n, x) + + >>> chebyshevt(n, 0) + cos(pi*n/2) + >>> chebyshevt(n, -1) + (-1)**n + + >>> diff(chebyshevt(n, x), x) + n*chebyshevu(n - 1, x) + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chebyshev_polynomial + .. [2] https://mathworld.wolfram.com/ChebyshevPolynomialoftheFirstKind.html + .. [3] https://mathworld.wolfram.com/ChebyshevPolynomialoftheSecondKind.html + .. [4] https://functions.wolfram.com/Polynomials/ChebyshevT/ + .. [5] https://functions.wolfram.com/Polynomials/ChebyshevU/ + + """ + + _ortho_poly = staticmethod(chebyshevt_poly) + + @classmethod + def eval(cls, n, x): + if not n.is_Number: + # Symbolic result T_n(x) + # T_n(-x) ---> (-1)**n * T_n(x) + if x.could_extract_minus_sign(): + return S.NegativeOne**n * chebyshevt(n, -x) + # T_{-n}(x) ---> T_n(x) + if n.could_extract_minus_sign(): + return chebyshevt(-n, x) + # We can evaluate for some special values of x + if x.is_zero: + return cos(S.Half * S.Pi * n) + if x == S.One: + return S.One + elif x is S.Infinity: + return S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial + if n.is_negative: + # T_{-n}(x) == T_n(x) + return cls._eval_at_order(-n, x) + else: + return cls._eval_at_order(n, x) + + def fdiff(self, argindex=2): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt x + n, x = self.args + return n * chebyshevu(n - 1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = binomial(n, 2*k) * (x**2 - 1)**k * x**(n - 2*k) + return Sum(kern, (k, 0, floor(n/2))) + + def _eval_rewrite_as_polynomial(self, n, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, x, **kwargs) + + +class chebyshevu(OrthogonalPolynomial): + r""" + Chebyshev polynomial of the second kind, $U_n(x)$. + + Explanation + =========== + + ``chebyshevu(n, x)`` gives the $n$th Chebyshev polynomial of the second + kind in x, $U_n(x)$. + + The Chebyshev polynomials of the second kind are orthogonal on + $[-1, 1]$ with respect to the weight $\sqrt{1-x^2}$. + + Examples + ======== + + >>> from sympy import chebyshevu, diff + >>> from sympy.abc import n,x + >>> chebyshevu(0, x) + 1 + >>> chebyshevu(1, x) + 2*x + >>> chebyshevu(2, x) + 4*x**2 - 1 + + >>> chebyshevu(n, x) + chebyshevu(n, x) + >>> chebyshevu(n, -x) + (-1)**n*chebyshevu(n, x) + >>> chebyshevu(-n, x) + -chebyshevu(n - 2, x) + + >>> chebyshevu(n, 0) + cos(pi*n/2) + >>> chebyshevu(n, 1) + n + 1 + + >>> diff(chebyshevu(n, x), x) + (-x*chebyshevu(n, x) + (n + 1)*chebyshevt(n + 1, x))/(x**2 - 1) + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chebyshev_polynomial + .. [2] https://mathworld.wolfram.com/ChebyshevPolynomialoftheFirstKind.html + .. [3] https://mathworld.wolfram.com/ChebyshevPolynomialoftheSecondKind.html + .. [4] https://functions.wolfram.com/Polynomials/ChebyshevT/ + .. [5] https://functions.wolfram.com/Polynomials/ChebyshevU/ + + """ + + _ortho_poly = staticmethod(chebyshevu_poly) + + @classmethod + def eval(cls, n, x): + if not n.is_Number: + # Symbolic result U_n(x) + # U_n(-x) ---> (-1)**n * U_n(x) + if x.could_extract_minus_sign(): + return S.NegativeOne**n * chebyshevu(n, -x) + # U_{-n}(x) ---> -U_{n-2}(x) + if n.could_extract_minus_sign(): + if n == S.NegativeOne: + # n can not be -1 here + return S.Zero + elif not (-n - 2).could_extract_minus_sign(): + return -chebyshevu(-n - 2, x) + # We can evaluate for some special values of x + if x.is_zero: + return cos(S.Half * S.Pi * n) + if x == S.One: + return S.One + n + elif x is S.Infinity: + return S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial + if n.is_negative: + # U_{-n}(x) ---> -U_{n-2}(x) + if n == S.NegativeOne: + return S.Zero + else: + return -cls._eval_at_order(-n - 2, x) + else: + return cls._eval_at_order(n, x) + + def fdiff(self, argindex=2): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt x + n, x = self.args + return ((n + 1) * chebyshevt(n + 1, x) - x * chebyshevu(n, x)) / (x**2 - 1) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = S.NegativeOne**k * factorial( + n - k) * (2*x)**(n - 2*k) / (factorial(k) * factorial(n - 2*k)) + return Sum(kern, (k, 0, floor(n/2))) + + def _eval_rewrite_as_polynomial(self, n, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, x, **kwargs) + + +class chebyshevt_root(DefinedFunction): + r""" + ``chebyshev_root(n, k)`` returns the $k$th root (indexed from zero) of + the $n$th Chebyshev polynomial of the first kind; that is, if + $0 \le k < n$, ``chebyshevt(n, chebyshevt_root(n, k)) == 0``. + + Examples + ======== + + >>> from sympy import chebyshevt, chebyshevt_root + >>> chebyshevt_root(3, 2) + -sqrt(3)/2 + >>> chebyshevt(3, chebyshevt_root(3, 2)) + 0 + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + """ + + @classmethod + def eval(cls, n, k): + if not ((0 <= k) and (k < n)): + raise ValueError("must have 0 <= k < n, " + "got k = %s and n = %s" % (k, n)) + return cos(S.Pi*(2*k + 1)/(2*n)) + + +class chebyshevu_root(DefinedFunction): + r""" + ``chebyshevu_root(n, k)`` returns the $k$th root (indexed from zero) of the + $n$th Chebyshev polynomial of the second kind; that is, if $0 \le k < n$, + ``chebyshevu(n, chebyshevu_root(n, k)) == 0``. + + Examples + ======== + + >>> from sympy import chebyshevu, chebyshevu_root + >>> chebyshevu_root(3, 2) + -sqrt(2)/2 + >>> chebyshevu(3, chebyshevu_root(3, 2)) + 0 + + See Also + ======== + + chebyshevt, chebyshevt_root, chebyshevu, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + """ + + + @classmethod + def eval(cls, n, k): + if not ((0 <= k) and (k < n)): + raise ValueError("must have 0 <= k < n, " + "got k = %s and n = %s" % (k, n)) + return cos(S.Pi*(k + 1)/(n + 1)) + +#---------------------------------------------------------------------------- +# Legendre polynomials and Associated Legendre polynomials +# + + +class legendre(OrthogonalPolynomial): + r""" + ``legendre(n, x)`` gives the $n$th Legendre polynomial of $x$, $P_n(x)$ + + Explanation + =========== + + The Legendre polynomials are orthogonal on $[-1, 1]$ with respect to + the constant weight 1. They satisfy $P_n(1) = 1$ for all $n$; further, + $P_n$ is odd for odd $n$ and even for even $n$. + + Examples + ======== + + >>> from sympy import legendre, diff + >>> from sympy.abc import x, n + >>> legendre(0, x) + 1 + >>> legendre(1, x) + x + >>> legendre(2, x) + 3*x**2/2 - 1/2 + >>> legendre(n, x) + legendre(n, x) + >>> diff(legendre(n,x), x) + n*(x*legendre(n, x) - legendre(n - 1, x))/(x**2 - 1) + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, + assoc_legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Legendre_polynomial + .. [2] https://mathworld.wolfram.com/LegendrePolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/LegendreP/ + .. [4] https://functions.wolfram.com/Polynomials/LegendreP2/ + + """ + + _ortho_poly = staticmethod(legendre_poly) + + @classmethod + def eval(cls, n, x): + if not n.is_Number: + # Symbolic result L_n(x) + # L_n(-x) ---> (-1)**n * L_n(x) + if x.could_extract_minus_sign(): + return S.NegativeOne**n * legendre(n, -x) + # L_{-n}(x) ---> L_{n-1}(x) + if n.could_extract_minus_sign() and not(-n - 1).could_extract_minus_sign(): + return legendre(-n - S.One, x) + # We can evaluate for some special values of x + if x.is_zero: + return sqrt(S.Pi)/(gamma(S.Half - n/2)*gamma(S.One + n/2)) + elif x == S.One: + return S.One + elif x is S.Infinity: + return S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial; + # L_{-n}(x) ---> L_{n-1}(x) + if n.is_negative: + n = -n - S.One + return cls._eval_at_order(n, x) + + def fdiff(self, argindex=2): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt x + # Find better formula, this is unsuitable for x = +/-1 + # https://www.autodiff.org/ad16/Oral/Buecker_Legendre.pdf says + # at x = 1: + # n*(n + 1)/2 , m = 0 + # oo , m = 1 + # -(n-1)*n*(n+1)*(n+2)/4 , m = 2 + # 0 , m = 3, 4, ..., n + # + # at x = -1 + # (-1)**(n+1)*n*(n + 1)/2 , m = 0 + # (-1)**n*oo , m = 1 + # (-1)**n*(n-1)*n*(n+1)*(n+2)/4 , m = 2 + # 0 , m = 3, 4, ..., n + n, x = self.args + return n/(x**2 - 1)*(x*legendre(n, x) - legendre(n - 1, x)) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = S.NegativeOne**k*binomial(n, k)**2*((1 + x)/2)**(n - k)*((1 - x)/2)**k + return Sum(kern, (k, 0, n)) + + def _eval_rewrite_as_polynomial(self, n, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, x, **kwargs) + + +class assoc_legendre(DefinedFunction): + r""" + ``assoc_legendre(n, m, x)`` gives $P_n^m(x)$, where $n$ and $m$ are + the degree and order or an expression which is related to the nth + order Legendre polynomial, $P_n(x)$ in the following manner: + + .. math:: + P_n^m(x) = (-1)^m (1 - x^2)^{\frac{m}{2}} + \frac{\mathrm{d}^m P_n(x)}{\mathrm{d} x^m} + + Explanation + =========== + + Associated Legendre polynomials are orthogonal on $[-1, 1]$ with: + + - weight $= 1$ for the same $m$ and different $n$. + - weight $= \frac{1}{1-x^2}$ for the same $n$ and different $m$. + + Examples + ======== + + >>> from sympy import assoc_legendre + >>> from sympy.abc import x, m, n + >>> assoc_legendre(0,0, x) + 1 + >>> assoc_legendre(1,0, x) + x + >>> assoc_legendre(1,1, x) + -sqrt(1 - x**2) + >>> assoc_legendre(n,m,x) + assoc_legendre(n, m, x) + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, + hermite, hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Associated_Legendre_polynomials + .. [2] https://mathworld.wolfram.com/LegendrePolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/LegendreP/ + .. [4] https://functions.wolfram.com/Polynomials/LegendreP2/ + + """ + + @classmethod + def _eval_at_order(cls, n, m): + P = legendre_poly(n, _x, polys=True).diff((_x, m)) + return S.NegativeOne**m * (1 - _x**2)**Rational(m, 2) * P.as_expr() + + @classmethod + def eval(cls, n, m, x): + if m.could_extract_minus_sign(): + # P^{-m}_n ---> F * P^m_n + return S.NegativeOne**(-m) * (factorial(m + n)/factorial(n - m)) * assoc_legendre(n, -m, x) + if m == 0: + # P^0_n ---> L_n + return legendre(n, x) + if x == 0: + return 2**m*sqrt(S.Pi) / (gamma((1 - m - n)/2)*gamma(1 - (m - n)/2)) + if n.is_Number and m.is_Number and n.is_integer and m.is_integer: + if n.is_negative: + raise ValueError("%s : 1st index must be nonnegative integer (got %r)" % (cls, n)) + if abs(m) > n: + raise ValueError("%s : abs('2nd index') must be <= '1st index' (got %r, %r)" % (cls, n, m)) + return cls._eval_at_order(int(n), abs(int(m))).subs(_x, x) + + def fdiff(self, argindex=3): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt m + raise ArgumentIndexError(self, argindex) + elif argindex == 3: + # Diff wrt x + # Find better formula, this is unsuitable for x = 1 + n, m, x = self.args + return 1/(x**2 - 1)*(x*n*assoc_legendre(n, m, x) - (m + n)*assoc_legendre(n - 1, m, x)) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, m, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = factorial(2*n - 2*k)/(2**n*factorial(n - k)*factorial( + k)*factorial(n - 2*k - m))*S.NegativeOne**k*x**(n - m - 2*k) + return (1 - x**2)**(m/2) * Sum(kern, (k, 0, floor((n - m)*S.Half))) + + def _eval_rewrite_as_polynomial(self, n, m, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, m, x, **kwargs) + + def _eval_conjugate(self): + n, m, x = self.args + return self.func(n, m.conjugate(), x.conjugate()) + +#---------------------------------------------------------------------------- +# Hermite polynomials +# + + +class hermite(OrthogonalPolynomial): + r""" + ``hermite(n, x)`` gives the $n$th Hermite polynomial in $x$, $H_n(x)$. + + Explanation + =========== + + The Hermite polynomials are orthogonal on $(-\infty, \infty)$ + with respect to the weight $\exp\left(-x^2\right)$. + + Examples + ======== + + >>> from sympy import hermite, diff + >>> from sympy.abc import x, n + >>> hermite(0, x) + 1 + >>> hermite(1, x) + 2*x + >>> hermite(2, x) + 4*x**2 - 2 + >>> hermite(n, x) + hermite(n, x) + >>> diff(hermite(n,x), x) + 2*n*hermite(n - 1, x) + >>> hermite(n, -x) + (-1)**n*hermite(n, x) + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite_prob, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hermite_polynomial + .. [2] https://mathworld.wolfram.com/HermitePolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/HermiteH/ + + """ + + _ortho_poly = staticmethod(hermite_poly) + + @classmethod + def eval(cls, n, x): + if not n.is_Number: + # Symbolic result H_n(x) + # H_n(-x) ---> (-1)**n * H_n(x) + if x.could_extract_minus_sign(): + return S.NegativeOne**n * hermite(n, -x) + # We can evaluate for some special values of x + if x.is_zero: + return 2**n * sqrt(S.Pi) / gamma((S.One - n)/2) + elif x is S.Infinity: + return S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial + if n.is_negative: + raise ValueError( + "The index n must be nonnegative integer (got %r)" % n) + else: + return cls._eval_at_order(n, x) + + def fdiff(self, argindex=2): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt x + n, x = self.args + return 2*n*hermite(n - 1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = S.NegativeOne**k / (factorial(k)*factorial(n - 2*k)) * (2*x)**(n - 2*k) + return factorial(n)*Sum(kern, (k, 0, floor(n/2))) + + def _eval_rewrite_as_polynomial(self, n, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, x, **kwargs) + + def _eval_rewrite_as_hermite_prob(self, n, x, **kwargs): + return sqrt(2)**n * hermite_prob(n, x*sqrt(2)) + + +class hermite_prob(OrthogonalPolynomial): + r""" + ``hermite_prob(n, x)`` gives the $n$th probabilist's Hermite polynomial + in $x$, $He_n(x)$. + + Explanation + =========== + + The probabilist's Hermite polynomials are orthogonal on $(-\infty, \infty)$ + with respect to the weight $\exp\left(-\frac{x^2}{2}\right)$. They are monic + polynomials, related to the plain Hermite polynomials (:py:class:`~.hermite`) by + + .. math :: He_n(x) = 2^{-n/2} H_n(x/\sqrt{2}) + + Examples + ======== + + >>> from sympy import hermite_prob, diff, I + >>> from sympy.abc import x, n + >>> hermite_prob(1, x) + x + >>> hermite_prob(5, x) + x**5 - 10*x**3 + 15*x + >>> diff(hermite_prob(n,x), x) + n*hermite_prob(n - 1, x) + >>> hermite_prob(n, -x) + (-1)**n*hermite_prob(n, x) + + The sum of absolute values of coefficients of $He_n(x)$ is the number of + matchings in the complete graph $K_n$ or telephone number, A000085 in the OEIS: + + >>> [hermite_prob(n,I) / I**n for n in range(11)] + [1, 1, 2, 4, 10, 26, 76, 232, 764, 2620, 9496] + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, + laguerre, assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Hermite_polynomial + .. [2] https://mathworld.wolfram.com/HermitePolynomial.html + """ + + _ortho_poly = staticmethod(hermite_prob_poly) + + @classmethod + def eval(cls, n, x): + if not n.is_Number: + if x.could_extract_minus_sign(): + return S.NegativeOne**n * hermite_prob(n, -x) + if x.is_zero: + return sqrt(S.Pi) / gamma((S.One-n) / 2) + elif x is S.Infinity: + return S.Infinity + else: + if n.is_negative: + ValueError("n must be a nonnegative integer, not %r" % n) + else: + return cls._eval_at_order(n, x) + + def fdiff(self, argindex=2): + if argindex == 2: + n, x = self.args + return n*hermite_prob(n-1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, x, **kwargs): + from sympy.concrete.summations import Sum + k = Dummy("k") + kern = (-S.Half)**k * x**(n-2*k) / (factorial(k) * factorial(n-2*k)) + return factorial(n)*Sum(kern, (k, 0, floor(n/2))) + + def _eval_rewrite_as_polynomial(self, n, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, x, **kwargs) + + def _eval_rewrite_as_hermite(self, n, x, **kwargs): + return sqrt(2)**(-n) * hermite(n, x/sqrt(2)) + + +#---------------------------------------------------------------------------- +# Laguerre polynomials +# + + +class laguerre(OrthogonalPolynomial): + r""" + Returns the $n$th Laguerre polynomial in $x$, $L_n(x)$. + + Examples + ======== + + >>> from sympy import laguerre, diff + >>> from sympy.abc import x, n + >>> laguerre(0, x) + 1 + >>> laguerre(1, x) + 1 - x + >>> laguerre(2, x) + x**2/2 - 2*x + 1 + >>> laguerre(3, x) + -x**3/6 + 3*x**2/2 - 3*x + 1 + + >>> laguerre(n, x) + laguerre(n, x) + + >>> diff(laguerre(n, x), x) + -assoc_laguerre(n - 1, 1, x) + + Parameters + ========== + + n : int + Degree of Laguerre polynomial. Must be `n \ge 0`. + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + assoc_laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Laguerre_polynomial + .. [2] https://mathworld.wolfram.com/LaguerrePolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/LaguerreL/ + .. [4] https://functions.wolfram.com/Polynomials/LaguerreL3/ + + """ + + _ortho_poly = staticmethod(laguerre_poly) + + @classmethod + def eval(cls, n, x): + if n.is_integer is False: + raise ValueError("Error: n should be an integer.") + if not n.is_Number: + # Symbolic result L_n(x) + # L_{n}(-x) ---> exp(-x) * L_{-n-1}(x) + # L_{-n}(x) ---> exp(x) * L_{n-1}(-x) + if n.could_extract_minus_sign() and not(-n - 1).could_extract_minus_sign(): + return exp(x)*laguerre(-n - 1, -x) + # We can evaluate for some special values of x + if x.is_zero: + return S.One + elif x is S.NegativeInfinity: + return S.Infinity + elif x is S.Infinity: + return S.NegativeOne**n * S.Infinity + else: + if n.is_negative: + return exp(x)*laguerre(-n - 1, -x) + else: + return cls._eval_at_order(n, x) + + def fdiff(self, argindex=2): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt x + n, x = self.args + return -assoc_laguerre(n - 1, 1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, x, **kwargs): + from sympy.concrete.summations import Sum + # Make sure n \in N_0 + if n.is_negative: + return exp(x) * self._eval_rewrite_as_Sum(-n - 1, -x, **kwargs) + if n.is_integer is False: + raise ValueError("Error: n should be an integer.") + k = Dummy("k") + kern = RisingFactorial(-n, k) / factorial(k)**2 * x**k + return Sum(kern, (k, 0, n)) + + def _eval_rewrite_as_polynomial(self, n, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, x, **kwargs) + + +class assoc_laguerre(OrthogonalPolynomial): + r""" + Returns the $n$th generalized Laguerre polynomial in $x$, $L_n(x)$. + + Examples + ======== + + >>> from sympy import assoc_laguerre, diff + >>> from sympy.abc import x, n, a + >>> assoc_laguerre(0, a, x) + 1 + >>> assoc_laguerre(1, a, x) + a - x + 1 + >>> assoc_laguerre(2, a, x) + a**2/2 + 3*a/2 + x**2/2 + x*(-a - 2) + 1 + >>> assoc_laguerre(3, a, x) + a**3/6 + a**2 + 11*a/6 - x**3/6 + x**2*(a/2 + 3/2) + + x*(-a**2/2 - 5*a/2 - 3) + 1 + + >>> assoc_laguerre(n, a, 0) + binomial(a + n, a) + + >>> assoc_laguerre(n, a, x) + assoc_laguerre(n, a, x) + + >>> assoc_laguerre(n, 0, x) + laguerre(n, x) + + >>> diff(assoc_laguerre(n, a, x), x) + -assoc_laguerre(n - 1, a + 1, x) + + >>> diff(assoc_laguerre(n, a, x), a) + Sum(assoc_laguerre(_k, a, x)/(-a + n), (_k, 0, n - 1)) + + Parameters + ========== + + n : int + Degree of Laguerre polynomial. Must be `n \ge 0`. + + alpha : Expr + Arbitrary expression. For ``alpha=0`` regular Laguerre + polynomials will be generated. + + See Also + ======== + + jacobi, gegenbauer, + chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, + legendre, assoc_legendre, + hermite, hermite_prob, + laguerre, + sympy.polys.orthopolys.jacobi_poly + sympy.polys.orthopolys.gegenbauer_poly + sympy.polys.orthopolys.chebyshevt_poly + sympy.polys.orthopolys.chebyshevu_poly + sympy.polys.orthopolys.hermite_poly + sympy.polys.orthopolys.hermite_prob_poly + sympy.polys.orthopolys.legendre_poly + sympy.polys.orthopolys.laguerre_poly + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Laguerre_polynomial#Generalized_Laguerre_polynomials + .. [2] https://mathworld.wolfram.com/AssociatedLaguerrePolynomial.html + .. [3] https://functions.wolfram.com/Polynomials/LaguerreL/ + .. [4] https://functions.wolfram.com/Polynomials/LaguerreL3/ + + """ + + @classmethod + def eval(cls, n, alpha, x): + # L_{n}^{0}(x) ---> L_{n}(x) + if alpha.is_zero: + return laguerre(n, x) + + if not n.is_Number: + # We can evaluate for some special values of x + if x.is_zero: + return binomial(n + alpha, alpha) + elif x is S.Infinity and n > 0: + return S.NegativeOne**n * S.Infinity + elif x is S.NegativeInfinity and n > 0: + return S.Infinity + else: + # n is a given fixed integer, evaluate into polynomial + if n.is_negative: + raise ValueError( + "The index n must be nonnegative integer (got %r)" % n) + else: + return laguerre_poly(n, x, alpha) + + def fdiff(self, argindex=3): + from sympy.concrete.summations import Sum + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt alpha + n, alpha, x = self.args + k = Dummy("k") + return Sum(assoc_laguerre(k, alpha, x) / (n - alpha), (k, 0, n - 1)) + elif argindex == 3: + # Diff wrt x + n, alpha, x = self.args + return -assoc_laguerre(n - 1, alpha + 1, x) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_Sum(self, n, alpha, x, **kwargs): + from sympy.concrete.summations import Sum + # Make sure n \in N_0 + if n.is_negative or n.is_integer is False: + raise ValueError("Error: n should be a non-negative integer.") + k = Dummy("k") + kern = RisingFactorial( + -n, k) / (gamma(k + alpha + 1) * factorial(k)) * x**k + return gamma(n + alpha + 1) / factorial(n) * Sum(kern, (k, 0, n)) + + def _eval_rewrite_as_polynomial(self, n, alpha, x, **kwargs): + # This function is just kept for backwards compatibility + # but should not be used + return self._eval_rewrite_as_Sum(n, alpha, x, **kwargs) + + def _eval_conjugate(self): + n, alpha, x = self.args + return self.func(n, alpha.conjugate(), x.conjugate()) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/singularity_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/singularity_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..a69026e6e657b1131880b47cb32202b6825b7158 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/singularity_functions.py @@ -0,0 +1,235 @@ +from sympy.core import S, oo, diff +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.logic import fuzzy_not +from sympy.core.relational import Eq +from sympy.functions.elementary.complexes import im +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import Heaviside + +############################################################################### +############################# SINGULARITY FUNCTION ############################ +############################################################################### + + +class SingularityFunction(DefinedFunction): + r""" + Singularity functions are a class of discontinuous functions. + + Explanation + =========== + + Singularity functions take a variable, an offset, and an exponent as + arguments. These functions are represented using Macaulay brackets as: + + SingularityFunction(x, a, n) := ^n + + The singularity function will automatically evaluate to + ``Derivative(DiracDelta(x - a), x, -n - 1)`` if ``n < 0`` + and ``(x - a)**n*Heaviside(x - a, 1)`` if ``n >= 0``. + + Examples + ======== + + >>> from sympy import SingularityFunction, diff, Piecewise, DiracDelta, Heaviside, Symbol + >>> from sympy.abc import x, a, n + >>> SingularityFunction(x, a, n) + SingularityFunction(x, a, n) + >>> y = Symbol('y', positive=True) + >>> n = Symbol('n', nonnegative=True) + >>> SingularityFunction(y, -10, n) + (y + 10)**n + >>> y = Symbol('y', negative=True) + >>> SingularityFunction(y, 10, n) + 0 + >>> SingularityFunction(x, 4, -1).subs(x, 4) + oo + >>> SingularityFunction(x, 10, -2).subs(x, 10) + oo + >>> SingularityFunction(4, 1, 5) + 243 + >>> diff(SingularityFunction(x, 1, 5) + SingularityFunction(x, 1, 4), x) + 4*SingularityFunction(x, 1, 3) + 5*SingularityFunction(x, 1, 4) + >>> diff(SingularityFunction(x, 4, 0), x, 2) + SingularityFunction(x, 4, -2) + >>> SingularityFunction(x, 4, 5).rewrite(Piecewise) + Piecewise(((x - 4)**5, x >= 4), (0, True)) + >>> expr = SingularityFunction(x, a, n) + >>> y = Symbol('y', positive=True) + >>> n = Symbol('n', nonnegative=True) + >>> expr.subs({x: y, a: -10, n: n}) + (y + 10)**n + + The methods ``rewrite(DiracDelta)``, ``rewrite(Heaviside)``, and + ``rewrite('HeavisideDiracDelta')`` returns the same output. One can use any + of these methods according to their choice. + + >>> expr = SingularityFunction(x, 4, 5) + SingularityFunction(x, -3, -1) - SingularityFunction(x, 0, -2) + >>> expr.rewrite(Heaviside) + (x - 4)**5*Heaviside(x - 4, 1) + DiracDelta(x + 3) - DiracDelta(x, 1) + >>> expr.rewrite(DiracDelta) + (x - 4)**5*Heaviside(x - 4, 1) + DiracDelta(x + 3) - DiracDelta(x, 1) + >>> expr.rewrite('HeavisideDiracDelta') + (x - 4)**5*Heaviside(x - 4, 1) + DiracDelta(x + 3) - DiracDelta(x, 1) + + See Also + ======== + + DiracDelta, Heaviside + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Singularity_function + + """ + + is_real = True + + def fdiff(self, argindex=1): + """ + Returns the first derivative of a DiracDelta Function. + + Explanation + =========== + + The difference between ``diff()`` and ``fdiff()`` is: ``diff()`` is the + user-level function and ``fdiff()`` is an object method. ``fdiff()`` is + a convenience method available in the ``Function`` class. It returns + the derivative of the function without considering the chain rule. + ``diff(function, x)`` calls ``Function._eval_derivative`` which in turn + calls ``fdiff()`` internally to compute the derivative of the function. + + """ + + if argindex == 1: + x, a, n = self.args + if n in (S.Zero, S.NegativeOne, S(-2), S(-3)): + return self.func(x, a, n-1) + elif n.is_positive: + return n*self.func(x, a, n-1) + else: + raise ArgumentIndexError(self, argindex) + + @classmethod + def eval(cls, variable, offset, exponent): + """ + Returns a simplified form or a value of Singularity Function depending + on the argument passed by the object. + + Explanation + =========== + + The ``eval()`` method is automatically called when the + ``SingularityFunction`` class is about to be instantiated and it + returns either some simplified instance or the unevaluated instance + depending on the argument passed. In other words, ``eval()`` method is + not needed to be called explicitly, it is being called and evaluated + once the object is called. + + Examples + ======== + + >>> from sympy import SingularityFunction, Symbol, nan + >>> from sympy.abc import x, a, n + >>> SingularityFunction(x, a, n) + SingularityFunction(x, a, n) + >>> SingularityFunction(5, 3, 2) + 4 + >>> SingularityFunction(x, a, nan) + nan + >>> SingularityFunction(x, 3, 0).subs(x, 3) + 1 + >>> SingularityFunction(4, 1, 5) + 243 + >>> x = Symbol('x', positive = True) + >>> a = Symbol('a', negative = True) + >>> n = Symbol('n', nonnegative = True) + >>> SingularityFunction(x, a, n) + (-a + x)**n + >>> x = Symbol('x', negative = True) + >>> a = Symbol('a', positive = True) + >>> SingularityFunction(x, a, n) + 0 + + """ + + x = variable + a = offset + n = exponent + shift = (x - a) + + if fuzzy_not(im(shift).is_zero): + raise ValueError("Singularity Functions are defined only for Real Numbers.") + if fuzzy_not(im(n).is_zero): + raise ValueError("Singularity Functions are not defined for imaginary exponents.") + if shift is S.NaN or n is S.NaN: + return S.NaN + if (n + 4).is_negative: + raise ValueError("Singularity Functions are not defined for exponents less than -4.") + if shift.is_extended_negative: + return S.Zero + if n.is_nonnegative: + if shift.is_zero: # use literal 0 in case of Symbol('z', zero=True) + return S.Zero**n + if shift.is_extended_nonnegative: + return shift**n + if n in (S.NegativeOne, -2, -3, -4): + if shift.is_negative or shift.is_extended_positive: + return S.Zero + if shift.is_zero: + return oo + + def _eval_rewrite_as_Piecewise(self, *args, **kwargs): + ''' + Converts a Singularity Function expression into its Piecewise form. + + ''' + x, a, n = self.args + + if n in (S.NegativeOne, S(-2), S(-3), S(-4)): + return Piecewise((oo, Eq(x - a, 0)), (0, True)) + elif n.is_nonnegative: + return Piecewise(((x - a)**n, x - a >= 0), (0, True)) + + def _eval_rewrite_as_Heaviside(self, *args, **kwargs): + ''' + Rewrites a Singularity Function expression using Heavisides and DiracDeltas. + + ''' + x, a, n = self.args + + if n == -4: + return diff(Heaviside(x - a), x.free_symbols.pop(), 4) + if n == -3: + return diff(Heaviside(x - a), x.free_symbols.pop(), 3) + if n == -2: + return diff(Heaviside(x - a), x.free_symbols.pop(), 2) + if n == -1: + return diff(Heaviside(x - a), x.free_symbols.pop(), 1) + if n.is_nonnegative: + return (x - a)**n*Heaviside(x - a, 1) + + def _eval_as_leading_term(self, x, logx, cdir): + z, a, n = self.args + shift = (z - a).subs(x, 0) + if n < 0: + return S.Zero + elif n.is_zero and shift.is_zero: + return S.Zero if cdir == -1 else S.One + elif shift.is_positive: + return shift**n + return S.Zero + + def _eval_nseries(self, x, n, logx=None, cdir=0): + z, a, n = self.args + shift = (z - a).subs(x, 0) + if n < 0: + return S.Zero + elif n.is_zero and shift.is_zero: + return S.Zero if cdir == -1 else S.One + elif shift.is_positive: + return ((z - a)**n)._eval_nseries(x, n, logx=logx, cdir=cdir) + return S.Zero + + _eval_rewrite_as_DiracDelta = _eval_rewrite_as_Heaviside + _eval_rewrite_as_HeavisideDiracDelta = _eval_rewrite_as_Heaviside diff --git a/phivenv/Lib/site-packages/sympy/functions/special/spherical_harmonics.py b/phivenv/Lib/site-packages/sympy/functions/special/spherical_harmonics.py new file mode 100644 index 0000000000000000000000000000000000000000..541546b75e882b43c41814b5e92bb85ee41628d1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/spherical_harmonics.py @@ -0,0 +1,334 @@ +from sympy.core.expr import Expr +from sympy.core.function import DefinedFunction, ArgumentIndexError +from sympy.core.numbers import I, pi +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.functions import assoc_legendre +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import Abs, conjugate +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin, cos, cot + +_x = Dummy("x") + +class Ynm(DefinedFunction): + r""" + Spherical harmonics defined as + + .. math:: + Y_n^m(\theta, \varphi) := \sqrt{\frac{(2n+1)(n-m)!}{4\pi(n+m)!}} + \exp(i m \varphi) + \mathrm{P}_n^m\left(\cos(\theta)\right) + + Explanation + =========== + + ``Ynm()`` gives the spherical harmonic function of order $n$ and $m$ + in $\theta$ and $\varphi$, $Y_n^m(\theta, \varphi)$. The four + parameters are as follows: $n \geq 0$ an integer and $m$ an integer + such that $-n \leq m \leq n$ holds. The two angles are real-valued + with $\theta \in [0, \pi]$ and $\varphi \in [0, 2\pi]$. + + Examples + ======== + + >>> from sympy import Ynm, Symbol, simplify + >>> from sympy.abc import n,m + >>> theta = Symbol("theta") + >>> phi = Symbol("phi") + + >>> Ynm(n, m, theta, phi) + Ynm(n, m, theta, phi) + + Several symmetries are known, for the order: + + >>> Ynm(n, -m, theta, phi) + (-1)**m*exp(-2*I*m*phi)*Ynm(n, m, theta, phi) + + As well as for the angles: + + >>> Ynm(n, m, -theta, phi) + Ynm(n, m, theta, phi) + + >>> Ynm(n, m, theta, -phi) + exp(-2*I*m*phi)*Ynm(n, m, theta, phi) + + For specific integers $n$ and $m$ we can evaluate the harmonics + to more useful expressions: + + >>> simplify(Ynm(0, 0, theta, phi).expand(func=True)) + 1/(2*sqrt(pi)) + + >>> simplify(Ynm(1, -1, theta, phi).expand(func=True)) + sqrt(6)*exp(-I*phi)*sin(theta)/(4*sqrt(pi)) + + >>> simplify(Ynm(1, 0, theta, phi).expand(func=True)) + sqrt(3)*cos(theta)/(2*sqrt(pi)) + + >>> simplify(Ynm(1, 1, theta, phi).expand(func=True)) + -sqrt(6)*exp(I*phi)*sin(theta)/(4*sqrt(pi)) + + >>> simplify(Ynm(2, -2, theta, phi).expand(func=True)) + sqrt(30)*exp(-2*I*phi)*sin(theta)**2/(8*sqrt(pi)) + + >>> simplify(Ynm(2, -1, theta, phi).expand(func=True)) + sqrt(30)*exp(-I*phi)*sin(2*theta)/(8*sqrt(pi)) + + >>> simplify(Ynm(2, 0, theta, phi).expand(func=True)) + sqrt(5)*(3*cos(theta)**2 - 1)/(4*sqrt(pi)) + + >>> simplify(Ynm(2, 1, theta, phi).expand(func=True)) + -sqrt(30)*exp(I*phi)*sin(2*theta)/(8*sqrt(pi)) + + >>> simplify(Ynm(2, 2, theta, phi).expand(func=True)) + sqrt(30)*exp(2*I*phi)*sin(theta)**2/(8*sqrt(pi)) + + We can differentiate the functions with respect + to both angles: + + >>> from sympy import Ynm, Symbol, diff + >>> from sympy.abc import n,m + >>> theta = Symbol("theta") + >>> phi = Symbol("phi") + + >>> diff(Ynm(n, m, theta, phi), theta) + m*cot(theta)*Ynm(n, m, theta, phi) + sqrt((-m + n)*(m + n + 1))*exp(-I*phi)*Ynm(n, m + 1, theta, phi) + + >>> diff(Ynm(n, m, theta, phi), phi) + I*m*Ynm(n, m, theta, phi) + + Further we can compute the complex conjugation: + + >>> from sympy import Ynm, Symbol, conjugate + >>> from sympy.abc import n,m + >>> theta = Symbol("theta") + >>> phi = Symbol("phi") + + >>> conjugate(Ynm(n, m, theta, phi)) + (-1)**(2*m)*exp(-2*I*m*phi)*Ynm(n, m, theta, phi) + + To get back the well known expressions in spherical + coordinates, we use full expansion: + + >>> from sympy import Ynm, Symbol, expand_func + >>> from sympy.abc import n,m + >>> theta = Symbol("theta") + >>> phi = Symbol("phi") + + >>> expand_func(Ynm(n, m, theta, phi)) + sqrt((2*n + 1)*factorial(-m + n)/factorial(m + n))*exp(I*m*phi)*assoc_legendre(n, m, cos(theta))/(2*sqrt(pi)) + + See Also + ======== + + Ynm_c, Znm + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Spherical_harmonics + .. [2] https://mathworld.wolfram.com/SphericalHarmonic.html + .. [3] https://functions.wolfram.com/Polynomials/SphericalHarmonicY/ + .. [4] https://dlmf.nist.gov/14.30 + + """ + + @classmethod + def eval(cls, n, m, theta, phi): + # Handle negative index m and arguments theta, phi + if m.could_extract_minus_sign(): + m = -m + return S.NegativeOne**m * exp(-2*I*m*phi) * Ynm(n, m, theta, phi) + if theta.could_extract_minus_sign(): + theta = -theta + return Ynm(n, m, theta, phi) + if phi.could_extract_minus_sign(): + phi = -phi + return exp(-2*I*m*phi) * Ynm(n, m, theta, phi) + + # TODO Add more simplififcation here + + def _eval_expand_func(self, **hints): + n, m, theta, phi = self.args + rv = (sqrt((2*n + 1)/(4*pi) * factorial(n - m)/factorial(n + m)) * + exp(I*m*phi) * assoc_legendre(n, m, cos(theta))) + # We can do this because of the range of theta + return rv.subs(sqrt(-cos(theta)**2 + 1), sin(theta)) + + def fdiff(self, argindex=4): + if argindex == 1: + # Diff wrt n + raise ArgumentIndexError(self, argindex) + elif argindex == 2: + # Diff wrt m + raise ArgumentIndexError(self, argindex) + elif argindex == 3: + # Diff wrt theta + n, m, theta, phi = self.args + return (m * cot(theta) * Ynm(n, m, theta, phi) + + sqrt((n - m)*(n + m + 1)) * exp(-I*phi) * Ynm(n, m + 1, theta, phi)) + elif argindex == 4: + # Diff wrt phi + n, m, theta, phi = self.args + return I * m * Ynm(n, m, theta, phi) + else: + raise ArgumentIndexError(self, argindex) + + def _eval_rewrite_as_polynomial(self, n, m, theta, phi, **kwargs): + # TODO: Make sure n \in N + # TODO: Assert |m| <= n ortherwise we should return 0 + return self.expand(func=True) + + def _eval_rewrite_as_sin(self, n, m, theta, phi, **kwargs): + return self.rewrite(cos) + + def _eval_rewrite_as_cos(self, n, m, theta, phi, **kwargs): + # This method can be expensive due to extensive use of simplification! + from sympy.simplify import simplify, trigsimp + # TODO: Make sure n \in N + # TODO: Assert |m| <= n ortherwise we should return 0 + term = simplify(self.expand(func=True)) + # We can do this because of the range of theta + term = term.xreplace({Abs(sin(theta)):sin(theta)}) + return simplify(trigsimp(term)) + + def _eval_conjugate(self): + # TODO: Make sure theta \in R and phi \in R + n, m, theta, phi = self.args + return S.NegativeOne**m * self.func(n, -m, theta, phi) + + def as_real_imag(self, deep=True, **hints): + # TODO: Handle deep and hints + n, m, theta, phi = self.args + re = (sqrt((2*n + 1)/(4*pi) * factorial(n - m)/factorial(n + m)) * + cos(m*phi) * assoc_legendre(n, m, cos(theta))) + im = (sqrt((2*n + 1)/(4*pi) * factorial(n - m)/factorial(n + m)) * + sin(m*phi) * assoc_legendre(n, m, cos(theta))) + return (re, im) + + def _eval_evalf(self, prec): + # Note: works without this function by just calling + # mpmath for Legendre polynomials. But using + # the dedicated function directly is cleaner. + from mpmath import mp, workprec + n = self.args[0]._to_mpmath(prec) + m = self.args[1]._to_mpmath(prec) + theta = self.args[2]._to_mpmath(prec) + phi = self.args[3]._to_mpmath(prec) + with workprec(prec): + res = mp.spherharm(n, m, theta, phi) + return Expr._from_mpmath(res, prec) + + +def Ynm_c(n, m, theta, phi): + r""" + Conjugate spherical harmonics defined as + + .. math:: + \overline{Y_n^m(\theta, \varphi)} := (-1)^m Y_n^{-m}(\theta, \varphi). + + Examples + ======== + + >>> from sympy import Ynm_c, Symbol, simplify + >>> from sympy.abc import n,m + >>> theta = Symbol("theta") + >>> phi = Symbol("phi") + >>> Ynm_c(n, m, theta, phi) + (-1)**(2*m)*exp(-2*I*m*phi)*Ynm(n, m, theta, phi) + >>> Ynm_c(n, m, -theta, phi) + (-1)**(2*m)*exp(-2*I*m*phi)*Ynm(n, m, theta, phi) + + For specific integers $n$ and $m$ we can evaluate the harmonics + to more useful expressions: + + >>> simplify(Ynm_c(0, 0, theta, phi).expand(func=True)) + 1/(2*sqrt(pi)) + >>> simplify(Ynm_c(1, -1, theta, phi).expand(func=True)) + sqrt(6)*exp(I*(-phi + 2*conjugate(phi)))*sin(theta)/(4*sqrt(pi)) + + See Also + ======== + + Ynm, Znm + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Spherical_harmonics + .. [2] https://mathworld.wolfram.com/SphericalHarmonic.html + .. [3] https://functions.wolfram.com/Polynomials/SphericalHarmonicY/ + + """ + return conjugate(Ynm(n, m, theta, phi)) + + +class Znm(DefinedFunction): + r""" + Real spherical harmonics defined as + + .. math:: + + Z_n^m(\theta, \varphi) := + \begin{cases} + \frac{Y_n^m(\theta, \varphi) + \overline{Y_n^m(\theta, \varphi)}}{\sqrt{2}} &\quad m > 0 \\ + Y_n^m(\theta, \varphi) &\quad m = 0 \\ + \frac{Y_n^m(\theta, \varphi) - \overline{Y_n^m(\theta, \varphi)}}{i \sqrt{2}} &\quad m < 0 \\ + \end{cases} + + which gives in simplified form + + .. math:: + + Z_n^m(\theta, \varphi) = + \begin{cases} + \frac{Y_n^m(\theta, \varphi) + (-1)^m Y_n^{-m}(\theta, \varphi)}{\sqrt{2}} &\quad m > 0 \\ + Y_n^m(\theta, \varphi) &\quad m = 0 \\ + \frac{Y_n^m(\theta, \varphi) - (-1)^m Y_n^{-m}(\theta, \varphi)}{i \sqrt{2}} &\quad m < 0 \\ + \end{cases} + + Examples + ======== + + >>> from sympy import Znm, Symbol, simplify + >>> from sympy.abc import n, m + >>> theta = Symbol("theta") + >>> phi = Symbol("phi") + >>> Znm(n, m, theta, phi) + Znm(n, m, theta, phi) + + For specific integers n and m we can evaluate the harmonics + to more useful expressions: + + >>> simplify(Znm(0, 0, theta, phi).expand(func=True)) + 1/(2*sqrt(pi)) + >>> simplify(Znm(1, 1, theta, phi).expand(func=True)) + -sqrt(3)*sin(theta)*cos(phi)/(2*sqrt(pi)) + >>> simplify(Znm(2, 1, theta, phi).expand(func=True)) + -sqrt(15)*sin(2*theta)*cos(phi)/(4*sqrt(pi)) + + See Also + ======== + + Ynm, Ynm_c + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Spherical_harmonics + .. [2] https://mathworld.wolfram.com/SphericalHarmonic.html + .. [3] https://functions.wolfram.com/Polynomials/SphericalHarmonicY/ + + """ + + @classmethod + def eval(cls, n, m, theta, phi): + if m.is_positive: + zz = (Ynm(n, m, theta, phi) + Ynm_c(n, m, theta, phi)) / sqrt(2) + return zz + elif m.is_zero: + return Ynm(n, m, theta, phi) + elif m.is_negative: + zz = (Ynm(n, m, theta, phi) - Ynm_c(n, m, theta, phi)) / (sqrt(2)*I) + return zz diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tensor_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tensor_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..6d996a58cbc8320620c9a1f6e68529c3b5e99aef --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tensor_functions.py @@ -0,0 +1,474 @@ +from math import prod + +from sympy.core import S, Integer +from sympy.core.function import DefinedFunction +from sympy.core.logic import fuzzy_not +from sympy.core.relational import Ne +from sympy.core.sorting import default_sort_key +from sympy.external.gmpy import SYMPY_INTS +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.piecewise import Piecewise +from sympy.utilities.iterables import has_dups + +############################################################################### +###################### Kronecker Delta, Levi-Civita etc. ###################### +############################################################################### + + +def Eijk(*args, **kwargs): + """ + Represent the Levi-Civita symbol. + + This is a compatibility wrapper to ``LeviCivita()``. + + See Also + ======== + + LeviCivita + + """ + return LeviCivita(*args, **kwargs) + + +def eval_levicivita(*args): + """Evaluate Levi-Civita symbol.""" + n = len(args) + return prod( + prod(args[j] - args[i] for j in range(i + 1, n)) + / factorial(i) for i in range(n)) + # converting factorial(i) to int is slightly faster + + +class LeviCivita(DefinedFunction): + """ + Represent the Levi-Civita symbol. + + Explanation + =========== + + For even permutations of indices it returns 1, for odd permutations -1, and + for everything else (a repeated index) it returns 0. + + Thus it represents an alternating pseudotensor. + + Examples + ======== + + >>> from sympy import LeviCivita + >>> from sympy.abc import i, j, k + >>> LeviCivita(1, 2, 3) + 1 + >>> LeviCivita(1, 3, 2) + -1 + >>> LeviCivita(1, 2, 2) + 0 + >>> LeviCivita(i, j, k) + LeviCivita(i, j, k) + >>> LeviCivita(i, j, i) + 0 + + See Also + ======== + + Eijk + + """ + + is_integer = True + + @classmethod + def eval(cls, *args): + if all(isinstance(a, (SYMPY_INTS, Integer)) for a in args): + return eval_levicivita(*args) + if has_dups(args): + return S.Zero + + def doit(self, **hints): + return eval_levicivita(*self.args) + + +class KroneckerDelta(DefinedFunction): + """ + The discrete, or Kronecker, delta function. + + Explanation + =========== + + A function that takes in two integers $i$ and $j$. It returns $0$ if $i$ + and $j$ are not equal, or it returns $1$ if $i$ and $j$ are equal. + + Examples + ======== + + An example with integer indices: + + >>> from sympy import KroneckerDelta + >>> KroneckerDelta(1, 2) + 0 + >>> KroneckerDelta(3, 3) + 1 + + Symbolic indices: + + >>> from sympy.abc import i, j, k + >>> KroneckerDelta(i, j) + KroneckerDelta(i, j) + >>> KroneckerDelta(i, i) + 1 + >>> KroneckerDelta(i, i + 1) + 0 + >>> KroneckerDelta(i, i + 1 + k) + KroneckerDelta(i, i + k + 1) + + Parameters + ========== + + i : Number, Symbol + The first index of the delta function. + j : Number, Symbol + The second index of the delta function. + + See Also + ======== + + eval + DiracDelta + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Kronecker_delta + + """ + + is_integer = True + + @classmethod + def eval(cls, i, j, delta_range=None): + """ + Evaluates the discrete delta function. + + Examples + ======== + + >>> from sympy import KroneckerDelta + >>> from sympy.abc import i, j, k + + >>> KroneckerDelta(i, j) + KroneckerDelta(i, j) + >>> KroneckerDelta(i, i) + 1 + >>> KroneckerDelta(i, i + 1) + 0 + >>> KroneckerDelta(i, i + 1 + k) + KroneckerDelta(i, i + k + 1) + + # indirect doctest + + """ + + if delta_range is not None: + dinf, dsup = delta_range + if (dinf - i > 0) == True: + return S.Zero + if (dinf - j > 0) == True: + return S.Zero + if (dsup - i < 0) == True: + return S.Zero + if (dsup - j < 0) == True: + return S.Zero + + diff = i - j + if diff.is_zero: + return S.One + elif fuzzy_not(diff.is_zero): + return S.Zero + + if i.assumptions0.get("below_fermi") and \ + j.assumptions0.get("above_fermi"): + return S.Zero + if j.assumptions0.get("below_fermi") and \ + i.assumptions0.get("above_fermi"): + return S.Zero + # to make KroneckerDelta canonical + # following lines will check if inputs are in order + # if not, will return KroneckerDelta with correct order + if default_sort_key(j) < default_sort_key(i): + if delta_range: + return cls(j, i, delta_range) + else: + return cls(j, i) + + @property + def delta_range(self): + if len(self.args) > 2: + return self.args[2] + + def _eval_power(self, expt): + if expt.is_positive: + return self + if expt.is_negative and expt is not S.NegativeOne: + return 1/self + + @property + def is_above_fermi(self): + """ + True if Delta can be non-zero above fermi. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> p = Symbol('p') + >>> q = Symbol('q') + >>> KroneckerDelta(p, a).is_above_fermi + True + >>> KroneckerDelta(p, i).is_above_fermi + False + >>> KroneckerDelta(p, q).is_above_fermi + True + + See Also + ======== + + is_below_fermi, is_only_below_fermi, is_only_above_fermi + + """ + if self.args[0].assumptions0.get("below_fermi"): + return False + if self.args[1].assumptions0.get("below_fermi"): + return False + return True + + @property + def is_below_fermi(self): + """ + True if Delta can be non-zero below fermi. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> p = Symbol('p') + >>> q = Symbol('q') + >>> KroneckerDelta(p, a).is_below_fermi + False + >>> KroneckerDelta(p, i).is_below_fermi + True + >>> KroneckerDelta(p, q).is_below_fermi + True + + See Also + ======== + + is_above_fermi, is_only_above_fermi, is_only_below_fermi + + """ + if self.args[0].assumptions0.get("above_fermi"): + return False + if self.args[1].assumptions0.get("above_fermi"): + return False + return True + + @property + def is_only_above_fermi(self): + """ + True if Delta is restricted to above fermi. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> p = Symbol('p') + >>> q = Symbol('q') + >>> KroneckerDelta(p, a).is_only_above_fermi + True + >>> KroneckerDelta(p, q).is_only_above_fermi + False + >>> KroneckerDelta(p, i).is_only_above_fermi + False + + See Also + ======== + + is_above_fermi, is_below_fermi, is_only_below_fermi + + """ + return ( self.args[0].assumptions0.get("above_fermi") + or + self.args[1].assumptions0.get("above_fermi") + ) or False + + @property + def is_only_below_fermi(self): + """ + True if Delta is restricted to below fermi. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> p = Symbol('p') + >>> q = Symbol('q') + >>> KroneckerDelta(p, i).is_only_below_fermi + True + >>> KroneckerDelta(p, q).is_only_below_fermi + False + >>> KroneckerDelta(p, a).is_only_below_fermi + False + + See Also + ======== + + is_above_fermi, is_below_fermi, is_only_above_fermi + + """ + return ( self.args[0].assumptions0.get("below_fermi") + or + self.args[1].assumptions0.get("below_fermi") + ) or False + + @property + def indices_contain_equal_information(self): + """ + Returns True if indices are either both above or below fermi. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> p = Symbol('p') + >>> q = Symbol('q') + >>> KroneckerDelta(p, q).indices_contain_equal_information + True + >>> KroneckerDelta(p, q+1).indices_contain_equal_information + True + >>> KroneckerDelta(i, p).indices_contain_equal_information + False + + """ + if (self.args[0].assumptions0.get("below_fermi") and + self.args[1].assumptions0.get("below_fermi")): + return True + if (self.args[0].assumptions0.get("above_fermi") + and self.args[1].assumptions0.get("above_fermi")): + return True + + # if both indices are general we are True, else false + return self.is_below_fermi and self.is_above_fermi + + @property + def preferred_index(self): + """ + Returns the index which is preferred to keep in the final expression. + + Explanation + =========== + + The preferred index is the index with more information regarding fermi + level. If indices contain the same information, 'a' is preferred before + 'b'. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> j = Symbol('j', below_fermi=True) + >>> p = Symbol('p') + >>> KroneckerDelta(p, i).preferred_index + i + >>> KroneckerDelta(p, a).preferred_index + a + >>> KroneckerDelta(i, j).preferred_index + i + + See Also + ======== + + killable_index + + """ + if self._get_preferred_index(): + return self.args[1] + else: + return self.args[0] + + @property + def killable_index(self): + """ + Returns the index which is preferred to substitute in the final + expression. + + Explanation + =========== + + The index to substitute is the index with less information regarding + fermi level. If indices contain the same information, 'a' is preferred + before 'b'. + + Examples + ======== + + >>> from sympy import KroneckerDelta, Symbol + >>> a = Symbol('a', above_fermi=True) + >>> i = Symbol('i', below_fermi=True) + >>> j = Symbol('j', below_fermi=True) + >>> p = Symbol('p') + >>> KroneckerDelta(p, i).killable_index + p + >>> KroneckerDelta(p, a).killable_index + p + >>> KroneckerDelta(i, j).killable_index + j + + See Also + ======== + + preferred_index + + """ + if self._get_preferred_index(): + return self.args[0] + else: + return self.args[1] + + def _get_preferred_index(self): + """ + Returns the index which is preferred to keep in the final expression. + + The preferred index is the index with more information regarding fermi + level. If indices contain the same information, index 0 is returned. + + """ + if not self.is_above_fermi: + if self.args[0].assumptions0.get("below_fermi"): + return 0 + else: + return 1 + elif not self.is_below_fermi: + if self.args[0].assumptions0.get("above_fermi"): + return 0 + else: + return 1 + else: + return 0 + + @property + def indices(self): + return self.args[0:2] + + def _eval_rewrite_as_Piecewise(self, *args, **kwargs): + i, j = args + return Piecewise((0, Ne(i, j)), (1, True)) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__init__.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea6c6c940721c39083fa4d6f7d952a28068df703 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_bessel.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_bessel.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97f0af0e95ab53080871b97c1f603396d03bf75a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_bessel.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_beta_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_beta_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0077dcb70cc972f3c91f223f1af2abf68669e63c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_beta_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_bsplines.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_bsplines.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8b5dbee19e5a6f8152f7ec0b091044bd72c0e9e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_bsplines.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_delta_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_delta_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73fb8af1c4dec24012b5b89687ae88c6d3648f0a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_delta_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_elliptic_integrals.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_elliptic_integrals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c5ee26b0dfbf1e66f90e51968b15f791c446488 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_elliptic_integrals.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_error_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_error_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a421a6e91b2d4b8212aa4eb45e64457f2a3e36b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_error_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_gamma_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_gamma_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49d150b133f910eac84ac01baed33fe41cc61fdd Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_gamma_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_hyper.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_hyper.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2337fe69ef3ffd68f252dbfa24f617c66c32744d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_hyper.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_mathieu.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_mathieu.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6dd4677adfefe41dbec0ffe1ad46b92de59ecac Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_mathieu.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_singularity_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_singularity_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0684f81ff14c601326deb35cbc06e2c65dfbf5c4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_singularity_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_spec_polynomials.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_spec_polynomials.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e213679b9e85a4723c4a100a5fd5926e1789f68 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_spec_polynomials.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_spherical_harmonics.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_spherical_harmonics.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94123e4c33162b8646acef62cd514c511f264270 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_spherical_harmonics.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_tensor_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_tensor_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b47fe0bb53c9d0b538e92139537afdad37f1cf8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_tensor_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_zeta_functions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_zeta_functions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbc2edb65c4a485fcbff2651f57d60f55c62993a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/functions/special/tests/__pycache__/test_zeta_functions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_bessel.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..ccd1ce88ca9dea15f065e7c57d488498b8f79f4e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_bessel.py @@ -0,0 +1,807 @@ +from itertools import product + +from sympy.concrete.summations import Sum +from sympy.core.function import (diff, expand_func) +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (conjugate, polar_lift) +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.hyperbolic import (cosh, sinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.bessel import (besseli, besselj, besselk, bessely, hankel1, hankel2, hn1, hn2, jn, jn_zeros, yn) +from sympy.functions.special.gamma_functions import (gamma, uppergamma) +from sympy.functions.special.hyper import hyper +from sympy.integrals.integrals import Integral +from sympy.series.order import O +from sympy.series.series import series +from sympy.functions.special.bessel import (airyai, airybi, + airyaiprime, airybiprime, marcumq) +from sympy.core.random import (random_complex_number as randcplx, + verify_numerically as tn, + test_derivative_numerically as td, + _randint) +from sympy.simplify import besselsimp +from sympy.testing.pytest import raises, slow + +from sympy.abc import z, n, k, x + +randint = _randint() + + +def test_bessel_rand(): + for f in [besselj, bessely, besseli, besselk, hankel1, hankel2]: + assert td(f(randcplx(), z), z) + + for f in [jn, yn, hn1, hn2]: + assert td(f(randint(-10, 10), z), z) + + +def test_bessel_twoinputs(): + for f in [besselj, bessely, besseli, besselk, hankel1, hankel2, jn, yn]: + raises(TypeError, lambda: f(1)) + raises(TypeError, lambda: f(1, 2, 3)) + + +def test_besselj_leading_term(): + assert besselj(0, x).as_leading_term(x) == 1 + assert besselj(1, sin(x)).as_leading_term(x) == x/2 + assert besselj(1, 2*sqrt(x)).as_leading_term(x) == sqrt(x) + + # https://github.com/sympy/sympy/issues/21701 + assert (besselj(z, x)/x**z).as_leading_term(x) == 1/(2**z*gamma(z + 1)) + + +def test_bessely_leading_term(): + assert bessely(0, x).as_leading_term(x) == (2*log(x) - 2*log(2) + 2*S.EulerGamma)/pi + assert bessely(1, sin(x)).as_leading_term(x) == -2/(pi*x) + assert bessely(1, 2*sqrt(x)).as_leading_term(x) == -1/(pi*sqrt(x)) + + +def test_besseli_leading_term(): + assert besseli(0, x).as_leading_term(x) == 1 + assert besseli(1, sin(x)).as_leading_term(x) == x/2 + assert besseli(1, 2*sqrt(x)).as_leading_term(x) == sqrt(x) + + +def test_besselk_leading_term(): + assert besselk(0, x).as_leading_term(x) == -log(x) - S.EulerGamma + log(2) + assert besselk(1, sin(x)).as_leading_term(x) == 1/x + assert besselk(1, 2*sqrt(x)).as_leading_term(x) == 1/(2*sqrt(x)) + assert besselk(S(5)/3, x).as_leading_term(x) == 2**(S(2)/3)*gamma(S(5)/3)/x**(S(5)/3) + assert besselk(S(2)/3, x).as_leading_term(x) == besselk(-S(2)/3, x).as_leading_term(x) + assert besselk(1,cos(x)).as_leading_term(x) == besselk(1,1) + assert besselk(3,1/x).as_leading_term(x) == sqrt(pi)*exp(-(1/x))/sqrt(2/x) + assert besselk(3,1/sin(x)).as_leading_term(x) == sqrt(pi)*exp(-(1/x))/sqrt(2/x) + + nz = Symbol("nz", nonzero=True) + assert besselk(nz, x).as_leading_term(x).subs({nz:S(5)/7}) == besselk(S(5)/7, x).series(x).as_leading_term(x) + assert besselk(nz, x).as_leading_term(x).subs({nz:S(-15)/7}) == besselk(S(-15)/7, x).series(x).as_leading_term(x) + assert besselk(nz, x).as_leading_term(x).subs({nz:3}) == besselk(3, x).series(x).as_leading_term(x) + assert besselk(nz, x).as_leading_term(x).subs({nz:-2}) == besselk(-2, x).series(x).as_leading_term(x) + + +def test_besselj_series(): + assert besselj(0, x).series(x) == 1 - x**2/4 + x**4/64 + O(x**6) + assert besselj(0, x**(1.1)).series(x) == 1 + x**4.4/64 - x**2.2/4 + O(x**6) + assert besselj(0, x**2 + x).series(x) == 1 - x**2/4 - x**3/2\ + - 15*x**4/64 + x**5/16 + O(x**6) + assert besselj(0, sqrt(x) + x).series(x, n=4) == 1 - x/4 - 15*x**2/64\ + + 215*x**3/2304 - x**Rational(3, 2)/2 + x**Rational(5, 2)/16\ + + 23*x**Rational(7, 2)/384 + O(x**4) + assert besselj(0, x/(1 - x)).series(x) == 1 - x**2/4 - x**3/2 - 47*x**4/64\ + - 15*x**5/16 + O(x**6) + assert besselj(0, log(1 + x)).series(x) == 1 - x**2/4 + x**3/4\ + - 41*x**4/192 + 17*x**5/96 + O(x**6) + assert besselj(1, sin(x)).series(x) == x/2 - 7*x**3/48 + 73*x**5/1920 + O(x**6) + assert besselj(1, 2*sqrt(x)).series(x) == sqrt(x) - x**Rational(3, 2)/2\ + + x**Rational(5, 2)/12 - x**Rational(7, 2)/144 + x**Rational(9, 2)/2880\ + - x**Rational(11, 2)/86400 + O(x**6) + assert besselj(-2, sin(x)).series(x, n=4) == besselj(2, sin(x)).series(x, n=4) + + +def test_bessely_series(): + const = 2*S.EulerGamma/pi - 2*log(2)/pi + 2*log(x)/pi + assert bessely(0, x).series(x, n=4) == const + x**2*(-log(x)/(2*pi)\ + + (2 - 2*S.EulerGamma)/(4*pi) + log(2)/(2*pi)) + O(x**4*log(x)) + assert bessely(1, x).series(x, n=4) == -2/(pi*x) + x*(log(x)/pi - log(2)/pi - \ + (1 - 2*S.EulerGamma)/(2*pi)) + x**3*(-log(x)/(8*pi) + \ + (S(5)/2 - 2*S.EulerGamma)/(16*pi) + log(2)/(8*pi)) + O(x**4*log(x)) + assert bessely(2, x).series(x, n=4) == -4/(pi*x**2) - 1/pi + x**2*(log(x)/(4*pi) - \ + log(2)/(4*pi) - (S(3)/2 - 2*S.EulerGamma)/(8*pi)) + O(x**4*log(x)) + assert bessely(3, x).series(x, n=4) == -16/(pi*x**3) - 2/(pi*x) - \ + x/(4*pi) + x**3*(log(x)/(24*pi) - log(2)/(24*pi) - \ + (S(11)/6 - 2*S.EulerGamma)/(48*pi)) + O(x**4*log(x)) + assert bessely(0, x**(1.1)).series(x, n=4) == 2*S.EulerGamma/pi\ + - 2*log(2)/pi + 2.2*log(x)/pi + x**2.2*(-0.55*log(x)/pi\ + + (2 - 2*S.EulerGamma)/(4*pi) + log(2)/(2*pi)) + O(x**4*log(x)) + assert bessely(0, x**2 + x).series(x, n=4) == \ + const - (2 - 2*S.EulerGamma)*(-x**3/(2*pi) - x**2/(4*pi)) + 2*x/pi\ + + x**2*(-log(x)/(2*pi) - 1/pi + log(2)/(2*pi))\ + + x**3*(-log(x)/pi + 1/(6*pi) + log(2)/pi) + O(x**4*log(x)) + assert bessely(0, x/(1 - x)).series(x, n=3) == const\ + + 2*x/pi + x**2*(-log(x)/(2*pi) + (2 - 2*S.EulerGamma)/(4*pi)\ + + log(2)/(2*pi) + 1/pi) + O(x**3*log(x)) + assert bessely(0, log(1 + x)).series(x, n=3) == const\ + - x/pi + x**2*(-log(x)/(2*pi) + (2 - 2*S.EulerGamma)/(4*pi)\ + + log(2)/(2*pi) + 5/(12*pi)) + O(x**3*log(x)) + assert bessely(1, sin(x)).series(x, n=4) == -1/(pi*(-x**3/12 + x/2)) - \ + (1 - 2*S.EulerGamma)*(-x**3/12 + x/2)/pi + x*(log(x)/pi - log(2)/pi) + \ + x**3*(-7*log(x)/(24*pi) - 1/(6*pi) + (S(5)/2 - 2*S.EulerGamma)/(16*pi) + + 7*log(2)/(24*pi)) + O(x**4*log(x)) + assert bessely(1, 2*sqrt(x)).series(x, n=3) == -1/(pi*sqrt(x)) + \ + sqrt(x)*(log(x)/pi - (1 - 2*S.EulerGamma)/pi) + x**(S(3)/2)*(-log(x)/(2*pi) + \ + (S(5)/2 - 2*S.EulerGamma)/(2*pi)) + x**(S(5)/2)*(log(x)/(12*pi) - \ + (S(10)/3 - 2*S.EulerGamma)/(12*pi)) + O(x**3*log(x)) + assert bessely(-2, sin(x)).series(x, n=4) == bessely(2, sin(x)).series(x, n=4) + + +def test_besseli_series(): + assert besseli(0, x).series(x) == 1 + x**2/4 + x**4/64 + O(x**6) + assert besseli(0, x**(1.1)).series(x) == 1 + x**4.4/64 + x**2.2/4 + O(x**6) + assert besseli(0, x**2 + x).series(x) == 1 + x**2/4 + x**3/2 + 17*x**4/64 + \ + x**5/16 + O(x**6) + assert besseli(0, sqrt(x) + x).series(x, n=4) == 1 + x/4 + 17*x**2/64 + \ + 217*x**3/2304 + x**(S(3)/2)/2 + x**(S(5)/2)/16 + 25*x**(S(7)/2)/384 + O(x**4) + assert besseli(0, x/(1 - x)).series(x) == 1 + x**2/4 + x**3/2 + 49*x**4/64 + \ + 17*x**5/16 + O(x**6) + assert besseli(0, log(1 + x)).series(x) == 1 + x**2/4 - x**3/4 + 47*x**4/192 - \ + 23*x**5/96 + O(x**6) + assert besseli(1, sin(x)).series(x) == x/2 - x**3/48 - 47*x**5/1920 + O(x**6) + assert besseli(1, 2*sqrt(x)).series(x) == sqrt(x) + x**(S(3)/2)/2 + x**(S(5)/2)/12 + \ + x**(S(7)/2)/144 + x**(S(9)/2)/2880 + x**(S(11)/2)/86400 + O(x**6) + assert besseli(-2, sin(x)).series(x, n=4) == besseli(2, sin(x)).series(x, n=4) + + #test for aseries + assert besseli(0,x).series(x, oo, n=4) == sqrt(2)*(sqrt(1/x) - (1/x)**(S(3)/2)/8 - \ + 3*(1/x)**(S(5)/2)/128 - 15*(1/x)**(S(7)/2)/1024 + O((1/x)**(S(9)/2), (x, oo)))*exp(x)/(2*sqrt(pi)) + assert besseli(0,x).series(x,-oo, n=4) == sqrt(2)*(sqrt(-1/x) - (-1/x)**(S(3)/2)/8 - 3*(-1/x)**(S(5)/2)/128 - \ + 15*(-1/x)**(S(7)/2)/1024 + O((-1/x)**(S(9)/2), (x, -oo)))*exp(-x)/(2*sqrt(pi)) + + +def test_besselk_series(): + const = log(2) - S.EulerGamma - log(x) + assert besselk(0, x).series(x, n=4) == const + \ + x**2*(-log(x)/4 - S.EulerGamma/4 + log(2)/4 + S(1)/4) + O(x**4*log(x)) + assert besselk(1, x).series(x, n=4) == 1/x + x*(log(x)/2 - log(2)/2 - \ + S(1)/4 + S.EulerGamma/2) + x**3*(log(x)/16 - S(5)/64 - log(2)/16 + \ + S.EulerGamma/16) + O(x**4*log(x)) + assert besselk(2, x).series(x, n=4) == 2/x**2 - S(1)/2 + x**2*(-log(x)/8 - \ + S.EulerGamma/8 + log(2)/8 + S(3)/32) + O(x**4*log(x)) + assert besselk(2, x).series(x, n=1) == 2/x**2 - S(1)/2 + O(x) #edge case for series truncation + assert besselk(0, x**(1.1)).series(x, n=4) == log(2) - S.EulerGamma - \ + 1.1*log(x) + x**2.2*(-0.275*log(x) - S.EulerGamma/4 + \ + log(2)/4 + S(1)/4) + O(x**4*log(x)) + assert besselk(0, x**2 + x).series(x, n=4) == const + \ + (2 - 2*S.EulerGamma)*(x**3/4 + x**2/8) - x + x**2*(-log(x)/4 + \ + log(2)/4 + S(1)/2) + x**3*(-log(x)/2 - S(7)/12 + log(2)/2) + O(x**4*log(x)) + assert besselk(0, x/(1 - x)).series(x, n=3) == const - x + x**2*(-log(x)/4 - \ + S(1)/4 - S.EulerGamma/4 + log(2)/4) + O(x**3*log(x)) + assert besselk(0, log(1 + x)).series(x, n=3) == const + x/2 + \ + x**2*(-log(x)/4 - S.EulerGamma/4 + S(1)/24 + log(2)/4) + O(x**3*log(x)) + assert besselk(1, 2*sqrt(x)).series(x, n=3) == 1/(2*sqrt(x)) + \ + sqrt(x)*(log(x)/2 - S(1)/2 + S.EulerGamma) + x**(S(3)/2)*(log(x)/4 - S(5)/8 + \ + S.EulerGamma/2) + x**(S(5)/2)*(log(x)/24 - S(5)/36 + S.EulerGamma/12) + O(x**3*log(x)) + assert besselk(-2, sin(x)).series(x, n=4) == besselk(2, sin(x)).series(x, n=4) + assert besselk(2, x**2).series(x, n=2) == 2/x**4 - S(1)/2 + O(x**2) #edge case for series truncation + assert besselk(2, x**2).series(x, n=6) == 2/x**4 - S(1)/2 + x**4*(-log(x)/4 - S.EulerGamma/8 + log(2)/8 + S(3)/32) + O(x**6*log(x)) + assert (x**2*besselk(2, x)).series(x, n=2) == 2 + O(x**2) + + #test for aseries + assert besselk(0,x).series(x, oo, n=4) == sqrt(2)*sqrt(pi)*(sqrt(1/x) + (1/x)**(S(3)/2)/8 - \ + 3*(1/x)**(S(5)/2)/128 + 15*(1/x)**(S(7)/2)/1024 + O((1/x)**(S(9)/2), (x, oo)))*exp(-x)/2 + assert besselk(0,x).series(x, -oo, n=4) == sqrt(2)*sqrt(pi)*(-I*sqrt(-1/x) + I*(-1/x)**(S(3)/2)/8 + \ + 3*I*(-1/x)**(S(5)/2)/128 + 15*I*(-1/x)**(S(7)/2)/1024 + O((-1/x)**(S(9)/2), (x, -oo)))*exp(-x)/2 + + +def test_besselk_frac_order_series(): + assert besselk(S(5)/3, x).series(x, n=2) == 2**(S(2)/3)*gamma(S(5)/3)/x**(S(5)/3) - \ + 3*gamma(S(5)/3)*x**(S(1)/3)/(4*2**(S(1)/3)) + \ + gamma(-S(5)/3)*x**(S(5)/3)/(4*2**(S(2)/3)) + O(x**2) + assert besselk(S(1)/2, x).series(x, n=2) == sqrt(pi/2)/sqrt(x) - \ + sqrt(pi*x/2) + x**(S(3)/2)*sqrt(pi/2)/2 + O(x**2) + assert besselk(S(1)/2, sqrt(x)).series(x, n=2) == sqrt(pi/2)/x**(S(1)/4) - \ + sqrt(pi/2)*x**(S(1)/4) + sqrt(pi/2)*x**(S(3)/4)/2 - \ + sqrt(pi/2)*x**(S(5)/4)/6 + sqrt(pi/2)*x**(S(7)/4)/24 + O(x**2) + assert besselk(S(1)/2, x**2).series(x, n=2) == sqrt(pi/2)/x \ + - sqrt(pi/2)*x + O(x**2) + assert besselk(-S(1)/2, x).series(x) == besselk(S(1)/2, x).series(x) + assert besselk(-S(7)/6, x).series(x) == besselk(S(7)/6, x).series(x) + + +def test_diff(): + assert besselj(n, z).diff(z) == besselj(n - 1, z)/2 - besselj(n + 1, z)/2 + assert bessely(n, z).diff(z) == bessely(n - 1, z)/2 - bessely(n + 1, z)/2 + assert besseli(n, z).diff(z) == besseli(n - 1, z)/2 + besseli(n + 1, z)/2 + assert besselk(n, z).diff(z) == -besselk(n - 1, z)/2 - besselk(n + 1, z)/2 + assert hankel1(n, z).diff(z) == hankel1(n - 1, z)/2 - hankel1(n + 1, z)/2 + assert hankel2(n, z).diff(z) == hankel2(n - 1, z)/2 - hankel2(n + 1, z)/2 + + +def test_rewrite(): + assert besselj(n, z).rewrite(jn) == sqrt(2*z/pi)*jn(n - S.Half, z) + assert bessely(n, z).rewrite(yn) == sqrt(2*z/pi)*yn(n - S.Half, z) + assert besseli(n, z).rewrite(besselj) == \ + exp(-I*n*pi/2)*besselj(n, polar_lift(I)*z) + assert besselj(n, z).rewrite(besseli) == \ + exp(I*n*pi/2)*besseli(n, polar_lift(-I)*z) + + nu = randcplx() + + assert tn(besselj(nu, z), besselj(nu, z).rewrite(besseli), z) + assert tn(besselj(nu, z), besselj(nu, z).rewrite(bessely), z) + + assert tn(besseli(nu, z), besseli(nu, z).rewrite(besselj), z) + assert tn(besseli(nu, z), besseli(nu, z).rewrite(bessely), z) + + assert tn(bessely(nu, z), bessely(nu, z).rewrite(besselj), z) + assert tn(bessely(nu, z), bessely(nu, z).rewrite(besseli), z) + + assert tn(besselk(nu, z), besselk(nu, z).rewrite(besselj), z) + assert tn(besselk(nu, z), besselk(nu, z).rewrite(besseli), z) + assert tn(besselk(nu, z), besselk(nu, z).rewrite(bessely), z) + + # check that a rewrite was triggered, when the order is set to a generic + # symbol 'nu' + assert yn(nu, z) != yn(nu, z).rewrite(jn) + assert hn1(nu, z) != hn1(nu, z).rewrite(jn) + assert hn2(nu, z) != hn2(nu, z).rewrite(jn) + assert jn(nu, z) != jn(nu, z).rewrite(yn) + assert hn1(nu, z) != hn1(nu, z).rewrite(yn) + assert hn2(nu, z) != hn2(nu, z).rewrite(yn) + + # rewriting spherical bessel functions (SBFs) w.r.t. besselj, bessely is + # not allowed if a generic symbol 'nu' is used as the order of the SBFs + # to avoid inconsistencies (the order of bessel[jy] is allowed to be + # complex-valued, whereas SBFs are defined only for integer orders) + order = nu + for f in (besselj, bessely): + assert hn1(order, z) == hn1(order, z).rewrite(f) + assert hn2(order, z) == hn2(order, z).rewrite(f) + + assert jn(order, z).rewrite(besselj) == sqrt(2)*sqrt(pi)*sqrt(1/z)*besselj(order + S.Half, z)/2 + assert jn(order, z).rewrite(bessely) == (-1)**nu*sqrt(2)*sqrt(pi)*sqrt(1/z)*bessely(-order - S.Half, z)/2 + + # for integral orders rewriting SBFs w.r.t bessel[jy] is allowed + N = Symbol('n', integer=True) + ri = randint(-11, 10) + for order in (ri, N): + for f in (besselj, bessely): + assert yn(order, z) != yn(order, z).rewrite(f) + assert jn(order, z) != jn(order, z).rewrite(f) + assert hn1(order, z) != hn1(order, z).rewrite(f) + assert hn2(order, z) != hn2(order, z).rewrite(f) + + for func, refunc in product((yn, jn, hn1, hn2), + (jn, yn, besselj, bessely)): + assert tn(func(ri, z), func(ri, z).rewrite(refunc), z) + + +def test_expand(): + assert expand_func(besselj(S.Half, z).rewrite(jn)) == \ + sqrt(2)*sin(z)/(sqrt(pi)*sqrt(z)) + assert expand_func(bessely(S.Half, z).rewrite(yn)) == \ + -sqrt(2)*cos(z)/(sqrt(pi)*sqrt(z)) + + # XXX: teach sin/cos to work around arguments like + # x*exp_polar(I*pi*n/2). Then change besselsimp -> expand_func + assert besselsimp(besselj(S.Half, z)) == sqrt(2)*sin(z)/(sqrt(pi)*sqrt(z)) + assert besselsimp(besselj(Rational(-1, 2), z)) == sqrt(2)*cos(z)/(sqrt(pi)*sqrt(z)) + assert besselsimp(besselj(Rational(5, 2), z)) == \ + -sqrt(2)*(z**2*sin(z) + 3*z*cos(z) - 3*sin(z))/(sqrt(pi)*z**Rational(5, 2)) + assert besselsimp(besselj(Rational(-5, 2), z)) == \ + -sqrt(2)*(z**2*cos(z) - 3*z*sin(z) - 3*cos(z))/(sqrt(pi)*z**Rational(5, 2)) + + assert besselsimp(bessely(S.Half, z)) == \ + -(sqrt(2)*cos(z))/(sqrt(pi)*sqrt(z)) + assert besselsimp(bessely(Rational(-1, 2), z)) == sqrt(2)*sin(z)/(sqrt(pi)*sqrt(z)) + assert besselsimp(bessely(Rational(5, 2), z)) == \ + sqrt(2)*(z**2*cos(z) - 3*z*sin(z) - 3*cos(z))/(sqrt(pi)*z**Rational(5, 2)) + assert besselsimp(bessely(Rational(-5, 2), z)) == \ + -sqrt(2)*(z**2*sin(z) + 3*z*cos(z) - 3*sin(z))/(sqrt(pi)*z**Rational(5, 2)) + + assert besselsimp(besseli(S.Half, z)) == sqrt(2)*sinh(z)/(sqrt(pi)*sqrt(z)) + assert besselsimp(besseli(Rational(-1, 2), z)) == \ + sqrt(2)*cosh(z)/(sqrt(pi)*sqrt(z)) + assert besselsimp(besseli(Rational(5, 2), z)) == \ + sqrt(2)*(z**2*sinh(z) - 3*z*cosh(z) + 3*sinh(z))/(sqrt(pi)*z**Rational(5, 2)) + assert besselsimp(besseli(Rational(-5, 2), z)) == \ + sqrt(2)*(z**2*cosh(z) - 3*z*sinh(z) + 3*cosh(z))/(sqrt(pi)*z**Rational(5, 2)) + + assert besselsimp(besselk(S.Half, z)) == \ + besselsimp(besselk(Rational(-1, 2), z)) == sqrt(pi)*exp(-z)/(sqrt(2)*sqrt(z)) + assert besselsimp(besselk(Rational(5, 2), z)) == \ + besselsimp(besselk(Rational(-5, 2), z)) == \ + sqrt(2)*sqrt(pi)*(z**2 + 3*z + 3)*exp(-z)/(2*z**Rational(5, 2)) + + n = Symbol('n', integer=True, positive=True) + + assert expand_func(besseli(n + 2, z)) == \ + besseli(n, z) + (-2*n - 2)*(-2*n*besseli(n, z)/z + besseli(n - 1, z))/z + assert expand_func(besselj(n + 2, z)) == \ + -besselj(n, z) + (2*n + 2)*(2*n*besselj(n, z)/z - besselj(n - 1, z))/z + assert expand_func(besselk(n + 2, z)) == \ + besselk(n, z) + (2*n + 2)*(2*n*besselk(n, z)/z + besselk(n - 1, z))/z + assert expand_func(bessely(n + 2, z)) == \ + -bessely(n, z) + (2*n + 2)*(2*n*bessely(n, z)/z - bessely(n - 1, z))/z + + assert expand_func(besseli(n + S.Half, z).rewrite(jn)) == \ + (sqrt(2)*sqrt(z)*exp(-I*pi*(n + S.Half)/2) * + exp_polar(I*pi/4)*jn(n, z*exp_polar(I*pi/2))/sqrt(pi)) + assert expand_func(besselj(n + S.Half, z).rewrite(jn)) == \ + sqrt(2)*sqrt(z)*jn(n, z)/sqrt(pi) + + r = Symbol('r', real=True) + p = Symbol('p', positive=True) + i = Symbol('i', integer=True) + + for besselx in [besselj, bessely, besseli, besselk]: + assert besselx(i, p).is_extended_real is True + assert besselx(i, x).is_extended_real is None + assert besselx(x, z).is_extended_real is None + + for besselx in [besselj, besseli]: + assert besselx(i, r).is_extended_real is True + for besselx in [bessely, besselk]: + assert besselx(i, r).is_extended_real is None + + for besselx in [besselj, bessely, besseli, besselk]: + assert expand_func(besselx(oo, x)) == besselx(oo, x, evaluate=False) + assert expand_func(besselx(-oo, x)) == besselx(-oo, x, evaluate=False) + + +# Quite varying time, but often really slow +@slow +def test_slow_expand(): + def check(eq, ans): + return tn(eq, ans) and eq == ans + + rn = randcplx(a=1, b=0, d=0, c=2) + + for besselx in [besselj, bessely, besseli, besselk]: + ri = S(2*randint(-11, 10) + 1) / 2 # half integer in [-21/2, 21/2] + assert tn(besselsimp(besselx(ri, z)), besselx(ri, z)) + + assert check(expand_func(besseli(rn, x)), + besseli(rn - 2, x) - 2*(rn - 1)*besseli(rn - 1, x)/x) + assert check(expand_func(besseli(-rn, x)), + besseli(-rn + 2, x) + 2*(-rn + 1)*besseli(-rn + 1, x)/x) + + assert check(expand_func(besselj(rn, x)), + -besselj(rn - 2, x) + 2*(rn - 1)*besselj(rn - 1, x)/x) + assert check(expand_func(besselj(-rn, x)), + -besselj(-rn + 2, x) + 2*(-rn + 1)*besselj(-rn + 1, x)/x) + + assert check(expand_func(besselk(rn, x)), + besselk(rn - 2, x) + 2*(rn - 1)*besselk(rn - 1, x)/x) + assert check(expand_func(besselk(-rn, x)), + besselk(-rn + 2, x) - 2*(-rn + 1)*besselk(-rn + 1, x)/x) + + assert check(expand_func(bessely(rn, x)), + -bessely(rn - 2, x) + 2*(rn - 1)*bessely(rn - 1, x)/x) + assert check(expand_func(bessely(-rn, x)), + -bessely(-rn + 2, x) + 2*(-rn + 1)*bessely(-rn + 1, x)/x) + + +def mjn(n, z): + return expand_func(jn(n, z)) + + +def myn(n, z): + return expand_func(yn(n, z)) + + +def test_jn(): + z = symbols("z") + assert jn(0, 0) == 1 + assert jn(1, 0) == 0 + assert jn(-1, 0) == S.ComplexInfinity + assert jn(z, 0) == jn(z, 0, evaluate=False) + assert jn(0, oo) == 0 + assert jn(0, -oo) == 0 + + assert mjn(0, z) == sin(z)/z + assert mjn(1, z) == sin(z)/z**2 - cos(z)/z + assert mjn(2, z) == (3/z**3 - 1/z)*sin(z) - (3/z**2) * cos(z) + assert mjn(3, z) == (15/z**4 - 6/z**2)*sin(z) + (1/z - 15/z**3)*cos(z) + assert mjn(4, z) == (1/z + 105/z**5 - 45/z**3)*sin(z) + \ + (-105/z**4 + 10/z**2)*cos(z) + assert mjn(5, z) == (945/z**6 - 420/z**4 + 15/z**2)*sin(z) + \ + (-1/z - 945/z**5 + 105/z**3)*cos(z) + assert mjn(6, z) == (-1/z + 10395/z**7 - 4725/z**5 + 210/z**3)*sin(z) + \ + (-10395/z**6 + 1260/z**4 - 21/z**2)*cos(z) + + assert expand_func(jn(n, z)) == jn(n, z) + + # SBFs not defined for complex-valued orders + assert jn(2+3j, 5.2+0.3j).evalf() == jn(2+3j, 5.2+0.3j) + + assert eq([jn(2, 5.2+0.3j).evalf(10)], + [0.09941975672 - 0.05452508024*I]) + + +def test_yn(): + z = symbols("z") + assert myn(0, z) == -cos(z)/z + assert myn(1, z) == -cos(z)/z**2 - sin(z)/z + assert myn(2, z) == -((3/z**3 - 1/z)*cos(z) + (3/z**2)*sin(z)) + assert expand_func(yn(n, z)) == yn(n, z) + + # SBFs not defined for complex-valued orders + assert yn(2+3j, 5.2+0.3j).evalf() == yn(2+3j, 5.2+0.3j) + + assert eq([yn(2, 5.2+0.3j).evalf(10)], + [0.185250342 + 0.01489557397*I]) + + +def test_sympify_yn(): + assert S(15) in myn(3, pi).atoms() + assert myn(3, pi) == 15/pi**4 - 6/pi**2 + + +def eq(a, b, tol=1e-6): + for u, v in zip(a, b): + if not (abs(u - v) < tol): + return False + return True + + +def test_jn_zeros(): + assert eq(jn_zeros(0, 4), [3.141592, 6.283185, 9.424777, 12.566370]) + assert eq(jn_zeros(1, 4), [4.493409, 7.725251, 10.904121, 14.066193]) + assert eq(jn_zeros(2, 4), [5.763459, 9.095011, 12.322940, 15.514603]) + assert eq(jn_zeros(3, 4), [6.987932, 10.417118, 13.698023, 16.923621]) + assert eq(jn_zeros(4, 4), [8.182561, 11.704907, 15.039664, 18.301255]) + + +def test_bessel_eval(): + n, m, k = Symbol('n', integer=True), Symbol('m'), Symbol('k', integer=True, zero=False) + + for f in [besselj, besseli]: + assert f(0, 0) is S.One + assert f(2.1, 0) is S.Zero + assert f(-3, 0) is S.Zero + assert f(-10.2, 0) is S.ComplexInfinity + assert f(1 + 3*I, 0) is S.Zero + assert f(-3 + I, 0) is S.ComplexInfinity + assert f(-2*I, 0) is S.NaN + assert f(n, 0) != S.One and f(n, 0) != S.Zero + assert f(m, 0) != S.One and f(m, 0) != S.Zero + assert f(k, 0) is S.Zero + + assert bessely(0, 0) is S.NegativeInfinity + assert besselk(0, 0) is S.Infinity + for f in [bessely, besselk]: + assert f(1 + I, 0) is S.ComplexInfinity + assert f(I, 0) is S.NaN + + for f in [besselj, bessely]: + assert f(m, S.Infinity) is S.Zero + assert f(m, S.NegativeInfinity) is S.Zero + + for f in [besseli, besselk]: + assert f(m, I*S.Infinity) is S.Zero + assert f(m, I*S.NegativeInfinity) is S.Zero + + for f in [besseli, besselk]: + assert f(-4, z) == f(4, z) + assert f(-3, z) == f(3, z) + assert f(-n, z) == f(n, z) + assert f(-m, z) != f(m, z) + + for f in [besselj, bessely]: + assert f(-4, z) == f(4, z) + assert f(-3, z) == -f(3, z) + assert f(-n, z) == (-1)**n*f(n, z) + assert f(-m, z) != (-1)**m*f(m, z) + + for f in [besselj, besseli]: + assert f(m, -z) == (-z)**m*z**(-m)*f(m, z) + + assert besseli(2, -z) == besseli(2, z) + assert besseli(3, -z) == -besseli(3, z) + + assert besselj(0, -z) == besselj(0, z) + assert besselj(1, -z) == -besselj(1, z) + + assert besseli(0, I*z) == besselj(0, z) + assert besseli(1, I*z) == I*besselj(1, z) + assert besselj(3, I*z) == -I*besseli(3, z) + + +def test_bessel_nan(): + # FIXME: could have these return NaN; for now just fix infinite recursion + for f in [besselj, bessely, besseli, besselk, hankel1, hankel2, yn, jn]: + assert f(1, S.NaN) == f(1, S.NaN, evaluate=False) + + +def test_meromorphic(): + assert besselj(2, x).is_meromorphic(x, 1) == True + assert besselj(2, x).is_meromorphic(x, 0) == True + assert besselj(2, x).is_meromorphic(x, oo) == False + assert besselj(S(2)/3, x).is_meromorphic(x, 1) == True + assert besselj(S(2)/3, x).is_meromorphic(x, 0) == False + assert besselj(S(2)/3, x).is_meromorphic(x, oo) == False + assert besselj(x, 2*x).is_meromorphic(x, 2) == False + assert besselk(0, x).is_meromorphic(x, 1) == True + assert besselk(2, x).is_meromorphic(x, 0) == True + assert besseli(0, x).is_meromorphic(x, 1) == True + assert besseli(2, x).is_meromorphic(x, 0) == True + assert bessely(0, x).is_meromorphic(x, 1) == True + assert bessely(0, x).is_meromorphic(x, 0) == False + assert bessely(2, x).is_meromorphic(x, 0) == True + assert hankel1(3, x**2 + 2*x).is_meromorphic(x, 1) == True + assert hankel1(0, x).is_meromorphic(x, 0) == False + assert hankel2(11, 4).is_meromorphic(x, 5) == True + assert hn1(6, 7*x**3 + 4).is_meromorphic(x, 7) == True + assert hn2(3, 2*x).is_meromorphic(x, 9) == True + assert jn(5, 2*x + 7).is_meromorphic(x, 4) == True + assert yn(8, x**2 + 11).is_meromorphic(x, 6) == True + + +def test_conjugate(): + n = Symbol('n') + z = Symbol('z', extended_real=False) + x = Symbol('x', extended_real=True) + y = Symbol('y', positive=True) + t = Symbol('t', negative=True) + + for f in [besseli, besselj, besselk, bessely, hankel1, hankel2]: + assert f(n, -1).conjugate() != f(conjugate(n), -1) + assert f(n, x).conjugate() != f(conjugate(n), x) + assert f(n, t).conjugate() != f(conjugate(n), t) + + rz = randcplx(b=0.5) + + for f in [besseli, besselj, besselk, bessely]: + assert f(n, 1 + I).conjugate() == f(conjugate(n), 1 - I) + assert f(n, 0).conjugate() == f(conjugate(n), 0) + assert f(n, 1).conjugate() == f(conjugate(n), 1) + assert f(n, z).conjugate() == f(conjugate(n), conjugate(z)) + assert f(n, y).conjugate() == f(conjugate(n), y) + assert tn(f(n, rz).conjugate(), f(conjugate(n), conjugate(rz))) + + assert hankel1(n, 1 + I).conjugate() == hankel2(conjugate(n), 1 - I) + assert hankel1(n, 0).conjugate() == hankel2(conjugate(n), 0) + assert hankel1(n, 1).conjugate() == hankel2(conjugate(n), 1) + assert hankel1(n, y).conjugate() == hankel2(conjugate(n), y) + assert hankel1(n, z).conjugate() == hankel2(conjugate(n), conjugate(z)) + assert tn(hankel1(n, rz).conjugate(), hankel2(conjugate(n), conjugate(rz))) + + assert hankel2(n, 1 + I).conjugate() == hankel1(conjugate(n), 1 - I) + assert hankel2(n, 0).conjugate() == hankel1(conjugate(n), 0) + assert hankel2(n, 1).conjugate() == hankel1(conjugate(n), 1) + assert hankel2(n, y).conjugate() == hankel1(conjugate(n), y) + assert hankel2(n, z).conjugate() == hankel1(conjugate(n), conjugate(z)) + assert tn(hankel2(n, rz).conjugate(), hankel1(conjugate(n), conjugate(rz))) + + +def test_branching(): + assert besselj(polar_lift(k), x) == besselj(k, x) + assert besseli(polar_lift(k), x) == besseli(k, x) + + n = Symbol('n', integer=True) + assert besselj(n, exp_polar(2*pi*I)*x) == besselj(n, x) + assert besselj(n, polar_lift(x)) == besselj(n, x) + assert besseli(n, exp_polar(2*pi*I)*x) == besseli(n, x) + assert besseli(n, polar_lift(x)) == besseli(n, x) + + def tn(func, s): + from sympy.core.random import uniform + c = uniform(1, 5) + expr = func(s, c*exp_polar(I*pi)) - func(s, c*exp_polar(-I*pi)) + eps = 1e-15 + expr2 = func(s + eps, -c + eps*I) - func(s + eps, -c - eps*I) + return abs(expr.n() - expr2.n()).n() < 1e-10 + + nu = Symbol('nu') + assert besselj(nu, exp_polar(2*pi*I)*x) == exp(2*pi*I*nu)*besselj(nu, x) + assert besseli(nu, exp_polar(2*pi*I)*x) == exp(2*pi*I*nu)*besseli(nu, x) + assert tn(besselj, 2) + assert tn(besselj, pi) + assert tn(besselj, I) + assert tn(besseli, 2) + assert tn(besseli, pi) + assert tn(besseli, I) + + +def test_airy_base(): + z = Symbol('z') + x = Symbol('x', real=True) + y = Symbol('y', real=True) + + assert conjugate(airyai(z)) == airyai(conjugate(z)) + assert airyai(x).is_extended_real + + assert airyai(x+I*y).as_real_imag() == ( + airyai(x - I*y)/2 + airyai(x + I*y)/2, + I*(airyai(x - I*y) - airyai(x + I*y))/2) + + +def test_airyai(): + z = Symbol('z', real=False) + t = Symbol('t', negative=True) + p = Symbol('p', positive=True) + + assert isinstance(airyai(z), airyai) + + assert airyai(0) == 3**Rational(1, 3)/(3*gamma(Rational(2, 3))) + assert airyai(oo) == 0 + assert airyai(-oo) == 0 + + assert diff(airyai(z), z) == airyaiprime(z) + + assert series(airyai(z), z, 0, 3) == ( + 3**Rational(5, 6)*gamma(Rational(1, 3))/(6*pi) - 3**Rational(1, 6)*z*gamma(Rational(2, 3))/(2*pi) + O(z**3)) + + assert airyai(z).rewrite(hyper) == ( + -3**Rational(2, 3)*z*hyper((), (Rational(4, 3),), z**3/9)/(3*gamma(Rational(1, 3))) + + 3**Rational(1, 3)*hyper((), (Rational(2, 3),), z**3/9)/(3*gamma(Rational(2, 3)))) + + assert isinstance(airyai(z).rewrite(besselj), airyai) + assert airyai(t).rewrite(besselj) == ( + sqrt(-t)*(besselj(Rational(-1, 3), 2*(-t)**Rational(3, 2)/3) + + besselj(Rational(1, 3), 2*(-t)**Rational(3, 2)/3))/3) + assert airyai(z).rewrite(besseli) == ( + -z*besseli(Rational(1, 3), 2*z**Rational(3, 2)/3)/(3*(z**Rational(3, 2))**Rational(1, 3)) + + (z**Rational(3, 2))**Rational(1, 3)*besseli(Rational(-1, 3), 2*z**Rational(3, 2)/3)/3) + assert airyai(p).rewrite(besseli) == ( + sqrt(p)*(besseli(Rational(-1, 3), 2*p**Rational(3, 2)/3) - + besseli(Rational(1, 3), 2*p**Rational(3, 2)/3))/3) + + assert expand_func(airyai(2*(3*z**5)**Rational(1, 3))) == ( + -sqrt(3)*(-1 + (z**5)**Rational(1, 3)/z**Rational(5, 3))*airybi(2*3**Rational(1, 3)*z**Rational(5, 3))/6 + + (1 + (z**5)**Rational(1, 3)/z**Rational(5, 3))*airyai(2*3**Rational(1, 3)*z**Rational(5, 3))/2) + + +def test_airybi(): + z = Symbol('z', real=False) + t = Symbol('t', negative=True) + p = Symbol('p', positive=True) + + assert isinstance(airybi(z), airybi) + + assert airybi(0) == 3**Rational(5, 6)/(3*gamma(Rational(2, 3))) + assert airybi(oo) is oo + assert airybi(-oo) == 0 + + assert diff(airybi(z), z) == airybiprime(z) + + assert series(airybi(z), z, 0, 3) == ( + 3**Rational(1, 3)*gamma(Rational(1, 3))/(2*pi) + 3**Rational(2, 3)*z*gamma(Rational(2, 3))/(2*pi) + O(z**3)) + + assert airybi(z).rewrite(hyper) == ( + 3**Rational(1, 6)*z*hyper((), (Rational(4, 3),), z**3/9)/gamma(Rational(1, 3)) + + 3**Rational(5, 6)*hyper((), (Rational(2, 3),), z**3/9)/(3*gamma(Rational(2, 3)))) + + assert isinstance(airybi(z).rewrite(besselj), airybi) + assert airyai(t).rewrite(besselj) == ( + sqrt(-t)*(besselj(Rational(-1, 3), 2*(-t)**Rational(3, 2)/3) + + besselj(Rational(1, 3), 2*(-t)**Rational(3, 2)/3))/3) + assert airybi(z).rewrite(besseli) == ( + sqrt(3)*(z*besseli(Rational(1, 3), 2*z**Rational(3, 2)/3)/(z**Rational(3, 2))**Rational(1, 3) + + (z**Rational(3, 2))**Rational(1, 3)*besseli(Rational(-1, 3), 2*z**Rational(3, 2)/3))/3) + assert airybi(p).rewrite(besseli) == ( + sqrt(3)*sqrt(p)*(besseli(Rational(-1, 3), 2*p**Rational(3, 2)/3) + + besseli(Rational(1, 3), 2*p**Rational(3, 2)/3))/3) + + assert expand_func(airybi(2*(3*z**5)**Rational(1, 3))) == ( + sqrt(3)*(1 - (z**5)**Rational(1, 3)/z**Rational(5, 3))*airyai(2*3**Rational(1, 3)*z**Rational(5, 3))/2 + + (1 + (z**5)**Rational(1, 3)/z**Rational(5, 3))*airybi(2*3**Rational(1, 3)*z**Rational(5, 3))/2) + + +def test_airyaiprime(): + z = Symbol('z', real=False) + t = Symbol('t', negative=True) + p = Symbol('p', positive=True) + + assert isinstance(airyaiprime(z), airyaiprime) + + assert airyaiprime(0) == -3**Rational(2, 3)/(3*gamma(Rational(1, 3))) + assert airyaiprime(oo) == 0 + + assert diff(airyaiprime(z), z) == z*airyai(z) + + assert series(airyaiprime(z), z, 0, 3) == ( + -3**Rational(2, 3)/(3*gamma(Rational(1, 3))) + 3**Rational(1, 3)*z**2/(6*gamma(Rational(2, 3))) + O(z**3)) + + assert airyaiprime(z).rewrite(hyper) == ( + 3**Rational(1, 3)*z**2*hyper((), (Rational(5, 3),), z**3/9)/(6*gamma(Rational(2, 3))) - + 3**Rational(2, 3)*hyper((), (Rational(1, 3),), z**3/9)/(3*gamma(Rational(1, 3)))) + + assert isinstance(airyaiprime(z).rewrite(besselj), airyaiprime) + assert airyai(t).rewrite(besselj) == ( + sqrt(-t)*(besselj(Rational(-1, 3), 2*(-t)**Rational(3, 2)/3) + + besselj(Rational(1, 3), 2*(-t)**Rational(3, 2)/3))/3) + assert airyaiprime(z).rewrite(besseli) == ( + z**2*besseli(Rational(2, 3), 2*z**Rational(3, 2)/3)/(3*(z**Rational(3, 2))**Rational(2, 3)) - + (z**Rational(3, 2))**Rational(2, 3)*besseli(Rational(-1, 3), 2*z**Rational(3, 2)/3)/3) + assert airyaiprime(p).rewrite(besseli) == ( + p*(-besseli(Rational(-2, 3), 2*p**Rational(3, 2)/3) + besseli(Rational(2, 3), 2*p**Rational(3, 2)/3))/3) + + assert expand_func(airyaiprime(2*(3*z**5)**Rational(1, 3))) == ( + sqrt(3)*(z**Rational(5, 3)/(z**5)**Rational(1, 3) - 1)*airybiprime(2*3**Rational(1, 3)*z**Rational(5, 3))/6 + + (z**Rational(5, 3)/(z**5)**Rational(1, 3) + 1)*airyaiprime(2*3**Rational(1, 3)*z**Rational(5, 3))/2) + + +def test_airybiprime(): + z = Symbol('z', real=False) + t = Symbol('t', negative=True) + p = Symbol('p', positive=True) + + assert isinstance(airybiprime(z), airybiprime) + + assert airybiprime(0) == 3**Rational(1, 6)/gamma(Rational(1, 3)) + assert airybiprime(oo) is oo + assert airybiprime(-oo) == 0 + + assert diff(airybiprime(z), z) == z*airybi(z) + + assert series(airybiprime(z), z, 0, 3) == ( + 3**Rational(1, 6)/gamma(Rational(1, 3)) + 3**Rational(5, 6)*z**2/(6*gamma(Rational(2, 3))) + O(z**3)) + + assert airybiprime(z).rewrite(hyper) == ( + 3**Rational(5, 6)*z**2*hyper((), (Rational(5, 3),), z**3/9)/(6*gamma(Rational(2, 3))) + + 3**Rational(1, 6)*hyper((), (Rational(1, 3),), z**3/9)/gamma(Rational(1, 3))) + + assert isinstance(airybiprime(z).rewrite(besselj), airybiprime) + assert airyai(t).rewrite(besselj) == ( + sqrt(-t)*(besselj(Rational(-1, 3), 2*(-t)**Rational(3, 2)/3) + + besselj(Rational(1, 3), 2*(-t)**Rational(3, 2)/3))/3) + assert airybiprime(z).rewrite(besseli) == ( + sqrt(3)*(z**2*besseli(Rational(2, 3), 2*z**Rational(3, 2)/3)/(z**Rational(3, 2))**Rational(2, 3) + + (z**Rational(3, 2))**Rational(2, 3)*besseli(Rational(-2, 3), 2*z**Rational(3, 2)/3))/3) + assert airybiprime(p).rewrite(besseli) == ( + sqrt(3)*p*(besseli(Rational(-2, 3), 2*p**Rational(3, 2)/3) + besseli(Rational(2, 3), 2*p**Rational(3, 2)/3))/3) + + assert expand_func(airybiprime(2*(3*z**5)**Rational(1, 3))) == ( + sqrt(3)*(z**Rational(5, 3)/(z**5)**Rational(1, 3) - 1)*airyaiprime(2*3**Rational(1, 3)*z**Rational(5, 3))/2 + + (z**Rational(5, 3)/(z**5)**Rational(1, 3) + 1)*airybiprime(2*3**Rational(1, 3)*z**Rational(5, 3))/2) + + +def test_marcumq(): + m = Symbol('m') + a = Symbol('a') + b = Symbol('b') + + assert marcumq(0, 0, 0) == 0 + assert marcumq(m, 0, b) == uppergamma(m, b**2/2)/gamma(m) + assert marcumq(2, 0, 5) == 27*exp(Rational(-25, 2))/2 + assert marcumq(0, a, 0) == 1 - exp(-a**2/2) + assert marcumq(0, pi, 0) == 1 - exp(-pi**2/2) + assert marcumq(1, a, a) == S.Half + exp(-a**2)*besseli(0, a**2)/2 + assert marcumq(2, a, a) == S.Half + exp(-a**2)*besseli(0, a**2)/2 + exp(-a**2)*besseli(1, a**2) + + assert diff(marcumq(1, a, 3), a) == a*(-marcumq(1, a, 3) + marcumq(2, a, 3)) + assert diff(marcumq(2, 3, b), b) == -b**2*exp(-b**2/2 - Rational(9, 2))*besseli(1, 3*b)/3 + + x = Symbol('x') + assert marcumq(2, 3, 4).rewrite(Integral, x=x) == \ + Integral(x**2*exp(-x**2/2 - Rational(9, 2))*besseli(1, 3*x), (x, 4, oo))/3 + assert eq([marcumq(5, -2, 3).rewrite(Integral).evalf(10)], + [0.7905769565]) + + k = Symbol('k') + assert marcumq(-3, -5, -7).rewrite(Sum, k=k) == \ + exp(-37)*Sum((Rational(5, 7))**k*besseli(k, 35), (k, 4, oo)) + assert eq([marcumq(1, 3, 1).rewrite(Sum).evalf(10)], + [0.9891705502]) + + assert marcumq(1, a, a, evaluate=False).rewrite(besseli) == S.Half + exp(-a**2)*besseli(0, a**2)/2 + assert marcumq(2, a, a, evaluate=False).rewrite(besseli) == S.Half + exp(-a**2)*besseli(0, a**2)/2 + \ + exp(-a**2)*besseli(1, a**2) + assert marcumq(3, a, a).rewrite(besseli) == (besseli(1, a**2) + besseli(2, a**2))*exp(-a**2) + \ + S.Half + exp(-a**2)*besseli(0, a**2)/2 + assert marcumq(5, 8, 8).rewrite(besseli) == exp(-64)*besseli(0, 64)/2 + \ + (besseli(4, 64) + besseli(3, 64) + besseli(2, 64) + besseli(1, 64))*exp(-64) + S.Half + assert marcumq(m, a, a).rewrite(besseli) == marcumq(m, a, a) + + x = Symbol('x', integer=True) + assert marcumq(x, a, a).rewrite(besseli) == marcumq(x, a, a) + + +def test_issue_26134(): + x = Symbol('x') + assert marcumq(2, 3, 4).rewrite(Integral, x=x).dummy_eq( + Integral(x**2*exp(-x**2/2 - Rational(9, 2))*besseli(1, 3*x), (x, 4, oo))/3) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_beta_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_beta_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..b34cb2febf9e2746d869cd878525d2794535aea5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_beta_functions.py @@ -0,0 +1,89 @@ +from sympy.core.function import (diff, expand_func) +from sympy.core.numbers import I, Rational, pi +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.combinatorial.numbers import catalan +from sympy.functions.elementary.complexes import conjugate +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.beta_functions import (beta, betainc, betainc_regularized) +from sympy.functions.special.gamma_functions import gamma, polygamma +from sympy.functions.special.hyper import hyper +from sympy.integrals.integrals import Integral +from sympy.core.function import ArgumentIndexError +from sympy.core.expr import unchanged +from sympy.testing.pytest import raises + + +def test_beta(): + x, y = symbols('x y') + t = Dummy('t') + + assert unchanged(beta, x, y) + assert unchanged(beta, x, x) + + assert beta(5, -3).is_real == True + assert beta(3, y).is_real is None + + assert expand_func(beta(x, y)) == gamma(x)*gamma(y)/gamma(x + y) + assert expand_func(beta(x, y) - beta(y, x)) == 0 # Symmetric + assert expand_func(beta(x, y)) == expand_func(beta(x, y + 1) + beta(x + 1, y)).simplify() + + assert diff(beta(x, y), x) == beta(x, y)*(polygamma(0, x) - polygamma(0, x + y)) + assert diff(beta(x, y), y) == beta(x, y)*(polygamma(0, y) - polygamma(0, x + y)) + + assert conjugate(beta(x, y)) == beta(conjugate(x), conjugate(y)) + + raises(ArgumentIndexError, lambda: beta(x, y).fdiff(3)) + + assert beta(x, y).rewrite(gamma) == gamma(x)*gamma(y)/gamma(x + y) + assert beta(x).rewrite(gamma) == gamma(x)**2/gamma(2*x) + assert beta(x, y).rewrite(Integral).dummy_eq(Integral(t**(x - 1) * (1 - t)**(y - 1), (t, 0, 1))) + assert beta(Rational(-19, 10), Rational(-1, 10)) == S.Zero + assert beta(Rational(-19, 10), Rational(-9, 10)) == \ + 800*2**(S(4)/5)*sqrt(pi)*gamma(S.One/10)/(171*gamma(-S(7)/5)) + assert beta(Rational(19, 10), Rational(29, 10)) == 100/(551*catalan(Rational(19, 10))) + assert beta(1, 0) == S.ComplexInfinity + assert beta(0, 1) == S.ComplexInfinity + assert beta(2, 3) == S.One/12 + assert unchanged(beta, x, x + 1) + assert unchanged(beta, x, 1) + assert unchanged(beta, 1, y) + assert beta(x, x + 1).doit() == 1/(x*(x+1)*catalan(x)) + assert beta(1, y).doit() == 1/y + assert beta(x, 1).doit() == 1/x + assert beta(Rational(-19, 10), Rational(-1, 10), evaluate=False).doit() == S.Zero + assert beta(2) == beta(2, 2) + assert beta(x, evaluate=False) != beta(x, x) + assert beta(x, evaluate=False).doit() == beta(x, x) + + +def test_betainc(): + a, b, x1, x2 = symbols('a b x1 x2') + + assert unchanged(betainc, a, b, x1, x2) + assert unchanged(betainc, a, b, 0, x1) + + assert betainc(1, 2, 0, -5).is_real == True + assert betainc(1, 2, 0, x2).is_real is None + assert conjugate(betainc(I, 2, 3 - I, 1 + 4*I)) == betainc(-I, 2, 3 + I, 1 - 4*I) + + assert betainc(a, b, 0, 1).rewrite(Integral).dummy_eq(beta(a, b).rewrite(Integral)) + assert betainc(1, 2, 0, x2).rewrite(hyper) == x2*hyper((1, -1), (2,), x2) + + assert betainc(1, 2, 3, 3).evalf() == 0 + + +def test_betainc_regularized(): + a, b, x1, x2 = symbols('a b x1 x2') + + assert unchanged(betainc_regularized, a, b, x1, x2) + assert unchanged(betainc_regularized, a, b, 0, x1) + + assert betainc_regularized(3, 5, 0, -1).is_real == True + assert betainc_regularized(3, 5, 0, x2).is_real is None + assert conjugate(betainc_regularized(3*I, 1, 2 + I, 1 + 2*I)) == betainc_regularized(-3*I, 1, 2 - I, 1 - 2*I) + + assert betainc_regularized(a, b, 0, 1).rewrite(Integral) == 1 + assert betainc_regularized(1, 2, x1, x2).rewrite(hyper) == 2*x2*hyper((1, -1), (2,), x2) - 2*x1*hyper((1, -1), (2,), x1) + + assert betainc_regularized(4, 1, 5, 5).evalf() == 0 diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_bsplines.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_bsplines.py new file mode 100644 index 0000000000000000000000000000000000000000..136831b96ba16c95edba12ecd47b6f1566b68427 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_bsplines.py @@ -0,0 +1,167 @@ +from sympy.functions import bspline_basis_set, interpolating_spline +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.piecewise import Piecewise +from sympy.logic.boolalg import And +from sympy.sets.sets import Interval +from sympy.testing.pytest import slow + +x, y = symbols('x,y') + + +def test_basic_degree_0(): + d = 0 + knots = range(5) + splines = bspline_basis_set(d, knots, x) + for i in range(len(splines)): + assert splines[i] == Piecewise((1, Interval(i, i + 1).contains(x)), + (0, True)) + + +def test_basic_degree_1(): + d = 1 + knots = range(5) + splines = bspline_basis_set(d, knots, x) + assert splines[0] == Piecewise((x, Interval(0, 1).contains(x)), + (2 - x, Interval(1, 2).contains(x)), + (0, True)) + assert splines[1] == Piecewise((-1 + x, Interval(1, 2).contains(x)), + (3 - x, Interval(2, 3).contains(x)), + (0, True)) + assert splines[2] == Piecewise((-2 + x, Interval(2, 3).contains(x)), + (4 - x, Interval(3, 4).contains(x)), + (0, True)) + + +def test_basic_degree_2(): + d = 2 + knots = range(5) + splines = bspline_basis_set(d, knots, x) + b0 = Piecewise((x**2/2, Interval(0, 1).contains(x)), + (Rational(-3, 2) + 3*x - x**2, Interval(1, 2).contains(x)), + (Rational(9, 2) - 3*x + x**2/2, Interval(2, 3).contains(x)), + (0, True)) + b1 = Piecewise((S.Half - x + x**2/2, Interval(1, 2).contains(x)), + (Rational(-11, 2) + 5*x - x**2, Interval(2, 3).contains(x)), + (8 - 4*x + x**2/2, Interval(3, 4).contains(x)), + (0, True)) + assert splines[0] == b0 + assert splines[1] == b1 + + +def test_basic_degree_3(): + d = 3 + knots = range(5) + splines = bspline_basis_set(d, knots, x) + b0 = Piecewise( + (x**3/6, Interval(0, 1).contains(x)), + (Rational(2, 3) - 2*x + 2*x**2 - x**3/2, Interval(1, 2).contains(x)), + (Rational(-22, 3) + 10*x - 4*x**2 + x**3/2, Interval(2, 3).contains(x)), + (Rational(32, 3) - 8*x + 2*x**2 - x**3/6, Interval(3, 4).contains(x)), + (0, True) + ) + assert splines[0] == b0 + + +def test_repeated_degree_1(): + d = 1 + knots = [0, 0, 1, 2, 2, 3, 4, 4] + splines = bspline_basis_set(d, knots, x) + assert splines[0] == Piecewise((1 - x, Interval(0, 1).contains(x)), + (0, True)) + assert splines[1] == Piecewise((x, Interval(0, 1).contains(x)), + (2 - x, Interval(1, 2).contains(x)), + (0, True)) + assert splines[2] == Piecewise((-1 + x, Interval(1, 2).contains(x)), + (0, True)) + assert splines[3] == Piecewise((3 - x, Interval(2, 3).contains(x)), + (0, True)) + assert splines[4] == Piecewise((-2 + x, Interval(2, 3).contains(x)), + (4 - x, Interval(3, 4).contains(x)), + (0, True)) + assert splines[5] == Piecewise((-3 + x, Interval(3, 4).contains(x)), + (0, True)) + + +def test_repeated_degree_2(): + d = 2 + knots = [0, 0, 1, 2, 2, 3, 4, 4] + splines = bspline_basis_set(d, knots, x) + + assert splines[0] == Piecewise(((-3*x**2/2 + 2*x), And(x <= 1, x >= 0)), + (x**2/2 - 2*x + 2, And(x <= 2, x >= 1)), + (0, True)) + assert splines[1] == Piecewise((x**2/2, And(x <= 1, x >= 0)), + (-3*x**2/2 + 4*x - 2, And(x <= 2, x >= 1)), + (0, True)) + assert splines[2] == Piecewise((x**2 - 2*x + 1, And(x <= 2, x >= 1)), + (x**2 - 6*x + 9, And(x <= 3, x >= 2)), + (0, True)) + assert splines[3] == Piecewise((-3*x**2/2 + 8*x - 10, And(x <= 3, x >= 2)), + (x**2/2 - 4*x + 8, And(x <= 4, x >= 3)), + (0, True)) + assert splines[4] == Piecewise((x**2/2 - 2*x + 2, And(x <= 3, x >= 2)), + (-3*x**2/2 + 10*x - 16, And(x <= 4, x >= 3)), + (0, True)) + +# Tests for interpolating_spline + + +def test_10_points_degree_1(): + d = 1 + X = [-5, 2, 3, 4, 7, 9, 10, 30, 31, 34] + Y = [-10, -2, 2, 4, 7, 6, 20, 45, 19, 25] + spline = interpolating_spline(d, x, X, Y) + + assert spline == Piecewise((x*Rational(8, 7) - Rational(30, 7), (x >= -5) & (x <= 2)), (4*x - 10, (x >= 2) & (x <= 3)), + (2*x - 4, (x >= 3) & (x <= 4)), (x, (x >= 4) & (x <= 7)), + (-x/2 + Rational(21, 2), (x >= 7) & (x <= 9)), (14*x - 120, (x >= 9) & (x <= 10)), + (x*Rational(5, 4) + Rational(15, 2), (x >= 10) & (x <= 30)), (-26*x + 825, (x >= 30) & (x <= 31)), + (2*x - 43, (x >= 31) & (x <= 34))) + + +def test_3_points_degree_2(): + d = 2 + X = [-3, 10, 19] + Y = [3, -4, 30] + spline = interpolating_spline(d, x, X, Y) + + assert spline == Piecewise((505*x**2/2574 - x*Rational(4921, 2574) - Rational(1931, 429), (x >= -3) & (x <= 19))) + + +def test_5_points_degree_2(): + d = 2 + X = [-3, 2, 4, 5, 10] + Y = [-1, 2, 5, 10, 14] + spline = interpolating_spline(d, x, X, Y) + + assert spline == Piecewise((4*x**2/329 + x*Rational(1007, 1645) + Rational(1196, 1645), (x >= -3) & (x <= 3)), + (2701*x**2/1645 - x*Rational(15079, 1645) + Rational(5065, 329), (x >= 3) & (x <= Rational(9, 2))), + (-1319*x**2/1645 + x*Rational(21101, 1645) - Rational(11216, 329), (x >= Rational(9, 2)) & (x <= 10))) + + +@slow +def test_6_points_degree_3(): + d = 3 + X = [-1, 0, 2, 3, 9, 12] + Y = [-4, 3, 3, 7, 9, 20] + spline = interpolating_spline(d, x, X, Y) + + assert spline == Piecewise((6058*x**3/5301 - 18427*x**2/5301 + x*Rational(12622, 5301) + 3, (x >= -1) & (x <= 2)), + (-8327*x**3/5301 + 67883*x**2/5301 - x*Rational(159998, 5301) + Rational(43661, 1767), (x >= 2) & (x <= 3)), + (5414*x**3/47709 - 1386*x**2/589 + x*Rational(4267, 279) - Rational(12232, 589), (x >= 3) & (x <= 12))) + + +def test_issue_19262(): + Delta = symbols('Delta', positive=True) + knots = [i*Delta for i in range(4)] + basis = bspline_basis_set(1, knots, x) + y = symbols('y', nonnegative=True) + basis2 = bspline_basis_set(1, knots, y) + assert basis[0].subs(x, y) == basis2[0] + assert interpolating_spline(1, x, + [Delta*i for i in [1, 2, 4, 7]], [3, 6, 5, 7] + ) == Piecewise((3*x/Delta, (Delta <= x) & (x <= 2*Delta)), + (7 - x/(2*Delta), (x >= 2*Delta) & (x <= 4*Delta)), + (Rational(7, 3) + 2*x/(3*Delta), (x >= 4*Delta) & (x <= 7*Delta))) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_delta_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_delta_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..d5a39d9e352143cf878cf69fa42f454f58be65c9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_delta_functions.py @@ -0,0 +1,165 @@ +from sympy.core.numbers import (I, nan, oo, pi) +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (adjoint, conjugate, sign, transpose) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import (DiracDelta, Heaviside) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.simplify.simplify import signsimp + + +from sympy.testing.pytest import raises + +from sympy.core.expr import unchanged + +from sympy.core.function import ArgumentIndexError + + +x, y = symbols('x y') +i = symbols('t', nonzero=True) +j = symbols('j', positive=True) +k = symbols('k', negative=True) + +def test_DiracDelta(): + assert DiracDelta(1) == 0 + assert DiracDelta(5.1) == 0 + assert DiracDelta(-pi) == 0 + assert DiracDelta(5, 7) == 0 + assert DiracDelta(x, 0) == DiracDelta(x) + assert DiracDelta(i) == 0 + assert DiracDelta(j) == 0 + assert DiracDelta(k) == 0 + assert DiracDelta(nan) is nan + assert DiracDelta(0).func is DiracDelta + assert DiracDelta(x).func is DiracDelta + # FIXME: this is generally undefined @ x=0 + # But then limit(Delta(c)*Heaviside(x),x,-oo) + # need's to be implemented. + # assert 0*DiracDelta(x) == 0 + + assert adjoint(DiracDelta(x)) == DiracDelta(x) + assert adjoint(DiracDelta(x - y)) == DiracDelta(x - y) + assert conjugate(DiracDelta(x)) == DiracDelta(x) + assert conjugate(DiracDelta(x - y)) == DiracDelta(x - y) + assert transpose(DiracDelta(x)) == DiracDelta(x) + assert transpose(DiracDelta(x - y)) == DiracDelta(x - y) + + assert DiracDelta(x).diff(x) == DiracDelta(x, 1) + assert DiracDelta(x, 1).diff(x) == DiracDelta(x, 2) + + assert DiracDelta(x).is_simple(x) is True + assert DiracDelta(3*x).is_simple(x) is True + assert DiracDelta(x**2).is_simple(x) is False + assert DiracDelta(sqrt(x)).is_simple(x) is False + assert DiracDelta(x).is_simple(y) is False + + assert DiracDelta(x*y).expand(diracdelta=True, wrt=x) == DiracDelta(x)/abs(y) + assert DiracDelta(x*y).expand(diracdelta=True, wrt=y) == DiracDelta(y)/abs(x) + assert DiracDelta(x**2*y).expand(diracdelta=True, wrt=x) == DiracDelta(x**2*y) + assert DiracDelta(y).expand(diracdelta=True, wrt=x) == DiracDelta(y) + assert DiracDelta((x - 1)*(x - 2)*(x - 3)).expand(diracdelta=True, wrt=x) == ( + DiracDelta(x - 3)/2 + DiracDelta(x - 2) + DiracDelta(x - 1)/2) + + assert DiracDelta(2*x) != DiracDelta(x) # scaling property + assert DiracDelta(x) == DiracDelta(-x) # even function + assert DiracDelta(-x, 2) == DiracDelta(x, 2) + assert DiracDelta(-x, 1) == -DiracDelta(x, 1) # odd deriv is odd + assert DiracDelta(-oo*x) == DiracDelta(oo*x) + assert DiracDelta(x - y) != DiracDelta(y - x) + assert signsimp(DiracDelta(x - y) - DiracDelta(y - x)) == 0 + + assert DiracDelta(x*y).expand(diracdelta=True, wrt=x) == DiracDelta(x)/abs(y) + assert DiracDelta(x*y).expand(diracdelta=True, wrt=y) == DiracDelta(y)/abs(x) + assert DiracDelta(x**2*y).expand(diracdelta=True, wrt=x) == DiracDelta(x**2*y) + assert DiracDelta(y).expand(diracdelta=True, wrt=x) == DiracDelta(y) + assert DiracDelta((x - 1)*(x - 2)*(x - 3)).expand(diracdelta=True) == ( + DiracDelta(x - 3)/2 + DiracDelta(x - 2) + DiracDelta(x - 1)/2) + + raises(ArgumentIndexError, lambda: DiracDelta(x).fdiff(2)) + raises(ValueError, lambda: DiracDelta(x, -1)) + raises(ValueError, lambda: DiracDelta(I)) + raises(ValueError, lambda: DiracDelta(2 + 3*I)) + + +def test_heaviside(): + assert Heaviside(-5) == 0 + assert Heaviside(1) == 1 + assert Heaviside(0) == S.Half + + assert Heaviside(0, x) == x + assert unchanged(Heaviside,x, nan) + assert Heaviside(0, nan) == nan + + h0 = Heaviside(x, 0) + h12 = Heaviside(x, S.Half) + h1 = Heaviside(x, 1) + + assert h0.args == h0.pargs == (x, 0) + assert h1.args == h1.pargs == (x, 1) + assert h12.args == (x, S.Half) + assert h12.pargs == (x,) # default 1/2 suppressed + + assert adjoint(Heaviside(x)) == Heaviside(x) + assert adjoint(Heaviside(x - y)) == Heaviside(x - y) + assert conjugate(Heaviside(x)) == Heaviside(x) + assert conjugate(Heaviside(x - y)) == Heaviside(x - y) + assert transpose(Heaviside(x)) == Heaviside(x) + assert transpose(Heaviside(x - y)) == Heaviside(x - y) + + assert Heaviside(x).diff(x) == DiracDelta(x) + assert Heaviside(x + I).is_Function is True + assert Heaviside(I*x).is_Function is True + + raises(ArgumentIndexError, lambda: Heaviside(x).fdiff(2)) + raises(ValueError, lambda: Heaviside(I)) + raises(ValueError, lambda: Heaviside(2 + 3*I)) + + +def test_rewrite(): + x, y = Symbol('x', real=True), Symbol('y') + assert Heaviside(x).rewrite(Piecewise) == ( + Piecewise((0, x < 0), (Heaviside(0), Eq(x, 0)), (1, True))) + assert Heaviside(y).rewrite(Piecewise) == ( + Piecewise((0, y < 0), (Heaviside(0), Eq(y, 0)), (1, True))) + assert Heaviside(x, y).rewrite(Piecewise) == ( + Piecewise((0, x < 0), (y, Eq(x, 0)), (1, True))) + assert Heaviside(x, 0).rewrite(Piecewise) == ( + Piecewise((0, x <= 0), (1, True))) + assert Heaviside(x, 1).rewrite(Piecewise) == ( + Piecewise((0, x < 0), (1, True))) + assert Heaviside(x, nan).rewrite(Piecewise) == ( + Piecewise((0, x < 0), (nan, Eq(x, 0)), (1, True))) + + assert Heaviside(x).rewrite(sign) == \ + Heaviside(x, H0=Heaviside(0)).rewrite(sign) == \ + Piecewise( + (sign(x)/2 + S(1)/2, Eq(Heaviside(0), S(1)/2)), + (Piecewise( + (sign(x)/2 + S(1)/2, Ne(x, 0)), (Heaviside(0), True)), True) + ) + + assert Heaviside(y).rewrite(sign) == Heaviside(y) + assert Heaviside(x, S.Half).rewrite(sign) == (sign(x)+1)/2 + assert Heaviside(x, y).rewrite(sign) == \ + Piecewise( + (sign(x)/2 + S(1)/2, Eq(y, S(1)/2)), + (Piecewise( + (sign(x)/2 + S(1)/2, Ne(x, 0)), (y, True)), True) + ) + + assert DiracDelta(y).rewrite(Piecewise) == Piecewise((DiracDelta(0), Eq(y, 0)), (0, True)) + assert DiracDelta(y, 1).rewrite(Piecewise) == DiracDelta(y, 1) + assert DiracDelta(x - 5).rewrite(Piecewise) == ( + Piecewise((DiracDelta(0), Eq(x - 5, 0)), (0, True))) + + assert (x*DiracDelta(x - 10)).rewrite(SingularityFunction) == x*SingularityFunction(x, 10, -1) + assert 5*x*y*DiracDelta(y, 1).rewrite(SingularityFunction) == 5*x*y*SingularityFunction(y, 0, -2) + assert DiracDelta(0).rewrite(SingularityFunction) == SingularityFunction(0, 0, -1) + assert DiracDelta(0, 1).rewrite(SingularityFunction) == SingularityFunction(0, 0, -2) + + assert Heaviside(x).rewrite(SingularityFunction) == SingularityFunction(x, 0, 0) + assert 5*x*y*Heaviside(y + 1).rewrite(SingularityFunction) == 5*x*y*SingularityFunction(y, -1, 0) + assert ((x - 3)**3*Heaviside(x - 3)).rewrite(SingularityFunction) == (x - 3)**3*SingularityFunction(x, 3, 0) + assert Heaviside(0).rewrite(SingularityFunction) == S.Half diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_elliptic_integrals.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_elliptic_integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..a11e531af32301a00b6fc864064d02f9318929e1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_elliptic_integrals.py @@ -0,0 +1,181 @@ +from sympy.core.numbers import (I, Rational, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.elementary.hyperbolic import atanh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (sin, tan) +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import (hyper, meijerg) +from sympy.integrals.integrals import Integral +from sympy.series.order import O +from sympy.functions.special.elliptic_integrals import (elliptic_k as K, + elliptic_f as F, elliptic_e as E, elliptic_pi as P) +from sympy.core.random import (test_derivative_numerically as td, + random_complex_number as randcplx, + verify_numerically as tn) +from sympy.abc import z, m, n + +i = Symbol('i', integer=True) +j = Symbol('k', integer=True, positive=True) +t = Dummy('t') + +def test_K(): + assert K(0) == pi/2 + assert K(S.Half) == 8*pi**Rational(3, 2)/gamma(Rational(-1, 4))**2 + assert K(1) is zoo + assert K(-1) == gamma(Rational(1, 4))**2/(4*sqrt(2*pi)) + assert K(oo) == 0 + assert K(-oo) == 0 + assert K(I*oo) == 0 + assert K(-I*oo) == 0 + assert K(zoo) == 0 + + assert K(z).diff(z) == (E(z) - (1 - z)*K(z))/(2*z*(1 - z)) + assert td(K(z), z) + + zi = Symbol('z', real=False) + assert K(zi).conjugate() == K(zi.conjugate()) + zr = Symbol('z', negative=True) + assert K(zr).conjugate() == K(zr) + + assert K(z).rewrite(hyper) == \ + (pi/2)*hyper((S.Half, S.Half), (S.One,), z) + assert tn(K(z), (pi/2)*hyper((S.Half, S.Half), (S.One,), z)) + assert K(z).rewrite(meijerg) == \ + meijerg(((S.Half, S.Half), []), ((S.Zero,), (S.Zero,)), -z)/2 + assert tn(K(z), meijerg(((S.Half, S.Half), []), ((S.Zero,), (S.Zero,)), -z)/2) + + assert K(z).series(z) == pi/2 + pi*z/8 + 9*pi*z**2/128 + \ + 25*pi*z**3/512 + 1225*pi*z**4/32768 + 3969*pi*z**5/131072 + O(z**6) + + assert K(m).rewrite(Integral).dummy_eq( + Integral(1/sqrt(1 - m*sin(t)**2), (t, 0, pi/2))) + +def test_F(): + assert F(z, 0) == z + assert F(0, m) == 0 + assert F(pi*i/2, m) == i*K(m) + assert F(z, oo) == 0 + assert F(z, -oo) == 0 + + assert F(-z, m) == -F(z, m) + + assert F(z, m).diff(z) == 1/sqrt(1 - m*sin(z)**2) + assert F(z, m).diff(m) == E(z, m)/(2*m*(1 - m)) - F(z, m)/(2*m) - \ + sin(2*z)/(4*(1 - m)*sqrt(1 - m*sin(z)**2)) + r = randcplx() + assert td(F(z, r), z) + assert td(F(r, m), m) + + mi = Symbol('m', real=False) + assert F(z, mi).conjugate() == F(z.conjugate(), mi.conjugate()) + mr = Symbol('m', negative=True) + assert F(z, mr).conjugate() == F(z.conjugate(), mr) + + assert F(z, m).series(z) == \ + z + z**5*(3*m**2/40 - m/30) + m*z**3/6 + O(z**6) + + assert F(z, m).rewrite(Integral).dummy_eq( + Integral(1/sqrt(1 - m*sin(t)**2), (t, 0, z))) + +def test_E(): + assert E(z, 0) == z + assert E(0, m) == 0 + assert E(i*pi/2, m) == i*E(m) + assert E(z, oo) is zoo + assert E(z, -oo) is zoo + assert E(0) == pi/2 + assert E(1) == 1 + assert E(oo) == I*oo + assert E(-oo) is oo + assert E(zoo) is zoo + + assert E(-z, m) == -E(z, m) + + assert E(z, m).diff(z) == sqrt(1 - m*sin(z)**2) + assert E(z, m).diff(m) == (E(z, m) - F(z, m))/(2*m) + assert E(z).diff(z) == (E(z) - K(z))/(2*z) + r = randcplx() + assert td(E(r, m), m) + assert td(E(z, r), z) + assert td(E(z), z) + + mi = Symbol('m', real=False) + assert E(z, mi).conjugate() == E(z.conjugate(), mi.conjugate()) + assert E(mi).conjugate() == E(mi.conjugate()) + mr = Symbol('m', negative=True) + assert E(z, mr).conjugate() == E(z.conjugate(), mr) + assert E(mr).conjugate() == E(mr) + + assert E(z).rewrite(hyper) == (pi/2)*hyper((Rational(-1, 2), S.Half), (S.One,), z) + assert tn(E(z), (pi/2)*hyper((Rational(-1, 2), S.Half), (S.One,), z)) + assert E(z).rewrite(meijerg) == \ + -meijerg(((S.Half, Rational(3, 2)), []), ((S.Zero,), (S.Zero,)), -z)/4 + assert tn(E(z), -meijerg(((S.Half, Rational(3, 2)), []), ((S.Zero,), (S.Zero,)), -z)/4) + + assert E(z, m).series(z) == \ + z + z**5*(-m**2/40 + m/30) - m*z**3/6 + O(z**6) + assert E(z).series(z) == pi/2 - pi*z/8 - 3*pi*z**2/128 - \ + 5*pi*z**3/512 - 175*pi*z**4/32768 - 441*pi*z**5/131072 + O(z**6) + assert E(4*z/(z+1)).series(z) == \ + pi/2 - pi*z/2 + pi*z**2/8 - 3*pi*z**3/8 - 15*pi*z**4/128 - 93*pi*z**5/128 + O(z**6) + + assert E(z, m).rewrite(Integral).dummy_eq( + Integral(sqrt(1 - m*sin(t)**2), (t, 0, z))) + assert E(m).rewrite(Integral).dummy_eq( + Integral(sqrt(1 - m*sin(t)**2), (t, 0, pi/2))) + +def test_P(): + assert P(0, z, m) == F(z, m) + assert P(1, z, m) == F(z, m) + \ + (sqrt(1 - m*sin(z)**2)*tan(z) - E(z, m))/(1 - m) + assert P(n, i*pi/2, m) == i*P(n, m) + assert P(n, z, 0) == atanh(sqrt(n - 1)*tan(z))/sqrt(n - 1) + assert P(n, z, n) == F(z, n) - P(1, z, n) + tan(z)/sqrt(1 - n*sin(z)**2) + assert P(oo, z, m) == 0 + assert P(-oo, z, m) == 0 + assert P(n, z, oo) == 0 + assert P(n, z, -oo) == 0 + assert P(0, m) == K(m) + assert P(1, m) is zoo + assert P(n, 0) == pi/(2*sqrt(1 - n)) + assert P(2, 1) is -oo + assert P(-1, 1) is oo + assert P(n, n) == E(n)/(1 - n) + + assert P(n, -z, m) == -P(n, z, m) + + ni, mi = Symbol('n', real=False), Symbol('m', real=False) + assert P(ni, z, mi).conjugate() == \ + P(ni.conjugate(), z.conjugate(), mi.conjugate()) + nr, mr = Symbol('n', negative=True), \ + Symbol('m', negative=True) + assert P(nr, z, mr).conjugate() == P(nr, z.conjugate(), mr) + assert P(n, m).conjugate() == P(n.conjugate(), m.conjugate()) + + assert P(n, z, m).diff(n) == (E(z, m) + (m - n)*F(z, m)/n + + (n**2 - m)*P(n, z, m)/n - n*sqrt(1 - + m*sin(z)**2)*sin(2*z)/(2*(1 - n*sin(z)**2)))/(2*(m - n)*(n - 1)) + assert P(n, z, m).diff(z) == 1/(sqrt(1 - m*sin(z)**2)*(1 - n*sin(z)**2)) + assert P(n, z, m).diff(m) == (E(z, m)/(m - 1) + P(n, z, m) - + m*sin(2*z)/(2*(m - 1)*sqrt(1 - m*sin(z)**2)))/(2*(n - m)) + assert P(n, m).diff(n) == (E(m) + (m - n)*K(m)/n + + (n**2 - m)*P(n, m)/n)/(2*(m - n)*(n - 1)) + assert P(n, m).diff(m) == (E(m)/(m - 1) + P(n, m))/(2*(n - m)) + + # These tests fail due to + # https://github.com/fredrik-johansson/mpmath/issues/571#issuecomment-777201962 + # https://github.com/sympy/sympy/issues/20933#issuecomment-777080385 + # + # rx, ry = randcplx(), randcplx() + # assert td(P(n, rx, ry), n) + # assert td(P(rx, z, ry), z) + # assert td(P(rx, ry, m), m) + + assert P(n, z, m).series(z) == z + z**3*(m/6 + n/3) + \ + z**5*(3*m**2/40 + m*n/10 - m/30 + n**2/5 - n/15) + O(z**6) + + assert P(n, z, m).rewrite(Integral).dummy_eq( + Integral(1/((1 - n*sin(t)**2)*sqrt(1 - m*sin(t)**2)), (t, 0, z))) + assert P(n, m).rewrite(Integral).dummy_eq( + Integral(1/((1 - n*sin(t)**2)*sqrt(1 - m*sin(t)**2)), (t, 0, pi/2))) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_error_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_error_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..073371d3d584b97936729dc2e39c833ac347559b --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_error_functions.py @@ -0,0 +1,860 @@ +from sympy.core.function import (diff, expand, expand_func) +from sympy.core import EulerGamma +from sympy.core.numbers import (E, Float, I, Rational, nan, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols, Dummy) +from sympy.functions.elementary.complexes import (conjugate, im, polar_lift, re) +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.hyperbolic import (cosh, sinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin, sinc) +from sympy.functions.special.error_functions import (Chi, Ci, E1, Ei, Li, Shi, Si, erf, erf2, erf2inv, erfc, erfcinv, erfi, erfinv, expint, fresnelc, fresnels, li) +from sympy.functions.special.gamma_functions import (gamma, uppergamma) +from sympy.functions.special.hyper import (hyper, meijerg) +from sympy.integrals.integrals import (Integral, integrate) +from sympy.series.gruntz import gruntz +from sympy.series.limits import limit +from sympy.series.order import O +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.functions.special.error_functions import _erfs, _eis +from sympy.testing.pytest import raises + +x, y, z = symbols('x,y,z') +w = Symbol("w", real=True) +n = Symbol("n", integer=True) +t = Dummy('t') + + +def test_erf(): + assert erf(nan) is nan + + assert erf(oo) == 1 + assert erf(-oo) == -1 + + assert erf(0) is S.Zero + + assert erf(I*oo) == oo*I + assert erf(-I*oo) == -oo*I + + assert erf(-2) == -erf(2) + assert erf(-x*y) == -erf(x*y) + assert erf(-x - y) == -erf(x + y) + + assert erf(erfinv(x)) == x + assert erf(erfcinv(x)) == 1 - x + assert erf(erf2inv(0, x)) == x + assert erf(erf2inv(0, x, evaluate=False)) == x # To cover code in erf + assert erf(erf2inv(0, erf(erfcinv(1 - erf(erfinv(x)))))) == x + + alpha = symbols('alpha', extended_real=True) + assert erf(alpha).is_real is True + assert erf(alpha).is_finite is True + alpha = symbols('alpha', extended_real=False) + assert erf(alpha).is_real is None + assert erf(alpha).is_finite is None + assert erf(alpha).is_zero is None + assert erf(alpha).is_positive is None + assert erf(alpha).is_negative is None + alpha = symbols('alpha', extended_positive=True) + assert erf(alpha).is_positive is True + alpha = symbols('alpha', extended_negative=True) + assert erf(alpha).is_negative is True + assert erf(I).is_real is False + assert erf(0, evaluate=False).is_real + assert erf(0, evaluate=False).is_zero + + assert conjugate(erf(z)) == erf(conjugate(z)) + + assert erf(x).as_leading_term(x) == 2*x/sqrt(pi) + assert erf(x*y).as_leading_term(y) == 2*x*y/sqrt(pi) + assert (erf(x*y)/erf(y)).as_leading_term(y) == x + assert erf(1/x).as_leading_term(x) == S.One + + assert erf(z).rewrite('uppergamma') == sqrt(z**2)*(1 - erfc(sqrt(z**2)))/z + assert erf(z).rewrite('erfc') == S.One - erfc(z) + assert erf(z).rewrite('erfi') == -I*erfi(I*z) + assert erf(z).rewrite('fresnels') == (1 + I)*(fresnelc(z*(1 - I)/sqrt(pi)) - + I*fresnels(z*(1 - I)/sqrt(pi))) + assert erf(z).rewrite('fresnelc') == (1 + I)*(fresnelc(z*(1 - I)/sqrt(pi)) - + I*fresnels(z*(1 - I)/sqrt(pi))) + assert erf(z).rewrite('hyper') == 2*z*hyper([S.Half], [3*S.Half], -z**2)/sqrt(pi) + assert erf(z).rewrite('meijerg') == z*meijerg([S.Half], [], [0], [Rational(-1, 2)], z**2)/sqrt(pi) + assert erf(z).rewrite('expint') == sqrt(z**2)/z - z*expint(S.Half, z**2)/sqrt(S.Pi) + + assert limit(exp(x)*exp(x**2)*(erf(x + 1/exp(x)) - erf(x)), x, oo) == \ + 2/sqrt(pi) + assert limit((1 - erf(z))*exp(z**2)*z, z, oo) == 1/sqrt(pi) + assert limit((1 - erf(x))*exp(x**2)*sqrt(pi)*x, x, oo) == 1 + assert limit(((1 - erf(x))*exp(x**2)*sqrt(pi)*x - 1)*2*x**2, x, oo) == -1 + assert limit(erf(x)/x, x, 0) == 2/sqrt(pi) + assert limit(x**(-4) - sqrt(pi)*erf(x**2) / (2*x**6), x, 0) == S(1)/3 + + assert erf(x).as_real_imag() == \ + (erf(re(x) - I*im(x))/2 + erf(re(x) + I*im(x))/2, + -I*(-erf(re(x) - I*im(x)) + erf(re(x) + I*im(x)))/2) + + assert erf(x).as_real_imag(deep=False) == \ + (erf(re(x) - I*im(x))/2 + erf(re(x) + I*im(x))/2, + -I*(-erf(re(x) - I*im(x)) + erf(re(x) + I*im(x)))/2) + + assert erf(w).as_real_imag() == (erf(w), 0) + assert erf(w).as_real_imag(deep=False) == (erf(w), 0) + # issue 13575 + assert erf(I).as_real_imag() == (0, -I*erf(I)) + + raises(ArgumentIndexError, lambda: erf(x).fdiff(2)) + + assert erf(x).inverse() == erfinv + + +def test_erf_series(): + assert erf(x).series(x, 0, 7) == 2*x/sqrt(pi) - \ + 2*x**3/3/sqrt(pi) + x**5/5/sqrt(pi) + O(x**7) + + assert erf(x).series(x, oo) == \ + -exp(-x**2)*(3/(4*x**5) - 1/(2*x**3) + 1/x + O(x**(-6), (x, oo)))/sqrt(pi) + 1 + assert erf(x**2).series(x, oo, n=8) == \ + (-1/(2*x**6) + x**(-2) + O(x**(-8), (x, oo)))*exp(-x**4)/sqrt(pi)*-1 + 1 + assert erf(sqrt(x)).series(x, oo, n=3) == (sqrt(1/x) - (1/x)**(S(3)/2)/2\ + + 3*(1/x)**(S(5)/2)/4 + O(x**(-3), (x, oo)))*exp(-x)/sqrt(pi)*-1 + 1 + + +def test_erf_evalf(): + assert abs( erf(Float(2.0)) - 0.995322265 ) < 1E-8 # XXX + + +def test__erfs(): + assert _erfs(z).diff(z) == -2/sqrt(S.Pi) + 2*z*_erfs(z) + + assert _erfs(1/z).series(z) == \ + z/sqrt(pi) - z**3/(2*sqrt(pi)) + 3*z**5/(4*sqrt(pi)) + O(z**6) + + assert expand(erf(z).rewrite('tractable').diff(z).rewrite('intractable')) \ + == erf(z).diff(z) + assert _erfs(z).rewrite("intractable") == (-erf(z) + 1)*exp(z**2) + raises(ArgumentIndexError, lambda: _erfs(z).fdiff(2)) + + +def test_erfc(): + assert erfc(nan) is nan + + assert erfc(oo) is S.Zero + assert erfc(-oo) == 2 + + assert erfc(0) == 1 + + assert erfc(I*oo) == -oo*I + assert erfc(-I*oo) == oo*I + + assert erfc(-x) == S(2) - erfc(x) + assert erfc(erfcinv(x)) == x + + alpha = symbols('alpha', extended_real=True) + assert erfc(alpha).is_real is True + alpha = symbols('alpha', extended_real=False) + assert erfc(alpha).is_real is None + assert erfc(I).is_real is False + assert erfc(0, evaluate=False).is_real + assert erfc(0, evaluate=False).is_zero is False + + assert erfc(erfinv(x)) == 1 - x + + assert conjugate(erfc(z)) == erfc(conjugate(z)) + + assert erfc(x).as_leading_term(x) is S.One + assert erfc(1/x).as_leading_term(x) == S.Zero + + assert erfc(z).rewrite('erf') == 1 - erf(z) + assert erfc(z).rewrite('erfi') == 1 + I*erfi(I*z) + assert erfc(z).rewrite('fresnels') == 1 - (1 + I)*(fresnelc(z*(1 - I)/sqrt(pi)) - + I*fresnels(z*(1 - I)/sqrt(pi))) + assert erfc(z).rewrite('fresnelc') == 1 - (1 + I)*(fresnelc(z*(1 - I)/sqrt(pi)) - + I*fresnels(z*(1 - I)/sqrt(pi))) + assert erfc(z).rewrite('hyper') == 1 - 2*z*hyper([S.Half], [3*S.Half], -z**2)/sqrt(pi) + assert erfc(z).rewrite('meijerg') == 1 - z*meijerg([S.Half], [], [0], [Rational(-1, 2)], z**2)/sqrt(pi) + assert erfc(z).rewrite('uppergamma') == 1 - sqrt(z**2)*(1 - erfc(sqrt(z**2)))/z + assert erfc(z).rewrite('expint') == S.One - sqrt(z**2)/z + z*expint(S.Half, z**2)/sqrt(S.Pi) + assert erfc(z).rewrite('tractable') == _erfs(z)*exp(-z**2) + assert expand_func(erf(x) + erfc(x)) is S.One + + assert erfc(x).as_real_imag() == \ + (erfc(re(x) - I*im(x))/2 + erfc(re(x) + I*im(x))/2, + -I*(-erfc(re(x) - I*im(x)) + erfc(re(x) + I*im(x)))/2) + + assert erfc(x).as_real_imag(deep=False) == \ + (erfc(re(x) - I*im(x))/2 + erfc(re(x) + I*im(x))/2, + -I*(-erfc(re(x) - I*im(x)) + erfc(re(x) + I*im(x)))/2) + + assert erfc(w).as_real_imag() == (erfc(w), 0) + assert erfc(w).as_real_imag(deep=False) == (erfc(w), 0) + raises(ArgumentIndexError, lambda: erfc(x).fdiff(2)) + + assert erfc(x).inverse() == erfcinv + + +def test_erfc_series(): + assert erfc(x).series(x, 0, 7) == 1 - 2*x/sqrt(pi) + \ + 2*x**3/3/sqrt(pi) - x**5/5/sqrt(pi) + O(x**7) + + assert erfc(x).series(x, oo) == \ + (3/(4*x**5) - 1/(2*x**3) + 1/x + O(x**(-6), (x, oo)))*exp(-x**2)/sqrt(pi) + + +def test_erfc_evalf(): + assert abs( erfc(Float(2.0)) - 0.00467773 ) < 1E-8 # XXX + + +def test_erfi(): + assert erfi(nan) is nan + + assert erfi(oo) is S.Infinity + assert erfi(-oo) is S.NegativeInfinity + + assert erfi(0) is S.Zero + + assert erfi(I*oo) == I + assert erfi(-I*oo) == -I + + assert erfi(-x) == -erfi(x) + + assert erfi(I*erfinv(x)) == I*x + assert erfi(I*erfcinv(x)) == I*(1 - x) + assert erfi(I*erf2inv(0, x)) == I*x + assert erfi(I*erf2inv(0, x, evaluate=False)) == I*x # To cover code in erfi + + assert erfi(I).is_real is False + assert erfi(0, evaluate=False).is_real + assert erfi(0, evaluate=False).is_zero + + assert conjugate(erfi(z)) == erfi(conjugate(z)) + + assert erfi(x).as_leading_term(x) == 2*x/sqrt(pi) + assert erfi(x*y).as_leading_term(y) == 2*x*y/sqrt(pi) + assert (erfi(x*y)/erfi(y)).as_leading_term(y) == x + assert erfi(1/x).as_leading_term(x) == erfi(1/x) + + assert erfi(z).rewrite('erf') == -I*erf(I*z) + assert erfi(z).rewrite('erfc') == I*erfc(I*z) - I + assert erfi(z).rewrite('fresnels') == (1 - I)*(fresnelc(z*(1 + I)/sqrt(pi)) - + I*fresnels(z*(1 + I)/sqrt(pi))) + assert erfi(z).rewrite('fresnelc') == (1 - I)*(fresnelc(z*(1 + I)/sqrt(pi)) - + I*fresnels(z*(1 + I)/sqrt(pi))) + assert erfi(z).rewrite('hyper') == 2*z*hyper([S.Half], [3*S.Half], z**2)/sqrt(pi) + assert erfi(z).rewrite('meijerg') == z*meijerg([S.Half], [], [0], [Rational(-1, 2)], -z**2)/sqrt(pi) + assert erfi(z).rewrite('uppergamma') == (sqrt(-z**2)/z*(uppergamma(S.Half, + -z**2)/sqrt(S.Pi) - S.One)) + assert erfi(z).rewrite('expint') == sqrt(-z**2)/z - z*expint(S.Half, -z**2)/sqrt(S.Pi) + assert erfi(z).rewrite('tractable') == -I*(-_erfs(I*z)*exp(z**2) + 1) + assert expand_func(erfi(I*z)) == I*erf(z) + + assert erfi(x).as_real_imag() == \ + (erfi(re(x) - I*im(x))/2 + erfi(re(x) + I*im(x))/2, + -I*(-erfi(re(x) - I*im(x)) + erfi(re(x) + I*im(x)))/2) + assert erfi(x).as_real_imag(deep=False) == \ + (erfi(re(x) - I*im(x))/2 + erfi(re(x) + I*im(x))/2, + -I*(-erfi(re(x) - I*im(x)) + erfi(re(x) + I*im(x)))/2) + + assert erfi(w).as_real_imag() == (erfi(w), 0) + assert erfi(w).as_real_imag(deep=False) == (erfi(w), 0) + + raises(ArgumentIndexError, lambda: erfi(x).fdiff(2)) + + +def test_erfi_series(): + assert erfi(x).series(x, 0, 7) == 2*x/sqrt(pi) + \ + 2*x**3/3/sqrt(pi) + x**5/5/sqrt(pi) + O(x**7) + + assert erfi(x).series(x, oo) == \ + (3/(4*x**5) + 1/(2*x**3) + 1/x + O(x**(-6), (x, oo)))*exp(x**2)/sqrt(pi) - I + + +def test_erfi_evalf(): + assert abs( erfi(Float(2.0)) - 18.5648024145756 ) < 1E-13 # XXX + + +def test_erf2(): + + assert erf2(0, 0) is S.Zero + assert erf2(x, x) is S.Zero + assert erf2(nan, 0) is nan + + assert erf2(-oo, y) == erf(y) + 1 + assert erf2( oo, y) == erf(y) - 1 + assert erf2( x, oo) == 1 - erf(x) + assert erf2( x,-oo) == -1 - erf(x) + assert erf2(x, erf2inv(x, y)) == y + + assert erf2(-x, -y) == -erf2(x,y) + assert erf2(-x, y) == erf(y) + erf(x) + assert erf2( x, -y) == -erf(y) - erf(x) + assert erf2(x, y).rewrite('fresnels') == erf(y).rewrite(fresnels)-erf(x).rewrite(fresnels) + assert erf2(x, y).rewrite('fresnelc') == erf(y).rewrite(fresnelc)-erf(x).rewrite(fresnelc) + assert erf2(x, y).rewrite('hyper') == erf(y).rewrite(hyper)-erf(x).rewrite(hyper) + assert erf2(x, y).rewrite('meijerg') == erf(y).rewrite(meijerg)-erf(x).rewrite(meijerg) + assert erf2(x, y).rewrite('uppergamma') == erf(y).rewrite(uppergamma) - erf(x).rewrite(uppergamma) + assert erf2(x, y).rewrite('expint') == erf(y).rewrite(expint)-erf(x).rewrite(expint) + + assert erf2(I, 0).is_real is False + assert erf2(0, 0, evaluate=False).is_real + assert erf2(0, 0, evaluate=False).is_zero + assert erf2(x, x, evaluate=False).is_zero + assert erf2(x, y).is_zero is None + + assert expand_func(erf(x) + erf2(x, y)) == erf(y) + + assert conjugate(erf2(x, y)) == erf2(conjugate(x), conjugate(y)) + + assert erf2(x, y).rewrite('erf') == erf(y) - erf(x) + assert erf2(x, y).rewrite('erfc') == erfc(x) - erfc(y) + assert erf2(x, y).rewrite('erfi') == I*(erfi(I*x) - erfi(I*y)) + + assert erf2(x, y).diff(x) == erf2(x, y).fdiff(1) + assert erf2(x, y).diff(y) == erf2(x, y).fdiff(2) + assert erf2(x, y).diff(x) == -2*exp(-x**2)/sqrt(pi) + assert erf2(x, y).diff(y) == 2*exp(-y**2)/sqrt(pi) + raises(ArgumentIndexError, lambda: erf2(x, y).fdiff(3)) + + assert erf2(x, y).is_extended_real is None + xr, yr = symbols('xr yr', extended_real=True) + assert erf2(xr, yr).is_extended_real is True + + +def test_erfinv(): + assert erfinv(0) is S.Zero + assert erfinv(1) is S.Infinity + assert erfinv(nan) is S.NaN + assert erfinv(-1) is S.NegativeInfinity + + assert erfinv(erf(w)) == w + assert erfinv(erf(-w)) == -w + + assert erfinv(x).diff() == sqrt(pi)*exp(erfinv(x)**2)/2 + raises(ArgumentIndexError, lambda: erfinv(x).fdiff(2)) + + assert erfinv(z).rewrite('erfcinv') == erfcinv(1-z) + assert erfinv(z).inverse() == erf + + +def test_erfinv_evalf(): + assert abs( erfinv(Float(0.2)) - 0.179143454621292 ) < 1E-13 + + +def test_erfcinv(): + assert erfcinv(1) is S.Zero + assert erfcinv(0) is S.Infinity + assert erfcinv(0, evaluate=False).is_infinite is True + assert erfcinv(2, evaluate=False).is_infinite is True + assert erfcinv(nan) is S.NaN + + assert erfcinv(x).diff() == -sqrt(pi)*exp(erfcinv(x)**2)/2 + raises(ArgumentIndexError, lambda: erfcinv(x).fdiff(2)) + + assert erfcinv(z).rewrite('erfinv') == erfinv(1-z) + assert erfcinv(z).inverse() == erfc + + +def test_erf2inv(): + assert erf2inv(0, 0) is S.Zero + assert erf2inv(0, 1) is S.Infinity + assert erf2inv(1, 0) is S.One + assert erf2inv(0, y) == erfinv(y) + assert erf2inv(oo, y) == erfcinv(-y) + assert erf2inv(x, 0) == x + assert erf2inv(x, oo) == erfinv(x) + assert erf2inv(nan, 0) is nan + assert erf2inv(0, nan) is nan + + assert erf2inv(x, y).diff(x) == exp(-x**2 + erf2inv(x, y)**2) + assert erf2inv(x, y).diff(y) == sqrt(pi)*exp(erf2inv(x, y)**2)/2 + raises(ArgumentIndexError, lambda: erf2inv(x, y).fdiff(3)) + + +# NOTE we multiply by exp_polar(I*pi) and need this to be on the principal +# branch, hence take x in the lower half plane (d=0). + + +def mytn(expr1, expr2, expr3, x, d=0): + from sympy.core.random import verify_numerically, random_complex_number + subs = {} + for a in expr1.free_symbols: + if a != x: + subs[a] = random_complex_number() + return expr2 == expr3 and verify_numerically(expr1.subs(subs), + expr2.subs(subs), x, d=d) + + +def mytd(expr1, expr2, x): + from sympy.core.random import test_derivative_numerically, \ + random_complex_number + subs = {} + for a in expr1.free_symbols: + if a != x: + subs[a] = random_complex_number() + return expr1.diff(x) == expr2 and test_derivative_numerically(expr1.subs(subs), x) + + +def tn_branch(func, s=None): + from sympy.core.random import uniform + + def fn(x): + if s is None: + return func(x) + return func(s, x) + c = uniform(1, 5) + expr = fn(c*exp_polar(I*pi)) - fn(c*exp_polar(-I*pi)) + eps = 1e-15 + expr2 = fn(-c + eps*I) - fn(-c - eps*I) + return abs(expr.n() - expr2.n()).n() < 1e-10 + + +def test_ei(): + assert Ei(0) is S.NegativeInfinity + assert Ei(oo) is S.Infinity + assert Ei(-oo) is S.Zero + + assert tn_branch(Ei) + assert mytd(Ei(x), exp(x)/x, x) + assert mytn(Ei(x), Ei(x).rewrite(uppergamma), + -uppergamma(0, x*polar_lift(-1)) - I*pi, x) + assert mytn(Ei(x), Ei(x).rewrite(expint), + -expint(1, x*polar_lift(-1)) - I*pi, x) + assert Ei(x).rewrite(expint).rewrite(Ei) == Ei(x) + assert Ei(x*exp_polar(2*I*pi)) == Ei(x) + 2*I*pi + assert Ei(x*exp_polar(-2*I*pi)) == Ei(x) - 2*I*pi + + assert mytn(Ei(x), Ei(x).rewrite(Shi), Chi(x) + Shi(x), x) + assert mytn(Ei(x*polar_lift(I)), Ei(x*polar_lift(I)).rewrite(Si), + Ci(x) + I*Si(x) + I*pi/2, x) + + assert Ei(log(x)).rewrite(li) == li(x) + assert Ei(2*log(x)).rewrite(li) == li(x**2) + + assert gruntz(Ei(x+exp(-x))*exp(-x)*x, x, oo) == 1 + + assert Ei(x).series(x) == EulerGamma + log(x) + x + x**2/4 + \ + x**3/18 + x**4/96 + x**5/600 + O(x**6) + assert Ei(x).series(x, 1, 3) == Ei(1) + E*(x - 1) + O((x - 1)**3, (x, 1)) + assert Ei(x).series(x, oo) == \ + (120/x**5 + 24/x**4 + 6/x**3 + 2/x**2 + 1/x + 1 + O(x**(-6), (x, oo)))*exp(x)/x + assert Ei(x).series(x, -oo) == \ + (120/x**5 + 24/x**4 + 6/x**3 + 2/x**2 + 1/x + 1 + O(x**(-6), (x, -oo)))*exp(x)/x + assert Ei(-x).series(x, oo) == \ + -((-120/x**5 + 24/x**4 - 6/x**3 + 2/x**2 - 1/x + 1 + O(x**(-6), (x, oo)))*exp(-x)/x) + + assert str(Ei(cos(2)).evalf(n=10)) == '-0.6760647401' + raises(ArgumentIndexError, lambda: Ei(x).fdiff(2)) + + +def test_expint(): + assert mytn(expint(x, y), expint(x, y).rewrite(uppergamma), + y**(x - 1)*uppergamma(1 - x, y), x) + assert mytd( + expint(x, y), -y**(x - 1)*meijerg([], [1, 1], [0, 0, 1 - x], [], y), x) + assert mytd(expint(x, y), -expint(x - 1, y), y) + assert mytn(expint(1, x), expint(1, x).rewrite(Ei), + -Ei(x*polar_lift(-1)) + I*pi, x) + + assert expint(-4, x) == exp(-x)/x + 4*exp(-x)/x**2 + 12*exp(-x)/x**3 \ + + 24*exp(-x)/x**4 + 24*exp(-x)/x**5 + assert expint(Rational(-3, 2), x) == \ + exp(-x)/x + 3*exp(-x)/(2*x**2) + 3*sqrt(pi)*erfc(sqrt(x))/(4*x**S('5/2')) + + assert tn_branch(expint, 1) + assert tn_branch(expint, 2) + assert tn_branch(expint, 3) + assert tn_branch(expint, 1.7) + assert tn_branch(expint, pi) + + assert expint(y, x*exp_polar(2*I*pi)) == \ + x**(y - 1)*(exp(2*I*pi*y) - 1)*gamma(-y + 1) + expint(y, x) + assert expint(y, x*exp_polar(-2*I*pi)) == \ + x**(y - 1)*(exp(-2*I*pi*y) - 1)*gamma(-y + 1) + expint(y, x) + assert expint(2, x*exp_polar(2*I*pi)) == 2*I*pi*x + expint(2, x) + assert expint(2, x*exp_polar(-2*I*pi)) == -2*I*pi*x + expint(2, x) + assert expint(1, x).rewrite(Ei).rewrite(expint) == expint(1, x) + assert expint(x, y).rewrite(Ei) == expint(x, y) + assert expint(x, y).rewrite(Ci) == expint(x, y) + + assert mytn(E1(x), E1(x).rewrite(Shi), Shi(x) - Chi(x), x) + assert mytn(E1(polar_lift(I)*x), E1(polar_lift(I)*x).rewrite(Si), + -Ci(x) + I*Si(x) - I*pi/2, x) + + assert mytn(expint(2, x), expint(2, x).rewrite(Ei).rewrite(expint), + -x*E1(x) + exp(-x), x) + assert mytn(expint(3, x), expint(3, x).rewrite(Ei).rewrite(expint), + x**2*E1(x)/2 + (1 - x)*exp(-x)/2, x) + + assert expint(Rational(3, 2), z).nseries(z) == \ + 2 + 2*z - z**2/3 + z**3/15 - z**4/84 + z**5/540 - \ + 2*sqrt(pi)*sqrt(z) + O(z**6) + + assert E1(z).series(z) == -EulerGamma - log(z) + z - \ + z**2/4 + z**3/18 - z**4/96 + z**5/600 + O(z**6) + + assert expint(4, z).series(z) == Rational(1, 3) - z/2 + z**2/2 + \ + z**3*(log(z)/6 - Rational(11, 36) + EulerGamma/6 - I*pi/6) - z**4/24 + \ + z**5/240 + O(z**6) + + assert expint(n, x).series(x, oo, n=3) == \ + (n*(n + 1)/x**2 - n/x + 1 + O(x**(-3), (x, oo)))*exp(-x)/x + + assert expint(z, y).series(z, 0, 2) == exp(-y)/y - z*meijerg(((), (1, 1)), + ((0, 0, 1), ()), y)/y + O(z**2) + raises(ArgumentIndexError, lambda: expint(x, y).fdiff(3)) + + neg = Symbol('neg', negative=True) + assert Ei(neg).rewrite(Si) == Shi(neg) + Chi(neg) - I*pi + + +def test__eis(): + assert _eis(z).diff(z) == -_eis(z) + 1/z + + assert _eis(1/z).series(z) == \ + z + z**2 + 2*z**3 + 6*z**4 + 24*z**5 + O(z**6) + + assert Ei(z).rewrite('tractable') == exp(z)*_eis(z) + assert li(z).rewrite('tractable') == z*_eis(log(z)) + + assert _eis(z).rewrite('intractable') == exp(-z)*Ei(z) + + assert expand(li(z).rewrite('tractable').diff(z).rewrite('intractable')) \ + == li(z).diff(z) + + assert expand(Ei(z).rewrite('tractable').diff(z).rewrite('intractable')) \ + == Ei(z).diff(z) + + assert _eis(z).series(z, n=3) == EulerGamma + log(z) + z*(-log(z) - \ + EulerGamma + 1) + z**2*(log(z)/2 - Rational(3, 4) + EulerGamma/2)\ + + O(z**3*log(z)) + raises(ArgumentIndexError, lambda: _eis(z).fdiff(2)) + + +def tn_arg(func): + def test(arg, e1, e2): + from sympy.core.random import uniform + v = uniform(1, 5) + v1 = func(arg*x).subs(x, v).n() + v2 = func(e1*v + e2*1e-15).n() + return abs(v1 - v2).n() < 1e-10 + return test(exp_polar(I*pi/2), I, 1) and \ + test(exp_polar(-I*pi/2), -I, 1) and \ + test(exp_polar(I*pi), -1, I) and \ + test(exp_polar(-I*pi), -1, -I) + + +def test_li(): + z = Symbol("z") + zr = Symbol("z", real=True) + zp = Symbol("z", positive=True) + zn = Symbol("z", negative=True) + + assert li(0) is S.Zero + assert li(1) is -oo + assert li(oo) is oo + + assert isinstance(li(z), li) + assert unchanged(li, -zp) + assert unchanged(li, zn) + + assert diff(li(z), z) == 1/log(z) + + assert conjugate(li(z)) == li(conjugate(z)) + assert conjugate(li(-zr)) == li(-zr) + assert unchanged(conjugate, li(-zp)) + assert unchanged(conjugate, li(zn)) + + assert li(z).rewrite(Li) == Li(z) + li(2) + assert li(z).rewrite(Ei) == Ei(log(z)) + assert li(z).rewrite(uppergamma) == (-log(1/log(z))/2 - log(-log(z)) + + log(log(z))/2 - expint(1, -log(z))) + assert li(z).rewrite(Si) == (-log(I*log(z)) - log(1/log(z))/2 + + log(log(z))/2 + Ci(I*log(z)) + Shi(log(z))) + assert li(z).rewrite(Ci) == (-log(I*log(z)) - log(1/log(z))/2 + + log(log(z))/2 + Ci(I*log(z)) + Shi(log(z))) + assert li(z).rewrite(Shi) == (-log(1/log(z))/2 + log(log(z))/2 + + Chi(log(z)) - Shi(log(z))) + assert li(z).rewrite(Chi) == (-log(1/log(z))/2 + log(log(z))/2 + + Chi(log(z)) - Shi(log(z))) + assert li(z).rewrite(hyper) ==(log(z)*hyper((1, 1), (2, 2), log(z)) - + log(1/log(z))/2 + log(log(z))/2 + EulerGamma) + assert li(z).rewrite(meijerg) == (-log(1/log(z))/2 - log(-log(z)) + log(log(z))/2 - + meijerg(((), (1,)), ((0, 0), ()), -log(z))) + + assert gruntz(1/li(z), z, oo) is S.Zero + assert li(z).series(z) == log(z)**5/600 + log(z)**4/96 + log(z)**3/18 + log(z)**2/4 + \ + log(z) + log(log(z)) + EulerGamma + raises(ArgumentIndexError, lambda: li(z).fdiff(2)) + + +def test_Li(): + assert Li(2) is S.Zero + assert Li(oo) is oo + + assert isinstance(Li(z), Li) + + assert diff(Li(z), z) == 1/log(z) + + assert gruntz(1/Li(z), z, oo) is S.Zero + assert Li(z).rewrite(li) == li(z) - li(2) + assert Li(z).series(z) == \ + log(z)**5/600 + log(z)**4/96 + log(z)**3/18 + log(z)**2/4 + log(z) + log(log(z)) - li(2) + EulerGamma + raises(ArgumentIndexError, lambda: Li(z).fdiff(2)) + + +def test_si(): + assert Si(I*x) == I*Shi(x) + assert Shi(I*x) == I*Si(x) + assert Si(-I*x) == -I*Shi(x) + assert Shi(-I*x) == -I*Si(x) + assert Si(-x) == -Si(x) + assert Shi(-x) == -Shi(x) + assert Si(exp_polar(2*pi*I)*x) == Si(x) + assert Si(exp_polar(-2*pi*I)*x) == Si(x) + assert Shi(exp_polar(2*pi*I)*x) == Shi(x) + assert Shi(exp_polar(-2*pi*I)*x) == Shi(x) + + assert Si(oo) == pi/2 + assert Si(-oo) == -pi/2 + assert Shi(oo) is oo + assert Shi(-oo) is -oo + + assert mytd(Si(x), sin(x)/x, x) + assert mytd(Shi(x), sinh(x)/x, x) + + assert mytn(Si(x), Si(x).rewrite(Ei), + -I*(-Ei(x*exp_polar(-I*pi/2))/2 + + Ei(x*exp_polar(I*pi/2))/2 - I*pi) + pi/2, x) + assert mytn(Si(x), Si(x).rewrite(expint), + -I*(-expint(1, x*exp_polar(-I*pi/2))/2 + + expint(1, x*exp_polar(I*pi/2))/2) + pi/2, x) + assert mytn(Shi(x), Shi(x).rewrite(Ei), + Ei(x)/2 - Ei(x*exp_polar(I*pi))/2 + I*pi/2, x) + assert mytn(Shi(x), Shi(x).rewrite(expint), + expint(1, x)/2 - expint(1, x*exp_polar(I*pi))/2 - I*pi/2, x) + + assert tn_arg(Si) + assert tn_arg(Shi) + + assert Si(x)._eval_as_leading_term(x, None, 1) == x + assert Si(2*x)._eval_as_leading_term(x, None, 1) == 2*x + assert Si(sin(x))._eval_as_leading_term(x, None, 1) == x + assert Si(x + 1)._eval_as_leading_term(x, None, 1) == Si(1) + assert Si(1/x)._eval_as_leading_term(x, None, 1) == \ + Si(1/x)._eval_as_leading_term(x, None, -1) == Si(1/x) + + assert Si(x).nseries(x, n=8) == \ + x - x**3/18 + x**5/600 - x**7/35280 + O(x**8) + assert Shi(x).nseries(x, n=8) == \ + x + x**3/18 + x**5/600 + x**7/35280 + O(x**8) + assert Si(sin(x)).nseries(x, n=5) == x - 2*x**3/9 + O(x**5) + assert Si(x).nseries(x, 1, n=3) == \ + Si(1) + (x - 1)*sin(1) + (x - 1)**2*(-sin(1)/2 + cos(1)/2) + O((x - 1)**3, (x, 1)) + + assert Si(x).series(x, oo) == -sin(x)*(-6/x**4 + x**(-2) + O(x**(-6), (x, oo))) - \ + cos(x)*(24/x**5 - 2/x**3 + 1/x + O(x**(-6), (x, oo))) + pi/2 + + t = Symbol('t', Dummy=True) + assert Si(x).rewrite(sinc).dummy_eq(Integral(sinc(t), (t, 0, x))) + + assert limit(Shi(x), x, S.Infinity) == S.Infinity + assert limit(Shi(x), x, S.NegativeInfinity) == S.NegativeInfinity + + +def test_ci(): + m1 = exp_polar(I*pi) + m1_ = exp_polar(-I*pi) + pI = exp_polar(I*pi/2) + mI = exp_polar(-I*pi/2) + + assert Ci(m1*x) == Ci(x) + I*pi + assert Ci(m1_*x) == Ci(x) - I*pi + assert Ci(pI*x) == Chi(x) + I*pi/2 + assert Ci(mI*x) == Chi(x) - I*pi/2 + assert Chi(m1*x) == Chi(x) + I*pi + assert Chi(m1_*x) == Chi(x) - I*pi + assert Chi(pI*x) == Ci(x) + I*pi/2 + assert Chi(mI*x) == Ci(x) - I*pi/2 + assert Ci(exp_polar(2*I*pi)*x) == Ci(x) + 2*I*pi + assert Chi(exp_polar(-2*I*pi)*x) == Chi(x) - 2*I*pi + assert Chi(exp_polar(2*I*pi)*x) == Chi(x) + 2*I*pi + assert Ci(exp_polar(-2*I*pi)*x) == Ci(x) - 2*I*pi + + assert Ci(oo) is S.Zero + assert Ci(-oo) == I*pi + assert Chi(oo) is oo + assert Chi(-oo) is oo + + assert mytd(Ci(x), cos(x)/x, x) + assert mytd(Chi(x), cosh(x)/x, x) + + assert mytn(Ci(x), Ci(x).rewrite(Ei), + Ei(x*exp_polar(-I*pi/2))/2 + Ei(x*exp_polar(I*pi/2))/2, x) + assert mytn(Chi(x), Chi(x).rewrite(Ei), + Ei(x)/2 + Ei(x*exp_polar(I*pi))/2 - I*pi/2, x) + + assert tn_arg(Ci) + assert tn_arg(Chi) + + assert Ci(x).nseries(x, n=4) == \ + EulerGamma + log(x) - x**2/4 + O(x**4) + assert Chi(x).nseries(x, n=4) == \ + EulerGamma + log(x) + x**2/4 + O(x**4) + + assert Ci(x).series(x, oo) == -cos(x)*(-6/x**4 + x**(-2) + O(x**(-6), (x, oo))) + \ + sin(x)*(24/x**5 - 2/x**3 + 1/x + O(x**(-6), (x, oo))) + + assert Ci(x).series(x, -oo) == -cos(x)*(-6/x**4 + x**(-2) + O(x**(-6), (x, -oo))) + \ + sin(x)*(24/x**5 - 2/x**3 + 1/x + O(x**(-6), (x, -oo))) + I*pi + + assert limit(log(x) - Ci(2*x), x, 0) == -log(2) - EulerGamma + assert Ci(x).rewrite(uppergamma) == -expint(1, x*exp_polar(-I*pi/2))/2 -\ + expint(1, x*exp_polar(I*pi/2))/2 + assert Ci(x).rewrite(expint) == -expint(1, x*exp_polar(-I*pi/2))/2 -\ + expint(1, x*exp_polar(I*pi/2))/2 + raises(ArgumentIndexError, lambda: Ci(x).fdiff(2)) + + +def test_fresnel(): + assert fresnels(0) is S.Zero + assert fresnels(oo) is S.Half + assert fresnels(-oo) == Rational(-1, 2) + assert fresnels(I*oo) == -I*S.Half + + assert unchanged(fresnels, z) + assert fresnels(-z) == -fresnels(z) + assert fresnels(I*z) == -I*fresnels(z) + assert fresnels(-I*z) == I*fresnels(z) + + assert conjugate(fresnels(z)) == fresnels(conjugate(z)) + + assert fresnels(z).diff(z) == sin(pi*z**2/2) + + assert fresnels(z).rewrite(erf) == (S.One + I)/4 * ( + erf((S.One + I)/2*sqrt(pi)*z) - I*erf((S.One - I)/2*sqrt(pi)*z)) + + assert fresnels(z).rewrite(hyper) == \ + pi*z**3/6 * hyper([Rational(3, 4)], [Rational(3, 2), Rational(7, 4)], -pi**2*z**4/16) + + assert fresnels(z).series(z, n=15) == \ + pi*z**3/6 - pi**3*z**7/336 + pi**5*z**11/42240 + O(z**15) + + assert fresnels(w).is_extended_real is True + assert fresnels(w).is_finite is True + + assert fresnels(z).is_extended_real is None + assert fresnels(z).is_finite is None + + assert fresnels(z).as_real_imag() == (fresnels(re(z) - I*im(z))/2 + + fresnels(re(z) + I*im(z))/2, + -I*(-fresnels(re(z) - I*im(z)) + fresnels(re(z) + I*im(z)))/2) + + assert fresnels(z).as_real_imag(deep=False) == (fresnels(re(z) - I*im(z))/2 + + fresnels(re(z) + I*im(z))/2, + -I*(-fresnels(re(z) - I*im(z)) + fresnels(re(z) + I*im(z)))/2) + + assert fresnels(w).as_real_imag() == (fresnels(w), 0) + assert fresnels(w).as_real_imag(deep=True) == (fresnels(w), 0) + + assert fresnels(2 + 3*I).as_real_imag() == ( + fresnels(2 + 3*I)/2 + fresnels(2 - 3*I)/2, + -I*(fresnels(2 + 3*I) - fresnels(2 - 3*I))/2 + ) + + assert expand_func(integrate(fresnels(z), z)) == \ + z*fresnels(z) + cos(pi*z**2/2)/pi + + assert fresnels(z).rewrite(meijerg) == sqrt(2)*pi*z**Rational(9, 4) * \ + meijerg(((), (1,)), ((Rational(3, 4),), + (Rational(1, 4), 0)), -pi**2*z**4/16)/(2*(-z)**Rational(3, 4)*(z**2)**Rational(3, 4)) + + assert fresnelc(0) is S.Zero + assert fresnelc(oo) == S.Half + assert fresnelc(-oo) == Rational(-1, 2) + assert fresnelc(I*oo) == I*S.Half + + assert unchanged(fresnelc, z) + assert fresnelc(-z) == -fresnelc(z) + assert fresnelc(I*z) == I*fresnelc(z) + assert fresnelc(-I*z) == -I*fresnelc(z) + + assert conjugate(fresnelc(z)) == fresnelc(conjugate(z)) + + assert fresnelc(z).diff(z) == cos(pi*z**2/2) + + assert fresnelc(z).rewrite(erf) == (S.One - I)/4 * ( + erf((S.One + I)/2*sqrt(pi)*z) + I*erf((S.One - I)/2*sqrt(pi)*z)) + + assert fresnelc(z).rewrite(hyper) == \ + z * hyper([Rational(1, 4)], [S.Half, Rational(5, 4)], -pi**2*z**4/16) + + assert fresnelc(w).is_extended_real is True + + assert fresnelc(z).as_real_imag() == \ + (fresnelc(re(z) - I*im(z))/2 + fresnelc(re(z) + I*im(z))/2, + -I*(-fresnelc(re(z) - I*im(z)) + fresnelc(re(z) + I*im(z)))/2) + + assert fresnelc(z).as_real_imag(deep=False) == \ + (fresnelc(re(z) - I*im(z))/2 + fresnelc(re(z) + I*im(z))/2, + -I*(-fresnelc(re(z) - I*im(z)) + fresnelc(re(z) + I*im(z)))/2) + + assert fresnelc(2 + 3*I).as_real_imag() == ( + fresnelc(2 - 3*I)/2 + fresnelc(2 + 3*I)/2, + -I*(fresnelc(2 + 3*I) - fresnelc(2 - 3*I))/2 + ) + + assert expand_func(integrate(fresnelc(z), z)) == \ + z*fresnelc(z) - sin(pi*z**2/2)/pi + + assert fresnelc(z).rewrite(meijerg) == sqrt(2)*pi*z**Rational(3, 4) * \ + meijerg(((), (1,)), ((Rational(1, 4),), + (Rational(3, 4), 0)), -pi**2*z**4/16)/(2*(-z)**Rational(1, 4)*(z**2)**Rational(1, 4)) + + from sympy.core.random import verify_numerically + + verify_numerically(re(fresnels(z)), fresnels(z).as_real_imag()[0], z) + verify_numerically(im(fresnels(z)), fresnels(z).as_real_imag()[1], z) + verify_numerically(fresnels(z), fresnels(z).rewrite(hyper), z) + verify_numerically(fresnels(z), fresnels(z).rewrite(meijerg), z) + + verify_numerically(re(fresnelc(z)), fresnelc(z).as_real_imag()[0], z) + verify_numerically(im(fresnelc(z)), fresnelc(z).as_real_imag()[1], z) + verify_numerically(fresnelc(z), fresnelc(z).rewrite(hyper), z) + verify_numerically(fresnelc(z), fresnelc(z).rewrite(meijerg), z) + + raises(ArgumentIndexError, lambda: fresnels(z).fdiff(2)) + raises(ArgumentIndexError, lambda: fresnelc(z).fdiff(2)) + + assert fresnels(x).taylor_term(-1, x) is S.Zero + assert fresnelc(x).taylor_term(-1, x) is S.Zero + assert fresnelc(x).taylor_term(1, x) == -pi**2*x**5/40 + + +def test_fresnel_series(): + assert fresnelc(z).series(z, n=15) == \ + z - pi**2*z**5/40 + pi**4*z**9/3456 - pi**6*z**13/599040 + O(z**15) + + # issues 6510, 10102 + fs = (S.Half - sin(pi*z**2/2)/(pi**2*z**3) + + (-1/(pi*z) + 3/(pi**3*z**5))*cos(pi*z**2/2)) + fc = (S.Half - cos(pi*z**2/2)/(pi**2*z**3) + + (1/(pi*z) - 3/(pi**3*z**5))*sin(pi*z**2/2)) + assert fresnels(z).series(z, oo) == fs + O(z**(-6), (z, oo)) + assert fresnelc(z).series(z, oo) == fc + O(z**(-6), (z, oo)) + assert (fresnels(z).series(z, -oo) + fs.subs(z, -z)).expand().is_Order + assert (fresnelc(z).series(z, -oo) + fc.subs(z, -z)).expand().is_Order + assert (fresnels(1/z).series(z) - fs.subs(z, 1/z)).expand().is_Order + assert (fresnelc(1/z).series(z) - fc.subs(z, 1/z)).expand().is_Order + assert ((2*fresnels(3*z)).series(z, oo) - 2*fs.subs(z, 3*z)).expand().is_Order + assert ((3*fresnelc(2*z)).series(z, oo) - 3*fc.subs(z, 2*z)).expand().is_Order + + +def test_integral_rewrites(): #issues 26134, 26144, 26306 + assert expint(n, x).rewrite(Integral).dummy_eq(Integral(t**-n * exp(-t*x), (t, 1, oo))) + assert Si(x).rewrite(Integral).dummy_eq(Integral(sinc(t), (t, 0, x))) + assert Ci(x).rewrite(Integral).dummy_eq(log(x) - Integral((1 - cos(t))/t, (t, 0, x)) + EulerGamma) + assert fresnels(x).rewrite(Integral).dummy_eq(Integral(sin(pi*t**2/2), (t, 0, x))) + assert fresnelc(x).rewrite(Integral).dummy_eq(Integral(cos(pi*t**2/2), (t, 0, x))) + assert Ei(x).rewrite(Integral).dummy_eq(Integral(exp(t)/t, (t, -oo, x))) + assert fresnels(x).diff(x) == fresnels(x).rewrite(Integral).diff(x) + assert fresnelc(x).diff(x) == fresnelc(x).rewrite(Integral).diff(x) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_gamma_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_gamma_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..14c57a31ce2edaa60fd5efc8bcbc95668961fd41 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_gamma_functions.py @@ -0,0 +1,741 @@ +from sympy.core.function import expand_func, Subs +from sympy.core import EulerGamma +from sympy.core.numbers import (I, Rational, nan, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.combinatorial.numbers import harmonic +from sympy.functions.elementary.complexes import (Abs, conjugate, im, re) +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.hyperbolic import tanh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin, atan) +from sympy.functions.special.error_functions import (Ei, erf, erfc) +from sympy.functions.special.gamma_functions import (digamma, gamma, loggamma, lowergamma, multigamma, polygamma, trigamma, uppergamma) +from sympy.functions.special.zeta_functions import zeta +from sympy.series.order import O + +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.testing.pytest import raises +from sympy.core.random import (test_derivative_numerically as td, + random_complex_number as randcplx, + verify_numerically as tn) + +x = Symbol('x') +y = Symbol('y') +n = Symbol('n', integer=True) +w = Symbol('w', real=True) + +def test_gamma(): + assert gamma(nan) is nan + assert gamma(oo) is oo + + assert gamma(-100) is zoo + assert gamma(0) is zoo + assert gamma(-100.0) is zoo + + assert gamma(1) == 1 + assert gamma(2) == 1 + assert gamma(3) == 2 + + assert gamma(102) == factorial(101) + + assert gamma(S.Half) == sqrt(pi) + + assert gamma(Rational(3, 2)) == sqrt(pi)*S.Half + assert gamma(Rational(5, 2)) == sqrt(pi)*Rational(3, 4) + assert gamma(Rational(7, 2)) == sqrt(pi)*Rational(15, 8) + + assert gamma(Rational(-1, 2)) == -2*sqrt(pi) + assert gamma(Rational(-3, 2)) == sqrt(pi)*Rational(4, 3) + assert gamma(Rational(-5, 2)) == sqrt(pi)*Rational(-8, 15) + + assert gamma(Rational(-15, 2)) == sqrt(pi)*Rational(256, 2027025) + + assert gamma(Rational( + -11, 8)).expand(func=True) == Rational(64, 33)*gamma(Rational(5, 8)) + assert gamma(Rational( + -10, 3)).expand(func=True) == Rational(81, 280)*gamma(Rational(2, 3)) + assert gamma(Rational( + 14, 3)).expand(func=True) == Rational(880, 81)*gamma(Rational(2, 3)) + assert gamma(Rational( + 17, 7)).expand(func=True) == Rational(30, 49)*gamma(Rational(3, 7)) + assert gamma(Rational( + 19, 8)).expand(func=True) == Rational(33, 64)*gamma(Rational(3, 8)) + + assert gamma(x).diff(x) == gamma(x)*polygamma(0, x) + + assert gamma(x - 1).expand(func=True) == gamma(x)/(x - 1) + assert gamma(x + 2).expand(func=True, mul=False) == x*(x + 1)*gamma(x) + + assert conjugate(gamma(x)) == gamma(conjugate(x)) + + assert expand_func(gamma(x + Rational(3, 2))) == \ + (x + S.Half)*gamma(x + S.Half) + + assert expand_func(gamma(x - S.Half)) == \ + gamma(S.Half + x)/(x - S.Half) + + # Test a bug: + assert expand_func(gamma(x + Rational(3, 4))) == gamma(x + Rational(3, 4)) + + # XXX: Not sure about these tests. I can fix them by defining e.g. + # exp_polar.is_integer but I'm not sure if that makes sense. + assert gamma(3*exp_polar(I*pi)/4).is_nonnegative is False + assert gamma(3*exp_polar(I*pi)/4).is_extended_nonpositive is True + + y = Symbol('y', nonpositive=True, integer=True) + assert gamma(y).is_real == False + y = Symbol('y', positive=True, noninteger=True) + assert gamma(y).is_real == True + + assert gamma(-1.0, evaluate=False).is_real == False + assert gamma(0, evaluate=False).is_real == False + assert gamma(-2, evaluate=False).is_real == False + + +def test_gamma_rewrite(): + assert gamma(n).rewrite(factorial) == factorial(n - 1) + + +def test_gamma_series(): + assert gamma(x + 1).series(x, 0, 3) == \ + 1 - EulerGamma*x + x**2*(EulerGamma**2/2 + pi**2/12) + O(x**3) + assert gamma(x).series(x, -1, 3) == \ + -1/(x + 1) + EulerGamma - 1 + (x + 1)*(-1 - pi**2/12 - EulerGamma**2/2 + \ + EulerGamma) + (x + 1)**2*(-1 - pi**2/12 - EulerGamma**2/2 + EulerGamma**3/6 - \ + polygamma(2, 1)/6 + EulerGamma*pi**2/12 + EulerGamma) + O((x + 1)**3, (x, -1)) + + +def tn_branch(s, func): + from sympy.core.random import uniform + c = uniform(1, 5) + expr = func(s, c*exp_polar(I*pi)) - func(s, c*exp_polar(-I*pi)) + eps = 1e-15 + expr2 = func(s + eps, -c + eps*I) - func(s + eps, -c - eps*I) + return abs(expr.n() - expr2.n()).n() < 1e-10 + + +def test_lowergamma(): + from sympy.functions.special.error_functions import expint + from sympy.functions.special.hyper import meijerg + assert lowergamma(x, 0) == 0 + assert lowergamma(x, y).diff(y) == y**(x - 1)*exp(-y) + assert td(lowergamma(randcplx(), y), y) + assert td(lowergamma(x, randcplx()), x) + assert lowergamma(x, y).diff(x) == \ + gamma(x)*digamma(x) - uppergamma(x, y)*log(y) \ + - meijerg([], [1, 1], [0, 0, x], [], y) + + assert lowergamma(S.Half, x) == sqrt(pi)*erf(sqrt(x)) + assert not lowergamma(S.Half - 3, x).has(lowergamma) + assert not lowergamma(S.Half + 3, x).has(lowergamma) + assert lowergamma(S.Half, x, evaluate=False).has(lowergamma) + assert tn(lowergamma(S.Half + 3, x, evaluate=False), + lowergamma(S.Half + 3, x), x) + assert tn(lowergamma(S.Half - 3, x, evaluate=False), + lowergamma(S.Half - 3, x), x) + + assert tn_branch(-3, lowergamma) + assert tn_branch(-4, lowergamma) + assert tn_branch(Rational(1, 3), lowergamma) + assert tn_branch(pi, lowergamma) + assert lowergamma(3, exp_polar(4*pi*I)*x) == lowergamma(3, x) + assert lowergamma(y, exp_polar(5*pi*I)*x) == \ + exp(4*I*pi*y)*lowergamma(y, x*exp_polar(pi*I)) + assert lowergamma(-2, exp_polar(5*pi*I)*x) == \ + lowergamma(-2, x*exp_polar(I*pi)) + 2*pi*I + + assert conjugate(lowergamma(x, y)) == lowergamma(conjugate(x), conjugate(y)) + assert conjugate(lowergamma(x, 0)) == 0 + assert unchanged(conjugate, lowergamma(x, -oo)) + + assert lowergamma(0, x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(S(1)/3, x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(1, x, evaluate=False)._eval_is_meromorphic(x, 0) == True + assert lowergamma(x, x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(x + 1, x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(1/x, x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(0, x + 1)._eval_is_meromorphic(x, 0) == False + assert lowergamma(S(1)/3, x + 1)._eval_is_meromorphic(x, 0) == True + assert lowergamma(1, x + 1, evaluate=False)._eval_is_meromorphic(x, 0) == True + assert lowergamma(x, x + 1)._eval_is_meromorphic(x, 0) == True + assert lowergamma(x + 1, x + 1)._eval_is_meromorphic(x, 0) == True + assert lowergamma(1/x, x + 1)._eval_is_meromorphic(x, 0) == False + assert lowergamma(0, 1/x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(S(1)/3, 1/x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(1, 1/x, evaluate=False)._eval_is_meromorphic(x, 0) == False + assert lowergamma(x, 1/x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(x + 1, 1/x)._eval_is_meromorphic(x, 0) == False + assert lowergamma(1/x, 1/x)._eval_is_meromorphic(x, 0) == False + + assert lowergamma(x, 2).series(x, oo, 3) == \ + 2**x*(1 + 2/(x + 1))*exp(-2)/x + O(exp(x*log(2))/x**3, (x, oo)) + + assert lowergamma( + x, y).rewrite(expint) == -y**x*expint(-x + 1, y) + gamma(x) + k = Symbol('k', integer=True) + assert lowergamma( + k, y).rewrite(expint) == -y**k*expint(-k + 1, y) + gamma(k) + k = Symbol('k', integer=True, positive=False) + assert lowergamma(k, y).rewrite(expint) == lowergamma(k, y) + assert lowergamma(x, y).rewrite(uppergamma) == gamma(x) - uppergamma(x, y) + + assert lowergamma(70, 6) == factorial(69) - 69035724522603011058660187038367026272747334489677105069435923032634389419656200387949342530805432320 * exp(-6) + assert (lowergamma(S(77) / 2, 6) - lowergamma(S(77) / 2, 6, evaluate=False)).evalf() < 1e-16 + assert (lowergamma(-S(77) / 2, 6) - lowergamma(-S(77) / 2, 6, evaluate=False)).evalf() < 1e-16 + + +def test_uppergamma(): + from sympy.functions.special.error_functions import expint + from sympy.functions.special.hyper import meijerg + assert uppergamma(4, 0) == 6 + assert uppergamma(x, y).diff(y) == -y**(x - 1)*exp(-y) + assert td(uppergamma(randcplx(), y), y) + assert uppergamma(x, y).diff(x) == \ + uppergamma(x, y)*log(y) + meijerg([], [1, 1], [0, 0, x], [], y) + assert td(uppergamma(x, randcplx()), x) + + p = Symbol('p', positive=True) + assert uppergamma(0, p) == -Ei(-p) + assert uppergamma(p, 0) == gamma(p) + assert uppergamma(S.Half, x) == sqrt(pi)*erfc(sqrt(x)) + assert not uppergamma(S.Half - 3, x).has(uppergamma) + assert not uppergamma(S.Half + 3, x).has(uppergamma) + assert uppergamma(S.Half, x, evaluate=False).has(uppergamma) + assert tn(uppergamma(S.Half + 3, x, evaluate=False), + uppergamma(S.Half + 3, x), x) + assert tn(uppergamma(S.Half - 3, x, evaluate=False), + uppergamma(S.Half - 3, x), x) + + assert unchanged(uppergamma, x, -oo) + assert unchanged(uppergamma, x, 0) + + assert tn_branch(-3, uppergamma) + assert tn_branch(-4, uppergamma) + assert tn_branch(Rational(1, 3), uppergamma) + assert tn_branch(pi, uppergamma) + assert uppergamma(3, exp_polar(4*pi*I)*x) == uppergamma(3, x) + assert uppergamma(y, exp_polar(5*pi*I)*x) == \ + exp(4*I*pi*y)*uppergamma(y, x*exp_polar(pi*I)) + \ + gamma(y)*(1 - exp(4*pi*I*y)) + assert uppergamma(-2, exp_polar(5*pi*I)*x) == \ + uppergamma(-2, x*exp_polar(I*pi)) - 2*pi*I + + assert uppergamma(-2, x) == expint(3, x)/x**2 + + assert conjugate(uppergamma(x, y)) == uppergamma(conjugate(x), conjugate(y)) + assert unchanged(conjugate, uppergamma(x, -oo)) + + assert uppergamma(x, y).rewrite(expint) == y**x*expint(-x + 1, y) + assert uppergamma(x, y).rewrite(lowergamma) == gamma(x) - lowergamma(x, y) + + assert uppergamma(70, 6) == 69035724522603011058660187038367026272747334489677105069435923032634389419656200387949342530805432320*exp(-6) + assert (uppergamma(S(77) / 2, 6) - uppergamma(S(77) / 2, 6, evaluate=False)).evalf() < 1e-16 + assert (uppergamma(-S(77) / 2, 6) - uppergamma(-S(77) / 2, 6, evaluate=False)).evalf() < 1e-16 + + +def test_polygamma(): + assert polygamma(n, nan) is nan + + assert polygamma(0, oo) is oo + assert polygamma(0, -oo) is oo + assert polygamma(0, I*oo) is oo + assert polygamma(0, -I*oo) is oo + assert polygamma(1, oo) == 0 + assert polygamma(5, oo) == 0 + + assert polygamma(0, -9) is zoo + + assert polygamma(0, -9) is zoo + assert polygamma(0, -1) is zoo + assert polygamma(Rational(3, 2), -1) is zoo + + assert polygamma(0, 0) is zoo + + assert polygamma(0, 1) == -EulerGamma + assert polygamma(0, 7) == Rational(49, 20) - EulerGamma + + assert polygamma(1, 1) == pi**2/6 + assert polygamma(1, 2) == pi**2/6 - 1 + assert polygamma(1, 3) == pi**2/6 - Rational(5, 4) + assert polygamma(3, 1) == pi**4 / 15 + assert polygamma(3, 5) == 6*(Rational(-22369, 20736) + pi**4/90) + assert polygamma(5, 1) == 8 * pi**6 / 63 + + assert polygamma(1, S.Half) == pi**2 / 2 + assert polygamma(2, S.Half) == -14*zeta(3) + assert polygamma(11, S.Half) == 176896*pi**12 + + def t(m, n): + x = S(m)/n + r = polygamma(0, x) + if r.has(polygamma): + return False + return abs(polygamma(0, x.n()).n() - r.n()).n() < 1e-10 + assert t(1, 2) + assert t(3, 2) + assert t(-1, 2) + assert t(1, 4) + assert t(-3, 4) + assert t(1, 3) + assert t(4, 3) + assert t(3, 4) + assert t(2, 3) + assert t(123, 5) + + assert polygamma(0, x).rewrite(zeta) == polygamma(0, x) + assert polygamma(1, x).rewrite(zeta) == zeta(2, x) + assert polygamma(2, x).rewrite(zeta) == -2*zeta(3, x) + assert polygamma(I, 2).rewrite(zeta) == polygamma(I, 2) + n1 = Symbol('n1') + n2 = Symbol('n2', real=True) + n3 = Symbol('n3', integer=True) + n4 = Symbol('n4', positive=True) + n5 = Symbol('n5', positive=True, integer=True) + assert polygamma(n1, x).rewrite(zeta) == polygamma(n1, x) + assert polygamma(n2, x).rewrite(zeta) == polygamma(n2, x) + assert polygamma(n3, x).rewrite(zeta) == polygamma(n3, x) + assert polygamma(n4, x).rewrite(zeta) == polygamma(n4, x) + assert polygamma(n5, x).rewrite(zeta) == (-1)**(n5 + 1) * factorial(n5) * zeta(n5 + 1, x) + + assert polygamma(3, 7*x).diff(x) == 7*polygamma(4, 7*x) + + assert polygamma(0, x).rewrite(harmonic) == harmonic(x - 1) - EulerGamma + assert polygamma(2, x).rewrite(harmonic) == 2*harmonic(x - 1, 3) - 2*zeta(3) + ni = Symbol("n", integer=True) + assert polygamma(ni, x).rewrite(harmonic) == (-1)**(ni + 1)*(-harmonic(x - 1, ni + 1) + + zeta(ni + 1))*factorial(ni) + + # Polygamma of non-negative integer order is unbranched: + k = Symbol('n', integer=True, nonnegative=True) + assert polygamma(k, exp_polar(2*I*pi)*x) == polygamma(k, x) + + # but negative integers are branched! + k = Symbol('n', integer=True) + assert polygamma(k, exp_polar(2*I*pi)*x).args == (k, exp_polar(2*I*pi)*x) + + # Polygamma of order -1 is loggamma: + assert polygamma(-1, x) == loggamma(x) - log(2*pi) / 2 + + # But smaller orders are iterated integrals and don't have a special name + assert polygamma(-2, x).func is polygamma + + # Test a bug + assert polygamma(0, -x).expand(func=True) == polygamma(0, -x) + + assert polygamma(2, 2.5).is_positive == False + assert polygamma(2, -2.5).is_positive == False + assert polygamma(3, 2.5).is_positive == True + assert polygamma(3, -2.5).is_positive is True + assert polygamma(-2, -2.5).is_positive is None + assert polygamma(-3, -2.5).is_positive is None + + assert polygamma(2, 2.5).is_negative == True + assert polygamma(3, 2.5).is_negative == False + assert polygamma(3, -2.5).is_negative == False + assert polygamma(2, -2.5).is_negative is True + assert polygamma(-2, -2.5).is_negative is None + assert polygamma(-3, -2.5).is_negative is None + + assert polygamma(I, 2).is_positive is None + assert polygamma(I, 3).is_negative is None + + # issue 17350 + assert (I*polygamma(I, pi)).as_real_imag() == \ + (-im(polygamma(I, pi)), re(polygamma(I, pi))) + assert (tanh(polygamma(I, 1))).rewrite(exp) == \ + (exp(polygamma(I, 1)) - exp(-polygamma(I, 1)))/(exp(polygamma(I, 1)) + exp(-polygamma(I, 1))) + assert (I / polygamma(I, 4)).rewrite(exp) == \ + I*exp(-I*atan(im(polygamma(I, 4))/re(polygamma(I, 4))))/Abs(polygamma(I, 4)) + + # issue 12569 + assert unchanged(im, polygamma(0, I)) + assert polygamma(Symbol('a', positive=True), Symbol('b', positive=True)).is_real is True + assert polygamma(0, I).is_real is None + + assert str(polygamma(pi, 3).evalf(n=10)) == "0.1169314564" + assert str(polygamma(2.3, 1.0).evalf(n=10)) == "-3.003302909" + assert str(polygamma(-1, 1).evalf(n=10)) == "-0.9189385332" # not zero + assert str(polygamma(I, 1).evalf(n=10)) == "-3.109856569 + 1.89089016*I" + assert str(polygamma(1, I).evalf(n=10)) == "-0.5369999034 - 0.7942335428*I" + assert str(polygamma(I, I).evalf(n=10)) == "6.332362889 + 45.92828268*I" + + +def test_polygamma_expand_func(): + assert polygamma(0, x).expand(func=True) == polygamma(0, x) + assert polygamma(0, 2*x).expand(func=True) == \ + polygamma(0, x)/2 + polygamma(0, S.Half + x)/2 + log(2) + assert polygamma(1, 2*x).expand(func=True) == \ + polygamma(1, x)/4 + polygamma(1, S.Half + x)/4 + assert polygamma(2, x).expand(func=True) == \ + polygamma(2, x) + assert polygamma(0, -1 + x).expand(func=True) == \ + polygamma(0, x) - 1/(x - 1) + assert polygamma(0, 1 + x).expand(func=True) == \ + 1/x + polygamma(0, x ) + assert polygamma(0, 2 + x).expand(func=True) == \ + 1/x + 1/(1 + x) + polygamma(0, x) + assert polygamma(0, 3 + x).expand(func=True) == \ + polygamma(0, x) + 1/x + 1/(1 + x) + 1/(2 + x) + assert polygamma(0, 4 + x).expand(func=True) == \ + polygamma(0, x) + 1/x + 1/(1 + x) + 1/(2 + x) + 1/(3 + x) + assert polygamma(1, 1 + x).expand(func=True) == \ + polygamma(1, x) - 1/x**2 + assert polygamma(1, 2 + x).expand(func=True, multinomial=False) == \ + polygamma(1, x) - 1/x**2 - 1/(1 + x)**2 + assert polygamma(1, 3 + x).expand(func=True, multinomial=False) == \ + polygamma(1, x) - 1/x**2 - 1/(1 + x)**2 - 1/(2 + x)**2 + assert polygamma(1, 4 + x).expand(func=True, multinomial=False) == \ + polygamma(1, x) - 1/x**2 - 1/(1 + x)**2 - \ + 1/(2 + x)**2 - 1/(3 + x)**2 + assert polygamma(0, x + y).expand(func=True) == \ + polygamma(0, x + y) + assert polygamma(1, x + y).expand(func=True) == \ + polygamma(1, x + y) + assert polygamma(1, 3 + 4*x + y).expand(func=True, multinomial=False) == \ + polygamma(1, y + 4*x) - 1/(y + 4*x)**2 - \ + 1/(1 + y + 4*x)**2 - 1/(2 + y + 4*x)**2 + assert polygamma(3, 3 + 4*x + y).expand(func=True, multinomial=False) == \ + polygamma(3, y + 4*x) - 6/(y + 4*x)**4 - \ + 6/(1 + y + 4*x)**4 - 6/(2 + y + 4*x)**4 + assert polygamma(3, 4*x + y + 1).expand(func=True, multinomial=False) == \ + polygamma(3, y + 4*x) - 6/(y + 4*x)**4 + e = polygamma(3, 4*x + y + Rational(3, 2)) + assert e.expand(func=True) == e + e = polygamma(3, x + y + Rational(3, 4)) + assert e.expand(func=True, basic=False) == e + + assert polygamma(-1, x, evaluate=False).expand(func=True) == \ + loggamma(x) - log(pi)/2 - log(2)/2 + p2 = polygamma(-2, x).expand(func=True) + x**2/2 - x/2 + S(1)/12 + assert isinstance(p2, Subs) + assert p2.point == (-1,) + + +def test_digamma(): + assert digamma(nan) == nan + + assert digamma(oo) == oo + assert digamma(-oo) == oo + assert digamma(I*oo) == oo + assert digamma(-I*oo) == oo + + assert digamma(-9) == zoo + + assert digamma(-9) == zoo + assert digamma(-1) == zoo + + assert digamma(0) == zoo + + assert digamma(1) == -EulerGamma + assert digamma(7) == Rational(49, 20) - EulerGamma + + def t(m, n): + x = S(m)/n + r = digamma(x) + if r.has(digamma): + return False + return abs(digamma(x.n()).n() - r.n()).n() < 1e-10 + assert t(1, 2) + assert t(3, 2) + assert t(-1, 2) + assert t(1, 4) + assert t(-3, 4) + assert t(1, 3) + assert t(4, 3) + assert t(3, 4) + assert t(2, 3) + assert t(123, 5) + + assert digamma(x).rewrite(zeta) == polygamma(0, x) + + assert digamma(x).rewrite(harmonic) == harmonic(x - 1) - EulerGamma + + assert digamma(I).is_real is None + + assert digamma(x,evaluate=False).fdiff() == polygamma(1, x) + + assert digamma(x,evaluate=False).is_real is None + + assert digamma(x,evaluate=False).is_positive is None + + assert digamma(x,evaluate=False).is_negative is None + + assert digamma(x,evaluate=False).rewrite(polygamma) == polygamma(0, x) + + +def test_digamma_expand_func(): + assert digamma(x).expand(func=True) == polygamma(0, x) + assert digamma(2*x).expand(func=True) == \ + polygamma(0, x)/2 + polygamma(0, Rational(1, 2) + x)/2 + log(2) + assert digamma(-1 + x).expand(func=True) == \ + polygamma(0, x) - 1/(x - 1) + assert digamma(1 + x).expand(func=True) == \ + 1/x + polygamma(0, x ) + assert digamma(2 + x).expand(func=True) == \ + 1/x + 1/(1 + x) + polygamma(0, x) + assert digamma(3 + x).expand(func=True) == \ + polygamma(0, x) + 1/x + 1/(1 + x) + 1/(2 + x) + assert digamma(4 + x).expand(func=True) == \ + polygamma(0, x) + 1/x + 1/(1 + x) + 1/(2 + x) + 1/(3 + x) + assert digamma(x + y).expand(func=True) == \ + polygamma(0, x + y) + +def test_trigamma(): + assert trigamma(nan) == nan + + assert trigamma(oo) == 0 + + assert trigamma(1) == pi**2/6 + assert trigamma(2) == pi**2/6 - 1 + assert trigamma(3) == pi**2/6 - Rational(5, 4) + + assert trigamma(x, evaluate=False).rewrite(zeta) == zeta(2, x) + assert trigamma(x, evaluate=False).rewrite(harmonic) == \ + trigamma(x).rewrite(polygamma).rewrite(harmonic) + + assert trigamma(x,evaluate=False).fdiff() == polygamma(2, x) + + assert trigamma(x,evaluate=False).is_real is None + + assert trigamma(x,evaluate=False).is_positive is None + + assert trigamma(x,evaluate=False).is_negative is None + + assert trigamma(x,evaluate=False).rewrite(polygamma) == polygamma(1, x) + +def test_trigamma_expand_func(): + assert trigamma(2*x).expand(func=True) == \ + polygamma(1, x)/4 + polygamma(1, Rational(1, 2) + x)/4 + assert trigamma(1 + x).expand(func=True) == \ + polygamma(1, x) - 1/x**2 + assert trigamma(2 + x).expand(func=True, multinomial=False) == \ + polygamma(1, x) - 1/x**2 - 1/(1 + x)**2 + assert trigamma(3 + x).expand(func=True, multinomial=False) == \ + polygamma(1, x) - 1/x**2 - 1/(1 + x)**2 - 1/(2 + x)**2 + assert trigamma(4 + x).expand(func=True, multinomial=False) == \ + polygamma(1, x) - 1/x**2 - 1/(1 + x)**2 - \ + 1/(2 + x)**2 - 1/(3 + x)**2 + assert trigamma(x + y).expand(func=True) == \ + polygamma(1, x + y) + assert trigamma(3 + 4*x + y).expand(func=True, multinomial=False) == \ + polygamma(1, y + 4*x) - 1/(y + 4*x)**2 - \ + 1/(1 + y + 4*x)**2 - 1/(2 + y + 4*x)**2 + +def test_loggamma(): + raises(TypeError, lambda: loggamma(2, 3)) + raises(ArgumentIndexError, lambda: loggamma(x).fdiff(2)) + + assert loggamma(-1) is oo + assert loggamma(-2) is oo + assert loggamma(0) is oo + assert loggamma(1) == 0 + assert loggamma(2) == 0 + assert loggamma(3) == log(2) + assert loggamma(4) == log(6) + + n = Symbol("n", integer=True, positive=True) + assert loggamma(n) == log(gamma(n)) + assert loggamma(-n) is oo + assert loggamma(n/2) == log(2**(-n + 1)*sqrt(pi)*gamma(n)/gamma(n/2 + S.Half)) + + assert loggamma(oo) is oo + assert loggamma(-oo) is zoo + assert loggamma(I*oo) is zoo + assert loggamma(-I*oo) is zoo + assert loggamma(zoo) is zoo + assert loggamma(nan) is nan + + L = loggamma(Rational(16, 3)) + E = -5*log(3) + loggamma(Rational(1, 3)) + log(4) + log(7) + log(10) + log(13) + assert expand_func(L).doit() == E + assert L.n() == E.n() + + L = loggamma(Rational(19, 4)) + E = -4*log(4) + loggamma(Rational(3, 4)) + log(3) + log(7) + log(11) + log(15) + assert expand_func(L).doit() == E + assert L.n() == E.n() + + L = loggamma(Rational(23, 7)) + E = -3*log(7) + log(2) + loggamma(Rational(2, 7)) + log(9) + log(16) + assert expand_func(L).doit() == E + assert L.n() == E.n() + + L = loggamma(Rational(19, 4) - 7) + E = -log(9) - log(5) + loggamma(Rational(3, 4)) + 3*log(4) - 3*I*pi + assert expand_func(L).doit() == E + assert L.n() == E.n() + + L = loggamma(Rational(23, 7) - 6) + E = -log(19) - log(12) - log(5) + loggamma(Rational(2, 7)) + 3*log(7) - 3*I*pi + assert expand_func(L).doit() == E + assert L.n() == E.n() + + assert loggamma(x).diff(x) == polygamma(0, x) + s1 = loggamma(1/(x + sin(x)) + cos(x)).nseries(x, n=4) + s2 = (-log(2*x) - 1)/(2*x) - log(x/pi)/2 + (4 - log(2*x))*x/24 + O(x**2) + \ + log(x)*x**2/2 + assert (s1 - s2).expand(force=True).removeO() == 0 + s1 = loggamma(1/x).series(x) + s2 = (1/x - S.Half)*log(1/x) - 1/x + log(2*pi)/2 + \ + x/12 - x**3/360 + x**5/1260 + O(x**7) + assert ((s1 - s2).expand(force=True)).removeO() == 0 + + assert loggamma(x).rewrite('intractable') == log(gamma(x)) + + s1 = loggamma(x).series(x).cancel() + assert s1 == -log(x) - EulerGamma*x + pi**2*x**2/12 + x**3*polygamma(2, 1)/6 + \ + pi**4*x**4/360 + x**5*polygamma(4, 1)/120 + O(x**6) + assert s1 == loggamma(x).rewrite('intractable').series(x).cancel() + + assert conjugate(loggamma(x)) == loggamma(conjugate(x)) + assert conjugate(loggamma(0)) is oo + assert conjugate(loggamma(1)) == loggamma(conjugate(1)) + assert conjugate(loggamma(-oo)) == conjugate(zoo) + + assert loggamma(Symbol('v', positive=True)).is_real is True + assert loggamma(Symbol('v', zero=True)).is_real is False + assert loggamma(Symbol('v', negative=True)).is_real is False + assert loggamma(Symbol('v', nonpositive=True)).is_real is False + assert loggamma(Symbol('v', nonnegative=True)).is_real is None + assert loggamma(Symbol('v', imaginary=True)).is_real is None + assert loggamma(Symbol('v', real=True)).is_real is None + assert loggamma(Symbol('v')).is_real is None + + assert loggamma(S.Half).is_real is True + assert loggamma(0).is_real is False + assert loggamma(Rational(-1, 2)).is_real is False + assert loggamma(I).is_real is None + assert loggamma(2 + 3*I).is_real is None + + def tN(N, M): + assert loggamma(1/x)._eval_nseries(x, n=N).getn() == M + tN(0, 0) + tN(1, 1) + tN(2, 2) + tN(3, 3) + tN(4, 4) + tN(5, 5) + + +def test_polygamma_expansion(): + # A. & S., pa. 259 and 260 + assert polygamma(0, 1/x).nseries(x, n=3) == \ + -log(x) - x/2 - x**2/12 + O(x**3) + assert polygamma(1, 1/x).series(x, n=5) == \ + x + x**2/2 + x**3/6 + O(x**5) + assert polygamma(3, 1/x).nseries(x, n=11) == \ + 2*x**3 + 3*x**4 + 2*x**5 - x**7 + 4*x**9/3 + O(x**11) + + +def test_polygamma_leading_term(): + expr = -log(1/x) + polygamma(0, 1 + 1/x) + S.EulerGamma + assert expr.as_leading_term(x, logx=-y) == S.EulerGamma + + +def test_issue_8657(): + n = Symbol('n', negative=True, integer=True) + m = Symbol('m', integer=True) + o = Symbol('o', positive=True) + p = Symbol('p', negative=True, integer=False) + assert gamma(n).is_real is False + assert gamma(m).is_real is None + assert gamma(o).is_real is True + assert gamma(p).is_real is True + assert gamma(w).is_real is None + + +def test_issue_8524(): + x = Symbol('x', positive=True) + y = Symbol('y', negative=True) + z = Symbol('z', positive=False) + p = Symbol('p', negative=False) + q = Symbol('q', integer=True) + r = Symbol('r', integer=False) + e = Symbol('e', even=True, negative=True) + assert gamma(x).is_positive is True + assert gamma(y).is_positive is None + assert gamma(z).is_positive is None + assert gamma(p).is_positive is None + assert gamma(q).is_positive is None + assert gamma(r).is_positive is None + assert gamma(e + S.Half).is_positive is True + assert gamma(e - S.Half).is_positive is False + +def test_issue_14450(): + assert uppergamma(Rational(3, 8), x).evalf() == uppergamma(Rational(3, 8), x) + assert lowergamma(x, Rational(3, 8)).evalf() == lowergamma(x, Rational(3, 8)) + # some values from Wolfram Alpha for comparison + assert abs(uppergamma(Rational(3, 8), 2).evalf() - 0.07105675881) < 1e-9 + assert abs(lowergamma(Rational(3, 8), 2).evalf() - 2.2993794256) < 1e-9 + +def test_issue_14528(): + k = Symbol('k', integer=True, nonpositive=True) + assert isinstance(gamma(k), gamma) + +def test_multigamma(): + from sympy.concrete.products import Product + p = Symbol('p') + _k = Dummy('_k') + + assert multigamma(x, p).dummy_eq(pi**(p*(p - 1)/4)*\ + Product(gamma(x + (1 - _k)/2), (_k, 1, p))) + + assert conjugate(multigamma(x, p)).dummy_eq(pi**((conjugate(p) - 1)*\ + conjugate(p)/4)*Product(gamma(conjugate(x) + (1-conjugate(_k))/2), (_k, 1, p))) + assert conjugate(multigamma(x, 1)) == gamma(conjugate(x)) + + p = Symbol('p', positive=True) + assert conjugate(multigamma(x, p)).dummy_eq(pi**((p - 1)*p/4)*\ + Product(gamma(conjugate(x) + (1-conjugate(_k))/2), (_k, 1, p))) + + assert multigamma(nan, 1) is nan + assert multigamma(oo, 1).doit() is oo + + assert multigamma(1, 1) == 1 + assert multigamma(2, 1) == 1 + assert multigamma(3, 1) == 2 + + assert multigamma(102, 1) == factorial(101) + assert multigamma(S.Half, 1) == sqrt(pi) + + assert multigamma(1, 2) == pi + assert multigamma(2, 2) == pi/2 + + assert multigamma(1, 3) is zoo + assert multigamma(2, 3) == pi**2/2 + assert multigamma(3, 3) == 3*pi**2/2 + + assert multigamma(x, 1).diff(x) == gamma(x)*polygamma(0, x) + assert multigamma(x, 2).diff(x) == sqrt(pi)*gamma(x)*gamma(x - S.Half)*\ + polygamma(0, x) + sqrt(pi)*gamma(x)*gamma(x - S.Half)*polygamma(0, x - S.Half) + + assert multigamma(x - 1, 1).expand(func=True) == gamma(x)/(x - 1) + assert multigamma(x + 2, 1).expand(func=True, mul=False) == x*(x + 1)*\ + gamma(x) + assert multigamma(x - 1, 2).expand(func=True) == sqrt(pi)*gamma(x)*\ + gamma(x + S.Half)/(x**3 - 3*x**2 + x*Rational(11, 4) - Rational(3, 4)) + assert multigamma(x - 1, 3).expand(func=True) == pi**Rational(3, 2)*gamma(x)**2*\ + gamma(x + S.Half)/(x**5 - 6*x**4 + 55*x**3/4 - 15*x**2 + x*Rational(31, 4) - Rational(3, 2)) + + assert multigamma(n, 1).rewrite(factorial) == factorial(n - 1) + assert multigamma(n, 2).rewrite(factorial) == sqrt(pi)*\ + factorial(n - Rational(3, 2))*factorial(n - 1) + assert multigamma(n, 3).rewrite(factorial) == pi**Rational(3, 2)*\ + factorial(n - 2)*factorial(n - Rational(3, 2))*factorial(n - 1) + + assert multigamma(Rational(-1, 2), 3, evaluate=False).is_real == False + assert multigamma(S.Half, 3, evaluate=False).is_real == False + assert multigamma(0, 1, evaluate=False).is_real == False + assert multigamma(1, 3, evaluate=False).is_real == False + assert multigamma(-1.0, 3, evaluate=False).is_real == False + assert multigamma(0.7, 3, evaluate=False).is_real == True + assert multigamma(3, 3, evaluate=False).is_real == True + +def test_gamma_as_leading_term(): + assert gamma(x).as_leading_term(x) == 1/x + assert gamma(2 + x).as_leading_term(x) == S(1) + assert gamma(cos(x)).as_leading_term(x) == S(1) + assert gamma(sin(x)).as_leading_term(x) == 1/x diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_hyper.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_hyper.py new file mode 100644 index 0000000000000000000000000000000000000000..f1be5b5f0db158ff76173e180ed8d88bd59461b9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_hyper.py @@ -0,0 +1,403 @@ +from sympy.core.containers import Tuple +from sympy.core.function import Derivative +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import cos +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import (appellf1, hyper, meijerg) +from sympy.series.order import O +from sympy.abc import x, z, k +from sympy.series.limits import limit +from sympy.testing.pytest import raises, slow +from sympy.core.random import ( + random_complex_number as randcplx, + verify_numerically as tn, + test_derivative_numerically as td) + + +def test_TupleParametersBase(): + # test that our implementation of the chain rule works + p = hyper((), (), z**2) + assert p.diff(z) == p*2*z + + +def test_hyper(): + raises(TypeError, lambda: hyper(1, 2, z)) + + assert hyper((2, 1), (1,), z) == hyper(Tuple(1, 2), Tuple(1), z) + assert hyper((2, 1, 2), (1, 2, 1, 3), z) == hyper((2,), (1, 3), z) + u = hyper((2, 1, 2), (1, 2, 1, 3), z, evaluate=False) + assert u.ap == Tuple(1, 2, 2) + assert u.bq == Tuple(1, 1, 2, 3) + + h = hyper((1, 2), (3, 4, 5), z) + assert h.ap == Tuple(1, 2) + assert h.bq == Tuple(3, 4, 5) + assert h.argument == z + assert h.is_commutative is True + h = hyper((2, 1), (4, 3, 5), z) + assert h.ap == Tuple(1, 2) + assert h.bq == Tuple(3, 4, 5) + assert h.argument == z + assert h.is_commutative is True + + # just a few checks to make sure that all arguments go where they should + assert tn(hyper(Tuple(), Tuple(), z), exp(z), z) + assert tn(z*hyper((1, 1), Tuple(2), -z), log(1 + z), z) + + # differentiation + h = hyper( + (randcplx(), randcplx(), randcplx()), (randcplx(), randcplx()), z) + assert td(h, z) + + a1, a2, b1, b2, b3 = symbols('a1:3, b1:4') + assert hyper((a1, a2), (b1, b2, b3), z).diff(z) == \ + a1*a2/(b1*b2*b3) * hyper((a1 + 1, a2 + 1), (b1 + 1, b2 + 1, b3 + 1), z) + + # differentiation wrt parameters is not supported + assert hyper([z], [], z).diff(z) == Derivative(hyper([z], [], z), z) + + # hyper is unbranched wrt parameters + from sympy.functions.elementary.complexes import polar_lift + assert hyper([polar_lift(z)], [polar_lift(k)], polar_lift(x)) == \ + hyper([z], [k], polar_lift(x)) + + # hyper does not automatically evaluate anyway, but the test is to make + # sure that the evaluate keyword is accepted + assert hyper((1, 2), (1,), z, evaluate=False).func is hyper + + +def test_expand_func(): + # evaluation at 1 of Gauss' hypergeometric function: + from sympy.abc import a, b, c + from sympy.core.function import expand_func + a1, b1, c1 = randcplx(), randcplx(), randcplx() + 5 + assert expand_func(hyper([a, b], [c], 1)) == \ + gamma(c)*gamma(-a - b + c)/(gamma(-a + c)*gamma(-b + c)) + assert abs(expand_func(hyper([a1, b1], [c1], 1)).n() + - hyper([a1, b1], [c1], 1).n()) < 1e-10 + + # hyperexpand wrapper for hyper: + assert expand_func(hyper([], [], z)) == exp(z) + assert expand_func(hyper([1, 2, 3], [], z)) == hyper([1, 2, 3], [], z) + assert expand_func(meijerg([[1, 1], []], [[1], [0]], z)) == log(z + 1) + assert expand_func(meijerg([[1, 1], []], [[], []], z)) == \ + meijerg([[1, 1], []], [[], []], z) + + +def replace_dummy(expr, sym): + from sympy.core.symbol import Dummy + dum = expr.atoms(Dummy) + if not dum: + return expr + assert len(dum) == 1 + return expr.xreplace({dum.pop(): sym}) + + +def test_hyper_rewrite_sum(): + from sympy.concrete.summations import Sum + from sympy.core.symbol import Dummy + from sympy.functions.combinatorial.factorials import (RisingFactorial, factorial) + _k = Dummy("k") + assert replace_dummy(hyper((1, 2), (1, 3), x).rewrite(Sum), _k) == \ + Sum(x**_k / factorial(_k) * RisingFactorial(2, _k) / + RisingFactorial(3, _k), (_k, 0, oo)) + + assert hyper((1, 2, 3), (-1, 3), z).rewrite(Sum) == \ + hyper((1, 2, 3), (-1, 3), z) + + +def test_radius_of_convergence(): + assert hyper((1, 2), [3], z).radius_of_convergence == 1 + assert hyper((1, 2), [3, 4], z).radius_of_convergence is oo + assert hyper((1, 2, 3), [4], z).radius_of_convergence == 0 + assert hyper((0, 1, 2), [4], z).radius_of_convergence is oo + assert hyper((-1, 1, 2), [-4], z).radius_of_convergence == 0 + assert hyper((-1, -2, 2), [-1], z).radius_of_convergence is oo + assert hyper((-1, 2), [-1, -2], z).radius_of_convergence == 0 + assert hyper([-1, 1, 3], [-2, 2], z).radius_of_convergence == 1 + assert hyper([-1, 1], [-2, 2], z).radius_of_convergence is oo + assert hyper([-1, 1, 3], [-2], z).radius_of_convergence == 0 + assert hyper((-1, 2, 3, 4), [], z).radius_of_convergence is oo + + assert hyper([1, 1], [3], 1).convergence_statement == True + assert hyper([1, 1], [2], 1).convergence_statement == False + assert hyper([1, 1], [2], -1).convergence_statement == True + assert hyper([1, 1], [1], -1).convergence_statement == False + + +def test_meijer(): + raises(TypeError, lambda: meijerg(1, z)) + raises(TypeError, lambda: meijerg(((1,), (2,)), (3,), (4,), z)) + + assert meijerg(((1, 2), (3,)), ((4,), (5,)), z) == \ + meijerg(Tuple(1, 2), Tuple(3), Tuple(4), Tuple(5), z) + + g = meijerg((1, 2), (3, 4, 5), (6, 7, 8, 9), (10, 11, 12, 13, 14), z) + assert g.an == Tuple(1, 2) + assert g.ap == Tuple(1, 2, 3, 4, 5) + assert g.aother == Tuple(3, 4, 5) + assert g.bm == Tuple(6, 7, 8, 9) + assert g.bq == Tuple(6, 7, 8, 9, 10, 11, 12, 13, 14) + assert g.bother == Tuple(10, 11, 12, 13, 14) + assert g.argument == z + assert g.nu == 75 + assert g.delta == -1 + assert g.is_commutative is True + assert g.is_number is False + #issue 13071 + assert meijerg([[],[]], [[S.Half],[0]], 1).is_number is True + + assert meijerg([1, 2], [3], [4], [5], z).delta == S.Half + + # just a few checks to make sure that all arguments go where they should + assert tn(meijerg(Tuple(), Tuple(), Tuple(0), Tuple(), -z), exp(z), z) + assert tn(sqrt(pi)*meijerg(Tuple(), Tuple(), + Tuple(0), Tuple(S.Half), z**2/4), cos(z), z) + assert tn(meijerg(Tuple(1, 1), Tuple(), Tuple(1), Tuple(0), z), + log(1 + z), z) + + # test exceptions + raises(ValueError, lambda: meijerg(((3, 1), (2,)), ((oo,), (2, 0)), x)) + raises(ValueError, lambda: meijerg(((3, 1), (2,)), ((1,), (2, 0)), x)) + + # differentiation + g = meijerg((randcplx(),), (randcplx() + 2*I,), Tuple(), + (randcplx(), randcplx()), z) + assert td(g, z) + + g = meijerg(Tuple(), (randcplx(),), Tuple(), + (randcplx(), randcplx()), z) + assert td(g, z) + + g = meijerg(Tuple(), Tuple(), Tuple(randcplx()), + Tuple(randcplx(), randcplx()), z) + assert td(g, z) + + a1, a2, b1, b2, c1, c2, d1, d2 = symbols('a1:3, b1:3, c1:3, d1:3') + assert meijerg((a1, a2), (b1, b2), (c1, c2), (d1, d2), z).diff(z) == \ + (meijerg((a1 - 1, a2), (b1, b2), (c1, c2), (d1, d2), z) + + (a1 - 1)*meijerg((a1, a2), (b1, b2), (c1, c2), (d1, d2), z))/z + + assert meijerg([z, z], [], [], [], z).diff(z) == \ + Derivative(meijerg([z, z], [], [], [], z), z) + + # meijerg is unbranched wrt parameters + from sympy.functions.elementary.complexes import polar_lift as pl + assert meijerg([pl(a1)], [pl(a2)], [pl(b1)], [pl(b2)], pl(z)) == \ + meijerg([a1], [a2], [b1], [b2], pl(z)) + + # integrand + from sympy.abc import a, b, c, d, s + assert meijerg([a], [b], [c], [d], z).integrand(s) == \ + z**s*gamma(c - s)*gamma(-a + s + 1)/(gamma(b - s)*gamma(-d + s + 1)) + + +def test_meijerg_derivative(): + assert meijerg([], [1, 1], [0, 0, x], [], z).diff(x) == \ + log(z)*meijerg([], [1, 1], [0, 0, x], [], z) \ + + 2*meijerg([], [1, 1, 1], [0, 0, x, 0], [], z) + + y = randcplx() + a = 5 # mpmath chokes with non-real numbers, and Mod1 with floats + assert td(meijerg([x], [], [], [], y), x) + assert td(meijerg([x**2], [], [], [], y), x) + assert td(meijerg([], [x], [], [], y), x) + assert td(meijerg([], [], [x], [], y), x) + assert td(meijerg([], [], [], [x], y), x) + assert td(meijerg([x], [a], [a + 1], [], y), x) + assert td(meijerg([x], [a + 1], [a], [], y), x) + assert td(meijerg([x, a], [], [], [a + 1], y), x) + assert td(meijerg([x, a + 1], [], [], [a], y), x) + b = Rational(3, 2) + assert td(meijerg([a + 2], [b], [b - 3, x], [a], y), x) + + +def test_meijerg_period(): + assert meijerg([], [1], [0], [], x).get_period() == 2*pi + assert meijerg([1], [], [], [0], x).get_period() == 2*pi + assert meijerg([], [], [0], [], x).get_period() == 2*pi # exp(x) + assert meijerg( + [], [], [0], [S.Half], x).get_period() == 2*pi # cos(sqrt(x)) + assert meijerg( + [], [], [S.Half], [0], x).get_period() == 4*pi # sin(sqrt(x)) + assert meijerg([1, 1], [], [1], [0], x).get_period() is oo # log(1 + x) + + +def test_hyper_unpolarify(): + from sympy.functions.elementary.exponential import exp_polar + a = exp_polar(2*pi*I)*x + b = x + assert hyper([], [], a).argument == b + assert hyper([0], [], a).argument == a + assert hyper([0], [0], a).argument == b + assert hyper([0, 1], [0], a).argument == a + assert hyper([0, 1], [0], exp_polar(2*pi*I)).argument == 1 + + +@slow +def test_hyperrep(): + from sympy.functions.special.hyper import (HyperRep, HyperRep_atanh, + HyperRep_power1, HyperRep_power2, HyperRep_log1, HyperRep_asin1, + HyperRep_asin2, HyperRep_sqrts1, HyperRep_sqrts2, HyperRep_log2, + HyperRep_cosasin, HyperRep_sinasin) + # First test the base class works. + from sympy.functions.elementary.exponential import exp_polar + from sympy.functions.elementary.piecewise import Piecewise + a, b, c, d, z = symbols('a b c d z') + + class myrep(HyperRep): + @classmethod + def _expr_small(cls, x): + return a + + @classmethod + def _expr_small_minus(cls, x): + return b + + @classmethod + def _expr_big(cls, x, n): + return c*n + + @classmethod + def _expr_big_minus(cls, x, n): + return d*n + assert myrep(z).rewrite('nonrep') == Piecewise((0, abs(z) > 1), (a, True)) + assert myrep(exp_polar(I*pi)*z).rewrite('nonrep') == \ + Piecewise((0, abs(z) > 1), (b, True)) + assert myrep(exp_polar(2*I*pi)*z).rewrite('nonrep') == \ + Piecewise((c, abs(z) > 1), (a, True)) + assert myrep(exp_polar(3*I*pi)*z).rewrite('nonrep') == \ + Piecewise((d, abs(z) > 1), (b, True)) + assert myrep(exp_polar(4*I*pi)*z).rewrite('nonrep') == \ + Piecewise((2*c, abs(z) > 1), (a, True)) + assert myrep(exp_polar(5*I*pi)*z).rewrite('nonrep') == \ + Piecewise((2*d, abs(z) > 1), (b, True)) + assert myrep(z).rewrite('nonrepsmall') == a + assert myrep(exp_polar(I*pi)*z).rewrite('nonrepsmall') == b + + def t(func, hyp, z): + """ Test that func is a valid representation of hyp. """ + # First test that func agrees with hyp for small z + if not tn(func.rewrite('nonrepsmall'), hyp, z, + a=Rational(-1, 2), b=Rational(-1, 2), c=S.Half, d=S.Half): + return False + # Next check that the two small representations agree. + if not tn( + func.rewrite('nonrepsmall').subs( + z, exp_polar(I*pi)*z).replace(exp_polar, exp), + func.subs(z, exp_polar(I*pi)*z).rewrite('nonrepsmall'), + z, a=Rational(-1, 2), b=Rational(-1, 2), c=S.Half, d=S.Half): + return False + # Next check continuity along exp_polar(I*pi)*t + expr = func.subs(z, exp_polar(I*pi)*z).rewrite('nonrep') + if abs(expr.subs(z, 1 + 1e-15).n() - expr.subs(z, 1 - 1e-15).n()) > 1e-10: + return False + # Finally check continuity of the big reps. + + def dosubs(func, a, b): + rv = func.subs(z, exp_polar(a)*z).rewrite('nonrep') + return rv.subs(z, exp_polar(b)*z).replace(exp_polar, exp) + for n in [0, 1, 2, 3, 4, -1, -2, -3, -4]: + expr1 = dosubs(func, 2*I*pi*n, I*pi/2) + expr2 = dosubs(func, 2*I*pi*n + I*pi, -I*pi/2) + if not tn(expr1, expr2, z): + return False + expr1 = dosubs(func, 2*I*pi*(n + 1), -I*pi/2) + expr2 = dosubs(func, 2*I*pi*n + I*pi, I*pi/2) + if not tn(expr1, expr2, z): + return False + return True + + # Now test the various representatives. + a = Rational(1, 3) + assert t(HyperRep_atanh(z), hyper([S.Half, 1], [Rational(3, 2)], z), z) + assert t(HyperRep_power1(a, z), hyper([-a], [], z), z) + assert t(HyperRep_power2(a, z), hyper([a, a - S.Half], [2*a], z), z) + assert t(HyperRep_log1(z), -z*hyper([1, 1], [2], z), z) + assert t(HyperRep_asin1(z), hyper([S.Half, S.Half], [Rational(3, 2)], z), z) + assert t(HyperRep_asin2(z), hyper([1, 1], [Rational(3, 2)], z), z) + assert t(HyperRep_sqrts1(a, z), hyper([-a, S.Half - a], [S.Half], z), z) + assert t(HyperRep_sqrts2(a, z), + -2*z/(2*a + 1)*hyper([-a - S.Half, -a], [S.Half], z).diff(z), z) + assert t(HyperRep_log2(z), -z/4*hyper([Rational(3, 2), 1, 1], [2, 2], z), z) + assert t(HyperRep_cosasin(a, z), hyper([-a, a], [S.Half], z), z) + assert t(HyperRep_sinasin(a, z), 2*a*z*hyper([1 - a, 1 + a], [Rational(3, 2)], z), z) + + +@slow +def test_meijerg_eval(): + from sympy.functions.elementary.exponential import exp_polar + from sympy.functions.special.bessel import besseli + from sympy.abc import l + a = randcplx() + arg = x*exp_polar(k*pi*I) + expr1 = pi*meijerg([[], [(a + 1)/2]], [[a/2], [-a/2, (a + 1)/2]], arg**2/4) + expr2 = besseli(a, arg) + + # Test that the two expressions agree for all arguments. + for x_ in [0.5, 1.5]: + for k_ in [0.0, 0.1, 0.3, 0.5, 0.8, 1, 5.751, 15.3]: + assert abs((expr1 - expr2).n(subs={x: x_, k: k_})) < 1e-10 + assert abs((expr1 - expr2).n(subs={x: x_, k: -k_})) < 1e-10 + + # Test continuity independently + eps = 1e-13 + expr2 = expr1.subs(k, l) + for x_ in [0.5, 1.5]: + for k_ in [0.5, Rational(1, 3), 0.25, 0.75, Rational(2, 3), 1.0, 1.5]: + assert abs((expr1 - expr2).n( + subs={x: x_, k: k_ + eps, l: k_ - eps})) < 1e-10 + assert abs((expr1 - expr2).n( + subs={x: x_, k: -k_ + eps, l: -k_ - eps})) < 1e-10 + + expr = (meijerg(((0.5,), ()), ((0.5, 0, 0.5), ()), exp_polar(-I*pi)/4) + + meijerg(((0.5,), ()), ((0.5, 0, 0.5), ()), exp_polar(I*pi)/4)) \ + /(2*sqrt(pi)) + assert (expr - pi/exp(1)).n(chop=True) == 0 + + +def test_limits(): + k, x = symbols('k, x') + assert hyper((1,), (Rational(4, 3), Rational(5, 3)), k**2).series(k) == \ + 1 + 9*k**2/20 + 81*k**4/1120 + O(k**6) # issue 6350 + + # https://github.com/sympy/sympy/issues/11465 + assert limit(1/hyper((1, ), (1, ), x), x, 0) == 1 + + +def test_appellf1(): + a, b1, b2, c, x, y = symbols('a b1 b2 c x y') + assert appellf1(a, b2, b1, c, y, x) == appellf1(a, b1, b2, c, x, y) + assert appellf1(a, b1, b1, c, y, x) == appellf1(a, b1, b1, c, x, y) + assert appellf1(a, b1, b2, c, S.Zero, S.Zero) is S.One + + f = appellf1(a, b1, b2, c, S.Zero, S.Zero, evaluate=False) + assert f.func is appellf1 + assert f.doit() is S.One + + +def test_derivative_appellf1(): + from sympy.core.function import diff + a, b1, b2, c, x, y, z = symbols('a b1 b2 c x y z') + assert diff(appellf1(a, b1, b2, c, x, y), x) == a*b1*appellf1(a + 1, b2, b1 + 1, c + 1, y, x)/c + assert diff(appellf1(a, b1, b2, c, x, y), y) == a*b2*appellf1(a + 1, b1, b2 + 1, c + 1, x, y)/c + assert diff(appellf1(a, b1, b2, c, x, y), z) == 0 + assert diff(appellf1(a, b1, b2, c, x, y), a) == Derivative(appellf1(a, b1, b2, c, x, y), a) + + +def test_eval_nseries(): + a1, b1, a2, b2 = symbols('a1 b1 a2 b2') + assert hyper((1,2), (1,2,3), x**2)._eval_nseries(x, 7, None) == \ + 1 + x**2/3 + x**4/24 + x**6/360 + O(x**7) + assert exp(x)._eval_nseries(x,7,None) == \ + hyper((a1, b1), (a1, b1), x)._eval_nseries(x, 7, None) + assert hyper((a1, a2), (b1, b2), x)._eval_nseries(z, 7, None) ==\ + hyper((a1, a2), (b1, b2), x) + O(z**7) + assert hyper((-S(1)/2, S(1)/2), (1,), 4*x/(x + 1)).nseries(x) == \ + 1 - x + x**2/4 - 3*x**3/4 - 15*x**4/64 - 93*x**5/64 + O(x**6) + assert (pi/2*hyper((-S(1)/2, S(1)/2), (1,), 4*x/(x + 1))).nseries(x) == \ + pi/2 - pi*x/2 + pi*x**2/8 - 3*pi*x**3/8 - 15*pi*x**4/128 - 93*pi*x**5/128 + O(x**6) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_mathieu.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_mathieu.py new file mode 100644 index 0000000000000000000000000000000000000000..b9296f0657d920c8d297f820fb3ab8b6a53129ab --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_mathieu.py @@ -0,0 +1,29 @@ +from sympy.core.function import diff +from sympy.functions.elementary.complexes import conjugate +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.mathieu_functions import (mathieuc, mathieucprime, mathieus, mathieusprime) + +from sympy.abc import a, q, z + + +def test_mathieus(): + assert isinstance(mathieus(a, q, z), mathieus) + assert mathieus(a, 0, z) == sin(sqrt(a)*z) + assert conjugate(mathieus(a, q, z)) == mathieus(conjugate(a), conjugate(q), conjugate(z)) + assert diff(mathieus(a, q, z), z) == mathieusprime(a, q, z) + +def test_mathieuc(): + assert isinstance(mathieuc(a, q, z), mathieuc) + assert mathieuc(a, 0, z) == cos(sqrt(a)*z) + assert diff(mathieuc(a, q, z), z) == mathieucprime(a, q, z) + +def test_mathieusprime(): + assert isinstance(mathieusprime(a, q, z), mathieusprime) + assert mathieusprime(a, 0, z) == sqrt(a)*cos(sqrt(a)*z) + assert diff(mathieusprime(a, q, z), z) == (-a + 2*q*cos(2*z))*mathieus(a, q, z) + +def test_mathieucprime(): + assert isinstance(mathieucprime(a, q, z), mathieucprime) + assert mathieucprime(a, 0, z) == -sqrt(a)*sin(sqrt(a)*z) + assert diff(mathieucprime(a, q, z), z) == (-a + 2*q*cos(2*z))*mathieuc(a, q, z) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_singularity_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_singularity_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..dbd85cb0c7e5524d4fe1441615879b9776ad1693 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_singularity_functions.py @@ -0,0 +1,129 @@ +from sympy.core.function import (Derivative, diff) +from sympy.core.numbers import (Float, I, nan, oo, pi) +from sympy.core.relational import Eq +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import (DiracDelta, Heaviside) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.series.order import O + + +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.testing.pytest import raises + +x, y, a, n = symbols('x y a n') + + +def test_fdiff(): + assert SingularityFunction(x, 4, 5).fdiff() == 5*SingularityFunction(x, 4, 4) + assert SingularityFunction(x, 4, -1).fdiff() == SingularityFunction(x, 4, -2) + assert SingularityFunction(x, 4, -2).fdiff() == SingularityFunction(x, 4, -3) + assert SingularityFunction(x, 4, -3).fdiff() == SingularityFunction(x, 4, -4) + assert SingularityFunction(x, 4, 0).fdiff() == SingularityFunction(x, 4, -1) + + assert SingularityFunction(y, 6, 2).diff(y) == 2*SingularityFunction(y, 6, 1) + assert SingularityFunction(y, -4, -1).diff(y) == SingularityFunction(y, -4, -2) + assert SingularityFunction(y, 4, 0).diff(y) == SingularityFunction(y, 4, -1) + assert SingularityFunction(y, 4, 0).diff(y, 2) == SingularityFunction(y, 4, -2) + + n = Symbol('n', positive=True) + assert SingularityFunction(x, a, n).fdiff() == n*SingularityFunction(x, a, n - 1) + assert SingularityFunction(y, a, n).diff(y) == n*SingularityFunction(y, a, n - 1) + + expr_in = 4*SingularityFunction(x, a, n) + 3*SingularityFunction(x, a, -1) + -10*SingularityFunction(x, a, 0) + expr_out = n*4*SingularityFunction(x, a, n - 1) + 3*SingularityFunction(x, a, -2) - 10*SingularityFunction(x, a, -1) + assert diff(expr_in, x) == expr_out + + assert SingularityFunction(x, -10, 5).diff(evaluate=False) == ( + Derivative(SingularityFunction(x, -10, 5), x)) + + raises(ArgumentIndexError, lambda: SingularityFunction(x, 4, 5).fdiff(2)) + + +def test_eval(): + assert SingularityFunction(x, a, n).func == SingularityFunction + assert unchanged(SingularityFunction, x, 5, n) + assert SingularityFunction(5, 3, 2) == 4 + assert SingularityFunction(3, 5, 1) == 0 + assert SingularityFunction(3, 3, 0) == 1 + assert SingularityFunction(3, 3, 1) == 0 + assert SingularityFunction(Symbol('z', zero=True), 0, 1) == 0 # like sin(z) == 0 + assert SingularityFunction(4, 4, -1) is oo + assert SingularityFunction(4, 2, -1) == 0 + assert SingularityFunction(4, 7, -1) == 0 + assert SingularityFunction(5, 6, -2) == 0 + assert SingularityFunction(4, 2, -2) == 0 + assert SingularityFunction(4, 4, -2) is oo + assert SingularityFunction(4, 2, -3) == 0 + assert SingularityFunction(8, 8, -3) is oo + assert SingularityFunction(4, 2, -4) == 0 + assert SingularityFunction(8, 8, -4) is oo + assert (SingularityFunction(6.1, 4, 5)).evalf(5) == Float('40.841', '5') + assert SingularityFunction(6.1, pi, 2) == (-pi + 6.1)**2 + assert SingularityFunction(x, a, nan) is nan + assert SingularityFunction(x, nan, 1) is nan + assert SingularityFunction(nan, a, n) is nan + + raises(ValueError, lambda: SingularityFunction(x, a, I)) + raises(ValueError, lambda: SingularityFunction(2*I, I, n)) + raises(ValueError, lambda: SingularityFunction(x, a, -5)) + + +def test_leading_term(): + l = Symbol('l', positive=True) + assert SingularityFunction(x, 3, 2).as_leading_term(x) == 0 + assert SingularityFunction(x, -2, 1).as_leading_term(x) == 2 + assert SingularityFunction(x, 0, 0).as_leading_term(x) == 1 + assert SingularityFunction(x, 0, 0).as_leading_term(x, cdir=-1) == 0 + assert SingularityFunction(x, 0, -1).as_leading_term(x) == 0 + assert SingularityFunction(x, 0, -2).as_leading_term(x) == 0 + assert SingularityFunction(x, 0, -3).as_leading_term(x) == 0 + assert SingularityFunction(x, 0, -4).as_leading_term(x) == 0 + assert (SingularityFunction(x + l, 0, 1)/2\ + - SingularityFunction(x + l, l/2, 1)\ + + SingularityFunction(x + l, l, 1)/2).as_leading_term(x) == -x/2 + + +def test_series(): + l = Symbol('l', positive=True) + assert SingularityFunction(x, -3, 2).series(x) == x**2 + 6*x + 9 + assert SingularityFunction(x, -2, 1).series(x) == x + 2 + assert SingularityFunction(x, 0, 0).series(x) == 1 + assert SingularityFunction(x, 0, 0).series(x, dir='-') == 0 + assert SingularityFunction(x, 0, -1).series(x) == 0 + assert SingularityFunction(x, 0, -2).series(x) == 0 + assert SingularityFunction(x, 0, -3).series(x) == 0 + assert SingularityFunction(x, 0, -4).series(x) == 0 + assert (SingularityFunction(x + l, 0, 1)/2\ + - SingularityFunction(x + l, l/2, 1)\ + + SingularityFunction(x + l, l, 1)/2).nseries(x) == -x/2 + O(x**6) + + +def test_rewrite(): + assert SingularityFunction(x, 4, 5).rewrite(Piecewise) == ( + Piecewise(((x - 4)**5, x - 4 >= 0), (0, True))) + assert SingularityFunction(x, -10, 0).rewrite(Piecewise) == ( + Piecewise((1, x + 10 >= 0), (0, True))) + assert SingularityFunction(x, 2, -1).rewrite(Piecewise) == ( + Piecewise((oo, Eq(x - 2, 0)), (0, True))) + assert SingularityFunction(x, 0, -2).rewrite(Piecewise) == ( + Piecewise((oo, Eq(x, 0)), (0, True))) + + n = Symbol('n', nonnegative=True) + p = SingularityFunction(x, a, n).rewrite(Piecewise) + assert p == ( + Piecewise(((x - a)**n, x - a >= 0), (0, True))) + assert p.subs(x, a).subs(n, 0) == 1 + + expr_in = SingularityFunction(x, 4, 5) + SingularityFunction(x, -3, -1) - SingularityFunction(x, 0, -2) + expr_out = (x - 4)**5*Heaviside(x - 4, 1) + DiracDelta(x + 3) - DiracDelta(x, 1) + assert expr_in.rewrite(Heaviside) == expr_out + assert expr_in.rewrite(DiracDelta) == expr_out + assert expr_in.rewrite('HeavisideDiracDelta') == expr_out + + expr_in = SingularityFunction(x, a, n) + SingularityFunction(x, a, -1) - SingularityFunction(x, a, -2) + expr_out = (x - a)**n*Heaviside(x - a, 1) + DiracDelta(x - a) + DiracDelta(a - x, 1) + assert expr_in.rewrite(Heaviside) == expr_out + assert expr_in.rewrite(DiracDelta) == expr_out + assert expr_in.rewrite('HeavisideDiracDelta') == expr_out diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_spec_polynomials.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_spec_polynomials.py new file mode 100644 index 0000000000000000000000000000000000000000..584ad3cf97df8b9d92da9fc7805ab4296f40671c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_spec_polynomials.py @@ -0,0 +1,475 @@ +from sympy.concrete.summations import Sum +from sympy.core.function import (Derivative, diff) +from sympy.core.numbers import (Rational, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.combinatorial.factorials import (RisingFactorial, binomial, factorial) +from sympy.functions.elementary.complexes import conjugate +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import cos +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import hyper +from sympy.functions.special.polynomials import (assoc_laguerre, assoc_legendre, chebyshevt, chebyshevt_root, chebyshevu, chebyshevu_root, gegenbauer, hermite, hermite_prob, jacobi, jacobi_normalized, laguerre, legendre) +from sympy.polys.orthopolys import laguerre_poly +from sympy.polys.polyroots import roots + +from sympy.core.expr import unchanged +from sympy.core.function import ArgumentIndexError +from sympy.testing.pytest import raises + + +x = Symbol('x') + + +def test_jacobi(): + n = Symbol("n") + a = Symbol("a") + b = Symbol("b") + + assert jacobi(0, a, b, x) == 1 + assert jacobi(1, a, b, x) == a/2 - b/2 + x*(a/2 + b/2 + 1) + + assert jacobi(n, a, a, x) == RisingFactorial( + a + 1, n)*gegenbauer(n, a + S.Half, x)/RisingFactorial(2*a + 1, n) + assert jacobi(n, a, -a, x) == ((-1)**a*(-x + 1)**(-a/2)*(x + 1)**(a/2)*assoc_legendre(n, a, x)* + factorial(-a + n)*gamma(a + n + 1)/(factorial(a + n)*gamma(n + 1))) + assert jacobi(n, -b, b, x) == ((-x + 1)**(b/2)*(x + 1)**(-b/2)*assoc_legendre(n, b, x)* + gamma(-b + n + 1)/gamma(n + 1)) + assert jacobi(n, 0, 0, x) == legendre(n, x) + assert jacobi(n, S.Half, S.Half, x) == RisingFactorial( + Rational(3, 2), n)*chebyshevu(n, x)/factorial(n + 1) + assert jacobi(n, Rational(-1, 2), Rational(-1, 2), x) == RisingFactorial( + S.Half, n)*chebyshevt(n, x)/factorial(n) + + X = jacobi(n, a, b, x) + assert isinstance(X, jacobi) + + assert jacobi(n, a, b, -x) == (-1)**n*jacobi(n, b, a, x) + assert jacobi(n, a, b, 0) == 2**(-n)*gamma(a + n + 1)*hyper( + (-b - n, -n), (a + 1,), -1)/(factorial(n)*gamma(a + 1)) + assert jacobi(n, a, b, 1) == RisingFactorial(a + 1, n)/factorial(n) + + m = Symbol("m", positive=True) + assert jacobi(m, a, b, oo) == oo*RisingFactorial(a + b + m + 1, m) + assert unchanged(jacobi, n, a, b, oo) + + assert conjugate(jacobi(m, a, b, x)) == \ + jacobi(m, conjugate(a), conjugate(b), conjugate(x)) + + _k = Dummy('k') + assert diff(jacobi(n, a, b, x), n) == Derivative(jacobi(n, a, b, x), n) + assert diff(jacobi(n, a, b, x), a).dummy_eq(Sum((jacobi(n, a, b, x) + + (2*_k + a + b + 1)*RisingFactorial(_k + b + 1, -_k + n)*jacobi(_k, a, + b, x)/((-_k + n)*RisingFactorial(_k + a + b + 1, -_k + n)))/(_k + a + + b + n + 1), (_k, 0, n - 1))) + assert diff(jacobi(n, a, b, x), b).dummy_eq(Sum(((-1)**(-_k + n)*(2*_k + + a + b + 1)*RisingFactorial(_k + a + 1, -_k + n)*jacobi(_k, a, b, x)/ + ((-_k + n)*RisingFactorial(_k + a + b + 1, -_k + n)) + jacobi(n, a, + b, x))/(_k + a + b + n + 1), (_k, 0, n - 1))) + assert diff(jacobi(n, a, b, x), x) == \ + (a/2 + b/2 + n/2 + S.Half)*jacobi(n - 1, a + 1, b + 1, x) + + assert jacobi_normalized(n, a, b, x) == \ + (jacobi(n, a, b, x)/sqrt(2**(a + b + 1)*gamma(a + n + 1)*gamma(b + n + 1) + /((a + b + 2*n + 1)*factorial(n)*gamma(a + b + n + 1)))) + + raises(ValueError, lambda: jacobi(-2.1, a, b, x)) + raises(ValueError, lambda: jacobi(Dummy(positive=True, integer=True), 1, 2, oo)) + + assert jacobi(n, a, b, x).rewrite(Sum).dummy_eq(Sum((S.Half - x/2) + **_k*RisingFactorial(-n, _k)*RisingFactorial(_k + a + 1, -_k + n)* + RisingFactorial(a + b + n + 1, _k)/factorial(_k), (_k, 0, n))/factorial(n)) + assert jacobi(n, a, b, x).rewrite("polynomial").dummy_eq(Sum((S.Half - x/2) + **_k*RisingFactorial(-n, _k)*RisingFactorial(_k + a + 1, -_k + n)* + RisingFactorial(a + b + n + 1, _k)/factorial(_k), (_k, 0, n))/factorial(n)) + raises(ArgumentIndexError, lambda: jacobi(n, a, b, x).fdiff(5)) + + +def test_gegenbauer(): + n = Symbol("n") + a = Symbol("a") + + assert gegenbauer(0, a, x) == 1 + assert gegenbauer(1, a, x) == 2*a*x + assert gegenbauer(2, a, x) == -a + x**2*(2*a**2 + 2*a) + assert gegenbauer(3, a, x) == \ + x**3*(4*a**3/3 + 4*a**2 + a*Rational(8, 3)) + x*(-2*a**2 - 2*a) + + assert gegenbauer(-1, a, x) == 0 + assert gegenbauer(n, S.Half, x) == legendre(n, x) + assert gegenbauer(n, 1, x) == chebyshevu(n, x) + assert gegenbauer(n, -1, x) == 0 + + X = gegenbauer(n, a, x) + assert isinstance(X, gegenbauer) + + assert gegenbauer(n, a, -x) == (-1)**n*gegenbauer(n, a, x) + assert gegenbauer(n, a, 0) == 2**n*sqrt(pi) * \ + gamma(a + n/2)/(gamma(a)*gamma(-n/2 + S.Half)*gamma(n + 1)) + assert gegenbauer(n, a, 1) == gamma(2*a + n)/(gamma(2*a)*gamma(n + 1)) + + assert gegenbauer(n, Rational(3, 4), -1) is zoo + assert gegenbauer(n, Rational(1, 4), -1) == (sqrt(2)*cos(pi*(n + S.One/4))* + gamma(n + S.Half)/(sqrt(pi)*gamma(n + 1))) + + m = Symbol("m", positive=True) + assert gegenbauer(m, a, oo) == oo*RisingFactorial(a, m) + assert unchanged(gegenbauer, n, a, oo) + + assert conjugate(gegenbauer(n, a, x)) == gegenbauer(n, conjugate(a), conjugate(x)) + + _k = Dummy('k') + + assert diff(gegenbauer(n, a, x), n) == Derivative(gegenbauer(n, a, x), n) + assert diff(gegenbauer(n, a, x), a).dummy_eq(Sum((2*(-1)**(-_k + n) + 2)* + (_k + a)*gegenbauer(_k, a, x)/((-_k + n)*(_k + 2*a + n)) + ((2*_k + + 2)/((_k + 2*a)*(2*_k + 2*a + 1)) + 2/(_k + 2*a + n))*gegenbauer(n, a + , x), (_k, 0, n - 1))) + assert diff(gegenbauer(n, a, x), x) == 2*a*gegenbauer(n - 1, a + 1, x) + + assert gegenbauer(n, a, x).rewrite(Sum).dummy_eq( + Sum((-1)**_k*(2*x)**(-2*_k + n)*RisingFactorial(a, -_k + n) + /(factorial(_k)*factorial(-2*_k + n)), (_k, 0, floor(n/2)))) + assert gegenbauer(n, a, x).rewrite("polynomial").dummy_eq( + Sum((-1)**_k*(2*x)**(-2*_k + n)*RisingFactorial(a, -_k + n) + /(factorial(_k)*factorial(-2*_k + n)), (_k, 0, floor(n/2)))) + + raises(ArgumentIndexError, lambda: gegenbauer(n, a, x).fdiff(4)) + + +def test_legendre(): + assert legendre(0, x) == 1 + assert legendre(1, x) == x + assert legendre(2, x) == ((3*x**2 - 1)/2).expand() + assert legendre(3, x) == ((5*x**3 - 3*x)/2).expand() + assert legendre(4, x) == ((35*x**4 - 30*x**2 + 3)/8).expand() + assert legendre(5, x) == ((63*x**5 - 70*x**3 + 15*x)/8).expand() + assert legendre(6, x) == ((231*x**6 - 315*x**4 + 105*x**2 - 5)/16).expand() + + assert legendre(10, -1) == 1 + assert legendre(11, -1) == -1 + assert legendre(10, 1) == 1 + assert legendre(11, 1) == 1 + assert legendre(10, 0) != 0 + assert legendre(11, 0) == 0 + + assert legendre(-1, x) == 1 + k = Symbol('k') + assert legendre(5 - k, x).subs(k, 2) == ((5*x**3 - 3*x)/2).expand() + + assert roots(legendre(4, x), x) == { + sqrt(Rational(3, 7) - Rational(2, 35)*sqrt(30)): 1, + -sqrt(Rational(3, 7) - Rational(2, 35)*sqrt(30)): 1, + sqrt(Rational(3, 7) + Rational(2, 35)*sqrt(30)): 1, + -sqrt(Rational(3, 7) + Rational(2, 35)*sqrt(30)): 1, + } + + n = Symbol("n") + + X = legendre(n, x) + assert isinstance(X, legendre) + assert unchanged(legendre, n, x) + + assert legendre(n, 0) == sqrt(pi)/(gamma(S.Half - n/2)*gamma(n/2 + 1)) + assert legendre(n, 1) == 1 + assert legendre(n, oo) is oo + assert legendre(-n, x) == legendre(n - 1, x) + assert legendre(n, -x) == (-1)**n*legendre(n, x) + assert unchanged(legendre, -n + k, x) + + assert conjugate(legendre(n, x)) == legendre(n, conjugate(x)) + + assert diff(legendre(n, x), x) == \ + n*(x*legendre(n, x) - legendre(n - 1, x))/(x**2 - 1) + assert diff(legendre(n, x), n) == Derivative(legendre(n, x), n) + + _k = Dummy('k') + assert legendre(n, x).rewrite(Sum).dummy_eq(Sum((-1)**_k*(S.Half - + x/2)**_k*(x/2 + S.Half)**(-_k + n)*binomial(n, _k)**2, (_k, 0, n))) + assert legendre(n, x).rewrite("polynomial").dummy_eq(Sum((-1)**_k*(S.Half - + x/2)**_k*(x/2 + S.Half)**(-_k + n)*binomial(n, _k)**2, (_k, 0, n))) + raises(ArgumentIndexError, lambda: legendre(n, x).fdiff(1)) + raises(ArgumentIndexError, lambda: legendre(n, x).fdiff(3)) + + +def test_assoc_legendre(): + Plm = assoc_legendre + Q = sqrt(1 - x**2) + + assert Plm(0, 0, x) == 1 + assert Plm(1, 0, x) == x + assert Plm(1, 1, x) == -Q + assert Plm(2, 0, x) == (3*x**2 - 1)/2 + assert Plm(2, 1, x) == -3*x*Q + assert Plm(2, 2, x) == 3*Q**2 + assert Plm(3, 0, x) == (5*x**3 - 3*x)/2 + assert Plm(3, 1, x).expand() == (( 3*(1 - 5*x**2)/2 ).expand() * Q).expand() + assert Plm(3, 2, x) == 15*x * Q**2 + assert Plm(3, 3, x) == -15 * Q**3 + + # negative m + assert Plm(1, -1, x) == -Plm(1, 1, x)/2 + assert Plm(2, -2, x) == Plm(2, 2, x)/24 + assert Plm(2, -1, x) == -Plm(2, 1, x)/6 + assert Plm(3, -3, x) == -Plm(3, 3, x)/720 + assert Plm(3, -2, x) == Plm(3, 2, x)/120 + assert Plm(3, -1, x) == -Plm(3, 1, x)/12 + + n = Symbol("n") + m = Symbol("m") + X = Plm(n, m, x) + assert isinstance(X, assoc_legendre) + + assert Plm(n, 0, x) == legendre(n, x) + assert Plm(n, m, 0) == 2**m*sqrt(pi)/(gamma(-m/2 - n/2 + + S.Half)*gamma(-m/2 + n/2 + 1)) + + assert diff(Plm(m, n, x), x) == (m*x*assoc_legendre(m, n, x) - + (m + n)*assoc_legendre(m - 1, n, x))/(x**2 - 1) + + _k = Dummy('k') + assert Plm(m, n, x).rewrite(Sum).dummy_eq( + (1 - x**2)**(n/2)*Sum((-1)**_k*2**(-m)*x**(-2*_k + m - n)*factorial + (-2*_k + 2*m)/(factorial(_k)*factorial(-_k + m)*factorial(-2*_k + m + - n)), (_k, 0, floor(m/2 - n/2)))) + assert Plm(m, n, x).rewrite("polynomial").dummy_eq( + (1 - x**2)**(n/2)*Sum((-1)**_k*2**(-m)*x**(-2*_k + m - n)*factorial + (-2*_k + 2*m)/(factorial(_k)*factorial(-_k + m)*factorial(-2*_k + m + - n)), (_k, 0, floor(m/2 - n/2)))) + assert conjugate(assoc_legendre(n, m, x)) == \ + assoc_legendre(n, conjugate(m), conjugate(x)) + raises(ValueError, lambda: Plm(0, 1, x)) + raises(ValueError, lambda: Plm(-1, 1, x)) + raises(ArgumentIndexError, lambda: Plm(n, m, x).fdiff(1)) + raises(ArgumentIndexError, lambda: Plm(n, m, x).fdiff(2)) + raises(ArgumentIndexError, lambda: Plm(n, m, x).fdiff(4)) + + +def test_chebyshev(): + assert chebyshevt(0, x) == 1 + assert chebyshevt(1, x) == x + assert chebyshevt(2, x) == 2*x**2 - 1 + assert chebyshevt(3, x) == 4*x**3 - 3*x + + for n in range(1, 4): + for k in range(n): + z = chebyshevt_root(n, k) + assert chebyshevt(n, z) == 0 + raises(ValueError, lambda: chebyshevt_root(n, n)) + + for n in range(1, 4): + for k in range(n): + z = chebyshevu_root(n, k) + assert chebyshevu(n, z) == 0 + raises(ValueError, lambda: chebyshevu_root(n, n)) + + n = Symbol("n") + X = chebyshevt(n, x) + assert isinstance(X, chebyshevt) + assert unchanged(chebyshevt, n, x) + assert chebyshevt(n, -x) == (-1)**n*chebyshevt(n, x) + assert chebyshevt(-n, x) == chebyshevt(n, x) + + assert chebyshevt(n, 0) == cos(pi*n/2) + assert chebyshevt(n, 1) == 1 + assert chebyshevt(n, oo) is oo + + assert conjugate(chebyshevt(n, x)) == chebyshevt(n, conjugate(x)) + + assert diff(chebyshevt(n, x), x) == n*chebyshevu(n - 1, x) + + X = chebyshevu(n, x) + assert isinstance(X, chebyshevu) + + y = Symbol('y') + assert chebyshevu(n, -x) == (-1)**n*chebyshevu(n, x) + assert chebyshevu(-n, x) == -chebyshevu(n - 2, x) + assert unchanged(chebyshevu, -n + y, x) + + assert chebyshevu(n, 0) == cos(pi*n/2) + assert chebyshevu(n, 1) == n + 1 + assert chebyshevu(n, oo) is oo + + assert conjugate(chebyshevu(n, x)) == chebyshevu(n, conjugate(x)) + + assert diff(chebyshevu(n, x), x) == \ + (-x*chebyshevu(n, x) + (n + 1)*chebyshevt(n + 1, x))/(x**2 - 1) + + _k = Dummy('k') + assert chebyshevt(n, x).rewrite(Sum).dummy_eq(Sum(x**(-2*_k + n) + *(x**2 - 1)**_k*binomial(n, 2*_k), (_k, 0, floor(n/2)))) + assert chebyshevt(n, x).rewrite("polynomial").dummy_eq(Sum(x**(-2*_k + n) + *(x**2 - 1)**_k*binomial(n, 2*_k), (_k, 0, floor(n/2)))) + assert chebyshevu(n, x).rewrite(Sum).dummy_eq(Sum((-1)**_k*(2*x) + **(-2*_k + n)*factorial(-_k + n)/(factorial(_k)* + factorial(-2*_k + n)), (_k, 0, floor(n/2)))) + assert chebyshevu(n, x).rewrite("polynomial").dummy_eq(Sum((-1)**_k*(2*x) + **(-2*_k + n)*factorial(-_k + n)/(factorial(_k)* + factorial(-2*_k + n)), (_k, 0, floor(n/2)))) + raises(ArgumentIndexError, lambda: chebyshevt(n, x).fdiff(1)) + raises(ArgumentIndexError, lambda: chebyshevt(n, x).fdiff(3)) + raises(ArgumentIndexError, lambda: chebyshevu(n, x).fdiff(1)) + raises(ArgumentIndexError, lambda: chebyshevu(n, x).fdiff(3)) + + +def test_hermite(): + assert hermite(0, x) == 1 + assert hermite(1, x) == 2*x + assert hermite(2, x) == 4*x**2 - 2 + assert hermite(3, x) == 8*x**3 - 12*x + assert hermite(4, x) == 16*x**4 - 48*x**2 + 12 + assert hermite(6, x) == 64*x**6 - 480*x**4 + 720*x**2 - 120 + + n = Symbol("n") + assert unchanged(hermite, n, x) + assert hermite(n, -x) == (-1)**n*hermite(n, x) + assert unchanged(hermite, -n, x) + + assert hermite(n, 0) == 2**n*sqrt(pi)/gamma(S.Half - n/2) + assert hermite(n, oo) is oo + + assert conjugate(hermite(n, x)) == hermite(n, conjugate(x)) + + _k = Dummy('k') + assert hermite(n, x).rewrite(Sum).dummy_eq(factorial(n)*Sum((-1) + **_k*(2*x)**(-2*_k + n)/(factorial(_k)*factorial(-2*_k + n)), (_k, + 0, floor(n/2)))) + assert hermite(n, x).rewrite("polynomial").dummy_eq(factorial(n)*Sum((-1) + **_k*(2*x)**(-2*_k + n)/(factorial(_k)*factorial(-2*_k + n)), (_k, + 0, floor(n/2)))) + + assert diff(hermite(n, x), x) == 2*n*hermite(n - 1, x) + assert diff(hermite(n, x), n) == Derivative(hermite(n, x), n) + raises(ArgumentIndexError, lambda: hermite(n, x).fdiff(3)) + + assert hermite(n, x).rewrite(hermite_prob) == \ + sqrt(2)**n * hermite_prob(n, x*sqrt(2)) + + +def test_hermite_prob(): + assert hermite_prob(0, x) == 1 + assert hermite_prob(1, x) == x + assert hermite_prob(2, x) == x**2 - 1 + assert hermite_prob(3, x) == x**3 - 3*x + assert hermite_prob(4, x) == x**4 - 6*x**2 + 3 + assert hermite_prob(6, x) == x**6 - 15*x**4 + 45*x**2 - 15 + + n = Symbol("n") + assert unchanged(hermite_prob, n, x) + assert hermite_prob(n, -x) == (-1)**n*hermite_prob(n, x) + assert unchanged(hermite_prob, -n, x) + + assert hermite_prob(n, 0) == sqrt(pi)/gamma(S.Half - n/2) + assert hermite_prob(n, oo) is oo + + assert conjugate(hermite_prob(n, x)) == hermite_prob(n, conjugate(x)) + + _k = Dummy('k') + assert hermite_prob(n, x).rewrite(Sum).dummy_eq(factorial(n) * + Sum((-S.Half)**_k * x**(n-2*_k) / (factorial(_k) * factorial(n-2*_k)), + (_k, 0, floor(n/2)))) + assert hermite_prob(n, x).rewrite("polynomial").dummy_eq(factorial(n) * + Sum((-S.Half)**_k * x**(n-2*_k) / (factorial(_k) * factorial(n-2*_k)), + (_k, 0, floor(n/2)))) + + assert diff(hermite_prob(n, x), x) == n*hermite_prob(n-1, x) + assert diff(hermite_prob(n, x), n) == Derivative(hermite_prob(n, x), n) + raises(ArgumentIndexError, lambda: hermite_prob(n, x).fdiff(3)) + + assert hermite_prob(n, x).rewrite(hermite) == \ + sqrt(2)**(-n) * hermite(n, x/sqrt(2)) + + +def test_laguerre(): + n = Symbol("n") + m = Symbol("m", negative=True) + + # Laguerre polynomials: + assert laguerre(0, x) == 1 + assert laguerre(1, x) == -x + 1 + assert laguerre(2, x) == x**2/2 - 2*x + 1 + assert laguerre(3, x) == -x**3/6 + 3*x**2/2 - 3*x + 1 + assert laguerre(-2, x) == (x + 1)*exp(x) + + X = laguerre(n, x) + assert isinstance(X, laguerre) + + assert laguerre(n, 0) == 1 + assert laguerre(n, oo) == (-1)**n*oo + assert laguerre(n, -oo) is oo + + assert conjugate(laguerre(n, x)) == laguerre(n, conjugate(x)) + + _k = Dummy('k') + + assert laguerre(n, x).rewrite(Sum).dummy_eq( + Sum(x**_k*RisingFactorial(-n, _k)/factorial(_k)**2, (_k, 0, n))) + assert laguerre(n, x).rewrite("polynomial").dummy_eq( + Sum(x**_k*RisingFactorial(-n, _k)/factorial(_k)**2, (_k, 0, n))) + assert laguerre(m, x).rewrite(Sum).dummy_eq( + exp(x)*Sum((-x)**_k*RisingFactorial(m + 1, _k)/factorial(_k)**2, + (_k, 0, -m - 1))) + assert laguerre(m, x).rewrite("polynomial").dummy_eq( + exp(x)*Sum((-x)**_k*RisingFactorial(m + 1, _k)/factorial(_k)**2, + (_k, 0, -m - 1))) + + assert diff(laguerre(n, x), x) == -assoc_laguerre(n - 1, 1, x) + + k = Symbol('k') + assert laguerre(-n, x) == exp(x)*laguerre(n - 1, -x) + assert laguerre(-3, x) == exp(x)*laguerre(2, -x) + assert unchanged(laguerre, -n + k, x) + + raises(ValueError, lambda: laguerre(-2.1, x)) + raises(ValueError, lambda: laguerre(Rational(5, 2), x)) + raises(ArgumentIndexError, lambda: laguerre(n, x).fdiff(1)) + raises(ArgumentIndexError, lambda: laguerre(n, x).fdiff(3)) + + +def test_assoc_laguerre(): + n = Symbol("n") + m = Symbol("m") + alpha = Symbol("alpha") + + # generalized Laguerre polynomials: + assert assoc_laguerre(0, alpha, x) == 1 + assert assoc_laguerre(1, alpha, x) == -x + alpha + 1 + assert assoc_laguerre(2, alpha, x).expand() == \ + (x**2/2 - (alpha + 2)*x + (alpha + 2)*(alpha + 1)/2).expand() + assert assoc_laguerre(3, alpha, x).expand() == \ + (-x**3/6 + (alpha + 3)*x**2/2 - (alpha + 2)*(alpha + 3)*x/2 + + (alpha + 1)*(alpha + 2)*(alpha + 3)/6).expand() + + # Test the lowest 10 polynomials with laguerre_poly, to make sure it works: + for i in range(10): + assert assoc_laguerre(i, 0, x).expand() == laguerre_poly(i, x) + + X = assoc_laguerre(n, m, x) + assert isinstance(X, assoc_laguerre) + + assert assoc_laguerre(n, 0, x) == laguerre(n, x) + assert assoc_laguerre(n, alpha, 0) == binomial(alpha + n, alpha) + p = Symbol("p", positive=True) + assert assoc_laguerre(p, alpha, oo) == (-1)**p*oo + assert assoc_laguerre(p, alpha, -oo) is oo + + assert diff(assoc_laguerre(n, alpha, x), x) == \ + -assoc_laguerre(n - 1, alpha + 1, x) + _k = Dummy('k') + assert diff(assoc_laguerre(n, alpha, x), alpha).dummy_eq( + Sum(assoc_laguerre(_k, alpha, x)/(-alpha + n), (_k, 0, n - 1))) + + assert conjugate(assoc_laguerre(n, alpha, x)) == \ + assoc_laguerre(n, conjugate(alpha), conjugate(x)) + + assert assoc_laguerre(n, alpha, x).rewrite(Sum).dummy_eq( + gamma(alpha + n + 1)*Sum(x**_k*RisingFactorial(-n, _k)/ + (factorial(_k)*gamma(_k + alpha + 1)), (_k, 0, n))/factorial(n)) + assert assoc_laguerre(n, alpha, x).rewrite("polynomial").dummy_eq( + gamma(alpha + n + 1)*Sum(x**_k*RisingFactorial(-n, _k)/ + (factorial(_k)*gamma(_k + alpha + 1)), (_k, 0, n))/factorial(n)) + raises(ValueError, lambda: assoc_laguerre(-2.1, alpha, x)) + raises(ArgumentIndexError, lambda: assoc_laguerre(n, alpha, x).fdiff(1)) + raises(ArgumentIndexError, lambda: assoc_laguerre(n, alpha, x).fdiff(4)) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_spherical_harmonics.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_spherical_harmonics.py new file mode 100644 index 0000000000000000000000000000000000000000..2e0d4ffebabb62c13d3fc2996e8ba23866467720 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_spherical_harmonics.py @@ -0,0 +1,66 @@ +from sympy.core.function import diff +from sympy.core.numbers import (I, pi) +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import conjugate +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, cot, sin) +from sympy.functions.special.spherical_harmonics import Ynm, Znm, Ynm_c + + +def test_Ynm(): + # https://en.wikipedia.org/wiki/Spherical_harmonics + th, ph = Symbol("theta", real=True), Symbol("phi", real=True) + from sympy.abc import n,m + + assert Ynm(0, 0, th, ph).expand(func=True) == 1/(2*sqrt(pi)) + assert Ynm(1, -1, th, ph) == -exp(-2*I*ph)*Ynm(1, 1, th, ph) + assert Ynm(1, -1, th, ph).expand(func=True) == sqrt(6)*sin(th)*exp(-I*ph)/(4*sqrt(pi)) + assert Ynm(1, 0, th, ph).expand(func=True) == sqrt(3)*cos(th)/(2*sqrt(pi)) + assert Ynm(1, 1, th, ph).expand(func=True) == -sqrt(6)*sin(th)*exp(I*ph)/(4*sqrt(pi)) + assert Ynm(2, 0, th, ph).expand(func=True) == 3*sqrt(5)*cos(th)**2/(4*sqrt(pi)) - sqrt(5)/(4*sqrt(pi)) + assert Ynm(2, 1, th, ph).expand(func=True) == -sqrt(30)*sin(th)*exp(I*ph)*cos(th)/(4*sqrt(pi)) + assert Ynm(2, -2, th, ph).expand(func=True) == (-sqrt(30)*exp(-2*I*ph)*cos(th)**2/(8*sqrt(pi)) + + sqrt(30)*exp(-2*I*ph)/(8*sqrt(pi))) + assert Ynm(2, 2, th, ph).expand(func=True) == (-sqrt(30)*exp(2*I*ph)*cos(th)**2/(8*sqrt(pi)) + + sqrt(30)*exp(2*I*ph)/(8*sqrt(pi))) + + assert diff(Ynm(n, m, th, ph), th) == (m*cot(th)*Ynm(n, m, th, ph) + + sqrt((-m + n)*(m + n + 1))*exp(-I*ph)*Ynm(n, m + 1, th, ph)) + assert diff(Ynm(n, m, th, ph), ph) == I*m*Ynm(n, m, th, ph) + + assert conjugate(Ynm(n, m, th, ph)) == (-1)**(2*m)*exp(-2*I*m*ph)*Ynm(n, m, th, ph) + + assert Ynm(n, m, -th, ph) == Ynm(n, m, th, ph) + assert Ynm(n, m, th, -ph) == exp(-2*I*m*ph)*Ynm(n, m, th, ph) + assert Ynm(n, -m, th, ph) == (-1)**m*exp(-2*I*m*ph)*Ynm(n, m, th, ph) + + +def test_Ynm_c(): + th, ph = Symbol("theta", real=True), Symbol("phi", real=True) + from sympy.abc import n,m + + assert Ynm_c(n, m, th, ph) == (-1)**(2*m)*exp(-2*I*m*ph)*Ynm(n, m, th, ph) + + +def test_Znm(): + # https://en.wikipedia.org/wiki/Solid_harmonics#List_of_lowest_functions + th, ph = Symbol("theta", real=True), Symbol("phi", real=True) + + assert Znm(0, 0, th, ph) == Ynm(0, 0, th, ph) + assert Znm(1, -1, th, ph) == (-sqrt(2)*I*(Ynm(1, 1, th, ph) + - exp(-2*I*ph)*Ynm(1, 1, th, ph))/2) + assert Znm(1, 0, th, ph) == Ynm(1, 0, th, ph) + assert Znm(1, 1, th, ph) == (sqrt(2)*(Ynm(1, 1, th, ph) + + exp(-2*I*ph)*Ynm(1, 1, th, ph))/2) + assert Znm(0, 0, th, ph).expand(func=True) == 1/(2*sqrt(pi)) + assert Znm(1, -1, th, ph).expand(func=True) == (sqrt(3)*I*sin(th)*exp(I*ph)/(4*sqrt(pi)) + - sqrt(3)*I*sin(th)*exp(-I*ph)/(4*sqrt(pi))) + assert Znm(1, 0, th, ph).expand(func=True) == sqrt(3)*cos(th)/(2*sqrt(pi)) + assert Znm(1, 1, th, ph).expand(func=True) == (-sqrt(3)*sin(th)*exp(I*ph)/(4*sqrt(pi)) + - sqrt(3)*sin(th)*exp(-I*ph)/(4*sqrt(pi))) + assert Znm(2, -1, th, ph).expand(func=True) == (sqrt(15)*I*sin(th)*exp(I*ph)*cos(th)/(4*sqrt(pi)) + - sqrt(15)*I*sin(th)*exp(-I*ph)*cos(th)/(4*sqrt(pi))) + assert Znm(2, 0, th, ph).expand(func=True) == 3*sqrt(5)*cos(th)**2/(4*sqrt(pi)) - sqrt(5)/(4*sqrt(pi)) + assert Znm(2, 1, th, ph).expand(func=True) == (-sqrt(15)*sin(th)*exp(I*ph)*cos(th)/(4*sqrt(pi)) + - sqrt(15)*sin(th)*exp(-I*ph)*cos(th)/(4*sqrt(pi))) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_tensor_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_tensor_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..7d4f31c45ae0a60a6f72dc5551794b2110f5ab99 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_tensor_functions.py @@ -0,0 +1,145 @@ +from sympy.core.relational import Ne +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.elementary.complexes import (adjoint, conjugate, transpose) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.tensor_functions import (Eijk, KroneckerDelta, LeviCivita) + +from sympy.physics.secondquant import evaluate_deltas, F + +x, y = symbols('x y') + + +def test_levicivita(): + assert Eijk(1, 2, 3) == LeviCivita(1, 2, 3) + assert LeviCivita(1, 2, 3) == 1 + assert LeviCivita(int(1), int(2), int(3)) == 1 + assert LeviCivita(1, 3, 2) == -1 + assert LeviCivita(1, 2, 2) == 0 + i, j, k = symbols('i j k') + assert LeviCivita(i, j, k) == LeviCivita(i, j, k, evaluate=False) + assert LeviCivita(i, j, i) == 0 + assert LeviCivita(1, i, i) == 0 + assert LeviCivita(i, j, k).doit() == (j - i)*(k - i)*(k - j)/2 + assert LeviCivita(1, 2, 3, 1) == 0 + assert LeviCivita(4, 5, 1, 2, 3) == 1 + assert LeviCivita(4, 5, 2, 1, 3) == -1 + + assert LeviCivita(i, j, k).is_integer is True + + assert adjoint(LeviCivita(i, j, k)) == LeviCivita(i, j, k) + assert conjugate(LeviCivita(i, j, k)) == LeviCivita(i, j, k) + assert transpose(LeviCivita(i, j, k)) == LeviCivita(i, j, k) + + +def test_kronecker_delta(): + i, j = symbols('i j') + k = Symbol('k', nonzero=True) + assert KroneckerDelta(1, 1) == 1 + assert KroneckerDelta(1, 2) == 0 + assert KroneckerDelta(k, 0) == 0 + assert KroneckerDelta(x, x) == 1 + assert KroneckerDelta(x**2 - y**2, x**2 - y**2) == 1 + assert KroneckerDelta(i, i) == 1 + assert KroneckerDelta(i, i + 1) == 0 + assert KroneckerDelta(0, 0) == 1 + assert KroneckerDelta(0, 1) == 0 + assert KroneckerDelta(i + k, i) == 0 + assert KroneckerDelta(i + k, i + k) == 1 + assert KroneckerDelta(i + k, i + 1 + k) == 0 + assert KroneckerDelta(i, j).subs({"i": 1, "j": 0}) == 0 + assert KroneckerDelta(i, j).subs({"i": 3, "j": 3}) == 1 + + assert KroneckerDelta(i, j)**0 == 1 + for n in range(1, 10): + assert KroneckerDelta(i, j)**n == KroneckerDelta(i, j) + assert KroneckerDelta(i, j)**-n == 1/KroneckerDelta(i, j) + + assert KroneckerDelta(i, j).is_integer is True + + assert adjoint(KroneckerDelta(i, j)) == KroneckerDelta(i, j) + assert conjugate(KroneckerDelta(i, j)) == KroneckerDelta(i, j) + assert transpose(KroneckerDelta(i, j)) == KroneckerDelta(i, j) + # to test if canonical + assert (KroneckerDelta(i, j) == KroneckerDelta(j, i)) == True + + assert KroneckerDelta(i, j).rewrite(Piecewise) == Piecewise((0, Ne(i, j)), (1, True)) + + # Tests with range: + assert KroneckerDelta(i, j, (0, i)).args == (i, j, (0, i)) + assert KroneckerDelta(i, j, (-j, i)).delta_range == (-j, i) + + # If index is out of range, return zero: + assert KroneckerDelta(i, j, (0, i-1)) == 0 + assert KroneckerDelta(-1, j, (0, i-1)) == 0 + assert KroneckerDelta(j, -1, (0, i-1)) == 0 + assert KroneckerDelta(j, i, (0, i-1)) == 0 + + +def test_kronecker_delta_secondquant(): + """secondquant-specific methods""" + D = KroneckerDelta + i, j, v, w = symbols('i j v w', below_fermi=True, cls=Dummy) + a, b, t, u = symbols('a b t u', above_fermi=True, cls=Dummy) + p, q, r, s = symbols('p q r s', cls=Dummy) + + assert D(i, a) == 0 + assert D(i, t) == 0 + + assert D(i, j).is_above_fermi is False + assert D(a, b).is_above_fermi is True + assert D(p, q).is_above_fermi is True + assert D(i, q).is_above_fermi is False + assert D(q, i).is_above_fermi is False + assert D(q, v).is_above_fermi is False + assert D(a, q).is_above_fermi is True + + assert D(i, j).is_below_fermi is True + assert D(a, b).is_below_fermi is False + assert D(p, q).is_below_fermi is True + assert D(p, j).is_below_fermi is True + assert D(q, b).is_below_fermi is False + + assert D(i, j).is_only_above_fermi is False + assert D(a, b).is_only_above_fermi is True + assert D(p, q).is_only_above_fermi is False + assert D(i, q).is_only_above_fermi is False + assert D(q, i).is_only_above_fermi is False + assert D(a, q).is_only_above_fermi is True + + assert D(i, j).is_only_below_fermi is True + assert D(a, b).is_only_below_fermi is False + assert D(p, q).is_only_below_fermi is False + assert D(p, j).is_only_below_fermi is True + assert D(q, b).is_only_below_fermi is False + + assert not D(i, q).indices_contain_equal_information + assert not D(a, q).indices_contain_equal_information + assert D(p, q).indices_contain_equal_information + assert D(a, b).indices_contain_equal_information + assert D(i, j).indices_contain_equal_information + + assert D(q, b).preferred_index == b + assert D(q, b).killable_index == q + assert D(q, t).preferred_index == t + assert D(q, t).killable_index == q + assert D(q, i).preferred_index == i + assert D(q, i).killable_index == q + assert D(q, v).preferred_index == v + assert D(q, v).killable_index == q + assert D(q, p).preferred_index == p + assert D(q, p).killable_index == q + + EV = evaluate_deltas + assert EV(D(a, q)*F(q)) == F(a) + assert EV(D(i, q)*F(q)) == F(i) + assert EV(D(a, q)*F(a)) == D(a, q)*F(a) + assert EV(D(i, q)*F(i)) == D(i, q)*F(i) + assert EV(D(a, b)*F(a)) == F(b) + assert EV(D(a, b)*F(b)) == F(a) + assert EV(D(i, j)*F(i)) == F(j) + assert EV(D(i, j)*F(j)) == F(i) + assert EV(D(p, q)*F(q)) == F(p) + assert EV(D(p, q)*F(p)) == F(q) + assert EV(D(p, j)*D(p, i)*F(i)) == F(j) + assert EV(D(p, j)*D(p, i)*F(j)) == F(i) + assert EV(D(p, q)*D(p, i))*F(i) == D(q, i)*F(i) diff --git a/phivenv/Lib/site-packages/sympy/functions/special/tests/test_zeta_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_zeta_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..c2083b0b6e8cb38fde17fb1ede2a34be6338b1dc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/tests/test_zeta_functions.py @@ -0,0 +1,286 @@ +from sympy.concrete.summations import Sum +from sympy.core.function import expand_func +from sympy.core.numbers import (Float, I, Rational, nan, oo, pi, zoo) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import (Abs, polar_lift) +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.zeta_functions import (dirichlet_eta, lerchphi, polylog, riemann_xi, stieltjes, zeta) +from sympy.series.order import O +from sympy.core.function import ArgumentIndexError +from sympy.functions.combinatorial.numbers import bernoulli, factorial, genocchi, harmonic +from sympy.testing.pytest import raises +from sympy.core.random import (test_derivative_numerically as td, + random_complex_number as randcplx, verify_numerically) + +x = Symbol('x') +a = Symbol('a') +b = Symbol('b', negative=True) +z = Symbol('z') +s = Symbol('s') + + +def test_zeta_eval(): + + assert zeta(nan) is nan + assert zeta(x, nan) is nan + + assert zeta(0) == Rational(-1, 2) + assert zeta(0, x) == S.Half - x + assert zeta(0, b) == S.Half - b + + assert zeta(1) is zoo + assert zeta(1, 2) is zoo + assert zeta(1, -7) is zoo + assert zeta(1, x) is zoo + + assert zeta(2, 1) == pi**2/6 + assert zeta(3, 1) == zeta(3) + + assert zeta(2) == pi**2/6 + assert zeta(4) == pi**4/90 + assert zeta(6) == pi**6/945 + + assert zeta(4, 3) == pi**4/90 - Rational(17, 16) + assert zeta(7, 4) == zeta(7) - Rational(282251, 279936) + assert zeta(S.Half, 2).func == zeta + assert expand_func(zeta(S.Half, 2)) == zeta(S.Half) - 1 + assert zeta(x, 3).func == zeta + assert expand_func(zeta(x, 3)) == zeta(x) - 1 - 1/2**x + + assert zeta(2, 0) is nan + assert zeta(3, -1) is nan + assert zeta(4, -2) is nan + + assert zeta(oo) == 1 + + assert zeta(-1) == Rational(-1, 12) + assert zeta(-2) == 0 + assert zeta(-3) == Rational(1, 120) + assert zeta(-4) == 0 + assert zeta(-5) == Rational(-1, 252) + + assert zeta(-1, 3) == Rational(-37, 12) + assert zeta(-1, 7) == Rational(-253, 12) + assert zeta(-1, -4) == Rational(-121, 12) + assert zeta(-1, -9) == Rational(-541, 12) + + assert zeta(-4, 3) == -17 + assert zeta(-4, -8) == 8772 + + assert zeta(0, 1) == Rational(-1, 2) + assert zeta(0, -1) == Rational(3, 2) + + assert zeta(0, 2) == Rational(-3, 2) + assert zeta(0, -2) == Rational(5, 2) + + assert zeta( + 3).evalf(20).epsilon_eq(Float("1.2020569031595942854", 20), 1e-19) + + +def test_zeta_series(): + assert zeta(x, a).series(a, z, 2) == \ + zeta(x, z) - x*(a-z)*zeta(x+1, z) + O((a-z)**2, (a, z)) + + +def test_dirichlet_eta_eval(): + assert dirichlet_eta(0) == S.Half + assert dirichlet_eta(-1) == Rational(1, 4) + assert dirichlet_eta(1) == log(2) + assert dirichlet_eta(1, S.Half).simplify() == pi/2 + assert dirichlet_eta(1, 2) == 1 - log(2) + assert dirichlet_eta(2) == pi**2/12 + assert dirichlet_eta(4) == pi**4*Rational(7, 720) + assert str(dirichlet_eta(I).evalf(n=10)) == '0.5325931818 + 0.2293848577*I' + assert str(dirichlet_eta(I, I).evalf(n=10)) == '3.462349253 + 0.220285771*I' + + +def test_riemann_xi_eval(): + assert riemann_xi(2) == pi/6 + assert riemann_xi(0) == Rational(1, 2) + assert riemann_xi(1) == Rational(1, 2) + assert riemann_xi(3).rewrite(zeta) == 3*zeta(3)/(2*pi) + assert riemann_xi(4) == pi**2/15 + + +def test_rewriting(): + from sympy.functions.elementary.piecewise import Piecewise + assert isinstance(dirichlet_eta(x).rewrite(zeta), Piecewise) + assert isinstance(dirichlet_eta(x).rewrite(genocchi), Piecewise) + assert zeta(x).rewrite(dirichlet_eta) == dirichlet_eta(x)/(1 - 2**(1 - x)) + assert zeta(x).rewrite(dirichlet_eta, a=2) == zeta(x) + assert verify_numerically(dirichlet_eta(x), dirichlet_eta(x).rewrite(zeta), x) + assert verify_numerically(dirichlet_eta(x), dirichlet_eta(x).rewrite(genocchi), x) + assert verify_numerically(zeta(x), zeta(x).rewrite(dirichlet_eta), x) + + assert zeta(x, a).rewrite(lerchphi) == lerchphi(1, x, a) + assert polylog(s, z).rewrite(lerchphi) == lerchphi(z, s, 1)*z + + assert lerchphi(1, x, a).rewrite(zeta) == zeta(x, a) + assert z*lerchphi(z, s, 1).rewrite(polylog) == polylog(s, z) + + +def test_derivatives(): + from sympy.core.function import Derivative + assert zeta(x, a).diff(x) == Derivative(zeta(x, a), x) + assert zeta(x, a).diff(a) == -x*zeta(x + 1, a) + assert lerchphi( + z, s, a).diff(z) == (lerchphi(z, s - 1, a) - a*lerchphi(z, s, a))/z + assert lerchphi(z, s, a).diff(a) == -s*lerchphi(z, s + 1, a) + assert polylog(s, z).diff(z) == polylog(s - 1, z)/z + + b = randcplx() + c = randcplx() + assert td(zeta(b, x), x) + assert td(polylog(b, z), z) + assert td(lerchphi(c, b, x), x) + assert td(lerchphi(x, b, c), x) + raises(ArgumentIndexError, lambda: lerchphi(c, b, x).fdiff(2)) + raises(ArgumentIndexError, lambda: lerchphi(c, b, x).fdiff(4)) + raises(ArgumentIndexError, lambda: polylog(b, z).fdiff(1)) + raises(ArgumentIndexError, lambda: polylog(b, z).fdiff(3)) + + +def myexpand(func, target): + expanded = expand_func(func) + if target is not None: + return expanded == target + if expanded == func: # it didn't expand + return False + + # check to see that the expanded and original evaluate to the same value + subs = {} + for a in func.free_symbols: + subs[a] = randcplx() + return abs(func.subs(subs).n() + - expanded.replace(exp_polar, exp).subs(subs).n()) < 1e-10 + + +def test_polylog_expansion(): + assert polylog(s, 0) == 0 + assert polylog(s, 1) == zeta(s) + assert polylog(s, -1) == -dirichlet_eta(s) + assert polylog(s, exp_polar(I*pi*Rational(4, 3))) == polylog(s, exp(I*pi*Rational(4, 3))) + assert polylog(s, exp_polar(I*pi)/3) == polylog(s, exp(I*pi)/3) + + assert myexpand(polylog(1, z), -log(1 - z)) + assert myexpand(polylog(0, z), z/(1 - z)) + assert myexpand(polylog(-1, z), z/(1 - z)**2) + assert ((1-z)**3 * expand_func(polylog(-2, z))).simplify() == z*(1 + z) + assert myexpand(polylog(-5, z), None) + + +def test_polylog_series(): + assert polylog(1, z).series(z, n=5) == z + z**2/2 + z**3/3 + z**4/4 + O(z**5) + assert polylog(1, sqrt(z)).series(z, n=3) == z/2 + z**2/4 + sqrt(z)\ + + z**(S(3)/2)/3 + z**(S(5)/2)/5 + O(z**3) + + # https://github.com/sympy/sympy/issues/9497 + assert polylog(S(3)/2, -z).series(z, 0, 5) == -z + sqrt(2)*z**2/4\ + - sqrt(3)*z**3/9 + z**4/8 + O(z**5) + + +def test_issue_8404(): + i = Symbol('i', integer=True) + assert Abs(Sum(1/(3*i + 1)**2, (i, 0, S.Infinity)).doit().n(4) + - 1.122) < 0.001 + + +def test_polylog_values(): + assert polylog(2, 2) == pi**2/4 - I*pi*log(2) + assert polylog(2, S.Half) == pi**2/12 - log(2)**2/2 + for z in [S.Half, 2, (sqrt(5)-1)/2, -(sqrt(5)-1)/2, -(sqrt(5)+1)/2, (3-sqrt(5))/2]: + assert Abs(polylog(2, z).evalf() - polylog(2, z, evaluate=False).evalf()) < 1e-15 + z = Symbol("z") + for s in [-1, 0]: + for _ in range(10): + assert verify_numerically(polylog(s, z), polylog(s, z, evaluate=False), + z, a=-3, b=-2, c=S.Half, d=2) + assert verify_numerically(polylog(s, z), polylog(s, z, evaluate=False), + z, a=2, b=-2, c=5, d=2) + + from sympy.integrals.integrals import Integral + assert polylog(0, Integral(1, (x, 0, 1))) == -S.Half + + +def test_lerchphi_expansion(): + assert myexpand(lerchphi(1, s, a), zeta(s, a)) + assert myexpand(lerchphi(z, s, 1), polylog(s, z)/z) + + # direct summation + assert myexpand(lerchphi(z, -1, a), a/(1 - z) + z/(1 - z)**2) + assert myexpand(lerchphi(z, -3, a), None) + # polylog reduction + assert myexpand(lerchphi(z, s, S.Half), + 2**(s - 1)*(polylog(s, sqrt(z))/sqrt(z) + - polylog(s, polar_lift(-1)*sqrt(z))/sqrt(z))) + assert myexpand(lerchphi(z, s, 2), -1/z + polylog(s, z)/z**2) + assert myexpand(lerchphi(z, s, Rational(3, 2)), None) + assert myexpand(lerchphi(z, s, Rational(7, 3)), None) + assert myexpand(lerchphi(z, s, Rational(-1, 3)), None) + assert myexpand(lerchphi(z, s, Rational(-5, 2)), None) + + # hurwitz zeta reduction + assert myexpand(lerchphi(-1, s, a), + 2**(-s)*zeta(s, a/2) - 2**(-s)*zeta(s, (a + 1)/2)) + assert myexpand(lerchphi(I, s, a), None) + assert myexpand(lerchphi(-I, s, a), None) + assert myexpand(lerchphi(exp(I*pi*Rational(2, 5)), s, a), None) + + +def test_stieltjes(): + assert isinstance(stieltjes(x), stieltjes) + assert isinstance(stieltjes(x, a), stieltjes) + + # Zero'th constant EulerGamma + assert stieltjes(0) == S.EulerGamma + assert stieltjes(0, 1) == S.EulerGamma + + # Not defined + assert stieltjes(nan) is nan + assert stieltjes(0, nan) is nan + assert stieltjes(-1) is S.ComplexInfinity + assert stieltjes(1.5) is S.ComplexInfinity + assert stieltjes(z, 0) is S.ComplexInfinity + assert stieltjes(z, -1) is S.ComplexInfinity + + +def test_stieltjes_evalf(): + assert abs(stieltjes(0).evalf() - 0.577215664) < 1E-9 + assert abs(stieltjes(0, 0.5).evalf() - 1.963510026) < 1E-9 + assert abs(stieltjes(1, 2).evalf() + 0.072815845) < 1E-9 + + +def test_issue_10475(): + a = Symbol('a', extended_real=True) + b = Symbol('b', extended_positive=True) + s = Symbol('s', zero=False) + + assert zeta(2 + I).is_finite + assert zeta(1).is_finite is False + assert zeta(x).is_finite is None + assert zeta(x + I).is_finite is None + assert zeta(a).is_finite is None + assert zeta(b).is_finite is None + assert zeta(-b).is_finite is True + assert zeta(b**2 - 2*b + 1).is_finite is None + assert zeta(a + I).is_finite is True + assert zeta(b + 1).is_finite is True + assert zeta(s + 1).is_finite is True + + +def test_issue_14177(): + n = Symbol('n', nonnegative=True, integer=True) + + assert zeta(-n).rewrite(bernoulli) == bernoulli(n+1) / (-n-1) + assert zeta(-n, a).rewrite(bernoulli) == bernoulli(n+1, a) / (-n-1) + z2n = -(2*I*pi)**(2*n)*bernoulli(2*n) / (2*factorial(2*n)) + assert zeta(2*n).rewrite(bernoulli) == z2n + assert expand_func(zeta(s, n+1)) == zeta(s) - harmonic(n, s) + assert expand_func(zeta(-b, -n)) is nan + assert expand_func(zeta(-b, n)) == zeta(-b, n) + + n = Symbol('n') + + assert zeta(2*n) == zeta(2*n) # As sign of z (= 2*n) is not determined diff --git a/phivenv/Lib/site-packages/sympy/functions/special/zeta_functions.py b/phivenv/Lib/site-packages/sympy/functions/special/zeta_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..8f410f0f1086de91490c714cd3becf11df9ab189 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/functions/special/zeta_functions.py @@ -0,0 +1,786 @@ +""" Riemann zeta and related function. """ + +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.function import ArgumentIndexError, expand_mul, DefinedFunction +from sympy.core.logic import fuzzy_not +from sympy.core.numbers import pi, I, Integer +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.numbers import bernoulli, factorial, genocchi, harmonic +from sympy.functions.elementary.complexes import re, unpolarify, Abs, polar_lift +from sympy.functions.elementary.exponential import log, exp_polar, exp +from sympy.functions.elementary.integers import ceiling, floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.polys.polytools import Poly + +############################################################################### +###################### LERCH TRANSCENDENT ##################################### +############################################################################### + + +class lerchphi(DefinedFunction): + r""" + Lerch transcendent (Lerch phi function). + + Explanation + =========== + + For $\operatorname{Re}(a) > 0$, $|z| < 1$ and $s \in \mathbb{C}$, the + Lerch transcendent is defined as + + .. math :: \Phi(z, s, a) = \sum_{n=0}^\infty \frac{z^n}{(n + a)^s}, + + where the standard branch of the argument is used for $n + a$, + and by analytic continuation for other values of the parameters. + + A commonly used related function is the Lerch zeta function, defined by + + .. math:: L(q, s, a) = \Phi(e^{2\pi i q}, s, a). + + **Analytic Continuation and Branching Behavior** + + It can be shown that + + .. math:: \Phi(z, s, a) = z\Phi(z, s, a+1) + a^{-s}. + + This provides the analytic continuation to $\operatorname{Re}(a) \le 0$. + + Assume now $\operatorname{Re}(a) > 0$. The integral representation + + .. math:: \Phi_0(z, s, a) = \int_0^\infty \frac{t^{s-1} e^{-at}}{1 - ze^{-t}} + \frac{\mathrm{d}t}{\Gamma(s)} + + provides an analytic continuation to $\mathbb{C} - [1, \infty)$. + Finally, for $x \in (1, \infty)$ we find + + .. math:: \lim_{\epsilon \to 0^+} \Phi_0(x + i\epsilon, s, a) + -\lim_{\epsilon \to 0^+} \Phi_0(x - i\epsilon, s, a) + = \frac{2\pi i \log^{s-1}{x}}{x^a \Gamma(s)}, + + using the standard branch for both $\log{x}$ and + $\log{\log{x}}$ (a branch of $\log{\log{x}}$ is needed to + evaluate $\log{x}^{s-1}$). + This concludes the analytic continuation. The Lerch transcendent is thus + branched at $z \in \{0, 1, \infty\}$ and + $a \in \mathbb{Z}_{\le 0}$. For fixed $z, a$ outside these + branch points, it is an entire function of $s$. + + Examples + ======== + + The Lerch transcendent is a fairly general function, for this reason it does + not automatically evaluate to simpler functions. Use ``expand_func()`` to + achieve this. + + If $z=1$, the Lerch transcendent reduces to the Hurwitz zeta function: + + >>> from sympy import lerchphi, expand_func + >>> from sympy.abc import z, s, a + >>> expand_func(lerchphi(1, s, a)) + zeta(s, a) + + More generally, if $z$ is a root of unity, the Lerch transcendent + reduces to a sum of Hurwitz zeta functions: + + >>> expand_func(lerchphi(-1, s, a)) + zeta(s, a/2)/2**s - zeta(s, a/2 + 1/2)/2**s + + If $a=1$, the Lerch transcendent reduces to the polylogarithm: + + >>> expand_func(lerchphi(z, s, 1)) + polylog(s, z)/z + + More generally, if $a$ is rational, the Lerch transcendent reduces + to a sum of polylogarithms: + + >>> from sympy import S + >>> expand_func(lerchphi(z, s, S(1)/2)) + 2**(s - 1)*(polylog(s, sqrt(z))/sqrt(z) - + polylog(s, sqrt(z)*exp_polar(I*pi))/sqrt(z)) + >>> expand_func(lerchphi(z, s, S(3)/2)) + -2**s/z + 2**(s - 1)*(polylog(s, sqrt(z))/sqrt(z) - + polylog(s, sqrt(z)*exp_polar(I*pi))/sqrt(z))/z + + The derivatives with respect to $z$ and $a$ can be computed in + closed form: + + >>> lerchphi(z, s, a).diff(z) + (-a*lerchphi(z, s, a) + lerchphi(z, s - 1, a))/z + >>> lerchphi(z, s, a).diff(a) + -s*lerchphi(z, s + 1, a) + + See Also + ======== + + polylog, zeta + + References + ========== + + .. [1] Bateman, H.; Erdelyi, A. (1953), Higher Transcendental Functions, + Vol. I, New York: McGraw-Hill. Section 1.11. + .. [2] https://dlmf.nist.gov/25.14 + .. [3] https://en.wikipedia.org/wiki/Lerch_transcendent + + """ + + def _eval_expand_func(self, **hints): + z, s, a = self.args + if z == 1: + return zeta(s, a) + if s.is_Integer and s <= 0: + t = Dummy('t') + p = Poly((t + a)**(-s), t) + start = 1/(1 - t) + res = S.Zero + for c in reversed(p.all_coeffs()): + res += c*start + start = t*start.diff(t) + return res.subs(t, z) + + if a.is_Rational: + # See section 18 of + # Kelly B. Roach. Hypergeometric Function Representations. + # In: Proceedings of the 1997 International Symposium on Symbolic and + # Algebraic Computation, pages 205-211, New York, 1997. ACM. + # TODO should something be polarified here? + add = S.Zero + mul = S.One + # First reduce a to the interaval (0, 1] + if a > 1: + n = floor(a) + if n == a: + n -= 1 + a -= n + mul = z**(-n) + add = Add(*[-z**(k - n)/(a + k)**s for k in range(n)]) + elif a <= 0: + n = floor(-a) + 1 + a += n + mul = z**n + add = Add(*[z**(n - 1 - k)/(a - k - 1)**s for k in range(n)]) + + m, n = S([a.p, a.q]) + zet = exp_polar(2*pi*I/n) + root = z**(1/n) + up_zet = unpolarify(zet) + addargs = [] + for k in range(n): + p = polylog(s, zet**k*root) + if isinstance(p, polylog): + p = p._eval_expand_func(**hints) + addargs.append(p/(up_zet**k*root)**m) + return add + mul*n**(s - 1)*Add(*addargs) + + # TODO use minpoly instead of ad-hoc methods when issue 5888 is fixed + if isinstance(z, exp) and (z.args[0]/(pi*I)).is_Rational or z in [-1, I, -I]: + # TODO reference? + if z == -1: + p, q = S([1, 2]) + elif z == I: + p, q = S([1, 4]) + elif z == -I: + p, q = S([-1, 4]) + else: + arg = z.args[0]/(2*pi*I) + p, q = S([arg.p, arg.q]) + return Add(*[exp(2*pi*I*k*p/q)/q**s*zeta(s, (k + a)/q) + for k in range(q)]) + + return lerchphi(z, s, a) + + def fdiff(self, argindex=1): + z, s, a = self.args + if argindex == 3: + return -s*lerchphi(z, s + 1, a) + elif argindex == 1: + return (lerchphi(z, s - 1, a) - a*lerchphi(z, s, a))/z + else: + raise ArgumentIndexError + + def _eval_rewrite_helper(self, target): + res = self._eval_expand_func() + if res.has(target): + return res + else: + return self + + def _eval_rewrite_as_zeta(self, z, s, a, **kwargs): + return self._eval_rewrite_helper(zeta) + + def _eval_rewrite_as_polylog(self, z, s, a, **kwargs): + return self._eval_rewrite_helper(polylog) + +############################################################################### +###################### POLYLOGARITHM ########################################## +############################################################################### + + +class polylog(DefinedFunction): + r""" + Polylogarithm function. + + Explanation + =========== + + For $|z| < 1$ and $s \in \mathbb{C}$, the polylogarithm is + defined by + + .. math:: \operatorname{Li}_s(z) = \sum_{n=1}^\infty \frac{z^n}{n^s}, + + where the standard branch of the argument is used for $n$. It admits + an analytic continuation which is branched at $z=1$ (notably not on the + sheet of initial definition), $z=0$ and $z=\infty$. + + The name polylogarithm comes from the fact that for $s=1$, the + polylogarithm is related to the ordinary logarithm (see examples), and that + + .. math:: \operatorname{Li}_{s+1}(z) = + \int_0^z \frac{\operatorname{Li}_s(t)}{t} \mathrm{d}t. + + The polylogarithm is a special case of the Lerch transcendent: + + .. math:: \operatorname{Li}_{s}(z) = z \Phi(z, s, 1). + + Examples + ======== + + For $z \in \{0, 1, -1\}$, the polylogarithm is automatically expressed + using other functions: + + >>> from sympy import polylog + >>> from sympy.abc import s + >>> polylog(s, 0) + 0 + >>> polylog(s, 1) + zeta(s) + >>> polylog(s, -1) + -dirichlet_eta(s) + + If $s$ is a negative integer, $0$ or $1$, the polylogarithm can be + expressed using elementary functions. This can be done using + ``expand_func()``: + + >>> from sympy import expand_func + >>> from sympy.abc import z + >>> expand_func(polylog(1, z)) + -log(1 - z) + >>> expand_func(polylog(0, z)) + z/(1 - z) + + The derivative with respect to $z$ can be computed in closed form: + + >>> polylog(s, z).diff(z) + polylog(s - 1, z)/z + + The polylogarithm can be expressed in terms of the lerch transcendent: + + >>> from sympy import lerchphi + >>> polylog(s, z).rewrite(lerchphi) + z*lerchphi(z, s, 1) + + See Also + ======== + + zeta, lerchphi + + """ + + @classmethod + def eval(cls, s, z): + if z.is_number: + if z is S.One: + return zeta(s) + elif z is S.NegativeOne: + return -dirichlet_eta(s) + elif z is S.Zero: + return S.Zero + elif s == 2: + dilogtable = _dilogtable() + if z in dilogtable: + return dilogtable[z] + + if z.is_zero: + return S.Zero + + # Make an effort to determine if z is 1 to avoid replacing into + # expression with singularity + zone = z.equals(S.One) + + if zone: + return zeta(s) + elif zone is False: + # For s = 0 or -1 use explicit formulas to evaluate, but + # automatically expanding polylog(1, z) to -log(1-z) seems + # undesirable for summation methods based on hypergeometric + # functions + if s is S.Zero: + return z/(1 - z) + elif s is S.NegativeOne: + return z/(1 - z)**2 + if s.is_zero: + return z/(1 - z) + + # polylog is branched, but not over the unit disk + if z.has(exp_polar, polar_lift) and (zone or (Abs(z) <= S.One) == True): + return cls(s, unpolarify(z)) + + def fdiff(self, argindex=1): + s, z = self.args + if argindex == 2: + return polylog(s - 1, z)/z + raise ArgumentIndexError + + def _eval_rewrite_as_lerchphi(self, s, z, **kwargs): + return z*lerchphi(z, s, 1) + + def _eval_expand_func(self, **hints): + s, z = self.args + if s == 1: + return -log(1 - z) + if s.is_Integer and s <= 0: + u = Dummy('u') + start = u/(1 - u) + for _ in range(-s): + start = u*start.diff(u) + return expand_mul(start).subs(u, z) + return polylog(s, z) + + def _eval_is_zero(self): + z = self.args[1] + if z.is_zero: + return True + + def _eval_nseries(self, x, n, logx, cdir=0): + from sympy.series.order import Order + nu, z = self.args + + z0 = z.subs(x, 0) + if z0 is S.NaN: + z0 = z.limit(x, 0, dir='-' if re(cdir).is_negative else '+') + + if z0.is_zero: + # In case of powers less than 1, number of terms need to be computed + # separately to avoid repeated callings of _eval_nseries with wrong n + try: + _, exp = z.leadterm(x) + except (ValueError, NotImplementedError): + return self + + if exp.is_positive: + newn = ceiling(n/exp) + o = Order(x**n, x) + r = z._eval_nseries(x, n, logx, cdir).removeO() + if r is S.Zero: + return o + + term = r + s = [term] + for k in range(2, newn): + term *= r + s.append(term/k**nu) + return Add(*s) + o + + return super(polylog, self)._eval_nseries(x, n, logx, cdir) + +############################################################################### +###################### HURWITZ GENERALIZED ZETA FUNCTION ###################### +############################################################################### + + +class zeta(DefinedFunction): + r""" + Hurwitz zeta function (or Riemann zeta function). + + Explanation + =========== + + For $\operatorname{Re}(a) > 0$ and $\operatorname{Re}(s) > 1$, this + function is defined as + + .. math:: \zeta(s, a) = \sum_{n=0}^\infty \frac{1}{(n + a)^s}, + + where the standard choice of argument for $n + a$ is used. For fixed + $a$ not a nonpositive integer the Hurwitz zeta function admits a + meromorphic continuation to all of $\mathbb{C}$; it is an unbranched + function with a simple pole at $s = 1$. + + The Hurwitz zeta function is a special case of the Lerch transcendent: + + .. math:: \zeta(s, a) = \Phi(1, s, a). + + This formula defines an analytic continuation for all possible values of + $s$ and $a$ (also $\operatorname{Re}(a) < 0$), see the documentation of + :class:`lerchphi` for a description of the branching behavior. + + If no value is passed for $a$ a default value of $a = 1$ is assumed, + yielding the Riemann zeta function. + + Examples + ======== + + For $a = 1$ the Hurwitz zeta function reduces to the famous Riemann + zeta function: + + .. math:: \zeta(s, 1) = \zeta(s) = \sum_{n=1}^\infty \frac{1}{n^s}. + + >>> from sympy import zeta + >>> from sympy.abc import s + >>> zeta(s, 1) + zeta(s) + >>> zeta(s) + zeta(s) + + The Riemann zeta function can also be expressed using the Dirichlet eta + function: + + >>> from sympy import dirichlet_eta + >>> zeta(s).rewrite(dirichlet_eta) + dirichlet_eta(s)/(1 - 2**(1 - s)) + + The Riemann zeta function at nonnegative even and negative integer + values is related to the Bernoulli numbers and polynomials: + + >>> zeta(2) + pi**2/6 + >>> zeta(4) + pi**4/90 + >>> zeta(0) + -1/2 + >>> zeta(-1) + -1/12 + >>> zeta(-4) + 0 + + The specific formulae are: + + .. math:: \zeta(2n) = -\frac{(2\pi i)^{2n} B_{2n}}{2(2n)!} + .. math:: \zeta(-n,a) = -\frac{B_{n+1}(a)}{n+1} + + No closed-form expressions are known at positive odd integers, but + numerical evaluation is possible: + + >>> zeta(3).n() + 1.20205690315959 + + The derivative of $\zeta(s, a)$ with respect to $a$ can be computed: + + >>> from sympy.abc import a + >>> zeta(s, a).diff(a) + -s*zeta(s + 1, a) + + However the derivative with respect to $s$ has no useful closed form + expression: + + >>> zeta(s, a).diff(s) + Derivative(zeta(s, a), s) + + The Hurwitz zeta function can be expressed in terms of the Lerch + transcendent, :class:`~.lerchphi`: + + >>> from sympy import lerchphi + >>> zeta(s, a).rewrite(lerchphi) + lerchphi(1, s, a) + + See Also + ======== + + dirichlet_eta, lerchphi, polylog + + References + ========== + + .. [1] https://dlmf.nist.gov/25.11 + .. [2] https://en.wikipedia.org/wiki/Hurwitz_zeta_function + + """ + + @classmethod + def eval(cls, s, a=None): + if a is S.One: + return cls(s) + elif s is S.NaN or a is S.NaN: + return S.NaN + elif s is S.One: + return S.ComplexInfinity + elif s is S.Infinity: + return S.One + elif a is S.Infinity: + return S.Zero + + sint = s.is_Integer + if a is None: + a = S.One + if sint and s.is_nonpositive: + return bernoulli(1-s, a) / (s-1) + elif a is S.One: + if sint and s.is_even: + return -(2*pi*I)**s * bernoulli(s) / (2*factorial(s)) + elif sint and a.is_Integer and a.is_positive: + return cls(s) - harmonic(a-1, s) + elif a.is_Integer and a.is_nonpositive and \ + (s.is_integer is False or s.is_nonpositive is False): + return S.NaN + + def _eval_rewrite_as_bernoulli(self, s, a=1, **kwargs): + if a == 1 and s.is_integer and s.is_nonnegative and s.is_even: + return -(2*pi*I)**s * bernoulli(s) / (2*factorial(s)) + return bernoulli(1-s, a) / (s-1) + + def _eval_rewrite_as_dirichlet_eta(self, s, a=1, **kwargs): + if a != 1: + return self + s = self.args[0] + return dirichlet_eta(s)/(1 - 2**(1 - s)) + + def _eval_rewrite_as_lerchphi(self, s, a=1, **kwargs): + return lerchphi(1, s, a) + + def _eval_is_finite(self): + return fuzzy_not((self.args[0] - 1).is_zero) + + def _eval_expand_func(self, **hints): + s = self.args[0] + a = self.args[1] if len(self.args) > 1 else S.One + if a.is_integer: + if a.is_positive: + return zeta(s) - harmonic(a-1, s) + if a.is_nonpositive and (s.is_integer is False or + s.is_nonpositive is False): + return S.NaN + return self + + def fdiff(self, argindex=1): + if len(self.args) == 2: + s, a = self.args + else: + s, a = self.args + (1,) + if argindex == 2: + return -s*zeta(s + 1, a) + else: + raise ArgumentIndexError + + def _eval_as_leading_term(self, x, logx, cdir): + if len(self.args) == 2: + s, a = self.args + else: + s, a = self.args + (S.One,) + + try: + c, e = a.leadterm(x) + except NotImplementedError: + return self + + if e.is_negative and not s.is_positive: + raise NotImplementedError + + return super(zeta, self)._eval_as_leading_term(x, logx=logx, cdir=cdir) + + +class dirichlet_eta(DefinedFunction): + r""" + Dirichlet eta function. + + Explanation + =========== + + For $\operatorname{Re}(s) > 0$ and $0 < x \le 1$, this function is defined as + + .. math:: \eta(s, a) = \sum_{n=0}^\infty \frac{(-1)^n}{(n+a)^s}. + + It admits a unique analytic continuation to all of $\mathbb{C}$ for any + fixed $a$ not a nonpositive integer. It is an entire, unbranched function. + + It can be expressed using the Hurwitz zeta function as + + .. math:: \eta(s, a) = \zeta(s,a) - 2^{1-s} \zeta\left(s, \frac{a+1}{2}\right) + + and using the generalized Genocchi function as + + .. math:: \eta(s, a) = \frac{G(1-s, a)}{2(s-1)}. + + In both cases the limiting value of $\log2 - \psi(a) + \psi\left(\frac{a+1}{2}\right)$ + is used when $s = 1$. + + Examples + ======== + + >>> from sympy import dirichlet_eta, zeta + >>> from sympy.abc import s + >>> dirichlet_eta(s).rewrite(zeta) + Piecewise((log(2), Eq(s, 1)), ((1 - 2**(1 - s))*zeta(s), True)) + + See Also + ======== + + zeta + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Dirichlet_eta_function + .. [2] Peter Luschny, "An introduction to the Bernoulli function", + https://arxiv.org/abs/2009.06743 + + """ + + @classmethod + def eval(cls, s, a=None): + if a is S.One: + return cls(s) + if a is None: + if s == 1: + return log(2) + z = zeta(s) + if not z.has(zeta): + return (1 - 2**(1-s)) * z + return + elif s == 1: + from sympy.functions.special.gamma_functions import digamma + return log(2) - digamma(a) + digamma((a+1)/2) + z1 = zeta(s, a) + z2 = zeta(s, (a+1)/2) + if not z1.has(zeta) and not z2.has(zeta): + return z1 - 2**(1-s) * z2 + + def _eval_rewrite_as_zeta(self, s, a=1, **kwargs): + from sympy.functions.special.gamma_functions import digamma + if a == 1: + return Piecewise((log(2), Eq(s, 1)), ((1 - 2**(1-s)) * zeta(s), True)) + return Piecewise((log(2) - digamma(a) + digamma((a+1)/2), Eq(s, 1)), + (zeta(s, a) - 2**(1-s) * zeta(s, (a+1)/2), True)) + + def _eval_rewrite_as_genocchi(self, s, a=S.One, **kwargs): + from sympy.functions.special.gamma_functions import digamma + return Piecewise((log(2) - digamma(a) + digamma((a+1)/2), Eq(s, 1)), + (genocchi(1-s, a) / (2 * (s-1)), True)) + + def _eval_evalf(self, prec): + if all(i.is_number for i in self.args): + return self.rewrite(zeta)._eval_evalf(prec) + + +class riemann_xi(DefinedFunction): + r""" + Riemann Xi function. + + Examples + ======== + + The Riemann Xi function is closely related to the Riemann zeta function. + The zeros of Riemann Xi function are precisely the non-trivial zeros + of the zeta function. + + >>> from sympy import riemann_xi, zeta + >>> from sympy.abc import s + >>> riemann_xi(s).rewrite(zeta) + s*(s - 1)*gamma(s/2)*zeta(s)/(2*pi**(s/2)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Riemann_Xi_function + + """ + + + @classmethod + def eval(cls, s): + from sympy.functions.special.gamma_functions import gamma + z = zeta(s) + if s in (S.Zero, S.One): + return S.Half + + if not isinstance(z, zeta): + return s*(s - 1)*gamma(s/2)*z/(2*pi**(s/2)) + + def _eval_rewrite_as_zeta(self, s, **kwargs): + from sympy.functions.special.gamma_functions import gamma + return s*(s - 1)*gamma(s/2)*zeta(s)/(2*pi**(s/2)) + + +class stieltjes(DefinedFunction): + r""" + Represents Stieltjes constants, $\gamma_{k}$ that occur in + Laurent Series expansion of the Riemann zeta function. + + Examples + ======== + + >>> from sympy import stieltjes + >>> from sympy.abc import n, m + >>> stieltjes(n) + stieltjes(n) + + The zero'th stieltjes constant: + + >>> stieltjes(0) + EulerGamma + >>> stieltjes(0, 1) + EulerGamma + + For generalized stieltjes constants: + + >>> stieltjes(n, m) + stieltjes(n, m) + + Constants are only defined for integers >= 0: + + >>> stieltjes(-1) + zoo + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Stieltjes_constants + + """ + + @classmethod + def eval(cls, n, a=None): + if a is not None: + a = sympify(a) + if a is S.NaN: + return S.NaN + if a.is_Integer and a.is_nonpositive: + return S.ComplexInfinity + + if n.is_Number: + if n is S.NaN: + return S.NaN + elif n < 0: + return S.ComplexInfinity + elif not n.is_Integer: + return S.ComplexInfinity + elif n is S.Zero and a in [None, 1]: + return S.EulerGamma + + if n.is_extended_negative: + return S.ComplexInfinity + + if n.is_zero and a in [None, 1]: + return S.EulerGamma + + if n.is_integer == False: + return S.ComplexInfinity + + +@cacheit +def _dilogtable(): + return { + S.Half: pi**2/12 - log(2)**2/2, + Integer(2) : pi**2/4 - I*pi*log(2), + -(sqrt(5) - 1)/2 : -pi**2/15 + log((sqrt(5)-1)/2)**2/2, + -(sqrt(5) + 1)/2 : -pi**2/10 - log((sqrt(5)+1)/2)**2, + (3 - sqrt(5))/2 : pi**2/15 - log((sqrt(5)-1)/2)**2, + (sqrt(5) - 1)/2 : pi**2/10 - log((sqrt(5)-1)/2)**2, + I : I*S.Catalan - pi**2/48, + -I : -I*S.Catalan - pi**2/48, + 1 - I : pi**2/16 - I*S.Catalan - pi*I/4*log(2), + 1 + I : pi**2/16 + I*S.Catalan + pi*I/4*log(2), + (1 - I)/2 : -log(2)**2/8 + pi*I*log(2)/8 + 5*pi**2/96 - I*S.Catalan + } diff --git a/phivenv/Lib/site-packages/sympy/galgebra.py b/phivenv/Lib/site-packages/sympy/galgebra.py new file mode 100644 index 0000000000000000000000000000000000000000..be812bfcbcece834c86d7b55eb846286acd0ac08 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/galgebra.py @@ -0,0 +1 @@ +raise ImportError("""As of SymPy 1.0 the galgebra module is maintained separately at https://github.com/pygae/galgebra""") diff --git a/phivenv/Lib/site-packages/sympy/geometry/__init__.py b/phivenv/Lib/site-packages/sympy/geometry/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bb85d4ff5d53eb44a039a95cfc2fff687322cc76 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/__init__.py @@ -0,0 +1,45 @@ +""" +A geometry module for the SymPy library. This module contains all of the +entities and functions needed to construct basic geometrical data and to +perform simple informational queries. + +Usage: +====== + +Examples +======== + +""" +from sympy.geometry.point import Point, Point2D, Point3D +from sympy.geometry.line import Line, Ray, Segment, Line2D, Segment2D, Ray2D, \ + Line3D, Segment3D, Ray3D +from sympy.geometry.plane import Plane +from sympy.geometry.ellipse import Ellipse, Circle +from sympy.geometry.polygon import Polygon, RegularPolygon, Triangle, rad, deg +from sympy.geometry.util import are_similar, centroid, convex_hull, idiff, \ + intersection, closest_points, farthest_points +from sympy.geometry.exceptions import GeometryError +from sympy.geometry.curve import Curve +from sympy.geometry.parabola import Parabola + +__all__ = [ + 'Point', 'Point2D', 'Point3D', + + 'Line', 'Ray', 'Segment', 'Line2D', 'Segment2D', 'Ray2D', 'Line3D', + 'Segment3D', 'Ray3D', + + 'Plane', + + 'Ellipse', 'Circle', + + 'Polygon', 'RegularPolygon', 'Triangle', 'rad', 'deg', + + 'are_similar', 'centroid', 'convex_hull', 'idiff', 'intersection', + 'closest_points', 'farthest_points', + + 'GeometryError', + + 'Curve', + + 'Parabola', +] diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f795e8969d2cb8cd9d1d752433665f90db040fb1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/curve.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/curve.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b43fa0cb35a512cab4a53ba2a8ed50d9c0e887f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/curve.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/ellipse.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/ellipse.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a21d656a65195fe85a2ef3763cdef48e1322d1d7 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/ellipse.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/entity.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/entity.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd4e451444ba7f2d05a0425198bd7623a891489d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/entity.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/exceptions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d291372089004f07c44c5c34b3487bf89aee274b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/exceptions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/line.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/line.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd3fa1e9f2bd167d3d3ff346591cabc988836a3d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/line.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/parabola.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/parabola.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..611dba2dfd8aabc65c4d93058c0c1fc8b8660aa2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/parabola.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/plane.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/plane.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28dff06cf0ddd273a8a5d53e3d235239a7bfe145 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/plane.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/point.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/point.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58df6b050760ce0dc5a0b138f01daf0043879317 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/point.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/polygon.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/polygon.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eafeb9d67388725084c18b732df9f4fd56760655 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/polygon.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/__pycache__/util.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..580e6b48f0b680a60c9f767c4c07fbfbaf2a6cc5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/__pycache__/util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/curve.py b/phivenv/Lib/site-packages/sympy/geometry/curve.py new file mode 100644 index 0000000000000000000000000000000000000000..c074f22cad79b1261ad44be4ccface972cdd3b82 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/curve.py @@ -0,0 +1,424 @@ +"""Curves in 2-dimensional Euclidean space. + +Contains +======== +Curve + +""" + +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.core import diff +from sympy.core.containers import Tuple +from sympy.core.symbol import _symbol +from sympy.geometry.entity import GeometryEntity, GeometrySet +from sympy.geometry.point import Point +from sympy.integrals import integrate +from sympy.matrices import Matrix, rot_axis3 +from sympy.utilities.iterables import is_sequence + +from mpmath.libmp.libmpf import prec_to_dps + + +class Curve(GeometrySet): + """A curve in space. + + A curve is defined by parametric functions for the coordinates, a + parameter and the lower and upper bounds for the parameter value. + + Parameters + ========== + + function : list of functions + limits : 3-tuple + Function parameter and lower and upper bounds. + + Attributes + ========== + + functions + parameter + limits + + Raises + ====== + + ValueError + When `functions` are specified incorrectly. + When `limits` are specified incorrectly. + + Examples + ======== + + >>> from sympy import Curve, sin, cos, interpolate + >>> from sympy.abc import t, a + >>> C = Curve((sin(t), cos(t)), (t, 0, 2)) + >>> C.functions + (sin(t), cos(t)) + >>> C.limits + (t, 0, 2) + >>> C.parameter + t + >>> C = Curve((t, interpolate([1, 4, 9, 16], t)), (t, 0, 1)); C + Curve((t, t**2), (t, 0, 1)) + >>> C.subs(t, 4) + Point2D(4, 16) + >>> C.arbitrary_point(a) + Point2D(a, a**2) + + See Also + ======== + + sympy.core.function.Function + sympy.polys.polyfuncs.interpolate + + """ + + def __new__(cls, function, limits): + if not is_sequence(function) or len(function) != 2: + raise ValueError("Function argument should be (x(t), y(t)) " + "but got %s" % str(function)) + if not is_sequence(limits) or len(limits) != 3: + raise ValueError("Limit argument should be (t, tmin, tmax) " + "but got %s" % str(limits)) + + return GeometryEntity.__new__(cls, Tuple(*function), Tuple(*limits)) + + def __call__(self, f): + return self.subs(self.parameter, f) + + def _eval_subs(self, old, new): + if old == self.parameter: + return Point(*[f.subs(old, new) for f in self.functions]) + + def _eval_evalf(self, prec=15, **options): + f, (t, a, b) = self.args + dps = prec_to_dps(prec) + f = tuple([i.evalf(n=dps, **options) for i in f]) + a, b = [i.evalf(n=dps, **options) for i in (a, b)] + return self.func(f, (t, a, b)) + + def arbitrary_point(self, parameter='t'): + """A parameterized point on the curve. + + Parameters + ========== + + parameter : str or Symbol, optional + Default value is 't'. + The Curve's parameter is selected with None or self.parameter + otherwise the provided symbol is used. + + Returns + ======= + + Point : + Returns a point in parametric form. + + Raises + ====== + + ValueError + When `parameter` already appears in the functions. + + Examples + ======== + + >>> from sympy import Curve, Symbol + >>> from sympy.abc import s + >>> C = Curve([2*s, s**2], (s, 0, 2)) + >>> C.arbitrary_point() + Point2D(2*t, t**2) + >>> C.arbitrary_point(C.parameter) + Point2D(2*s, s**2) + >>> C.arbitrary_point(None) + Point2D(2*s, s**2) + >>> C.arbitrary_point(Symbol('a')) + Point2D(2*a, a**2) + + See Also + ======== + + sympy.geometry.point.Point + + """ + if parameter is None: + return Point(*self.functions) + + tnew = _symbol(parameter, self.parameter, real=True) + t = self.parameter + if (tnew.name != t.name and + tnew.name in (f.name for f in self.free_symbols)): + raise ValueError('Symbol %s already appears in object ' + 'and cannot be used as a parameter.' % tnew.name) + return Point(*[w.subs(t, tnew) for w in self.functions]) + + @property + def free_symbols(self): + """Return a set of symbols other than the bound symbols used to + parametrically define the Curve. + + Returns + ======= + + set : + Set of all non-parameterized symbols. + + Examples + ======== + + >>> from sympy.abc import t, a + >>> from sympy import Curve + >>> Curve((t, t**2), (t, 0, 2)).free_symbols + set() + >>> Curve((t, t**2), (t, a, 2)).free_symbols + {a} + + """ + free = set() + for a in self.functions + self.limits[1:]: + free |= a.free_symbols + free = free.difference({self.parameter}) + return free + + @property + def ambient_dimension(self): + """The dimension of the curve. + + Returns + ======= + + int : + the dimension of curve. + + Examples + ======== + + >>> from sympy.abc import t + >>> from sympy import Curve + >>> C = Curve((t, t**2), (t, 0, 2)) + >>> C.ambient_dimension + 2 + + """ + + return len(self.args[0]) + + @property + def functions(self): + """The functions specifying the curve. + + Returns + ======= + + functions : + list of parameterized coordinate functions. + + Examples + ======== + + >>> from sympy.abc import t + >>> from sympy import Curve + >>> C = Curve((t, t**2), (t, 0, 2)) + >>> C.functions + (t, t**2) + + See Also + ======== + + parameter + + """ + return self.args[0] + + @property + def limits(self): + """The limits for the curve. + + Returns + ======= + + limits : tuple + Contains parameter and lower and upper limits. + + Examples + ======== + + >>> from sympy.abc import t + >>> from sympy import Curve + >>> C = Curve([t, t**3], (t, -2, 2)) + >>> C.limits + (t, -2, 2) + + See Also + ======== + + plot_interval + + """ + return self.args[1] + + @property + def parameter(self): + """The curve function variable. + + Returns + ======= + + Symbol : + returns a bound symbol. + + Examples + ======== + + >>> from sympy.abc import t + >>> from sympy import Curve + >>> C = Curve([t, t**2], (t, 0, 2)) + >>> C.parameter + t + + See Also + ======== + + functions + + """ + return self.args[1][0] + + @property + def length(self): + """The curve length. + + Examples + ======== + + >>> from sympy import Curve + >>> from sympy.abc import t + >>> Curve((t, t), (t, 0, 1)).length + sqrt(2) + + """ + integrand = sqrt(sum(diff(func, self.limits[0])**2 for func in self.functions)) + return integrate(integrand, self.limits) + + def plot_interval(self, parameter='t'): + """The plot interval for the default geometric plot of the curve. + + Parameters + ========== + + parameter : str or Symbol, optional + Default value is 't'; + otherwise the provided symbol is used. + + Returns + ======= + + List : + the plot interval as below: + [parameter, lower_bound, upper_bound] + + Examples + ======== + + >>> from sympy import Curve, sin + >>> from sympy.abc import x, s + >>> Curve((x, sin(x)), (x, 1, 2)).plot_interval() + [t, 1, 2] + >>> Curve((x, sin(x)), (x, 1, 2)).plot_interval(s) + [s, 1, 2] + + See Also + ======== + + limits : Returns limits of the parameter interval + + """ + t = _symbol(parameter, self.parameter, real=True) + return [t] + list(self.limits[1:]) + + def rotate(self, angle=0, pt=None): + """This function is used to rotate a curve along given point ``pt`` at given angle(in radian). + + Parameters + ========== + + angle : + the angle at which the curve will be rotated(in radian) in counterclockwise direction. + default value of angle is 0. + + pt : Point + the point along which the curve will be rotated. + If no point given, the curve will be rotated around origin. + + Returns + ======= + + Curve : + returns a curve rotated at given angle along given point. + + Examples + ======== + + >>> from sympy import Curve, pi + >>> from sympy.abc import x + >>> Curve((x, x), (x, 0, 1)).rotate(pi/2) + Curve((-x, x), (x, 0, 1)) + + """ + if pt: + pt = -Point(pt, dim=2) + else: + pt = Point(0,0) + rv = self.translate(*pt.args) + f = list(rv.functions) + f.append(0) + f = Matrix(1, 3, f) + f *= rot_axis3(angle) + rv = self.func(f[0, :2].tolist()[0], self.limits) + pt = -pt + return rv.translate(*pt.args) + + def scale(self, x=1, y=1, pt=None): + """Override GeometryEntity.scale since Curve is not made up of Points. + + Returns + ======= + + Curve : + returns scaled curve. + + Examples + ======== + + >>> from sympy import Curve + >>> from sympy.abc import x + >>> Curve((x, x), (x, 0, 1)).scale(2) + Curve((2*x, x), (x, 0, 1)) + + """ + if pt: + pt = Point(pt, dim=2) + return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) + fx, fy = self.functions + return self.func((fx*x, fy*y), self.limits) + + def translate(self, x=0, y=0): + """Translate the Curve by (x, y). + + Returns + ======= + + Curve : + returns a translated curve. + + Examples + ======== + + >>> from sympy import Curve + >>> from sympy.abc import x + >>> Curve((x, x), (x, 0, 1)).translate(1, 2) + Curve((x + 1, x + 2), (x, 0, 1)) + + """ + fx, fy = self.functions + return self.func((fx + x, fy + y), self.limits) diff --git a/phivenv/Lib/site-packages/sympy/geometry/ellipse.py b/phivenv/Lib/site-packages/sympy/geometry/ellipse.py new file mode 100644 index 0000000000000000000000000000000000000000..199db25fde9b019893a275d69959154990e8a4a7 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/ellipse.py @@ -0,0 +1,1768 @@ +"""Elliptical geometrical entities. + +Contains +* Ellipse +* Circle + +""" + +from sympy.core.expr import Expr +from sympy.core.relational import Eq +from sympy.core import S, pi, sympify +from sympy.core.evalf import N +from sympy.core.parameters import global_parameters +from sympy.core.logic import fuzzy_bool +from sympy.core.numbers import Rational, oo +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy, uniquely_named_symbol, _symbol +from sympy.simplify.simplify import simplify +from sympy.simplify.trigsimp import trigsimp +from sympy.functions.elementary.miscellaneous import sqrt, Max +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.functions.special.elliptic_integrals import elliptic_e +from .entity import GeometryEntity, GeometrySet +from .exceptions import GeometryError +from .line import Line, Segment, Ray2D, Segment2D, Line2D, LinearEntity3D +from .point import Point, Point2D, Point3D +from .util import idiff, find +from sympy.polys import DomainError, Poly, PolynomialError +from sympy.polys.polyutils import _not_a_coeff, _nsort +from sympy.solvers import solve +from sympy.solvers.solveset import linear_coeffs +from sympy.utilities.misc import filldedent, func_name + +from mpmath.libmp.libmpf import prec_to_dps + +import random + +x, y = [Dummy('ellipse_dummy', real=True) for i in range(2)] + + +class Ellipse(GeometrySet): + """An elliptical GeometryEntity. + + Parameters + ========== + + center : Point, optional + Default value is Point(0, 0) + hradius : number or SymPy expression, optional + vradius : number or SymPy expression, optional + eccentricity : number or SymPy expression, optional + Two of `hradius`, `vradius` and `eccentricity` must be supplied to + create an Ellipse. The third is derived from the two supplied. + + Attributes + ========== + + center + hradius + vradius + area + circumference + eccentricity + periapsis + apoapsis + focus_distance + foci + + Raises + ====== + + GeometryError + When `hradius`, `vradius` and `eccentricity` are incorrectly supplied + as parameters. + TypeError + When `center` is not a Point. + + See Also + ======== + + Circle + + Notes + ----- + Constructed from a center and two radii, the first being the horizontal + radius (along the x-axis) and the second being the vertical radius (along + the y-axis). + + When symbolic value for hradius and vradius are used, any calculation that + refers to the foci or the major or minor axis will assume that the ellipse + has its major radius on the x-axis. If this is not true then a manual + rotation is necessary. + + Examples + ======== + + >>> from sympy import Ellipse, Point, Rational + >>> e1 = Ellipse(Point(0, 0), 5, 1) + >>> e1.hradius, e1.vradius + (5, 1) + >>> e2 = Ellipse(Point(3, 1), hradius=3, eccentricity=Rational(4, 5)) + >>> e2 + Ellipse(Point2D(3, 1), 3, 9/5) + + """ + + def __contains__(self, o): + if isinstance(o, Point): + res = self.equation(x, y).subs({x: o.x, y: o.y}) + return trigsimp(simplify(res)) is S.Zero + elif isinstance(o, Ellipse): + return self == o + return False + + def __eq__(self, o): + """Is the other GeometryEntity the same as this ellipse?""" + return isinstance(o, Ellipse) and (self.center == o.center and + self.hradius == o.hradius and + self.vradius == o.vradius) + + def __hash__(self): + return super().__hash__() + + def __new__( + cls, center=None, hradius=None, vradius=None, eccentricity=None, **kwargs): + + hradius = sympify(hradius) + vradius = sympify(vradius) + + if center is None: + center = Point(0, 0) + else: + if len(center) != 2: + raise ValueError('The center of "{}" must be a two dimensional point'.format(cls)) + center = Point(center, dim=2) + + if len(list(filter(lambda x: x is not None, (hradius, vradius, eccentricity)))) != 2: + raise ValueError(filldedent(''' + Exactly two arguments of "hradius", "vradius", and + "eccentricity" must not be None.''')) + + if eccentricity is not None: + eccentricity = sympify(eccentricity) + if eccentricity.is_negative: + raise GeometryError("Eccentricity of ellipse/circle should lie between [0, 1)") + elif hradius is None: + hradius = vradius / sqrt(1 - eccentricity**2) + elif vradius is None: + vradius = hradius * sqrt(1 - eccentricity**2) + + if hradius == vradius: + return Circle(center, hradius, **kwargs) + + if S.Zero in (hradius, vradius): + return Segment(Point(center[0] - hradius, center[1] - vradius), Point(center[0] + hradius, center[1] + vradius)) + + if hradius.is_real is False or vradius.is_real is False: + raise GeometryError("Invalid value encountered when computing hradius / vradius.") + + return GeometryEntity.__new__(cls, center, hradius, vradius, **kwargs) + + def _svg(self, scale_factor=1., fill_color="#66cc99"): + """Returns SVG ellipse element for the Ellipse. + + Parameters + ========== + + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + fill_color : str, optional + Hex string for fill color. Default is "#66cc99". + """ + + c = N(self.center) + h, v = N(self.hradius), N(self.vradius) + return ( + '' + ).format(2. * scale_factor, fill_color, c.x, c.y, h, v) + + @property + def ambient_dimension(self): + return 2 + + @property + def apoapsis(self): + """The apoapsis of the ellipse. + + The greatest distance between the focus and the contour. + + Returns + ======= + + apoapsis : number + + See Also + ======== + + periapsis : Returns shortest distance between foci and contour + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.apoapsis + 2*sqrt(2) + 3 + + """ + return self.major * (1 + self.eccentricity) + + def arbitrary_point(self, parameter='t'): + """A parameterized point on the ellipse. + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + arbitrary_point : Point + + Raises + ====== + + ValueError + When `parameter` already appears in the functions. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> e1 = Ellipse(Point(0, 0), 3, 2) + >>> e1.arbitrary_point() + Point2D(3*cos(t), 2*sin(t)) + + """ + t = _symbol(parameter, real=True) + if t.name in (f.name for f in self.free_symbols): + raise ValueError(filldedent('Symbol %s already appears in object ' + 'and cannot be used as a parameter.' % t.name)) + return Point(self.center.x + self.hradius*cos(t), + self.center.y + self.vradius*sin(t)) + + @property + def area(self): + """The area of the ellipse. + + Returns + ======= + + area : number + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.area + 3*pi + + """ + return simplify(S.Pi * self.hradius * self.vradius) + + @property + def bounds(self): + """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding + rectangle for the geometric figure. + + """ + + h, v = self.hradius, self.vradius + return (self.center.x - h, self.center.y - v, self.center.x + h, self.center.y + v) + + @property + def center(self): + """The center of the ellipse. + + Returns + ======= + + center : number + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.center + Point2D(0, 0) + + """ + return self.args[0] + + @property + def circumference(self): + """The circumference of the ellipse. + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.circumference + 12*elliptic_e(8/9) + + """ + if self.eccentricity == 1: + # degenerate + return 4*self.major + elif self.eccentricity == 0: + # circle + return 2*pi*self.hradius + else: + return 4*self.major*elliptic_e(self.eccentricity**2) + + @property + def eccentricity(self): + """The eccentricity of the ellipse. + + Returns + ======= + + eccentricity : number + + Examples + ======== + + >>> from sympy import Point, Ellipse, sqrt + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, sqrt(2)) + >>> e1.eccentricity + sqrt(7)/3 + + """ + return self.focus_distance / self.major + + def encloses_point(self, p): + """ + Return True if p is enclosed by (is inside of) self. + + Notes + ----- + Being on the border of self is considered False. + + Parameters + ========== + + p : Point + + Returns + ======= + + encloses_point : True, False or None + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Ellipse, S + >>> from sympy.abc import t + >>> e = Ellipse((0, 0), 3, 2) + >>> e.encloses_point((0, 0)) + True + >>> e.encloses_point(e.arbitrary_point(t).subs(t, S.Half)) + False + >>> e.encloses_point((4, 0)) + False + + """ + p = Point(p, dim=2) + if p in self: + return False + + if len(self.foci) == 2: + # if the combined distance from the foci to p (h1 + h2) is less + # than the combined distance from the foci to the minor axis + # (which is the same as the major axis length) then p is inside + # the ellipse + h1, h2 = [f.distance(p) for f in self.foci] + test = 2*self.major - (h1 + h2) + else: + test = self.radius - self.center.distance(p) + + return fuzzy_bool(test.is_positive) + + def equation(self, x='x', y='y', _slope=None): + """ + Returns the equation of an ellipse aligned with the x and y axes; + when slope is given, the equation returned corresponds to an ellipse + with a major axis having that slope. + + Parameters + ========== + + x : str, optional + Label for the x-axis. Default value is 'x'. + y : str, optional + Label for the y-axis. Default value is 'y'. + _slope : Expr, optional + The slope of the major axis. Ignored when 'None'. + + Returns + ======= + + equation : SymPy expression + + See Also + ======== + + arbitrary_point : Returns parameterized point on ellipse + + Examples + ======== + + >>> from sympy import Point, Ellipse, pi + >>> from sympy.abc import x, y + >>> e1 = Ellipse(Point(1, 0), 3, 2) + >>> eq1 = e1.equation(x, y); eq1 + y**2/4 + (x/3 - 1/3)**2 - 1 + >>> eq2 = e1.equation(x, y, _slope=1); eq2 + (-x + y + 1)**2/8 + (x + y - 1)**2/18 - 1 + + A point on e1 satisfies eq1. Let's use one on the x-axis: + + >>> p1 = e1.center + Point(e1.major, 0) + >>> assert eq1.subs(x, p1.x).subs(y, p1.y) == 0 + + When rotated the same as the rotated ellipse, about the center + point of the ellipse, it will satisfy the rotated ellipse's + equation, too: + + >>> r1 = p1.rotate(pi/4, e1.center) + >>> assert eq2.subs(x, r1.x).subs(y, r1.y) == 0 + + References + ========== + + .. [1] https://math.stackexchange.com/questions/108270/what-is-the-equation-of-an-ellipse-that-is-not-aligned-with-the-axis + .. [2] https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse + + """ + + x = _symbol(x, real=True) + y = _symbol(y, real=True) + + dx = x - self.center.x + dy = y - self.center.y + + if _slope is not None: + L = (dy - _slope*dx)**2 + l = (_slope*dy + dx)**2 + h = 1 + _slope**2 + b = h*self.major**2 + a = h*self.minor**2 + return l/b + L/a - 1 + + else: + t1 = (dx/self.hradius)**2 + t2 = (dy/self.vradius)**2 + return t1 + t2 - 1 + + def evolute(self, x='x', y='y'): + """The equation of evolute of the ellipse. + + Parameters + ========== + + x : str, optional + Label for the x-axis. Default value is 'x'. + y : str, optional + Label for the y-axis. Default value is 'y'. + + Returns + ======= + + equation : SymPy expression + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> e1 = Ellipse(Point(1, 0), 3, 2) + >>> e1.evolute() + 2**(2/3)*y**(2/3) + (3*x - 3)**(2/3) - 5**(2/3) + """ + if len(self.args) != 3: + raise NotImplementedError('Evolute of arbitrary Ellipse is not supported.') + x = _symbol(x, real=True) + y = _symbol(y, real=True) + t1 = (self.hradius*(x - self.center.x))**Rational(2, 3) + t2 = (self.vradius*(y - self.center.y))**Rational(2, 3) + return t1 + t2 - (self.hradius**2 - self.vradius**2)**Rational(2, 3) + + @property + def foci(self): + """The foci of the ellipse. + + Notes + ----- + The foci can only be calculated if the major/minor axes are known. + + Raises + ====== + + ValueError + When the major and minor axis cannot be determined. + + See Also + ======== + + sympy.geometry.point.Point + focus_distance : Returns the distance between focus and center + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.foci + (Point2D(-2*sqrt(2), 0), Point2D(2*sqrt(2), 0)) + + """ + c = self.center + hr, vr = self.hradius, self.vradius + if hr == vr: + return (c, c) + + # calculate focus distance manually, since focus_distance calls this + # routine + fd = sqrt(self.major**2 - self.minor**2) + if hr == self.minor: + # foci on the y-axis + return (c + Point(0, -fd), c + Point(0, fd)) + elif hr == self.major: + # foci on the x-axis + return (c + Point(-fd, 0), c + Point(fd, 0)) + + @property + def focus_distance(self): + """The focal distance of the ellipse. + + The distance between the center and one focus. + + Returns + ======= + + focus_distance : number + + See Also + ======== + + foci + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.focus_distance + 2*sqrt(2) + + """ + return Point.distance(self.center, self.foci[0]) + + @property + def hradius(self): + """The horizontal radius of the ellipse. + + Returns + ======= + + hradius : number + + See Also + ======== + + vradius, major, minor + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.hradius + 3 + + """ + return self.args[1] + + def intersection(self, o): + """The intersection of this ellipse and another geometrical entity + `o`. + + Parameters + ========== + + o : GeometryEntity + + Returns + ======= + + intersection : list of GeometryEntity objects + + Notes + ----- + Currently supports intersections with Point, Line, Segment, Ray, + Circle and Ellipse types. + + See Also + ======== + + sympy.geometry.entity.GeometryEntity + + Examples + ======== + + >>> from sympy import Ellipse, Point, Line + >>> e = Ellipse(Point(0, 0), 5, 7) + >>> e.intersection(Point(0, 0)) + [] + >>> e.intersection(Point(5, 0)) + [Point2D(5, 0)] + >>> e.intersection(Line(Point(0,0), Point(0, 1))) + [Point2D(0, -7), Point2D(0, 7)] + >>> e.intersection(Line(Point(5,0), Point(5, 1))) + [Point2D(5, 0)] + >>> e.intersection(Line(Point(6,0), Point(6, 1))) + [] + >>> e = Ellipse(Point(-1, 0), 4, 3) + >>> e.intersection(Ellipse(Point(1, 0), 4, 3)) + [Point2D(0, -3*sqrt(15)/4), Point2D(0, 3*sqrt(15)/4)] + >>> e.intersection(Ellipse(Point(5, 0), 4, 3)) + [Point2D(2, -3*sqrt(7)/4), Point2D(2, 3*sqrt(7)/4)] + >>> e.intersection(Ellipse(Point(100500, 0), 4, 3)) + [] + >>> e.intersection(Ellipse(Point(0, 0), 3, 4)) + [Point2D(3, 0), Point2D(-363/175, -48*sqrt(111)/175), Point2D(-363/175, 48*sqrt(111)/175)] + >>> e.intersection(Ellipse(Point(-1, 0), 3, 4)) + [Point2D(-17/5, -12/5), Point2D(-17/5, 12/5), Point2D(7/5, -12/5), Point2D(7/5, 12/5)] + """ + # TODO: Replace solve with nonlinsolve, when nonlinsolve will be able to solve in real domain + + if isinstance(o, Point): + if o in self: + return [o] + else: + return [] + + elif isinstance(o, (Segment2D, Ray2D)): + ellipse_equation = self.equation(x, y) + result = solve([ellipse_equation, Line( + o.points[0], o.points[1]).equation(x, y)], [x, y], + set=True)[1] + return list(ordered([Point(i) for i in result if i in o])) + + elif isinstance(o, Polygon): + return o.intersection(self) + + elif isinstance(o, (Ellipse, Line2D)): + if o == self: + return self + else: + ellipse_equation = self.equation(x, y) + return list(ordered([Point(i) for i in solve( + [ellipse_equation, o.equation(x, y)], [x, y], + set=True)[1]])) + elif isinstance(o, LinearEntity3D): + raise TypeError('Entity must be two dimensional, not three dimensional') + else: + raise TypeError('Intersection not handled for %s' % func_name(o)) + + def is_tangent(self, o): + """Is `o` tangent to the ellipse? + + Parameters + ========== + + o : GeometryEntity + An Ellipse, LinearEntity or Polygon + + Raises + ====== + + NotImplementedError + When the wrong type of argument is supplied. + + Returns + ======= + + is_tangent: boolean + True if o is tangent to the ellipse, False otherwise. + + See Also + ======== + + tangent_lines + + Examples + ======== + + >>> from sympy import Point, Ellipse, Line + >>> p0, p1, p2 = Point(0, 0), Point(3, 0), Point(3, 3) + >>> e1 = Ellipse(p0, 3, 2) + >>> l1 = Line(p1, p2) + >>> e1.is_tangent(l1) + True + + """ + if isinstance(o, Point2D): + return False + elif isinstance(o, Ellipse): + intersect = self.intersection(o) + if isinstance(intersect, Ellipse): + return True + elif intersect: + return all((self.tangent_lines(i)[0]).equals(o.tangent_lines(i)[0]) for i in intersect) + else: + return False + elif isinstance(o, Line2D): + hit = self.intersection(o) + if not hit: + return False + if len(hit) == 1: + return True + # might return None if it can't decide + return hit[0].equals(hit[1]) + elif isinstance(o, (Segment2D, Ray2D)): + intersect = self.intersection(o) + if len(intersect) == 1: + return o in self.tangent_lines(intersect[0])[0] + else: + return False + elif isinstance(o, Polygon): + return all(self.is_tangent(s) for s in o.sides) + elif isinstance(o, (LinearEntity3D, Point3D)): + raise TypeError('Entity must be two dimensional, not three dimensional') + else: + raise TypeError('Is_tangent not handled for %s' % func_name(o)) + + @property + def major(self): + """Longer axis of the ellipse (if it can be determined) else hradius. + + Returns + ======= + + major : number or expression + + See Also + ======== + + hradius, vradius, minor + + Examples + ======== + + >>> from sympy import Point, Ellipse, Symbol + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.major + 3 + + >>> a = Symbol('a') + >>> b = Symbol('b') + >>> Ellipse(p1, a, b).major + a + >>> Ellipse(p1, b, a).major + b + + >>> m = Symbol('m') + >>> M = m + 1 + >>> Ellipse(p1, m, M).major + m + 1 + + """ + ab = self.args[1:3] + if len(ab) == 1: + return ab[0] + a, b = ab + o = b - a < 0 + if o == True: + return a + elif o == False: + return b + return self.hradius + + @property + def minor(self): + """Shorter axis of the ellipse (if it can be determined) else vradius. + + Returns + ======= + + minor : number or expression + + See Also + ======== + + hradius, vradius, major + + Examples + ======== + + >>> from sympy import Point, Ellipse, Symbol + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.minor + 1 + + >>> a = Symbol('a') + >>> b = Symbol('b') + >>> Ellipse(p1, a, b).minor + b + >>> Ellipse(p1, b, a).minor + a + + >>> m = Symbol('m') + >>> M = m + 1 + >>> Ellipse(p1, m, M).minor + m + + """ + ab = self.args[1:3] + if len(ab) == 1: + return ab[0] + a, b = ab + o = a - b < 0 + if o == True: + return a + elif o == False: + return b + return self.vradius + + def normal_lines(self, p, prec=None): + """Normal lines between `p` and the ellipse. + + Parameters + ========== + + p : Point + + Returns + ======= + + normal_lines : list with 1, 2 or 4 Lines + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> e = Ellipse((0, 0), 2, 3) + >>> c = e.center + >>> e.normal_lines(c + Point(1, 0)) + [Line2D(Point2D(0, 0), Point2D(1, 0))] + >>> e.normal_lines(c) + [Line2D(Point2D(0, 0), Point2D(0, 1)), Line2D(Point2D(0, 0), Point2D(1, 0))] + + Off-axis points require the solution of a quartic equation. This + often leads to very large expressions that may be of little practical + use. An approximate solution of `prec` digits can be obtained by + passing in the desired value: + + >>> e.normal_lines((3, 3), prec=2) + [Line2D(Point2D(-0.81, -2.7), Point2D(0.19, -1.2)), + Line2D(Point2D(1.5, -2.0), Point2D(2.5, -2.7))] + + Whereas the above solution has an operation count of 12, the exact + solution has an operation count of 2020. + """ + p = Point(p, dim=2) + + # XXX change True to something like self.angle == 0 if the arbitrarily + # rotated ellipse is introduced. + # https://github.com/sympy/sympy/issues/2815) + if True: + rv = [] + if p.x == self.center.x: + rv.append(Line(self.center, slope=oo)) + if p.y == self.center.y: + rv.append(Line(self.center, slope=0)) + if rv: + # at these special orientations of p either 1 or 2 normals + # exist and we are done + return rv + + # find the 4 normal points and construct lines through them with + # the corresponding slope + eq = self.equation(x, y) + dydx = idiff(eq, y, x) + norm = -1/dydx + slope = Line(p, (x, y)).slope + seq = slope - norm + + # TODO: Replace solve with solveset, when this line is tested + yis = solve(seq, y)[0] + xeq = eq.subs(y, yis).as_numer_denom()[0].expand() + if len(xeq.free_symbols) == 1: + try: + # this is so much faster, it's worth a try + xsol = Poly(xeq, x).real_roots() + except (DomainError, PolynomialError, NotImplementedError): + # TODO: Replace solve with solveset, when these lines are tested + xsol = _nsort(solve(xeq, x), separated=True)[0] + points = [Point(i, solve(eq.subs(x, i), y)[0]) for i in xsol] + else: + raise NotImplementedError( + 'intersections for the general ellipse are not supported') + slopes = [norm.subs(zip((x, y), pt.args)) for pt in points] + if prec is not None: + points = [pt.n(prec) for pt in points] + slopes = [i if _not_a_coeff(i) else i.n(prec) for i in slopes] + return [Line(pt, slope=s) for pt, s in zip(points, slopes)] + + @property + def periapsis(self): + """The periapsis of the ellipse. + + The shortest distance between the focus and the contour. + + Returns + ======= + + periapsis : number + + See Also + ======== + + apoapsis : Returns greatest distance between focus and contour + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.periapsis + 3 - 2*sqrt(2) + + """ + return self.major * (1 - self.eccentricity) + + @property + def semilatus_rectum(self): + """ + Calculates the semi-latus rectum of the Ellipse. + + Semi-latus rectum is defined as one half of the chord through a + focus parallel to the conic section directrix of a conic section. + + Returns + ======= + + semilatus_rectum : number + + See Also + ======== + + apoapsis : Returns greatest distance between focus and contour + + periapsis : The shortest distance between the focus and the contour + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.semilatus_rectum + 1/3 + + References + ========== + + .. [1] https://mathworld.wolfram.com/SemilatusRectum.html + .. [2] https://en.wikipedia.org/wiki/Ellipse#Semi-latus_rectum + + """ + return self.major * (1 - self.eccentricity ** 2) + + def auxiliary_circle(self): + """Returns a Circle whose diameter is the major axis of the ellipse. + + Examples + ======== + + >>> from sympy import Ellipse, Point, symbols + >>> c = Point(1, 2) + >>> Ellipse(c, 8, 7).auxiliary_circle() + Circle(Point2D(1, 2), 8) + >>> a, b = symbols('a b') + >>> Ellipse(c, a, b).auxiliary_circle() + Circle(Point2D(1, 2), Max(a, b)) + """ + return Circle(self.center, Max(self.hradius, self.vradius)) + + def director_circle(self): + """ + Returns a Circle consisting of all points where two perpendicular + tangent lines to the ellipse cross each other. + + Returns + ======= + + Circle + A director circle returned as a geometric object. + + Examples + ======== + + >>> from sympy import Ellipse, Point, symbols + >>> c = Point(3,8) + >>> Ellipse(c, 7, 9).director_circle() + Circle(Point2D(3, 8), sqrt(130)) + >>> a, b = symbols('a b') + >>> Ellipse(c, a, b).director_circle() + Circle(Point2D(3, 8), sqrt(a**2 + b**2)) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Director_circle + + """ + return Circle(self.center, sqrt(self.hradius**2 + self.vradius**2)) + + def plot_interval(self, parameter='t'): + """The plot interval for the default geometric plot of the Ellipse. + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + plot_interval : list + [parameter, lower_bound, upper_bound] + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> e1 = Ellipse(Point(0, 0), 3, 2) + >>> e1.plot_interval() + [t, -pi, pi] + + """ + t = _symbol(parameter, real=True) + return [t, -S.Pi, S.Pi] + + def random_point(self, seed=None): + """A random point on the ellipse. + + Returns + ======= + + point : Point + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> e1 = Ellipse(Point(0, 0), 3, 2) + >>> e1.random_point() # gives some random point + Point2D(...) + >>> p1 = e1.random_point(seed=0); p1.n(2) + Point2D(2.1, 1.4) + + Notes + ===== + + When creating a random point, one may simply replace the + parameter with a random number. When doing so, however, the + random number should be made a Rational or else the point + may not test as being in the ellipse: + + >>> from sympy.abc import t + >>> from sympy import Rational + >>> arb = e1.arbitrary_point(t); arb + Point2D(3*cos(t), 2*sin(t)) + >>> arb.subs(t, .1) in e1 + False + >>> arb.subs(t, Rational(.1)) in e1 + True + >>> arb.subs(t, Rational('.1')) in e1 + True + + See Also + ======== + sympy.geometry.point.Point + arbitrary_point : Returns parameterized point on ellipse + """ + t = _symbol('t', real=True) + x, y = self.arbitrary_point(t).args + # get a random value in [-1, 1) corresponding to cos(t) + # and confirm that it will test as being in the ellipse + if seed is not None: + rng = random.Random(seed) + else: + rng = random + # simplify this now or else the Float will turn s into a Float + r = Rational(rng.random()) + c = 2*r - 1 + s = sqrt(1 - c**2) + return Point(x.subs(cos(t), c), y.subs(sin(t), s)) + + def reflect(self, line): + """Override GeometryEntity.reflect since the radius + is not a GeometryEntity. + + Examples + ======== + + >>> from sympy import Circle, Line + >>> Circle((0, 1), 1).reflect(Line((0, 0), (1, 1))) + Circle(Point2D(1, 0), -1) + >>> from sympy import Ellipse, Line, Point + >>> Ellipse(Point(3, 4), 1, 3).reflect(Line(Point(0, -4), Point(5, 0))) + Traceback (most recent call last): + ... + NotImplementedError: + General Ellipse is not supported but the equation of the reflected + Ellipse is given by the zeros of: f(x, y) = (9*x/41 + 40*y/41 + + 37/41)**2 + (40*x/123 - 3*y/41 - 364/123)**2 - 1 + + Notes + ===== + + Until the general ellipse (with no axis parallel to the x-axis) is + supported a NotImplemented error is raised and the equation whose + zeros define the rotated ellipse is given. + + """ + + if line.slope in (0, oo): + c = self.center + c = c.reflect(line) + return self.func(c, -self.hradius, self.vradius) + else: + x, y = [uniquely_named_symbol( + name, (self, line), modify=lambda s: '_' + s, real=True) + for name in 'xy'] + expr = self.equation(x, y) + p = Point(x, y).reflect(line) + result = expr.subs(zip((x, y), p.args + ), simultaneous=True) + raise NotImplementedError(filldedent( + 'General Ellipse is not supported but the equation ' + 'of the reflected Ellipse is given by the zeros of: ' + + "f(%s, %s) = %s" % (str(x), str(y), str(result)))) + + def rotate(self, angle=0, pt=None): + """Rotate ``angle`` radians counterclockwise about Point ``pt``. + + Note: since the general ellipse is not supported, only rotations that + are integer multiples of pi/2 are allowed. + + Examples + ======== + + >>> from sympy import Ellipse, pi + >>> Ellipse((1, 0), 2, 1).rotate(pi/2) + Ellipse(Point2D(0, 1), 1, 2) + >>> Ellipse((1, 0), 2, 1).rotate(pi) + Ellipse(Point2D(-1, 0), 2, 1) + """ + if self.hradius == self.vradius: + return self.func(self.center.rotate(angle, pt), self.hradius) + if (angle/S.Pi).is_integer: + return super().rotate(angle, pt) + if (2*angle/S.Pi).is_integer: + return self.func(self.center.rotate(angle, pt), self.vradius, self.hradius) + # XXX see https://github.com/sympy/sympy/issues/2815 for general ellipes + raise NotImplementedError('Only rotations of pi/2 are currently supported for Ellipse.') + + def scale(self, x=1, y=1, pt=None): + """Override GeometryEntity.scale since it is the major and minor + axes which must be scaled and they are not GeometryEntities. + + Examples + ======== + + >>> from sympy import Ellipse + >>> Ellipse((0, 0), 2, 1).scale(2, 4) + Circle(Point2D(0, 0), 4) + >>> Ellipse((0, 0), 2, 1).scale(2) + Ellipse(Point2D(0, 0), 4, 1) + """ + c = self.center + if pt: + pt = Point(pt, dim=2) + return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) + h = self.hradius + v = self.vradius + return self.func(c.scale(x, y), hradius=h*x, vradius=v*y) + + def tangent_lines(self, p): + """Tangent lines between `p` and the ellipse. + + If `p` is on the ellipse, returns the tangent line through point `p`. + Otherwise, returns the tangent line(s) from `p` to the ellipse, or + None if no tangent line is possible (e.g., `p` inside ellipse). + + Parameters + ========== + + p : Point + + Returns + ======= + + tangent_lines : list with 1 or 2 Lines + + Raises + ====== + + NotImplementedError + Can only find tangent lines for a point, `p`, on the ellipse. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Line + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> e1 = Ellipse(Point(0, 0), 3, 2) + >>> e1.tangent_lines(Point(3, 0)) + [Line2D(Point2D(3, 0), Point2D(3, -12))] + + """ + p = Point(p, dim=2) + if self.encloses_point(p): + return [] + + if p in self: + delta = self.center - p + rise = (self.vradius**2)*delta.x + run = -(self.hradius**2)*delta.y + p2 = Point(simplify(p.x + run), + simplify(p.y + rise)) + return [Line(p, p2)] + else: + if len(self.foci) == 2: + f1, f2 = self.foci + maj = self.hradius + test = (2*maj - + Point.distance(f1, p) - + Point.distance(f2, p)) + else: + test = self.radius - Point.distance(self.center, p) + if test.is_number and test.is_positive: + return [] + # else p is outside the ellipse or we can't tell. In case of the + # latter, the solutions returned will only be valid if + # the point is not inside the ellipse; if it is, nan will result. + eq = self.equation(x, y) + dydx = idiff(eq, y, x) + slope = Line(p, Point(x, y)).slope + + # TODO: Replace solve with solveset, when this line is tested + tangent_points = solve([slope - dydx, eq], [x, y]) + + # handle horizontal and vertical tangent lines + if len(tangent_points) == 1: + if tangent_points[0][ + 0] == p.x or tangent_points[0][1] == p.y: + return [Line(p, p + Point(1, 0)), Line(p, p + Point(0, 1))] + else: + return [Line(p, p + Point(0, 1)), Line(p, tangent_points[0])] + + # others + return [Line(p, tangent_points[0]), Line(p, tangent_points[1])] + + @property + def vradius(self): + """The vertical radius of the ellipse. + + Returns + ======= + + vradius : number + + See Also + ======== + + hradius, major, minor + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.vradius + 1 + + """ + return self.args[2] + + + def second_moment_of_area(self, point=None): + """Returns the second moment and product moment area of an ellipse. + + Parameters + ========== + + point : Point, two-tuple of sympifiable objects, or None(default=None) + point is the point about which second moment of area is to be found. + If "point=None" it will be calculated about the axis passing through the + centroid of the ellipse. + + Returns + ======= + + I_xx, I_yy, I_xy : number or SymPy expression + I_xx, I_yy are second moment of area of an ellise. + I_xy is product moment of area of an ellipse. + + Examples + ======== + + >>> from sympy import Point, Ellipse + >>> p1 = Point(0, 0) + >>> e1 = Ellipse(p1, 3, 1) + >>> e1.second_moment_of_area() + (3*pi/4, 27*pi/4, 0) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/List_of_second_moments_of_area + + """ + + I_xx = (S.Pi*(self.hradius)*(self.vradius**3))/4 + I_yy = (S.Pi*(self.hradius**3)*(self.vradius))/4 + I_xy = 0 + + if point is None: + return I_xx, I_yy, I_xy + + # parallel axis theorem + I_xx = I_xx + self.area*((point[1] - self.center.y)**2) + I_yy = I_yy + self.area*((point[0] - self.center.x)**2) + I_xy = I_xy + self.area*(point[0] - self.center.x)*(point[1] - self.center.y) + + return I_xx, I_yy, I_xy + + + def polar_second_moment_of_area(self): + """Returns the polar second moment of area of an Ellipse + + It is a constituent of the second moment of area, linked through + the perpendicular axis theorem. While the planar second moment of + area describes an object's resistance to deflection (bending) when + subjected to a force applied to a plane parallel to the central + axis, the polar second moment of area describes an object's + resistance to deflection when subjected to a moment applied in a + plane perpendicular to the object's central axis (i.e. parallel to + the cross-section) + + Examples + ======== + + >>> from sympy import symbols, Circle, Ellipse + >>> c = Circle((5, 5), 4) + >>> c.polar_second_moment_of_area() + 128*pi + >>> a, b = symbols('a, b') + >>> e = Ellipse((0, 0), a, b) + >>> e.polar_second_moment_of_area() + pi*a**3*b/4 + pi*a*b**3/4 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia + + """ + second_moment = self.second_moment_of_area() + return second_moment[0] + second_moment[1] + + + def section_modulus(self, point=None): + """Returns a tuple with the section modulus of an ellipse + + Section modulus is a geometric property of an ellipse defined as the + ratio of second moment of area to the distance of the extreme end of + the ellipse from the centroidal axis. + + Parameters + ========== + + point : Point, two-tuple of sympifyable objects, or None(default=None) + point is the point at which section modulus is to be found. + If "point=None" section modulus will be calculated for the + point farthest from the centroidal axis of the ellipse. + + Returns + ======= + + S_x, S_y: numbers or SymPy expressions + S_x is the section modulus with respect to the x-axis + S_y is the section modulus with respect to the y-axis + A negative sign indicates that the section modulus is + determined for a point below the centroidal axis. + + Examples + ======== + + >>> from sympy import Symbol, Ellipse, Circle, Point2D + >>> d = Symbol('d', positive=True) + >>> c = Circle((0, 0), d/2) + >>> c.section_modulus() + (pi*d**3/32, pi*d**3/32) + >>> e = Ellipse(Point2D(0, 0), 2, 4) + >>> e.section_modulus() + (8*pi, 4*pi) + >>> e.section_modulus((2, 2)) + (16*pi, 4*pi) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Section_modulus + + """ + x_c, y_c = self.center + if point is None: + # taking x and y as maximum distances from centroid + x_min, y_min, x_max, y_max = self.bounds + y = max(y_c - y_min, y_max - y_c) + x = max(x_c - x_min, x_max - x_c) + else: + # taking x and y as distances of the given point from the center + point = Point2D(point) + y = point.y - y_c + x = point.x - x_c + + second_moment = self.second_moment_of_area() + S_x = second_moment[0]/y + S_y = second_moment[1]/x + + return S_x, S_y + + +class Circle(Ellipse): + r"""A circle in space. + + Constructed simply from a center and a radius, from three + non-collinear points, or the equation of a circle. + + Parameters + ========== + + center : Point + radius : number or SymPy expression + points : sequence of three Points + equation : equation of a circle + + Attributes + ========== + + radius (synonymous with hradius, vradius, major and minor) + circumference + equation + + Raises + ====== + + GeometryError + When the given equation is not that of a circle. + When trying to construct circle from incorrect parameters. + + See Also + ======== + + Ellipse, sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Circle, Eq + >>> from sympy.abc import x, y, a, b + + A circle constructed from a center and radius: + + >>> c1 = Circle(Point(0, 0), 5) + >>> c1.hradius, c1.vradius, c1.radius + (5, 5, 5) + + A circle constructed from three points: + + >>> c2 = Circle(Point(0, 0), Point(1, 1), Point(1, 0)) + >>> c2.hradius, c2.vradius, c2.radius, c2.center + (sqrt(2)/2, sqrt(2)/2, sqrt(2)/2, Point2D(1/2, 1/2)) + + A circle can be constructed from an equation in the form + `ax^2 + by^2 + gx + hy + c = 0`, too: + + >>> Circle(x**2 + y**2 - 25) + Circle(Point2D(0, 0), 5) + + If the variables corresponding to x and y are named something + else, their name or symbol can be supplied: + + >>> Circle(Eq(a**2 + b**2, 25), x='a', y=b) + Circle(Point2D(0, 0), 5) + """ + + def __new__(cls, *args, **kwargs): + evaluate = kwargs.get('evaluate', global_parameters.evaluate) + if len(args) == 1 and isinstance(args[0], (Expr, Eq)): + x = kwargs.get('x', 'x') + y = kwargs.get('y', 'y') + equation = args[0].expand() + if isinstance(equation, Eq): + equation = equation.lhs - equation.rhs + x = find(x, equation) + y = find(y, equation) + + try: + a, b, c, d, e = linear_coeffs(equation, x**2, y**2, x, y) + except ValueError: + raise GeometryError("The given equation is not that of a circle.") + + if S.Zero in (a, b) or a != b: + raise GeometryError("The given equation is not that of a circle.") + + center_x = -c/a/2 + center_y = -d/b/2 + r2 = (center_x**2) + (center_y**2) - e/a + + return Circle((center_x, center_y), sqrt(r2), evaluate=evaluate) + + else: + c, r = None, None + if len(args) == 3: + args = [Point(a, dim=2, evaluate=evaluate) for a in args] + t = Triangle(*args) + if not isinstance(t, Triangle): + return t + c = t.circumcenter + r = t.circumradius + elif len(args) == 2: + # Assume (center, radius) pair + c = Point(args[0], dim=2, evaluate=evaluate) + r = args[1] + # this will prohibit imaginary radius + try: + r = Point(r, 0, evaluate=evaluate).x + except ValueError: + raise GeometryError("Circle with imaginary radius is not permitted") + + if not (c is None or r is None): + if r == 0: + return c + return GeometryEntity.__new__(cls, c, r, **kwargs) + + raise GeometryError("Circle.__new__ received unknown arguments") + + def _eval_evalf(self, prec=15, **options): + pt, r = self.args + dps = prec_to_dps(prec) + pt = pt.evalf(n=dps, **options) + r = r.evalf(n=dps, **options) + return self.func(pt, r, evaluate=False) + + @property + def circumference(self): + """The circumference of the circle. + + Returns + ======= + + circumference : number or SymPy expression + + Examples + ======== + + >>> from sympy import Point, Circle + >>> c1 = Circle(Point(3, 4), 6) + >>> c1.circumference + 12*pi + + """ + return 2 * S.Pi * self.radius + + def equation(self, x='x', y='y'): + """The equation of the circle. + + Parameters + ========== + + x : str or Symbol, optional + Default value is 'x'. + y : str or Symbol, optional + Default value is 'y'. + + Returns + ======= + + equation : SymPy expression + + Examples + ======== + + >>> from sympy import Point, Circle + >>> c1 = Circle(Point(0, 0), 5) + >>> c1.equation() + x**2 + y**2 - 25 + + """ + x = _symbol(x, real=True) + y = _symbol(y, real=True) + t1 = (x - self.center.x)**2 + t2 = (y - self.center.y)**2 + return t1 + t2 - self.major**2 + + def intersection(self, o): + """The intersection of this circle with another geometrical entity. + + Parameters + ========== + + o : GeometryEntity + + Returns + ======= + + intersection : list of GeometryEntities + + Examples + ======== + + >>> from sympy import Point, Circle, Line, Ray + >>> p1, p2, p3 = Point(0, 0), Point(5, 5), Point(6, 0) + >>> p4 = Point(5, 0) + >>> c1 = Circle(p1, 5) + >>> c1.intersection(p2) + [] + >>> c1.intersection(p4) + [Point2D(5, 0)] + >>> c1.intersection(Ray(p1, p2)) + [Point2D(5*sqrt(2)/2, 5*sqrt(2)/2)] + >>> c1.intersection(Line(p2, p3)) + [] + + """ + return Ellipse.intersection(self, o) + + @property + def radius(self): + """The radius of the circle. + + Returns + ======= + + radius : number or SymPy expression + + See Also + ======== + + Ellipse.major, Ellipse.minor, Ellipse.hradius, Ellipse.vradius + + Examples + ======== + + >>> from sympy import Point, Circle + >>> c1 = Circle(Point(3, 4), 6) + >>> c1.radius + 6 + + """ + return self.args[1] + + def reflect(self, line): + """Override GeometryEntity.reflect since the radius + is not a GeometryEntity. + + Examples + ======== + + >>> from sympy import Circle, Line + >>> Circle((0, 1), 1).reflect(Line((0, 0), (1, 1))) + Circle(Point2D(1, 0), -1) + """ + c = self.center + c = c.reflect(line) + return self.func(c, -self.radius) + + def scale(self, x=1, y=1, pt=None): + """Override GeometryEntity.scale since the radius + is not a GeometryEntity. + + Examples + ======== + + >>> from sympy import Circle + >>> Circle((0, 0), 1).scale(2, 2) + Circle(Point2D(0, 0), 2) + >>> Circle((0, 0), 1).scale(2, 4) + Ellipse(Point2D(0, 0), 2, 4) + """ + c = self.center + if pt: + pt = Point(pt, dim=2) + return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) + c = c.scale(x, y) + x, y = [abs(i) for i in (x, y)] + if x == y: + return self.func(c, x*self.radius) + h = v = self.radius + return Ellipse(c, hradius=h*x, vradius=v*y) + + @property + def vradius(self): + """ + This Ellipse property is an alias for the Circle's radius. + + Whereas hradius, major and minor can use Ellipse's conventions, + the vradius does not exist for a circle. It is always a positive + value in order that the Circle, like Polygons, will have an + area that can be positive or negative as determined by the sign + of the hradius. + + Examples + ======== + + >>> from sympy import Point, Circle + >>> c1 = Circle(Point(3, 4), 6) + >>> c1.vradius + 6 + """ + return abs(self.radius) + + +from .polygon import Polygon, Triangle diff --git a/phivenv/Lib/site-packages/sympy/geometry/entity.py b/phivenv/Lib/site-packages/sympy/geometry/entity.py new file mode 100644 index 0000000000000000000000000000000000000000..5ea1e807542c43eb955c2d778cec0f101d78bdce --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/entity.py @@ -0,0 +1,641 @@ +"""The definition of the base geometrical entity with attributes common to +all derived geometrical entities. + +Contains +======== + +GeometryEntity +GeometricSet + +Notes +===== + +A GeometryEntity is any object that has special geometric properties. +A GeometrySet is a superclass of any GeometryEntity that can also +be viewed as a sympy.sets.Set. In particular, points are the only +GeometryEntity not considered a Set. + +Rn is a GeometrySet representing n-dimensional Euclidean space. R2 and +R3 are currently the only ambient spaces implemented. + +""" +from __future__ import annotations + +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.evalf import EvalfMixin, N +from sympy.core.numbers import oo +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify +from sympy.functions.elementary.trigonometric import cos, sin, atan +from sympy.matrices import eye +from sympy.multipledispatch import dispatch +from sympy.printing import sstr +from sympy.sets import Set, Union, FiniteSet +from sympy.sets.handlers.intersection import intersection_sets +from sympy.sets.handlers.union import union_sets +from sympy.solvers.solvers import solve +from sympy.utilities.misc import func_name +from sympy.utilities.iterables import is_sequence + + +# How entities are ordered; used by __cmp__ in GeometryEntity +ordering_of_classes = [ + "Point2D", + "Point3D", + "Point", + "Segment2D", + "Ray2D", + "Line2D", + "Segment3D", + "Line3D", + "Ray3D", + "Segment", + "Ray", + "Line", + "Plane", + "Triangle", + "RegularPolygon", + "Polygon", + "Circle", + "Ellipse", + "Curve", + "Parabola" +] + + +x, y = [Dummy('entity_dummy') for i in range(2)] +T = Dummy('entity_dummy', real=True) + + +class GeometryEntity(Basic, EvalfMixin): + """The base class for all geometrical entities. + + This class does not represent any particular geometric entity, it only + provides the implementation of some methods common to all subclasses. + + """ + + __slots__: tuple[str, ...] = () + + def __cmp__(self, other): + """Comparison of two GeometryEntities.""" + n1 = self.__class__.__name__ + n2 = other.__class__.__name__ + c = (n1 > n2) - (n1 < n2) + if not c: + return 0 + + i1 = -1 + for cls in self.__class__.__mro__: + try: + i1 = ordering_of_classes.index(cls.__name__) + break + except ValueError: + i1 = -1 + if i1 == -1: + return c + + i2 = -1 + for cls in other.__class__.__mro__: + try: + i2 = ordering_of_classes.index(cls.__name__) + break + except ValueError: + i2 = -1 + if i2 == -1: + return c + + return (i1 > i2) - (i1 < i2) + + def __contains__(self, other): + """Subclasses should implement this method for anything more complex than equality.""" + if type(self) is type(other): + return self == other + raise NotImplementedError() + + def __getnewargs__(self): + """Returns a tuple that will be passed to __new__ on unpickling.""" + return tuple(self.args) + + def __ne__(self, o): + """Test inequality of two geometrical entities.""" + return not self == o + + def __new__(cls, *args, **kwargs): + # Points are sequences, but they should not + # be converted to Tuples, so use this detection function instead. + def is_seq_and_not_point(a): + # we cannot use isinstance(a, Point) since we cannot import Point + if hasattr(a, 'is_Point') and a.is_Point: + return False + return is_sequence(a) + + args = [Tuple(*a) if is_seq_and_not_point(a) else sympify(a) for a in args] + return Basic.__new__(cls, *args) + + def __radd__(self, a): + """Implementation of reverse add method.""" + return a.__add__(self) + + def __rtruediv__(self, a): + """Implementation of reverse division method.""" + return a.__truediv__(self) + + def __repr__(self): + """String representation of a GeometryEntity that can be evaluated + by sympy.""" + return type(self).__name__ + repr(self.args) + + def __rmul__(self, a): + """Implementation of reverse multiplication method.""" + return a.__mul__(self) + + def __rsub__(self, a): + """Implementation of reverse subtraction method.""" + return a.__sub__(self) + + def __str__(self): + """String representation of a GeometryEntity.""" + return type(self).__name__ + sstr(self.args) + + def _eval_subs(self, old, new): + from sympy.geometry.point import Point, Point3D + if is_sequence(old) or is_sequence(new): + if isinstance(self, Point3D): + old = Point3D(old) + new = Point3D(new) + else: + old = Point(old) + new = Point(new) + return self._subs(old, new) + + def _repr_svg_(self): + """SVG representation of a GeometryEntity suitable for IPython""" + + try: + bounds = self.bounds + except (NotImplementedError, TypeError): + # if we have no SVG representation, return None so IPython + # will fall back to the next representation + return None + + if not all(x.is_number and x.is_finite for x in bounds): + return None + + svg_top = ''' + + + + + + + + + + + ''' + + # Establish SVG canvas that will fit all the data + small space + xmin, ymin, xmax, ymax = map(N, bounds) + if xmin == xmax and ymin == ymax: + # This is a point; buffer using an arbitrary size + xmin, ymin, xmax, ymax = xmin - .5, ymin -.5, xmax + .5, ymax + .5 + else: + # Expand bounds by a fraction of the data ranges + expand = 0.1 # or 10%; this keeps arrowheads in view (R plots use 4%) + widest_part = max([xmax - xmin, ymax - ymin]) + expand_amount = widest_part * expand + xmin -= expand_amount + ymin -= expand_amount + xmax += expand_amount + ymax += expand_amount + dx = xmax - xmin + dy = ymax - ymin + width = min([max([100., dx]), 300]) + height = min([max([100., dy]), 300]) + + scale_factor = 1. if max(width, height) == 0 else max(dx, dy) / max(width, height) + try: + svg = self._svg(scale_factor) + except (NotImplementedError, TypeError): + # if we have no SVG representation, return None so IPython + # will fall back to the next representation + return None + + view_box = "{} {} {} {}".format(xmin, ymin, dx, dy) + transform = "matrix(1,0,0,-1,0,{})".format(ymax + ymin) + svg_top = svg_top.format(view_box, width, height) + + return svg_top + ( + '{}' + ).format(transform, svg) + + def _svg(self, scale_factor=1., fill_color="#66cc99"): + """Returns SVG path element for the GeometryEntity. + + Parameters + ========== + + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + fill_color : str, optional + Hex string for fill color. Default is "#66cc99". + """ + raise NotImplementedError() + + def _sympy_(self): + return self + + @property + def ambient_dimension(self): + """What is the dimension of the space that the object is contained in?""" + raise NotImplementedError() + + @property + def bounds(self): + """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding + rectangle for the geometric figure. + + """ + + raise NotImplementedError() + + def encloses(self, o): + """ + Return True if o is inside (not on or outside) the boundaries of self. + + The object will be decomposed into Points and individual Entities need + only define an encloses_point method for their class. + + See Also + ======== + + sympy.geometry.ellipse.Ellipse.encloses_point + sympy.geometry.polygon.Polygon.encloses_point + + Examples + ======== + + >>> from sympy import RegularPolygon, Point, Polygon + >>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices) + >>> t2 = Polygon(*RegularPolygon(Point(0, 0), 2, 3).vertices) + >>> t2.encloses(t) + True + >>> t.encloses(t2) + False + + """ + + from sympy.geometry.point import Point + from sympy.geometry.line import Segment, Ray, Line + from sympy.geometry.ellipse import Ellipse + from sympy.geometry.polygon import Polygon, RegularPolygon + + if isinstance(o, Point): + return self.encloses_point(o) + elif isinstance(o, Segment): + return all(self.encloses_point(x) for x in o.points) + elif isinstance(o, (Ray, Line)): + return False + elif isinstance(o, Ellipse): + return self.encloses_point(o.center) and \ + self.encloses_point( + Point(o.center.x + o.hradius, o.center.y)) and \ + not self.intersection(o) + elif isinstance(o, Polygon): + if isinstance(o, RegularPolygon): + if not self.encloses_point(o.center): + return False + return all(self.encloses_point(v) for v in o.vertices) + raise NotImplementedError() + + def equals(self, o): + return self == o + + def intersection(self, o): + """ + Returns a list of all of the intersections of self with o. + + Notes + ===== + + An entity is not required to implement this method. + + If two different types of entities can intersect, the item with + higher index in ordering_of_classes should implement + intersections with anything having a lower index. + + See Also + ======== + + sympy.geometry.util.intersection + + """ + raise NotImplementedError() + + def is_similar(self, other): + """Is this geometrical entity similar to another geometrical entity? + + Two entities are similar if a uniform scaling (enlarging or + shrinking) of one of the entities will allow one to obtain the other. + + Notes + ===== + + This method is not intended to be used directly but rather + through the `are_similar` function found in util.py. + An entity is not required to implement this method. + If two different types of entities can be similar, it is only + required that one of them be able to determine this. + + See Also + ======== + + scale + + """ + raise NotImplementedError() + + def reflect(self, line): + """ + Reflects an object across a line. + + Parameters + ========== + + line: Line + + Examples + ======== + + >>> from sympy import pi, sqrt, Line, RegularPolygon + >>> l = Line((0, pi), slope=sqrt(2)) + >>> pent = RegularPolygon((1, 2), 1, 5) + >>> rpent = pent.reflect(l) + >>> rpent + RegularPolygon(Point2D(-2*sqrt(2)*pi/3 - 1/3 + 4*sqrt(2)/3, 2/3 + 2*sqrt(2)/3 + 2*pi/3), -1, 5, -atan(2*sqrt(2)) + 3*pi/5) + + >>> from sympy import pi, Line, Circle, Point + >>> l = Line((0, pi), slope=1) + >>> circ = Circle(Point(0, 0), 5) + >>> rcirc = circ.reflect(l) + >>> rcirc + Circle(Point2D(-pi, pi), -5) + + """ + from sympy.geometry.point import Point + + g = self + l = line + o = Point(0, 0) + if l.slope.is_zero: + v = l.args[0].y + if not v: # x-axis + return g.scale(y=-1) + reps = [(p, p.translate(y=2*(v - p.y))) for p in g.atoms(Point)] + elif l.slope is oo: + v = l.args[0].x + if not v: # y-axis + return g.scale(x=-1) + reps = [(p, p.translate(x=2*(v - p.x))) for p in g.atoms(Point)] + else: + if not hasattr(g, 'reflect') and not all( + isinstance(arg, Point) for arg in g.args): + raise NotImplementedError( + 'reflect undefined or non-Point args in %s' % g) + a = atan(l.slope) + c = l.coefficients + d = -c[-1]/c[1] # y-intercept + # apply the transform to a single point + xf = Point(x, y) + xf = xf.translate(y=-d).rotate(-a, o).scale(y=-1 + ).rotate(a, o).translate(y=d) + # replace every point using that transform + reps = [(p, xf.xreplace({x: p.x, y: p.y})) for p in g.atoms(Point)] + return g.xreplace(dict(reps)) + + def rotate(self, angle, pt=None): + """Rotate ``angle`` radians counterclockwise about Point ``pt``. + + The default pt is the origin, Point(0, 0) + + See Also + ======== + + scale, translate + + Examples + ======== + + >>> from sympy import Point, RegularPolygon, Polygon, pi + >>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices) + >>> t # vertex on x axis + Triangle(Point2D(1, 0), Point2D(-1/2, sqrt(3)/2), Point2D(-1/2, -sqrt(3)/2)) + >>> t.rotate(pi/2) # vertex on y axis now + Triangle(Point2D(0, 1), Point2D(-sqrt(3)/2, -1/2), Point2D(sqrt(3)/2, -1/2)) + + """ + newargs = [] + for a in self.args: + if isinstance(a, GeometryEntity): + newargs.append(a.rotate(angle, pt)) + else: + newargs.append(a) + return type(self)(*newargs) + + def scale(self, x=1, y=1, pt=None): + """Scale the object by multiplying the x,y-coordinates by x and y. + + If pt is given, the scaling is done relative to that point; the + object is shifted by -pt, scaled, and shifted by pt. + + See Also + ======== + + rotate, translate + + Examples + ======== + + >>> from sympy import RegularPolygon, Point, Polygon + >>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices) + >>> t + Triangle(Point2D(1, 0), Point2D(-1/2, sqrt(3)/2), Point2D(-1/2, -sqrt(3)/2)) + >>> t.scale(2) + Triangle(Point2D(2, 0), Point2D(-1, sqrt(3)/2), Point2D(-1, -sqrt(3)/2)) + >>> t.scale(2, 2) + Triangle(Point2D(2, 0), Point2D(-1, sqrt(3)), Point2D(-1, -sqrt(3))) + + """ + from sympy.geometry.point import Point + if pt: + pt = Point(pt, dim=2) + return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) + return type(self)(*[a.scale(x, y) for a in self.args]) # if this fails, override this class + + def translate(self, x=0, y=0): + """Shift the object by adding to the x,y-coordinates the values x and y. + + See Also + ======== + + rotate, scale + + Examples + ======== + + >>> from sympy import RegularPolygon, Point, Polygon + >>> t = Polygon(*RegularPolygon(Point(0, 0), 1, 3).vertices) + >>> t + Triangle(Point2D(1, 0), Point2D(-1/2, sqrt(3)/2), Point2D(-1/2, -sqrt(3)/2)) + >>> t.translate(2) + Triangle(Point2D(3, 0), Point2D(3/2, sqrt(3)/2), Point2D(3/2, -sqrt(3)/2)) + >>> t.translate(2, 2) + Triangle(Point2D(3, 2), Point2D(3/2, sqrt(3)/2 + 2), Point2D(3/2, 2 - sqrt(3)/2)) + + """ + newargs = [] + for a in self.args: + if isinstance(a, GeometryEntity): + newargs.append(a.translate(x, y)) + else: + newargs.append(a) + return self.func(*newargs) + + def parameter_value(self, other, t): + """Return the parameter corresponding to the given point. + Evaluating an arbitrary point of the entity at this parameter + value will return the given point. + + Examples + ======== + + >>> from sympy import Line, Point + >>> from sympy.abc import t + >>> a = Point(0, 0) + >>> b = Point(2, 2) + >>> Line(a, b).parameter_value((1, 1), t) + {t: 1/2} + >>> Line(a, b).arbitrary_point(t).subs(_) + Point2D(1, 1) + """ + from sympy.geometry.point import Point + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if not isinstance(other, Point): + raise ValueError("other must be a point") + sol = solve(self.arbitrary_point(T) - other, T, dict=True) + if not sol: + raise ValueError("Given point is not on %s" % func_name(self)) + return {t: sol[0][T]} + + +class GeometrySet(GeometryEntity, Set): + """Parent class of all GeometryEntity that are also Sets + (compatible with sympy.sets) + """ + __slots__ = () + + def _contains(self, other): + """sympy.sets uses the _contains method, so include it for compatibility.""" + + if isinstance(other, Set) and other.is_FiniteSet: + return all(self.__contains__(i) for i in other) + + return self.__contains__(other) + +@dispatch(GeometrySet, Set) # type:ignore # noqa:F811 +def union_sets(self, o): # noqa:F811 + """ Returns the union of self and o + for use with sympy.sets.Set, if possible. """ + + + # if its a FiniteSet, merge any points + # we contain and return a union with the rest + if o.is_FiniteSet: + other_points = [p for p in o if not self._contains(p)] + if len(other_points) == len(o): + return None + return Union(self, FiniteSet(*other_points)) + if self._contains(o): + return self + return None + + +@dispatch(GeometrySet, Set) # type: ignore # noqa:F811 +def intersection_sets(self, o): # noqa:F811 + """ Returns a sympy.sets.Set of intersection objects, + if possible. """ + + from sympy.geometry.point import Point + + try: + # if o is a FiniteSet, find the intersection directly + # to avoid infinite recursion + if o.is_FiniteSet: + inter = FiniteSet(*(p for p in o if self.contains(p))) + else: + inter = self.intersection(o) + except NotImplementedError: + # sympy.sets.Set.reduce expects None if an object + # doesn't know how to simplify + return None + + # put the points in a FiniteSet + points = FiniteSet(*[p for p in inter if isinstance(p, Point)]) + non_points = [p for p in inter if not isinstance(p, Point)] + + return Union(*(non_points + [points])) + +def translate(x, y): + """Return the matrix to translate a 2-D point by x and y.""" + rv = eye(3) + rv[2, 0] = x + rv[2, 1] = y + return rv + + +def scale(x, y, pt=None): + """Return the matrix to multiply a 2-D point's coordinates by x and y. + + If pt is given, the scaling is done relative to that point.""" + rv = eye(3) + rv[0, 0] = x + rv[1, 1] = y + if pt: + from sympy.geometry.point import Point + pt = Point(pt, dim=2) + tr1 = translate(*(-pt).args) + tr2 = translate(*pt.args) + return tr1*rv*tr2 + return rv + + +def rotate(th): + """Return the matrix to rotate a 2-D point about the origin by ``angle``. + + The angle is measured in radians. To Point a point about a point other + then the origin, translate the Point, do the rotation, and + translate it back: + + >>> from sympy.geometry.entity import rotate, translate + >>> from sympy import Point, pi + >>> rot_about_11 = translate(-1, -1)*rotate(pi/2)*translate(1, 1) + >>> Point(1, 1).transform(rot_about_11) + Point2D(1, 1) + >>> Point(0, 0).transform(rot_about_11) + Point2D(2, 0) + """ + s = sin(th) + rv = eye(3)*cos(th) + rv[0, 1] = s + rv[1, 0] = -s + rv[2, 2] = 1 + return rv diff --git a/phivenv/Lib/site-packages/sympy/geometry/exceptions.py b/phivenv/Lib/site-packages/sympy/geometry/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..41d97af718de2cebad3accefcd60e43ccf74a3f6 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/exceptions.py @@ -0,0 +1,5 @@ +"""Geometry Errors.""" + +class GeometryError(ValueError): + """An exception raised by classes in the geometry module.""" + pass diff --git a/phivenv/Lib/site-packages/sympy/geometry/line.py b/phivenv/Lib/site-packages/sympy/geometry/line.py new file mode 100644 index 0000000000000000000000000000000000000000..ed73d43d0c9581f9d51f299cf4425acb11958e57 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/line.py @@ -0,0 +1,2877 @@ +"""Line-like geometrical entities. + +Contains +======== +LinearEntity +Line +Ray +Segment +LinearEntity2D +Line2D +Ray2D +Segment2D +LinearEntity3D +Line3D +Ray3D +Segment3D + +""" + +from sympy.core.containers import Tuple +from sympy.core.evalf import N +from sympy.core.expr import Expr +from sympy.core.numbers import Rational, oo, Float +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.sorting import ordered +from sympy.core.symbol import _symbol, Dummy, uniquely_named_symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (_pi_coeff, acos, tan, atan2) +from .entity import GeometryEntity, GeometrySet +from .exceptions import GeometryError +from .point import Point, Point3D +from .util import find, intersection +from sympy.logic.boolalg import And +from sympy.matrices import Matrix +from sympy.sets.sets import Intersection +from sympy.simplify.simplify import simplify +from sympy.solvers.solvers import solve +from sympy.solvers.solveset import linear_coeffs +from sympy.utilities.misc import Undecidable, filldedent + + +import random + + +t, u = [Dummy('line_dummy') for i in range(2)] + + +class LinearEntity(GeometrySet): + """A base class for all linear entities (Line, Ray and Segment) + in n-dimensional Euclidean space. + + Attributes + ========== + + ambient_dimension + direction + length + p1 + p2 + points + + Notes + ===== + + This is an abstract class and is not meant to be instantiated. + + See Also + ======== + + sympy.geometry.entity.GeometryEntity + + """ + def __new__(cls, p1, p2=None, **kwargs): + p1, p2 = Point._normalize_dimension(p1, p2) + if p1 == p2: + # sometimes we return a single point if we are not given two unique + # points. This is done in the specific subclass + raise ValueError( + "%s.__new__ requires two unique Points." % cls.__name__) + if len(p1) != len(p2): + raise ValueError( + "%s.__new__ requires two Points of equal dimension." % cls.__name__) + + return GeometryEntity.__new__(cls, p1, p2, **kwargs) + + def __contains__(self, other): + """Return a definitive answer or else raise an error if it cannot + be determined that other is on the boundaries of self.""" + result = self.contains(other) + + if result is not None: + return result + else: + raise Undecidable( + "Cannot decide whether '%s' contains '%s'" % (self, other)) + + def _span_test(self, other): + """Test whether the point `other` lies in the positive span of `self`. + A point x is 'in front' of a point y if x.dot(y) >= 0. Return + -1 if `other` is behind `self.p1`, 0 if `other` is `self.p1` and + and 1 if `other` is in front of `self.p1`.""" + if self.p1 == other: + return 0 + + rel_pos = other - self.p1 + d = self.direction + if d.dot(rel_pos) > 0: + return 1 + return -1 + + @property + def ambient_dimension(self): + """A property method that returns the dimension of LinearEntity + object. + + Parameters + ========== + + p1 : LinearEntity + + Returns + ======= + + dimension : integer + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(1, 1) + >>> l1 = Line(p1, p2) + >>> l1.ambient_dimension + 2 + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1) + >>> l1 = Line(p1, p2) + >>> l1.ambient_dimension + 3 + + """ + return len(self.p1) + + def angle_between(l1, l2): + """Return the non-reflex angle formed by rays emanating from + the origin with directions the same as the direction vectors + of the linear entities. + + Parameters + ========== + + l1 : LinearEntity + l2 : LinearEntity + + Returns + ======= + + angle : angle in radians + + Notes + ===== + + From the dot product of vectors v1 and v2 it is known that: + + ``dot(v1, v2) = |v1|*|v2|*cos(A)`` + + where A is the angle formed between the two vectors. We can + get the directional vectors of the two lines and readily + find the angle between the two using the above formula. + + See Also + ======== + + is_perpendicular, Ray2D.closing_angle + + Examples + ======== + + >>> from sympy import Line + >>> e = Line((0, 0), (1, 0)) + >>> ne = Line((0, 0), (1, 1)) + >>> sw = Line((1, 1), (0, 0)) + >>> ne.angle_between(e) + pi/4 + >>> sw.angle_between(e) + 3*pi/4 + + To obtain the non-obtuse angle at the intersection of lines, use + the ``smallest_angle_between`` method: + + >>> sw.smallest_angle_between(e) + pi/4 + + >>> from sympy import Point3D, Line3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0) + >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3) + >>> l1.angle_between(l2) + acos(-sqrt(2)/3) + >>> l1.smallest_angle_between(l2) + acos(sqrt(2)/3) + """ + if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): + raise TypeError('Must pass only LinearEntity objects') + + v1, v2 = l1.direction, l2.direction + return acos(v1.dot(v2)/(abs(v1)*abs(v2))) + + def smallest_angle_between(l1, l2): + """Return the smallest angle formed at the intersection of the + lines containing the linear entities. + + Parameters + ========== + + l1 : LinearEntity + l2 : LinearEntity + + Returns + ======= + + angle : angle in radians + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2, p3 = Point(0, 0), Point(0, 4), Point(2, -2) + >>> l1, l2 = Line(p1, p2), Line(p1, p3) + >>> l1.smallest_angle_between(l2) + pi/4 + + See Also + ======== + + angle_between, is_perpendicular, Ray2D.closing_angle + """ + if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): + raise TypeError('Must pass only LinearEntity objects') + + v1, v2 = l1.direction, l2.direction + return acos(abs(v1.dot(v2))/(abs(v1)*abs(v2))) + + def arbitrary_point(self, parameter='t'): + """A parameterized point on the Line. + + Parameters + ========== + + parameter : str, optional + The name of the parameter which will be used for the parametric + point. The default value is 't'. When this parameter is 0, the + first point used to define the line will be returned, and when + it is 1 the second point will be returned. + + Returns + ======= + + point : Point + + Raises + ====== + + ValueError + When ``parameter`` already appears in the Line's definition. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(1, 0), Point(5, 3) + >>> l1 = Line(p1, p2) + >>> l1.arbitrary_point() + Point2D(4*t + 1, 3*t) + >>> from sympy import Point3D, Line3D + >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 1) + >>> l1 = Line3D(p1, p2) + >>> l1.arbitrary_point() + Point3D(4*t + 1, 3*t, t) + + """ + t = _symbol(parameter, real=True) + if t.name in (f.name for f in self.free_symbols): + raise ValueError(filldedent(''' + Symbol %s already appears in object + and cannot be used as a parameter. + ''' % t.name)) + # multiply on the right so the variable gets + # combined with the coordinates of the point + return self.p1 + (self.p2 - self.p1)*t + + @staticmethod + def are_concurrent(*lines): + """Is a sequence of linear entities concurrent? + + Two or more linear entities are concurrent if they all + intersect at a single point. + + Parameters + ========== + + lines + A sequence of linear entities. + + Returns + ======= + + True : if the set of linear entities intersect in one point + False : otherwise. + + See Also + ======== + + sympy.geometry.util.intersection + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(3, 5) + >>> p3, p4 = Point(-2, -2), Point(0, 2) + >>> l1, l2, l3 = Line(p1, p2), Line(p1, p3), Line(p1, p4) + >>> Line.are_concurrent(l1, l2, l3) + True + >>> l4 = Line(p2, p3) + >>> Line.are_concurrent(l2, l3, l4) + False + >>> from sympy import Point3D, Line3D + >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 5, 2) + >>> p3, p4 = Point3D(-2, -2, -2), Point3D(0, 2, 1) + >>> l1, l2, l3 = Line3D(p1, p2), Line3D(p1, p3), Line3D(p1, p4) + >>> Line3D.are_concurrent(l1, l2, l3) + True + >>> l4 = Line3D(p2, p3) + >>> Line3D.are_concurrent(l2, l3, l4) + False + + """ + common_points = Intersection(*lines) + if common_points.is_FiniteSet and len(common_points) == 1: + return True + return False + + def contains(self, other): + """Subclasses should implement this method and should return + True if other is on the boundaries of self; + False if not on the boundaries of self; + None if a determination cannot be made.""" + raise NotImplementedError() + + @property + def direction(self): + """The direction vector of the LinearEntity. + + Returns + ======= + + p : a Point; the ray from the origin to this point is the + direction of `self` + + Examples + ======== + + >>> from sympy import Line + >>> a, b = (1, 1), (1, 3) + >>> Line(a, b).direction + Point2D(0, 2) + >>> Line(b, a).direction + Point2D(0, -2) + + This can be reported so the distance from the origin is 1: + + >>> Line(b, a).direction.unit + Point2D(0, -1) + + See Also + ======== + + sympy.geometry.point.Point.unit + + """ + return self.p2 - self.p1 + + def intersection(self, other): + """The intersection with another geometrical entity. + + Parameters + ========== + + o : Point or LinearEntity + + Returns + ======= + + intersection : list of geometrical entities + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Line, Segment + >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7) + >>> l1 = Line(p1, p2) + >>> l1.intersection(p3) + [Point2D(7, 7)] + >>> p4, p5 = Point(5, 0), Point(0, 3) + >>> l2 = Line(p4, p5) + >>> l1.intersection(l2) + [Point2D(15/8, 15/8)] + >>> p6, p7 = Point(0, 5), Point(2, 6) + >>> s1 = Segment(p6, p7) + >>> l1.intersection(s1) + [] + >>> from sympy import Point3D, Line3D, Segment3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(7, 7, 7) + >>> l1 = Line3D(p1, p2) + >>> l1.intersection(p3) + [Point3D(7, 7, 7)] + >>> l1 = Line3D(Point3D(4,19,12), Point3D(5,25,17)) + >>> l2 = Line3D(Point3D(-3, -15, -19), direction_ratio=[2,8,8]) + >>> l1.intersection(l2) + [Point3D(1, 1, -3)] + >>> p6, p7 = Point3D(0, 5, 2), Point3D(2, 6, 3) + >>> s1 = Segment3D(p6, p7) + >>> l1.intersection(s1) + [] + + """ + def intersect_parallel_rays(ray1, ray2): + if ray1.direction.dot(ray2.direction) > 0: + # rays point in the same direction + # so return the one that is "in front" + return [ray2] if ray1._span_test(ray2.p1) >= 0 else [ray1] + else: + # rays point in opposite directions + st = ray1._span_test(ray2.p1) + if st < 0: + return [] + elif st == 0: + return [ray2.p1] + return [Segment(ray1.p1, ray2.p1)] + + def intersect_parallel_ray_and_segment(ray, seg): + st1, st2 = ray._span_test(seg.p1), ray._span_test(seg.p2) + if st1 < 0 and st2 < 0: + return [] + elif st1 >= 0 and st2 >= 0: + return [seg] + elif st1 >= 0: # st2 < 0: + return [Segment(ray.p1, seg.p1)] + else: # st1 < 0 and st2 >= 0: + return [Segment(ray.p1, seg.p2)] + + def intersect_parallel_segments(seg1, seg2): + if seg1.contains(seg2): + return [seg2] + if seg2.contains(seg1): + return [seg1] + + # direct the segments so they're oriented the same way + if seg1.direction.dot(seg2.direction) < 0: + seg2 = Segment(seg2.p2, seg2.p1) + # order the segments so seg1 is "behind" seg2 + if seg1._span_test(seg2.p1) < 0: + seg1, seg2 = seg2, seg1 + if seg2._span_test(seg1.p2) < 0: + return [] + return [Segment(seg2.p1, seg1.p2)] + + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if other.is_Point: + if self.contains(other): + return [other] + else: + return [] + elif isinstance(other, LinearEntity): + # break into cases based on whether + # the lines are parallel, non-parallel intersecting, or skew + pts = Point._normalize_dimension(self.p1, self.p2, other.p1, other.p2) + rank = Point.affine_rank(*pts) + + if rank == 1: + # we're collinear + if isinstance(self, Line): + return [other] + if isinstance(other, Line): + return [self] + + if isinstance(self, Ray) and isinstance(other, Ray): + return intersect_parallel_rays(self, other) + if isinstance(self, Ray) and isinstance(other, Segment): + return intersect_parallel_ray_and_segment(self, other) + if isinstance(self, Segment) and isinstance(other, Ray): + return intersect_parallel_ray_and_segment(other, self) + if isinstance(self, Segment) and isinstance(other, Segment): + return intersect_parallel_segments(self, other) + elif rank == 2: + # we're in the same plane + l1 = Line(*pts[:2]) + l2 = Line(*pts[2:]) + + # check to see if we're parallel. If we are, we can't + # be intersecting, since the collinear case was already + # handled + if l1.direction.is_scalar_multiple(l2.direction): + return [] + + # find the intersection as if everything were lines + # by solving the equation t*d + p1 == s*d' + p1' + m = Matrix([l1.direction, -l2.direction]).transpose() + v = Matrix([l2.p1 - l1.p1]).transpose() + + # we cannot use m.solve(v) because that only works for square matrices + m_rref, pivots = m.col_insert(2, v).rref(simplify=True) + # rank == 2 ensures we have 2 pivots, but let's check anyway + if len(pivots) != 2: + raise GeometryError("Failed when solving Mx=b when M={} and b={}".format(m, v)) + coeff = m_rref[0, 2] + line_intersection = l1.direction*coeff + self.p1 + + # if both are lines, skip a containment check + if isinstance(self, Line) and isinstance(other, Line): + return [line_intersection] + + if ((isinstance(self, Line) or + self.contains(line_intersection)) and + other.contains(line_intersection)): + return [line_intersection] + if not self.atoms(Float) and not other.atoms(Float): + # if it can fail when there are no Floats then + # maybe the following parametric check should be + # done + return [] + # floats may fail exact containment so check that the + # arbitrary points, when equal, both give a + # non-negative parameter when the arbitrary point + # coordinates are equated + tu = solve(self.arbitrary_point(t) - other.arbitrary_point(u), + t, u, dict=True)[0] + def ok(p, l): + if isinstance(l, Line): + # p > -oo + return True + if isinstance(l, Ray): + # p >= 0 + return p.is_nonnegative + if isinstance(l, Segment): + # 0 <= p <= 1 + return p.is_nonnegative and (1 - p).is_nonnegative + raise ValueError("unexpected line type") + if ok(tu[t], self) and ok(tu[u], other): + return [line_intersection] + return [] + else: + # we're skew + return [] + + return other.intersection(self) + + def is_parallel(l1, l2): + """Are two linear entities parallel? + + Parameters + ========== + + l1 : LinearEntity + l2 : LinearEntity + + Returns + ======= + + True : if l1 and l2 are parallel, + False : otherwise. + + See Also + ======== + + coefficients + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(1, 1) + >>> p3, p4 = Point(3, 4), Point(6, 7) + >>> l1, l2 = Line(p1, p2), Line(p3, p4) + >>> Line.is_parallel(l1, l2) + True + >>> p5 = Point(6, 6) + >>> l3 = Line(p3, p5) + >>> Line.is_parallel(l1, l3) + False + >>> from sympy import Point3D, Line3D + >>> p1, p2 = Point3D(0, 0, 0), Point3D(3, 4, 5) + >>> p3, p4 = Point3D(2, 1, 1), Point3D(8, 9, 11) + >>> l1, l2 = Line3D(p1, p2), Line3D(p3, p4) + >>> Line3D.is_parallel(l1, l2) + True + >>> p5 = Point3D(6, 6, 6) + >>> l3 = Line3D(p3, p5) + >>> Line3D.is_parallel(l1, l3) + False + + """ + if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): + raise TypeError('Must pass only LinearEntity objects') + + return l1.direction.is_scalar_multiple(l2.direction) + + def is_perpendicular(l1, l2): + """Are two linear entities perpendicular? + + Parameters + ========== + + l1 : LinearEntity + l2 : LinearEntity + + Returns + ======= + + True : if l1 and l2 are perpendicular, + False : otherwise. + + See Also + ======== + + coefficients + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(-1, 1) + >>> l1, l2 = Line(p1, p2), Line(p1, p3) + >>> l1.is_perpendicular(l2) + True + >>> p4 = Point(5, 3) + >>> l3 = Line(p1, p4) + >>> l1.is_perpendicular(l3) + False + >>> from sympy import Point3D, Line3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(-1, 2, 0) + >>> l1, l2 = Line3D(p1, p2), Line3D(p2, p3) + >>> l1.is_perpendicular(l2) + False + >>> p4 = Point3D(5, 3, 7) + >>> l3 = Line3D(p1, p4) + >>> l1.is_perpendicular(l3) + False + + """ + if not isinstance(l1, LinearEntity) and not isinstance(l2, LinearEntity): + raise TypeError('Must pass only LinearEntity objects') + + return S.Zero.equals(l1.direction.dot(l2.direction)) + + def is_similar(self, other): + """ + Return True if self and other are contained in the same line. + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2, p3 = Point(0, 1), Point(3, 4), Point(2, 3) + >>> l1 = Line(p1, p2) + >>> l2 = Line(p1, p3) + >>> l1.is_similar(l2) + True + """ + l = Line(self.p1, self.p2) + return l.contains(other) + + @property + def length(self): + """ + The length of the line. + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(3, 5) + >>> l1 = Line(p1, p2) + >>> l1.length + oo + """ + return S.Infinity + + @property + def p1(self): + """The first defining point of a linear entity. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(5, 3) + >>> l = Line(p1, p2) + >>> l.p1 + Point2D(0, 0) + + """ + return self.args[0] + + @property + def p2(self): + """The second defining point of a linear entity. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(5, 3) + >>> l = Line(p1, p2) + >>> l.p2 + Point2D(5, 3) + + """ + return self.args[1] + + def parallel_line(self, p): + """Create a new Line parallel to this linear entity which passes + through the point `p`. + + Parameters + ========== + + p : Point + + Returns + ======= + + line : Line + + See Also + ======== + + is_parallel + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2) + >>> l1 = Line(p1, p2) + >>> l2 = l1.parallel_line(p3) + >>> p3 in l2 + True + >>> l1.is_parallel(l2) + True + >>> from sympy import Point3D, Line3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0) + >>> l1 = Line3D(p1, p2) + >>> l2 = l1.parallel_line(p3) + >>> p3 in l2 + True + >>> l1.is_parallel(l2) + True + + """ + p = Point(p, dim=self.ambient_dimension) + return Line(p, p + self.direction) + + def perpendicular_line(self, p): + """Create a new Line perpendicular to this linear entity which passes + through the point `p`. + + Parameters + ========== + + p : Point + + Returns + ======= + + line : Line + + See Also + ======== + + sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment + + Examples + ======== + + >>> from sympy import Point3D, Line3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(2, 3, 4), Point3D(-2, 2, 0) + >>> L = Line3D(p1, p2) + >>> P = L.perpendicular_line(p3); P + Line3D(Point3D(-2, 2, 0), Point3D(4/29, 6/29, 8/29)) + >>> L.is_perpendicular(P) + True + + In 3D the, the first point used to define the line is the point + through which the perpendicular was required to pass; the + second point is (arbitrarily) contained in the given line: + + >>> P.p2 in L + True + """ + p = Point(p, dim=self.ambient_dimension) + if p in self: + p = p + self.direction.orthogonal_direction + return Line(p, self.projection(p)) + + def perpendicular_segment(self, p): + """Create a perpendicular line segment from `p` to this line. + + The endpoints of the segment are ``p`` and the closest point in + the line containing self. (If self is not a line, the point might + not be in self.) + + Parameters + ========== + + p : Point + + Returns + ======= + + segment : Segment + + Notes + ===== + + Returns `p` itself if `p` is on this linear entity. + + See Also + ======== + + perpendicular_line + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 2) + >>> l1 = Line(p1, p2) + >>> s1 = l1.perpendicular_segment(p3) + >>> l1.is_perpendicular(s1) + True + >>> p3 in s1 + True + >>> l1.perpendicular_segment(Point(4, 0)) + Segment2D(Point2D(4, 0), Point2D(2, 2)) + >>> from sympy import Point3D, Line3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 2, 0) + >>> l1 = Line3D(p1, p2) + >>> s1 = l1.perpendicular_segment(p3) + >>> l1.is_perpendicular(s1) + True + >>> p3 in s1 + True + >>> l1.perpendicular_segment(Point3D(4, 0, 0)) + Segment3D(Point3D(4, 0, 0), Point3D(4/3, 4/3, 4/3)) + + """ + p = Point(p, dim=self.ambient_dimension) + if p in self: + return p + l = self.perpendicular_line(p) + # The intersection should be unique, so unpack the singleton + p2, = Intersection(Line(self.p1, self.p2), l) + + return Segment(p, p2) + + @property + def points(self): + """The two points used to define this linear entity. + + Returns + ======= + + points : tuple of Points + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(5, 11) + >>> l1 = Line(p1, p2) + >>> l1.points + (Point2D(0, 0), Point2D(5, 11)) + + """ + return (self.p1, self.p2) + + def projection(self, other): + """Project a point, line, ray, or segment onto this linear entity. + + Parameters + ========== + + other : Point or LinearEntity (Line, Ray, Segment) + + Returns + ======= + + projection : Point or LinearEntity (Line, Ray, Segment) + The return type matches the type of the parameter ``other``. + + Raises + ====== + + GeometryError + When method is unable to perform projection. + + Notes + ===== + + A projection involves taking the two points that define + the linear entity and projecting those points onto a + Line and then reforming the linear entity using these + projections. + A point P is projected onto a line L by finding the point + on L that is closest to P. This point is the intersection + of L and the line perpendicular to L that passes through P. + + See Also + ======== + + sympy.geometry.point.Point, perpendicular_line + + Examples + ======== + + >>> from sympy import Point, Line, Segment, Rational + >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(Rational(1, 2), 0) + >>> l1 = Line(p1, p2) + >>> l1.projection(p3) + Point2D(1/4, 1/4) + >>> p4, p5 = Point(10, 0), Point(12, 1) + >>> s1 = Segment(p4, p5) + >>> l1.projection(s1) + Segment2D(Point2D(5, 5), Point2D(13/2, 13/2)) + >>> p1, p2, p3 = Point(0, 0, 1), Point(1, 1, 2), Point(2, 0, 1) + >>> l1 = Line(p1, p2) + >>> l1.projection(p3) + Point3D(2/3, 2/3, 5/3) + >>> p4, p5 = Point(10, 0, 1), Point(12, 1, 3) + >>> s1 = Segment(p4, p5) + >>> l1.projection(s1) + Segment3D(Point3D(10/3, 10/3, 13/3), Point3D(5, 5, 6)) + + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + + def proj_point(p): + return Point.project(p - self.p1, self.direction) + self.p1 + + if isinstance(other, Point): + return proj_point(other) + elif isinstance(other, LinearEntity): + p1, p2 = proj_point(other.p1), proj_point(other.p2) + # test to see if we're degenerate + if p1 == p2: + return p1 + projected = other.__class__(p1, p2) + projected = Intersection(self, projected) + if projected.is_empty: + return projected + # if we happen to have intersected in only a point, return that + if projected.is_FiniteSet and len(projected) == 1: + # projected is a set of size 1, so unpack it in `a` + a, = projected + return a + # order args so projection is in the same direction as self + if self.direction.dot(projected.direction) < 0: + p1, p2 = projected.args + projected = projected.func(p2, p1) + return projected + + raise GeometryError( + "Do not know how to project %s onto %s" % (other, self)) + + def random_point(self, seed=None): + """A random point on a LinearEntity. + + Returns + ======= + + point : Point + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Line, Ray, Segment + >>> p1, p2 = Point(0, 0), Point(5, 3) + >>> line = Line(p1, p2) + >>> r = line.random_point(seed=42) # seed value is optional + >>> r.n(3) + Point2D(-0.72, -0.432) + >>> r in line + True + >>> Ray(p1, p2).random_point(seed=42).n(3) + Point2D(0.72, 0.432) + >>> Segment(p1, p2).random_point(seed=42).n(3) + Point2D(3.2, 1.92) + + """ + if seed is not None: + rng = random.Random(seed) + else: + rng = random + pt = self.arbitrary_point(t) + if isinstance(self, Ray): + v = abs(rng.gauss(0, 1)) + elif isinstance(self, Segment): + v = rng.random() + elif isinstance(self, Line): + v = rng.gauss(0, 1) + else: + raise NotImplementedError('unhandled line type') + return pt.subs(t, Rational(v)) + + def bisectors(self, other): + """Returns the perpendicular lines which pass through the intersections + of self and other that are in the same plane. + + Parameters + ========== + + line : Line3D + + Returns + ======= + + list: two Line instances + + Examples + ======== + + >>> from sympy import Point3D, Line3D + >>> r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)) + >>> r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0)) + >>> r1.bisectors(r2) + [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))] + + """ + if not isinstance(other, LinearEntity): + raise GeometryError("Expecting LinearEntity, not %s" % other) + + l1, l2 = self, other + + # make sure dimensions match or else a warning will rise from + # intersection calculation + if l1.p1.ambient_dimension != l2.p1.ambient_dimension: + if isinstance(l1, Line2D): + l1, l2 = l2, l1 + _, p1 = Point._normalize_dimension(l1.p1, l2.p1, on_morph='ignore') + _, p2 = Point._normalize_dimension(l1.p2, l2.p2, on_morph='ignore') + l2 = Line(p1, p2) + + point = intersection(l1, l2) + + # Three cases: Lines may intersect in a point, may be equal or may not intersect. + if not point: + raise GeometryError("The lines do not intersect") + else: + pt = point[0] + if isinstance(pt, Line): + # Intersection is a line because both lines are coincident + return [self] + + + d1 = l1.direction.unit + d2 = l2.direction.unit + + bis1 = Line(pt, pt + d1 + d2) + bis2 = Line(pt, pt + d1 - d2) + + return [bis1, bis2] + + +class Line(LinearEntity): + """An infinite line in space. + + A 2D line is declared with two distinct points, point and slope, or + an equation. A 3D line may be defined with a point and a direction ratio. + + Parameters + ========== + + p1 : Point + p2 : Point + slope : SymPy expression + direction_ratio : list + equation : equation of a line + + Notes + ===== + + `Line` will automatically subclass to `Line2D` or `Line3D` based + on the dimension of `p1`. The `slope` argument is only relevant + for `Line2D` and the `direction_ratio` argument is only relevant + for `Line3D`. + + The order of the points will define the direction of the line + which is used when calculating the angle between lines. + + See Also + ======== + + sympy.geometry.point.Point + sympy.geometry.line.Line2D + sympy.geometry.line.Line3D + + Examples + ======== + + >>> from sympy import Line, Segment, Point, Eq + >>> from sympy.abc import x, y, a, b + + >>> L = Line(Point(2,3), Point(3,5)) + >>> L + Line2D(Point2D(2, 3), Point2D(3, 5)) + >>> L.points + (Point2D(2, 3), Point2D(3, 5)) + >>> L.equation() + -2*x + y + 1 + >>> L.coefficients + (-2, 1, 1) + + Instantiate with keyword ``slope``: + + >>> Line(Point(0, 0), slope=0) + Line2D(Point2D(0, 0), Point2D(1, 0)) + + Instantiate with another linear object + + >>> s = Segment((0, 0), (0, 1)) + >>> Line(s).equation() + x + + The line corresponding to an equation in the for `ax + by + c = 0`, + can be entered: + + >>> Line(3*x + y + 18) + Line2D(Point2D(0, -18), Point2D(1, -21)) + + If `x` or `y` has a different name, then they can be specified, too, + as a string (to match the name) or symbol: + + >>> Line(Eq(3*a + b, -18), x='a', y=b) + Line2D(Point2D(0, -18), Point2D(1, -21)) + """ + def __new__(cls, *args, **kwargs): + if len(args) == 1 and isinstance(args[0], (Expr, Eq)): + missing = uniquely_named_symbol('?', args) + if not kwargs: + x = 'x' + y = 'y' + else: + x = kwargs.pop('x', missing) + y = kwargs.pop('y', missing) + if kwargs: + raise ValueError('expecting only x and y as keywords') + + equation = args[0] + if isinstance(equation, Eq): + equation = equation.lhs - equation.rhs + + def find_or_missing(x): + try: + return find(x, equation) + except ValueError: + return missing + x = find_or_missing(x) + y = find_or_missing(y) + + a, b, c = linear_coeffs(equation, x, y) + + if b: + return Line((0, -c/b), slope=-a/b) + if a: + return Line((-c/a, 0), slope=oo) + + raise ValueError('not found in equation: %s' % (set('xy') - {x, y})) + + else: + if len(args) > 0: + p1 = args[0] + if len(args) > 1: + p2 = args[1] + else: + p2 = None + + if isinstance(p1, LinearEntity): + if p2: + raise ValueError('If p1 is a LinearEntity, p2 must be None.') + dim = len(p1.p1) + else: + p1 = Point(p1) + dim = len(p1) + if p2 is not None or isinstance(p2, Point) and p2.ambient_dimension != dim: + p2 = Point(p2) + + if dim == 2: + return Line2D(p1, p2, **kwargs) + elif dim == 3: + return Line3D(p1, p2, **kwargs) + return LinearEntity.__new__(cls, p1, p2, **kwargs) + + def contains(self, other): + """ + Return True if `other` is on this Line, or False otherwise. + + Examples + ======== + + >>> from sympy import Line,Point + >>> p1, p2 = Point(0, 1), Point(3, 4) + >>> l = Line(p1, p2) + >>> l.contains(p1) + True + >>> l.contains((0, 1)) + True + >>> l.contains((0, 0)) + False + >>> a = (0, 0, 0) + >>> b = (1, 1, 1) + >>> c = (2, 2, 2) + >>> l1 = Line(a, b) + >>> l2 = Line(b, a) + >>> l1 == l2 + False + >>> l1 in l2 + True + + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if isinstance(other, Point): + return Point.is_collinear(other, self.p1, self.p2) + if isinstance(other, LinearEntity): + return Point.is_collinear(self.p1, self.p2, other.p1, other.p2) + return False + + def distance(self, other): + """ + Finds the shortest distance between a line and a point. + + Raises + ====== + + NotImplementedError is raised if `other` is not a Point + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(1, 1) + >>> s = Line(p1, p2) + >>> s.distance(Point(-1, 1)) + sqrt(2) + >>> s.distance((-1, 2)) + 3*sqrt(2)/2 + >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 1) + >>> s = Line(p1, p2) + >>> s.distance(Point(-1, 1, 1)) + 2*sqrt(6)/3 + >>> s.distance((-1, 1, 1)) + 2*sqrt(6)/3 + + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if self.contains(other): + return S.Zero + return self.perpendicular_segment(other).length + + def equals(self, other): + """Returns True if self and other are the same mathematical entities""" + if not isinstance(other, Line): + return False + return Point.is_collinear(self.p1, other.p1, self.p2, other.p2) + + def plot_interval(self, parameter='t'): + """The plot interval for the default geometric plot of line. Gives + values that will produce a line that is +/- 5 units long (where a + unit is the distance between the two points that define the line). + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + plot_interval : list (plot interval) + [parameter, lower_bound, upper_bound] + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(5, 3) + >>> l1 = Line(p1, p2) + >>> l1.plot_interval() + [t, -5, 5] + + """ + t = _symbol(parameter, real=True) + return [t, -5, 5] + + +class Ray(LinearEntity): + """A Ray is a semi-line in the space with a source point and a direction. + + Parameters + ========== + + p1 : Point + The source of the Ray + p2 : Point or radian value + This point determines the direction in which the Ray propagates. + If given as an angle it is interpreted in radians with the positive + direction being ccw. + + Attributes + ========== + + source + + See Also + ======== + + sympy.geometry.line.Ray2D + sympy.geometry.line.Ray3D + sympy.geometry.point.Point + sympy.geometry.line.Line + + Notes + ===== + + `Ray` will automatically subclass to `Ray2D` or `Ray3D` based on the + dimension of `p1`. + + Examples + ======== + + >>> from sympy import Ray, Point, pi + >>> r = Ray(Point(2, 3), Point(3, 5)) + >>> r + Ray2D(Point2D(2, 3), Point2D(3, 5)) + >>> r.points + (Point2D(2, 3), Point2D(3, 5)) + >>> r.source + Point2D(2, 3) + >>> r.xdirection + oo + >>> r.ydirection + oo + >>> r.slope + 2 + >>> Ray(Point(0, 0), angle=pi/4).slope + 1 + + """ + def __new__(cls, p1, p2=None, **kwargs): + p1 = Point(p1) + if p2 is not None: + p1, p2 = Point._normalize_dimension(p1, Point(p2)) + dim = len(p1) + + if dim == 2: + return Ray2D(p1, p2, **kwargs) + elif dim == 3: + return Ray3D(p1, p2, **kwargs) + return LinearEntity.__new__(cls, p1, p2, **kwargs) + + def _svg(self, scale_factor=1., fill_color="#66cc99"): + """Returns SVG path element for the LinearEntity. + + Parameters + ========== + + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + fill_color : str, optional + Hex string for fill color. Default is "#66cc99". + """ + verts = (N(self.p1), N(self.p2)) + coords = ["{},{}".format(p.x, p.y) for p in verts] + path = "M {} L {}".format(coords[0], " L ".join(coords[1:])) + + return ( + '' + ).format(2.*scale_factor, path, fill_color) + + def contains(self, other): + """ + Is other GeometryEntity contained in this Ray? + + Examples + ======== + + >>> from sympy import Ray,Point,Segment + >>> p1, p2 = Point(0, 0), Point(4, 4) + >>> r = Ray(p1, p2) + >>> r.contains(p1) + True + >>> r.contains((1, 1)) + True + >>> r.contains((1, 3)) + False + >>> s = Segment((1, 1), (2, 2)) + >>> r.contains(s) + True + >>> s = Segment((1, 2), (2, 5)) + >>> r.contains(s) + False + >>> r1 = Ray((2, 2), (3, 3)) + >>> r.contains(r1) + True + >>> r1 = Ray((2, 2), (3, 5)) + >>> r.contains(r1) + False + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if isinstance(other, Point): + if Point.is_collinear(self.p1, self.p2, other): + # if we're in the direction of the ray, our + # direction vector dot the ray's direction vector + # should be non-negative + return bool((self.p2 - self.p1).dot(other - self.p1) >= S.Zero) + return False + elif isinstance(other, Ray): + if Point.is_collinear(self.p1, self.p2, other.p1, other.p2): + return bool((self.p2 - self.p1).dot(other.p2 - other.p1) > S.Zero) + return False + elif isinstance(other, Segment): + return other.p1 in self and other.p2 in self + + # No other known entity can be contained in a Ray + return False + + def distance(self, other): + """ + Finds the shortest distance between the ray and a point. + + Raises + ====== + + NotImplementedError is raised if `other` is not a Point + + Examples + ======== + + >>> from sympy import Point, Ray + >>> p1, p2 = Point(0, 0), Point(1, 1) + >>> s = Ray(p1, p2) + >>> s.distance(Point(-1, -1)) + sqrt(2) + >>> s.distance((-1, 2)) + 3*sqrt(2)/2 + >>> p1, p2 = Point(0, 0, 0), Point(1, 1, 2) + >>> s = Ray(p1, p2) + >>> s + Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 2)) + >>> s.distance(Point(-1, -1, 2)) + 4*sqrt(3)/3 + >>> s.distance((-1, -1, 2)) + 4*sqrt(3)/3 + + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if self.contains(other): + return S.Zero + + proj = Line(self.p1, self.p2).projection(other) + if self.contains(proj): + return abs(other - proj) + else: + return abs(other - self.source) + + def equals(self, other): + """Returns True if self and other are the same mathematical entities""" + if not isinstance(other, Ray): + return False + return self.source == other.source and other.p2 in self + + def plot_interval(self, parameter='t'): + """The plot interval for the default geometric plot of the Ray. Gives + values that will produce a ray that is 10 units long (where a unit is + the distance between the two points that define the ray). + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + plot_interval : list + [parameter, lower_bound, upper_bound] + + Examples + ======== + + >>> from sympy import Ray, pi + >>> r = Ray((0, 0), angle=pi/4) + >>> r.plot_interval() + [t, 0, 10] + + """ + t = _symbol(parameter, real=True) + return [t, 0, 10] + + @property + def source(self): + """The point from which the ray emanates. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Ray + >>> p1, p2 = Point(0, 0), Point(4, 1) + >>> r1 = Ray(p1, p2) + >>> r1.source + Point2D(0, 0) + >>> p1, p2 = Point(0, 0, 0), Point(4, 1, 5) + >>> r1 = Ray(p2, p1) + >>> r1.source + Point3D(4, 1, 5) + + """ + return self.p1 + + +class Segment(LinearEntity): + """A line segment in space. + + Parameters + ========== + + p1 : Point + p2 : Point + + Attributes + ========== + + length : number or SymPy expression + midpoint : Point + + See Also + ======== + + sympy.geometry.line.Segment2D + sympy.geometry.line.Segment3D + sympy.geometry.point.Point + sympy.geometry.line.Line + + Notes + ===== + + If 2D or 3D points are used to define `Segment`, it will + be automatically subclassed to `Segment2D` or `Segment3D`. + + Examples + ======== + + >>> from sympy import Point, Segment + >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts + Segment2D(Point2D(1, 0), Point2D(1, 1)) + >>> s = Segment(Point(4, 3), Point(1, 1)) + >>> s.points + (Point2D(4, 3), Point2D(1, 1)) + >>> s.slope + 2/3 + >>> s.length + sqrt(13) + >>> s.midpoint + Point2D(5/2, 2) + >>> Segment((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts + Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1)) + >>> s = Segment(Point(4, 3, 9), Point(1, 1, 7)); s + Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)) + >>> s.points + (Point3D(4, 3, 9), Point3D(1, 1, 7)) + >>> s.length + sqrt(17) + >>> s.midpoint + Point3D(5/2, 2, 8) + + """ + def __new__(cls, p1, p2, **kwargs): + p1, p2 = Point._normalize_dimension(Point(p1), Point(p2)) + dim = len(p1) + + if dim == 2: + return Segment2D(p1, p2, **kwargs) + elif dim == 3: + return Segment3D(p1, p2, **kwargs) + return LinearEntity.__new__(cls, p1, p2, **kwargs) + + def contains(self, other): + """ + Is the other GeometryEntity contained within this Segment? + + Examples + ======== + + >>> from sympy import Point, Segment + >>> p1, p2 = Point(0, 1), Point(3, 4) + >>> s = Segment(p1, p2) + >>> s2 = Segment(p2, p1) + >>> s.contains(s2) + True + >>> from sympy import Point3D, Segment3D + >>> p1, p2 = Point3D(0, 1, 1), Point3D(3, 4, 5) + >>> s = Segment3D(p1, p2) + >>> s2 = Segment3D(p2, p1) + >>> s.contains(s2) + True + >>> s.contains((p1 + p2)/2) + True + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if isinstance(other, Point): + if Point.is_collinear(other, self.p1, self.p2): + if isinstance(self, Segment2D): + # if it is collinear and is in the bounding box of the + # segment then it must be on the segment + vert = (1/self.slope).equals(0) + if vert is False: + isin = (self.p1.x - other.x)*(self.p2.x - other.x) <= 0 + if isin in (True, False): + return isin + if vert is True: + isin = (self.p1.y - other.y)*(self.p2.y - other.y) <= 0 + if isin in (True, False): + return isin + # use the triangle inequality + d1, d2 = other - self.p1, other - self.p2 + d = self.p2 - self.p1 + # without the call to simplify, SymPy cannot tell that an expression + # like (a+b)*(a/2+b/2) is always non-negative. If it cannot be + # determined, raise an Undecidable error + try: + # the triangle inequality says that |d1|+|d2| >= |d| and is strict + # only if other lies in the line segment + return bool(simplify(Eq(abs(d1) + abs(d2) - abs(d), 0))) + except TypeError: + raise Undecidable("Cannot determine if {} is in {}".format(other, self)) + if isinstance(other, Segment): + return other.p1 in self and other.p2 in self + + return False + + def equals(self, other): + """Returns True if self and other are the same mathematical entities""" + return isinstance(other, self.func) and list( + ordered(self.args)) == list(ordered(other.args)) + + def distance(self, other): + """ + Finds the shortest distance between a line segment and a point. + + Raises + ====== + + NotImplementedError is raised if `other` is not a Point + + Examples + ======== + + >>> from sympy import Point, Segment + >>> p1, p2 = Point(0, 1), Point(3, 4) + >>> s = Segment(p1, p2) + >>> s.distance(Point(10, 15)) + sqrt(170) + >>> s.distance((0, 12)) + sqrt(73) + >>> from sympy import Point3D, Segment3D + >>> p1, p2 = Point3D(0, 0, 3), Point3D(1, 1, 4) + >>> s = Segment3D(p1, p2) + >>> s.distance(Point3D(10, 15, 12)) + sqrt(341) + >>> s.distance((10, 15, 12)) + sqrt(341) + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if isinstance(other, Point): + vp1 = other - self.p1 + vp2 = other - self.p2 + + dot_prod_sign_1 = self.direction.dot(vp1) >= 0 + dot_prod_sign_2 = self.direction.dot(vp2) <= 0 + if dot_prod_sign_1 and dot_prod_sign_2: + return Line(self.p1, self.p2).distance(other) + if dot_prod_sign_1 and not dot_prod_sign_2: + return abs(vp2) + if not dot_prod_sign_1 and dot_prod_sign_2: + return abs(vp1) + raise NotImplementedError() + + @property + def length(self): + """The length of the line segment. + + See Also + ======== + + sympy.geometry.point.Point.distance + + Examples + ======== + + >>> from sympy import Point, Segment + >>> p1, p2 = Point(0, 0), Point(4, 3) + >>> s1 = Segment(p1, p2) + >>> s1.length + 5 + >>> from sympy import Point3D, Segment3D + >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3) + >>> s1 = Segment3D(p1, p2) + >>> s1.length + sqrt(34) + + """ + return Point.distance(self.p1, self.p2) + + @property + def midpoint(self): + """The midpoint of the line segment. + + See Also + ======== + + sympy.geometry.point.Point.midpoint + + Examples + ======== + + >>> from sympy import Point, Segment + >>> p1, p2 = Point(0, 0), Point(4, 3) + >>> s1 = Segment(p1, p2) + >>> s1.midpoint + Point2D(2, 3/2) + >>> from sympy import Point3D, Segment3D + >>> p1, p2 = Point3D(0, 0, 0), Point3D(4, 3, 3) + >>> s1 = Segment3D(p1, p2) + >>> s1.midpoint + Point3D(2, 3/2, 3/2) + + """ + return Point.midpoint(self.p1, self.p2) + + def perpendicular_bisector(self, p=None): + """The perpendicular bisector of this segment. + + If no point is specified or the point specified is not on the + bisector then the bisector is returned as a Line. Otherwise a + Segment is returned that joins the point specified and the + intersection of the bisector and the segment. + + Parameters + ========== + + p : Point + + Returns + ======= + + bisector : Line or Segment + + See Also + ======== + + LinearEntity.perpendicular_segment + + Examples + ======== + + >>> from sympy import Point, Segment + >>> p1, p2, p3 = Point(0, 0), Point(6, 6), Point(5, 1) + >>> s1 = Segment(p1, p2) + >>> s1.perpendicular_bisector() + Line2D(Point2D(3, 3), Point2D(-3, 9)) + + >>> s1.perpendicular_bisector(p3) + Segment2D(Point2D(5, 1), Point2D(3, 3)) + + """ + l = self.perpendicular_line(self.midpoint) + if p is not None: + p2 = Point(p, dim=self.ambient_dimension) + if p2 in l: + return Segment(p2, self.midpoint) + return l + + def plot_interval(self, parameter='t'): + """The plot interval for the default geometric plot of the Segment gives + values that will produce the full segment in a plot. + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + plot_interval : list + [parameter, lower_bound, upper_bound] + + Examples + ======== + + >>> from sympy import Point, Segment + >>> p1, p2 = Point(0, 0), Point(5, 3) + >>> s1 = Segment(p1, p2) + >>> s1.plot_interval() + [t, 0, 1] + + """ + t = _symbol(parameter, real=True) + return [t, 0, 1] + + +class LinearEntity2D(LinearEntity): + """A base class for all linear entities (line, ray and segment) + in a 2-dimensional Euclidean space. + + Attributes + ========== + + p1 + p2 + coefficients + slope + points + + Notes + ===== + + This is an abstract class and is not meant to be instantiated. + + See Also + ======== + + sympy.geometry.entity.GeometryEntity + + """ + @property + def bounds(self): + """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding + rectangle for the geometric figure. + + """ + verts = self.points + xs = [p.x for p in verts] + ys = [p.y for p in verts] + return (min(xs), min(ys), max(xs), max(ys)) + + def perpendicular_line(self, p): + """Create a new Line perpendicular to this linear entity which passes + through the point `p`. + + Parameters + ========== + + p : Point + + Returns + ======= + + line : Line + + See Also + ======== + + sympy.geometry.line.LinearEntity.is_perpendicular, perpendicular_segment + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2) + >>> L = Line(p1, p2) + >>> P = L.perpendicular_line(p3); P + Line2D(Point2D(-2, 2), Point2D(-5, 4)) + >>> L.is_perpendicular(P) + True + + In 2D, the first point of the perpendicular line is the + point through which was required to pass; the second + point is arbitrarily chosen. To get a line that explicitly + uses a point in the line, create a line from the perpendicular + segment from the line to the point: + + >>> Line(L.perpendicular_segment(p3)) + Line2D(Point2D(-2, 2), Point2D(4/13, 6/13)) + """ + p = Point(p, dim=self.ambient_dimension) + # any two lines in R^2 intersect, so blindly making + # a line through p in an orthogonal direction will work + # and is faster than finding the projection point as in 3D + return Line(p, p + self.direction.orthogonal_direction) + + @property + def slope(self): + """The slope of this linear entity, or infinity if vertical. + + Returns + ======= + + slope : number or SymPy expression + + See Also + ======== + + coefficients + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(0, 0), Point(3, 5) + >>> l1 = Line(p1, p2) + >>> l1.slope + 5/3 + + >>> p3 = Point(0, 4) + >>> l2 = Line(p1, p3) + >>> l2.slope + oo + + """ + d1, d2 = (self.p1 - self.p2).args + if d1 == 0: + return S.Infinity + return simplify(d2/d1) + + +class Line2D(LinearEntity2D, Line): + """An infinite line in space 2D. + + A line is declared with two distinct points or a point and slope + as defined using keyword `slope`. + + Parameters + ========== + + p1 : Point + pt : Point + slope : SymPy expression + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Line, Segment, Point + >>> L = Line(Point(2,3), Point(3,5)) + >>> L + Line2D(Point2D(2, 3), Point2D(3, 5)) + >>> L.points + (Point2D(2, 3), Point2D(3, 5)) + >>> L.equation() + -2*x + y + 1 + >>> L.coefficients + (-2, 1, 1) + + Instantiate with keyword ``slope``: + + >>> Line(Point(0, 0), slope=0) + Line2D(Point2D(0, 0), Point2D(1, 0)) + + Instantiate with another linear object + + >>> s = Segment((0, 0), (0, 1)) + >>> Line(s).equation() + x + """ + def __new__(cls, p1, pt=None, slope=None, **kwargs): + if isinstance(p1, LinearEntity): + if pt is not None: + raise ValueError('When p1 is a LinearEntity, pt should be None') + p1, pt = Point._normalize_dimension(*p1.args, dim=2) + else: + p1 = Point(p1, dim=2) + if pt is not None and slope is None: + try: + p2 = Point(pt, dim=2) + except (NotImplementedError, TypeError, ValueError): + raise ValueError(filldedent(''' + The 2nd argument was not a valid Point. + If it was a slope, enter it with keyword "slope". + ''')) + elif slope is not None and pt is None: + slope = sympify(slope) + if slope.is_finite is False: + # when infinite slope, don't change x + dx = 0 + dy = 1 + else: + # go over 1 up slope + dx = 1 + dy = slope + # XXX avoiding simplification by adding to coords directly + p2 = Point(p1.x + dx, p1.y + dy, evaluate=False) + else: + raise ValueError('A 2nd Point or keyword "slope" must be used.') + return LinearEntity2D.__new__(cls, p1, p2, **kwargs) + + def _svg(self, scale_factor=1., fill_color="#66cc99"): + """Returns SVG path element for the LinearEntity. + + Parameters + ========== + + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + fill_color : str, optional + Hex string for fill color. Default is "#66cc99". + """ + verts = (N(self.p1), N(self.p2)) + coords = ["{},{}".format(p.x, p.y) for p in verts] + path = "M {} L {}".format(coords[0], " L ".join(coords[1:])) + + return ( + '' + ).format(2.*scale_factor, path, fill_color) + + @property + def coefficients(self): + """The coefficients (`a`, `b`, `c`) for `ax + by + c = 0`. + + See Also + ======== + + sympy.geometry.line.Line2D.equation + + Examples + ======== + + >>> from sympy import Point, Line + >>> from sympy.abc import x, y + >>> p1, p2 = Point(0, 0), Point(5, 3) + >>> l = Line(p1, p2) + >>> l.coefficients + (-3, 5, 0) + + >>> p3 = Point(x, y) + >>> l2 = Line(p1, p3) + >>> l2.coefficients + (-y, x, 0) + + """ + p1, p2 = self.points + if p1.x == p2.x: + return (S.One, S.Zero, -p1.x) + elif p1.y == p2.y: + return (S.Zero, S.One, -p1.y) + return tuple([simplify(i) for i in + (self.p1.y - self.p2.y, + self.p2.x - self.p1.x, + self.p1.x*self.p2.y - self.p1.y*self.p2.x)]) + + def equation(self, x='x', y='y'): + """The equation of the line: ax + by + c. + + Parameters + ========== + + x : str, optional + The name to use for the x-axis, default value is 'x'. + y : str, optional + The name to use for the y-axis, default value is 'y'. + + Returns + ======= + + equation : SymPy expression + + See Also + ======== + + sympy.geometry.line.Line2D.coefficients + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(1, 0), Point(5, 3) + >>> l1 = Line(p1, p2) + >>> l1.equation() + -3*x + 4*y + 3 + + """ + x = _symbol(x, real=True) + y = _symbol(y, real=True) + p1, p2 = self.points + if p1.x == p2.x: + return x - p1.x + elif p1.y == p2.y: + return y - p1.y + + a, b, c = self.coefficients + return a*x + b*y + c + + +class Ray2D(LinearEntity2D, Ray): + """ + A Ray is a semi-line in the space with a source point and a direction. + + Parameters + ========== + + p1 : Point + The source of the Ray + p2 : Point or radian value + This point determines the direction in which the Ray propagates. + If given as an angle it is interpreted in radians with the positive + direction being ccw. + + Attributes + ========== + + source + xdirection + ydirection + + See Also + ======== + + sympy.geometry.point.Point, Line + + Examples + ======== + + >>> from sympy import Point, pi, Ray + >>> r = Ray(Point(2, 3), Point(3, 5)) + >>> r + Ray2D(Point2D(2, 3), Point2D(3, 5)) + >>> r.points + (Point2D(2, 3), Point2D(3, 5)) + >>> r.source + Point2D(2, 3) + >>> r.xdirection + oo + >>> r.ydirection + oo + >>> r.slope + 2 + >>> Ray(Point(0, 0), angle=pi/4).slope + 1 + + """ + def __new__(cls, p1, pt=None, angle=None, **kwargs): + p1 = Point(p1, dim=2) + if pt is not None and angle is None: + try: + p2 = Point(pt, dim=2) + except (NotImplementedError, TypeError, ValueError): + raise ValueError(filldedent(''' + The 2nd argument was not a valid Point; if + it was meant to be an angle it should be + given with keyword "angle".''')) + if p1 == p2: + raise ValueError('A Ray requires two distinct points.') + elif angle is not None and pt is None: + # we need to know if the angle is an odd multiple of pi/2 + angle = sympify(angle) + c = _pi_coeff(angle) + p2 = None + if c is not None: + if c.is_Rational: + if c.q == 2: + if c.p == 1: + p2 = p1 + Point(0, 1) + elif c.p == 3: + p2 = p1 + Point(0, -1) + elif c.q == 1: + if c.p == 0: + p2 = p1 + Point(1, 0) + elif c.p == 1: + p2 = p1 + Point(-1, 0) + if p2 is None: + c *= S.Pi + else: + c = angle % (2*S.Pi) + if not p2: + m = 2*c/S.Pi + left = And(1 < m, m < 3) # is it in quadrant 2 or 3? + x = Piecewise((-1, left), (Piecewise((0, Eq(m % 1, 0)), (1, True)), True)) + y = Piecewise((-tan(c), left), (Piecewise((1, Eq(m, 1)), (-1, Eq(m, 3)), (tan(c), True)), True)) + p2 = p1 + Point(x, y) + else: + raise ValueError('A 2nd point or keyword "angle" must be used.') + + return LinearEntity2D.__new__(cls, p1, p2, **kwargs) + + @property + def xdirection(self): + """The x direction of the ray. + + Positive infinity if the ray points in the positive x direction, + negative infinity if the ray points in the negative x direction, + or 0 if the ray is vertical. + + See Also + ======== + + ydirection + + Examples + ======== + + >>> from sympy import Point, Ray + >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, -1) + >>> r1, r2 = Ray(p1, p2), Ray(p1, p3) + >>> r1.xdirection + oo + >>> r2.xdirection + 0 + + """ + if self.p1.x < self.p2.x: + return S.Infinity + elif self.p1.x == self.p2.x: + return S.Zero + else: + return S.NegativeInfinity + + @property + def ydirection(self): + """The y direction of the ray. + + Positive infinity if the ray points in the positive y direction, + negative infinity if the ray points in the negative y direction, + or 0 if the ray is horizontal. + + See Also + ======== + + xdirection + + Examples + ======== + + >>> from sympy import Point, Ray + >>> p1, p2, p3 = Point(0, 0), Point(-1, -1), Point(-1, 0) + >>> r1, r2 = Ray(p1, p2), Ray(p1, p3) + >>> r1.ydirection + -oo + >>> r2.ydirection + 0 + + """ + if self.p1.y < self.p2.y: + return S.Infinity + elif self.p1.y == self.p2.y: + return S.Zero + else: + return S.NegativeInfinity + + def closing_angle(r1, r2): + """Return the angle by which r2 must be rotated so it faces the same + direction as r1. + + Parameters + ========== + + r1 : Ray2D + r2 : Ray2D + + Returns + ======= + + angle : angle in radians (ccw angle is positive) + + See Also + ======== + + LinearEntity.angle_between + + Examples + ======== + + >>> from sympy import Ray, pi + >>> r1 = Ray((0, 0), (1, 0)) + >>> r2 = r1.rotate(-pi/2) + >>> angle = r1.closing_angle(r2); angle + pi/2 + >>> r2.rotate(angle).direction.unit == r1.direction.unit + True + >>> r2.closing_angle(r1) + -pi/2 + """ + if not all(isinstance(r, Ray2D) for r in (r1, r2)): + # although the direction property is defined for + # all linear entities, only the Ray is truly a + # directed object + raise TypeError('Both arguments must be Ray2D objects.') + + a1 = atan2(*list(reversed(r1.direction.args))) + a2 = atan2(*list(reversed(r2.direction.args))) + if a1*a2 < 0: + a1 = 2*S.Pi + a1 if a1 < 0 else a1 + a2 = 2*S.Pi + a2 if a2 < 0 else a2 + return a1 - a2 + + +class Segment2D(LinearEntity2D, Segment): + """A line segment in 2D space. + + Parameters + ========== + + p1 : Point + p2 : Point + + Attributes + ========== + + length : number or SymPy expression + midpoint : Point + + See Also + ======== + + sympy.geometry.point.Point, Line + + Examples + ======== + + >>> from sympy import Point, Segment + >>> Segment((1, 0), (1, 1)) # tuples are interpreted as pts + Segment2D(Point2D(1, 0), Point2D(1, 1)) + >>> s = Segment(Point(4, 3), Point(1, 1)); s + Segment2D(Point2D(4, 3), Point2D(1, 1)) + >>> s.points + (Point2D(4, 3), Point2D(1, 1)) + >>> s.slope + 2/3 + >>> s.length + sqrt(13) + >>> s.midpoint + Point2D(5/2, 2) + + """ + def __new__(cls, p1, p2, **kwargs): + p1 = Point(p1, dim=2) + p2 = Point(p2, dim=2) + + if p1 == p2: + return p1 + + return LinearEntity2D.__new__(cls, p1, p2, **kwargs) + + def _svg(self, scale_factor=1., fill_color="#66cc99"): + """Returns SVG path element for the LinearEntity. + + Parameters + ========== + + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + fill_color : str, optional + Hex string for fill color. Default is "#66cc99". + """ + verts = (N(self.p1), N(self.p2)) + coords = ["{},{}".format(p.x, p.y) for p in verts] + path = "M {} L {}".format(coords[0], " L ".join(coords[1:])) + return ( + '' + ).format(2.*scale_factor, path, fill_color) + + +class LinearEntity3D(LinearEntity): + """An base class for all linear entities (line, ray and segment) + in a 3-dimensional Euclidean space. + + Attributes + ========== + + p1 + p2 + direction_ratio + direction_cosine + points + + Notes + ===== + + This is a base class and is not meant to be instantiated. + """ + def __new__(cls, p1, p2, **kwargs): + p1 = Point3D(p1, dim=3) + p2 = Point3D(p2, dim=3) + if p1 == p2: + # if it makes sense to return a Point, handle in subclass + raise ValueError( + "%s.__new__ requires two unique Points." % cls.__name__) + + return GeometryEntity.__new__(cls, p1, p2, **kwargs) + + ambient_dimension = 3 + + @property + def direction_ratio(self): + """The direction ratio of a given line in 3D. + + See Also + ======== + + sympy.geometry.line.Line3D.equation + + Examples + ======== + + >>> from sympy import Point3D, Line3D + >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1) + >>> l = Line3D(p1, p2) + >>> l.direction_ratio + [5, 3, 1] + """ + p1, p2 = self.points + return p1.direction_ratio(p2) + + @property + def direction_cosine(self): + """The normalized direction ratio of a given line in 3D. + + See Also + ======== + + sympy.geometry.line.Line3D.equation + + Examples + ======== + + >>> from sympy import Point3D, Line3D + >>> p1, p2 = Point3D(0, 0, 0), Point3D(5, 3, 1) + >>> l = Line3D(p1, p2) + >>> l.direction_cosine + [sqrt(35)/7, 3*sqrt(35)/35, sqrt(35)/35] + >>> sum(i**2 for i in _) + 1 + """ + p1, p2 = self.points + return p1.direction_cosine(p2) + + +class Line3D(LinearEntity3D, Line): + """An infinite 3D line in space. + + A line is declared with two distinct points or a point and direction_ratio + as defined using keyword `direction_ratio`. + + Parameters + ========== + + p1 : Point3D + pt : Point3D + direction_ratio : list + + See Also + ======== + + sympy.geometry.point.Point3D + sympy.geometry.line.Line + sympy.geometry.line.Line2D + + Examples + ======== + + >>> from sympy import Line3D, Point3D + >>> L = Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1)) + >>> L + Line3D(Point3D(2, 3, 4), Point3D(3, 5, 1)) + >>> L.points + (Point3D(2, 3, 4), Point3D(3, 5, 1)) + """ + def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs): + if isinstance(p1, LinearEntity3D): + if pt is not None: + raise ValueError('if p1 is a LinearEntity, pt must be None.') + p1, pt = p1.args + else: + p1 = Point(p1, dim=3) + if pt is not None and len(direction_ratio) == 0: + pt = Point(pt, dim=3) + elif len(direction_ratio) == 3 and pt is None: + pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1], + p1.z + direction_ratio[2]) + else: + raise ValueError('A 2nd Point or keyword "direction_ratio" must ' + 'be used.') + + return LinearEntity3D.__new__(cls, p1, pt, **kwargs) + + def equation(self, x='x', y='y', z='z'): + """Return the equations that define the line in 3D. + + Parameters + ========== + + x : str, optional + The name to use for the x-axis, default value is 'x'. + y : str, optional + The name to use for the y-axis, default value is 'y'. + z : str, optional + The name to use for the z-axis, default value is 'z'. + + Returns + ======= + + equation : Tuple of simultaneous equations + + Examples + ======== + + >>> from sympy import Point3D, Line3D, solve + >>> from sympy.abc import x, y, z + >>> p1, p2 = Point3D(1, 0, 0), Point3D(5, 3, 0) + >>> l1 = Line3D(p1, p2) + >>> eq = l1.equation(x, y, z); eq + (-3*x + 4*y + 3, z) + >>> solve(eq.subs(z, 0), (x, y, z)) + {x: 4*y/3 + 1} + """ + x, y, z, k = [_symbol(i, real=True) for i in (x, y, z, 'k')] + p1, p2 = self.points + d1, d2, d3 = p1.direction_ratio(p2) + x1, y1, z1 = p1 + eqs = [-d1*k + x - x1, -d2*k + y - y1, -d3*k + z - z1] + # eliminate k from equations by solving first eq with k for k + for i, e in enumerate(eqs): + if e.has(k): + kk = solve(e, k)[0] + eqs.pop(i) + break + return Tuple(*[i.subs(k, kk).as_numer_denom()[0] for i in eqs]) + + def distance(self, other): + """ + Finds the shortest distance between a line and another object. + + Parameters + ========== + + Point3D, Line3D, Plane, tuple, list + + Returns + ======= + + distance + + Notes + ===== + + This method accepts only 3D entities as it's parameter + + Tuples and lists are converted to Point3D and therefore must be of + length 3, 2 or 1. + + NotImplementedError is raised if `other` is not an instance of one + of the specified classes: Point3D, Line3D, or Plane. + + Examples + ======== + + >>> from sympy.geometry import Line3D + >>> l1 = Line3D((0, 0, 0), (0, 0, 1)) + >>> l2 = Line3D((0, 1, 0), (1, 1, 1)) + >>> l1.distance(l2) + 1 + + The computed distance may be symbolic, too: + + >>> from sympy.abc import x, y + >>> l1 = Line3D((0, 0, 0), (0, 0, 1)) + >>> l2 = Line3D((0, x, 0), (y, x, 1)) + >>> l1.distance(l2) + Abs(x*y)/Abs(sqrt(y**2)) + + """ + + from .plane import Plane # Avoid circular import + + if isinstance(other, (tuple, list)): + try: + other = Point3D(other) + except ValueError: + pass + + if isinstance(other, Point3D): + return super().distance(other) + + if isinstance(other, Line3D): + if self == other: + return S.Zero + if self.is_parallel(other): + return super().distance(other.p1) + + # Skew lines + self_direction = Matrix(self.direction_ratio) + other_direction = Matrix(other.direction_ratio) + normal = self_direction.cross(other_direction) + plane_through_self = Plane(p1=self.p1, normal_vector=normal) + return other.p1.distance(plane_through_self) + + if isinstance(other, Plane): + return other.distance(self) + + msg = f"{other} has type {type(other)}, which is unsupported" + raise NotImplementedError(msg) + + +class Ray3D(LinearEntity3D, Ray): + """ + A Ray is a semi-line in the space with a source point and a direction. + + Parameters + ========== + + p1 : Point3D + The source of the Ray + p2 : Point or a direction vector + direction_ratio: Determines the direction in which the Ray propagates. + + + Attributes + ========== + + source + xdirection + ydirection + zdirection + + See Also + ======== + + sympy.geometry.point.Point3D, Line3D + + + Examples + ======== + + >>> from sympy import Point3D, Ray3D + >>> r = Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0)) + >>> r + Ray3D(Point3D(2, 3, 4), Point3D(3, 5, 0)) + >>> r.points + (Point3D(2, 3, 4), Point3D(3, 5, 0)) + >>> r.source + Point3D(2, 3, 4) + >>> r.xdirection + oo + >>> r.ydirection + oo + >>> r.direction_ratio + [1, 2, -4] + + """ + def __new__(cls, p1, pt=None, direction_ratio=(), **kwargs): + if isinstance(p1, LinearEntity3D): + if pt is not None: + raise ValueError('If p1 is a LinearEntity, pt must be None') + p1, pt = p1.args + else: + p1 = Point(p1, dim=3) + if pt is not None and len(direction_ratio) == 0: + pt = Point(pt, dim=3) + elif len(direction_ratio) == 3 and pt is None: + pt = Point3D(p1.x + direction_ratio[0], p1.y + direction_ratio[1], + p1.z + direction_ratio[2]) + else: + raise ValueError(filldedent(''' + A 2nd Point or keyword "direction_ratio" must be used. + ''')) + + return LinearEntity3D.__new__(cls, p1, pt, **kwargs) + + @property + def xdirection(self): + """The x direction of the ray. + + Positive infinity if the ray points in the positive x direction, + negative infinity if the ray points in the negative x direction, + or 0 if the ray is vertical. + + See Also + ======== + + ydirection + + Examples + ======== + + >>> from sympy import Point3D, Ray3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, -1, 0) + >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3) + >>> r1.xdirection + oo + >>> r2.xdirection + 0 + + """ + if self.p1.x < self.p2.x: + return S.Infinity + elif self.p1.x == self.p2.x: + return S.Zero + else: + return S.NegativeInfinity + + @property + def ydirection(self): + """The y direction of the ray. + + Positive infinity if the ray points in the positive y direction, + negative infinity if the ray points in the negative y direction, + or 0 if the ray is horizontal. + + See Also + ======== + + xdirection + + Examples + ======== + + >>> from sympy import Point3D, Ray3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0) + >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3) + >>> r1.ydirection + -oo + >>> r2.ydirection + 0 + + """ + if self.p1.y < self.p2.y: + return S.Infinity + elif self.p1.y == self.p2.y: + return S.Zero + else: + return S.NegativeInfinity + + @property + def zdirection(self): + """The z direction of the ray. + + Positive infinity if the ray points in the positive z direction, + negative infinity if the ray points in the negative z direction, + or 0 if the ray is horizontal. + + See Also + ======== + + xdirection + + Examples + ======== + + >>> from sympy import Point3D, Ray3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(-1, -1, -1), Point3D(-1, 0, 0) + >>> r1, r2 = Ray3D(p1, p2), Ray3D(p1, p3) + >>> r1.ydirection + -oo + >>> r2.ydirection + 0 + >>> r2.zdirection + 0 + + """ + if self.p1.z < self.p2.z: + return S.Infinity + elif self.p1.z == self.p2.z: + return S.Zero + else: + return S.NegativeInfinity + + +class Segment3D(LinearEntity3D, Segment): + """A line segment in a 3D space. + + Parameters + ========== + + p1 : Point3D + p2 : Point3D + + Attributes + ========== + + length : number or SymPy expression + midpoint : Point3D + + See Also + ======== + + sympy.geometry.point.Point3D, Line3D + + Examples + ======== + + >>> from sympy import Point3D, Segment3D + >>> Segment3D((1, 0, 0), (1, 1, 1)) # tuples are interpreted as pts + Segment3D(Point3D(1, 0, 0), Point3D(1, 1, 1)) + >>> s = Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)); s + Segment3D(Point3D(4, 3, 9), Point3D(1, 1, 7)) + >>> s.points + (Point3D(4, 3, 9), Point3D(1, 1, 7)) + >>> s.length + sqrt(17) + >>> s.midpoint + Point3D(5/2, 2, 8) + + """ + def __new__(cls, p1, p2, **kwargs): + p1 = Point(p1, dim=3) + p2 = Point(p2, dim=3) + + if p1 == p2: + return p1 + + return LinearEntity3D.__new__(cls, p1, p2, **kwargs) diff --git a/phivenv/Lib/site-packages/sympy/geometry/parabola.py b/phivenv/Lib/site-packages/sympy/geometry/parabola.py new file mode 100644 index 0000000000000000000000000000000000000000..183c593785bb610e6f451a0c87abb2aa34d22494 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/parabola.py @@ -0,0 +1,422 @@ +"""Parabolic geometrical entity. + +Contains +* Parabola + +""" + +from sympy.core import S +from sympy.core.sorting import ordered +from sympy.core.symbol import _symbol, symbols +from sympy.geometry.entity import GeometryEntity, GeometrySet +from sympy.geometry.point import Point, Point2D +from sympy.geometry.line import Line, Line2D, Ray2D, Segment2D, LinearEntity3D +from sympy.geometry.ellipse import Ellipse +from sympy.functions import sign +from sympy.simplify.simplify import simplify +from sympy.solvers.solvers import solve + + +class Parabola(GeometrySet): + """A parabolic GeometryEntity. + + A parabola is declared with a point, that is called 'focus', and + a line, that is called 'directrix'. + Only vertical or horizontal parabolas are currently supported. + + Parameters + ========== + + focus : Point + Default value is Point(0, 0) + directrix : Line + + Attributes + ========== + + focus + directrix + axis of symmetry + focal length + p parameter + vertex + eccentricity + + Raises + ====== + ValueError + When `focus` is not a two dimensional point. + When `focus` is a point of directrix. + NotImplementedError + When `directrix` is neither horizontal nor vertical. + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7,8))) + >>> p1.focus + Point2D(0, 0) + >>> p1.directrix + Line2D(Point2D(5, 8), Point2D(7, 8)) + + """ + + def __new__(cls, focus=None, directrix=None, **kwargs): + + if focus: + focus = Point(focus, dim=2) + else: + focus = Point(0, 0) + + directrix = Line(directrix) + + if directrix.contains(focus): + raise ValueError('The focus must not be a point of directrix') + + return GeometryEntity.__new__(cls, focus, directrix, **kwargs) + + @property + def ambient_dimension(self): + """Returns the ambient dimension of parabola. + + Returns + ======= + + ambient_dimension : integer + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> f1 = Point(0, 0) + >>> p1 = Parabola(f1, Line(Point(5, 8), Point(7, 8))) + >>> p1.ambient_dimension + 2 + + """ + return 2 + + @property + def axis_of_symmetry(self): + """Return the axis of symmetry of the parabola: a line + perpendicular to the directrix passing through the focus. + + Returns + ======= + + axis_of_symmetry : Line + + See Also + ======== + + sympy.geometry.line.Line + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) + >>> p1.axis_of_symmetry + Line2D(Point2D(0, 0), Point2D(0, 1)) + + """ + return self.directrix.perpendicular_line(self.focus) + + @property + def directrix(self): + """The directrix of the parabola. + + Returns + ======= + + directrix : Line + + See Also + ======== + + sympy.geometry.line.Line + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> l1 = Line(Point(5, 8), Point(7, 8)) + >>> p1 = Parabola(Point(0, 0), l1) + >>> p1.directrix + Line2D(Point2D(5, 8), Point2D(7, 8)) + + """ + return self.args[1] + + @property + def eccentricity(self): + """The eccentricity of the parabola. + + Returns + ======= + + eccentricity : number + + A parabola may also be characterized as a conic section with an + eccentricity of 1. As a consequence of this, all parabolas are + similar, meaning that while they can be different sizes, + they are all the same shape. + + See Also + ======== + + https://en.wikipedia.org/wiki/Parabola + + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) + >>> p1.eccentricity + 1 + + Notes + ----- + The eccentricity for every Parabola is 1 by definition. + + """ + return S.One + + def equation(self, x='x', y='y'): + """The equation of the parabola. + + Parameters + ========== + x : str, optional + Label for the x-axis. Default value is 'x'. + y : str, optional + Label for the y-axis. Default value is 'y'. + + Returns + ======= + equation : SymPy expression + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) + >>> p1.equation() + -x**2 - 16*y + 64 + >>> p1.equation('f') + -f**2 - 16*y + 64 + >>> p1.equation(y='z') + -x**2 - 16*z + 64 + + """ + x = _symbol(x, real=True) + y = _symbol(y, real=True) + + m = self.directrix.slope + if m is S.Infinity: + t1 = 4 * (self.p_parameter) * (x - self.vertex.x) + t2 = (y - self.vertex.y)**2 + elif m == 0: + t1 = 4 * (self.p_parameter) * (y - self.vertex.y) + t2 = (x - self.vertex.x)**2 + else: + a, b = self.focus + c, d = self.directrix.coefficients[:2] + t1 = (x - a)**2 + (y - b)**2 + t2 = self.directrix.equation(x, y)**2/(c**2 + d**2) + return t1 - t2 + + @property + def focal_length(self): + """The focal length of the parabola. + + Returns + ======= + + focal_lenght : number or symbolic expression + + Notes + ===== + + The distance between the vertex and the focus + (or the vertex and directrix), measured along the axis + of symmetry, is the "focal length". + + See Also + ======== + + https://en.wikipedia.org/wiki/Parabola + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) + >>> p1.focal_length + 4 + + """ + distance = self.directrix.distance(self.focus) + focal_length = distance/2 + + return focal_length + + @property + def focus(self): + """The focus of the parabola. + + Returns + ======= + + focus : Point + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> f1 = Point(0, 0) + >>> p1 = Parabola(f1, Line(Point(5, 8), Point(7, 8))) + >>> p1.focus + Point2D(0, 0) + + """ + return self.args[0] + + def intersection(self, o): + """The intersection of the parabola and another geometrical entity `o`. + + Parameters + ========== + + o : GeometryEntity, LinearEntity + + Returns + ======= + + intersection : list of GeometryEntity objects + + Examples + ======== + + >>> from sympy import Parabola, Point, Ellipse, Line, Segment + >>> p1 = Point(0,0) + >>> l1 = Line(Point(1, -2), Point(-1,-2)) + >>> parabola1 = Parabola(p1, l1) + >>> parabola1.intersection(Ellipse(Point(0, 0), 2, 5)) + [Point2D(-2, 0), Point2D(2, 0)] + >>> parabola1.intersection(Line(Point(-7, 3), Point(12, 3))) + [Point2D(-4, 3), Point2D(4, 3)] + >>> parabola1.intersection(Segment((-12, -65), (14, -68))) + [] + + """ + x, y = symbols('x y', real=True) + parabola_eq = self.equation() + if isinstance(o, Parabola): + if o in self: + return [o] + else: + return list(ordered([Point(i) for i in solve( + [parabola_eq, o.equation()], [x, y], set=True)[1]])) + elif isinstance(o, Point2D): + if simplify(parabola_eq.subs([(x, o._args[0]), (y, o._args[1])])) == 0: + return [o] + else: + return [] + elif isinstance(o, (Segment2D, Ray2D)): + result = solve([parabola_eq, + Line2D(o.points[0], o.points[1]).equation()], + [x, y], set=True)[1] + return list(ordered([Point2D(i) for i in result if i in o])) + elif isinstance(o, (Line2D, Ellipse)): + return list(ordered([Point2D(i) for i in solve( + [parabola_eq, o.equation()], [x, y], set=True)[1]])) + elif isinstance(o, LinearEntity3D): + raise TypeError('Entity must be two dimensional, not three dimensional') + else: + raise TypeError('Wrong type of argument were put') + + @property + def p_parameter(self): + """P is a parameter of parabola. + + Returns + ======= + + p : number or symbolic expression + + Notes + ===== + + The absolute value of p is the focal length. The sign on p tells + which way the parabola faces. Vertical parabolas that open up + and horizontal that open right, give a positive value for p. + Vertical parabolas that open down and horizontal that open left, + give a negative value for p. + + + See Also + ======== + + https://www.sparknotes.com/math/precalc/conicsections/section2/ + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) + >>> p1.p_parameter + -4 + + """ + m = self.directrix.slope + if m is S.Infinity: + x = self.directrix.coefficients[2] + p = sign(self.focus.args[0] + x) + elif m == 0: + y = self.directrix.coefficients[2] + p = sign(self.focus.args[1] + y) + else: + d = self.directrix.projection(self.focus) + p = sign(self.focus.x - d.x) + return p * self.focal_length + + @property + def vertex(self): + """The vertex of the parabola. + + Returns + ======= + + vertex : Point + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Parabola, Point, Line + >>> p1 = Parabola(Point(0, 0), Line(Point(5, 8), Point(7, 8))) + >>> p1.vertex + Point2D(0, 4) + + """ + focus = self.focus + m = self.directrix.slope + if m is S.Infinity: + vertex = Point(focus.args[0] - self.p_parameter, focus.args[1]) + elif m == 0: + vertex = Point(focus.args[0], focus.args[1] - self.p_parameter) + else: + vertex = self.axis_of_symmetry.intersection(self)[0] + return vertex diff --git a/phivenv/Lib/site-packages/sympy/geometry/plane.py b/phivenv/Lib/site-packages/sympy/geometry/plane.py new file mode 100644 index 0000000000000000000000000000000000000000..509dc4be5dc41c5df7c33561fdbe5bb0b6620352 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/plane.py @@ -0,0 +1,878 @@ +"""Geometrical Planes. + +Contains +======== +Plane + +""" + +from sympy.core import Dummy, Rational, S, Symbol +from sympy.core.symbol import _symbol +from sympy.functions.elementary.trigonometric import cos, sin, acos, asin, sqrt +from .entity import GeometryEntity +from .line import (Line, Ray, Segment, Line3D, LinearEntity, LinearEntity3D, + Ray3D, Segment3D) +from .point import Point, Point3D +from sympy.matrices import Matrix +from sympy.polys.polytools import cancel +from sympy.solvers import solve, linsolve +from sympy.utilities.iterables import uniq, is_sequence +from sympy.utilities.misc import filldedent, func_name, Undecidable + +from mpmath.libmp.libmpf import prec_to_dps + +import random + + +x, y, z, t = [Dummy('plane_dummy') for i in range(4)] + + +class Plane(GeometryEntity): + """ + A plane is a flat, two-dimensional surface. A plane is the two-dimensional + analogue of a point (zero-dimensions), a line (one-dimension) and a solid + (three-dimensions). A plane can generally be constructed by two types of + inputs. They are: + - three non-collinear points + - a point and the plane's normal vector + + Attributes + ========== + + p1 + normal_vector + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2)) + Plane(Point3D(1, 1, 1), (-1, 2, -1)) + >>> Plane((1, 1, 1), (2, 3, 4), (2, 2, 2)) + Plane(Point3D(1, 1, 1), (-1, 2, -1)) + >>> Plane(Point3D(1, 1, 1), normal_vector=(1,4,7)) + Plane(Point3D(1, 1, 1), (1, 4, 7)) + + """ + def __new__(cls, p1, a=None, b=None, **kwargs): + p1 = Point3D(p1, dim=3) + if a and b: + p2 = Point(a, dim=3) + p3 = Point(b, dim=3) + if Point3D.are_collinear(p1, p2, p3): + raise ValueError('Enter three non-collinear points') + a = p1.direction_ratio(p2) + b = p1.direction_ratio(p3) + normal_vector = tuple(Matrix(a).cross(Matrix(b))) + else: + a = kwargs.pop('normal_vector', a) + evaluate = kwargs.get('evaluate', True) + if is_sequence(a) and len(a) == 3: + normal_vector = Point3D(a).args if evaluate else a + else: + raise ValueError(filldedent(''' + Either provide 3 3D points or a point with a + normal vector expressed as a sequence of length 3''')) + if all(coord.is_zero for coord in normal_vector): + raise ValueError('Normal vector cannot be zero vector') + return GeometryEntity.__new__(cls, p1, normal_vector, **kwargs) + + def __contains__(self, o): + k = self.equation(x, y, z) + if isinstance(o, (LinearEntity, LinearEntity3D)): + d = Point3D(o.arbitrary_point(t)) + e = k.subs([(x, d.x), (y, d.y), (z, d.z)]) + return e.equals(0) + try: + o = Point(o, dim=3, strict=True) + d = k.xreplace(dict(zip((x, y, z), o.args))) + return d.equals(0) + except TypeError: + return False + + def _eval_evalf(self, prec=15, **options): + pt, tup = self.args + dps = prec_to_dps(prec) + pt = pt.evalf(n=dps, **options) + tup = tuple([i.evalf(n=dps, **options) for i in tup]) + return self.func(pt, normal_vector=tup, evaluate=False) + + def angle_between(self, o): + """Angle between the plane and other geometric entity. + + Parameters + ========== + + LinearEntity3D, Plane. + + Returns + ======= + + angle : angle in radians + + Notes + ===== + + This method accepts only 3D entities as it's parameter, but if you want + to calculate the angle between a 2D entity and a plane you should + first convert to a 3D entity by projecting onto a desired plane and + then proceed to calculate the angle. + + Examples + ======== + + >>> from sympy import Point3D, Line3D, Plane + >>> a = Plane(Point3D(1, 2, 2), normal_vector=(1, 2, 3)) + >>> b = Line3D(Point3D(1, 3, 4), Point3D(2, 2, 2)) + >>> a.angle_between(b) + -asin(sqrt(21)/6) + + """ + if isinstance(o, LinearEntity3D): + a = Matrix(self.normal_vector) + b = Matrix(o.direction_ratio) + c = a.dot(b) + d = sqrt(sum(i**2 for i in self.normal_vector)) + e = sqrt(sum(i**2 for i in o.direction_ratio)) + return asin(c/(d*e)) + if isinstance(o, Plane): + a = Matrix(self.normal_vector) + b = Matrix(o.normal_vector) + c = a.dot(b) + d = sqrt(sum(i**2 for i in self.normal_vector)) + e = sqrt(sum(i**2 for i in o.normal_vector)) + return acos(c/(d*e)) + + + def arbitrary_point(self, u=None, v=None): + """ Returns an arbitrary point on the Plane. If given two + parameters, the point ranges over the entire plane. If given 1 + or no parameters, returns a point with one parameter which, + when varying from 0 to 2*pi, moves the point in a circle of + radius 1 about p1 of the Plane. + + Examples + ======== + + >>> from sympy import Plane, Ray + >>> from sympy.abc import u, v, t, r + >>> p = Plane((1, 1, 1), normal_vector=(1, 0, 0)) + >>> p.arbitrary_point(u, v) + Point3D(1, u + 1, v + 1) + >>> p.arbitrary_point(t) + Point3D(1, cos(t) + 1, sin(t) + 1) + + While arbitrary values of u and v can move the point anywhere in + the plane, the single-parameter point can be used to construct a + ray whose arbitrary point can be located at angle t and radius + r from p.p1: + + >>> Ray(p.p1, _).arbitrary_point(r) + Point3D(1, r*cos(t) + 1, r*sin(t) + 1) + + Returns + ======= + + Point3D + + """ + circle = v is None + if circle: + u = _symbol(u or 't', real=True) + else: + u = _symbol(u or 'u', real=True) + v = _symbol(v or 'v', real=True) + x, y, z = self.normal_vector + a, b, c = self.p1.args + # x1, y1, z1 is a nonzero vector parallel to the plane + if x.is_zero and y.is_zero: + x1, y1, z1 = S.One, S.Zero, S.Zero + else: + x1, y1, z1 = -y, x, S.Zero + # x2, y2, z2 is also parallel to the plane, and orthogonal to x1, y1, z1 + x2, y2, z2 = tuple(Matrix((x, y, z)).cross(Matrix((x1, y1, z1)))) + if circle: + x1, y1, z1 = (w/sqrt(x1**2 + y1**2 + z1**2) for w in (x1, y1, z1)) + x2, y2, z2 = (w/sqrt(x2**2 + y2**2 + z2**2) for w in (x2, y2, z2)) + p = Point3D(a + x1*cos(u) + x2*sin(u), \ + b + y1*cos(u) + y2*sin(u), \ + c + z1*cos(u) + z2*sin(u)) + else: + p = Point3D(a + x1*u + x2*v, b + y1*u + y2*v, c + z1*u + z2*v) + return p + + + @staticmethod + def are_concurrent(*planes): + """Is a sequence of Planes concurrent? + + Two or more Planes are concurrent if their intersections + are a common line. + + Parameters + ========== + + planes: list + + Returns + ======= + + Boolean + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a = Plane(Point3D(5, 0, 0), normal_vector=(1, -1, 1)) + >>> b = Plane(Point3D(0, -2, 0), normal_vector=(3, 1, 1)) + >>> c = Plane(Point3D(0, -1, 0), normal_vector=(5, -1, 9)) + >>> Plane.are_concurrent(a, b) + True + >>> Plane.are_concurrent(a, b, c) + False + + """ + planes = list(uniq(planes)) + for i in planes: + if not isinstance(i, Plane): + raise ValueError('All objects should be Planes but got %s' % i.func) + if len(planes) < 2: + return False + planes = list(planes) + first = planes.pop(0) + sol = first.intersection(planes[0]) + if sol == []: + return False + else: + line = sol[0] + for i in planes[1:]: + l = first.intersection(i) + if not l or l[0] not in line: + return False + return True + + + def distance(self, o): + """Distance between the plane and another geometric entity. + + Parameters + ========== + + Point3D, LinearEntity3D, Plane. + + Returns + ======= + + distance + + Notes + ===== + + This method accepts only 3D entities as it's parameter, but if you want + to calculate the distance between a 2D entity and a plane you should + first convert to a 3D entity by projecting onto a desired plane and + then proceed to calculate the distance. + + Examples + ======== + + >>> from sympy import Point3D, Line3D, Plane + >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1)) + >>> b = Point3D(1, 2, 3) + >>> a.distance(b) + sqrt(3) + >>> c = Line3D(Point3D(2, 3, 1), Point3D(1, 2, 2)) + >>> a.distance(c) + 0 + + """ + if self.intersection(o) != []: + return S.Zero + + if isinstance(o, (Segment3D, Ray3D)): + a, b = o.p1, o.p2 + pi, = self.intersection(Line3D(a, b)) + if pi in o: + return self.distance(pi) + elif a in Segment3D(pi, b): + return self.distance(a) + else: + assert isinstance(o, Segment3D) is True + return self.distance(b) + + # following code handles `Point3D`, `LinearEntity3D`, `Plane` + a = o if isinstance(o, Point3D) else o.p1 + n = Point3D(self.normal_vector).unit + d = (a - self.p1).dot(n) + return abs(d) + + + def equals(self, o): + """ + Returns True if self and o are the same mathematical entities. + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1)) + >>> b = Plane(Point3D(1, 2, 3), normal_vector=(2, 2, 2)) + >>> c = Plane(Point3D(1, 2, 3), normal_vector=(-1, 4, 6)) + >>> a.equals(a) + True + >>> a.equals(b) + True + >>> a.equals(c) + False + """ + if isinstance(o, Plane): + a = self.equation() + b = o.equation() + return cancel(a/b).is_constant() + else: + return False + + + def equation(self, x=None, y=None, z=None): + """The equation of the Plane. + + Examples + ======== + + >>> from sympy import Point3D, Plane + >>> a = Plane(Point3D(1, 1, 2), Point3D(2, 4, 7), Point3D(3, 5, 1)) + >>> a.equation() + -23*x + 11*y - 2*z + 16 + >>> a = Plane(Point3D(1, 4, 2), normal_vector=(6, 6, 6)) + >>> a.equation() + 6*x + 6*y + 6*z - 42 + + """ + x, y, z = [i if i else Symbol(j, real=True) for i, j in zip((x, y, z), 'xyz')] + a = Point3D(x, y, z) + b = self.p1.direction_ratio(a) + c = self.normal_vector + return (sum(i*j for i, j in zip(b, c))) + + + def intersection(self, o): + """ The intersection with other geometrical entity. + + Parameters + ========== + + Point, Point3D, LinearEntity, LinearEntity3D, Plane + + Returns + ======= + + List + + Examples + ======== + + >>> from sympy import Point3D, Line3D, Plane + >>> a = Plane(Point3D(1, 2, 3), normal_vector=(1, 1, 1)) + >>> b = Point3D(1, 2, 3) + >>> a.intersection(b) + [Point3D(1, 2, 3)] + >>> c = Line3D(Point3D(1, 4, 7), Point3D(2, 2, 2)) + >>> a.intersection(c) + [Point3D(2, 2, 2)] + >>> d = Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3)) + >>> e = Plane(Point3D(2, 0, 0), normal_vector=(3, 4, -3)) + >>> d.intersection(e) + [Line3D(Point3D(78/23, -24/23, 0), Point3D(147/23, 321/23, 23))] + + """ + if not isinstance(o, GeometryEntity): + o = Point(o, dim=3) + if isinstance(o, Point): + if o in self: + return [o] + else: + return [] + if isinstance(o, (LinearEntity, LinearEntity3D)): + # recast to 3D + p1, p2 = o.p1, o.p2 + if isinstance(o, Segment): + o = Segment3D(p1, p2) + elif isinstance(o, Ray): + o = Ray3D(p1, p2) + elif isinstance(o, Line): + o = Line3D(p1, p2) + else: + raise ValueError('unhandled linear entity: %s' % o.func) + if o in self: + return [o] + else: + a = Point3D(o.arbitrary_point(t)) + p1, n = self.p1, Point3D(self.normal_vector) + + # TODO: Replace solve with solveset, when this line is tested + c = solve((a - p1).dot(n), t) + if not c: + return [] + else: + c = [i for i in c if i.is_real is not False] + if len(c) > 1: + c = [i for i in c if i.is_real] + if len(c) != 1: + raise Undecidable("not sure which point is real") + p = a.subs(t, c[0]) + if p not in o: + return [] # e.g. a segment might not intersect a plane + return [p] + if isinstance(o, Plane): + if self.equals(o): + return [self] + if self.is_parallel(o): + return [] + else: + x, y, z = map(Dummy, 'xyz') + a, b = Matrix([self.normal_vector]), Matrix([o.normal_vector]) + c = list(a.cross(b)) + d = self.equation(x, y, z) + e = o.equation(x, y, z) + result = list(linsolve([d, e], x, y, z))[0] + for i in (x, y, z): result = result.subs(i, 0) + return [Line3D(Point3D(result), direction_ratio=c)] + + + def is_coplanar(self, o): + """ Returns True if `o` is coplanar with self, else False. + + Examples + ======== + + >>> from sympy import Plane + >>> o = (0, 0, 0) + >>> p = Plane(o, (1, 1, 1)) + >>> p2 = Plane(o, (2, 2, 2)) + >>> p == p2 + False + >>> p.is_coplanar(p2) + True + """ + if isinstance(o, Plane): + return not cancel(self.equation(x, y, z)/o.equation(x, y, z)).has(x, y, z) + if isinstance(o, Point3D): + return o in self + elif isinstance(o, LinearEntity3D): + return all(i in self for i in self) + elif isinstance(o, GeometryEntity): # XXX should only be handling 2D objects now + return all(i == 0 for i in self.normal_vector[:2]) + + + def is_parallel(self, l): + """Is the given geometric entity parallel to the plane? + + Parameters + ========== + + LinearEntity3D or Plane + + Returns + ======= + + Boolean + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6)) + >>> b = Plane(Point3D(3,1,3), normal_vector=(4, 8, 12)) + >>> a.is_parallel(b) + True + + """ + if isinstance(l, LinearEntity3D): + a = l.direction_ratio + b = self.normal_vector + return sum(i*j for i, j in zip(a, b)) == 0 + if isinstance(l, Plane): + a = Matrix(l.normal_vector) + b = Matrix(self.normal_vector) + return bool(a.cross(b).is_zero_matrix) + + + def is_perpendicular(self, l): + """Is the given geometric entity perpendicualar to the given plane? + + Parameters + ========== + + LinearEntity3D or Plane + + Returns + ======= + + Boolean + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6)) + >>> b = Plane(Point3D(2, 2, 2), normal_vector=(-1, 2, -1)) + >>> a.is_perpendicular(b) + True + + """ + if isinstance(l, LinearEntity3D): + a = Matrix(l.direction_ratio) + b = Matrix(self.normal_vector) + if a.cross(b).is_zero_matrix: + return True + else: + return False + elif isinstance(l, Plane): + a = Matrix(l.normal_vector) + b = Matrix(self.normal_vector) + if a.dot(b) == 0: + return True + else: + return False + else: + return False + + @property + def normal_vector(self): + """Normal vector of the given plane. + + Examples + ======== + + >>> from sympy import Point3D, Plane + >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2)) + >>> a.normal_vector + (-1, 2, -1) + >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 4, 7)) + >>> a.normal_vector + (1, 4, 7) + + """ + return self.args[1] + + @property + def p1(self): + """The only defining point of the plane. Others can be obtained from the + arbitrary_point method. + + See Also + ======== + + sympy.geometry.point.Point3D + + Examples + ======== + + >>> from sympy import Point3D, Plane + >>> a = Plane(Point3D(1, 1, 1), Point3D(2, 3, 4), Point3D(2, 2, 2)) + >>> a.p1 + Point3D(1, 1, 1) + + """ + return self.args[0] + + def parallel_plane(self, pt): + """ + Plane parallel to the given plane and passing through the point pt. + + Parameters + ========== + + pt: Point3D + + Returns + ======= + + Plane + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a = Plane(Point3D(1, 4, 6), normal_vector=(2, 4, 6)) + >>> a.parallel_plane(Point3D(2, 3, 5)) + Plane(Point3D(2, 3, 5), (2, 4, 6)) + + """ + a = self.normal_vector + return Plane(pt, normal_vector=a) + + def perpendicular_line(self, pt): + """A line perpendicular to the given plane. + + Parameters + ========== + + pt: Point3D + + Returns + ======= + + Line3D + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a = Plane(Point3D(1,4,6), normal_vector=(2, 4, 6)) + >>> a.perpendicular_line(Point3D(9, 8, 7)) + Line3D(Point3D(9, 8, 7), Point3D(11, 12, 13)) + + """ + a = self.normal_vector + return Line3D(pt, direction_ratio=a) + + def perpendicular_plane(self, *pts): + """ + Return a perpendicular passing through the given points. If the + direction ratio between the points is the same as the Plane's normal + vector then, to select from the infinite number of possible planes, + a third point will be chosen on the z-axis (or the y-axis + if the normal vector is already parallel to the z-axis). If less than + two points are given they will be supplied as follows: if no point is + given then pt1 will be self.p1; if a second point is not given it will + be a point through pt1 on a line parallel to the z-axis (if the normal + is not already the z-axis, otherwise on the line parallel to the + y-axis). + + Parameters + ========== + + pts: 0, 1 or 2 Point3D + + Returns + ======= + + Plane + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> a, b = Point3D(0, 0, 0), Point3D(0, 1, 0) + >>> Z = (0, 0, 1) + >>> p = Plane(a, normal_vector=Z) + >>> p.perpendicular_plane(a, b) + Plane(Point3D(0, 0, 0), (1, 0, 0)) + """ + if len(pts) > 2: + raise ValueError('No more than 2 pts should be provided.') + + pts = list(pts) + if len(pts) == 0: + pts.append(self.p1) + if len(pts) == 1: + x, y, z = self.normal_vector + if x == y == 0: + dir = (0, 1, 0) + else: + dir = (0, 0, 1) + pts.append(pts[0] + Point3D(*dir)) + + p1, p2 = [Point(i, dim=3) for i in pts] + l = Line3D(p1, p2) + n = Line3D(p1, direction_ratio=self.normal_vector) + if l in n: # XXX should an error be raised instead? + # there are infinitely many perpendicular planes; + x, y, z = self.normal_vector + if x == y == 0: + # the z axis is the normal so pick a pt on the y-axis + p3 = Point3D(0, 1, 0) # case 1 + else: + # else pick a pt on the z axis + p3 = Point3D(0, 0, 1) # case 2 + # in case that point is already given, move it a bit + if p3 in l: + p3 *= 2 # case 3 + else: + p3 = p1 + Point3D(*self.normal_vector) # case 4 + return Plane(p1, p2, p3) + + def projection_line(self, line): + """Project the given line onto the plane through the normal plane + containing the line. + + Parameters + ========== + + LinearEntity or LinearEntity3D + + Returns + ======= + + Point3D, Line3D, Ray3D or Segment3D + + Notes + ===== + + For the interaction between 2D and 3D lines(segments, rays), you should + convert the line to 3D by using this method. For example for finding the + intersection between a 2D and a 3D line, convert the 2D line to a 3D line + by projecting it on a required plane and then proceed to find the + intersection between those lines. + + Examples + ======== + + >>> from sympy import Plane, Line, Line3D, Point3D + >>> a = Plane(Point3D(1, 1, 1), normal_vector=(1, 1, 1)) + >>> b = Line(Point3D(1, 1), Point3D(2, 2)) + >>> a.projection_line(b) + Line3D(Point3D(4/3, 4/3, 1/3), Point3D(5/3, 5/3, -1/3)) + >>> c = Line3D(Point3D(1, 1, 1), Point3D(2, 2, 2)) + >>> a.projection_line(c) + Point3D(1, 1, 1) + + """ + if not isinstance(line, (LinearEntity, LinearEntity3D)): + raise NotImplementedError('Enter a linear entity only') + a, b = self.projection(line.p1), self.projection(line.p2) + if a == b: + # projection does not imply intersection so for + # this case (line parallel to plane's normal) we + # return the projection point + return a + if isinstance(line, (Line, Line3D)): + return Line3D(a, b) + if isinstance(line, (Ray, Ray3D)): + return Ray3D(a, b) + if isinstance(line, (Segment, Segment3D)): + return Segment3D(a, b) + + def projection(self, pt): + """Project the given point onto the plane along the plane normal. + + Parameters + ========== + + Point or Point3D + + Returns + ======= + + Point3D + + Examples + ======== + + >>> from sympy import Plane, Point3D + >>> A = Plane(Point3D(1, 1, 2), normal_vector=(1, 1, 1)) + + The projection is along the normal vector direction, not the z + axis, so (1, 1) does not project to (1, 1, 2) on the plane A: + + >>> b = Point3D(1, 1) + >>> A.projection(b) + Point3D(5/3, 5/3, 2/3) + >>> _ in A + True + + But the point (1, 1, 2) projects to (1, 1) on the XY-plane: + + >>> XY = Plane((0, 0, 0), (0, 0, 1)) + >>> XY.projection((1, 1, 2)) + Point3D(1, 1, 0) + """ + rv = Point(pt, dim=3) + if rv in self: + return rv + return self.intersection(Line3D(rv, rv + Point3D(self.normal_vector)))[0] + + def random_point(self, seed=None): + """ Returns a random point on the Plane. + + Returns + ======= + + Point3D + + Examples + ======== + + >>> from sympy import Plane + >>> p = Plane((1, 0, 0), normal_vector=(0, 1, 0)) + >>> r = p.random_point(seed=42) # seed value is optional + >>> r.n(3) + Point3D(2.29, 0, -1.35) + + The random point can be moved to lie on the circle of radius + 1 centered on p1: + + >>> c = p.p1 + (r - p.p1).unit + >>> c.distance(p.p1).equals(1) + True + """ + if seed is not None: + rng = random.Random(seed) + else: + rng = random + params = { + x: 2*Rational(rng.gauss(0, 1)) - 1, + y: 2*Rational(rng.gauss(0, 1)) - 1} + return self.arbitrary_point(x, y).subs(params) + + def parameter_value(self, other, u, v=None): + """Return the parameter(s) corresponding to the given point. + + Examples + ======== + + >>> from sympy import pi, Plane + >>> from sympy.abc import t, u, v + >>> p = Plane((2, 0, 0), (0, 0, 1), (0, 1, 0)) + + By default, the parameter value returned defines a point + that is a distance of 1 from the Plane's p1 value and + in line with the given point: + + >>> on_circle = p.arbitrary_point(t).subs(t, pi/4) + >>> on_circle.distance(p.p1) + 1 + >>> p.parameter_value(on_circle, t) + {t: pi/4} + + Moving the point twice as far from p1 does not change + the parameter value: + + >>> off_circle = p.p1 + (on_circle - p.p1)*2 + >>> off_circle.distance(p.p1) + 2 + >>> p.parameter_value(off_circle, t) + {t: pi/4} + + If the 2-value parameter is desired, supply the two + parameter symbols and a replacement dictionary will + be returned: + + >>> p.parameter_value(on_circle, u, v) + {u: sqrt(10)/10, v: sqrt(10)/30} + >>> p.parameter_value(off_circle, u, v) + {u: sqrt(10)/5, v: sqrt(10)/15} + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if not isinstance(other, Point): + raise ValueError("other must be a point") + if other == self.p1: + return other + if isinstance(u, Symbol) and v is None: + delta = self.arbitrary_point(u) - self.p1 + eq = delta - (other - self.p1).unit + sol = solve(eq, u, dict=True) + elif isinstance(u, Symbol) and isinstance(v, Symbol): + pt = self.arbitrary_point(u, v) + sol = solve(pt - other, (u, v), dict=True) + else: + raise ValueError('expecting 1 or 2 symbols') + if not sol: + raise ValueError("Given point is not on %s" % func_name(self)) + return sol[0] # {t: tval} or {u: uval, v: vval} + + @property + def ambient_dimension(self): + return self.p1.ambient_dimension diff --git a/phivenv/Lib/site-packages/sympy/geometry/point.py b/phivenv/Lib/site-packages/sympy/geometry/point.py new file mode 100644 index 0000000000000000000000000000000000000000..19e6c566f06de4df086912470dc35d0f4af3bd38 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/point.py @@ -0,0 +1,1378 @@ +"""Geometrical Points. + +Contains +======== +Point +Point2D +Point3D + +When methods of Point require 1 or more points as arguments, they +can be passed as a sequence of coordinates or Points: + +>>> from sympy import Point +>>> Point(1, 1).is_collinear((2, 2), (3, 4)) +False +>>> Point(1, 1).is_collinear(Point(2, 2), Point(3, 4)) +False + +""" + +import warnings + +from sympy.core import S, sympify, Expr +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.numbers import Float +from sympy.core.parameters import global_parameters +from sympy.simplify.simplify import nsimplify, simplify +from sympy.geometry.exceptions import GeometryError +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.complexes import im +from sympy.functions.elementary.trigonometric import cos, sin +from sympy.matrices import Matrix +from sympy.matrices.expressions import Transpose +from sympy.utilities.iterables import uniq, is_sequence +from sympy.utilities.misc import filldedent, func_name, Undecidable + +from .entity import GeometryEntity + +from mpmath.libmp.libmpf import prec_to_dps + + +class Point(GeometryEntity): + """A point in a n-dimensional Euclidean space. + + Parameters + ========== + + coords : sequence of n-coordinate values. In the special + case where n=2 or 3, a Point2D or Point3D will be created + as appropriate. + evaluate : if `True` (default), all floats are turn into + exact types. + dim : number of coordinates the point should have. If coordinates + are unspecified, they are padded with zeros. + on_morph : indicates what should happen when the number of + coordinates of a point need to be changed by adding or + removing zeros. Possible values are `'warn'`, `'error'`, or + `ignore` (default). No warning or error is given when `*args` + is empty and `dim` is given. An error is always raised when + trying to remove nonzero coordinates. + + + Attributes + ========== + + length + origin: A `Point` representing the origin of the + appropriately-dimensioned space. + + Raises + ====== + + TypeError : When instantiating with anything but a Point or sequence + ValueError : when instantiating with a sequence with length < 2 or + when trying to reduce dimensions if keyword `on_morph='error'` is + set. + + See Also + ======== + + sympy.geometry.line.Segment : Connects two Points + + Examples + ======== + + >>> from sympy import Point + >>> from sympy.abc import x + >>> Point(1, 2, 3) + Point3D(1, 2, 3) + >>> Point([1, 2]) + Point2D(1, 2) + >>> Point(0, x) + Point2D(0, x) + >>> Point(dim=4) + Point(0, 0, 0, 0) + + Floats are automatically converted to Rational unless the + evaluate flag is False: + + >>> Point(0.5, 0.25) + Point2D(1/2, 1/4) + >>> Point(0.5, 0.25, evaluate=False) + Point2D(0.5, 0.25) + + """ + + is_Point = True + + def __new__(cls, *args, **kwargs): + evaluate = kwargs.get('evaluate', global_parameters.evaluate) + on_morph = kwargs.get('on_morph', 'ignore') + + # unpack into coords + coords = args[0] if len(args) == 1 else args + + # check args and handle quickly handle Point instances + if isinstance(coords, Point): + # even if we're mutating the dimension of a point, we + # don't reevaluate its coordinates + evaluate = False + if len(coords) == kwargs.get('dim', len(coords)): + return coords + + if not is_sequence(coords): + raise TypeError(filldedent(''' + Expecting sequence of coordinates, not `{}`''' + .format(func_name(coords)))) + # A point where only `dim` is specified is initialized + # to zeros. + if len(coords) == 0 and kwargs.get('dim', None): + coords = (S.Zero,)*kwargs.get('dim') + + coords = Tuple(*coords) + dim = kwargs.get('dim', len(coords)) + + if len(coords) < 2: + raise ValueError(filldedent(''' + Point requires 2 or more coordinates or + keyword `dim` > 1.''')) + if len(coords) != dim: + message = ("Dimension of {} needs to be changed " + "from {} to {}.").format(coords, len(coords), dim) + if on_morph == 'ignore': + pass + elif on_morph == "error": + raise ValueError(message) + elif on_morph == 'warn': + warnings.warn(message, stacklevel=2) + else: + raise ValueError(filldedent(''' + on_morph value should be 'error', + 'warn' or 'ignore'.''')) + if any(coords[dim:]): + raise ValueError('Nonzero coordinates cannot be removed.') + if any(a.is_number and im(a).is_zero is False for a in coords): + raise ValueError('Imaginary coordinates are not permitted.') + if not all(isinstance(a, Expr) for a in coords): + raise TypeError('Coordinates must be valid SymPy expressions.') + + # pad with zeros appropriately + coords = coords[:dim] + (S.Zero,)*(dim - len(coords)) + + # Turn any Floats into rationals and simplify + # any expressions before we instantiate + if evaluate: + coords = coords.xreplace({ + f: simplify(nsimplify(f, rational=True)) + for f in coords.atoms(Float)}) + + # return 2D or 3D instances + if len(coords) == 2: + kwargs['_nocheck'] = True + return Point2D(*coords, **kwargs) + elif len(coords) == 3: + kwargs['_nocheck'] = True + return Point3D(*coords, **kwargs) + + # the general Point + return GeometryEntity.__new__(cls, *coords) + + def __abs__(self): + """Returns the distance between this point and the origin.""" + origin = Point([0]*len(self)) + return Point.distance(origin, self) + + def __add__(self, other): + """Add other to self by incrementing self's coordinates by + those of other. + + Notes + ===== + + >>> from sympy import Point + + When sequences of coordinates are passed to Point methods, they + are converted to a Point internally. This __add__ method does + not do that so if floating point values are used, a floating + point result (in terms of SymPy Floats) will be returned. + + >>> Point(1, 2) + (.1, .2) + Point2D(1.1, 2.2) + + If this is not desired, the `translate` method can be used or + another Point can be added: + + >>> Point(1, 2).translate(.1, .2) + Point2D(11/10, 11/5) + >>> Point(1, 2) + Point(.1, .2) + Point2D(11/10, 11/5) + + See Also + ======== + + sympy.geometry.point.Point.translate + + """ + try: + s, o = Point._normalize_dimension(self, Point(other, evaluate=False)) + except TypeError: + raise GeometryError("Don't know how to add {} and a Point object".format(other)) + + coords = [simplify(a + b) for a, b in zip(s, o)] + return Point(coords, evaluate=False) + + def __contains__(self, item): + return item in self.args + + def __truediv__(self, divisor): + """Divide point's coordinates by a factor.""" + divisor = sympify(divisor) + coords = [simplify(x/divisor) for x in self.args] + return Point(coords, evaluate=False) + + def __eq__(self, other): + if not isinstance(other, Point) or len(self.args) != len(other.args): + return False + return self.args == other.args + + def __getitem__(self, key): + return self.args[key] + + def __hash__(self): + return hash(self.args) + + def __iter__(self): + return self.args.__iter__() + + def __len__(self): + return len(self.args) + + def __mul__(self, factor): + """Multiply point's coordinates by a factor. + + Notes + ===== + + >>> from sympy import Point + + When multiplying a Point by a floating point number, + the coordinates of the Point will be changed to Floats: + + >>> Point(1, 2)*0.1 + Point2D(0.1, 0.2) + + If this is not desired, the `scale` method can be used or + else only multiply or divide by integers: + + >>> Point(1, 2).scale(1.1, 1.1) + Point2D(11/10, 11/5) + >>> Point(1, 2)*11/10 + Point2D(11/10, 11/5) + + See Also + ======== + + sympy.geometry.point.Point.scale + """ + factor = sympify(factor) + coords = [simplify(x*factor) for x in self.args] + return Point(coords, evaluate=False) + + def __rmul__(self, factor): + """Multiply a factor by point's coordinates.""" + return self.__mul__(factor) + + def __neg__(self): + """Negate the point.""" + coords = [-x for x in self.args] + return Point(coords, evaluate=False) + + def __sub__(self, other): + """Subtract two points, or subtract a factor from this point's + coordinates.""" + return self + [-x for x in other] + + @classmethod + def _normalize_dimension(cls, *points, **kwargs): + """Ensure that points have the same dimension. + By default `on_morph='warn'` is passed to the + `Point` constructor.""" + # if we have a built-in ambient dimension, use it + dim = getattr(cls, '_ambient_dimension', None) + # override if we specified it + dim = kwargs.get('dim', dim) + # if no dim was given, use the highest dimensional point + if dim is None: + dim = max(i.ambient_dimension for i in points) + if all(i.ambient_dimension == dim for i in points): + return list(points) + kwargs['dim'] = dim + kwargs['on_morph'] = kwargs.get('on_morph', 'warn') + return [Point(i, **kwargs) for i in points] + + @staticmethod + def affine_rank(*args): + """The affine rank of a set of points is the dimension + of the smallest affine space containing all the points. + For example, if the points lie on a line (and are not all + the same) their affine rank is 1. If the points lie on a plane + but not a line, their affine rank is 2. By convention, the empty + set has affine rank -1.""" + + if len(args) == 0: + return -1 + # make sure we're genuinely points + # and translate every point to the origin + points = Point._normalize_dimension(*[Point(i) for i in args]) + origin = points[0] + points = [i - origin for i in points[1:]] + + m = Matrix([i.args for i in points]) + # XXX fragile -- what is a better way? + return m.rank(iszerofunc = lambda x: + abs(x.n(2)) < 1e-12 if x.is_number else x.is_zero) + + @property + def ambient_dimension(self): + """Number of components this point has.""" + return getattr(self, '_ambient_dimension', len(self)) + + @classmethod + def are_coplanar(cls, *points): + """Return True if there exists a plane in which all the points + lie. A trivial True value is returned if `len(points) < 3` or + all Points are 2-dimensional. + + Parameters + ========== + + A set of points + + Raises + ====== + + ValueError : if less than 3 unique points are given + + Returns + ======= + + boolean + + Examples + ======== + + >>> from sympy import Point3D + >>> p1 = Point3D(1, 2, 2) + >>> p2 = Point3D(2, 7, 2) + >>> p3 = Point3D(0, 0, 2) + >>> p4 = Point3D(1, 1, 2) + >>> Point3D.are_coplanar(p1, p2, p3, p4) + True + >>> p5 = Point3D(0, 1, 3) + >>> Point3D.are_coplanar(p1, p2, p3, p5) + False + + """ + if len(points) <= 1: + return True + + points = cls._normalize_dimension(*[Point(i) for i in points]) + # quick exit if we are in 2D + if points[0].ambient_dimension == 2: + return True + points = list(uniq(points)) + return Point.affine_rank(*points) <= 2 + + def distance(self, other): + """The Euclidean distance between self and another GeometricEntity. + + Returns + ======= + + distance : number or symbolic expression. + + Raises + ====== + + TypeError : if other is not recognized as a GeometricEntity or is a + GeometricEntity for which distance is not defined. + + See Also + ======== + + sympy.geometry.line.Segment.length + sympy.geometry.point.Point.taxicab_distance + + Examples + ======== + + >>> from sympy import Point, Line + >>> p1, p2 = Point(1, 1), Point(4, 5) + >>> l = Line((3, 1), (2, 2)) + >>> p1.distance(p2) + 5 + >>> p1.distance(l) + sqrt(2) + + The computed distance may be symbolic, too: + + >>> from sympy.abc import x, y + >>> p3 = Point(x, y) + >>> p3.distance((0, 0)) + sqrt(x**2 + y**2) + + """ + if not isinstance(other, GeometryEntity): + try: + other = Point(other, dim=self.ambient_dimension) + except TypeError: + raise TypeError("not recognized as a GeometricEntity: %s" % type(other)) + if isinstance(other, Point): + s, p = Point._normalize_dimension(self, Point(other)) + return sqrt(Add(*((a - b)**2 for a, b in zip(s, p)))) + distance = getattr(other, 'distance', None) + if distance is None: + raise TypeError("distance between Point and %s is not defined" % type(other)) + return distance(self) + + def dot(self, p): + """Return dot product of self with another Point.""" + if not is_sequence(p): + p = Point(p) # raise the error via Point + return Add(*(a*b for a, b in zip(self, p))) + + def equals(self, other): + """Returns whether the coordinates of self and other agree.""" + # a point is equal to another point if all its components are equal + if not isinstance(other, Point) or len(self) != len(other): + return False + return all(a.equals(b) for a, b in zip(self, other)) + + def _eval_evalf(self, prec=15, **options): + """Evaluate the coordinates of the point. + + This method will, where possible, create and return a new Point + where the coordinates are evaluated as floating point numbers to + the precision indicated (default=15). + + Parameters + ========== + + prec : int + + Returns + ======= + + point : Point + + Examples + ======== + + >>> from sympy import Point, Rational + >>> p1 = Point(Rational(1, 2), Rational(3, 2)) + >>> p1 + Point2D(1/2, 3/2) + >>> p1.evalf() + Point2D(0.5, 1.5) + + """ + dps = prec_to_dps(prec) + coords = [x.evalf(n=dps, **options) for x in self.args] + return Point(*coords, evaluate=False) + + def intersection(self, other): + """The intersection between this point and another GeometryEntity. + + Parameters + ========== + + other : GeometryEntity or sequence of coordinates + + Returns + ======= + + intersection : list of Points + + Notes + ===== + + The return value will either be an empty list if there is no + intersection, otherwise it will contain this point. + + Examples + ======== + + >>> from sympy import Point + >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(0, 0) + >>> p1.intersection(p2) + [] + >>> p1.intersection(p3) + [Point2D(0, 0)] + + """ + if not isinstance(other, GeometryEntity): + other = Point(other) + if isinstance(other, Point): + if self == other: + return [self] + p1, p2 = Point._normalize_dimension(self, other) + if p1 == self and p1 == p2: + return [self] + return [] + return other.intersection(self) + + def is_collinear(self, *args): + """Returns `True` if there exists a line + that contains `self` and `points`. Returns `False` otherwise. + A trivially True value is returned if no points are given. + + Parameters + ========== + + args : sequence of Points + + Returns + ======= + + is_collinear : boolean + + See Also + ======== + + sympy.geometry.line.Line + + Examples + ======== + + >>> from sympy import Point + >>> from sympy.abc import x + >>> p1, p2 = Point(0, 0), Point(1, 1) + >>> p3, p4, p5 = Point(2, 2), Point(x, x), Point(1, 2) + >>> Point.is_collinear(p1, p2, p3, p4) + True + >>> Point.is_collinear(p1, p2, p3, p5) + False + + """ + points = (self,) + args + points = Point._normalize_dimension(*[Point(i) for i in points]) + points = list(uniq(points)) + return Point.affine_rank(*points) <= 1 + + def is_concyclic(self, *args): + """Do `self` and the given sequence of points lie in a circle? + + Returns True if the set of points are concyclic and + False otherwise. A trivial value of True is returned + if there are fewer than 2 other points. + + Parameters + ========== + + args : sequence of Points + + Returns + ======= + + is_concyclic : boolean + + + Examples + ======== + + >>> from sympy import Point + + Define 4 points that are on the unit circle: + + >>> p1, p2, p3, p4 = Point(1, 0), (0, 1), (-1, 0), (0, -1) + + >>> p1.is_concyclic() == p1.is_concyclic(p2, p3, p4) == True + True + + Define a point not on that circle: + + >>> p = Point(1, 1) + + >>> p.is_concyclic(p1, p2, p3) + False + + """ + points = (self,) + args + points = Point._normalize_dimension(*[Point(i) for i in points]) + points = list(uniq(points)) + if not Point.affine_rank(*points) <= 2: + return False + origin = points[0] + points = [p - origin for p in points] + # points are concyclic if they are coplanar and + # there is a point c so that ||p_i-c|| == ||p_j-c|| for all + # i and j. Rearranging this equation gives us the following + # condition: the matrix `mat` must not a pivot in the last + # column. + mat = Matrix([list(i) + [i.dot(i)] for i in points]) + rref, pivots = mat.rref() + if len(origin) not in pivots: + return True + return False + + @property + def is_nonzero(self): + """True if any coordinate is nonzero, False if every coordinate is zero, + and None if it cannot be determined.""" + is_zero = self.is_zero + if is_zero is None: + return None + return not is_zero + + def is_scalar_multiple(self, p): + """Returns whether each coordinate of `self` is a scalar + multiple of the corresponding coordinate in point p. + """ + s, o = Point._normalize_dimension(self, Point(p)) + # 2d points happen a lot, so optimize this function call + if s.ambient_dimension == 2: + (x1, y1), (x2, y2) = s.args, o.args + rv = (x1*y2 - x2*y1).equals(0) + if rv is None: + raise Undecidable(filldedent( + '''Cannot determine if %s is a scalar multiple of + %s''' % (s, o))) + + # if the vectors p1 and p2 are linearly dependent, then they must + # be scalar multiples of each other + m = Matrix([s.args, o.args]) + return m.rank() < 2 + + @property + def is_zero(self): + """True if every coordinate is zero, False if any coordinate is not zero, + and None if it cannot be determined.""" + nonzero = [x.is_nonzero for x in self.args] + if any(nonzero): + return False + if any(x is None for x in nonzero): + return None + return True + + @property + def length(self): + """ + Treating a Point as a Line, this returns 0 for the length of a Point. + + Examples + ======== + + >>> from sympy import Point + >>> p = Point(0, 1) + >>> p.length + 0 + """ + return S.Zero + + def midpoint(self, p): + """The midpoint between self and point p. + + Parameters + ========== + + p : Point + + Returns + ======= + + midpoint : Point + + See Also + ======== + + sympy.geometry.line.Segment.midpoint + + Examples + ======== + + >>> from sympy import Point + >>> p1, p2 = Point(1, 1), Point(13, 5) + >>> p1.midpoint(p2) + Point2D(7, 3) + + """ + s, p = Point._normalize_dimension(self, Point(p)) + return Point([simplify((a + b)*S.Half) for a, b in zip(s, p)]) + + @property + def origin(self): + """A point of all zeros of the same ambient dimension + as the current point""" + return Point([0]*len(self), evaluate=False) + + @property + def orthogonal_direction(self): + """Returns a non-zero point that is orthogonal to the + line containing `self` and the origin. + + Examples + ======== + + >>> from sympy import Line, Point + >>> a = Point(1, 2, 3) + >>> a.orthogonal_direction + Point3D(-2, 1, 0) + >>> b = _ + >>> Line(b, b.origin).is_perpendicular(Line(a, a.origin)) + True + """ + dim = self.ambient_dimension + # if a coordinate is zero, we can put a 1 there and zeros elsewhere + if self[0].is_zero: + return Point([1] + (dim - 1)*[0]) + if self[1].is_zero: + return Point([0,1] + (dim - 2)*[0]) + # if the first two coordinates aren't zero, we can create a non-zero + # orthogonal vector by swapping them, negating one, and padding with zeros + return Point([-self[1], self[0]] + (dim - 2)*[0]) + + @staticmethod + def project(a, b): + """Project the point `a` onto the line between the origin + and point `b` along the normal direction. + + Parameters + ========== + + a : Point + b : Point + + Returns + ======= + + p : Point + + See Also + ======== + + sympy.geometry.line.LinearEntity.projection + + Examples + ======== + + >>> from sympy import Line, Point + >>> a = Point(1, 2) + >>> b = Point(2, 5) + >>> z = a.origin + >>> p = Point.project(a, b) + >>> Line(p, a).is_perpendicular(Line(p, b)) + True + >>> Point.is_collinear(z, p, b) + True + """ + a, b = Point._normalize_dimension(Point(a), Point(b)) + if b.is_zero: + raise ValueError("Cannot project to the zero vector.") + return b*(a.dot(b) / b.dot(b)) + + def taxicab_distance(self, p): + """The Taxicab Distance from self to point p. + + Returns the sum of the horizontal and vertical distances to point p. + + Parameters + ========== + + p : Point + + Returns + ======= + + taxicab_distance : The sum of the horizontal + and vertical distances to point p. + + See Also + ======== + + sympy.geometry.point.Point.distance + + Examples + ======== + + >>> from sympy import Point + >>> p1, p2 = Point(1, 1), Point(4, 5) + >>> p1.taxicab_distance(p2) + 7 + + """ + s, p = Point._normalize_dimension(self, Point(p)) + return Add(*(abs(a - b) for a, b in zip(s, p))) + + def canberra_distance(self, p): + """The Canberra Distance from self to point p. + + Returns the weighted sum of horizontal and vertical distances to + point p. + + Parameters + ========== + + p : Point + + Returns + ======= + + canberra_distance : The weighted sum of horizontal and vertical + distances to point p. The weight used is the sum of absolute values + of the coordinates. + + Examples + ======== + + >>> from sympy import Point + >>> p1, p2 = Point(1, 1), Point(3, 3) + >>> p1.canberra_distance(p2) + 1 + >>> p1, p2 = Point(0, 0), Point(3, 3) + >>> p1.canberra_distance(p2) + 2 + + Raises + ====== + + ValueError when both vectors are zero. + + See Also + ======== + + sympy.geometry.point.Point.distance + + """ + + s, p = Point._normalize_dimension(self, Point(p)) + if self.is_zero and p.is_zero: + raise ValueError("Cannot project to the zero vector.") + return Add(*((abs(a - b)/(abs(a) + abs(b))) for a, b in zip(s, p))) + + @property + def unit(self): + """Return the Point that is in the same direction as `self` + and a distance of 1 from the origin""" + return self / abs(self) + + +class Point2D(Point): + """A point in a 2-dimensional Euclidean space. + + Parameters + ========== + + coords + A sequence of 2 coordinate values. + + Attributes + ========== + + x + y + length + + Raises + ====== + + TypeError + When trying to add or subtract points with different dimensions. + When trying to create a point with more than two dimensions. + When `intersection` is called with object other than a Point. + + See Also + ======== + + sympy.geometry.line.Segment : Connects two Points + + Examples + ======== + + >>> from sympy import Point2D + >>> from sympy.abc import x + >>> Point2D(1, 2) + Point2D(1, 2) + >>> Point2D([1, 2]) + Point2D(1, 2) + >>> Point2D(0, x) + Point2D(0, x) + + Floats are automatically converted to Rational unless the + evaluate flag is False: + + >>> Point2D(0.5, 0.25) + Point2D(1/2, 1/4) + >>> Point2D(0.5, 0.25, evaluate=False) + Point2D(0.5, 0.25) + + """ + + _ambient_dimension = 2 + + def __new__(cls, *args, _nocheck=False, **kwargs): + if not _nocheck: + kwargs['dim'] = 2 + args = Point(*args, **kwargs) + return GeometryEntity.__new__(cls, *args) + + def __contains__(self, item): + return item == self + + @property + def bounds(self): + """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding + rectangle for the geometric figure. + + """ + + return (self.x, self.y, self.x, self.y) + + def rotate(self, angle, pt=None): + """Rotate ``angle`` radians counterclockwise about Point ``pt``. + + See Also + ======== + + translate, scale + + Examples + ======== + + >>> from sympy import Point2D, pi + >>> t = Point2D(1, 0) + >>> t.rotate(pi/2) + Point2D(0, 1) + >>> t.rotate(pi/2, (2, 0)) + Point2D(2, -1) + + """ + c = cos(angle) + s = sin(angle) + + rv = self + if pt is not None: + pt = Point(pt, dim=2) + rv -= pt + x, y = rv.args + rv = Point(c*x - s*y, s*x + c*y) + if pt is not None: + rv += pt + return rv + + def scale(self, x=1, y=1, pt=None): + """Scale the coordinates of the Point by multiplying by + ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) -- + and then adding ``pt`` back again (i.e. ``pt`` is the point of + reference for the scaling). + + See Also + ======== + + rotate, translate + + Examples + ======== + + >>> from sympy import Point2D + >>> t = Point2D(1, 1) + >>> t.scale(2) + Point2D(2, 1) + >>> t.scale(2, 2) + Point2D(2, 2) + + """ + if pt: + pt = Point(pt, dim=2) + return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) + return Point(self.x*x, self.y*y) + + def transform(self, matrix): + """Return the point after applying the transformation described + by the 3x3 Matrix, ``matrix``. + + See Also + ======== + sympy.geometry.point.Point2D.rotate + sympy.geometry.point.Point2D.scale + sympy.geometry.point.Point2D.translate + """ + if not (matrix.is_Matrix and matrix.shape == (3, 3)): + raise ValueError("matrix must be a 3x3 matrix") + x, y = self.args + return Point(*(Matrix(1, 3, [x, y, 1])*matrix).tolist()[0][:2]) + + def translate(self, x=0, y=0): + """Shift the Point by adding x and y to the coordinates of the Point. + + See Also + ======== + + sympy.geometry.point.Point2D.rotate, scale + + Examples + ======== + + >>> from sympy import Point2D + >>> t = Point2D(0, 1) + >>> t.translate(2) + Point2D(2, 1) + >>> t.translate(2, 2) + Point2D(2, 3) + >>> t + Point2D(2, 2) + Point2D(2, 3) + + """ + return Point(self.x + x, self.y + y) + + @property + def coordinates(self): + """ + Returns the two coordinates of the Point. + + Examples + ======== + + >>> from sympy import Point2D + >>> p = Point2D(0, 1) + >>> p.coordinates + (0, 1) + """ + return self.args + + @property + def x(self): + """ + Returns the X coordinate of the Point. + + Examples + ======== + + >>> from sympy import Point2D + >>> p = Point2D(0, 1) + >>> p.x + 0 + """ + return self.args[0] + + @property + def y(self): + """ + Returns the Y coordinate of the Point. + + Examples + ======== + + >>> from sympy import Point2D + >>> p = Point2D(0, 1) + >>> p.y + 1 + """ + return self.args[1] + +class Point3D(Point): + """A point in a 3-dimensional Euclidean space. + + Parameters + ========== + + coords + A sequence of 3 coordinate values. + + Attributes + ========== + + x + y + z + length + + Raises + ====== + + TypeError + When trying to add or subtract points with different dimensions. + When `intersection` is called with object other than a Point. + + Examples + ======== + + >>> from sympy import Point3D + >>> from sympy.abc import x + >>> Point3D(1, 2, 3) + Point3D(1, 2, 3) + >>> Point3D([1, 2, 3]) + Point3D(1, 2, 3) + >>> Point3D(0, x, 3) + Point3D(0, x, 3) + + Floats are automatically converted to Rational unless the + evaluate flag is False: + + >>> Point3D(0.5, 0.25, 2) + Point3D(1/2, 1/4, 2) + >>> Point3D(0.5, 0.25, 3, evaluate=False) + Point3D(0.5, 0.25, 3) + + """ + + _ambient_dimension = 3 + + def __new__(cls, *args, _nocheck=False, **kwargs): + if not _nocheck: + kwargs['dim'] = 3 + args = Point(*args, **kwargs) + return GeometryEntity.__new__(cls, *args) + + def __contains__(self, item): + return item == self + + @staticmethod + def are_collinear(*points): + """Is a sequence of points collinear? + + Test whether or not a set of points are collinear. Returns True if + the set of points are collinear, or False otherwise. + + Parameters + ========== + + points : sequence of Point + + Returns + ======= + + are_collinear : boolean + + See Also + ======== + + sympy.geometry.line.Line3D + + Examples + ======== + + >>> from sympy import Point3D + >>> from sympy.abc import x + >>> p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1) + >>> p3, p4, p5 = Point3D(2, 2, 2), Point3D(x, x, x), Point3D(1, 2, 6) + >>> Point3D.are_collinear(p1, p2, p3, p4) + True + >>> Point3D.are_collinear(p1, p2, p3, p5) + False + """ + return Point.is_collinear(*points) + + def direction_cosine(self, point): + """ + Gives the direction cosine between 2 points + + Parameters + ========== + + p : Point3D + + Returns + ======= + + list + + Examples + ======== + + >>> from sympy import Point3D + >>> p1 = Point3D(1, 2, 3) + >>> p1.direction_cosine(Point3D(2, 3, 5)) + [sqrt(6)/6, sqrt(6)/6, sqrt(6)/3] + """ + a = self.direction_ratio(point) + b = sqrt(Add(*(i**2 for i in a))) + return [(point.x - self.x) / b,(point.y - self.y) / b, + (point.z - self.z) / b] + + def direction_ratio(self, point): + """ + Gives the direction ratio between 2 points + + Parameters + ========== + + p : Point3D + + Returns + ======= + + list + + Examples + ======== + + >>> from sympy import Point3D + >>> p1 = Point3D(1, 2, 3) + >>> p1.direction_ratio(Point3D(2, 3, 5)) + [1, 1, 2] + """ + return [(point.x - self.x),(point.y - self.y),(point.z - self.z)] + + def intersection(self, other): + """The intersection between this point and another GeometryEntity. + + Parameters + ========== + + other : GeometryEntity or sequence of coordinates + + Returns + ======= + + intersection : list of Points + + Notes + ===== + + The return value will either be an empty list if there is no + intersection, otherwise it will contain this point. + + Examples + ======== + + >>> from sympy import Point3D + >>> p1, p2, p3 = Point3D(0, 0, 0), Point3D(1, 1, 1), Point3D(0, 0, 0) + >>> p1.intersection(p2) + [] + >>> p1.intersection(p3) + [Point3D(0, 0, 0)] + + """ + if not isinstance(other, GeometryEntity): + other = Point(other, dim=3) + if isinstance(other, Point3D): + if self == other: + return [self] + return [] + return other.intersection(self) + + def scale(self, x=1, y=1, z=1, pt=None): + """Scale the coordinates of the Point by multiplying by + ``x`` and ``y`` after subtracting ``pt`` -- default is (0, 0) -- + and then adding ``pt`` back again (i.e. ``pt`` is the point of + reference for the scaling). + + See Also + ======== + + translate + + Examples + ======== + + >>> from sympy import Point3D + >>> t = Point3D(1, 1, 1) + >>> t.scale(2) + Point3D(2, 1, 1) + >>> t.scale(2, 2) + Point3D(2, 2, 1) + + """ + if pt: + pt = Point3D(pt) + return self.translate(*(-pt).args).scale(x, y, z).translate(*pt.args) + return Point3D(self.x*x, self.y*y, self.z*z) + + def transform(self, matrix): + """Return the point after applying the transformation described + by the 4x4 Matrix, ``matrix``. + + See Also + ======== + sympy.geometry.point.Point3D.scale + sympy.geometry.point.Point3D.translate + """ + if not (matrix.is_Matrix and matrix.shape == (4, 4)): + raise ValueError("matrix must be a 4x4 matrix") + x, y, z = self.args + m = Transpose(matrix) + return Point3D(*(Matrix(1, 4, [x, y, z, 1])*m).tolist()[0][:3]) + + def translate(self, x=0, y=0, z=0): + """Shift the Point by adding x and y to the coordinates of the Point. + + See Also + ======== + + scale + + Examples + ======== + + >>> from sympy import Point3D + >>> t = Point3D(0, 1, 1) + >>> t.translate(2) + Point3D(2, 1, 1) + >>> t.translate(2, 2) + Point3D(2, 3, 1) + >>> t + Point3D(2, 2, 2) + Point3D(2, 3, 3) + + """ + return Point3D(self.x + x, self.y + y, self.z + z) + + @property + def coordinates(self): + """ + Returns the three coordinates of the Point. + + Examples + ======== + + >>> from sympy import Point3D + >>> p = Point3D(0, 1, 2) + >>> p.coordinates + (0, 1, 2) + """ + return self.args + + @property + def x(self): + """ + Returns the X coordinate of the Point. + + Examples + ======== + + >>> from sympy import Point3D + >>> p = Point3D(0, 1, 3) + >>> p.x + 0 + """ + return self.args[0] + + @property + def y(self): + """ + Returns the Y coordinate of the Point. + + Examples + ======== + + >>> from sympy import Point3D + >>> p = Point3D(0, 1, 2) + >>> p.y + 1 + """ + return self.args[1] + + @property + def z(self): + """ + Returns the Z coordinate of the Point. + + Examples + ======== + + >>> from sympy import Point3D + >>> p = Point3D(0, 1, 1) + >>> p.z + 1 + """ + return self.args[2] diff --git a/phivenv/Lib/site-packages/sympy/geometry/polygon.py b/phivenv/Lib/site-packages/sympy/geometry/polygon.py new file mode 100644 index 0000000000000000000000000000000000000000..63031183438e2d228f881fd82e1b0ecca04ec534 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/polygon.py @@ -0,0 +1,2891 @@ +from sympy.core import Expr, S, oo, pi, sympify +from sympy.core.evalf import N +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import _symbol, Dummy, Symbol +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import cos, sin, tan +from .ellipse import Circle +from .entity import GeometryEntity, GeometrySet +from .exceptions import GeometryError +from .line import Line, Segment, Ray +from .point import Point +from sympy.logic import And +from sympy.matrices import Matrix +from sympy.simplify.simplify import simplify +from sympy.solvers.solvers import solve +from sympy.utilities.iterables import has_dups, has_variety, uniq, rotate_left, least_rotation +from sympy.utilities.misc import as_int, func_name + +from mpmath.libmp.libmpf import prec_to_dps + +import warnings + + +x, y, T = [Dummy('polygon_dummy', real=True) for i in range(3)] + + +class Polygon(GeometrySet): + """A two-dimensional polygon. + + A simple polygon in space. Can be constructed from a sequence of points + or from a center, radius, number of sides and rotation angle. + + Parameters + ========== + + vertices + A sequence of points. + + n : int, optional + If $> 0$, an n-sided RegularPolygon is created. + Default value is $0$. + + Attributes + ========== + + area + angles + perimeter + vertices + centroid + sides + + Raises + ====== + + GeometryError + If all parameters are not Points. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Segment, Triangle + + Notes + ===== + + Polygons are treated as closed paths rather than 2D areas so + some calculations can be be negative or positive (e.g., area) + based on the orientation of the points. + + Any consecutive identical points are reduced to a single point + and any points collinear and between two points will be removed + unless they are needed to define an explicit intersection (see examples). + + A Triangle, Segment or Point will be returned when there are 3 or + fewer points provided. + + Examples + ======== + + >>> from sympy import Polygon, pi + >>> p1, p2, p3, p4, p5 = [(0, 0), (1, 0), (5, 1), (0, 1), (3, 0)] + >>> Polygon(p1, p2, p3, p4) + Polygon(Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)) + >>> Polygon(p1, p2) + Segment2D(Point2D(0, 0), Point2D(1, 0)) + >>> Polygon(p1, p2, p5) + Segment2D(Point2D(0, 0), Point2D(3, 0)) + + The area of a polygon is calculated as positive when vertices are + traversed in a ccw direction. When the sides of a polygon cross the + area will have positive and negative contributions. The following + defines a Z shape where the bottom right connects back to the top + left. + + >>> Polygon((0, 2), (2, 2), (0, 0), (2, 0)).area + 0 + + When the keyword `n` is used to define the number of sides of the + Polygon then a RegularPolygon is created and the other arguments are + interpreted as center, radius and rotation. The unrotated RegularPolygon + will always have a vertex at Point(r, 0) where `r` is the radius of the + circle that circumscribes the RegularPolygon. Its method `spin` can be + used to increment that angle. + + >>> p = Polygon((0,0), 1, n=3) + >>> p + RegularPolygon(Point2D(0, 0), 1, 3, 0) + >>> p.vertices[0] + Point2D(1, 0) + >>> p.args[0] + Point2D(0, 0) + >>> p.spin(pi/2) + >>> p.vertices[0] + Point2D(0, 1) + + """ + + __slots__ = () + + def __new__(cls, *args, n = 0, **kwargs): + if n: + args = list(args) + # return a virtual polygon with n sides + if len(args) == 2: # center, radius + args.append(n) + elif len(args) == 3: # center, radius, rotation + args.insert(2, n) + return RegularPolygon(*args, **kwargs) + + vertices = [Point(a, dim=2, **kwargs) for a in args] + + # remove consecutive duplicates + nodup = [] + for p in vertices: + if nodup and p == nodup[-1]: + continue + nodup.append(p) + if len(nodup) > 1 and nodup[-1] == nodup[0]: + nodup.pop() # last point was same as first + + # remove collinear points + i = -3 + while i < len(nodup) - 3 and len(nodup) > 2: + a, b, c = nodup[i], nodup[i + 1], nodup[i + 2] + if Point.is_collinear(a, b, c): + nodup.pop(i + 1) + if a == c: + nodup.pop(i) + else: + i += 1 + + vertices = list(nodup) + + if len(vertices) > 3: + return GeometryEntity.__new__(cls, *vertices, **kwargs) + elif len(vertices) == 3: + return Triangle(*vertices, **kwargs) + elif len(vertices) == 2: + return Segment(*vertices, **kwargs) + else: + return Point(*vertices, **kwargs) + + @property + def area(self): + """ + The area of the polygon. + + Notes + ===== + + The area calculation can be positive or negative based on the + orientation of the points. If any side of the polygon crosses + any other side, there will be areas having opposite signs. + + See Also + ======== + + sympy.geometry.ellipse.Ellipse.area + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.area + 3 + + In the Z shaped polygon (with the lower right connecting back + to the upper left) the areas cancel out: + + >>> Z = Polygon((0, 1), (1, 1), (0, 0), (1, 0)) + >>> Z.area + 0 + + In the M shaped polygon, areas do not cancel because no side + crosses any other (though there is a point of contact). + + >>> M = Polygon((0, 0), (0, 1), (2, 0), (3, 1), (3, 0)) + >>> M.area + -3/2 + + """ + area = 0 + args = self.args + for i in range(len(args)): + x1, y1 = args[i - 1].args + x2, y2 = args[i].args + area += x1*y2 - x2*y1 + return simplify(area) / 2 + + @staticmethod + def _is_clockwise(a, b, c): + """Return True/False for cw/ccw orientation. + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> a, b, c = [Point(i) for i in [(0, 0), (1, 1), (1, 0)]] + >>> Polygon._is_clockwise(a, b, c) + True + >>> Polygon._is_clockwise(a, c, b) + False + """ + ba = b - a + ca = c - a + t_area = simplify(ba.x*ca.y - ca.x*ba.y) + res = t_area.is_nonpositive + if res is None: + raise ValueError("Can't determine orientation") + return res + + @property + def angles(self): + """The internal angle at each vertex. + + Returns + ======= + + angles : dict + A dictionary where each key is a vertex and each value is the + internal angle at that vertex. The vertices are represented as + Points. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.LinearEntity.angle_between + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.angles[p1] + pi/2 + >>> poly.angles[p2] + acos(-4*sqrt(17)/17) + + """ + + args = self.vertices + n = len(args) + ret = {} + for i in range(n): + a, b, c = args[i - 2], args[i - 1], args[i] + reflex_ang = Ray(b, a).angle_between(Ray(b, c)) + if self._is_clockwise(a, b, c): + ret[b] = 2*S.Pi - reflex_ang + else: + ret[b] = reflex_ang + + # internal sum should be pi*(n - 2), not pi*(n+2) + # so if ratio is (n+2)/(n-2) > 1 it is wrong + wrong = ((sum(ret.values())/S.Pi-1)/(n - 2) - 1).is_positive + if wrong: + two_pi = 2*S.Pi + for b in ret: + ret[b] = two_pi - ret[b] + elif wrong is None: + raise ValueError("could not determine Polygon orientation.") + return ret + + @property + def ambient_dimension(self): + return self.vertices[0].ambient_dimension + + @property + def perimeter(self): + """The perimeter of the polygon. + + Returns + ======= + + perimeter : number or Basic instance + + See Also + ======== + + sympy.geometry.line.Segment.length + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.perimeter + sqrt(17) + 7 + """ + p = 0 + args = self.vertices + for i in range(len(args)): + p += args[i - 1].distance(args[i]) + return simplify(p) + + @property + def vertices(self): + """The vertices of the polygon. + + Returns + ======= + + vertices : list of Points + + Notes + ===== + + When iterating over the vertices, it is more efficient to index self + rather than to request the vertices and index them. Only use the + vertices when you want to process all of them at once. This is even + more important with RegularPolygons that calculate each vertex. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.vertices + [Point2D(0, 0), Point2D(1, 0), Point2D(5, 1), Point2D(0, 1)] + >>> poly.vertices[0] + Point2D(0, 0) + + """ + return list(self.args) + + @property + def centroid(self): + """The centroid of the polygon. + + Returns + ======= + + centroid : Point + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.util.centroid + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.centroid + Point2D(31/18, 11/18) + + """ + A = 1/(6*self.area) + cx, cy = 0, 0 + args = self.args + for i in range(len(args)): + x1, y1 = args[i - 1].args + x2, y2 = args[i].args + v = x1*y2 - x2*y1 + cx += v*(x1 + x2) + cy += v*(y1 + y2) + return Point(simplify(A*cx), simplify(A*cy)) + + + def second_moment_of_area(self, point=None): + """Returns the second moment and product moment of area of a two dimensional polygon. + + Parameters + ========== + + point : Point, two-tuple of sympifyable objects, or None(default=None) + point is the point about which second moment of area is to be found. + If "point=None" it will be calculated about the axis passing through the + centroid of the polygon. + + Returns + ======= + + I_xx, I_yy, I_xy : number or SymPy expression + I_xx, I_yy are second moment of area of a two dimensional polygon. + I_xy is product moment of area of a two dimensional polygon. + + Examples + ======== + + >>> from sympy import Polygon, symbols + >>> a, b = symbols('a, b') + >>> p1, p2, p3, p4, p5 = [(0, 0), (a, 0), (a, b), (0, b), (a/3, b/3)] + >>> rectangle = Polygon(p1, p2, p3, p4) + >>> rectangle.second_moment_of_area() + (a*b**3/12, a**3*b/12, 0) + >>> rectangle.second_moment_of_area(p5) + (a*b**3/9, a**3*b/9, a**2*b**2/36) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Second_moment_of_area + + """ + + I_xx, I_yy, I_xy = 0, 0, 0 + args = self.vertices + for i in range(len(args)): + x1, y1 = args[i-1].args + x2, y2 = args[i].args + v = x1*y2 - x2*y1 + I_xx += (y1**2 + y1*y2 + y2**2)*v + I_yy += (x1**2 + x1*x2 + x2**2)*v + I_xy += (x1*y2 + 2*x1*y1 + 2*x2*y2 + x2*y1)*v + A = self.area + c_x = self.centroid[0] + c_y = self.centroid[1] + # parallel axis theorem + I_xx_c = (I_xx/12) - (A*(c_y**2)) + I_yy_c = (I_yy/12) - (A*(c_x**2)) + I_xy_c = (I_xy/24) - (A*(c_x*c_y)) + if point is None: + return I_xx_c, I_yy_c, I_xy_c + + I_xx = (I_xx_c + A*((point[1]-c_y)**2)) + I_yy = (I_yy_c + A*((point[0]-c_x)**2)) + I_xy = (I_xy_c + A*((point[0]-c_x)*(point[1]-c_y))) + + return I_xx, I_yy, I_xy + + + def first_moment_of_area(self, point=None): + """ + Returns the first moment of area of a two-dimensional polygon with + respect to a certain point of interest. + + First moment of area is a measure of the distribution of the area + of a polygon in relation to an axis. The first moment of area of + the entire polygon about its own centroid is always zero. Therefore, + here it is calculated for an area, above or below a certain point + of interest, that makes up a smaller portion of the polygon. This + area is bounded by the point of interest and the extreme end + (top or bottom) of the polygon. The first moment for this area is + is then determined about the centroidal axis of the initial polygon. + + References + ========== + + .. [1] https://skyciv.com/docs/tutorials/section-tutorials/calculating-the-statical-or-first-moment-of-area-of-beam-sections/?cc=BMD + .. [2] https://mechanicalc.com/reference/cross-sections + + Parameters + ========== + + point: Point, two-tuple of sympifyable objects, or None (default=None) + point is the point above or below which the area of interest lies + If ``point=None`` then the centroid acts as the point of interest. + + Returns + ======= + + Q_x, Q_y: number or SymPy expressions + Q_x is the first moment of area about the x-axis + Q_y is the first moment of area about the y-axis + A negative sign indicates that the section modulus is + determined for a section below (or left of) the centroidal axis + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> a, b = 50, 10 + >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)] + >>> p = Polygon(p1, p2, p3, p4) + >>> p.first_moment_of_area() + (625, 3125) + >>> p.first_moment_of_area(point=Point(30, 7)) + (525, 3000) + """ + if point: + xc, yc = self.centroid + else: + point = self.centroid + xc, yc = point + + h_line = Line(point, slope=0) + v_line = Line(point, slope=S.Infinity) + + h_poly = self.cut_section(h_line) + v_poly = self.cut_section(v_line) + + poly_1 = h_poly[0] if h_poly[0].area <= h_poly[1].area else h_poly[1] + poly_2 = v_poly[0] if v_poly[0].area <= v_poly[1].area else v_poly[1] + + Q_x = (poly_1.centroid.y - yc)*poly_1.area + Q_y = (poly_2.centroid.x - xc)*poly_2.area + + return Q_x, Q_y + + + def polar_second_moment_of_area(self): + """Returns the polar modulus of a two-dimensional polygon + + It is a constituent of the second moment of area, linked through + the perpendicular axis theorem. While the planar second moment of + area describes an object's resistance to deflection (bending) when + subjected to a force applied to a plane parallel to the central + axis, the polar second moment of area describes an object's + resistance to deflection when subjected to a moment applied in a + plane perpendicular to the object's central axis (i.e. parallel to + the cross-section) + + Examples + ======== + + >>> from sympy import Polygon, symbols + >>> a, b = symbols('a, b') + >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b)) + >>> rectangle.polar_second_moment_of_area() + a**3*b/12 + a*b**3/12 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Polar_moment_of_inertia + + """ + second_moment = self.second_moment_of_area() + return second_moment[0] + second_moment[1] + + + def section_modulus(self, point=None): + """Returns a tuple with the section modulus of a two-dimensional + polygon. + + Section modulus is a geometric property of a polygon defined as the + ratio of second moment of area to the distance of the extreme end of + the polygon from the centroidal axis. + + Parameters + ========== + + point : Point, two-tuple of sympifyable objects, or None(default=None) + point is the point at which section modulus is to be found. + If "point=None" it will be calculated for the point farthest from the + centroidal axis of the polygon. + + Returns + ======= + + S_x, S_y: numbers or SymPy expressions + S_x is the section modulus with respect to the x-axis + S_y is the section modulus with respect to the y-axis + A negative sign indicates that the section modulus is + determined for a point below the centroidal axis + + Examples + ======== + + >>> from sympy import symbols, Polygon, Point + >>> a, b = symbols('a, b', positive=True) + >>> rectangle = Polygon((0, 0), (a, 0), (a, b), (0, b)) + >>> rectangle.section_modulus() + (a*b**2/6, a**2*b/6) + >>> rectangle.section_modulus(Point(a/4, b/4)) + (-a*b**2/3, -a**2*b/3) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Section_modulus + + """ + x_c, y_c = self.centroid + if point is None: + # taking x and y as maximum distances from centroid + x_min, y_min, x_max, y_max = self.bounds + y = max(y_c - y_min, y_max - y_c) + x = max(x_c - x_min, x_max - x_c) + else: + # taking x and y as distances of the given point from the centroid + y = point.y - y_c + x = point.x - x_c + + second_moment= self.second_moment_of_area() + S_x = second_moment[0]/y + S_y = second_moment[1]/x + + return S_x, S_y + + + @property + def sides(self): + """The directed line segments that form the sides of the polygon. + + Returns + ======= + + sides : list of sides + Each side is a directed Segment. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Segment + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.sides + [Segment2D(Point2D(0, 0), Point2D(1, 0)), + Segment2D(Point2D(1, 0), Point2D(5, 1)), + Segment2D(Point2D(5, 1), Point2D(0, 1)), Segment2D(Point2D(0, 1), Point2D(0, 0))] + + """ + res = [] + args = self.vertices + for i in range(-len(args), 0): + res.append(Segment(args[i], args[i + 1])) + return res + + @property + def bounds(self): + """Return a tuple (xmin, ymin, xmax, ymax) representing the bounding + rectangle for the geometric figure. + + """ + + verts = self.vertices + xs = [p.x for p in verts] + ys = [p.y for p in verts] + return (min(xs), min(ys), max(xs), max(ys)) + + def is_convex(self): + """Is the polygon convex? + + A polygon is convex if all its interior angles are less than 180 + degrees and there are no intersections between sides. + + Returns + ======= + + is_convex : boolean + True if this polygon is convex, False otherwise. + + See Also + ======== + + sympy.geometry.util.convex_hull + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly = Polygon(p1, p2, p3, p4) + >>> poly.is_convex() + True + + """ + # Determine orientation of points + args = self.vertices + cw = self._is_clockwise(args[-2], args[-1], args[0]) + for i in range(1, len(args)): + if cw ^ self._is_clockwise(args[i - 2], args[i - 1], args[i]): + return False + # check for intersecting sides + sides = self.sides + for i, si in enumerate(sides): + pts = si.args + # exclude the sides connected to si + for j in range(1 if i == len(sides) - 1 else 0, i - 1): + sj = sides[j] + if sj.p1 not in pts and sj.p2 not in pts: + hit = si.intersection(sj) + if hit: + return False + return True + + def encloses_point(self, p): + """ + Return True if p is enclosed by (is inside of) self. + + Notes + ===== + + Being on the border of self is considered False. + + Parameters + ========== + + p : Point + + Returns + ======= + + encloses_point : True, False or None + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.encloses_point + + Examples + ======== + + >>> from sympy import Polygon, Point + >>> p = Polygon((0, 0), (4, 0), (4, 4)) + >>> p.encloses_point(Point(2, 1)) + True + >>> p.encloses_point(Point(2, 2)) + False + >>> p.encloses_point(Point(5, 5)) + False + + References + ========== + + .. [1] https://paulbourke.net/geometry/polygonmesh/#insidepoly + + """ + p = Point(p, dim=2) + if p in self.vertices or any(p in s for s in self.sides): + return False + + # move to p, checking that the result is numeric + lit = [] + for v in self.vertices: + lit.append(v - p) # the difference is simplified + if lit[-1].free_symbols: + return None + + poly = Polygon(*lit) + + # polygon closure is assumed in the following test but Polygon removes duplicate pts so + # the last point has to be added so all sides are computed. Using Polygon.sides is + # not good since Segments are unordered. + args = poly.args + indices = list(range(-len(args), 1)) + + if poly.is_convex(): + orientation = None + for i in indices: + a = args[i] + b = args[i + 1] + test = ((-a.y)*(b.x - a.x) - (-a.x)*(b.y - a.y)).is_negative + if orientation is None: + orientation = test + elif test is not orientation: + return False + return True + + hit_odd = False + p1x, p1y = args[0].args + for i in indices[1:]: + p2x, p2y = args[i].args + if 0 > min(p1y, p2y): + if 0 <= max(p1y, p2y): + if 0 <= max(p1x, p2x): + if p1y != p2y: + xinters = (-p1y)*(p2x - p1x)/(p2y - p1y) + p1x + if p1x == p2x or 0 <= xinters: + hit_odd = not hit_odd + p1x, p1y = p2x, p2y + return hit_odd + + def arbitrary_point(self, parameter='t'): + """A parameterized point on the polygon. + + The parameter, varying from 0 to 1, assigns points to the position on + the perimeter that is that fraction of the total perimeter. So the + point evaluated at t=1/2 would return the point from the first vertex + that is 1/2 way around the polygon. + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + arbitrary_point : Point + + Raises + ====== + + ValueError + When `parameter` already appears in the Polygon's definition. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Polygon, Symbol + >>> t = Symbol('t', real=True) + >>> tri = Polygon((0, 0), (1, 0), (1, 1)) + >>> p = tri.arbitrary_point('t') + >>> perimeter = tri.perimeter + >>> s1, s2 = [s.length for s in tri.sides[:2]] + >>> p.subs(t, (s1 + s2/2)/perimeter) + Point2D(1, 1/2) + + """ + t = _symbol(parameter, real=True) + if t.name in (f.name for f in self.free_symbols): + raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name) + sides = [] + perimeter = self.perimeter + perim_fraction_start = 0 + for s in self.sides: + side_perim_fraction = s.length/perimeter + perim_fraction_end = perim_fraction_start + side_perim_fraction + pt = s.arbitrary_point(parameter).subs( + t, (t - perim_fraction_start)/side_perim_fraction) + sides.append( + (pt, (And(perim_fraction_start <= t, t < perim_fraction_end)))) + perim_fraction_start = perim_fraction_end + return Piecewise(*sides) + + def parameter_value(self, other, t): + if not isinstance(other,GeometryEntity): + other = Point(other, dim=self.ambient_dimension) + if not isinstance(other,Point): + raise ValueError("other must be a point") + if other.free_symbols: + raise NotImplementedError('non-numeric coordinates') + unknown = False + p = self.arbitrary_point(T) + for pt, cond in p.args: + sol = solve(pt - other, T, dict=True) + if not sol: + continue + value = sol[0][T] + if simplify(cond.subs(T, value)) == True: + return {t: value} + unknown = True + if unknown: + raise ValueError("Given point may not be on %s" % func_name(self)) + raise ValueError("Given point is not on %s" % func_name(self)) + + def plot_interval(self, parameter='t'): + """The plot interval for the default geometric plot of the polygon. + + Parameters + ========== + + parameter : str, optional + Default value is 't'. + + Returns + ======= + + plot_interval : list (plot interval) + [parameter, lower_bound, upper_bound] + + Examples + ======== + + >>> from sympy import Polygon + >>> p = Polygon((0, 0), (1, 0), (1, 1)) + >>> p.plot_interval() + [t, 0, 1] + + """ + t = Symbol(parameter, real=True) + return [t, 0, 1] + + def intersection(self, o): + """The intersection of polygon and geometry entity. + + The intersection may be empty and can contain individual Points and + complete Line Segments. + + Parameters + ========== + + other: GeometryEntity + + Returns + ======= + + intersection : list + The list of Segments and Points + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Segment + + Examples + ======== + + >>> from sympy import Point, Polygon, Line + >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + >>> poly1 = Polygon(p1, p2, p3, p4) + >>> p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)]) + >>> poly2 = Polygon(p5, p6, p7) + >>> poly1.intersection(poly2) + [Point2D(1/3, 1), Point2D(2/3, 0), Point2D(9/5, 1/5), Point2D(7/3, 1)] + >>> poly1.intersection(Line(p1, p2)) + [Segment2D(Point2D(0, 0), Point2D(1, 0))] + >>> poly1.intersection(p1) + [Point2D(0, 0)] + """ + intersection_result = [] + k = o.sides if isinstance(o, Polygon) else [o] + for side in self.sides: + for side1 in k: + intersection_result.extend(side.intersection(side1)) + + intersection_result = list(uniq(intersection_result)) + points = [entity for entity in intersection_result if isinstance(entity, Point)] + segments = [entity for entity in intersection_result if isinstance(entity, Segment)] + + if points and segments: + points_in_segments = list(uniq([point for point in points for segment in segments if point in segment])) + if points_in_segments: + for i in points_in_segments: + points.remove(i) + return list(ordered(segments + points)) + else: + return list(ordered(intersection_result)) + + + def cut_section(self, line): + """ + Returns a tuple of two polygon segments that lie above and below + the intersecting line respectively. + + Parameters + ========== + + line: Line object of geometry module + line which cuts the Polygon. The part of the Polygon that lies + above and below this line is returned. + + Returns + ======= + + upper_polygon, lower_polygon: Polygon objects or None + upper_polygon is the polygon that lies above the given line. + lower_polygon is the polygon that lies below the given line. + upper_polygon and lower polygon are ``None`` when no polygon + exists above the line or below the line. + + Raises + ====== + + ValueError: When the line does not intersect the polygon + + Examples + ======== + + >>> from sympy import Polygon, Line + >>> a, b = 20, 10 + >>> p1, p2, p3, p4 = [(0, b), (0, 0), (a, 0), (a, b)] + >>> rectangle = Polygon(p1, p2, p3, p4) + >>> t = rectangle.cut_section(Line((0, 5), slope=0)) + >>> t + (Polygon(Point2D(0, 10), Point2D(0, 5), Point2D(20, 5), Point2D(20, 10)), + Polygon(Point2D(0, 5), Point2D(0, 0), Point2D(20, 0), Point2D(20, 5))) + >>> upper_segment, lower_segment = t + >>> upper_segment.area + 100 + >>> upper_segment.centroid + Point2D(10, 15/2) + >>> lower_segment.centroid + Point2D(10, 5/2) + + References + ========== + + .. [1] https://github.com/sympy/sympy/wiki/A-method-to-return-a-cut-section-of-any-polygon-geometry + + """ + intersection_points = self.intersection(line) + if not intersection_points: + raise ValueError("This line does not intersect the polygon") + + points = list(self.vertices) + points.append(points[0]) + + eq = line.equation(x, y) + + # considering equation of line to be `ax +by + c` + a = eq.coeff(x) + b = eq.coeff(y) + + upper_vertices = [] + lower_vertices = [] + # prev is true when previous point is above the line + prev = True + prev_point = None + for point in points: + # when coefficient of y is 0, right side of the line is + # considered + compare = eq.subs({x: point.x, y: point.y})/b if b \ + else eq.subs(x, point.x)/a + + # if point lies above line + if compare > 0: + if not prev: + # if previous point lies below the line, the intersection + # point of the polygon edge and the line has to be included + edge = Line(point, prev_point) + new_point = edge.intersection(line) + upper_vertices.append(new_point[0]) + lower_vertices.append(new_point[0]) + + upper_vertices.append(point) + prev = True + else: + if prev and prev_point: + edge = Line(point, prev_point) + new_point = edge.intersection(line) + upper_vertices.append(new_point[0]) + lower_vertices.append(new_point[0]) + lower_vertices.append(point) + prev = False + prev_point = point + + upper_polygon, lower_polygon = None, None + if upper_vertices and isinstance(Polygon(*upper_vertices), Polygon): + upper_polygon = Polygon(*upper_vertices) + if lower_vertices and isinstance(Polygon(*lower_vertices), Polygon): + lower_polygon = Polygon(*lower_vertices) + + return upper_polygon, lower_polygon + + + def distance(self, o): + """ + Returns the shortest distance between self and o. + + If o is a point, then self does not need to be convex. + If o is another polygon self and o must be convex. + + Examples + ======== + + >>> from sympy import Point, Polygon, RegularPolygon + >>> p1, p2 = map(Point, [(0, 0), (7, 5)]) + >>> poly = Polygon(*RegularPolygon(p1, 1, 3).vertices) + >>> poly.distance(p2) + sqrt(61) + """ + if isinstance(o, Point): + dist = oo + for side in self.sides: + current = side.distance(o) + if current == 0: + return S.Zero + elif current < dist: + dist = current + return dist + elif isinstance(o, Polygon) and self.is_convex() and o.is_convex(): + return self._do_poly_distance(o) + raise NotImplementedError() + + def _do_poly_distance(self, e2): + """ + Calculates the least distance between the exteriors of two + convex polygons e1 and e2. Does not check for the convexity + of the polygons as this is checked by Polygon.distance. + + Notes + ===== + + - Prints a warning if the two polygons possibly intersect as the return + value will not be valid in such a case. For a more through test of + intersection use intersection(). + + See Also + ======== + + sympy.geometry.point.Point.distance + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> square = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) + >>> triangle = Polygon(Point(1, 2), Point(2, 2), Point(2, 1)) + >>> square._do_poly_distance(triangle) + sqrt(2)/2 + + Description of method used + ========================== + + Method: + [1] https://web.archive.org/web/20150509035744/http://cgm.cs.mcgill.ca/~orm/mind2p.html + Uses rotating calipers: + [2] https://en.wikipedia.org/wiki/Rotating_calipers + and antipodal points: + [3] https://en.wikipedia.org/wiki/Antipodal_point + """ + e1 = self + + '''Tests for a possible intersection between the polygons and outputs a warning''' + e1_center = e1.centroid + e2_center = e2.centroid + e1_max_radius = S.Zero + e2_max_radius = S.Zero + for vertex in e1.vertices: + r = Point.distance(e1_center, vertex) + if e1_max_radius < r: + e1_max_radius = r + for vertex in e2.vertices: + r = Point.distance(e2_center, vertex) + if e2_max_radius < r: + e2_max_radius = r + center_dist = Point.distance(e1_center, e2_center) + if center_dist <= e1_max_radius + e2_max_radius: + warnings.warn("Polygons may intersect producing erroneous output", + stacklevel=3) + + ''' + Find the upper rightmost vertex of e1 and the lowest leftmost vertex of e2 + ''' + e1_ymax = Point(0, -oo) + e2_ymin = Point(0, oo) + + for vertex in e1.vertices: + if vertex.y > e1_ymax.y or (vertex.y == e1_ymax.y and vertex.x > e1_ymax.x): + e1_ymax = vertex + for vertex in e2.vertices: + if vertex.y < e2_ymin.y or (vertex.y == e2_ymin.y and vertex.x < e2_ymin.x): + e2_ymin = vertex + min_dist = Point.distance(e1_ymax, e2_ymin) + + ''' + Produce a dictionary with vertices of e1 as the keys and, for each vertex, the points + to which the vertex is connected as its value. The same is then done for e2. + ''' + e1_connections = {} + e2_connections = {} + + for side in e1.sides: + if side.p1 in e1_connections: + e1_connections[side.p1].append(side.p2) + else: + e1_connections[side.p1] = [side.p2] + + if side.p2 in e1_connections: + e1_connections[side.p2].append(side.p1) + else: + e1_connections[side.p2] = [side.p1] + + for side in e2.sides: + if side.p1 in e2_connections: + e2_connections[side.p1].append(side.p2) + else: + e2_connections[side.p1] = [side.p2] + + if side.p2 in e2_connections: + e2_connections[side.p2].append(side.p1) + else: + e2_connections[side.p2] = [side.p1] + + e1_current = e1_ymax + e2_current = e2_ymin + support_line = Line(Point(S.Zero, S.Zero), Point(S.One, S.Zero)) + + ''' + Determine which point in e1 and e2 will be selected after e2_ymin and e1_ymax, + this information combined with the above produced dictionaries determines the + path that will be taken around the polygons + ''' + point1 = e1_connections[e1_ymax][0] + point2 = e1_connections[e1_ymax][1] + angle1 = support_line.angle_between(Line(e1_ymax, point1)) + angle2 = support_line.angle_between(Line(e1_ymax, point2)) + if angle1 < angle2: + e1_next = point1 + elif angle2 < angle1: + e1_next = point2 + elif Point.distance(e1_ymax, point1) > Point.distance(e1_ymax, point2): + e1_next = point2 + else: + e1_next = point1 + + point1 = e2_connections[e2_ymin][0] + point2 = e2_connections[e2_ymin][1] + angle1 = support_line.angle_between(Line(e2_ymin, point1)) + angle2 = support_line.angle_between(Line(e2_ymin, point2)) + if angle1 > angle2: + e2_next = point1 + elif angle2 > angle1: + e2_next = point2 + elif Point.distance(e2_ymin, point1) > Point.distance(e2_ymin, point2): + e2_next = point2 + else: + e2_next = point1 + + ''' + Loop which determines the distance between anti-podal pairs and updates the + minimum distance accordingly. It repeats until it reaches the starting position. + ''' + while True: + e1_angle = support_line.angle_between(Line(e1_current, e1_next)) + e2_angle = pi - support_line.angle_between(Line( + e2_current, e2_next)) + + if (e1_angle < e2_angle) is True: + support_line = Line(e1_current, e1_next) + e1_segment = Segment(e1_current, e1_next) + min_dist_current = e1_segment.distance(e2_current) + + if min_dist_current.evalf() < min_dist.evalf(): + min_dist = min_dist_current + + if e1_connections[e1_next][0] != e1_current: + e1_current = e1_next + e1_next = e1_connections[e1_next][0] + else: + e1_current = e1_next + e1_next = e1_connections[e1_next][1] + elif (e1_angle > e2_angle) is True: + support_line = Line(e2_next, e2_current) + e2_segment = Segment(e2_current, e2_next) + min_dist_current = e2_segment.distance(e1_current) + + if min_dist_current.evalf() < min_dist.evalf(): + min_dist = min_dist_current + + if e2_connections[e2_next][0] != e2_current: + e2_current = e2_next + e2_next = e2_connections[e2_next][0] + else: + e2_current = e2_next + e2_next = e2_connections[e2_next][1] + else: + support_line = Line(e1_current, e1_next) + e1_segment = Segment(e1_current, e1_next) + e2_segment = Segment(e2_current, e2_next) + min1 = e1_segment.distance(e2_next) + min2 = e2_segment.distance(e1_next) + + min_dist_current = min(min1, min2) + if min_dist_current.evalf() < min_dist.evalf(): + min_dist = min_dist_current + + if e1_connections[e1_next][0] != e1_current: + e1_current = e1_next + e1_next = e1_connections[e1_next][0] + else: + e1_current = e1_next + e1_next = e1_connections[e1_next][1] + + if e2_connections[e2_next][0] != e2_current: + e2_current = e2_next + e2_next = e2_connections[e2_next][0] + else: + e2_current = e2_next + e2_next = e2_connections[e2_next][1] + if e1_current == e1_ymax and e2_current == e2_ymin: + break + return min_dist + + def _svg(self, scale_factor=1., fill_color="#66cc99"): + """Returns SVG path element for the Polygon. + + Parameters + ========== + + scale_factor : float + Multiplication factor for the SVG stroke-width. Default is 1. + fill_color : str, optional + Hex string for fill color. Default is "#66cc99". + """ + verts = map(N, self.vertices) + coords = ["{},{}".format(p.x, p.y) for p in verts] + path = "M {} L {} z".format(coords[0], " L ".join(coords[1:])) + return ( + '' + ).format(2. * scale_factor, path, fill_color) + + def _hashable_content(self): + + D = {} + def ref_list(point_list): + kee = {} + for i, p in enumerate(ordered(set(point_list))): + kee[p] = i + D[i] = p + return [kee[p] for p in point_list] + + S1 = ref_list(self.args) + r_nor = rotate_left(S1, least_rotation(S1)) + S2 = ref_list(list(reversed(self.args))) + r_rev = rotate_left(S2, least_rotation(S2)) + if r_nor < r_rev: + r = r_nor + else: + r = r_rev + canonical_args = [ D[order] for order in r ] + return tuple(canonical_args) + + def __contains__(self, o): + """ + Return True if o is contained within the boundary lines of self.altitudes + + Parameters + ========== + + other : GeometryEntity + + Returns + ======= + + contained in : bool + The points (and sides, if applicable) are contained in self. + + See Also + ======== + + sympy.geometry.entity.GeometryEntity.encloses + + Examples + ======== + + >>> from sympy import Line, Segment, Point + >>> p = Point(0, 0) + >>> q = Point(1, 1) + >>> s = Segment(p, q*2) + >>> l = Line(p, q) + >>> p in q + False + >>> p in s + True + >>> q*3 in s + False + >>> s in l + True + + """ + + if isinstance(o, Polygon): + return self == o + elif isinstance(o, Segment): + return any(o in s for s in self.sides) + elif isinstance(o, Point): + if o in self.vertices: + return True + for side in self.sides: + if o in side: + return True + + return False + + def bisectors(p, prec=None): + """Returns angle bisectors of a polygon. If prec is given + then approximate the point defining the ray to that precision. + + The distance between the points defining the bisector ray is 1. + + Examples + ======== + + >>> from sympy import Polygon, Point + >>> p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3)) + >>> p.bisectors(2) + {Point2D(0, 0): Ray2D(Point2D(0, 0), Point2D(0.71, 0.71)), + Point2D(0, 3): Ray2D(Point2D(0, 3), Point2D(0.23, 2.0)), + Point2D(1, 1): Ray2D(Point2D(1, 1), Point2D(0.19, 0.42)), + Point2D(2, 0): Ray2D(Point2D(2, 0), Point2D(1.1, 0.38))} + """ + b = {} + pts = list(p.args) + pts.append(pts[0]) # close it + cw = Polygon._is_clockwise(*pts[:3]) + if cw: + pts = list(reversed(pts)) + for v, a in p.angles.items(): + i = pts.index(v) + p1, p2 = Point._normalize_dimension(pts[i], pts[i + 1]) + ray = Ray(p1, p2).rotate(a/2, v) + dir = ray.direction + ray = Ray(ray.p1, ray.p1 + dir/dir.distance((0, 0))) + if prec is not None: + ray = Ray(ray.p1, ray.p2.n(prec)) + b[v] = ray + return b + + +class RegularPolygon(Polygon): + """ + A regular polygon. + + Such a polygon has all internal angles equal and all sides the same length. + + Parameters + ========== + + center : Point + radius : number or Basic instance + The distance from the center to a vertex + n : int + The number of sides + + Attributes + ========== + + vertices + center + radius + rotation + apothem + interior_angle + exterior_angle + circumcircle + incircle + angles + + Raises + ====== + + GeometryError + If the `center` is not a Point, or the `radius` is not a number or Basic + instance, or the number of sides, `n`, is less than three. + + Notes + ===== + + A RegularPolygon can be instantiated with Polygon with the kwarg n. + + Regular polygons are instantiated with a center, radius, number of sides + and a rotation angle. Whereas the arguments of a Polygon are vertices, the + vertices of the RegularPolygon must be obtained with the vertices method. + + See Also + ======== + + sympy.geometry.point.Point, Polygon + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> r = RegularPolygon(Point(0, 0), 5, 3) + >>> r + RegularPolygon(Point2D(0, 0), 5, 3, 0) + >>> r.vertices[0] + Point2D(5, 0) + + """ + + __slots__ = ('_n', '_center', '_radius', '_rot') + + def __new__(self, c, r, n, rot=0, **kwargs): + r, n, rot = map(sympify, (r, n, rot)) + c = Point(c, dim=2, **kwargs) + if not isinstance(r, Expr): + raise GeometryError("r must be an Expr object, not %s" % r) + if n.is_Number: + as_int(n) # let an error raise if necessary + if n < 3: + raise GeometryError("n must be a >= 3, not %s" % n) + + obj = GeometryEntity.__new__(self, c, r, n, **kwargs) + obj._n = n + obj._center = c + obj._radius = r + obj._rot = rot % (2*S.Pi/n) if rot.is_number else rot + return obj + + def _eval_evalf(self, prec=15, **options): + c, r, n, a = self.args + dps = prec_to_dps(prec) + c, r, a = [i.evalf(n=dps, **options) for i in (c, r, a)] + return self.func(c, r, n, a) + + @property + def args(self): + """ + Returns the center point, the radius, + the number of sides, and the orientation angle. + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> r = RegularPolygon(Point(0, 0), 5, 3) + >>> r.args + (Point2D(0, 0), 5, 3, 0) + """ + return self._center, self._radius, self._n, self._rot + + def __str__(self): + return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args) + + def __repr__(self): + return 'RegularPolygon(%s, %s, %s, %s)' % tuple(self.args) + + @property + def area(self): + """Returns the area. + + Examples + ======== + + >>> from sympy import RegularPolygon + >>> square = RegularPolygon((0, 0), 1, 4) + >>> square.area + 2 + >>> _ == square.length**2 + True + """ + c, r, n, rot = self.args + return sign(r)*n*self.length**2/(4*tan(pi/n)) + + @property + def length(self): + """Returns the length of the sides. + + The half-length of the side and the apothem form two legs + of a right triangle whose hypotenuse is the radius of the + regular polygon. + + Examples + ======== + + >>> from sympy import RegularPolygon + >>> from sympy import sqrt + >>> s = square_in_unit_circle = RegularPolygon((0, 0), 1, 4) + >>> s.length + sqrt(2) + >>> sqrt((_/2)**2 + s.apothem**2) == s.radius + True + + """ + return self.radius*2*sin(pi/self._n) + + @property + def center(self): + """The center of the RegularPolygon + + This is also the center of the circumscribing circle. + + Returns + ======= + + center : Point + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.ellipse.Ellipse.center + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 5, 4) + >>> rp.center + Point2D(0, 0) + """ + return self._center + + centroid = center + + @property + def circumcenter(self): + """ + Alias for center. + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 5, 4) + >>> rp.circumcenter + Point2D(0, 0) + """ + return self.center + + @property + def radius(self): + """Radius of the RegularPolygon + + This is also the radius of the circumscribing circle. + + Returns + ======= + + radius : number or instance of Basic + + See Also + ======== + + sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy import RegularPolygon, Point + >>> radius = Symbol('r') + >>> rp = RegularPolygon(Point(0, 0), radius, 4) + >>> rp.radius + r + + """ + return self._radius + + @property + def circumradius(self): + """ + Alias for radius. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy import RegularPolygon, Point + >>> radius = Symbol('r') + >>> rp = RegularPolygon(Point(0, 0), radius, 4) + >>> rp.circumradius + r + """ + return self.radius + + @property + def rotation(self): + """CCW angle by which the RegularPolygon is rotated + + Returns + ======= + + rotation : number or instance of Basic + + Examples + ======== + + >>> from sympy import pi + >>> from sympy.abc import a + >>> from sympy import RegularPolygon, Point + >>> RegularPolygon(Point(0, 0), 3, 4, pi/4).rotation + pi/4 + + Numerical rotation angles are made canonical: + + >>> RegularPolygon(Point(0, 0), 3, 4, a).rotation + a + >>> RegularPolygon(Point(0, 0), 3, 4, pi).rotation + 0 + + """ + return self._rot + + @property + def apothem(self): + """The inradius of the RegularPolygon. + + The apothem/inradius is the radius of the inscribed circle. + + Returns + ======= + + apothem : number or instance of Basic + + See Also + ======== + + sympy.geometry.line.Segment.length, sympy.geometry.ellipse.Circle.radius + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy import RegularPolygon, Point + >>> radius = Symbol('r') + >>> rp = RegularPolygon(Point(0, 0), radius, 4) + >>> rp.apothem + sqrt(2)*r/2 + + """ + return self.radius * cos(S.Pi/self._n) + + @property + def inradius(self): + """ + Alias for apothem. + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy import RegularPolygon, Point + >>> radius = Symbol('r') + >>> rp = RegularPolygon(Point(0, 0), radius, 4) + >>> rp.inradius + sqrt(2)*r/2 + """ + return self.apothem + + @property + def interior_angle(self): + """Measure of the interior angles. + + Returns + ======= + + interior_angle : number + + See Also + ======== + + sympy.geometry.line.LinearEntity.angle_between + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 4, 8) + >>> rp.interior_angle + 3*pi/4 + + """ + return (self._n - 2)*S.Pi/self._n + + @property + def exterior_angle(self): + """Measure of the exterior angles. + + Returns + ======= + + exterior_angle : number + + See Also + ======== + + sympy.geometry.line.LinearEntity.angle_between + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 4, 8) + >>> rp.exterior_angle + pi/4 + + """ + return 2*S.Pi/self._n + + @property + def circumcircle(self): + """The circumcircle of the RegularPolygon. + + Returns + ======= + + circumcircle : Circle + + See Also + ======== + + circumcenter, sympy.geometry.ellipse.Circle + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 4, 8) + >>> rp.circumcircle + Circle(Point2D(0, 0), 4) + + """ + return Circle(self.center, self.radius) + + @property + def incircle(self): + """The incircle of the RegularPolygon. + + Returns + ======= + + incircle : Circle + + See Also + ======== + + inradius, sympy.geometry.ellipse.Circle + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 4, 7) + >>> rp.incircle + Circle(Point2D(0, 0), 4*cos(pi/7)) + + """ + return Circle(self.center, self.apothem) + + @property + def angles(self): + """ + Returns a dictionary with keys, the vertices of the Polygon, + and values, the interior angle at each vertex. + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> r = RegularPolygon(Point(0, 0), 5, 3) + >>> r.angles + {Point2D(-5/2, -5*sqrt(3)/2): pi/3, + Point2D(-5/2, 5*sqrt(3)/2): pi/3, + Point2D(5, 0): pi/3} + """ + ret = {} + ang = self.interior_angle + for v in self.vertices: + ret[v] = ang + return ret + + def encloses_point(self, p): + """ + Return True if p is enclosed by (is inside of) self. + + Notes + ===== + + Being on the border of self is considered False. + + The general Polygon.encloses_point method is called only if + a point is not within or beyond the incircle or circumcircle, + respectively. + + Parameters + ========== + + p : Point + + Returns + ======= + + encloses_point : True, False or None + + See Also + ======== + + sympy.geometry.ellipse.Ellipse.encloses_point + + Examples + ======== + + >>> from sympy import RegularPolygon, S, Point, Symbol + >>> p = RegularPolygon((0, 0), 3, 4) + >>> p.encloses_point(Point(0, 0)) + True + >>> r, R = p.inradius, p.circumradius + >>> p.encloses_point(Point((r + R)/2, 0)) + True + >>> p.encloses_point(Point(R/2, R/2 + (R - r)/10)) + False + >>> t = Symbol('t', real=True) + >>> p.encloses_point(p.arbitrary_point().subs(t, S.Half)) + False + >>> p.encloses_point(Point(5, 5)) + False + + """ + + c = self.center + d = Segment(c, p).length + if d >= self.radius: + return False + elif d < self.inradius: + return True + else: + # now enumerate the RegularPolygon like a general polygon. + return Polygon.encloses_point(self, p) + + def spin(self, angle): + """Increment *in place* the virtual Polygon's rotation by ccw angle. + + See also: rotate method which moves the center. + + >>> from sympy import Polygon, Point, pi + >>> r = Polygon(Point(0,0), 1, n=3) + >>> r.vertices[0] + Point2D(1, 0) + >>> r.spin(pi/6) + >>> r.vertices[0] + Point2D(sqrt(3)/2, 1/2) + + See Also + ======== + + rotation + rotate : Creates a copy of the RegularPolygon rotated about a Point + + """ + self._rot += angle + + def rotate(self, angle, pt=None): + """Override GeometryEntity.rotate to first rotate the RegularPolygon + about its center. + + >>> from sympy import Point, RegularPolygon, pi + >>> t = RegularPolygon(Point(1, 0), 1, 3) + >>> t.vertices[0] # vertex on x-axis + Point2D(2, 0) + >>> t.rotate(pi/2).vertices[0] # vertex on y axis now + Point2D(0, 2) + + See Also + ======== + + rotation + spin : Rotates a RegularPolygon in place + + """ + + r = type(self)(*self.args) # need a copy or else changes are in-place + r._rot += angle + return GeometryEntity.rotate(r, angle, pt) + + def scale(self, x=1, y=1, pt=None): + """Override GeometryEntity.scale since it is the radius that must be + scaled (if x == y) or else a new Polygon must be returned. + + >>> from sympy import RegularPolygon + + Symmetric scaling returns a RegularPolygon: + + >>> RegularPolygon((0, 0), 1, 4).scale(2, 2) + RegularPolygon(Point2D(0, 0), 2, 4, 0) + + Asymmetric scaling returns a kite as a Polygon: + + >>> RegularPolygon((0, 0), 1, 4).scale(2, 1) + Polygon(Point2D(2, 0), Point2D(0, 1), Point2D(-2, 0), Point2D(0, -1)) + + """ + if pt: + pt = Point(pt, dim=2) + return self.translate(*(-pt).args).scale(x, y).translate(*pt.args) + if x != y: + return Polygon(*self.vertices).scale(x, y) + c, r, n, rot = self.args + r *= x + return self.func(c, r, n, rot) + + def reflect(self, line): + """Override GeometryEntity.reflect since this is not made of only + points. + + Examples + ======== + + >>> from sympy import RegularPolygon, Line + + >>> RegularPolygon((0, 0), 1, 4).reflect(Line((0, 1), slope=-2)) + RegularPolygon(Point2D(4/5, 2/5), -1, 4, atan(4/3)) + + """ + c, r, n, rot = self.args + v = self.vertices[0] + d = v - c + cc = c.reflect(line) + vv = v.reflect(line) + dd = vv - cc + # calculate rotation about the new center + # which will align the vertices + l1 = Ray((0, 0), dd) + l2 = Ray((0, 0), d) + ang = l1.closing_angle(l2) + rot += ang + # change sign of radius as point traversal is reversed + return self.func(cc, -r, n, rot) + + @property + def vertices(self): + """The vertices of the RegularPolygon. + + Returns + ======= + + vertices : list + Each vertex is a Point. + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import RegularPolygon, Point + >>> rp = RegularPolygon(Point(0, 0), 5, 4) + >>> rp.vertices + [Point2D(5, 0), Point2D(0, 5), Point2D(-5, 0), Point2D(0, -5)] + + """ + c = self._center + r = abs(self._radius) + rot = self._rot + v = 2*S.Pi/self._n + + return [Point(c.x + r*cos(k*v + rot), c.y + r*sin(k*v + rot)) + for k in range(self._n)] + + def __eq__(self, o): + if not isinstance(o, Polygon): + return False + elif not isinstance(o, RegularPolygon): + return Polygon.__eq__(o, self) + return self.args == o.args + + def __hash__(self): + return super().__hash__() + + +class Triangle(Polygon): + """ + A polygon with three vertices and three sides. + + Parameters + ========== + + points : sequence of Points + keyword: asa, sas, or sss to specify sides/angles of the triangle + + Attributes + ========== + + vertices + altitudes + orthocenter + circumcenter + circumradius + circumcircle + inradius + incircle + exradii + medians + medial + nine_point_circle + + Raises + ====== + + GeometryError + If the number of vertices is not equal to three, or one of the vertices + is not a Point, or a valid keyword is not given. + + See Also + ======== + + sympy.geometry.point.Point, Polygon + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) + Triangle(Point2D(0, 0), Point2D(4, 0), Point2D(4, 3)) + + Keywords sss, sas, or asa can be used to give the desired + side lengths (in order) and interior angles (in degrees) that + define the triangle: + + >>> Triangle(sss=(3, 4, 5)) + Triangle(Point2D(0, 0), Point2D(3, 0), Point2D(3, 4)) + >>> Triangle(asa=(30, 1, 30)) + Triangle(Point2D(0, 0), Point2D(1, 0), Point2D(1/2, sqrt(3)/6)) + >>> Triangle(sas=(1, 45, 2)) + Triangle(Point2D(0, 0), Point2D(2, 0), Point2D(sqrt(2)/2, sqrt(2)/2)) + + """ + + def __new__(cls, *args, **kwargs): + if len(args) != 3: + if 'sss' in kwargs: + return _sss(*[simplify(a) for a in kwargs['sss']]) + if 'asa' in kwargs: + return _asa(*[simplify(a) for a in kwargs['asa']]) + if 'sas' in kwargs: + return _sas(*[simplify(a) for a in kwargs['sas']]) + msg = "Triangle instantiates with three points or a valid keyword." + raise GeometryError(msg) + + vertices = [Point(a, dim=2, **kwargs) for a in args] + + # remove consecutive duplicates + nodup = [] + for p in vertices: + if nodup and p == nodup[-1]: + continue + nodup.append(p) + if len(nodup) > 1 and nodup[-1] == nodup[0]: + nodup.pop() # last point was same as first + + # remove collinear points + i = -3 + while i < len(nodup) - 3 and len(nodup) > 2: + a, b, c = sorted( + [nodup[i], nodup[i + 1], nodup[i + 2]], key=default_sort_key) + if Point.is_collinear(a, b, c): + nodup[i] = a + nodup[i + 1] = None + nodup.pop(i + 1) + i += 1 + + vertices = list(filter(lambda x: x is not None, nodup)) + + if len(vertices) == 3: + return GeometryEntity.__new__(cls, *vertices, **kwargs) + elif len(vertices) == 2: + return Segment(*vertices, **kwargs) + else: + return Point(*vertices, **kwargs) + + @property + def vertices(self): + """The triangle's vertices + + Returns + ======= + + vertices : tuple + Each element in the tuple is a Point + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> t = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) + >>> t.vertices + (Point2D(0, 0), Point2D(4, 0), Point2D(4, 3)) + + """ + return self.args + + def is_similar(t1, t2): + """Is another triangle similar to this one. + + Two triangles are similar if one can be uniformly scaled to the other. + + Parameters + ========== + + other: Triangle + + Returns + ======= + + is_similar : boolean + + See Also + ======== + + sympy.geometry.entity.GeometryEntity.is_similar + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) + >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -3)) + >>> t1.is_similar(t2) + True + + >>> t2 = Triangle(Point(0, 0), Point(-4, 0), Point(-4, -4)) + >>> t1.is_similar(t2) + False + + """ + if not isinstance(t2, Polygon): + return False + + s1_1, s1_2, s1_3 = [side.length for side in t1.sides] + s2 = [side.length for side in t2.sides] + + def _are_similar(u1, u2, u3, v1, v2, v3): + e1 = simplify(u1/v1) + e2 = simplify(u2/v2) + e3 = simplify(u3/v3) + return bool(e1 == e2) and bool(e2 == e3) + + # There's only 6 permutations, so write them out + return _are_similar(s1_1, s1_2, s1_3, *s2) or \ + _are_similar(s1_1, s1_3, s1_2, *s2) or \ + _are_similar(s1_2, s1_1, s1_3, *s2) or \ + _are_similar(s1_2, s1_3, s1_1, *s2) or \ + _are_similar(s1_3, s1_1, s1_2, *s2) or \ + _are_similar(s1_3, s1_2, s1_1, *s2) + + def is_equilateral(self): + """Are all the sides the same length? + + Returns + ======= + + is_equilateral : boolean + + See Also + ======== + + sympy.geometry.entity.GeometryEntity.is_similar, RegularPolygon + is_isosceles, is_right, is_scalene + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) + >>> t1.is_equilateral() + False + + >>> from sympy import sqrt + >>> t2 = Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3))) + >>> t2.is_equilateral() + True + + """ + return not has_variety(s.length for s in self.sides) + + def is_isosceles(self): + """Are two or more of the sides the same length? + + Returns + ======= + + is_isosceles : boolean + + See Also + ======== + + is_equilateral, is_right, is_scalene + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(2, 4)) + >>> t1.is_isosceles() + True + + """ + return has_dups(s.length for s in self.sides) + + def is_scalene(self): + """Are all the sides of the triangle of different lengths? + + Returns + ======= + + is_scalene : boolean + + See Also + ======== + + is_equilateral, is_isosceles, is_right + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(1, 4)) + >>> t1.is_scalene() + True + + """ + return not has_dups(s.length for s in self.sides) + + def is_right(self): + """Is the triangle right-angled. + + Returns + ======= + + is_right : boolean + + See Also + ======== + + sympy.geometry.line.LinearEntity.is_perpendicular + is_equilateral, is_isosceles, is_scalene + + Examples + ======== + + >>> from sympy import Triangle, Point + >>> t1 = Triangle(Point(0, 0), Point(4, 0), Point(4, 3)) + >>> t1.is_right() + True + + """ + s = self.sides + return Segment.is_perpendicular(s[0], s[1]) or \ + Segment.is_perpendicular(s[1], s[2]) or \ + Segment.is_perpendicular(s[0], s[2]) + + @property + def altitudes(self): + """The altitudes of the triangle. + + An altitude of a triangle is a segment through a vertex, + perpendicular to the opposite side, with length being the + height of the vertex measured from the line containing the side. + + Returns + ======= + + altitudes : dict + The dictionary consists of keys which are vertices and values + which are Segments. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Segment.length + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.altitudes[p1] + Segment2D(Point2D(0, 0), Point2D(1/2, 1/2)) + + """ + s = self.sides + v = self.vertices + return {v[0]: s[1].perpendicular_segment(v[0]), + v[1]: s[2].perpendicular_segment(v[1]), + v[2]: s[0].perpendicular_segment(v[2])} + + @property + def orthocenter(self): + """The orthocenter of the triangle. + + The orthocenter is the intersection of the altitudes of a triangle. + It may lie inside, outside or on the triangle. + + Returns + ======= + + orthocenter : Point + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.orthocenter + Point2D(0, 0) + + """ + a = self.altitudes + v = self.vertices + return Line(a[v[0]]).intersection(Line(a[v[1]]))[0] + + @property + def circumcenter(self): + """The circumcenter of the triangle + + The circumcenter is the center of the circumcircle. + + Returns + ======= + + circumcenter : Point + + See Also + ======== + + sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.circumcenter + Point2D(1/2, 1/2) + """ + a, b, c = [x.perpendicular_bisector() for x in self.sides] + return a.intersection(b)[0] + + @property + def circumradius(self): + """The radius of the circumcircle of the triangle. + + Returns + ======= + + circumradius : number of Basic instance + + See Also + ======== + + sympy.geometry.ellipse.Circle.radius + + Examples + ======== + + >>> from sympy import Symbol + >>> from sympy import Point, Triangle + >>> a = Symbol('a') + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, a) + >>> t = Triangle(p1, p2, p3) + >>> t.circumradius + sqrt(a**2/4 + 1/4) + """ + return Point.distance(self.circumcenter, self.vertices[0]) + + @property + def circumcircle(self): + """The circle which passes through the three vertices of the triangle. + + Returns + ======= + + circumcircle : Circle + + See Also + ======== + + sympy.geometry.ellipse.Circle + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.circumcircle + Circle(Point2D(1/2, 1/2), sqrt(2)/2) + + """ + return Circle(self.circumcenter, self.circumradius) + + def bisectors(self): + """The angle bisectors of the triangle. + + An angle bisector of a triangle is a straight line through a vertex + which cuts the corresponding angle in half. + + Returns + ======= + + bisectors : dict + Each key is a vertex (Point) and each value is the corresponding + bisector (Segment). + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Segment + + Examples + ======== + + >>> from sympy import Point, Triangle, Segment + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> from sympy import sqrt + >>> t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1)) + True + + """ + # use lines containing sides so containment check during + # intersection calculation can be avoided, thus reducing + # the processing time for calculating the bisectors + s = [Line(l) for l in self.sides] + v = self.vertices + c = self.incenter + l1 = Segment(v[0], Line(v[0], c).intersection(s[1])[0]) + l2 = Segment(v[1], Line(v[1], c).intersection(s[2])[0]) + l3 = Segment(v[2], Line(v[2], c).intersection(s[0])[0]) + return {v[0]: l1, v[1]: l2, v[2]: l3} + + @property + def incenter(self): + """The center of the incircle. + + The incircle is the circle which lies inside the triangle and touches + all three sides. + + Returns + ======= + + incenter : Point + + See Also + ======== + + incircle, sympy.geometry.point.Point + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.incenter + Point2D(1 - sqrt(2)/2, 1 - sqrt(2)/2) + + """ + s = self.sides + l = Matrix([s[i].length for i in [1, 2, 0]]) + p = sum(l) + v = self.vertices + x = simplify(l.dot(Matrix([vi.x for vi in v]))/p) + y = simplify(l.dot(Matrix([vi.y for vi in v]))/p) + return Point(x, y) + + @property + def inradius(self): + """The radius of the incircle. + + Returns + ======= + + inradius : number of Basic instance + + See Also + ======== + + incircle, sympy.geometry.ellipse.Circle.radius + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(4, 0), Point(0, 3) + >>> t = Triangle(p1, p2, p3) + >>> t.inradius + 1 + + """ + return simplify(2 * self.area / self.perimeter) + + @property + def incircle(self): + """The incircle of the triangle. + + The incircle is the circle which lies inside the triangle and touches + all three sides. + + Returns + ======= + + incircle : Circle + + See Also + ======== + + sympy.geometry.ellipse.Circle + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(2, 0), Point(0, 2) + >>> t = Triangle(p1, p2, p3) + >>> t.incircle + Circle(Point2D(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2)) + + """ + return Circle(self.incenter, self.inradius) + + @property + def exradii(self): + """The radius of excircles of a triangle. + + An excircle of the triangle is a circle lying outside the triangle, + tangent to one of its sides and tangent to the extensions of the + other two. + + Returns + ======= + + exradii : dict + + See Also + ======== + + sympy.geometry.polygon.Triangle.inradius + + Examples + ======== + + The exradius touches the side of the triangle to which it is keyed, e.g. + the exradius touching side 2 is: + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2) + >>> t = Triangle(p1, p2, p3) + >>> t.exradii[t.sides[2]] + -2 + sqrt(10) + + References + ========== + + .. [1] https://mathworld.wolfram.com/Exradius.html + .. [2] https://mathworld.wolfram.com/Excircles.html + + """ + + side = self.sides + a = side[0].length + b = side[1].length + c = side[2].length + s = (a+b+c)/2 + area = self.area + exradii = {self.sides[0]: simplify(area/(s-a)), + self.sides[1]: simplify(area/(s-b)), + self.sides[2]: simplify(area/(s-c))} + + return exradii + + @property + def excenters(self): + """Excenters of the triangle. + + An excenter is the center of a circle that is tangent to a side of the + triangle and the extensions of the other two sides. + + Returns + ======= + + excenters : dict + + + Examples + ======== + + The excenters are keyed to the side of the triangle to which their corresponding + excircle is tangent: The center is keyed, e.g. the excenter of a circle touching + side 0 is: + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(6, 0), Point(0, 2) + >>> t = Triangle(p1, p2, p3) + >>> t.excenters[t.sides[0]] + Point2D(12*sqrt(10), 2/3 + sqrt(10)/3) + + See Also + ======== + + sympy.geometry.polygon.Triangle.exradii + + References + ========== + + .. [1] https://mathworld.wolfram.com/Excircles.html + + """ + + s = self.sides + v = self.vertices + a = s[0].length + b = s[1].length + c = s[2].length + x = [v[0].x, v[1].x, v[2].x] + y = [v[0].y, v[1].y, v[2].y] + + exc_coords = { + "x1": simplify(-a*x[0]+b*x[1]+c*x[2]/(-a+b+c)), + "x2": simplify(a*x[0]-b*x[1]+c*x[2]/(a-b+c)), + "x3": simplify(a*x[0]+b*x[1]-c*x[2]/(a+b-c)), + "y1": simplify(-a*y[0]+b*y[1]+c*y[2]/(-a+b+c)), + "y2": simplify(a*y[0]-b*y[1]+c*y[2]/(a-b+c)), + "y3": simplify(a*y[0]+b*y[1]-c*y[2]/(a+b-c)) + } + + excenters = { + s[0]: Point(exc_coords["x1"], exc_coords["y1"]), + s[1]: Point(exc_coords["x2"], exc_coords["y2"]), + s[2]: Point(exc_coords["x3"], exc_coords["y3"]) + } + + return excenters + + @property + def medians(self): + """The medians of the triangle. + + A median of a triangle is a straight line through a vertex and the + midpoint of the opposite side, and divides the triangle into two + equal areas. + + Returns + ======= + + medians : dict + Each key is a vertex (Point) and each value is the median (Segment) + at that point. + + See Also + ======== + + sympy.geometry.point.Point.midpoint, sympy.geometry.line.Segment.midpoint + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.medians[p1] + Segment2D(Point2D(0, 0), Point2D(1/2, 1/2)) + + """ + s = self.sides + v = self.vertices + return {v[0]: Segment(v[0], s[1].midpoint), + v[1]: Segment(v[1], s[2].midpoint), + v[2]: Segment(v[2], s[0].midpoint)} + + @property + def medial(self): + """The medial triangle of the triangle. + + The triangle which is formed from the midpoints of the three sides. + + Returns + ======= + + medial : Triangle + + See Also + ======== + + sympy.geometry.line.Segment.midpoint + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.medial + Triangle(Point2D(1/2, 0), Point2D(1/2, 1/2), Point2D(0, 1/2)) + + """ + s = self.sides + return Triangle(s[0].midpoint, s[1].midpoint, s[2].midpoint) + + @property + def nine_point_circle(self): + """The nine-point circle of the triangle. + + Nine-point circle is the circumcircle of the medial triangle, which + passes through the feet of altitudes and the middle points of segments + connecting the vertices and the orthocenter. + + Returns + ======= + + nine_point_circle : Circle + + See also + ======== + + sympy.geometry.line.Segment.midpoint + sympy.geometry.polygon.Triangle.medial + sympy.geometry.polygon.Triangle.orthocenter + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.nine_point_circle + Circle(Point2D(1/4, 1/4), sqrt(2)/4) + + """ + return Circle(*self.medial.vertices) + + @property + def eulerline(self): + """The Euler line of the triangle. + + The line which passes through circumcenter, centroid and orthocenter. + + Returns + ======= + + eulerline : Line (or Point for equilateral triangles in which case all + centers coincide) + + Examples + ======== + + >>> from sympy import Point, Triangle + >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + >>> t = Triangle(p1, p2, p3) + >>> t.eulerline + Line2D(Point2D(0, 0), Point2D(1/2, 1/2)) + + """ + if self.is_equilateral(): + return self.orthocenter + return Line(self.orthocenter, self.circumcenter) + +def rad(d): + """Return the radian value for the given degrees (pi = 180 degrees).""" + return d*pi/180 + + +def deg(r): + """Return the degree value for the given radians (pi = 180 degrees).""" + return r/pi*180 + + +def _slope(d): + rv = tan(rad(d)) + return rv + + +def _asa(d1, l, d2): + """Return triangle having side with length l on the x-axis.""" + xy = Line((0, 0), slope=_slope(d1)).intersection( + Line((l, 0), slope=_slope(180 - d2)))[0] + return Triangle((0, 0), (l, 0), xy) + + +def _sss(l1, l2, l3): + """Return triangle having side of length l1 on the x-axis.""" + c1 = Circle((0, 0), l3) + c2 = Circle((l1, 0), l2) + inter = [a for a in c1.intersection(c2) if a.y.is_nonnegative] + if not inter: + return None + pt = inter[0] + return Triangle((0, 0), (l1, 0), pt) + + +def _sas(l1, d, l2): + """Return triangle having side with length l2 on the x-axis.""" + p1 = Point(0, 0) + p2 = Point(l2, 0) + p3 = Point(cos(rad(d))*l1, sin(rad(d))*l1) + return Triangle(p1, p2, p3) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__init__.py b/phivenv/Lib/site-packages/sympy/geometry/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48e6edcfbbb9a2eadb86427c232effcc5c5369a6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_curve.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_curve.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca4343af76cedef745dabaf73d6e18395a3cec2b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_curve.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_ellipse.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_ellipse.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2303fd5fa82402d942aadf438043e3c3e307e91 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_ellipse.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_entity.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_entity.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8b9c9e894ad549f47f7fbdf6c9db19cc5ce6351 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_entity.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_geometrysets.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_geometrysets.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e77ed85f97922d3eac1a4d2fa5e87b6614addfe2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_geometrysets.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_line.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_line.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d007cad44d8ce3a0058dbc8b9407bb078297a7d9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_line.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_parabola.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_parabola.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc7b363ab33e7e6314a97ec150385b68a405c72c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_parabola.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_plane.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_plane.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..262d98ccd6ec8caedc47fb8d9f6e48d11eff6606 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_plane.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_point.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_point.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b83a1b897f64eede4471ed0341fb6050ec0cd7c9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_point.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_polygon.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_polygon.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfe6a9bbaf1575c6a2f83709472c8d30bcbaf86f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_polygon.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_util.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1a839c68d566422bb3b18c6f521c254d631f715 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/geometry/tests/__pycache__/test_util.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_curve.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_curve.py new file mode 100644 index 0000000000000000000000000000000000000000..50aa80273a1d8eb9e414a8d591571f3127352dad --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_curve.py @@ -0,0 +1,120 @@ +from sympy.core.containers import Tuple +from sympy.core.numbers import (Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.hyperbolic import asinh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.geometry import Curve, Line, Point, Ellipse, Ray, Segment, Circle, Polygon, RegularPolygon +from sympy.testing.pytest import raises, slow + + +def test_curve(): + x = Symbol('x', real=True) + s = Symbol('s') + z = Symbol('z') + + # this curve is independent of the indicated parameter + c = Curve([2*s, s**2], (z, 0, 2)) + + assert c.parameter == z + assert c.functions == (2*s, s**2) + assert c.arbitrary_point() == Point(2*s, s**2) + assert c.arbitrary_point(z) == Point(2*s, s**2) + + # this is how it is normally used + c = Curve([2*s, s**2], (s, 0, 2)) + + assert c.parameter == s + assert c.functions == (2*s, s**2) + t = Symbol('t') + # the t returned as assumptions + assert c.arbitrary_point() != Point(2*t, t**2) + t = Symbol('t', real=True) + # now t has the same assumptions so the test passes + assert c.arbitrary_point() == Point(2*t, t**2) + assert c.arbitrary_point(z) == Point(2*z, z**2) + assert c.arbitrary_point(c.parameter) == Point(2*s, s**2) + assert c.arbitrary_point(None) == Point(2*s, s**2) + assert c.plot_interval() == [t, 0, 2] + assert c.plot_interval(z) == [z, 0, 2] + + assert Curve([x, x], (x, 0, 1)).rotate(pi/2) == Curve([-x, x], (x, 0, 1)) + assert Curve([x, x], (x, 0, 1)).rotate(pi/2, (1, 2)).scale(2, 3).translate( + 1, 3).arbitrary_point(s) == \ + Line((0, 0), (1, 1)).rotate(pi/2, (1, 2)).scale(2, 3).translate( + 1, 3).arbitrary_point(s) == \ + Point(-2*s + 7, 3*s + 6) + + raises(ValueError, lambda: Curve((s), (s, 1, 2))) + raises(ValueError, lambda: Curve((x, x * 2), (1, x))) + + raises(ValueError, lambda: Curve((s, s + t), (s, 1, 2)).arbitrary_point()) + raises(ValueError, lambda: Curve((s, s + t), (t, 1, 2)).arbitrary_point(s)) + + +@slow +def test_free_symbols(): + a, b, c, d, e, f, s = symbols('a:f,s') + assert Point(a, b).free_symbols == {a, b} + assert Line((a, b), (c, d)).free_symbols == {a, b, c, d} + assert Ray((a, b), (c, d)).free_symbols == {a, b, c, d} + assert Ray((a, b), angle=c).free_symbols == {a, b, c} + assert Segment((a, b), (c, d)).free_symbols == {a, b, c, d} + assert Line((a, b), slope=c).free_symbols == {a, b, c} + assert Curve((a*s, b*s), (s, c, d)).free_symbols == {a, b, c, d} + assert Ellipse((a, b), c, d).free_symbols == {a, b, c, d} + assert Ellipse((a, b), c, eccentricity=d).free_symbols == \ + {a, b, c, d} + assert Ellipse((a, b), vradius=c, eccentricity=d).free_symbols == \ + {a, b, c, d} + assert Circle((a, b), c).free_symbols == {a, b, c} + assert Circle((a, b), (c, d), (e, f)).free_symbols == \ + {e, d, c, b, f, a} + assert Polygon((a, b), (c, d), (e, f)).free_symbols == \ + {e, b, d, f, a, c} + assert RegularPolygon((a, b), c, d, e).free_symbols == {e, a, b, c, d} + + +def test_transform(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + c = Curve((x, x**2), (x, 0, 1)) + cout = Curve((2*x - 4, 3*x**2 - 10), (x, 0, 1)) + pts = [Point(0, 0), Point(S.Half, Rational(1, 4)), Point(1, 1)] + pts_out = [Point(-4, -10), Point(-3, Rational(-37, 4)), Point(-2, -7)] + + assert c.scale(2, 3, (4, 5)) == cout + assert [c.subs(x, xi/2) for xi in Tuple(0, 1, 2)] == pts + assert [cout.subs(x, xi/2) for xi in Tuple(0, 1, 2)] == pts_out + assert Curve((x + y, 3*x), (x, 0, 1)).subs(y, S.Half) == \ + Curve((x + S.Half, 3*x), (x, 0, 1)) + assert Curve((x, 3*x), (x, 0, 1)).translate(4, 5) == \ + Curve((x + 4, 3*x + 5), (x, 0, 1)) + + +def test_length(): + t = Symbol('t', real=True) + + c1 = Curve((t, 0), (t, 0, 1)) + assert c1.length == 1 + + c2 = Curve((t, t), (t, 0, 1)) + assert c2.length == sqrt(2) + + c3 = Curve((t ** 2, t), (t, 2, 5)) + assert c3.length == -sqrt(17) - asinh(4) / 4 + asinh(10) / 4 + 5 * sqrt(101) / 2 + + +def test_parameter_value(): + t = Symbol('t') + C = Curve([2*t, t**2], (t, 0, 2)) + assert C.parameter_value((2, 1), t) == {t: 1} + raises(ValueError, lambda: C.parameter_value((2, 0), t)) + + +def test_issue_17997(): + t, s = symbols('t s') + c = Curve((t, t**2), (t, 0, 10)) + p = Curve([2*s, s**2], (s, 0, 2)) + assert c(2) == Point(2, 4) + assert p(1) == Point(2, 1) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_ellipse.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_ellipse.py new file mode 100644 index 0000000000000000000000000000000000000000..a79eba8c35771bda9f0980aca68d937f8e625c0a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_ellipse.py @@ -0,0 +1,613 @@ +from sympy.core import expand +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sec +from sympy.geometry.line import Segment2D +from sympy.geometry.point import Point2D +from sympy.geometry import (Circle, Ellipse, GeometryError, Line, Point, + Polygon, Ray, RegularPolygon, Segment, + Triangle, intersection) +from sympy.testing.pytest import raises, slow +from sympy.integrals.integrals import integrate +from sympy.functions.special.elliptic_integrals import elliptic_e +from sympy.functions.elementary.miscellaneous import Max + + +def test_ellipse_equation_using_slope(): + from sympy.abc import x, y + + e1 = Ellipse(Point(1, 0), 3, 2) + assert str(e1.equation(_slope=1)) == str((-x + y + 1)**2/8 + (x + y - 1)**2/18 - 1) + + e2 = Ellipse(Point(0, 0), 4, 1) + assert str(e2.equation(_slope=1)) == str((-x + y)**2/2 + (x + y)**2/32 - 1) + + e3 = Ellipse(Point(1, 5), 6, 2) + assert str(e3.equation(_slope=2)) == str((-2*x + y - 3)**2/20 + (x + 2*y - 11)**2/180 - 1) + + +def test_object_from_equation(): + from sympy.abc import x, y, a, b, c, d, e + assert Circle(x**2 + y**2 + 3*x + 4*y - 8) == Circle(Point2D(S(-3) / 2, -2), sqrt(57) / 2) + assert Circle(x**2 + y**2 + 6*x + 8*y + 25) == Circle(Point2D(-3, -4), 0) + assert Circle(a**2 + b**2 + 6*a + 8*b + 25, x='a', y='b') == Circle(Point2D(-3, -4), 0) + assert Circle(x**2 + y**2 - 25) == Circle(Point2D(0, 0), 5) + assert Circle(x**2 + y**2) == Circle(Point2D(0, 0), 0) + assert Circle(a**2 + b**2, x='a', y='b') == Circle(Point2D(0, 0), 0) + assert Circle(x**2 + y**2 + 6*x + 8) == Circle(Point2D(-3, 0), 1) + assert Circle(x**2 + y**2 + 6*y + 8) == Circle(Point2D(0, -3), 1) + assert Circle((x - 1)**2 + y**2 - 9) == Circle(Point2D(1, 0), 3) + assert Circle(6*(x**2) + 6*(y**2) + 6*x + 8*y - 25) == Circle(Point2D(Rational(-1, 2), Rational(-2, 3)), 5*sqrt(7)/6) + assert Circle(Eq(a**2 + b**2, 25), x='a', y=b) == Circle(Point2D(0, 0), 5) + raises(GeometryError, lambda: Circle(x**2 + y**2 + 3*x + 4*y + 26)) + raises(GeometryError, lambda: Circle(x**2 + y**2 + 25)) + raises(GeometryError, lambda: Circle(a**2 + b**2 + 25, x='a', y='b')) + raises(GeometryError, lambda: Circle(x**2 + 6*y + 8)) + raises(GeometryError, lambda: Circle(6*(x ** 2) + 4*(y**2) + 6*x + 8*y + 25)) + raises(ValueError, lambda: Circle(a**2 + b**2 + 3*a + 4*b - 8)) + # .equation() adds 'real=True' assumption; '==' would fail if assumptions differed + x, y = symbols('x y', real=True) + eq = a*x**2 + a*y**2 + c*x + d*y + e + assert expand(Circle(eq).equation()*a) == eq + + +@slow +def test_ellipse_geom(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + t = Symbol('t', real=True) + y1 = Symbol('y1', real=True) + half = S.Half + p1 = Point(0, 0) + p2 = Point(1, 1) + p4 = Point(0, 1) + + e1 = Ellipse(p1, 1, 1) + e2 = Ellipse(p2, half, 1) + e3 = Ellipse(p1, y1, y1) + c1 = Circle(p1, 1) + c2 = Circle(p2, 1) + c3 = Circle(Point(sqrt(2), sqrt(2)), 1) + l1 = Line(p1, p2) + + # Test creation with three points + cen, rad = Point(3*half, 2), 5*half + assert Circle(Point(0, 0), Point(3, 0), Point(0, 4)) == Circle(cen, rad) + assert Circle(Point(0, 0), Point(1, 1), Point(2, 2)) == Segment2D(Point2D(0, 0), Point2D(2, 2)) + + raises(ValueError, lambda: Ellipse(None, None, None, 1)) + raises(ValueError, lambda: Ellipse()) + raises(GeometryError, lambda: Circle(Point(0, 0))) + raises(GeometryError, lambda: Circle(Symbol('x')*Symbol('y'))) + + # Basic Stuff + assert Ellipse(None, 1, 1).center == Point(0, 0) + assert e1 == c1 + assert e1 != e2 + assert e1 != l1 + assert p4 in e1 + assert e1 in e1 + assert e2 in e2 + assert 1 not in e2 + assert p2 not in e2 + assert e1.area == pi + assert e2.area == pi/2 + assert e3.area == pi*y1*abs(y1) + assert c1.area == e1.area + assert c1.circumference == e1.circumference + assert e3.circumference == 2*pi*y1 + assert e1.plot_interval() == e2.plot_interval() == [t, -pi, pi] + assert e1.plot_interval(x) == e2.plot_interval(x) == [x, -pi, pi] + + assert c1.minor == 1 + assert c1.major == 1 + assert c1.hradius == 1 + assert c1.vradius == 1 + + assert Ellipse((1, 1), 0, 0) == Point(1, 1) + assert Ellipse((1, 1), 1, 0) == Segment(Point(0, 1), Point(2, 1)) + assert Ellipse((1, 1), 0, 1) == Segment(Point(1, 0), Point(1, 2)) + + # Private Functions + assert hash(c1) == hash(Circle(Point(1, 0), Point(0, 1), Point(0, -1))) + assert c1 in e1 + assert (Line(p1, p2) in e1) is False + assert e1.__cmp__(e1) == 0 + assert e1.__cmp__(Point(0, 0)) > 0 + + # Encloses + assert e1.encloses(Segment(Point(-0.5, -0.5), Point(0.5, 0.5))) is True + assert e1.encloses(Line(p1, p2)) is False + assert e1.encloses(Ray(p1, p2)) is False + assert e1.encloses(e1) is False + assert e1.encloses( + Polygon(Point(-0.5, -0.5), Point(-0.5, 0.5), Point(0.5, 0.5))) is True + assert e1.encloses(RegularPolygon(p1, 0.5, 3)) is True + assert e1.encloses(RegularPolygon(p1, 5, 3)) is False + assert e1.encloses(RegularPolygon(p2, 5, 3)) is False + + assert e2.arbitrary_point() in e2 + raises(ValueError, lambda: Ellipse(Point(x, y), 1, 1).arbitrary_point(parameter='x')) + + # Foci + f1, f2 = Point(sqrt(12), 0), Point(-sqrt(12), 0) + ef = Ellipse(Point(0, 0), 4, 2) + assert ef.foci in [(f1, f2), (f2, f1)] + + # Tangents + v = sqrt(2) / 2 + p1_1 = Point(v, v) + p1_2 = p2 + Point(half, 0) + p1_3 = p2 + Point(0, 1) + assert e1.tangent_lines(p4) == c1.tangent_lines(p4) + assert e2.tangent_lines(p1_2) == [Line(Point(Rational(3, 2), 1), Point(Rational(3, 2), S.Half))] + assert e2.tangent_lines(p1_3) == [Line(Point(1, 2), Point(Rational(5, 4), 2))] + assert c1.tangent_lines(p1_1) != [Line(p1_1, Point(0, sqrt(2)))] + assert c1.tangent_lines(p1) == [] + assert e2.is_tangent(Line(p1_2, p2 + Point(half, 1))) + assert e2.is_tangent(Line(p1_3, p2 + Point(half, 1))) + assert c1.is_tangent(Line(p1_1, Point(0, sqrt(2)))) + assert e1.is_tangent(Line(Point(0, 0), Point(1, 1))) is False + assert c1.is_tangent(e1) is True + assert c1.is_tangent(Ellipse(Point(2, 0), 1, 1)) is True + assert c1.is_tangent( + Polygon(Point(1, 1), Point(1, -1), Point(2, 0))) is False + assert c1.is_tangent( + Polygon(Point(1, 1), Point(1, 0), Point(2, 0))) is False + assert Circle(Point(5, 5), 3).is_tangent(Circle(Point(0, 5), 1)) is False + + assert Ellipse(Point(5, 5), 2, 1).tangent_lines(Point(0, 0)) == \ + [Line(Point(0, 0), Point(Rational(77, 25), Rational(132, 25))), + Line(Point(0, 0), Point(Rational(33, 5), Rational(22, 5)))] + assert Ellipse(Point(5, 5), 2, 1).tangent_lines(Point(3, 4)) == \ + [Line(Point(3, 4), Point(4, 4)), Line(Point(3, 4), Point(3, 5))] + assert Circle(Point(5, 5), 2).tangent_lines(Point(3, 3)) == \ + [Line(Point(3, 3), Point(4, 3)), Line(Point(3, 3), Point(3, 4))] + assert Circle(Point(5, 5), 2).tangent_lines(Point(5 - 2*sqrt(2), 5)) == \ + [Line(Point(5 - 2*sqrt(2), 5), Point(5 - sqrt(2), 5 - sqrt(2))), + Line(Point(5 - 2*sqrt(2), 5), Point(5 - sqrt(2), 5 + sqrt(2))), ] + assert Circle(Point(5, 5), 5).tangent_lines(Point(4, 0)) == \ + [Line(Point(4, 0), Point(Rational(40, 13), Rational(5, 13))), + Line(Point(4, 0), Point(5, 0))] + assert Circle(Point(5, 5), 5).tangent_lines(Point(0, 6)) == \ + [Line(Point(0, 6), Point(0, 7)), + Line(Point(0, 6), Point(Rational(5, 13), Rational(90, 13)))] + + # for numerical calculations, we shouldn't demand exact equality, + # so only test up to the desired precision + def lines_close(l1, l2, prec): + """ tests whether l1 and 12 are within 10**(-prec) + of each other """ + return abs(l1.p1 - l2.p1) < 10**(-prec) and abs(l1.p2 - l2.p2) < 10**(-prec) + def line_list_close(ll1, ll2, prec): + return all(lines_close(l1, l2, prec) for l1, l2 in zip(ll1, ll2)) + + e = Ellipse(Point(0, 0), 2, 1) + assert e.normal_lines(Point(0, 0)) == \ + [Line(Point(0, 0), Point(0, 1)), Line(Point(0, 0), Point(1, 0))] + assert e.normal_lines(Point(1, 0)) == \ + [Line(Point(0, 0), Point(1, 0))] + assert e.normal_lines((0, 1)) == \ + [Line(Point(0, 0), Point(0, 1))] + assert line_list_close(e.normal_lines(Point(1, 1), 2), [ + Line(Point(Rational(-51, 26), Rational(-1, 5)), Point(Rational(-25, 26), Rational(17, 83))), + Line(Point(Rational(28, 29), Rational(-7, 8)), Point(Rational(57, 29), Rational(-9, 2)))], 2) + # test the failure of Poly.intervals and checks a point on the boundary + p = Point(sqrt(3), S.Half) + assert p in e + assert line_list_close(e.normal_lines(p, 2), [ + Line(Point(Rational(-341, 171), Rational(-1, 13)), Point(Rational(-170, 171), Rational(5, 64))), + Line(Point(Rational(26, 15), Rational(-1, 2)), Point(Rational(41, 15), Rational(-43, 26)))], 2) + # be sure to use the slope that isn't undefined on boundary + e = Ellipse((0, 0), 2, 2*sqrt(3)/3) + assert line_list_close(e.normal_lines((1, 1), 2), [ + Line(Point(Rational(-64, 33), Rational(-20, 71)), Point(Rational(-31, 33), Rational(2, 13))), + Line(Point(1, -1), Point(2, -4))], 2) + # general ellipse fails except under certain conditions + e = Ellipse((0, 0), x, 1) + assert e.normal_lines((x + 1, 0)) == [Line(Point(0, 0), Point(1, 0))] + raises(NotImplementedError, lambda: e.normal_lines((x + 1, 1))) + # Properties + major = 3 + minor = 1 + e4 = Ellipse(p2, minor, major) + assert e4.focus_distance == sqrt(major**2 - minor**2) + ecc = e4.focus_distance / major + assert e4.eccentricity == ecc + assert e4.periapsis == major*(1 - ecc) + assert e4.apoapsis == major*(1 + ecc) + assert e4.semilatus_rectum == major*(1 - ecc ** 2) + # independent of orientation + e4 = Ellipse(p2, major, minor) + assert e4.focus_distance == sqrt(major**2 - minor**2) + ecc = e4.focus_distance / major + assert e4.eccentricity == ecc + assert e4.periapsis == major*(1 - ecc) + assert e4.apoapsis == major*(1 + ecc) + + # Intersection + l1 = Line(Point(1, -5), Point(1, 5)) + l2 = Line(Point(-5, -1), Point(5, -1)) + l3 = Line(Point(-1, -1), Point(1, 1)) + l4 = Line(Point(-10, 0), Point(0, 10)) + pts_c1_l3 = [Point(sqrt(2)/2, sqrt(2)/2), Point(-sqrt(2)/2, -sqrt(2)/2)] + + assert intersection(e2, l4) == [] + assert intersection(c1, Point(1, 0)) == [Point(1, 0)] + assert intersection(c1, l1) == [Point(1, 0)] + assert intersection(c1, l2) == [Point(0, -1)] + assert intersection(c1, l3) in [pts_c1_l3, [pts_c1_l3[1], pts_c1_l3[0]]] + assert intersection(c1, c2) == [Point(0, 1), Point(1, 0)] + assert intersection(c1, c3) == [Point(sqrt(2)/2, sqrt(2)/2)] + assert e1.intersection(l1) == [Point(1, 0)] + assert e2.intersection(l4) == [] + assert e1.intersection(Circle(Point(0, 2), 1)) == [Point(0, 1)] + assert e1.intersection(Circle(Point(5, 0), 1)) == [] + assert e1.intersection(Ellipse(Point(2, 0), 1, 1)) == [Point(1, 0)] + assert e1.intersection(Ellipse(Point(5, 0), 1, 1)) == [] + assert e1.intersection(Point(2, 0)) == [] + assert e1.intersection(e1) == e1 + assert intersection(Ellipse(Point(0, 0), 2, 1), Ellipse(Point(3, 0), 1, 2)) == [Point(2, 0)] + assert intersection(Circle(Point(0, 0), 2), Circle(Point(3, 0), 1)) == [Point(2, 0)] + assert intersection(Circle(Point(0, 0), 2), Circle(Point(7, 0), 1)) == [] + assert intersection(Ellipse(Point(0, 0), 5, 17), Ellipse(Point(4, 0), 1, 0.2) + ) == [Point(5.0, 0, evaluate=False)] + assert intersection(Ellipse(Point(0, 0), 5, 17), Ellipse(Point(4, 0), 0.999, 0.2)) == [] + assert Circle((0, 0), S.Half).intersection( + Triangle((-1, 0), (1, 0), (0, 1))) == [ + Point(Rational(-1, 2), 0), Point(S.Half, 0)] + raises(TypeError, lambda: intersection(e2, Line((0, 0, 0), (0, 0, 1)))) + raises(TypeError, lambda: intersection(e2, Rational(12))) + raises(TypeError, lambda: Ellipse.intersection(e2, 1)) + # some special case intersections + csmall = Circle(p1, 3) + cbig = Circle(p1, 5) + cout = Circle(Point(5, 5), 1) + # one circle inside of another + assert csmall.intersection(cbig) == [] + # separate circles + assert csmall.intersection(cout) == [] + # coincident circles + assert csmall.intersection(csmall) == csmall + + v = sqrt(2) + t1 = Triangle(Point(0, v), Point(0, -v), Point(v, 0)) + points = intersection(t1, c1) + assert len(points) == 4 + assert Point(0, 1) in points + assert Point(0, -1) in points + assert Point(v/2, v/2) in points + assert Point(v/2, -v/2) in points + + circ = Circle(Point(0, 0), 5) + elip = Ellipse(Point(0, 0), 5, 20) + assert intersection(circ, elip) in \ + [[Point(5, 0), Point(-5, 0)], [Point(-5, 0), Point(5, 0)]] + assert elip.tangent_lines(Point(0, 0)) == [] + elip = Ellipse(Point(0, 0), 3, 2) + assert elip.tangent_lines(Point(3, 0)) == \ + [Line(Point(3, 0), Point(3, -12))] + + e1 = Ellipse(Point(0, 0), 5, 10) + e2 = Ellipse(Point(2, 1), 4, 8) + a = Rational(53, 17) + c = 2*sqrt(3991)/17 + ans = [Point(a - c/8, a/2 + c), Point(a + c/8, a/2 - c)] + assert e1.intersection(e2) == ans + e2 = Ellipse(Point(x, y), 4, 8) + c = sqrt(3991) + ans = [Point(-c/68 + a, c*Rational(2, 17) + a/2), Point(c/68 + a, c*Rational(-2, 17) + a/2)] + assert [p.subs({x: 2, y:1}) for p in e1.intersection(e2)] == ans + + # Combinations of above + assert e3.is_tangent(e3.tangent_lines(p1 + Point(y1, 0))[0]) + + e = Ellipse((1, 2), 3, 2) + assert e.tangent_lines(Point(10, 0)) == \ + [Line(Point(10, 0), Point(1, 0)), + Line(Point(10, 0), Point(Rational(14, 5), Rational(18, 5)))] + + # encloses_point + e = Ellipse((0, 0), 1, 2) + assert e.encloses_point(e.center) + assert e.encloses_point(e.center + Point(0, e.vradius - Rational(1, 10))) + assert e.encloses_point(e.center + Point(e.hradius - Rational(1, 10), 0)) + assert e.encloses_point(e.center + Point(e.hradius, 0)) is False + assert e.encloses_point( + e.center + Point(e.hradius + Rational(1, 10), 0)) is False + e = Ellipse((0, 0), 2, 1) + assert e.encloses_point(e.center) + assert e.encloses_point(e.center + Point(0, e.vradius - Rational(1, 10))) + assert e.encloses_point(e.center + Point(e.hradius - Rational(1, 10), 0)) + assert e.encloses_point(e.center + Point(e.hradius, 0)) is False + assert e.encloses_point( + e.center + Point(e.hradius + Rational(1, 10), 0)) is False + assert c1.encloses_point(Point(1, 0)) is False + assert c1.encloses_point(Point(0.3, 0.4)) is True + + assert e.scale(2, 3) == Ellipse((0, 0), 4, 3) + assert e.scale(3, 6) == Ellipse((0, 0), 6, 6) + assert e.rotate(pi) == e + assert e.rotate(pi, (1, 2)) == Ellipse(Point(2, 4), 2, 1) + raises(NotImplementedError, lambda: e.rotate(pi/3)) + + # Circle rotation tests (Issue #11743) + # Link - https://github.com/sympy/sympy/issues/11743 + cir = Circle(Point(1, 0), 1) + assert cir.rotate(pi/2) == Circle(Point(0, 1), 1) + assert cir.rotate(pi/3) == Circle(Point(S.Half, sqrt(3)/2), 1) + assert cir.rotate(pi/3, Point(1, 0)) == Circle(Point(1, 0), 1) + assert cir.rotate(pi/3, Point(0, 1)) == Circle(Point(S.Half + sqrt(3)/2, S.Half + sqrt(3)/2), 1) + + +def test_construction(): + e1 = Ellipse(hradius=2, vradius=1, eccentricity=None) + assert e1.eccentricity == sqrt(3)/2 + + e2 = Ellipse(hradius=2, vradius=None, eccentricity=sqrt(3)/2) + assert e2.vradius == 1 + + e3 = Ellipse(hradius=None, vradius=1, eccentricity=sqrt(3)/2) + assert e3.hradius == 2 + + # filter(None, iterator) filters out anything falsey, including 0 + # eccentricity would be filtered out in this case and the constructor would throw an error + e4 = Ellipse(Point(0, 0), hradius=1, eccentricity=0) + assert e4.vradius == 1 + + #tests for eccentricity > 1 + raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity = S(3)/2)) + raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity=sec(5))) + raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity=S.Pi-S(2))) + + #tests for eccentricity = 1 + #if vradius is not defined + assert Ellipse(None, 1, None, 1).length == 2 + #if hradius is not defined + raises(GeometryError, lambda: Ellipse(None, None, 1, eccentricity = 1)) + + #tests for eccentricity < 0 + raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity = -3)) + raises(GeometryError, lambda: Ellipse(Point(3, 1), hradius=3, eccentricity = -0.5)) + +def test_ellipse_random_point(): + y1 = Symbol('y1', real=True) + e3 = Ellipse(Point(0, 0), y1, y1) + rx, ry = Symbol('rx'), Symbol('ry') + for ind in range(0, 5): + r = e3.random_point() + # substitution should give zero*y1**2 + assert e3.equation(rx, ry).subs(zip((rx, ry), r.args)).equals(0) + # test for the case with seed + r = e3.random_point(seed=1) + assert e3.equation(rx, ry).subs(zip((rx, ry), r.args)).equals(0) + + +def test_repr(): + assert repr(Circle((0, 1), 2)) == 'Circle(Point2D(0, 1), 2)' + + +def test_transform(): + c = Circle((1, 1), 2) + assert c.scale(-1) == Circle((-1, 1), 2) + assert c.scale(y=-1) == Circle((1, -1), 2) + assert c.scale(2) == Ellipse((2, 1), 4, 2) + + assert Ellipse((0, 0), 2, 3).scale(2, 3, (4, 5)) == \ + Ellipse(Point(-4, -10), 4, 9) + assert Circle((0, 0), 2).scale(2, 3, (4, 5)) == \ + Ellipse(Point(-4, -10), 4, 6) + assert Ellipse((0, 0), 2, 3).scale(3, 3, (4, 5)) == \ + Ellipse(Point(-8, -10), 6, 9) + assert Circle((0, 0), 2).scale(3, 3, (4, 5)) == \ + Circle(Point(-8, -10), 6) + assert Circle(Point(-8, -10), 6).scale(Rational(1, 3), Rational(1, 3), (4, 5)) == \ + Circle((0, 0), 2) + assert Circle((0, 0), 2).translate(4, 5) == \ + Circle((4, 5), 2) + assert Circle((0, 0), 2).scale(3, 3) == \ + Circle((0, 0), 6) + + +def test_bounds(): + e1 = Ellipse(Point(0, 0), 3, 5) + e2 = Ellipse(Point(2, -2), 7, 7) + c1 = Circle(Point(2, -2), 7) + c2 = Circle(Point(-2, 0), Point(0, 2), Point(2, 0)) + assert e1.bounds == (-3, -5, 3, 5) + assert e2.bounds == (-5, -9, 9, 5) + assert c1.bounds == (-5, -9, 9, 5) + assert c2.bounds == (-2, -2, 2, 2) + + +def test_reflect(): + b = Symbol('b') + m = Symbol('m') + l = Line((0, b), slope=m) + t1 = Triangle((0, 0), (1, 0), (2, 3)) + assert t1.area == -t1.reflect(l).area + e = Ellipse((1, 0), 1, 2) + assert e.area == -e.reflect(Line((1, 0), slope=0)).area + assert e.area == -e.reflect(Line((1, 0), slope=oo)).area + raises(NotImplementedError, lambda: e.reflect(Line((1, 0), slope=m))) + assert Circle((0, 1), 1).reflect(Line((0, 0), (1, 1))) == Circle(Point2D(1, 0), -1) + + +def test_is_tangent(): + e1 = Ellipse(Point(0, 0), 3, 5) + c1 = Circle(Point(2, -2), 7) + assert e1.is_tangent(Point(0, 0)) is False + assert e1.is_tangent(Point(3, 0)) is False + assert e1.is_tangent(e1) is True + assert e1.is_tangent(Ellipse((0, 0), 1, 2)) is False + assert e1.is_tangent(Ellipse((0, 0), 3, 2)) is True + assert c1.is_tangent(Ellipse((2, -2), 7, 1)) is True + assert c1.is_tangent(Circle((11, -2), 2)) is True + assert c1.is_tangent(Circle((7, -2), 2)) is True + assert c1.is_tangent(Ray((-5, -2), (-15, -20))) is False + assert c1.is_tangent(Ray((-3, -2), (-15, -20))) is False + assert c1.is_tangent(Ray((-3, -22), (15, 20))) is False + assert c1.is_tangent(Ray((9, 20), (9, -20))) is True + assert c1.is_tangent(Ray((2, 5), (9, 5))) is True + assert c1.is_tangent(Segment((2, 5), (9, 5))) is True + assert e1.is_tangent(Segment((2, 2), (-7, 7))) is False + assert e1.is_tangent(Segment((0, 0), (1, 2))) is False + assert c1.is_tangent(Segment((0, 0), (-5, -2))) is False + assert e1.is_tangent(Segment((3, 0), (12, 12))) is False + assert e1.is_tangent(Segment((12, 12), (3, 0))) is False + assert e1.is_tangent(Segment((-3, 0), (3, 0))) is False + assert e1.is_tangent(Segment((-3, 5), (3, 5))) is True + assert e1.is_tangent(Line((10, 0), (10, 10))) is False + assert e1.is_tangent(Line((0, 0), (1, 1))) is False + assert e1.is_tangent(Line((-3, 0), (-2.99, -0.001))) is False + assert e1.is_tangent(Line((-3, 0), (-3, 1))) is True + assert e1.is_tangent(Polygon((0, 0), (5, 5), (5, -5))) is False + assert e1.is_tangent(Polygon((-100, -50), (-40, -334), (-70, -52))) is False + assert e1.is_tangent(Polygon((-3, 0), (3, 0), (0, 1))) is False + assert e1.is_tangent(Polygon((-3, 0), (3, 0), (0, 5))) is False + assert e1.is_tangent(Polygon((-3, 0), (0, -5), (3, 0), (0, 5))) is False + assert e1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is True + assert c1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is False + assert e1.is_tangent(Polygon((0, 0), (3, 0), (7, 7), (0, 5))) is False + assert e1.is_tangent(Polygon((3, 12), (3, -12), (6, 5))) is False + assert e1.is_tangent(Polygon((3, 12), (3, -12), (0, -5), (0, 5))) is False + assert e1.is_tangent(Polygon((3, 0), (5, 7), (6, -5))) is False + assert c1.is_tangent(Segment((0, 0), (-5, -2))) is False + assert e1.is_tangent(Segment((-3, 0), (3, 0))) is False + assert e1.is_tangent(Segment((-3, 5), (3, 5))) is True + assert e1.is_tangent(Polygon((0, 0), (5, 5), (5, -5))) is False + assert e1.is_tangent(Polygon((-100, -50), (-40, -334), (-70, -52))) is False + assert e1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is True + assert c1.is_tangent(Polygon((-3, -5), (-3, 5), (3, 5), (3, -5))) is False + assert e1.is_tangent(Polygon((3, 12), (3, -12), (0, -5), (0, 5))) is False + assert e1.is_tangent(Polygon((3, 0), (5, 7), (6, -5))) is False + raises(TypeError, lambda: e1.is_tangent(Point(0, 0, 0))) + raises(TypeError, lambda: e1.is_tangent(Rational(5))) + + +def test_parameter_value(): + t = Symbol('t') + e = Ellipse(Point(0, 0), 3, 5) + assert e.parameter_value((3, 0), t) == {t: 0} + raises(ValueError, lambda: e.parameter_value((4, 0), t)) + + +@slow +def test_second_moment_of_area(): + x, y = symbols('x, y') + e = Ellipse(Point(0, 0), 5, 4) + I_yy = 2*4*integrate(sqrt(25 - x**2)*x**2, (x, -5, 5))/5 + I_xx = 2*5*integrate(sqrt(16 - y**2)*y**2, (y, -4, 4))/4 + Y = 3*sqrt(1 - x**2/5**2) + I_xy = integrate(integrate(y, (y, -Y, Y))*x, (x, -5, 5)) + assert I_yy == e.second_moment_of_area()[1] + assert I_xx == e.second_moment_of_area()[0] + assert I_xy == e.second_moment_of_area()[2] + #checking for other point + t1 = e.second_moment_of_area(Point(6,5)) + t2 = (580*pi, 845*pi, 600*pi) + assert t1==t2 + + +def test_section_modulus_and_polar_second_moment_of_area(): + d = Symbol('d', positive=True) + c = Circle((3, 7), 8) + assert c.polar_second_moment_of_area() == 2048*pi + assert c.section_modulus() == (128*pi, 128*pi) + c = Circle((2, 9), d/2) + assert c.polar_second_moment_of_area() == pi*d**3*Abs(d)/64 + pi*d*Abs(d)**3/64 + assert c.section_modulus() == (pi*d**3/S(32), pi*d**3/S(32)) + + a, b = symbols('a, b', positive=True) + e = Ellipse((4, 6), a, b) + assert e.section_modulus() == (pi*a*b**2/S(4), pi*a**2*b/S(4)) + assert e.polar_second_moment_of_area() == pi*a**3*b/S(4) + pi*a*b**3/S(4) + e = e.rotate(pi/2) # no change in polar and section modulus + assert e.section_modulus() == (pi*a**2*b/S(4), pi*a*b**2/S(4)) + assert e.polar_second_moment_of_area() == pi*a**3*b/S(4) + pi*a*b**3/S(4) + + e = Ellipse((a, b), 2, 6) + assert e.section_modulus() == (18*pi, 6*pi) + assert e.polar_second_moment_of_area() == 120*pi + + e = Ellipse(Point(0, 0), 2, 2) + assert e.section_modulus() == (2*pi, 2*pi) + assert e.section_modulus(Point(2, 2)) == (2*pi, 2*pi) + assert e.section_modulus((2, 2)) == (2*pi, 2*pi) + + +def test_circumference(): + M = Symbol('M') + m = Symbol('m') + assert Ellipse(Point(0, 0), M, m).circumference == 4 * M * elliptic_e((M ** 2 - m ** 2) / M**2) + + assert Ellipse(Point(0, 0), 5, 4).circumference == 20 * elliptic_e(S(9) / 25) + + # circle + assert Ellipse(None, 1, None, 0).circumference == 2*pi + + # test numerically + assert abs(Ellipse(None, hradius=5, vradius=3).circumference.evalf(16) - 25.52699886339813) < 1e-10 + + +def test_issue_15259(): + assert Circle((1, 2), 0) == Point(1, 2) + + +def test_issue_15797_equals(): + Ri = 0.024127189424130748 + Ci = (0.0864931002830291, 0.0819863295239654) + A = Point(0, 0.0578591400998346) + c = Circle(Ci, Ri) # evaluated + assert c.is_tangent(c.tangent_lines(A)[0]) == True + assert c.center.x.is_Rational + assert c.center.y.is_Rational + assert c.radius.is_Rational + u = Circle(Ci, Ri, evaluate=False) # unevaluated + assert u.center.x.is_Float + assert u.center.y.is_Float + assert u.radius.is_Float + + +def test_auxiliary_circle(): + x, y, a, b = symbols('x y a b') + e = Ellipse((x, y), a, b) + # the general result + assert e.auxiliary_circle() == Circle((x, y), Max(a, b)) + # a special case where Ellipse is a Circle + assert Circle((3, 4), 8).auxiliary_circle() == Circle((3, 4), 8) + + +def test_director_circle(): + x, y, a, b = symbols('x y a b') + e = Ellipse((x, y), a, b) + # the general result + assert e.director_circle() == Circle((x, y), sqrt(a**2 + b**2)) + # a special case where Ellipse is a Circle + assert Circle((3, 4), 8).director_circle() == Circle((3, 4), 8*sqrt(2)) + + +def test_evolute(): + #ellipse centered at h,k + x, y, h, k = symbols('x y h k',real = True) + a, b = symbols('a b') + e = Ellipse(Point(h, k), a, b) + t1 = (e.hradius*(x - e.center.x))**Rational(2, 3) + t2 = (e.vradius*(y - e.center.y))**Rational(2, 3) + E = t1 + t2 - (e.hradius**2 - e.vradius**2)**Rational(2, 3) + assert e.evolute() == E + #Numerical Example + e = Ellipse(Point(1, 1), 6, 3) + t1 = (6*(x - 1))**Rational(2, 3) + t2 = (3*(y - 1))**Rational(2, 3) + E = t1 + t2 - (27)**Rational(2, 3) + assert e.evolute() == E + + +def test_svg(): + e1 = Ellipse(Point(1, 0), 3, 2) + assert e1._svg(2, "#FFAAFF") == '' diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_entity.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_entity.py new file mode 100644 index 0000000000000000000000000000000000000000..0d440fd5dbd193c7c490b45a706fab2703e247ec --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_entity.py @@ -0,0 +1,120 @@ +from sympy.core.numbers import (Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.geometry import (Circle, Ellipse, Point, Line, Parabola, + Polygon, Ray, RegularPolygon, Segment, Triangle, Plane, Curve) +from sympy.geometry.entity import scale, GeometryEntity +from sympy.testing.pytest import raises + + +def test_entity(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + + assert GeometryEntity(x, y) in GeometryEntity(x, y) + raises(NotImplementedError, lambda: Point(0, 0) in GeometryEntity(x, y)) + + assert GeometryEntity(x, y) == GeometryEntity(x, y) + assert GeometryEntity(x, y).equals(GeometryEntity(x, y)) + + c = Circle((0, 0), 5) + assert GeometryEntity.encloses(c, Point(0, 0)) + assert GeometryEntity.encloses(c, Segment((0, 0), (1, 1))) + assert GeometryEntity.encloses(c, Line((0, 0), (1, 1))) is False + assert GeometryEntity.encloses(c, Circle((0, 0), 4)) + assert GeometryEntity.encloses(c, Polygon(Point(0, 0), Point(1, 0), Point(0, 1))) + assert GeometryEntity.encloses(c, RegularPolygon(Point(8, 8), 1, 3)) is False + + +def test_svg(): + a = Symbol('a') + b = Symbol('b') + d = Symbol('d') + + entity = Circle(Point(a, b), d) + assert entity._repr_svg_() is None + + entity = Circle(Point(0, 0), S.Infinity) + assert entity._repr_svg_() is None + + +def test_subs(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + p = Point(x, 2) + q = Point(1, 1) + r = Point(3, 4) + for o in [p, + Segment(p, q), + Ray(p, q), + Line(p, q), + Triangle(p, q, r), + RegularPolygon(p, 3, 6), + Polygon(p, q, r, Point(5, 4)), + Circle(p, 3), + Ellipse(p, 3, 4)]: + assert 'y' in str(o.subs(x, y)) + assert p.subs({x: 1}) == Point(1, 2) + assert Point(1, 2).subs(Point(1, 2), Point(3, 4)) == Point(3, 4) + assert Point(1, 2).subs((1, 2), Point(3, 4)) == Point(3, 4) + assert Point(1, 2).subs(Point(1, 2), Point(3, 4)) == Point(3, 4) + assert Point(1, 2).subs({(1, 2)}) == Point(2, 2) + raises(ValueError, lambda: Point(1, 2).subs(1)) + raises(TypeError, lambda: Point(1, 1).subs((Point(1, 1), Point(1, + 2)), 1, 2)) + + +def test_transform(): + assert scale(1, 2, (3, 4)).tolist() == \ + [[1, 0, 0], [0, 2, 0], [0, -4, 1]] + + +def test_reflect_entity_overrides(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + b = Symbol('b') + m = Symbol('m') + l = Line((0, b), slope=m) + p = Point(x, y) + r = p.reflect(l) + c = Circle((x, y), 3) + cr = c.reflect(l) + assert cr == Circle(r, -3) + assert c.area == -cr.area + + pent = RegularPolygon((1, 2), 1, 5) + slope = S.ComplexInfinity + while slope is S.ComplexInfinity: + slope = Rational(*(x._random()/2).as_real_imag()) + l = Line(pent.vertices[1], slope=slope) + rpent = pent.reflect(l) + assert rpent.center == pent.center.reflect(l) + rvert = [i.reflect(l) for i in pent.vertices] + for v in rpent.vertices: + for i in range(len(rvert)): + ri = rvert[i] + if ri.equals(v): + rvert.remove(ri) + break + assert not rvert + assert pent.area.equals(-rpent.area) + + +def test_geometry_EvalfMixin(): + x = pi + t = Symbol('t') + for g in [ + Point(x, x), + Plane(Point(0, x, 0), (0, 0, x)), + Curve((x*t, x), (t, 0, x)), + Ellipse((x, x), x, -x), + Circle((x, x), x), + Line((0, x), (x, 0)), + Segment((0, x), (x, 0)), + Ray((0, x), (x, 0)), + Parabola((0, x), Line((-x, 0), (x, 0))), + Polygon((0, 0), (0, x), (x, 0), (x, x)), + RegularPolygon((0, x), x, 4, x), + Triangle((0, 0), (x, 0), (x, x)), + ]: + assert str(g).replace('pi', '3.1') == str(g.n(2)) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_geometrysets.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_geometrysets.py new file mode 100644 index 0000000000000000000000000000000000000000..c52898b3c9ba4e9db80c244db3aebf88db2cc8b4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_geometrysets.py @@ -0,0 +1,38 @@ +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.geometry import Circle, Line, Point, Polygon, Segment +from sympy.sets import FiniteSet, Union, Intersection, EmptySet + + +def test_booleans(): + """ test basic unions and intersections """ + half = S.Half + + p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) + p5, p6, p7 = map(Point, [(3, 2), (1, -1), (0, 2)]) + l1 = Line(Point(0,0), Point(1,1)) + l2 = Line(Point(half, half), Point(5,5)) + l3 = Line(p2, p3) + l4 = Line(p3, p4) + poly1 = Polygon(p1, p2, p3, p4) + poly2 = Polygon(p5, p6, p7) + poly3 = Polygon(p1, p2, p5) + assert Union(l1, l2).equals(l1) + assert Intersection(l1, l2).equals(l1) + assert Intersection(l1, l4) == FiniteSet(Point(1,1)) + assert Intersection(Union(l1, l4), l3) == FiniteSet(Point(Rational(-1, 3), Rational(-1, 3)), Point(5, 1)) + assert Intersection(l1, FiniteSet(Point(7,-7))) == EmptySet + assert Intersection(Circle(Point(0,0), 3), Line(p1,p2)) == FiniteSet(Point(-3,0), Point(3,0)) + assert Intersection(l1, FiniteSet(p1)) == FiniteSet(p1) + assert Union(l1, FiniteSet(p1)) == l1 + + fs = FiniteSet(Point(Rational(1, 3), 1), Point(Rational(2, 3), 0), Point(Rational(9, 5), Rational(1, 5)), Point(Rational(7, 3), 1)) + # test the intersection of polygons + assert Intersection(poly1, poly2) == fs + # make sure if we union polygons with subsets, the subsets go away + assert Union(poly1, poly2, fs) == Union(poly1, poly2) + # make sure that if we union with a FiniteSet that isn't a subset, + # that the points in the intersection stop being listed + assert Union(poly1, FiniteSet(Point(0,0), Point(3,5))) == Union(poly1, FiniteSet(Point(3,5))) + # intersect two polygons that share an edge + assert Intersection(poly1, poly3) == Union(FiniteSet(Point(Rational(3, 2), 1), Point(2, 1)), Segment(Point(0, 0), Point(1, 0))) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_line.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_line.py new file mode 100644 index 0000000000000000000000000000000000000000..5158ec05ab414020fbbe2681a2658454dd15b6eb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_line.py @@ -0,0 +1,861 @@ +from sympy.core.numbers import (Float, Rational, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, cos, sin) +from sympy.sets import EmptySet +from sympy.simplify.simplify import simplify +from sympy.functions.elementary.trigonometric import tan +from sympy.geometry import (Circle, GeometryError, Line, Point, Ray, + Segment, Triangle, intersection, Point3D, Line3D, Ray3D, Segment3D, + Point2D, Line2D, Plane) +from sympy.geometry.line import Undecidable +from sympy.geometry.polygon import _asa as asa +from sympy.utilities.iterables import cartes +from sympy.testing.pytest import raises, warns + + +x = Symbol('x', real=True) +y = Symbol('y', real=True) +z = Symbol('z', real=True) +k = Symbol('k', real=True) +x1 = Symbol('x1', real=True) +y1 = Symbol('y1', real=True) +t = Symbol('t', real=True) +a, b = symbols('a,b', real=True) +m = symbols('m', real=True) + + +def test_object_from_equation(): + from sympy.abc import x, y, a, b + assert Line(3*x + y + 18) == Line2D(Point2D(0, -18), Point2D(1, -21)) + assert Line(3*x + 5 * y + 1) == Line2D( + Point2D(0, Rational(-1, 5)), Point2D(1, Rational(-4, 5))) + assert Line(3*a + b + 18, x="a", y="b") == Line2D( + Point2D(0, -18), Point2D(1, -21)) + assert Line(3*x + y) == Line2D(Point2D(0, 0), Point2D(1, -3)) + assert Line(x + y) == Line2D(Point2D(0, 0), Point2D(1, -1)) + assert Line(Eq(3*a + b, -18), x="a", y=b) == Line2D( + Point2D(0, -18), Point2D(1, -21)) + # issue 22361 + assert Line(x - 1) == Line2D(Point2D(1, 0), Point2D(1, 1)) + assert Line(2*x - 2, y=x) == Line2D(Point2D(0, 1), Point2D(1, 1)) + assert Line(y) == Line2D(Point2D(0, 0), Point2D(1, 0)) + assert Line(2*y, x=y) == Line2D(Point2D(0, 0), Point2D(0, 1)) + assert Line(y, x=y) == Line2D(Point2D(0, 0), Point2D(0, 1)) + raises(ValueError, lambda: Line(x / y)) + raises(ValueError, lambda: Line(a / b, x='a', y='b')) + raises(ValueError, lambda: Line(y / x)) + raises(ValueError, lambda: Line(b / a, x='a', y='b')) + raises(ValueError, lambda: Line((x + 1)**2 + y)) + + +def feq(a, b): + """Test if two floating point values are 'equal'.""" + t_float = Float("1.0E-10") + return -t_float < a - b < t_float + + +def test_angle_between(): + a = Point(1, 2, 3, 4) + b = a.orthogonal_direction + o = a.origin + assert feq(Line.angle_between(Line(Point(0, 0), Point(1, 1)), + Line(Point(0, 0), Point(5, 0))).evalf(), pi.evalf() / 4) + assert Line(a, o).angle_between(Line(b, o)) == pi / 2 + z = Point3D(0, 0, 0) + assert Line3D.angle_between(Line3D(z, Point3D(1, 1, 1)), + Line3D(z, Point3D(5, 0, 0))) == acos(sqrt(3) / 3) + # direction of points is used to determine angle + assert Line3D.angle_between(Line3D(z, Point3D(1, 1, 1)), + Line3D(Point3D(5, 0, 0), z)) == acos(-sqrt(3) / 3) + + +def test_closing_angle(): + a = Ray((0, 0), angle=0) + b = Ray((1, 2), angle=pi/2) + assert a.closing_angle(b) == -pi/2 + assert b.closing_angle(a) == pi/2 + assert a.closing_angle(a) == 0 + + +def test_smallest_angle(): + a = Line(Point(1, 1), Point(1, 2)) + b = Line(Point(1, 1),Point(2, 3)) + assert a.smallest_angle_between(b) == acos(2*sqrt(5)/5) + + +def test_svg(): + a = Line(Point(1, 1),Point(1, 2)) + assert a._svg() == '' + a = Segment(Point(1, 0),Point(1, 1)) + assert a._svg() == '' + a = Ray(Point(2, 3), Point(3, 5)) + assert a._svg() == '' + + +def test_arbitrary_point(): + l1 = Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1)) + l2 = Line(Point(x1, x1), Point(y1, y1)) + assert l2.arbitrary_point() in l2 + assert Ray((1, 1), angle=pi / 4).arbitrary_point() == \ + Point(t + 1, t + 1) + assert Segment((1, 1), (2, 3)).arbitrary_point() == Point(1 + t, 1 + 2 * t) + assert l1.perpendicular_segment(l1.arbitrary_point()) == l1.arbitrary_point() + assert Ray3D((1, 1, 1), direction_ratio=[1, 2, 3]).arbitrary_point() == \ + Point3D(t + 1, 2 * t + 1, 3 * t + 1) + assert Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1)).midpoint == \ + Point3D(S.Half, S.Half, S.Half) + assert Segment3D(Point3D(x1, x1, x1), Point3D(y1, y1, y1)).length == sqrt(3) * sqrt((x1 - y1) ** 2) + assert Segment3D((1, 1, 1), (2, 3, 4)).arbitrary_point() == \ + Point3D(t + 1, 2 * t + 1, 3 * t + 1) + raises(ValueError, (lambda: Line((x, 1), (2, 3)).arbitrary_point(x))) + + +def test_are_concurrent_2d(): + l1 = Line(Point(0, 0), Point(1, 1)) + l2 = Line(Point(x1, x1), Point(x1, 1 + x1)) + assert Line.are_concurrent(l1) is False + assert Line.are_concurrent(l1, l2) + assert Line.are_concurrent(l1, l1, l1, l2) + assert Line.are_concurrent(l1, l2, Line(Point(5, x1), Point(Rational(-3, 5), x1))) + assert Line.are_concurrent(l1, Line(Point(0, 0), Point(-x1, x1)), l2) is False + + +def test_are_concurrent_3d(): + p1 = Point3D(0, 0, 0) + l1 = Line(p1, Point3D(1, 1, 1)) + parallel_1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)) + parallel_2 = Line3D(Point3D(0, 1, 0), Point3D(1, 1, 0)) + assert Line3D.are_concurrent(l1) is False + assert Line3D.are_concurrent(l1, Line(Point3D(x1, x1, x1), Point3D(y1, y1, y1))) is False + assert Line3D.are_concurrent(l1, Line3D(p1, Point3D(x1, x1, x1)), + Line(Point3D(x1, x1, x1), Point3D(x1, 1 + x1, 1))) is True + assert Line3D.are_concurrent(parallel_1, parallel_2) is False + + +def test_arguments(): + """Functions accepting `Point` objects in `geometry` + should also accept tuples, lists, and generators and + automatically convert them to points.""" + from sympy.utilities.iterables import subsets + + singles2d = ((1, 2), [1, 3], Point(1, 5)) + doubles2d = subsets(singles2d, 2) + l2d = Line(Point2D(1, 2), Point2D(2, 3)) + singles3d = ((1, 2, 3), [1, 2, 4], Point(1, 2, 6)) + doubles3d = subsets(singles3d, 2) + l3d = Line(Point3D(1, 2, 3), Point3D(1, 1, 2)) + singles4d = ((1, 2, 3, 4), [1, 2, 3, 5], Point(1, 2, 3, 7)) + doubles4d = subsets(singles4d, 2) + l4d = Line(Point(1, 2, 3, 4), Point(2, 2, 2, 2)) + # test 2D + test_single = ['contains', 'distance', 'equals', 'parallel_line', 'perpendicular_line', 'perpendicular_segment', + 'projection', 'intersection'] + for p in doubles2d: + Line2D(*p) + for func in test_single: + for p in singles2d: + getattr(l2d, func)(p) + # test 3D + for p in doubles3d: + Line3D(*p) + for func in test_single: + for p in singles3d: + getattr(l3d, func)(p) + # test 4D + for p in doubles4d: + Line(*p) + for func in test_single: + for p in singles4d: + getattr(l4d, func)(p) + + +def test_basic_properties_2d(): + p1 = Point(0, 0) + p2 = Point(1, 1) + p10 = Point(2000, 2000) + p_r3 = Ray(p1, p2).random_point() + p_r4 = Ray(p2, p1).random_point() + + l1 = Line(p1, p2) + l3 = Line(Point(x1, x1), Point(x1, 1 + x1)) + l4 = Line(p1, Point(1, 0)) + + r1 = Ray(p1, Point(0, 1)) + r2 = Ray(Point(0, 1), p1) + + s1 = Segment(p1, p10) + p_s1 = s1.random_point() + + assert Line((1, 1), slope=1) == Line((1, 1), (2, 2)) + assert Line((1, 1), slope=oo) == Line((1, 1), (1, 2)) + assert Line((1, 1), slope=oo).bounds == (1, 1, 1, 2) + assert Line((1, 1), slope=-oo) == Line((1, 1), (1, 2)) + assert Line(p1, p2).scale(2, 1) == Line(p1, Point(2, 1)) + assert Line(p1, p2) == Line(p1, p2) + assert Line(p1, p2) != Line(p2, p1) + assert l1 != Line(Point(x1, x1), Point(y1, y1)) + assert l1 != l3 + assert Line(p1, p10) != Line(p10, p1) + assert Line(p1, p10) != p1 + assert p1 in l1 # is p1 on the line l1? + assert p1 not in l3 + assert s1 in Line(p1, p10) + assert Ray(Point(0, 0), Point(0, 1)) in Ray(Point(0, 0), Point(0, 2)) + assert Ray(Point(0, 0), Point(0, 2)) in Ray(Point(0, 0), Point(0, 1)) + assert Ray(Point(0, 0), Point(0, 2)).xdirection == S.Zero + assert Ray(Point(0, 0), Point(1, 2)).xdirection == S.Infinity + assert Ray(Point(0, 0), Point(-1, 2)).xdirection == S.NegativeInfinity + assert Ray(Point(0, 0), Point(2, 0)).ydirection == S.Zero + assert Ray(Point(0, 0), Point(2, 2)).ydirection == S.Infinity + assert Ray(Point(0, 0), Point(2, -2)).ydirection == S.NegativeInfinity + assert (r1 in s1) is False + assert Segment(p1, p2) in s1 + assert Ray(Point(x1, x1), Point(x1, 1 + x1)) != Ray(p1, Point(-1, 5)) + assert Segment(p1, p2).midpoint == Point(S.Half, S.Half) + assert Segment(p1, Point(-x1, x1)).length == sqrt(2 * (x1 ** 2)) + + assert l1.slope == 1 + assert l3.slope is oo + assert l4.slope == 0 + assert Line(p1, Point(0, 1)).slope is oo + assert Line(r1.source, r1.random_point()).slope == r1.slope + assert Line(r2.source, r2.random_point()).slope == r2.slope + assert Segment(Point(0, -1), Segment(p1, Point(0, 1)).random_point()).slope == Segment(p1, Point(0, 1)).slope + + assert l4.coefficients == (0, 1, 0) + assert Line((-x, x), (-x + 1, x - 1)).coefficients == (1, 1, 0) + assert Line(p1, Point(0, 1)).coefficients == (1, 0, 0) + # issue 7963 + r = Ray((0, 0), angle=x) + assert r.subs(x, 3 * pi / 4) == Ray((0, 0), (-1, 1)) + assert r.subs(x, 5 * pi / 4) == Ray((0, 0), (-1, -1)) + assert r.subs(x, -pi / 4) == Ray((0, 0), (1, -1)) + assert r.subs(x, pi / 2) == Ray((0, 0), (0, 1)) + assert r.subs(x, -pi / 2) == Ray((0, 0), (0, -1)) + + for ind in range(0, 5): + assert l3.random_point() in l3 + + assert p_r3.x >= p1.x and p_r3.y >= p1.y + assert p_r4.x <= p2.x and p_r4.y <= p2.y + assert p1.x <= p_s1.x <= p10.x and p1.y <= p_s1.y <= p10.y + assert hash(s1) != hash(Segment(p10, p1)) + + assert s1.plot_interval() == [t, 0, 1] + assert Line(p1, p10).plot_interval() == [t, -5, 5] + assert Ray((0, 0), angle=pi / 4).plot_interval() == [t, 0, 10] + + +def test_basic_properties_3d(): + p1 = Point3D(0, 0, 0) + p2 = Point3D(1, 1, 1) + p3 = Point3D(x1, x1, x1) + p5 = Point3D(x1, 1 + x1, 1) + + l1 = Line3D(p1, p2) + l3 = Line3D(p3, p5) + + r1 = Ray3D(p1, Point3D(-1, 5, 0)) + r3 = Ray3D(p1, p2) + + s1 = Segment3D(p1, p2) + + assert Line3D((1, 1, 1), direction_ratio=[2, 3, 4]) == Line3D(Point3D(1, 1, 1), Point3D(3, 4, 5)) + assert Line3D((1, 1, 1), direction_ratio=[1, 5, 7]) == Line3D(Point3D(1, 1, 1), Point3D(2, 6, 8)) + assert Line3D((1, 1, 1), direction_ratio=[1, 2, 3]) == Line3D(Point3D(1, 1, 1), Point3D(2, 3, 4)) + assert Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).direction_cosine == [1, 0, 0] + assert Line3D(Line3D(p1, Point3D(0, 1, 0))) == Line3D(p1, Point3D(0, 1, 0)) + assert Ray3D(Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0))) == Ray3D(p1, Point3D(1, 0, 0)) + assert Line3D(p1, p2) != Line3D(p2, p1) + assert l1 != l3 + assert l1 != Line3D(p3, Point3D(y1, y1, y1)) + assert r3 != r1 + assert Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 1)) in Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)) + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)) in Ray3D(Point3D(0, 0, 0), Point3D(1, 1, 1)) + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)).xdirection == S.Infinity + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)).ydirection == S.Infinity + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 2)).zdirection == S.Infinity + assert Ray3D(Point3D(0, 0, 0), Point3D(-2, 2, 2)).xdirection == S.NegativeInfinity + assert Ray3D(Point3D(0, 0, 0), Point3D(2, -2, 2)).ydirection == S.NegativeInfinity + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, -2)).zdirection == S.NegativeInfinity + assert Ray3D(Point3D(0, 0, 0), Point3D(0, 2, 2)).xdirection == S.Zero + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 0, 2)).ydirection == S.Zero + assert Ray3D(Point3D(0, 0, 0), Point3D(2, 2, 0)).zdirection == S.Zero + assert p1 in l1 + assert p1 not in l3 + + assert l1.direction_ratio == [1, 1, 1] + + assert s1.midpoint == Point3D(S.Half, S.Half, S.Half) + # Test zdirection + assert Ray3D(p1, Point3D(0, 0, -1)).zdirection is S.NegativeInfinity + + +def test_contains(): + p1 = Point(0, 0) + + r = Ray(p1, Point(4, 4)) + r1 = Ray3D(p1, Point3D(0, 0, -1)) + r2 = Ray3D(p1, Point3D(0, 1, 0)) + r3 = Ray3D(p1, Point3D(0, 0, 1)) + + l = Line(Point(0, 1), Point(3, 4)) + # Segment contains + assert Point(0, (a + b) / 2) in Segment((0, a), (0, b)) + assert Point((a + b) / 2, 0) in Segment((a, 0), (b, 0)) + assert Point3D(0, 1, 0) in Segment3D((0, 1, 0), (0, 1, 0)) + assert Point3D(1, 0, 0) in Segment3D((1, 0, 0), (1, 0, 0)) + assert Segment3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).contains([]) is True + assert Segment3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).contains( + Segment3D(Point3D(2, 2, 2), Point3D(3, 2, 2))) is False + # Line contains + assert l.contains(Point(0, 1)) is True + assert l.contains((0, 1)) is True + assert l.contains((0, 0)) is False + # Ray contains + assert r.contains(p1) is True + assert r.contains((1, 1)) is True + assert r.contains((1, 3)) is False + assert r.contains(Segment((1, 1), (2, 2))) is True + assert r.contains(Segment((1, 2), (2, 5))) is False + assert r.contains(Ray((2, 2), (3, 3))) is True + assert r.contains(Ray((2, 2), (3, 5))) is False + assert r1.contains(Segment3D(p1, Point3D(0, 0, -10))) is True + assert r1.contains(Segment3D(Point3D(1, 1, 1), Point3D(2, 2, 2))) is False + assert r2.contains(Point3D(0, 0, 0)) is True + assert r3.contains(Point3D(0, 0, 0)) is True + assert Ray3D(Point3D(1, 1, 1), Point3D(1, 0, 0)).contains([]) is False + assert Line3D((0, 0, 0), (x, y, z)).contains((2 * x, 2 * y, 2 * z)) + with warns(UserWarning, test_stacklevel=False): + assert Line3D(p1, Point3D(0, 1, 0)).contains(Point(1.0, 1.0)) is False + + with warns(UserWarning, test_stacklevel=False): + assert r3.contains(Point(1.0, 1.0)) is False + + +def test_contains_nonreal_symbols(): + u, v, w, z = symbols('u, v, w, z') + l = Segment(Point(u, w), Point(v, z)) + p = Point(u*Rational(2, 3) + v/3, w*Rational(2, 3) + z/3) + assert l.contains(p) + + +def test_distance_2d(): + p1 = Point(0, 0) + p2 = Point(1, 1) + half = S.Half + + s1 = Segment(Point(0, 0), Point(1, 1)) + s2 = Segment(Point(half, half), Point(1, 0)) + + r = Ray(p1, p2) + + assert s1.distance(Point(0, 0)) == 0 + assert s1.distance((0, 0)) == 0 + assert s2.distance(Point(0, 0)) == 2 ** half / 2 + assert s2.distance(Point(Rational(3) / 2, Rational(3) / 2)) == 2 ** half + assert Line(p1, p2).distance(Point(-1, 1)) == sqrt(2) + assert Line(p1, p2).distance(Point(1, -1)) == sqrt(2) + assert Line(p1, p2).distance(Point(2, 2)) == 0 + assert Line(p1, p2).distance((-1, 1)) == sqrt(2) + assert Line((0, 0), (0, 1)).distance(p1) == 0 + assert Line((0, 0), (0, 1)).distance(p2) == 1 + assert Line((0, 0), (1, 0)).distance(p1) == 0 + assert Line((0, 0), (1, 0)).distance(p2) == 1 + assert r.distance(Point(-1, -1)) == sqrt(2) + assert r.distance(Point(1, 1)) == 0 + assert r.distance(Point(-1, 1)) == sqrt(2) + assert Ray((1, 1), (2, 2)).distance(Point(1.5, 3)) == 3 * sqrt(2) / 4 + assert r.distance((1, 1)) == 0 + + +def test_dimension_normalization(): + with warns(UserWarning, test_stacklevel=False): + assert Ray((1, 1), (2, 1, 2)) == Ray((1, 1, 0), (2, 1, 2)) + + +def test_distance_3d(): + p1, p2 = Point3D(0, 0, 0), Point3D(1, 1, 1) + p3 = Point3D(Rational(3) / 2, Rational(3) / 2, Rational(3) / 2) + + s1 = Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1)) + s2 = Segment3D(Point3D(S.Half, S.Half, S.Half), Point3D(1, 0, 1)) + + r = Ray3D(p1, p2) + + assert s1.distance(p1) == 0 + assert s2.distance(p1) == sqrt(3) / 2 + assert s2.distance(p3) == 2 * sqrt(6) / 3 + assert s1.distance((0, 0, 0)) == 0 + assert s2.distance((0, 0, 0)) == sqrt(3) / 2 + assert s1.distance(p1) == 0 + assert s2.distance(p1) == sqrt(3) / 2 + assert s2.distance(p3) == 2 * sqrt(6) / 3 + assert s1.distance((0, 0, 0)) == 0 + assert s2.distance((0, 0, 0)) == sqrt(3) / 2 + # Line to point + assert Line3D(p1, p2).distance(Point3D(-1, 1, 1)) == 2 * sqrt(6) / 3 + assert Line3D(p1, p2).distance(Point3D(1, -1, 1)) == 2 * sqrt(6) / 3 + assert Line3D(p1, p2).distance(Point3D(2, 2, 2)) == 0 + assert Line3D(p1, p2).distance((2, 2, 2)) == 0 + assert Line3D(p1, p2).distance((1, -1, 1)) == 2 * sqrt(6) / 3 + assert Line3D((0, 0, 0), (0, 1, 0)).distance(p1) == 0 + assert Line3D((0, 0, 0), (0, 1, 0)).distance(p2) == sqrt(2) + assert Line3D((0, 0, 0), (1, 0, 0)).distance(p1) == 0 + assert Line3D((0, 0, 0), (1, 0, 0)).distance(p2) == sqrt(2) + # Line to line + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((0, 0, 0), (0, 1, 2))) == 0 + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((0, 0, 0), (1, 0, 0))) == 0 + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((10, 0, 0), (10, 1, 2))) == 0 + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Line3D((0, 1, 0), (0, 1, 1))) == 1 + # Line to plane + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Plane((2, 0, 0), (0, 0, 1))) == 0 + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Plane((0, 1, 0), (0, 1, 0))) == 1 + assert Line3D((0, 0, 0), (1, 0, 0)).distance(Plane((1, 1, 3), (1, 0, 0))) == 0 + # Ray to point + assert r.distance(Point3D(-1, -1, -1)) == sqrt(3) + assert r.distance(Point3D(1, 1, 1)) == 0 + assert r.distance((-1, -1, -1)) == sqrt(3) + assert r.distance((1, 1, 1)) == 0 + assert Ray3D((0, 0, 0), (1, 1, 2)).distance((-1, -1, 2)) == 4 * sqrt(3) / 3 + assert Ray3D((1, 1, 1), (2, 2, 2)).distance(Point3D(1.5, -3, -1)) == Rational(9) / 2 + assert Ray3D((1, 1, 1), (2, 2, 2)).distance(Point3D(1.5, 3, 1)) == sqrt(78) / 6 + + +def test_equals(): + p1 = Point(0, 0) + p2 = Point(1, 1) + + l1 = Line(p1, p2) + l2 = Line((0, 5), slope=m) + l3 = Line(Point(x1, x1), Point(x1, 1 + x1)) + + assert l1.perpendicular_line(p1.args).equals(Line(Point(0, 0), Point(1, -1))) + assert l1.perpendicular_line(p1).equals(Line(Point(0, 0), Point(1, -1))) + assert Line(Point(x1, x1), Point(y1, y1)).parallel_line(Point(-x1, x1)). \ + equals(Line(Point(-x1, x1), Point(-y1, 2 * x1 - y1))) + assert l3.parallel_line(p1.args).equals(Line(Point(0, 0), Point(0, -1))) + assert l3.parallel_line(p1).equals(Line(Point(0, 0), Point(0, -1))) + assert (l2.distance(Point(2, 3)) - 2 * abs(m + 1) / sqrt(m ** 2 + 1)).equals(0) + assert Line3D(p1, Point3D(0, 1, 0)).equals(Point(1.0, 1.0)) is False + assert Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).equals(Line3D(Point3D(-5, 0, 0), Point3D(-1, 0, 0))) is True + assert Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)).equals(Line3D(p1, Point3D(0, 1, 0))) is False + assert Ray3D(p1, Point3D(0, 0, -1)).equals(Point(1.0, 1.0)) is False + assert Ray3D(p1, Point3D(0, 0, -1)).equals(Ray3D(p1, Point3D(0, 0, -1))) is True + assert Line3D((0, 0), (t, t)).perpendicular_line(Point(0, 1, 0)).equals( + Line3D(Point3D(0, 1, 0), Point3D(S.Half, S.Half, 0))) + assert Line3D((0, 0), (t, t)).perpendicular_segment(Point(0, 1, 0)).equals(Segment3D((0, 1), (S.Half, S.Half))) + assert Line3D(p1, Point3D(0, 1, 0)).equals(Point(1.0, 1.0)) is False + + +def test_equation(): + p1 = Point(0, 0) + p2 = Point(1, 1) + l1 = Line(p1, p2) + l3 = Line(Point(x1, x1), Point(x1, 1 + x1)) + + assert simplify(l1.equation()) in (x - y, y - x) + assert simplify(l3.equation()) in (x - x1, x1 - x) + assert simplify(l1.equation()) in (x - y, y - x) + assert simplify(l3.equation()) in (x - x1, x1 - x) + + assert Line(p1, Point(1, 0)).equation(x=x, y=y) == y + assert Line(p1, Point(0, 1)).equation() == x + assert Line(Point(2, 0), Point(2, 1)).equation() == x - 2 + assert Line(p2, Point(2, 1)).equation() == y - 1 + + assert Line3D(Point(x1, x1, x1), Point(y1, y1, y1) + ).equation() == (-x + y, -x + z) + assert Line3D(Point(1, 2, 3), Point(2, 3, 4) + ).equation() == (-x + y - 1, -x + z - 2) + assert Line3D(Point(1, 2, 3), Point(1, 3, 4) + ).equation() == (x - 1, -y + z - 1) + assert Line3D(Point(1, 2, 3), Point(2, 2, 4) + ).equation() == (y - 2, -x + z - 2) + assert Line3D(Point(1, 2, 3), Point(2, 3, 3) + ).equation() == (-x + y - 1, z - 3) + assert Line3D(Point(1, 2, 3), Point(1, 2, 4) + ).equation() == (x - 1, y - 2) + assert Line3D(Point(1, 2, 3), Point(1, 3, 3) + ).equation() == (x - 1, z - 3) + assert Line3D(Point(1, 2, 3), Point(2, 2, 3) + ).equation() == (y - 2, z - 3) + + +def test_intersection_2d(): + p1 = Point(0, 0) + p2 = Point(1, 1) + p3 = Point(x1, x1) + p4 = Point(y1, y1) + + l1 = Line(p1, p2) + l3 = Line(Point(0, 0), Point(3, 4)) + + r1 = Ray(Point(1, 1), Point(2, 2)) + r2 = Ray(Point(0, 0), Point(3, 4)) + r4 = Ray(p1, p2) + r6 = Ray(Point(0, 1), Point(1, 2)) + r7 = Ray(Point(0.5, 0.5), Point(1, 1)) + + s1 = Segment(p1, p2) + s2 = Segment(Point(0.25, 0.25), Point(0.5, 0.5)) + s3 = Segment(Point(0, 0), Point(3, 4)) + + assert intersection(l1, p1) == [p1] + assert intersection(l1, Point(x1, 1 + x1)) == [] + assert intersection(l1, Line(p3, p4)) in [[l1], [Line(p3, p4)]] + assert intersection(l1, l1.parallel_line(Point(x1, 1 + x1))) == [] + assert intersection(l3, l3) == [l3] + assert intersection(l3, r2) == [r2] + assert intersection(l3, s3) == [s3] + assert intersection(s3, l3) == [s3] + assert intersection(Segment(Point(-10, 10), Point(10, 10)), Segment(Point(-5, -5), Point(-5, 5))) == [] + assert intersection(r2, l3) == [r2] + assert intersection(r1, Ray(Point(2, 2), Point(0, 0))) == [Segment(Point(1, 1), Point(2, 2))] + assert intersection(r1, Ray(Point(1, 1), Point(-1, -1))) == [Point(1, 1)] + assert intersection(r1, Segment(Point(0, 0), Point(2, 2))) == [Segment(Point(1, 1), Point(2, 2))] + + assert r4.intersection(s2) == [s2] + assert r4.intersection(Segment(Point(2, 3), Point(3, 4))) == [] + assert r4.intersection(Segment(Point(-1, -1), Point(0.5, 0.5))) == [Segment(p1, Point(0.5, 0.5))] + assert r4.intersection(Ray(p2, p1)) == [s1] + assert Ray(p2, p1).intersection(r6) == [] + assert r4.intersection(r7) == r7.intersection(r4) == [r7] + assert Ray3D((0, 0), (3, 0)).intersection(Ray3D((1, 0), (3, 0))) == [Ray3D((1, 0), (3, 0))] + assert Ray3D((1, 0), (3, 0)).intersection(Ray3D((0, 0), (3, 0))) == [Ray3D((1, 0), (3, 0))] + assert Ray(Point(0, 0), Point(0, 4)).intersection(Ray(Point(0, 1), Point(0, -1))) == \ + [Segment(Point(0, 0), Point(0, 1))] + + assert Segment3D((0, 0), (3, 0)).intersection( + Segment3D((1, 0), (2, 0))) == [Segment3D((1, 0), (2, 0))] + assert Segment3D((1, 0), (2, 0)).intersection( + Segment3D((0, 0), (3, 0))) == [Segment3D((1, 0), (2, 0))] + assert Segment3D((0, 0), (3, 0)).intersection( + Segment3D((3, 0), (4, 0))) == [Point3D((3, 0))] + assert Segment3D((0, 0), (3, 0)).intersection( + Segment3D((2, 0), (5, 0))) == [Segment3D((2, 0), (3, 0))] + assert Segment3D((0, 0), (3, 0)).intersection( + Segment3D((-2, 0), (1, 0))) == [Segment3D((0, 0), (1, 0))] + assert Segment3D((0, 0), (3, 0)).intersection( + Segment3D((-2, 0), (0, 0))) == [Point3D(0, 0)] + assert s1.intersection(Segment(Point(1, 1), Point(2, 2))) == [Point(1, 1)] + assert s1.intersection(Segment(Point(0.5, 0.5), Point(1.5, 1.5))) == [Segment(Point(0.5, 0.5), p2)] + assert s1.intersection(Segment(Point(4, 4), Point(5, 5))) == [] + assert s1.intersection(Segment(Point(-1, -1), p1)) == [p1] + assert s1.intersection(Segment(Point(-1, -1), Point(0.5, 0.5))) == [Segment(p1, Point(0.5, 0.5))] + assert s1.intersection(Line(Point(1, 0), Point(2, 1))) == [] + assert s1.intersection(s2) == [s2] + assert s2.intersection(s1) == [s2] + + assert asa(120, 8, 52) == \ + Triangle( + Point(0, 0), + Point(8, 0), + Point(-4 * cos(19 * pi / 90) / sin(2 * pi / 45), + 4 * sqrt(3) * cos(19 * pi / 90) / sin(2 * pi / 45))) + assert Line((0, 0), (1, 1)).intersection(Ray((1, 0), (1, 2))) == [Point(1, 1)] + assert Line((0, 0), (1, 1)).intersection(Segment((1, 0), (1, 2))) == [Point(1, 1)] + assert Ray((0, 0), (1, 1)).intersection(Ray((1, 0), (1, 2))) == [Point(1, 1)] + assert Ray((0, 0), (1, 1)).intersection(Segment((1, 0), (1, 2))) == [Point(1, 1)] + assert Ray((0, 0), (10, 10)).contains(Segment((1, 1), (2, 2))) is True + assert Segment((1, 1), (2, 2)) in Line((0, 0), (10, 10)) + assert s1.intersection(Ray((1, 1), (4, 4))) == [Point(1, 1)] + + # This test is disabled because it hangs after rref changes which simplify + # intermediate results and return a different representation from when the + # test was written. + # # 16628 - this should be fast + # p0 = Point2D(Rational(249, 5), Rational(497999, 10000)) + # p1 = Point2D((-58977084786*sqrt(405639795226) + 2030690077184193 + + # 20112207807*sqrt(630547164901) + 99600*sqrt(255775022850776494562626)) + # /(2000*sqrt(255775022850776494562626) + 1991998000*sqrt(405639795226) + # + 1991998000*sqrt(630547164901) + 1622561172902000), + # (-498000*sqrt(255775022850776494562626) - 995999*sqrt(630547164901) + + # 90004251917891999 + + # 496005510002*sqrt(405639795226))/(10000*sqrt(255775022850776494562626) + # + 9959990000*sqrt(405639795226) + 9959990000*sqrt(630547164901) + + # 8112805864510000)) + # p2 = Point2D(Rational(497, 10), Rational(-497, 10)) + # p3 = Point2D(Rational(-497, 10), Rational(-497, 10)) + # l = Line(p0, p1) + # s = Segment(p2, p3) + # n = (-52673223862*sqrt(405639795226) - 15764156209307469 - + # 9803028531*sqrt(630547164901) + + # 33200*sqrt(255775022850776494562626)) + # d = sqrt(405639795226) + 315274080450 + 498000*sqrt( + # 630547164901) + sqrt(255775022850776494562626) + # assert intersection(l, s) == [ + # Point2D(n/d*Rational(3, 2000), Rational(-497, 10))] + + +def test_line_intersection(): + # see also test_issue_11238 in test_matrices.py + x0 = tan(pi*Rational(13, 45)) + x1 = sqrt(3) + x2 = x0**2 + x, y = [8*x0/(x0 + x1), (24*x0 - 8*x1*x2)/(x2 - 3)] + assert Line(Point(0, 0), Point(1, -sqrt(3))).contains(Point(x, y)) is True + + +def test_intersection_3d(): + p1 = Point3D(0, 0, 0) + p2 = Point3D(1, 1, 1) + + l1 = Line3D(p1, p2) + l2 = Line3D(Point3D(0, 0, 0), Point3D(3, 4, 0)) + + r1 = Ray3D(Point3D(1, 1, 1), Point3D(2, 2, 2)) + r2 = Ray3D(Point3D(0, 0, 0), Point3D(3, 4, 0)) + + s1 = Segment3D(Point3D(0, 0, 0), Point3D(3, 4, 0)) + + assert intersection(l1, p1) == [p1] + assert intersection(l1, Point3D(x1, 1 + x1, 1)) == [] + assert intersection(l1, l1.parallel_line(p1)) == [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1))] + assert intersection(l2, r2) == [r2] + assert intersection(l2, s1) == [s1] + assert intersection(r2, l2) == [r2] + assert intersection(r1, Ray3D(Point3D(1, 1, 1), Point3D(-1, -1, -1))) == [Point3D(1, 1, 1)] + assert intersection(r1, Segment3D(Point3D(0, 0, 0), Point3D(2, 2, 2))) == [ + Segment3D(Point3D(1, 1, 1), Point3D(2, 2, 2))] + assert intersection(Ray3D(Point3D(1, 0, 0), Point3D(-1, 0, 0)), Ray3D(Point3D(0, 1, 0), Point3D(0, -1, 0))) \ + == [Point3D(0, 0, 0)] + assert intersection(r1, Ray3D(Point3D(2, 2, 2), Point3D(0, 0, 0))) == \ + [Segment3D(Point3D(1, 1, 1), Point3D(2, 2, 2))] + assert intersection(s1, r2) == [s1] + + assert Line3D(Point3D(4, 0, 1), Point3D(0, 4, 1)).intersection(Line3D(Point3D(0, 0, 1), Point3D(4, 4, 1))) == \ + [Point3D(2, 2, 1)] + assert Line3D((0, 1, 2), (0, 2, 3)).intersection(Line3D((0, 1, 2), (0, 1, 1))) == [Point3D(0, 1, 2)] + assert Line3D((0, 0), (t, t)).intersection(Line3D((0, 1), (t, t))) == \ + [Point3D(t, t)] + + assert Ray3D(Point3D(0, 0, 0), Point3D(0, 4, 0)).intersection(Ray3D(Point3D(0, 1, 1), Point3D(0, -1, 1))) == [] + + +def test_is_parallel(): + p1 = Point3D(0, 0, 0) + p2 = Point3D(1, 1, 1) + p3 = Point3D(x1, x1, x1) + + l2 = Line(Point(x1, x1), Point(y1, y1)) + l2_1 = Line(Point(x1, x1), Point(x1, 1 + x1)) + + assert Line.is_parallel(Line(Point(0, 0), Point(1, 1)), l2) + assert Line.is_parallel(l2, Line(Point(x1, x1), Point(x1, 1 + x1))) is False + assert Line.is_parallel(l2, l2.parallel_line(Point(-x1, x1))) + assert Line.is_parallel(l2_1, l2_1.parallel_line(Point(0, 0))) + assert Line3D(p1, p2).is_parallel(Line3D(p1, p2)) # same as in 2D + assert Line3D(Point3D(4, 0, 1), Point3D(0, 4, 1)).is_parallel(Line3D(Point3D(0, 0, 1), Point3D(4, 4, 1))) is False + assert Line3D(p1, p2).parallel_line(p3) == Line3D(Point3D(x1, x1, x1), + Point3D(x1 + 1, x1 + 1, x1 + 1)) + assert Line3D(p1, p2).parallel_line(p3.args) == \ + Line3D(Point3D(x1, x1, x1), Point3D(x1 + 1, x1 + 1, x1 + 1)) + assert Line3D(Point3D(4, 0, 1), Point3D(0, 4, 1)).is_parallel(Line3D(Point3D(0, 0, 1), Point3D(4, 4, 1))) is False + + +def test_is_perpendicular(): + p1 = Point(0, 0) + p2 = Point(1, 1) + + l1 = Line(p1, p2) + l2 = Line(Point(x1, x1), Point(y1, y1)) + l1_1 = Line(p1, Point(-x1, x1)) + # 2D + assert Line.is_perpendicular(l1, l1_1) + assert Line.is_perpendicular(l1, l2) is False + p = l1.random_point() + assert l1.perpendicular_segment(p) == p + # 3D + assert Line3D.is_perpendicular(Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)), + Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))) is True + assert Line3D.is_perpendicular(Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)), + Line3D(Point3D(0, 1, 0), Point3D(1, 1, 0))) is False + assert Line3D.is_perpendicular(Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1)), + Line3D(Point3D(x1, x1, x1), Point3D(y1, y1, y1))) is False + + +def test_is_similar(): + p1 = Point(2000, 2000) + p2 = p1.scale(2, 2) + + r1 = Ray3D(Point3D(1, 1, 1), Point3D(1, 0, 0)) + r2 = Ray(Point(0, 0), Point(0, 1)) + + s1 = Segment(Point(0, 0), p1) + + assert s1.is_similar(Segment(p1, p2)) + assert s1.is_similar(r2) is False + assert r1.is_similar(Line3D(Point3D(1, 1, 1), Point3D(1, 0, 0))) is True + assert r1.is_similar(Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0))) is False + + +def test_length(): + s2 = Segment3D(Point3D(x1, x1, x1), Point3D(y1, y1, y1)) + assert Line(Point(0, 0), Point(1, 1)).length is oo + assert s2.length == sqrt(3) * sqrt((x1 - y1) ** 2) + assert Line3D(Point3D(0, 0, 0), Point3D(1, 1, 1)).length is oo + + +def test_projection(): + p1 = Point(0, 0) + p2 = Point3D(0, 0, 0) + p3 = Point(-x1, x1) + + l1 = Line(p1, Point(1, 1)) + l2 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)) + l3 = Line3D(p2, Point3D(1, 1, 1)) + + r1 = Ray(Point(1, 1), Point(2, 2)) + + s1 = Segment(Point2D(0, 0), Point2D(0, 1)) + s2 = Segment(Point2D(1, 0), Point2D(2, 1/2)) + + assert Line(Point(x1, x1), Point(y1, y1)).projection(Point(y1, y1)) == Point(y1, y1) + assert Line(Point(x1, x1), Point(x1, 1 + x1)).projection(Point(1, 1)) == Point(x1, 1) + assert Segment(Point(-2, 2), Point(0, 4)).projection(r1) == Segment(Point(-1, 3), Point(0, 4)) + assert Segment(Point(0, 4), Point(-2, 2)).projection(r1) == Segment(Point(0, 4), Point(-1, 3)) + assert s2.projection(s1) == EmptySet + assert l1.projection(p3) == p1 + assert l1.projection(Ray(p1, Point(-1, 5))) == Ray(Point(0, 0), Point(2, 2)) + assert l1.projection(Ray(p1, Point(-1, 1))) == p1 + assert r1.projection(Ray(Point(1, 1), Point(-1, -1))) == Point(1, 1) + assert r1.projection(Ray(Point(0, 4), Point(-1, -5))) == Segment(Point(1, 1), Point(2, 2)) + assert r1.projection(Segment(Point(-1, 5), Point(-5, -10))) == Segment(Point(1, 1), Point(2, 2)) + assert r1.projection(Ray(Point(1, 1), Point(-1, -1))) == Point(1, 1) + assert r1.projection(Ray(Point(0, 4), Point(-1, -5))) == Segment(Point(1, 1), Point(2, 2)) + assert r1.projection(Segment(Point(-1, 5), Point(-5, -10))) == Segment(Point(1, 1), Point(2, 2)) + + assert l3.projection(Ray3D(p2, Point3D(-1, 5, 0))) == Ray3D(Point3D(0, 0, 0), Point3D(Rational(4, 3), Rational(4, 3), Rational(4, 3))) + assert l3.projection(Ray3D(p2, Point3D(-1, 1, 1))) == Ray3D(Point3D(0, 0, 0), Point3D(Rational(1, 3), Rational(1, 3), Rational(1, 3))) + assert l2.projection(Point3D(5, 5, 0)) == Point3D(5, 0) + assert l2.projection(Line3D(Point3D(0, 1, 0), Point3D(1, 1, 0))).equals(l2) + + +def test_perpendicular_line(): + # 3d - requires a particular orthogonal to be selected + p1, p2, p3 = Point(0, 0, 0), Point(2, 3, 4), Point(-2, 2, 0) + l = Line(p1, p2) + p = l.perpendicular_line(p3) + assert p.p1 == p3 + assert p.p2 in l + # 2d - does not require special selection + p1, p2, p3 = Point(0, 0), Point(2, 3), Point(-2, 2) + l = Line(p1, p2) + p = l.perpendicular_line(p3) + assert p.p1 == p3 + # p is directed from l to p3 + assert p.direction.unit == (p3 - l.projection(p3)).unit + + +def test_perpendicular_bisector(): + s1 = Segment(Point(0, 0), Point(1, 1)) + aline = Line(Point(S.Half, S.Half), Point(Rational(3, 2), Rational(-1, 2))) + on_line = Segment(Point(S.Half, S.Half), Point(Rational(3, 2), Rational(-1, 2))).midpoint + + assert s1.perpendicular_bisector().equals(aline) + assert s1.perpendicular_bisector(on_line).equals(Segment(s1.midpoint, on_line)) + assert s1.perpendicular_bisector(on_line + (1, 0)).equals(aline) + + +def test_raises(): + d, e = symbols('a,b', real=True) + s = Segment((d, 0), (e, 0)) + + raises(TypeError, lambda: Line((1, 1), 1)) + raises(ValueError, lambda: Line(Point(0, 0), Point(0, 0))) + raises(Undecidable, lambda: Point(2 * d, 0) in s) + raises(ValueError, lambda: Ray3D(Point(1.0, 1.0))) + raises(ValueError, lambda: Line3D(Point3D(0, 0, 0), Point3D(0, 0, 0))) + raises(TypeError, lambda: Line3D((1, 1), 1)) + raises(ValueError, lambda: Line3D(Point3D(0, 0, 0))) + raises(TypeError, lambda: Ray((1, 1), 1)) + raises(GeometryError, lambda: Line(Point(0, 0), Point(1, 0)) + .projection(Circle(Point(0, 0), 1))) + + +def test_ray_generation(): + assert Ray((1, 1), angle=pi / 4) == Ray((1, 1), (2, 2)) + assert Ray((1, 1), angle=pi / 2) == Ray((1, 1), (1, 2)) + assert Ray((1, 1), angle=-pi / 2) == Ray((1, 1), (1, 0)) + assert Ray((1, 1), angle=-3 * pi / 2) == Ray((1, 1), (1, 2)) + assert Ray((1, 1), angle=5 * pi / 2) == Ray((1, 1), (1, 2)) + assert Ray((1, 1), angle=5.0 * pi / 2) == Ray((1, 1), (1, 2)) + assert Ray((1, 1), angle=pi) == Ray((1, 1), (0, 1)) + assert Ray((1, 1), angle=3.0 * pi) == Ray((1, 1), (0, 1)) + assert Ray((1, 1), angle=4.0 * pi) == Ray((1, 1), (2, 1)) + assert Ray((1, 1), angle=0) == Ray((1, 1), (2, 1)) + assert Ray((1, 1), angle=4.05 * pi) == Ray(Point(1, 1), + Point(2, -sqrt(5) * sqrt(2 * sqrt(5) + 10) / 4 - sqrt( + 2 * sqrt(5) + 10) / 4 + 2 + sqrt(5))) + assert Ray((1, 1), angle=4.02 * pi) == Ray(Point(1, 1), + Point(2, 1 + tan(4.02 * pi))) + assert Ray((1, 1), angle=5) == Ray((1, 1), (2, 1 + tan(5))) + + assert Ray3D((1, 1, 1), direction_ratio=[4, 4, 4]) == Ray3D(Point3D(1, 1, 1), Point3D(5, 5, 5)) + assert Ray3D((1, 1, 1), direction_ratio=[1, 2, 3]) == Ray3D(Point3D(1, 1, 1), Point3D(2, 3, 4)) + assert Ray3D((1, 1, 1), direction_ratio=[1, 1, 1]) == Ray3D(Point3D(1, 1, 1), Point3D(2, 2, 2)) + + +def test_issue_7814(): + circle = Circle(Point(x, 0), y) + line = Line(Point(k, z), slope=0) + _s = sqrt((y - z)*(y + z)) + assert line.intersection(circle) == [Point2D(x + _s, z), Point2D(x - _s, z)] + + +def test_issue_2941(): + def _check(): + for f, g in cartes(*[(Line, Ray, Segment)] * 2): + l1 = f(a, b) + l2 = g(c, d) + assert l1.intersection(l2) == l2.intersection(l1) + # intersect at end point + c, d = (-2, -2), (-2, 0) + a, b = (0, 0), (1, 1) + _check() + # midline intersection + c, d = (-2, -3), (-2, 0) + _check() + + +def test_parameter_value(): + t = Symbol('t') + p1, p2 = Point(0, 1), Point(5, 6) + l = Line(p1, p2) + assert l.parameter_value((5, 6), t) == {t: 1} + raises(ValueError, lambda: l.parameter_value((0, 0), t)) + + +def test_bisectors(): + r1 = Line3D(Point3D(0, 0, 0), Point3D(1, 0, 0)) + r2 = Line3D(Point3D(0, 0, 0), Point3D(0, 1, 0)) + bisections = r1.bisectors(r2) + assert bisections == [Line3D(Point3D(0, 0, 0), Point3D(1, 1, 0)), + Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))] + ans = [Line3D(Point3D(0, 0, 0), Point3D(1, 0, 1)), + Line3D(Point3D(0, 0, 0), Point3D(-1, 0, 1))] + l1 = (0, 0, 0), (0, 0, 1) + l2 = (0, 0), (1, 0) + for a, b in cartes((Line, Segment, Ray), repeat=2): + assert a(*l1).bisectors(b(*l2)) == ans + + +def test_issue_8615(): + a = Line3D(Point3D(6, 5, 0), Point3D(6, -6, 0)) + b = Line3D(Point3D(6, -1, 19/10), Point3D(6, -1, 0)) + assert a.intersection(b) == [Point3D(6, -1, 0)] + + +def test_issue_12598(): + r1 = Ray(Point(0, 1), Point(0.98, 0.79).n(2)) + r2 = Ray(Point(0, 0), Point(0.71, 0.71).n(2)) + assert str(r1.intersection(r2)[0]) == 'Point2D(0.82, 0.82)' + l1 = Line((0, 0), (1, 1)) + l2 = Segment((-1, 1), (0, -1)).n(2) + assert str(l1.intersection(l2)[0]) == 'Point2D(-0.33, -0.33)' + l2 = Segment((-1, 1), (-1/2, 1/2)).n(2) + assert not l1.intersection(l2) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_parabola.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_parabola.py new file mode 100644 index 0000000000000000000000000000000000000000..2a683f26619952d93475aca9ebd3d47cfb3657a6 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_parabola.py @@ -0,0 +1,143 @@ +from sympy.core.numbers import (Rational, oo) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.geometry.ellipse import (Circle, Ellipse) +from sympy.geometry.line import (Line, Ray2D, Segment2D) +from sympy.geometry.parabola import Parabola +from sympy.geometry.point import (Point, Point2D) +from sympy.testing.pytest import raises + +from sympy.abc import x, y + +def test_parabola_geom(): + a, b = symbols('a b') + p1 = Point(0, 0) + p2 = Point(3, 7) + p3 = Point(0, 4) + p4 = Point(6, 0) + p5 = Point(a, a) + d1 = Line(Point(4, 0), Point(4, 9)) + d2 = Line(Point(7, 6), Point(3, 6)) + d3 = Line(Point(4, 0), slope=oo) + d4 = Line(Point(7, 6), slope=0) + d5 = Line(Point(b, a), slope=oo) + d6 = Line(Point(a, b), slope=0) + + half = S.Half + + pa1 = Parabola(None, d2) + pa2 = Parabola(directrix=d1) + pa3 = Parabola(p1, d1) + pa4 = Parabola(p2, d2) + pa5 = Parabola(p2, d4) + pa6 = Parabola(p3, d2) + pa7 = Parabola(p2, d1) + pa8 = Parabola(p4, d1) + pa9 = Parabola(p4, d3) + pa10 = Parabola(p5, d5) + pa11 = Parabola(p5, d6) + d = Line(Point(3, 7), Point(2, 9)) + pa12 = Parabola(Point(7, 8), d) + pa12r = Parabola(Point(7, 8).reflect(d), d) + + raises(ValueError, lambda: + Parabola(Point(7, 8, 9), Line(Point(6, 7), Point(7, 7)))) + raises(ValueError, lambda: + Parabola(Point(0, 2), Line(Point(7, 2), Point(6, 2)))) + raises(ValueError, lambda: Parabola(Point(7, 8), Point(3, 8))) + + # Basic Stuff + assert pa1.focus == Point(0, 0) + assert pa1.ambient_dimension == S(2) + assert pa2 == pa3 + assert pa4 != pa7 + assert pa6 != pa7 + assert pa6.focus == Point2D(0, 4) + assert pa6.focal_length == 1 + assert pa6.p_parameter == -1 + assert pa6.vertex == Point2D(0, 5) + assert pa6.eccentricity == 1 + assert pa7.focus == Point2D(3, 7) + assert pa7.focal_length == half + assert pa7.p_parameter == -half + assert pa7.vertex == Point2D(7*half, 7) + assert pa4.focal_length == half + assert pa4.p_parameter == half + assert pa4.vertex == Point2D(3, 13*half) + assert pa8.focal_length == 1 + assert pa8.p_parameter == 1 + assert pa8.vertex == Point2D(5, 0) + assert pa4.focal_length == pa5.focal_length + assert pa4.p_parameter == pa5.p_parameter + assert pa4.vertex == pa5.vertex + assert pa4.equation() == pa5.equation() + assert pa8.focal_length == pa9.focal_length + assert pa8.p_parameter == pa9.p_parameter + assert pa8.vertex == pa9.vertex + assert pa8.equation() == pa9.equation() + assert pa10.focal_length == pa11.focal_length == sqrt((a - b) ** 2) / 2 # if a, b real == abs(a - b)/2 + assert pa11.vertex == Point(*pa10.vertex[::-1]) == Point(a, + a - sqrt((a - b)**2)*sign(a - b)/2) # change axis x->y, y->x on pa10 + aos = pa12.axis_of_symmetry + assert aos == Line(Point(7, 8), Point(5, 7)) + assert pa12.directrix == Line(Point(3, 7), Point(2, 9)) + assert pa12.directrix.angle_between(aos) == S.Pi/2 + assert pa12.eccentricity == 1 + assert pa12.equation(x, y) == (x - 7)**2 + (y - 8)**2 - (-2*x - y + 13)**2/5 + assert pa12.focal_length == 9*sqrt(5)/10 + assert pa12.focus == Point(7, 8) + assert pa12.p_parameter == 9*sqrt(5)/10 + assert pa12.vertex == Point2D(S(26)/5, S(71)/10) + assert pa12r.focal_length == 9*sqrt(5)/10 + assert pa12r.focus == Point(-S(1)/5, S(22)/5) + assert pa12r.p_parameter == -9*sqrt(5)/10 + assert pa12r.vertex == Point(S(8)/5, S(53)/10) + + +def test_parabola_intersection(): + l1 = Line(Point(1, -2), Point(-1,-2)) + l2 = Line(Point(1, 2), Point(-1,2)) + l3 = Line(Point(1, 0), Point(-1,0)) + + p1 = Point(0,0) + p2 = Point(0, -2) + p3 = Point(120, -12) + parabola1 = Parabola(p1, l1) + + # parabola with parabola + assert parabola1.intersection(parabola1) == [parabola1] + assert parabola1.intersection(Parabola(p1, l2)) == [Point2D(-2, 0), Point2D(2, 0)] + assert parabola1.intersection(Parabola(p2, l3)) == [Point2D(0, -1)] + assert parabola1.intersection(Parabola(Point(16, 0), l1)) == [Point2D(8, 15)] + assert parabola1.intersection(Parabola(Point(0, 16), l1)) == [Point2D(-6, 8), Point2D(6, 8)] + assert parabola1.intersection(Parabola(p3, l3)) == [] + # parabola with point + assert parabola1.intersection(p1) == [] + assert parabola1.intersection(Point2D(0, -1)) == [Point2D(0, -1)] + assert parabola1.intersection(Point2D(4, 3)) == [Point2D(4, 3)] + # parabola with line + assert parabola1.intersection(Line(Point2D(-7, 3), Point(12, 3))) == [Point2D(-4, 3), Point2D(4, 3)] + assert parabola1.intersection(Line(Point(-4, -1), Point(4, -1))) == [Point(0, -1)] + assert parabola1.intersection(Line(Point(2, 0), Point(0, -2))) == [Point2D(2, 0)] + raises(TypeError, lambda: parabola1.intersection(Line(Point(0, 0, 0), Point(1, 1, 1)))) + # parabola with segment + assert parabola1.intersection(Segment2D((-4, -5), (4, 3))) == [Point2D(0, -1), Point2D(4, 3)] + assert parabola1.intersection(Segment2D((0, -5), (0, 6))) == [Point2D(0, -1)] + assert parabola1.intersection(Segment2D((-12, -65), (14, -68))) == [] + # parabola with ray + assert parabola1.intersection(Ray2D((-4, -5), (4, 3))) == [Point2D(0, -1), Point2D(4, 3)] + assert parabola1.intersection(Ray2D((0, 7), (1, 14))) == [Point2D(14 + 2*sqrt(57), 105 + 14*sqrt(57))] + assert parabola1.intersection(Ray2D((0, 7), (0, 14))) == [] + # parabola with ellipse/circle + assert parabola1.intersection(Circle(p1, 2)) == [Point2D(-2, 0), Point2D(2, 0)] + assert parabola1.intersection(Circle(p2, 1)) == [Point2D(0, -1)] + assert parabola1.intersection(Ellipse(p2, 2, 1)) == [Point2D(0, -1)] + assert parabola1.intersection(Ellipse(Point(0, 19), 5, 7)) == [] + assert parabola1.intersection(Ellipse((0, 3), 12, 4)) == [ + Point2D(0, -1), + Point2D(-4*sqrt(17)/3, Rational(59, 9)), + Point2D(4*sqrt(17)/3, Rational(59, 9))] + # parabola with unsupported type + raises(TypeError, lambda: parabola1.intersection(2)) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_plane.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_plane.py new file mode 100644 index 0000000000000000000000000000000000000000..1010fce5c3bc68348eacee13f29c1d7588f17e39 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_plane.py @@ -0,0 +1,268 @@ +from sympy.core.numbers import (Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (asin, cos, sin) +from sympy.geometry import Line, Point, Ray, Segment, Point3D, Line3D, Ray3D, Segment3D, Plane, Circle +from sympy.geometry.util import are_coplanar +from sympy.testing.pytest import raises + + +def test_plane(): + x, y, z, u, v = symbols('x y z u v', real=True) + p1 = Point3D(0, 0, 0) + p2 = Point3D(1, 1, 1) + p3 = Point3D(1, 2, 3) + pl3 = Plane(p1, p2, p3) + pl4 = Plane(p1, normal_vector=(1, 1, 1)) + pl4b = Plane(p1, p2) + pl5 = Plane(p3, normal_vector=(1, 2, 3)) + pl6 = Plane(Point3D(2, 3, 7), normal_vector=(2, 2, 2)) + pl7 = Plane(Point3D(1, -5, -6), normal_vector=(1, -2, 1)) + pl8 = Plane(p1, normal_vector=(0, 0, 1)) + pl9 = Plane(p1, normal_vector=(0, 12, 0)) + pl10 = Plane(p1, normal_vector=(-2, 0, 0)) + pl11 = Plane(p2, normal_vector=(0, 0, 1)) + l1 = Line3D(Point3D(5, 0, 0), Point3D(1, -1, 1)) + l2 = Line3D(Point3D(0, -2, 0), Point3D(3, 1, 1)) + l3 = Line3D(Point3D(0, -1, 0), Point3D(5, -1, 9)) + + raises(ValueError, lambda: Plane(p1, p1, p1)) + + assert Plane(p1, p2, p3) != Plane(p1, p3, p2) + assert Plane(p1, p2, p3).is_coplanar(Plane(p1, p3, p2)) + assert Plane(p1, p2, p3).is_coplanar(p1) + assert Plane(p1, p2, p3).is_coplanar(Circle(p1, 1)) is False + assert Plane(p1, normal_vector=(0, 0, 1)).is_coplanar(Circle(p1, 1)) + + assert pl3 == Plane(Point3D(0, 0, 0), normal_vector=(1, -2, 1)) + assert pl3 != pl4 + assert pl4 == pl4b + assert pl5 == Plane(Point3D(1, 2, 3), normal_vector=(1, 2, 3)) + + assert pl5.equation(x, y, z) == x + 2*y + 3*z - 14 + assert pl3.equation(x, y, z) == x - 2*y + z + + assert pl3.p1 == p1 + assert pl4.p1 == p1 + assert pl5.p1 == p3 + + assert pl4.normal_vector == (1, 1, 1) + assert pl5.normal_vector == (1, 2, 3) + + assert p1 in pl3 + assert p1 in pl4 + assert p3 in pl5 + + assert pl3.projection(Point(0, 0)) == p1 + p = pl3.projection(Point3D(1, 1, 0)) + assert p == Point3D(Rational(7, 6), Rational(2, 3), Rational(1, 6)) + assert p in pl3 + + l = pl3.projection_line(Line(Point(0, 0), Point(1, 1))) + assert l == Line3D(Point3D(0, 0, 0), Point3D(Rational(7, 6), Rational(2, 3), Rational(1, 6))) + assert l in pl3 + # get a segment that does not intersect the plane which is also + # parallel to pl3's normal veector + t = Dummy() + r = pl3.random_point() + a = pl3.perpendicular_line(r).arbitrary_point(t) + s = Segment3D(a.subs(t, 1), a.subs(t, 2)) + assert s.p1 not in pl3 and s.p2 not in pl3 + assert pl3.projection_line(s).equals(r) + assert pl3.projection_line(Segment(Point(1, 0), Point(1, 1))) == \ + Segment3D(Point3D(Rational(5, 6), Rational(1, 3), Rational(-1, 6)), Point3D(Rational(7, 6), Rational(2, 3), Rational(1, 6))) + assert pl6.projection_line(Ray(Point(1, 0), Point(1, 1))) == \ + Ray3D(Point3D(Rational(14, 3), Rational(11, 3), Rational(11, 3)), Point3D(Rational(13, 3), Rational(13, 3), Rational(10, 3))) + assert pl3.perpendicular_line(r.args) == pl3.perpendicular_line(r) + + assert pl3.is_parallel(pl6) is False + assert pl4.is_parallel(pl6) + assert pl3.is_parallel(Line(p1, p2)) + assert pl6.is_parallel(l1) is False + + assert pl3.is_perpendicular(pl6) + assert pl4.is_perpendicular(pl7) + assert pl6.is_perpendicular(pl7) + assert pl6.is_perpendicular(pl4) is False + assert pl6.is_perpendicular(l1) is False + assert pl6.is_perpendicular(Line((0, 0, 0), (1, 1, 1))) + assert pl6.is_perpendicular((1, 1)) is False + + assert pl6.distance(pl6.arbitrary_point(u, v)) == 0 + assert pl7.distance(pl7.arbitrary_point(u, v)) == 0 + assert pl6.distance(pl6.arbitrary_point(t)) == 0 + assert pl7.distance(pl7.arbitrary_point(t)) == 0 + assert pl6.p1.distance(pl6.arbitrary_point(t)).simplify() == 1 + assert pl7.p1.distance(pl7.arbitrary_point(t)).simplify() == 1 + assert pl3.arbitrary_point(t) == Point3D(-sqrt(30)*sin(t)/30 + \ + 2*sqrt(5)*cos(t)/5, sqrt(30)*sin(t)/15 + sqrt(5)*cos(t)/5, sqrt(30)*sin(t)/6) + assert pl3.arbitrary_point(u, v) == Point3D(2*u - v, u + 2*v, 5*v) + + assert pl7.distance(Point3D(1, 3, 5)) == 5*sqrt(6)/6 + assert pl6.distance(Point3D(0, 0, 0)) == 4*sqrt(3) + assert pl6.distance(pl6.p1) == 0 + assert pl7.distance(pl6) == 0 + assert pl7.distance(l1) == 0 + assert pl6.distance(Segment3D(Point3D(2, 3, 1), Point3D(1, 3, 4))) == \ + pl6.distance(Point3D(1, 3, 4)) == 4*sqrt(3)/3 + assert pl6.distance(Segment3D(Point3D(1, 3, 4), Point3D(0, 3, 7))) == \ + pl6.distance(Point3D(0, 3, 7)) == 2*sqrt(3)/3 + assert pl6.distance(Segment3D(Point3D(0, 3, 7), Point3D(-1, 3, 10))) == 0 + assert pl6.distance(Segment3D(Point3D(-1, 3, 10), Point3D(-2, 3, 13))) == 0 + assert pl6.distance(Segment3D(Point3D(-2, 3, 13), Point3D(-3, 3, 16))) == \ + pl6.distance(Point3D(-2, 3, 13)) == 2*sqrt(3)/3 + assert pl6.distance(Plane(Point3D(5, 5, 5), normal_vector=(8, 8, 8))) == sqrt(3) + assert pl6.distance(Ray3D(Point3D(1, 3, 4), direction_ratio=[1, 0, -3])) == 4*sqrt(3)/3 + assert pl6.distance(Ray3D(Point3D(2, 3, 1), direction_ratio=[-1, 0, 3])) == 0 + + + assert pl6.angle_between(pl3) == pi/2 + assert pl6.angle_between(pl6) == 0 + assert pl6.angle_between(pl4) == 0 + assert pl7.angle_between(Line3D(Point3D(2, 3, 5), Point3D(2, 4, 6))) == \ + -asin(sqrt(3)/6) + assert pl6.angle_between(Ray3D(Point3D(2, 4, 1), Point3D(6, 5, 3))) == \ + asin(sqrt(7)/3) + assert pl7.angle_between(Segment3D(Point3D(5, 6, 1), Point3D(1, 2, 4))) == \ + asin(7*sqrt(246)/246) + + assert are_coplanar(l1, l2, l3) is False + assert are_coplanar(l1) is False + assert are_coplanar(Point3D(2, 7, 2), Point3D(0, 0, 2), + Point3D(1, 1, 2), Point3D(1, 2, 2)) + assert are_coplanar(Plane(p1, p2, p3), Plane(p1, p3, p2)) + assert Plane.are_concurrent(pl3, pl4, pl5) is False + assert Plane.are_concurrent(pl6) is False + raises(ValueError, lambda: Plane.are_concurrent(Point3D(0, 0, 0))) + raises(ValueError, lambda: Plane((1, 2, 3), normal_vector=(0, 0, 0))) + + assert pl3.parallel_plane(Point3D(1, 2, 5)) == Plane(Point3D(1, 2, 5), \ + normal_vector=(1, -2, 1)) + + # perpendicular_plane + p = Plane((0, 0, 0), (1, 0, 0)) + # default + assert p.perpendicular_plane() == Plane(Point3D(0, 0, 0), (0, 1, 0)) + # 1 pt + assert p.perpendicular_plane(Point3D(1, 0, 1)) == \ + Plane(Point3D(1, 0, 1), (0, 1, 0)) + # pts as tuples + assert p.perpendicular_plane((1, 0, 1), (1, 1, 1)) == \ + Plane(Point3D(1, 0, 1), (0, 0, -1)) + # more than two planes + raises(ValueError, lambda: p.perpendicular_plane((1, 0, 1), (1, 1, 1), (1, 1, 0))) + + a, b = Point3D(0, 0, 0), Point3D(0, 1, 0) + Z = (0, 0, 1) + p = Plane(a, normal_vector=Z) + # case 4 + assert p.perpendicular_plane(a, b) == Plane(a, (1, 0, 0)) + n = Point3D(*Z) + # case 1 + assert p.perpendicular_plane(a, n) == Plane(a, (-1, 0, 0)) + # case 2 + assert Plane(a, normal_vector=b.args).perpendicular_plane(a, a + b) == \ + Plane(Point3D(0, 0, 0), (1, 0, 0)) + # case 1&3 + assert Plane(b, normal_vector=Z).perpendicular_plane(b, b + n) == \ + Plane(Point3D(0, 1, 0), (-1, 0, 0)) + # case 2&3 + assert Plane(b, normal_vector=b.args).perpendicular_plane(n, n + b) == \ + Plane(Point3D(0, 0, 1), (1, 0, 0)) + + p = Plane(a, normal_vector=(0, 0, 1)) + assert p.perpendicular_plane() == Plane(a, normal_vector=(1, 0, 0)) + + assert pl6.intersection(pl6) == [pl6] + assert pl4.intersection(pl4.p1) == [pl4.p1] + assert pl3.intersection(pl6) == [ + Line3D(Point3D(8, 4, 0), Point3D(2, 4, 6))] + assert pl3.intersection(Line3D(Point3D(1,2,4), Point3D(4,4,2))) == [ + Point3D(2, Rational(8, 3), Rational(10, 3))] + assert pl3.intersection(Plane(Point3D(6, 0, 0), normal_vector=(2, -5, 3)) + ) == [Line3D(Point3D(-24, -12, 0), Point3D(-25, -13, -1))] + assert pl6.intersection(Ray3D(Point3D(2, 3, 1), Point3D(1, 3, 4))) == [ + Point3D(-1, 3, 10)] + assert pl6.intersection(Segment3D(Point3D(2, 3, 1), Point3D(1, 3, 4))) == [] + assert pl7.intersection(Line(Point(2, 3), Point(4, 2))) == [ + Point3D(Rational(13, 2), Rational(3, 4), 0)] + r = Ray(Point(2, 3), Point(4, 2)) + assert Plane((1,2,0), normal_vector=(0,0,1)).intersection(r) == [ + Ray3D(Point(2, 3), Point(4, 2))] + assert pl9.intersection(pl8) == [Line3D(Point3D(0, 0, 0), Point3D(12, 0, 0))] + assert pl10.intersection(pl11) == [Line3D(Point3D(0, 0, 1), Point3D(0, 2, 1))] + assert pl4.intersection(pl8) == [Line3D(Point3D(0, 0, 0), Point3D(1, -1, 0))] + assert pl11.intersection(pl8) == [] + assert pl9.intersection(pl11) == [Line3D(Point3D(0, 0, 1), Point3D(12, 0, 1))] + assert pl9.intersection(pl4) == [Line3D(Point3D(0, 0, 0), Point3D(12, 0, -12))] + assert pl3.random_point() in pl3 + assert pl3.random_point(seed=1) in pl3 + + # test geometrical entity using equals + assert pl4.intersection(pl4.p1)[0].equals(pl4.p1) + assert pl3.intersection(pl6)[0].equals(Line3D(Point3D(8, 4, 0), Point3D(2, 4, 6))) + pl8 = Plane((1, 2, 0), normal_vector=(0, 0, 1)) + assert pl8.intersection(Line3D(p1, (1, 12, 0)))[0].equals(Line((0, 0, 0), (0.1, 1.2, 0))) + assert pl8.intersection(Ray3D(p1, (1, 12, 0)))[0].equals(Ray((0, 0, 0), (1, 12, 0))) + assert pl8.intersection(Segment3D(p1, (21, 1, 0)))[0].equals(Segment3D(p1, (21, 1, 0))) + assert pl8.intersection(Plane(p1, normal_vector=(0, 0, 112)))[0].equals(pl8) + assert pl8.intersection(Plane(p1, normal_vector=(0, 12, 0)))[0].equals( + Line3D(p1, direction_ratio=(112 * pi, 0, 0))) + assert pl8.intersection(Plane(p1, normal_vector=(11, 0, 1)))[0].equals( + Line3D(p1, direction_ratio=(0, -11, 0))) + assert pl8.intersection(Plane(p1, normal_vector=(1, 0, 11)))[0].equals( + Line3D(p1, direction_ratio=(0, 11, 0))) + assert pl8.intersection(Plane(p1, normal_vector=(-1, -1, -11)))[0].equals( + Line3D(p1, direction_ratio=(1, -1, 0))) + assert pl3.random_point() in pl3 + assert len(pl8.intersection(Ray3D(Point3D(0, 2, 3), Point3D(1, 0, 3)))) == 0 + # check if two plane are equals + assert pl6.intersection(pl6)[0].equals(pl6) + assert pl8.equals(Plane(p1, normal_vector=(0, 12, 0))) is False + assert pl8.equals(pl8) + assert pl8.equals(Plane(p1, normal_vector=(0, 0, -12))) + assert pl8.equals(Plane(p1, normal_vector=(0, 0, -12*sqrt(3)))) + assert pl8.equals(p1) is False + + # issue 8570 + l2 = Line3D(Point3D(Rational(50000004459633, 5000000000000), + Rational(-891926590718643, 1000000000000000), + Rational(231800966893633, 100000000000000)), + Point3D(Rational(50000004459633, 50000000000000), + Rational(-222981647679771, 250000000000000), + Rational(231800966893633, 100000000000000))) + + p2 = Plane(Point3D(Rational(402775636372767, 100000000000000), + Rational(-97224357654973, 100000000000000), + Rational(216793600814789, 100000000000000)), + (-S('9.00000087501922'), -S('4.81170658872543e-13'), + S('0.0'))) + + assert str([i.n(2) for i in p2.intersection(l2)]) == \ + '[Point3D(4.0, -0.89, 2.3)]' + + +def test_dimension_normalization(): + A = Plane(Point3D(1, 1, 2), normal_vector=(1, 1, 1)) + b = Point(1, 1) + assert A.projection(b) == Point(Rational(5, 3), Rational(5, 3), Rational(2, 3)) + + a, b = Point(0, 0), Point3D(0, 1) + Z = (0, 0, 1) + p = Plane(a, normal_vector=Z) + assert p.perpendicular_plane(a, b) == Plane(Point3D(0, 0, 0), (1, 0, 0)) + assert Plane((1, 2, 1), (2, 1, 0), (3, 1, 2) + ).intersection((2, 1)) == [Point(2, 1, 0)] + + +def test_parameter_value(): + t, u, v = symbols("t, u v") + p1, p2, p3 = Point(0, 0, 0), Point(0, 0, 1), Point(0, 1, 0) + p = Plane(p1, p2, p3) + assert p.parameter_value((0, -3, 2), t) == {t: asin(2*sqrt(13)/13)} + assert p.parameter_value((0, -3, 2), u, v) == {u: 3, v: 2} + assert p.parameter_value(p1, t) == p1 + raises(ValueError, lambda: p.parameter_value((1, 0, 0), t)) + raises(ValueError, lambda: p.parameter_value(Line(Point(0, 0), Point(1, 1)), t)) + raises(ValueError, lambda: p.parameter_value((0, -3, 2), t, 1)) diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_point.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_point.py new file mode 100644 index 0000000000000000000000000000000000000000..1f2b2768eb3fba2009f702351de1aac3ed6e71d4 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_point.py @@ -0,0 +1,481 @@ +from sympy.core.basic import Basic +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.parameters import evaluate +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.geometry import Line, Point, Point2D, Point3D, Line3D, Plane +from sympy.geometry.entity import rotate, scale, translate, GeometryEntity +from sympy.matrices import Matrix +from sympy.utilities.iterables import subsets, permutations, cartes +from sympy.utilities.misc import Undecidable +from sympy.testing.pytest import raises, warns + + +def test_point(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + x1 = Symbol('x1', real=True) + x2 = Symbol('x2', real=True) + y1 = Symbol('y1', real=True) + y2 = Symbol('y2', real=True) + half = S.Half + p1 = Point(x1, x2) + p2 = Point(y1, y2) + p3 = Point(0, 0) + p4 = Point(1, 1) + p5 = Point(0, 1) + line = Line(Point(1, 0), slope=1) + + assert p1 in p1 + assert p1 not in p2 + assert p2.y == y2 + assert (p3 + p4) == p4 + assert (p2 - p1) == Point(y1 - x1, y2 - x2) + assert -p2 == Point(-y1, -y2) + raises(TypeError, lambda: Point(1)) + raises(ValueError, lambda: Point([1])) + raises(ValueError, lambda: Point(3, I)) + raises(ValueError, lambda: Point(2*I, I)) + raises(ValueError, lambda: Point(3 + I, I)) + + assert Point(34.05, sqrt(3)) == Point(Rational(681, 20), sqrt(3)) + assert Point.midpoint(p3, p4) == Point(half, half) + assert Point.midpoint(p1, p4) == Point(half + half*x1, half + half*x2) + assert Point.midpoint(p2, p2) == p2 + assert p2.midpoint(p2) == p2 + assert p1.origin == Point(0, 0) + + assert Point.distance(p3, p4) == sqrt(2) + assert Point.distance(p1, p1) == 0 + assert Point.distance(p3, p2) == sqrt(p2.x**2 + p2.y**2) + raises(TypeError, lambda: Point.distance(p1, 0)) + raises(TypeError, lambda: Point.distance(p1, GeometryEntity())) + + # distance should be symmetric + assert p1.distance(line) == line.distance(p1) + assert p4.distance(line) == line.distance(p4) + + assert Point.taxicab_distance(p4, p3) == 2 + + assert Point.canberra_distance(p4, p5) == 1 + raises(ValueError, lambda: Point.canberra_distance(p3, p3)) + + p1_1 = Point(x1, x1) + p1_2 = Point(y2, y2) + p1_3 = Point(x1 + 1, x1) + assert Point.is_collinear(p3) + + with warns(UserWarning, test_stacklevel=False): + assert Point.is_collinear(p3, Point(p3, dim=4)) + assert p3.is_collinear() + assert Point.is_collinear(p3, p4) + assert Point.is_collinear(p3, p4, p1_1, p1_2) + assert Point.is_collinear(p3, p4, p1_1, p1_3) is False + assert Point.is_collinear(p3, p3, p4, p5) is False + + raises(TypeError, lambda: Point.is_collinear(line)) + raises(TypeError, lambda: p1_1.is_collinear(line)) + + assert p3.intersection(Point(0, 0)) == [p3] + assert p3.intersection(p4) == [] + assert p3.intersection(line) == [] + with warns(UserWarning, test_stacklevel=False): + assert Point.intersection(Point(0, 0, 0), Point(0, 0)) == [Point(0, 0, 0)] + + x_pos = Symbol('x', positive=True) + p2_1 = Point(x_pos, 0) + p2_2 = Point(0, x_pos) + p2_3 = Point(-x_pos, 0) + p2_4 = Point(0, -x_pos) + p2_5 = Point(x_pos, 5) + assert Point.is_concyclic(p2_1) + assert Point.is_concyclic(p2_1, p2_2) + assert Point.is_concyclic(p2_1, p2_2, p2_3, p2_4) + for pts in permutations((p2_1, p2_2, p2_3, p2_5)): + assert Point.is_concyclic(*pts) is False + assert Point.is_concyclic(p4, p4 * 2, p4 * 3) is False + assert Point(0, 0).is_concyclic((1, 1), (2, 2), (2, 1)) is False + assert Point.is_concyclic(Point(0, 0, 0, 0), Point(1, 0, 0, 0), Point(1, 1, 0, 0), Point(1, 1, 1, 0)) is False + + assert p1.is_scalar_multiple(p1) + assert p1.is_scalar_multiple(2*p1) + assert not p1.is_scalar_multiple(p2) + assert Point.is_scalar_multiple(Point(1, 1), (-1, -1)) + assert Point.is_scalar_multiple(Point(0, 0), (0, -1)) + # test when is_scalar_multiple can't be determined + raises(Undecidable, lambda: Point.is_scalar_multiple(Point(sympify("x1%y1"), sympify("x2%y2")), Point(0, 1))) + + assert Point(0, 1).orthogonal_direction == Point(1, 0) + assert Point(1, 0).orthogonal_direction == Point(0, 1) + + assert p1.is_zero is None + assert p3.is_zero + assert p4.is_zero is False + assert p1.is_nonzero is None + assert p3.is_nonzero is False + assert p4.is_nonzero + + assert p4.scale(2, 3) == Point(2, 3) + assert p3.scale(2, 3) == p3 + + assert p4.rotate(pi, Point(0.5, 0.5)) == p3 + assert p1.__radd__(p2) == p1.midpoint(p2).scale(2, 2) + assert (-p3).__rsub__(p4) == p3.midpoint(p4).scale(2, 2) + + assert p4 * 5 == Point(5, 5) + assert p4 / 5 == Point(0.2, 0.2) + assert 5 * p4 == Point(5, 5) + + raises(ValueError, lambda: Point(0, 0) + 10) + + # Point differences should be simplified + assert Point(x*(x - 1), y) - Point(x**2 - x, y + 1) == Point(0, -1) + + a, b = S.Half, Rational(1, 3) + assert Point(a, b).evalf(2) == \ + Point(a.n(2), b.n(2), evaluate=False) + raises(ValueError, lambda: Point(1, 2) + 1) + + # test project + assert Point.project((0, 1), (1, 0)) == Point(0, 0) + assert Point.project((1, 1), (1, 0)) == Point(1, 0) + raises(ValueError, lambda: Point.project(p1, Point(0, 0))) + + # test transformations + p = Point(1, 0) + assert p.rotate(pi/2) == Point(0, 1) + assert p.rotate(pi/2, p) == p + p = Point(1, 1) + assert p.scale(2, 3) == Point(2, 3) + assert p.translate(1, 2) == Point(2, 3) + assert p.translate(1) == Point(2, 1) + assert p.translate(y=1) == Point(1, 2) + assert p.translate(*p.args) == Point(2, 2) + + # Check invalid input for transform + raises(ValueError, lambda: p3.transform(p3)) + raises(ValueError, lambda: p.transform(Matrix([[1, 0], [0, 1]]))) + + # test __contains__ + assert 0 in Point(0, 0, 0, 0) + assert 1 not in Point(0, 0, 0, 0) + + # test affine_rank + assert Point.affine_rank() == -1 + + +def test_point3D(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + x1 = Symbol('x1', real=True) + x2 = Symbol('x2', real=True) + x3 = Symbol('x3', real=True) + y1 = Symbol('y1', real=True) + y2 = Symbol('y2', real=True) + y3 = Symbol('y3', real=True) + half = S.Half + p1 = Point3D(x1, x2, x3) + p2 = Point3D(y1, y2, y3) + p3 = Point3D(0, 0, 0) + p4 = Point3D(1, 1, 1) + p5 = Point3D(0, 1, 2) + + assert p1 in p1 + assert p1 not in p2 + assert p2.y == y2 + assert (p3 + p4) == p4 + assert (p2 - p1) == Point3D(y1 - x1, y2 - x2, y3 - x3) + assert -p2 == Point3D(-y1, -y2, -y3) + + assert Point(34.05, sqrt(3)) == Point(Rational(681, 20), sqrt(3)) + assert Point3D.midpoint(p3, p4) == Point3D(half, half, half) + assert Point3D.midpoint(p1, p4) == Point3D(half + half*x1, half + half*x2, + half + half*x3) + assert Point3D.midpoint(p2, p2) == p2 + assert p2.midpoint(p2) == p2 + + assert Point3D.distance(p3, p4) == sqrt(3) + assert Point3D.distance(p1, p1) == 0 + assert Point3D.distance(p3, p2) == sqrt(p2.x**2 + p2.y**2 + p2.z**2) + + p1_1 = Point3D(x1, x1, x1) + p1_2 = Point3D(y2, y2, y2) + p1_3 = Point3D(x1 + 1, x1, x1) + Point3D.are_collinear(p3) + assert Point3D.are_collinear(p3, p4) + assert Point3D.are_collinear(p3, p4, p1_1, p1_2) + assert Point3D.are_collinear(p3, p4, p1_1, p1_3) is False + assert Point3D.are_collinear(p3, p3, p4, p5) is False + + assert p3.intersection(Point3D(0, 0, 0)) == [p3] + assert p3.intersection(p4) == [] + + + assert p4 * 5 == Point3D(5, 5, 5) + assert p4 / 5 == Point3D(0.2, 0.2, 0.2) + assert 5 * p4 == Point3D(5, 5, 5) + + raises(ValueError, lambda: Point3D(0, 0, 0) + 10) + + # Test coordinate properties + assert p1.coordinates == (x1, x2, x3) + assert p2.coordinates == (y1, y2, y3) + assert p3.coordinates == (0, 0, 0) + assert p4.coordinates == (1, 1, 1) + assert p5.coordinates == (0, 1, 2) + assert p5.x == 0 + assert p5.y == 1 + assert p5.z == 2 + + # Point differences should be simplified + assert Point3D(x*(x - 1), y, 2) - Point3D(x**2 - x, y + 1, 1) == \ + Point3D(0, -1, 1) + + a, b, c = S.Half, Rational(1, 3), Rational(1, 4) + assert Point3D(a, b, c).evalf(2) == \ + Point(a.n(2), b.n(2), c.n(2), evaluate=False) + raises(ValueError, lambda: Point3D(1, 2, 3) + 1) + + # test transformations + p = Point3D(1, 1, 1) + assert p.scale(2, 3) == Point3D(2, 3, 1) + assert p.translate(1, 2) == Point3D(2, 3, 1) + assert p.translate(1) == Point3D(2, 1, 1) + assert p.translate(z=1) == Point3D(1, 1, 2) + assert p.translate(*p.args) == Point3D(2, 2, 2) + + # Test __new__ + assert Point3D(0.1, 0.2, evaluate=False, on_morph='ignore').args[0].is_Float + + # Test length property returns correctly + assert p.length == 0 + assert p1_1.length == 0 + assert p1_2.length == 0 + + # Test are_colinear type error + raises(TypeError, lambda: Point3D.are_collinear(p, x)) + + # Test are_coplanar + assert Point.are_coplanar() + assert Point.are_coplanar((1, 2, 0), (1, 2, 0), (1, 3, 0)) + assert Point.are_coplanar((1, 2, 0), (1, 2, 3)) + with warns(UserWarning, test_stacklevel=False): + raises(ValueError, lambda: Point2D.are_coplanar((1, 2), (1, 2, 3))) + assert Point3D.are_coplanar((1, 2, 0), (1, 2, 3)) + assert Point.are_coplanar((0, 0, 0), (1, 1, 0), (1, 1, 1), (1, 2, 1)) is False + planar2 = Point3D(1, -1, 1) + planar3 = Point3D(-1, 1, 1) + assert Point3D.are_coplanar(p, planar2, planar3) == True + assert Point3D.are_coplanar(p, planar2, planar3, p3) == False + assert Point.are_coplanar(p, planar2) + planar2 = Point3D(1, 1, 2) + planar3 = Point3D(1, 1, 3) + assert Point3D.are_coplanar(p, planar2, planar3) # line, not plane + plane = Plane((1, 2, 1), (2, 1, 0), (3, 1, 2)) + assert Point.are_coplanar(*[plane.projection(((-1)**i, i)) for i in range(4)]) + + # all 2D points are coplanar + assert Point.are_coplanar(Point(x, y), Point(x, x + y), Point(y, x + 2)) is True + + # Test Intersection + assert planar2.intersection(Line3D(p, planar3)) == [Point3D(1, 1, 2)] + + # Test Scale + assert planar2.scale(1, 1, 1) == planar2 + assert planar2.scale(2, 2, 2, planar3) == Point3D(1, 1, 1) + assert planar2.scale(1, 1, 1, p3) == planar2 + + # Test Transform + identity = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + assert p.transform(identity) == p + trans = Matrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 1], [0, 0, 0, 1]]) + assert p.transform(trans) == Point3D(2, 2, 2) + raises(ValueError, lambda: p.transform(p)) + raises(ValueError, lambda: p.transform(Matrix([[1, 0], [0, 1]]))) + + # Test Equals + assert p.equals(x1) == False + + # Test __sub__ + p_4d = Point(0, 0, 0, 1) + with warns(UserWarning, test_stacklevel=False): + assert p - p_4d == Point(1, 1, 1, -1) + p_4d3d = Point(0, 0, 1, 0) + with warns(UserWarning, test_stacklevel=False): + assert p - p_4d3d == Point(1, 1, 0, 0) + + +def test_Point2D(): + + # Test Distance + p1 = Point2D(1, 5) + p2 = Point2D(4, 2.5) + p3 = (6, 3) + assert p1.distance(p2) == sqrt(61)/2 + assert p2.distance(p3) == sqrt(17)/2 + + # Test coordinates + assert p1.x == 1 + assert p1.y == 5 + assert p2.x == 4 + assert p2.y == S(5)/2 + assert p1.coordinates == (1, 5) + assert p2.coordinates == (4, S(5)/2) + + # test bounds + assert p1.bounds == (1, 5, 1, 5) + +def test_issue_9214(): + p1 = Point3D(4, -2, 6) + p2 = Point3D(1, 2, 3) + p3 = Point3D(7, 2, 3) + + assert Point3D.are_collinear(p1, p2, p3) is False + + +def test_issue_11617(): + p1 = Point3D(1,0,2) + p2 = Point2D(2,0) + + with warns(UserWarning, test_stacklevel=False): + assert p1.distance(p2) == sqrt(5) + + +def test_transform(): + p = Point(1, 1) + assert p.transform(rotate(pi/2)) == Point(-1, 1) + assert p.transform(scale(3, 2)) == Point(3, 2) + assert p.transform(translate(1, 2)) == Point(2, 3) + assert Point(1, 1).scale(2, 3, (4, 5)) == \ + Point(-2, -7) + assert Point(1, 1).translate(4, 5) == \ + Point(5, 6) + + +def test_concyclic_doctest_bug(): + p1, p2 = Point(-1, 0), Point(1, 0) + p3, p4 = Point(0, 1), Point(-1, 2) + assert Point.is_concyclic(p1, p2, p3) + assert not Point.is_concyclic(p1, p2, p3, p4) + + +def test_arguments(): + """Functions accepting `Point` objects in `geometry` + should also accept tuples and lists and + automatically convert them to points.""" + + singles2d = ((1,2), [1,2], Point(1,2)) + singles2d2 = ((1,3), [1,3], Point(1,3)) + doubles2d = cartes(singles2d, singles2d2) + p2d = Point2D(1,2) + singles3d = ((1,2,3), [1,2,3], Point(1,2,3)) + doubles3d = subsets(singles3d, 2) + p3d = Point3D(1,2,3) + singles4d = ((1,2,3,4), [1,2,3,4], Point(1,2,3,4)) + doubles4d = subsets(singles4d, 2) + p4d = Point(1,2,3,4) + + # test 2D + test_single = ['distance', 'is_scalar_multiple', 'taxicab_distance', 'midpoint', 'intersection', 'dot', 'equals', '__add__', '__sub__'] + test_double = ['is_concyclic', 'is_collinear'] + for p in singles2d: + Point2D(p) + for func in test_single: + for p in singles2d: + getattr(p2d, func)(p) + for func in test_double: + for p in doubles2d: + getattr(p2d, func)(*p) + + # test 3D + test_double = ['is_collinear'] + for p in singles3d: + Point3D(p) + for func in test_single: + for p in singles3d: + getattr(p3d, func)(p) + for func in test_double: + for p in doubles3d: + getattr(p3d, func)(*p) + + # test 4D + test_double = ['is_collinear'] + for p in singles4d: + Point(p) + for func in test_single: + for p in singles4d: + getattr(p4d, func)(p) + for func in test_double: + for p in doubles4d: + getattr(p4d, func)(*p) + + # test evaluate=False for ops + x = Symbol('x') + a = Point(0, 1) + assert a + (0.1, x) == Point(0.1, 1 + x, evaluate=False) + a = Point(0, 1) + assert a/10.0 == Point(0, 0.1, evaluate=False) + a = Point(0, 1) + assert a*10.0 == Point(0, 10.0, evaluate=False) + + # test evaluate=False when changing dimensions + u = Point(.1, .2, evaluate=False) + u4 = Point(u, dim=4, on_morph='ignore') + assert u4.args == (.1, .2, 0, 0) + assert all(i.is_Float for i in u4.args[:2]) + # and even when *not* changing dimensions + assert all(i.is_Float for i in Point(u).args) + + # never raise error if creating an origin + assert Point(dim=3, on_morph='error') + + # raise error with unmatched dimension + raises(ValueError, lambda: Point(1, 1, dim=3, on_morph='error')) + # test unknown on_morph + raises(ValueError, lambda: Point(1, 1, dim=3, on_morph='unknown')) + # test invalid expressions + raises(TypeError, lambda: Point(Basic(), Basic())) + +def test_unit(): + assert Point(1, 1).unit == Point(sqrt(2)/2, sqrt(2)/2) + + +def test_dot(): + raises(TypeError, lambda: Point(1, 2).dot(Line((0, 0), (1, 1)))) + + +def test__normalize_dimension(): + assert Point._normalize_dimension(Point(1, 2), Point(3, 4)) == [ + Point(1, 2), Point(3, 4)] + assert Point._normalize_dimension( + Point(1, 2), Point(3, 4, 0), on_morph='ignore') == [ + Point(1, 2, 0), Point(3, 4, 0)] + + +def test_issue_22684(): + # Used to give an error + with evaluate(False): + Point(1, 2) + + +def test_direction_cosine(): + p1 = Point3D(0, 0, 0) + p2 = Point3D(1, 1, 1) + + assert p1.direction_cosine(Point3D(1, 0, 0)) == [1, 0, 0] + assert p1.direction_cosine(Point3D(0, 1, 0)) == [0, 1, 0] + assert p1.direction_cosine(Point3D(0, 0, pi)) == [0, 0, 1] + + assert p1.direction_cosine(Point3D(5, 0, 0)) == [1, 0, 0] + assert p1.direction_cosine(Point3D(0, sqrt(3), 0)) == [0, 1, 0] + assert p1.direction_cosine(Point3D(0, 0, 5)) == [0, 0, 1] + + assert p1.direction_cosine(Point3D(2.4, 2.4, 0)) == [sqrt(2)/2, sqrt(2)/2, 0] + assert p1.direction_cosine(Point3D(1, 1, 1)) == [sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3] + assert p1.direction_cosine(Point3D(-12, 0 -15)) == [-4*sqrt(41)/41, -5*sqrt(41)/41, 0] + + assert p2.direction_cosine(Point3D(0, 0, 0)) == [-sqrt(3) / 3, -sqrt(3) / 3, -sqrt(3) / 3] + assert p2.direction_cosine(Point3D(1, 1, 12)) == [0, 0, 1] + assert p2.direction_cosine(Point3D(12, 1, 12)) == [sqrt(2) / 2, 0, sqrt(2) / 2] diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_polygon.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_polygon.py new file mode 100644 index 0000000000000000000000000000000000000000..520023349f363bdb12146465305c2a5650c80934 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_polygon.py @@ -0,0 +1,676 @@ +from sympy.core.numbers import (Float, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, cos, sin) +from sympy.functions.elementary.trigonometric import tan +from sympy.geometry import (Circle, Ellipse, GeometryError, Point, Point2D, + Polygon, Ray, RegularPolygon, Segment, Triangle, + are_similar, convex_hull, intersection, Line, Ray2D) +from sympy.testing.pytest import raises, slow, warns +from sympy.core.random import verify_numerically +from sympy.geometry.polygon import rad, deg +from sympy.integrals.integrals import integrate +from sympy.utilities.iterables import rotate_left + + +def feq(a, b): + """Test if two floating point values are 'equal'.""" + t_float = Float("1.0E-10") + return -t_float < a - b < t_float + +@slow +def test_polygon(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + q = Symbol('q', real=True) + u = Symbol('u', real=True) + v = Symbol('v', real=True) + w = Symbol('w', real=True) + x1 = Symbol('x1', real=True) + half = S.Half + a, b, c = Point(0, 0), Point(2, 0), Point(3, 3) + t = Triangle(a, b, c) + assert Polygon(Point(0, 0)) == Point(0, 0) + assert Polygon(a, Point(1, 0), b, c) == t + assert Polygon(Point(1, 0), b, c, a) == t + assert Polygon(b, c, a, Point(1, 0)) == t + # 2 "remove folded" tests + assert Polygon(a, Point(3, 0), b, c) == t + assert Polygon(a, b, Point(3, -1), b, c) == t + # remove multiple collinear points + assert Polygon(Point(-4, 15), Point(-11, 15), Point(-15, 15), + Point(-15, 33/5), Point(-15, -87/10), Point(-15, -15), + Point(-42/5, -15), Point(-2, -15), Point(7, -15), Point(15, -15), + Point(15, -3), Point(15, 10), Point(15, 15)) == \ + Polygon(Point(-15, -15), Point(15, -15), Point(15, 15), Point(-15, 15)) + + p1 = Polygon( + Point(0, 0), Point(3, -1), + Point(6, 0), Point(4, 5), + Point(2, 3), Point(0, 3)) + p2 = Polygon( + Point(6, 0), Point(3, -1), + Point(0, 0), Point(0, 3), + Point(2, 3), Point(4, 5)) + p3 = Polygon( + Point(0, 0), Point(3, 0), + Point(5, 2), Point(4, 4)) + p4 = Polygon( + Point(0, 0), Point(4, 4), + Point(5, 2), Point(3, 0)) + p5 = Polygon( + Point(0, 0), Point(4, 4), + Point(0, 4)) + p6 = Polygon( + Point(-11, 1), Point(-9, 6.6), + Point(-4, -3), Point(-8.4, -8.7)) + p7 = Polygon( + Point(x, y), Point(q, u), + Point(v, w)) + p8 = Polygon( + Point(x, y), Point(v, w), + Point(q, u)) + p9 = Polygon( + Point(0, 0), Point(4, 4), + Point(3, 0), Point(5, 2)) + p10 = Polygon( + Point(0, 2), Point(2, 2), + Point(0, 0), Point(2, 0)) + p11 = Polygon(Point(0, 0), 1, n=3) + p12 = Polygon(Point(0, 0), 1, 0, n=3) + p13 = Polygon( + Point(0, 0),Point(8, 8), + Point(23, 20),Point(0, 20)) + p14 = Polygon(*rotate_left(p13.args, 1)) + + + r = Ray(Point(-9, 6.6), Point(-9, 5.5)) + # + # General polygon + # + assert p1 == p2 + assert len(p1.args) == 6 + assert len(p1.sides) == 6 + assert p1.perimeter == 5 + 2*sqrt(10) + sqrt(29) + sqrt(8) + assert p1.area == 22 + assert not p1.is_convex() + assert Polygon((-1, 1), (2, -1), (2, 1), (-1, -1), (3, 0) + ).is_convex() is False + # ensure convex for both CW and CCW point specification + assert p3.is_convex() + assert p4.is_convex() + dict5 = p5.angles + assert dict5[Point(0, 0)] == pi / 4 + assert dict5[Point(0, 4)] == pi / 2 + assert p5.encloses_point(Point(x, y)) is None + assert p5.encloses_point(Point(1, 3)) + assert p5.encloses_point(Point(0, 0)) is False + assert p5.encloses_point(Point(4, 0)) is False + assert p1.encloses(Circle(Point(2.5, 2.5), 5)) is False + assert p1.encloses(Ellipse(Point(2.5, 2), 5, 6)) is False + assert p5.plot_interval('x') == [x, 0, 1] + assert p5.distance( + Polygon(Point(10, 10), Point(14, 14), Point(10, 14))) == 6 * sqrt(2) + assert p5.distance( + Polygon(Point(1, 8), Point(5, 8), Point(8, 12), Point(1, 12))) == 4 + with warns(UserWarning, \ + match="Polygons may intersect producing erroneous output"): + Polygon(Point(0, 0), Point(1, 0), Point(1, 1)).distance( + Polygon(Point(0, 0), Point(0, 1), Point(1, 1))) + assert hash(p5) == hash(Polygon(Point(0, 0), Point(4, 4), Point(0, 4))) + assert hash(p1) == hash(p2) + assert hash(p7) == hash(p8) + assert hash(p3) != hash(p9) + assert p5 == Polygon(Point(4, 4), Point(0, 4), Point(0, 0)) + assert Polygon(Point(4, 4), Point(0, 4), Point(0, 0)) in p5 + assert p5 != Point(0, 4) + assert Point(0, 1) in p5 + assert p5.arbitrary_point('t').subs(Symbol('t', real=True), 0) == \ + Point(0, 0) + raises(ValueError, lambda: Polygon( + Point(x, 0), Point(0, y), Point(x, y)).arbitrary_point('x')) + assert p6.intersection(r) == [Point(-9, Rational(-84, 13)), Point(-9, Rational(33, 5))] + assert p10.area == 0 + assert p11 == RegularPolygon(Point(0, 0), 1, 3, 0) + assert p11 == p12 + assert p11.vertices[0] == Point(1, 0) + assert p11.args[0] == Point(0, 0) + p11.spin(pi/2) + assert p11.vertices[0] == Point(0, 1) + # + # Regular polygon + # + p1 = RegularPolygon(Point(0, 0), 10, 5) + p2 = RegularPolygon(Point(0, 0), 5, 5) + raises(GeometryError, lambda: RegularPolygon(Point(0, 0), Point(0, + 1), Point(1, 1))) + raises(GeometryError, lambda: RegularPolygon(Point(0, 0), 1, 2)) + raises(ValueError, lambda: RegularPolygon(Point(0, 0), 1, 2.5)) + + assert p1 != p2 + assert p1.interior_angle == pi*Rational(3, 5) + assert p1.exterior_angle == pi*Rational(2, 5) + assert p2.apothem == 5*cos(pi/5) + assert p2.circumcenter == p1.circumcenter == Point(0, 0) + assert p1.circumradius == p1.radius == 10 + assert p2.circumcircle == Circle(Point(0, 0), 5) + assert p2.incircle == Circle(Point(0, 0), p2.apothem) + assert p2.inradius == p2.apothem == (5 * (1 + sqrt(5)) / 4) + p2.spin(pi / 10) + dict1 = p2.angles + assert dict1[Point(0, 5)] == 3 * pi / 5 + assert p1.is_convex() + assert p1.rotation == 0 + assert p1.encloses_point(Point(0, 0)) + assert p1.encloses_point(Point(11, 0)) is False + assert p2.encloses_point(Point(0, 4.9)) + p1.spin(pi/3) + assert p1.rotation == pi/3 + assert p1.vertices[0] == Point(5, 5*sqrt(3)) + for var in p1.args: + if isinstance(var, Point): + assert var == Point(0, 0) + else: + assert var in (5, 10, pi / 3) + assert p1 != Point(0, 0) + assert p1 != p5 + + # while spin works in place (notice that rotation is 2pi/3 below) + # rotate returns a new object + p1_old = p1 + assert p1.rotate(pi/3) == RegularPolygon(Point(0, 0), 10, 5, pi*Rational(2, 3)) + assert p1 == p1_old + + assert p1.area == (-250*sqrt(5) + 1250)/(4*tan(pi/5)) + assert p1.length == 20*sqrt(-sqrt(5)/8 + Rational(5, 8)) + assert p1.scale(2, 2) == \ + RegularPolygon(p1.center, p1.radius*2, p1._n, p1.rotation) + assert RegularPolygon((0, 0), 1, 4).scale(2, 3) == \ + Polygon(Point(2, 0), Point(0, 3), Point(-2, 0), Point(0, -3)) + + assert repr(p1) == str(p1) + + # + # Angles + # + angles = p4.angles + assert feq(angles[Point(0, 0)].evalf(), Float("0.7853981633974483")) + assert feq(angles[Point(4, 4)].evalf(), Float("1.2490457723982544")) + assert feq(angles[Point(5, 2)].evalf(), Float("1.8925468811915388")) + assert feq(angles[Point(3, 0)].evalf(), Float("2.3561944901923449")) + + angles = p3.angles + assert feq(angles[Point(0, 0)].evalf(), Float("0.7853981633974483")) + assert feq(angles[Point(4, 4)].evalf(), Float("1.2490457723982544")) + assert feq(angles[Point(5, 2)].evalf(), Float("1.8925468811915388")) + assert feq(angles[Point(3, 0)].evalf(), Float("2.3561944901923449")) + + # https://github.com/sympy/sympy/issues/24885 + interior_angles_sum = sum(p13.angles.values()) + assert feq(interior_angles_sum, (len(p13.angles) - 2)*pi ) + interior_angles_sum = sum(p14.angles.values()) + assert feq(interior_angles_sum, (len(p14.angles) - 2)*pi ) + + # + # Triangle + # + p1 = Point(0, 0) + p2 = Point(5, 0) + p3 = Point(0, 5) + t1 = Triangle(p1, p2, p3) + t2 = Triangle(p1, p2, Point(Rational(5, 2), sqrt(Rational(75, 4)))) + t3 = Triangle(p1, Point(x1, 0), Point(0, x1)) + s1 = t1.sides + assert Triangle(p1, p2, p1) == Polygon(p1, p2, p1) == Segment(p1, p2) + raises(GeometryError, lambda: Triangle(Point(0, 0))) + + # Basic stuff + assert Triangle(p1, p1, p1) == p1 + assert Triangle(p2, p2*2, p2*3) == Segment(p2, p2*3) + assert t1.area == Rational(25, 2) + assert t1.is_right() + assert t2.is_right() is False + assert t3.is_right() + assert p1 in t1 + assert t1.sides[0] in t1 + assert Segment((0, 0), (1, 0)) in t1 + assert Point(5, 5) not in t2 + assert t1.is_convex() + assert feq(t1.angles[p1].evalf(), pi.evalf()/2) + + assert t1.is_equilateral() is False + assert t2.is_equilateral() + assert t3.is_equilateral() is False + assert are_similar(t1, t2) is False + assert are_similar(t1, t3) + assert are_similar(t2, t3) is False + assert t1.is_similar(Point(0, 0)) is False + assert t1.is_similar(t2) is False + + # Bisectors + bisectors = t1.bisectors() + assert bisectors[p1] == Segment( + p1, Point(Rational(5, 2), Rational(5, 2))) + assert t2.bisectors()[p2] == Segment( + Point(5, 0), Point(Rational(5, 4), 5*sqrt(3)/4)) + p4 = Point(0, x1) + assert t3.bisectors()[p4] == Segment(p4, Point(x1*(sqrt(2) - 1), 0)) + ic = (250 - 125*sqrt(2))/50 + assert t1.incenter == Point(ic, ic) + + # Inradius + assert t1.inradius == t1.incircle.radius == 5 - 5*sqrt(2)/2 + assert t2.inradius == t2.incircle.radius == 5*sqrt(3)/6 + assert t3.inradius == t3.incircle.radius == x1**2/((2 + sqrt(2))*Abs(x1)) + + # Exradius + assert t1.exradii[t1.sides[2]] == 5*sqrt(2)/2 + + # Excenters + assert t1.excenters[t1.sides[2]] == Point2D(25*sqrt(2), -5*sqrt(2)/2) + + # Circumcircle + assert t1.circumcircle.center == Point(2.5, 2.5) + + # Medians + Centroid + m = t1.medians + assert t1.centroid == Point(Rational(5, 3), Rational(5, 3)) + assert m[p1] == Segment(p1, Point(Rational(5, 2), Rational(5, 2))) + assert t3.medians[p1] == Segment(p1, Point(x1/2, x1/2)) + assert intersection(m[p1], m[p2], m[p3]) == [t1.centroid] + assert t1.medial == Triangle(Point(2.5, 0), Point(0, 2.5), Point(2.5, 2.5)) + + # Nine-point circle + assert t1.nine_point_circle == Circle(Point(2.5, 0), + Point(0, 2.5), Point(2.5, 2.5)) + assert t1.nine_point_circle == Circle(Point(0, 0), + Point(0, 2.5), Point(2.5, 2.5)) + + # Perpendicular + altitudes = t1.altitudes + assert altitudes[p1] == Segment(p1, Point(Rational(5, 2), Rational(5, 2))) + assert altitudes[p2].equals(s1[0]) + assert altitudes[p3] == s1[2] + assert t1.orthocenter == p1 + t = S('''Triangle( + Point(100080156402737/5000000000000, 79782624633431/500000000000), + Point(39223884078253/2000000000000, 156345163124289/1000000000000), + Point(31241359188437/1250000000000, 338338270939941/1000000000000000))''') + assert t.orthocenter == S('''Point(-780660869050599840216997''' + '''79471538701955848721853/80368430960602242240789074233100000000000000,''' + '''20151573611150265741278060334545897615974257/16073686192120448448157''' + '''8148466200000000000)''') + + # Ensure + assert len(intersection(*bisectors.values())) == 1 + assert len(intersection(*altitudes.values())) == 1 + assert len(intersection(*m.values())) == 1 + + # Distance + p1 = Polygon( + Point(0, 0), Point(1, 0), + Point(1, 1), Point(0, 1)) + p2 = Polygon( + Point(0, Rational(5)/4), Point(1, Rational(5)/4), + Point(1, Rational(9)/4), Point(0, Rational(9)/4)) + p3 = Polygon( + Point(1, 2), Point(2, 2), + Point(2, 1)) + p4 = Polygon( + Point(1, 1), Point(Rational(6)/5, 1), + Point(1, Rational(6)/5)) + pt1 = Point(half, half) + pt2 = Point(1, 1) + + '''Polygon to Point''' + assert p1.distance(pt1) == half + assert p1.distance(pt2) == 0 + assert p2.distance(pt1) == Rational(3)/4 + assert p3.distance(pt2) == sqrt(2)/2 + + '''Polygon to Polygon''' + # p1.distance(p2) emits a warning + with warns(UserWarning, \ + match="Polygons may intersect producing erroneous output"): + assert p1.distance(p2) == half/2 + + assert p1.distance(p3) == sqrt(2)/2 + + # p3.distance(p4) emits a warning + with warns(UserWarning, \ + match="Polygons may intersect producing erroneous output"): + assert p3.distance(p4) == (sqrt(2)/2 - sqrt(Rational(2)/25)/2) + + +def test_convex_hull(): + p = [Point(-5, -1), Point(-2, 1), Point(-2, -1), Point(-1, -3), \ + Point(0, 0), Point(1, 1), Point(2, 2), Point(2, -1), Point(3, 1), \ + Point(4, -1), Point(6, 2)] + ch = Polygon(p[0], p[3], p[9], p[10], p[6], p[1]) + #test handling of duplicate points + p.append(p[3]) + + #more than 3 collinear points + another_p = [Point(-45, -85), Point(-45, 85), Point(-45, 26), \ + Point(-45, -24)] + ch2 = Segment(another_p[0], another_p[1]) + + assert convex_hull(*another_p) == ch2 + assert convex_hull(*p) == ch + assert convex_hull(p[0]) == p[0] + assert convex_hull(p[0], p[1]) == Segment(p[0], p[1]) + + # no unique points + assert convex_hull(*[p[-1]]*3) == p[-1] + + # collection of items + assert convex_hull(*[Point(0, 0), \ + Segment(Point(1, 0), Point(1, 1)), \ + RegularPolygon(Point(2, 0), 2, 4)]) == \ + Polygon(Point(0, 0), Point(2, -2), Point(4, 0), Point(2, 2)) + + +def test_encloses(): + # square with a dimpled left side + s = Polygon(Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1), \ + Point(S.Half, S.Half)) + # the following is True if the polygon isn't treated as closing on itself + assert s.encloses(Point(0, S.Half)) is False + assert s.encloses(Point(S.Half, S.Half)) is False # it's a vertex + assert s.encloses(Point(Rational(3, 4), S.Half)) is True + + +def test_triangle_kwargs(): + assert Triangle(sss=(3, 4, 5)) == \ + Triangle(Point(0, 0), Point(3, 0), Point(3, 4)) + assert Triangle(asa=(30, 2, 30)) == \ + Triangle(Point(0, 0), Point(2, 0), Point(1, sqrt(3)/3)) + assert Triangle(sas=(1, 45, 2)) == \ + Triangle(Point(0, 0), Point(2, 0), Point(sqrt(2)/2, sqrt(2)/2)) + assert Triangle(sss=(1, 2, 5)) is None + assert deg(rad(180)) == 180 + + +def test_transform(): + pts = [Point(0, 0), Point(S.Half, Rational(1, 4)), Point(1, 1)] + pts_out = [Point(-4, -10), Point(-3, Rational(-37, 4)), Point(-2, -7)] + assert Triangle(*pts).scale(2, 3, (4, 5)) == Triangle(*pts_out) + assert RegularPolygon((0, 0), 1, 4).scale(2, 3, (4, 5)) == \ + Polygon(Point(-2, -10), Point(-4, -7), Point(-6, -10), Point(-4, -13)) + # Checks for symmetric scaling + assert RegularPolygon((0, 0), 1, 4).scale(2, 2) == \ + RegularPolygon(Point2D(0, 0), 2, 4, 0) + +def test_reflect(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + b = Symbol('b') + m = Symbol('m') + l = Line((0, b), slope=m) + p = Point(x, y) + r = p.reflect(l) + dp = l.perpendicular_segment(p).length + dr = l.perpendicular_segment(r).length + + assert verify_numerically(dp, dr) + + assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((3, 0), slope=oo)) \ + == Triangle(Point(5, 0), Point(4, 0), Point(4, 2)) + assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((0, 3), slope=oo)) \ + == Triangle(Point(-1, 0), Point(-2, 0), Point(-2, 2)) + assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((0, 3), slope=0)) \ + == Triangle(Point(1, 6), Point(2, 6), Point(2, 4)) + assert Polygon((1, 0), (2, 0), (2, 2)).reflect(Line((3, 0), slope=0)) \ + == Triangle(Point(1, 0), Point(2, 0), Point(2, -2)) + +def test_bisectors(): + p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) + p = Polygon(Point(0, 0), Point(2, 0), Point(1, 1), Point(0, 3)) + q = Polygon(Point(1, 0), Point(2, 0), Point(3, 3), Point(-1, 5)) + poly = Polygon(Point(3, 4), Point(0, 0), Point(8, 7), Point(-1, 1), Point(19, -19)) + t = Triangle(p1, p2, p3) + assert t.bisectors()[p2] == Segment(Point(1, 0), Point(0, sqrt(2) - 1)) + assert p.bisectors()[Point2D(0, 3)] == Ray2D(Point2D(0, 3), \ + Point2D(sin(acos(2*sqrt(5)/5)/2), 3 - cos(acos(2*sqrt(5)/5)/2))) + assert q.bisectors()[Point2D(-1, 5)] == \ + Ray2D(Point2D(-1, 5), Point2D(-1 + sqrt(29)*(5*sin(acos(9*sqrt(145)/145)/2) + \ + 2*cos(acos(9*sqrt(145)/145)/2))/29, sqrt(29)*(-5*cos(acos(9*sqrt(145)/145)/2) + \ + 2*sin(acos(9*sqrt(145)/145)/2))/29 + 5)) + assert poly.bisectors()[Point2D(-1, 1)] == Ray2D(Point2D(-1, 1), \ + Point2D(-1 + sin(acos(sqrt(26)/26)/2 + pi/4), 1 - sin(-acos(sqrt(26)/26)/2 + pi/4))) + +def test_incenter(): + assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).incenter \ + == Point(1 - sqrt(2)/2, 1 - sqrt(2)/2) + +def test_inradius(): + assert Triangle(Point(0, 0), Point(4, 0), Point(0, 3)).inradius == 1 + +def test_incircle(): + assert Triangle(Point(0, 0), Point(2, 0), Point(0, 2)).incircle \ + == Circle(Point(2 - sqrt(2), 2 - sqrt(2)), 2 - sqrt(2)) + +def test_exradii(): + t = Triangle(Point(0, 0), Point(6, 0), Point(0, 2)) + assert t.exradii[t.sides[2]] == (-2 + sqrt(10)) + +def test_medians(): + t = Triangle(Point(0, 0), Point(1, 0), Point(0, 1)) + assert t.medians[Point(0, 0)] == Segment(Point(0, 0), Point(S.Half, S.Half)) + +def test_medial(): + assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).medial \ + == Triangle(Point(S.Half, 0), Point(S.Half, S.Half), Point(0, S.Half)) + +def test_nine_point_circle(): + assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).nine_point_circle \ + == Circle(Point2D(Rational(1, 4), Rational(1, 4)), sqrt(2)/4) + +def test_eulerline(): + assert Triangle(Point(0, 0), Point(1, 0), Point(0, 1)).eulerline \ + == Line(Point2D(0, 0), Point2D(S.Half, S.Half)) + assert Triangle(Point(0, 0), Point(10, 0), Point(5, 5*sqrt(3))).eulerline \ + == Point2D(5, 5*sqrt(3)/3) + assert Triangle(Point(4, -6), Point(4, -1), Point(-3, 3)).eulerline \ + == Line(Point2D(Rational(64, 7), 3), Point2D(Rational(-29, 14), Rational(-7, 2))) + +def test_intersection(): + poly1 = Triangle(Point(0, 0), Point(1, 0), Point(0, 1)) + poly2 = Polygon(Point(0, 1), Point(-5, 0), + Point(0, -4), Point(0, Rational(1, 5)), + Point(S.Half, -0.1), Point(1, 0), Point(0, 1)) + + assert poly1.intersection(poly2) == [Point2D(Rational(1, 3), 0), + Segment(Point(0, Rational(1, 5)), Point(0, 0)), + Segment(Point(1, 0), Point(0, 1))] + assert poly2.intersection(poly1) == [Point(Rational(1, 3), 0), + Segment(Point(0, 0), Point(0, Rational(1, 5))), + Segment(Point(1, 0), Point(0, 1))] + assert poly1.intersection(Point(0, 0)) == [Point(0, 0)] + assert poly1.intersection(Point(-12, -43)) == [] + assert poly2.intersection(Line((-12, 0), (12, 0))) == [Point(-5, 0), + Point(0, 0), Point(Rational(1, 3), 0), Point(1, 0)] + assert poly2.intersection(Line((-12, 12), (12, 12))) == [] + assert poly2.intersection(Ray((-3, 4), (1, 0))) == [Segment(Point(1, 0), + Point(0, 1))] + assert poly2.intersection(Circle((0, -1), 1)) == [Point(0, -2), + Point(0, 0)] + assert poly1.intersection(poly1) == [Segment(Point(0, 0), Point(1, 0)), + Segment(Point(0, 1), Point(0, 0)), Segment(Point(1, 0), Point(0, 1))] + assert poly2.intersection(poly2) == [Segment(Point(-5, 0), Point(0, -4)), + Segment(Point(0, -4), Point(0, Rational(1, 5))), + Segment(Point(0, Rational(1, 5)), Point(S.Half, Rational(-1, 10))), + Segment(Point(0, 1), Point(-5, 0)), + Segment(Point(S.Half, Rational(-1, 10)), Point(1, 0)), + Segment(Point(1, 0), Point(0, 1))] + assert poly2.intersection(Triangle(Point(0, 1), Point(1, 0), Point(-1, 1))) \ + == [Point(Rational(-5, 7), Rational(6, 7)), Segment(Point2D(0, 1), Point(1, 0))] + assert poly1.intersection(RegularPolygon((-12, -15), 3, 3)) == [] + + +def test_parameter_value(): + t = Symbol('t') + sq = Polygon((0, 0), (0, 1), (1, 1), (1, 0)) + assert sq.parameter_value((0.5, 1), t) == {t: Rational(3, 8)} + q = Polygon((0, 0), (2, 1), (2, 4), (4, 0)) + assert q.parameter_value((4, 0), t) == {t: -6 + 3*sqrt(5)} # ~= 0.708 + + raises(ValueError, lambda: sq.parameter_value((5, 6), t)) + raises(ValueError, lambda: sq.parameter_value(Circle(Point(0, 0), 1), t)) + + +def test_issue_12966(): + poly = Polygon(Point(0, 0), Point(0, 10), Point(5, 10), Point(5, 5), + Point(10, 5), Point(10, 0)) + t = Symbol('t') + pt = poly.arbitrary_point(t) + DELTA = 5/poly.perimeter + assert [pt.subs(t, DELTA*i) for i in range(int(1/DELTA))] == [ + Point(0, 0), Point(0, 5), Point(0, 10), Point(5, 10), + Point(5, 5), Point(10, 5), Point(10, 0), Point(5, 0)] + + +def test_second_moment_of_area(): + x, y = symbols('x, y') + # triangle + p1, p2, p3 = [(0, 0), (4, 0), (0, 2)] + p = (0, 0) + # equation of hypotenuse + eq_y = (1-x/4)*2 + I_yy = integrate((x**2) * (integrate(1, (y, 0, eq_y))), (x, 0, 4)) + I_xx = integrate(1 * (integrate(y**2, (y, 0, eq_y))), (x, 0, 4)) + I_xy = integrate(x * (integrate(y, (y, 0, eq_y))), (x, 0, 4)) + + triangle = Polygon(p1, p2, p3) + + assert (I_xx - triangle.second_moment_of_area(p)[0]) == 0 + assert (I_yy - triangle.second_moment_of_area(p)[1]) == 0 + assert (I_xy - triangle.second_moment_of_area(p)[2]) == 0 + + # rectangle + p1, p2, p3, p4=[(0, 0), (4, 0), (4, 2), (0, 2)] + I_yy = integrate((x**2) * integrate(1, (y, 0, 2)), (x, 0, 4)) + I_xx = integrate(1 * integrate(y**2, (y, 0, 2)), (x, 0, 4)) + I_xy = integrate(x * integrate(y, (y, 0, 2)), (x, 0, 4)) + + rectangle = Polygon(p1, p2, p3, p4) + + assert (I_xx - rectangle.second_moment_of_area(p)[0]) == 0 + assert (I_yy - rectangle.second_moment_of_area(p)[1]) == 0 + assert (I_xy - rectangle.second_moment_of_area(p)[2]) == 0 + + + r = RegularPolygon(Point(0, 0), 5, 3) + assert r.second_moment_of_area() == (1875*sqrt(3)/S(32), 1875*sqrt(3)/S(32), 0) + + +def test_first_moment(): + a, b = symbols('a, b', positive=True) + # rectangle + p1 = Polygon((0, 0), (a, 0), (a, b), (0, b)) + assert p1.first_moment_of_area() == (a*b**2/8, a**2*b/8) + assert p1.first_moment_of_area((a/3, b/4)) == (-3*a*b**2/32, -a**2*b/9) + + p1 = Polygon((0, 0), (40, 0), (40, 30), (0, 30)) + assert p1.first_moment_of_area() == (4500, 6000) + + # triangle + p2 = Polygon((0, 0), (a, 0), (a/2, b)) + assert p2.first_moment_of_area() == (4*a*b**2/81, a**2*b/24) + assert p2.first_moment_of_area((a/8, b/6)) == (-25*a*b**2/648, -5*a**2*b/768) + + p2 = Polygon((0, 0), (12, 0), (12, 30)) + assert p2.first_moment_of_area() == (S(1600)/3, -S(640)/3) + + +def test_section_modulus_and_polar_second_moment_of_area(): + a, b = symbols('a, b', positive=True) + x, y = symbols('x, y') + rectangle = Polygon((0, b), (0, 0), (a, 0), (a, b)) + assert rectangle.section_modulus(Point(x, y)) == (a*b**3/12/(-b/2 + y), a**3*b/12/(-a/2 + x)) + assert rectangle.polar_second_moment_of_area() == a**3*b/12 + a*b**3/12 + + convex = RegularPolygon((0, 0), 1, 6) + assert convex.section_modulus() == (Rational(5, 8), sqrt(3)*Rational(5, 16)) + assert convex.polar_second_moment_of_area() == 5*sqrt(3)/S(8) + + concave = Polygon((0, 0), (1, 8), (3, 4), (4, 6), (7, 1)) + assert concave.section_modulus() == (Rational(-6371, 429), Rational(-9778, 519)) + assert concave.polar_second_moment_of_area() == Rational(-38669, 252) + + +def test_cut_section(): + # concave polygon + p = Polygon((-1, -1), (1, Rational(5, 2)), (2, 1), (3, Rational(5, 2)), (4, 2), (5, 3), (-1, 3)) + l = Line((0, 0), (Rational(9, 2), 3)) + p1 = p.cut_section(l)[0] + p2 = p.cut_section(l)[1] + assert p1 == Polygon( + Point2D(Rational(-9, 13), Rational(-6, 13)), Point2D(1, Rational(5, 2)), Point2D(Rational(24, 13), Rational(16, 13)), + Point2D(Rational(12, 5), Rational(8, 5)), Point2D(3, Rational(5, 2)), Point2D(Rational(24, 7), Rational(16, 7)), + Point2D(Rational(9, 2), 3), Point2D(-1, 3), Point2D(-1, Rational(-2, 3))) + assert p2 == Polygon(Point2D(-1, -1), Point2D(Rational(-9, 13), Rational(-6, 13)), Point2D(Rational(24, 13), Rational(16, 13)), + Point2D(2, 1), Point2D(Rational(12, 5), Rational(8, 5)), Point2D(Rational(24, 7), Rational(16, 7)), Point2D(4, 2), Point2D(5, 3), + Point2D(Rational(9, 2), 3), Point2D(-1, Rational(-2, 3))) + + # convex polygon + p = RegularPolygon(Point2D(0, 0), 6, 6) + s = p.cut_section(Line((0, 0), slope=1)) + assert s[0] == Polygon(Point2D(-3*sqrt(3) + 9, -3*sqrt(3) + 9), Point2D(3, 3*sqrt(3)), + Point2D(-3, 3*sqrt(3)), Point2D(-6, 0), Point2D(-9 + 3*sqrt(3), -9 + 3*sqrt(3))) + assert s[1] == Polygon(Point2D(6, 0), Point2D(-3*sqrt(3) + 9, -3*sqrt(3) + 9), + Point2D(-9 + 3*sqrt(3), -9 + 3*sqrt(3)), Point2D(-3, -3*sqrt(3)), Point2D(3, -3*sqrt(3))) + + # case where line does not intersects but coincides with the edge of polygon + a, b = 20, 10 + t1, t2, t3, t4 = [(0, b), (0, 0), (a, 0), (a, b)] + p = Polygon(t1, t2, t3, t4) + p1, p2 = p.cut_section(Line((0, b), slope=0)) + assert p1 == None + assert p2 == Polygon(Point2D(0, 10), Point2D(0, 0), Point2D(20, 0), Point2D(20, 10)) + + p3, p4 = p.cut_section(Line((0, 0), slope=0)) + assert p3 == Polygon(Point2D(0, 10), Point2D(0, 0), Point2D(20, 0), Point2D(20, 10)) + assert p4 == None + + # case where the line does not intersect with a polygon at all + raises(ValueError, lambda: p.cut_section(Line((0, a), slope=0))) + +def test_type_of_triangle(): + # Isoceles triangle + p1 = Polygon(Point(0, 0), Point(5, 0), Point(2, 4)) + assert p1.is_isosceles() == True + assert p1.is_scalene() == False + assert p1.is_equilateral() == False + + # Scalene triangle + p2 = Polygon (Point(0, 0), Point(0, 2), Point(4, 0)) + assert p2.is_isosceles() == False + assert p2.is_scalene() == True + assert p2.is_equilateral() == False + + # Equilateral triangle + p3 = Polygon(Point(0, 0), Point(6, 0), Point(3, sqrt(27))) + assert p3.is_isosceles() == True + assert p3.is_scalene() == False + assert p3.is_equilateral() == True + +def test_do_poly_distance(): + # Non-intersecting polygons + square1 = Polygon (Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) + triangle1 = Polygon(Point(1, 2), Point(2, 2), Point(2, 1)) + assert square1._do_poly_distance(triangle1) == sqrt(2)/2 + + # Polygons which sides intersect + square2 = Polygon(Point(1, 0), Point(2, 0), Point(2, 1), Point(1, 1)) + with warns(UserWarning, \ + match="Polygons may intersect producing erroneous output", test_stacklevel=False): + assert square1._do_poly_distance(square2) == 0 + + # Polygons which bodies intersect + triangle2 = Polygon(Point(0, -1), Point(2, -1), Point(S.Half, S.Half)) + with warns(UserWarning, \ + match="Polygons may intersect producing erroneous output", test_stacklevel=False): + assert triangle2._do_poly_distance(square1) == 0 diff --git a/phivenv/Lib/site-packages/sympy/geometry/tests/test_util.py b/phivenv/Lib/site-packages/sympy/geometry/tests/test_util.py new file mode 100644 index 0000000000000000000000000000000000000000..da52a795a9383c6438ca06303e8ae6506dccdc65 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/tests/test_util.py @@ -0,0 +1,170 @@ +import pytest +from sympy.core.numbers import Float +from sympy.core.function import (Derivative, Function) +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions import exp, cos, sin, tan, cosh, sinh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.geometry import Point, Point2D, Line, Polygon, Segment, convex_hull,\ + intersection, centroid, Point3D, Line3D, Ray, Ellipse +from sympy.geometry.util import idiff, closest_points, farthest_points, _ordered_points, are_coplanar +from sympy.solvers.solvers import solve +from sympy.testing.pytest import raises + + +def test_idiff(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + t = Symbol('t', real=True) + f = Function('f') + g = Function('g') + # the use of idiff in ellipse also provides coverage + circ = x**2 + y**2 - 4 + ans = -3*x*(x**2/y**2 + 1)/y**3 + assert ans == idiff(circ, y, x, 3), idiff(circ, y, x, 3) + assert ans == idiff(circ, [y], x, 3) + assert idiff(circ, y, x, 3) == ans + explicit = 12*x/sqrt(-x**2 + 4)**5 + assert ans.subs(y, solve(circ, y)[0]).equals(explicit) + assert True in [sol.diff(x, 3).equals(explicit) for sol in solve(circ, y)] + assert idiff(x + t + y, [y, t], x) == -Derivative(t, x) - 1 + assert idiff(f(x) * exp(f(x)) - x * exp(x), f(x), x) == (x + 1)*exp(x)*exp(-f(x))/(f(x) + 1) + assert idiff(f(x) - y * exp(x), [f(x), y], x) == (y + Derivative(y, x))*exp(x) + assert idiff(f(x) - y * exp(x), [y, f(x)], x) == -y + Derivative(f(x), x)*exp(-x) + assert idiff(f(x) - g(x), [f(x), g(x)], x) == Derivative(g(x), x) + # this should be fast + fxy = y - (-10*(-sin(x) + 1/x)**2 + tan(x)**2 + 2*cosh(x/10)) + assert idiff(fxy, y, x) == -20*sin(x)*cos(x) + 2*tan(x)**3 + \ + 2*tan(x) + sinh(x/10)/5 + 20*cos(x)/x - 20*sin(x)/x**2 + 20/x**3 + + +def test_intersection(): + assert intersection(Point(0, 0)) == [] + raises(TypeError, lambda: intersection(Point(0, 0), 3)) + assert intersection( + Segment((0, 0), (2, 0)), + Segment((-1, 0), (1, 0)), + Line((0, 0), (0, 1)), pairwise=True) == [ + Point(0, 0), Segment((0, 0), (1, 0))] + assert intersection( + Line((0, 0), (0, 1)), + Segment((0, 0), (2, 0)), + Segment((-1, 0), (1, 0)), pairwise=True) == [ + Point(0, 0), Segment((0, 0), (1, 0))] + assert intersection( + Line((0, 0), (0, 1)), + Segment((0, 0), (2, 0)), + Segment((-1, 0), (1, 0)), + Line((0, 0), slope=1), pairwise=True) == [ + Point(0, 0), Segment((0, 0), (1, 0))] + R = 4.0 + c = intersection( + Ray(Point2D(0.001, -1), + Point2D(0.0008, -1.7)), + Ellipse(center=Point2D(0, 0), hradius=R, vradius=2.0), pairwise=True)[0].coordinates + assert c == pytest.approx( + Point2D(0.000714285723396502, -1.99999996811224, evaluate=False).coordinates) + # check this is responds to a lower precision parameter + R = Float(4, 5) + c2 = intersection( + Ray(Point2D(0.001, -1), + Point2D(0.0008, -1.7)), + Ellipse(center=Point2D(0, 0), hradius=R, vradius=2.0), pairwise=True)[0].coordinates + assert c2 == pytest.approx( + Point2D(0.000714285723396502, -1.99999996811224, evaluate=False).coordinates) + assert c[0]._prec == 53 + assert c2[0]._prec == 20 + + +def test_convex_hull(): + raises(TypeError, lambda: convex_hull(Point(0, 0), 3)) + points = [(1, -1), (1, -2), (3, -1), (-5, -2), (15, -4)] + assert convex_hull(*points, **{"polygon": False}) == ( + [Point2D(-5, -2), Point2D(1, -1), Point2D(3, -1), Point2D(15, -4)], + [Point2D(-5, -2), Point2D(15, -4)]) + + +def test_centroid(): + p = Polygon((0, 0), (10, 0), (10, 10)) + q = p.translate(0, 20) + assert centroid(p, q) == Point(20, 40)/3 + p = Segment((0, 0), (2, 0)) + q = Segment((0, 0), (2, 2)) + assert centroid(p, q) == Point(1, -sqrt(2) + 2) + assert centroid(Point(0, 0), Point(2, 0)) == Point(2, 0)/2 + assert centroid(Point(0, 0), Point(0, 0), Point(2, 0)) == Point(2, 0)/3 + + +def test_farthest_points_closest_points(): + from sympy.core.random import randint + from sympy.utilities.iterables import subsets + + for how in (min, max): + if how == min: + func = closest_points + else: + func = farthest_points + + raises(ValueError, lambda: func(Point2D(0, 0), Point2D(0, 0))) + + # 3rd pt dx is close and pt is closer to 1st pt + p1 = [Point2D(0, 0), Point2D(3, 0), Point2D(1, 1)] + # 3rd pt dx is close and pt is closer to 2nd pt + p2 = [Point2D(0, 0), Point2D(3, 0), Point2D(2, 1)] + # 3rd pt dx is close and but pt is not closer + p3 = [Point2D(0, 0), Point2D(3, 0), Point2D(1, 10)] + # 3rd pt dx is not closer and it's closer to 2nd pt + p4 = [Point2D(0, 0), Point2D(3, 0), Point2D(4, 0)] + # 3rd pt dx is not closer and it's closer to 1st pt + p5 = [Point2D(0, 0), Point2D(3, 0), Point2D(-1, 0)] + # duplicate point doesn't affect outcome + dup = [Point2D(0, 0), Point2D(3, 0), Point2D(3, 0), Point2D(-1, 0)] + # symbolic + x = Symbol('x', positive=True) + s = [Point2D(a) for a in ((x, 1), (x + 3, 2), (x + 2, 2))] + + for points in (p1, p2, p3, p4, p5, dup, s): + d = how(i.distance(j) for i, j in subsets(set(points), 2)) + ans = a, b = list(func(*points))[0] + assert a.distance(b) == d + assert ans == _ordered_points(ans) + + # if the following ever fails, the above tests were not sufficient + # and the logical error in the routine should be fixed + points = set() + while len(points) != 7: + points.add(Point2D(randint(1, 100), randint(1, 100))) + points = list(points) + d = how(i.distance(j) for i, j in subsets(points, 2)) + ans = a, b = list(func(*points))[0] + assert a.distance(b) == d + assert ans == _ordered_points(ans) + + # equidistant points + a, b, c = ( + Point2D(0, 0), Point2D(1, 0), Point2D(S.Half, sqrt(3)/2)) + ans = {_ordered_points((i, j)) + for i, j in subsets((a, b, c), 2)} + assert closest_points(b, c, a) == ans + assert farthest_points(b, c, a) == ans + + # unique to farthest + points = [(1, 1), (1, 2), (3, 1), (-5, 2), (15, 4)] + assert farthest_points(*points) == { + (Point2D(-5, 2), Point2D(15, 4))} + points = [(1, -1), (1, -2), (3, -1), (-5, -2), (15, -4)] + assert farthest_points(*points) == { + (Point2D(-5, -2), Point2D(15, -4))} + assert farthest_points((1, 1), (0, 0)) == { + (Point2D(0, 0), Point2D(1, 1))} + raises(ValueError, lambda: farthest_points((1, 1))) + + +def test_are_coplanar(): + a = Line3D(Point3D(5, 0, 0), Point3D(1, -1, 1)) + b = Line3D(Point3D(0, -2, 0), Point3D(3, 1, 1)) + c = Line3D(Point3D(0, -1, 0), Point3D(5, -1, 9)) + d = Line(Point2D(0, 3), Point2D(1, 5)) + + assert are_coplanar(a, b, c) == False + assert are_coplanar(a, d) == False diff --git a/phivenv/Lib/site-packages/sympy/geometry/util.py b/phivenv/Lib/site-packages/sympy/geometry/util.py new file mode 100644 index 0000000000000000000000000000000000000000..1d8fb77550f2faea8185ff0c373b5f1680e623ec --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/geometry/util.py @@ -0,0 +1,731 @@ +"""Utility functions for geometrical entities. + +Contains +======== +intersection +convex_hull +closest_points +farthest_points +are_coplanar +are_similar + +""" + +from collections import deque +from math import sqrt as _sqrt + +from sympy import nsimplify +from .entity import GeometryEntity +from .exceptions import GeometryError +from .point import Point, Point2D, Point3D +from sympy.core.containers import OrderedSet +from sympy.core.exprtools import factor_terms +from sympy.core.function import Function, expand_mul +from sympy.core.numbers import Float +from sympy.core.sorting import ordered +from sympy.core.symbol import Symbol +from sympy.core.singleton import S +from sympy.polys.polytools import cancel +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.utilities.iterables import is_sequence + +from mpmath.libmp.libmpf import prec_to_dps + + +def find(x, equation): + """ + Checks whether a Symbol matching ``x`` is present in ``equation`` + or not. If present, the matching symbol is returned, else a + ValueError is raised. If ``x`` is a string the matching symbol + will have the same name; if ``x`` is a Symbol then it will be + returned if found. + + Examples + ======== + + >>> from sympy.geometry.util import find + >>> from sympy import Dummy + >>> from sympy.abc import x + >>> find('x', x) + x + >>> find('x', Dummy('x')) + _x + + The dummy symbol is returned since it has a matching name: + + >>> _.name == 'x' + True + >>> find(x, Dummy('x')) + Traceback (most recent call last): + ... + ValueError: could not find x + """ + + free = equation.free_symbols + xs = [i for i in free if (i.name if isinstance(x, str) else i) == x] + if not xs: + raise ValueError('could not find %s' % x) + if len(xs) != 1: + raise ValueError('ambiguous %s' % x) + return xs[0] + + +def _ordered_points(p): + """Return the tuple of points sorted numerically according to args""" + return tuple(sorted(p, key=lambda x: x.args)) + + +def are_coplanar(*e): + """ Returns True if the given entities are coplanar otherwise False + + Parameters + ========== + + e: entities to be checked for being coplanar + + Returns + ======= + + Boolean + + Examples + ======== + + >>> from sympy import Point3D, Line3D + >>> from sympy.geometry.util import are_coplanar + >>> a = Line3D(Point3D(5, 0, 0), Point3D(1, -1, 1)) + >>> b = Line3D(Point3D(0, -2, 0), Point3D(3, 1, 1)) + >>> c = Line3D(Point3D(0, -1, 0), Point3D(5, -1, 9)) + >>> are_coplanar(a, b, c) + False + + """ + from .line import LinearEntity3D + from .plane import Plane + # XXX update tests for coverage + + e = set(e) + # first work with a Plane if present + for i in list(e): + if isinstance(i, Plane): + e.remove(i) + return all(p.is_coplanar(i) for p in e) + + if all(isinstance(i, Point3D) for i in e): + if len(e) < 3: + return False + + # remove pts that are collinear with 2 pts + a, b = e.pop(), e.pop() + for i in list(e): + if Point3D.are_collinear(a, b, i): + e.remove(i) + + if not e: + return False + else: + # define a plane + p = Plane(a, b, e.pop()) + for i in e: + if i not in p: + return False + return True + else: + pt3d = [] + for i in e: + if isinstance(i, Point3D): + pt3d.append(i) + elif isinstance(i, LinearEntity3D): + pt3d.extend(i.args) + elif isinstance(i, GeometryEntity): # XXX we should have a GeometryEntity3D class so we can tell the difference between 2D and 3D -- here we just want to deal with 2D objects; if new 3D objects are encountered that we didn't handle above, an error should be raised + # all 2D objects have some Point that defines them; so convert those points to 3D pts by making z=0 + for p in i.args: + if isinstance(p, Point): + pt3d.append(Point3D(*(p.args + (0,)))) + return are_coplanar(*pt3d) + + +def are_similar(e1, e2): + """Are two geometrical entities similar. + + Can one geometrical entity be uniformly scaled to the other? + + Parameters + ========== + + e1 : GeometryEntity + e2 : GeometryEntity + + Returns + ======= + + are_similar : boolean + + Raises + ====== + + GeometryError + When `e1` and `e2` cannot be compared. + + Notes + ===== + + If the two objects are equal then they are similar. + + See Also + ======== + + sympy.geometry.entity.GeometryEntity.is_similar + + Examples + ======== + + >>> from sympy import Point, Circle, Triangle, are_similar + >>> c1, c2 = Circle(Point(0, 0), 4), Circle(Point(1, 4), 3) + >>> t1 = Triangle(Point(0, 0), Point(1, 0), Point(0, 1)) + >>> t2 = Triangle(Point(0, 0), Point(2, 0), Point(0, 2)) + >>> t3 = Triangle(Point(0, 0), Point(3, 0), Point(0, 1)) + >>> are_similar(t1, t2) + True + >>> are_similar(t1, t3) + False + + """ + if e1 == e2: + return True + is_similar1 = getattr(e1, 'is_similar', None) + if is_similar1: + return is_similar1(e2) + is_similar2 = getattr(e2, 'is_similar', None) + if is_similar2: + return is_similar2(e1) + n1 = e1.__class__.__name__ + n2 = e2.__class__.__name__ + raise GeometryError( + "Cannot test similarity between %s and %s" % (n1, n2)) + + +def centroid(*args): + """Find the centroid (center of mass) of the collection containing only Points, + Segments or Polygons. The centroid is the weighted average of the individual centroid + where the weights are the lengths (of segments) or areas (of polygons). + Overlapping regions will add to the weight of that region. + + If there are no objects (or a mixture of objects) then None is returned. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.line.Segment, + sympy.geometry.polygon.Polygon + + Examples + ======== + + >>> from sympy import Point, Segment, Polygon + >>> from sympy.geometry.util import centroid + >>> p = Polygon((0, 0), (10, 0), (10, 10)) + >>> q = p.translate(0, 20) + >>> p.centroid, q.centroid + (Point2D(20/3, 10/3), Point2D(20/3, 70/3)) + >>> centroid(p, q) + Point2D(20/3, 40/3) + >>> p, q = Segment((0, 0), (2, 0)), Segment((0, 0), (2, 2)) + >>> centroid(p, q) + Point2D(1, 2 - sqrt(2)) + >>> centroid(Point(0, 0), Point(2, 0)) + Point2D(1, 0) + + Stacking 3 polygons on top of each other effectively triples the + weight of that polygon: + + >>> p = Polygon((0, 0), (1, 0), (1, 1), (0, 1)) + >>> q = Polygon((1, 0), (3, 0), (3, 1), (1, 1)) + >>> centroid(p, q) + Point2D(3/2, 1/2) + >>> centroid(p, p, p, q) # centroid x-coord shifts left + Point2D(11/10, 1/2) + + Stacking the squares vertically above and below p has the same + effect: + + >>> centroid(p, p.translate(0, 1), p.translate(0, -1), q) + Point2D(11/10, 1/2) + + """ + from .line import Segment + from .polygon import Polygon + if args: + if all(isinstance(g, Point) for g in args): + c = Point(0, 0) + for g in args: + c += g + den = len(args) + elif all(isinstance(g, Segment) for g in args): + c = Point(0, 0) + L = 0 + for g in args: + l = g.length + c += g.midpoint*l + L += l + den = L + elif all(isinstance(g, Polygon) for g in args): + c = Point(0, 0) + A = 0 + for g in args: + a = g.area + c += g.centroid*a + A += a + den = A + c /= den + return c.func(*[i.simplify() for i in c.args]) + + +def closest_points(*args): + """Return the subset of points from a set of points that were + the closest to each other in the 2D plane. + + Parameters + ========== + + args + A collection of Points on 2D plane. + + Notes + ===== + + This can only be performed on a set of points whose coordinates can + be ordered on the number line. If there are no ties then a single + pair of Points will be in the set. + + Examples + ======== + + >>> from sympy import closest_points, Triangle + >>> Triangle(sss=(3, 4, 5)).args + (Point2D(0, 0), Point2D(3, 0), Point2D(3, 4)) + >>> closest_points(*_) + {(Point2D(0, 0), Point2D(3, 0))} + + References + ========== + + .. [1] https://www.cs.mcgill.ca/~cs251/ClosestPair/ClosestPairPS.html + + .. [2] Sweep line algorithm + https://en.wikipedia.org/wiki/Sweep_line_algorithm + + """ + p = [Point2D(i) for i in set(args)] + if len(p) < 2: + raise ValueError('At least 2 distinct points must be given.') + + try: + p.sort(key=lambda x: x.args) + except TypeError: + raise ValueError("The points could not be sorted.") + + if not all(i.is_Rational for j in p for i in j.args): + def hypot(x, y): + arg = x*x + y*y + if arg.is_Rational: + return _sqrt(arg) + return sqrt(arg) + else: + from math import hypot + + rv = [(0, 1)] + best_dist = hypot(p[1].x - p[0].x, p[1].y - p[0].y) + left = 0 + box = deque([0, 1]) + for i in range(2, len(p)): + while left < i and p[i][0] - p[left][0] > best_dist: + box.popleft() + left += 1 + + for j in box: + d = hypot(p[i].x - p[j].x, p[i].y - p[j].y) + if d < best_dist: + rv = [(j, i)] + elif d == best_dist: + rv.append((j, i)) + else: + continue + best_dist = d + box.append(i) + + return {tuple([p[i] for i in pair]) for pair in rv} + + +def convex_hull(*args, polygon=True): + """The convex hull surrounding the Points contained in the list of entities. + + Parameters + ========== + + args : a collection of Points, Segments and/or Polygons + + Optional parameters + =================== + + polygon : Boolean. If True, returns a Polygon, if false a tuple, see below. + Default is True. + + Returns + ======= + + convex_hull : Polygon if ``polygon`` is True else as a tuple `(U, L)` where + ``L`` and ``U`` are the lower and upper hulls, respectively. + + Notes + ===== + + This can only be performed on a set of points whose coordinates can + be ordered on the number line. + + See Also + ======== + + sympy.geometry.point.Point, sympy.geometry.polygon.Polygon + + Examples + ======== + + >>> from sympy import convex_hull + >>> points = [(1, 1), (1, 2), (3, 1), (-5, 2), (15, 4)] + >>> convex_hull(*points) + Polygon(Point2D(-5, 2), Point2D(1, 1), Point2D(3, 1), Point2D(15, 4)) + >>> convex_hull(*points, **dict(polygon=False)) + ([Point2D(-5, 2), Point2D(15, 4)], + [Point2D(-5, 2), Point2D(1, 1), Point2D(3, 1), Point2D(15, 4)]) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Graham_scan + + .. [2] Andrew's Monotone Chain Algorithm + (A.M. Andrew, + "Another Efficient Algorithm for Convex Hulls in Two Dimensions", 1979) + https://web.archive.org/web/20210511015444/http://geomalgorithms.com/a10-_hull-1.html + + """ + from .line import Segment + from .polygon import Polygon + p = OrderedSet() + for e in args: + if not isinstance(e, GeometryEntity): + try: + e = Point(e) + except NotImplementedError: + raise ValueError('%s is not a GeometryEntity and cannot be made into Point' % str(e)) + if isinstance(e, Point): + p.add(e) + elif isinstance(e, Segment): + p.update(e.points) + elif isinstance(e, Polygon): + p.update(e.vertices) + else: + raise NotImplementedError( + 'Convex hull for %s not implemented.' % type(e)) + + # make sure all our points are of the same dimension + if any(len(x) != 2 for x in p): + raise ValueError('Can only compute the convex hull in two dimensions') + + p = list(p) + if len(p) == 1: + return p[0] if polygon else (p[0], None) + elif len(p) == 2: + s = Segment(p[0], p[1]) + return s if polygon else (s, None) + + def _orientation(p, q, r): + '''Return positive if p-q-r are clockwise, neg if ccw, zero if + collinear.''' + return (q.y - p.y)*(r.x - p.x) - (q.x - p.x)*(r.y - p.y) + + # scan to find upper and lower convex hulls of a set of 2d points. + U = [] + L = [] + try: + p.sort(key=lambda x: x.args) + except TypeError: + raise ValueError("The points could not be sorted.") + for p_i in p: + while len(U) > 1 and _orientation(U[-2], U[-1], p_i) <= 0: + U.pop() + while len(L) > 1 and _orientation(L[-2], L[-1], p_i) >= 0: + L.pop() + U.append(p_i) + L.append(p_i) + U.reverse() + convexHull = tuple(L + U[1:-1]) + + if len(convexHull) == 2: + s = Segment(convexHull[0], convexHull[1]) + return s if polygon else (s, None) + if polygon: + return Polygon(*convexHull) + else: + U.reverse() + return (U, L) + +def farthest_points(*args): + """Return the subset of points from a set of points that were + the furthest apart from each other in the 2D plane. + + Parameters + ========== + + args + A collection of Points on 2D plane. + + Notes + ===== + + This can only be performed on a set of points whose coordinates can + be ordered on the number line. If there are no ties then a single + pair of Points will be in the set. + + Examples + ======== + + >>> from sympy.geometry import farthest_points, Triangle + >>> Triangle(sss=(3, 4, 5)).args + (Point2D(0, 0), Point2D(3, 0), Point2D(3, 4)) + >>> farthest_points(*_) + {(Point2D(0, 0), Point2D(3, 4))} + + References + ========== + + .. [1] https://code.activestate.com/recipes/117225-convex-hull-and-diameter-of-2d-point-sets/ + + .. [2] Rotating Callipers Technique + https://en.wikipedia.org/wiki/Rotating_calipers + + """ + + def rotatingCalipers(Points): + U, L = convex_hull(*Points, **{"polygon": False}) + + if L is None: + if isinstance(U, Point): + raise ValueError('At least two distinct points must be given.') + yield U.args + else: + i = 0 + j = len(L) - 1 + while i < len(U) - 1 or j > 0: + yield U[i], L[j] + # if all the way through one side of hull, advance the other side + if i == len(U) - 1: + j -= 1 + elif j == 0: + i += 1 + # still points left on both lists, compare slopes of next hull edges + # being careful to avoid divide-by-zero in slope calculation + elif (U[i+1].y - U[i].y) * (L[j].x - L[j-1].x) > \ + (L[j].y - L[j-1].y) * (U[i+1].x - U[i].x): + i += 1 + else: + j -= 1 + + p = [Point2D(i) for i in set(args)] + + if not all(i.is_Rational for j in p for i in j.args): + def hypot(x, y): + arg = x*x + y*y + if arg.is_Rational: + return _sqrt(arg) + return sqrt(arg) + else: + from math import hypot + + rv = [] + diam = 0 + for pair in rotatingCalipers(args): + h, q = _ordered_points(pair) + d = hypot(h.x - q.x, h.y - q.y) + if d > diam: + rv = [(h, q)] + elif d == diam: + rv.append((h, q)) + else: + continue + diam = d + + return set(rv) + + +def idiff(eq, y, x, n=1): + """Return ``dy/dx`` assuming that ``eq == 0``. + + Parameters + ========== + + y : the dependent variable or a list of dependent variables (with y first) + x : the variable that the derivative is being taken with respect to + n : the order of the derivative (default is 1) + + Examples + ======== + + >>> from sympy.abc import x, y, a + >>> from sympy.geometry.util import idiff + + >>> circ = x**2 + y**2 - 4 + >>> idiff(circ, y, x) + -x/y + >>> idiff(circ, y, x, 2).simplify() + (-x**2 - y**2)/y**3 + + Here, ``a`` is assumed to be independent of ``x``: + + >>> idiff(x + a + y, y, x) + -1 + + Now the x-dependence of ``a`` is made explicit by listing ``a`` after + ``y`` in a list. + + >>> idiff(x + a + y, [y, a], x) + -Derivative(a, x) - 1 + + See Also + ======== + + sympy.core.function.Derivative: represents unevaluated derivatives + sympy.core.function.diff: explicitly differentiates wrt symbols + + """ + if is_sequence(y): + dep = set(y) + y = y[0] + elif isinstance(y, Symbol): + dep = {y} + elif isinstance(y, Function): + pass + else: + raise ValueError("expecting x-dependent symbol(s) or function(s) but got: %s" % y) + + f = {s: Function(s.name)(x) for s in eq.free_symbols + if s != x and s in dep} + + if isinstance(y, Symbol): + dydx = Function(y.name)(x).diff(x) + else: + dydx = y.diff(x) + + eq = eq.subs(f) + derivs = {} + for i in range(n): + # equation will be linear in dydx, a*dydx + b, so dydx = -b/a + deq = eq.diff(x) + b = deq.xreplace({dydx: S.Zero}) + a = (deq - b).xreplace({dydx: S.One}) + yp = factor_terms(expand_mul(cancel((-b/a).subs(derivs)), deep=False)) + if i == n - 1: + return yp.subs([(v, k) for k, v in f.items()]) + derivs[dydx] = yp + eq = dydx - yp + dydx = dydx.diff(x) + + +def intersection(*entities, pairwise=False, **kwargs): + """The intersection of a collection of GeometryEntity instances. + + Parameters + ========== + entities : sequence of GeometryEntity + pairwise (keyword argument) : Can be either True or False + + Returns + ======= + intersection : list of GeometryEntity + + Raises + ====== + NotImplementedError + When unable to calculate intersection. + + Notes + ===== + The intersection of any geometrical entity with itself should return + a list with one item: the entity in question. + An intersection requires two or more entities. If only a single + entity is given then the function will return an empty list. + It is possible for `intersection` to miss intersections that one + knows exists because the required quantities were not fully + simplified internally. + Reals should be converted to Rationals, e.g. Rational(str(real_num)) + or else failures due to floating point issues may result. + + Case 1: When the keyword argument 'pairwise' is False (default value): + In this case, the function returns a list of intersections common to + all entities. + + Case 2: When the keyword argument 'pairwise' is True: + In this case, the functions returns a list intersections that occur + between any pair of entities. + + See Also + ======== + + sympy.geometry.entity.GeometryEntity.intersection + + Examples + ======== + + >>> from sympy import Ray, Circle, intersection + >>> c = Circle((0, 1), 1) + >>> intersection(c, c.center) + [] + >>> right = Ray((0, 0), (1, 0)) + >>> up = Ray((0, 0), (0, 1)) + >>> intersection(c, right, up) + [Point2D(0, 0)] + >>> intersection(c, right, up, pairwise=True) + [Point2D(0, 0), Point2D(0, 2)] + >>> left = Ray((1, 0), (0, 0)) + >>> intersection(right, left) + [Segment2D(Point2D(0, 0), Point2D(1, 0))] + + """ + if len(entities) <= 1: + return [] + + entities = list(entities) + prec = None + for i, e in enumerate(entities): + if not isinstance(e, GeometryEntity): + # entities may be an immutable tuple + e = Point(e) + # convert to exact Rationals + d = {} + for f in e.atoms(Float): + prec = f._prec if prec is None else min(f._prec, prec) + d.setdefault(f, nsimplify(f, rational=True)) + entities[i] = e.xreplace(d) + + if not pairwise: + # find the intersection common to all objects + res = entities[0].intersection(entities[1]) + for entity in entities[2:]: + newres = [] + for x in res: + newres.extend(x.intersection(entity)) + res = newres + else: + # find all pairwise intersections + ans = [] + for j in range(len(entities)): + for k in range(j + 1, len(entities)): + ans.extend(intersection(entities[j], entities[k])) + res = list(ordered(set(ans))) + + # convert back to Floats + if prec is not None: + p = prec_to_dps(prec) + res = [i.n(p) for i in res] + return res diff --git a/phivenv/Lib/site-packages/sympy/holonomic/__init__.py b/phivenv/Lib/site-packages/sympy/holonomic/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..45412acad0ab9e5c7424b1888648a638ef208142 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/__init__.py @@ -0,0 +1,18 @@ +r""" +The :py:mod:`~sympy.holonomic` module is intended to deal with holonomic functions along +with various operations on them like addition, multiplication, composition, +integration and differentiation. The module also implements various kinds of +conversions such as converting holonomic functions to a different form and the +other way around. +""" + +from .holonomic import (DifferentialOperator, HolonomicFunction, DifferentialOperators, + from_hyper, from_meijerg, expr_to_holonomic) +from .recurrence import RecurrenceOperators, RecurrenceOperator, HolonomicSequence + +__all__ = [ + 'DifferentialOperator', 'HolonomicFunction', 'DifferentialOperators', + 'from_hyper', 'from_meijerg', 'expr_to_holonomic', + + 'RecurrenceOperators', 'RecurrenceOperator', 'HolonomicSequence', +] diff --git a/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40b1aa41e455145eb3eaa9492068cd08335603dd Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/holonomic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/holonomic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20c737a8e82dccf746a2d9e3ce8bad5d671d5858 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/holonomic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/holonomicerrors.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/holonomicerrors.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a680875022ec624c49b92a2f638eaf2243880c7d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/holonomicerrors.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/numerical.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/numerical.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00c7b64050de8cddc0cf901c43a960dfe30236cf Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/numerical.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/recurrence.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/recurrence.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af8f8f4a741aa33df409de28eec1f2a6c00af3ea Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/__pycache__/recurrence.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/holonomic.py b/phivenv/Lib/site-packages/sympy/holonomic/holonomic.py new file mode 100644 index 0000000000000000000000000000000000000000..e31c4d4511d4c07aa4049a62253cdb060758cf3d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/holonomic.py @@ -0,0 +1,2765 @@ +""" +This module implements Holonomic Functions and +various operations on them. +""" + +from sympy.core import Add, Mul, Pow +from sympy.core.numbers import (NaN, Infinity, NegativeInfinity, Float, I, pi, + equal_valued, int_valued) +from sympy.core.singleton import S +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy, Symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import binomial, factorial, rf +from sympy.functions.elementary.exponential import exp_polar, exp, log +from sympy.functions.elementary.hyperbolic import (cosh, sinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin, sinc) +from sympy.functions.special.error_functions import (Ci, Shi, Si, erf, erfc, erfi) +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import hyper, meijerg +from sympy.integrals import meijerint +from sympy.matrices import Matrix +from sympy.polys.rings import PolyElement +from sympy.polys.fields import FracElement +from sympy.polys.domains import QQ, RR +from sympy.polys.polyclasses import DMF +from sympy.polys.polyroots import roots +from sympy.polys.polytools import Poly +from sympy.polys.matrices import DomainMatrix +from sympy.printing import sstr +from sympy.series.limits import limit +from sympy.series.order import Order +from sympy.simplify.hyperexpand import hyperexpand +from sympy.simplify.simplify import nsimplify +from sympy.solvers.solvers import solve + +from .recurrence import HolonomicSequence, RecurrenceOperator, RecurrenceOperators +from .holonomicerrors import (NotPowerSeriesError, NotHyperSeriesError, + SingularityError, NotHolonomicError) + + +def _find_nonzero_solution(r, homosys): + ones = lambda shape: DomainMatrix.ones(shape, r.domain) + particular, nullspace = r._solve(homosys) + nullity = nullspace.shape[0] + nullpart = ones((1, nullity)) * nullspace + sol = (particular + nullpart).transpose() + return sol + + + +def DifferentialOperators(base, generator): + r""" + This function is used to create annihilators using ``Dx``. + + Explanation + =========== + + Returns an Algebra of Differential Operators also called Weyl Algebra + and the operator for differentiation i.e. the ``Dx`` operator. + + Parameters + ========== + + base: + Base polynomial ring for the algebra. + The base polynomial ring is the ring of polynomials in :math:`x` that + will appear as coefficients in the operators. + generator: + Generator of the algebra which can + be either a noncommutative ``Symbol`` or a string. e.g. "Dx" or "D". + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy.abc import x + >>> from sympy.holonomic.holonomic import DifferentialOperators + >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + >>> R + Univariate Differential Operator Algebra in intermediate Dx over the base ring ZZ[x] + >>> Dx*x + (1) + (x)*Dx + """ + + ring = DifferentialOperatorAlgebra(base, generator) + return (ring, ring.derivative_operator) + + +class DifferentialOperatorAlgebra: + r""" + An Ore Algebra is a set of noncommutative polynomials in the + intermediate ``Dx`` and coefficients in a base polynomial ring :math:`A`. + It follows the commutation rule: + + .. math :: + Dxa = \sigma(a)Dx + \delta(a) + + for :math:`a \subset A`. + + Where :math:`\sigma: A \Rightarrow A` is an endomorphism and :math:`\delta: A \rightarrow A` + is a skew-derivation i.e. :math:`\delta(ab) = \delta(a) b + \sigma(a) \delta(b)`. + + If one takes the sigma as identity map and delta as the standard derivation + then it becomes the algebra of Differential Operators also called + a Weyl Algebra i.e. an algebra whose elements are Differential Operators. + + This class represents a Weyl Algebra and serves as the parent ring for + Differential Operators. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy import symbols + >>> from sympy.holonomic.holonomic import DifferentialOperators + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + >>> R + Univariate Differential Operator Algebra in intermediate Dx over the base ring + ZZ[x] + + See Also + ======== + + DifferentialOperator + """ + + def __init__(self, base, generator): + # the base polynomial ring for the algebra + self.base = base + # the operator representing differentiation i.e. `Dx` + self.derivative_operator = DifferentialOperator( + [base.zero, base.one], self) + + if generator is None: + self.gen_symbol = Symbol('Dx', commutative=False) + else: + if isinstance(generator, str): + self.gen_symbol = Symbol(generator, commutative=False) + elif isinstance(generator, Symbol): + self.gen_symbol = generator + + def __str__(self): + string = 'Univariate Differential Operator Algebra in intermediate '\ + + sstr(self.gen_symbol) + ' over the base ring ' + \ + (self.base).__str__() + + return string + + __repr__ = __str__ + + def __eq__(self, other): + return self.base == other.base and \ + self.gen_symbol == other.gen_symbol + + +class DifferentialOperator: + """ + Differential Operators are elements of Weyl Algebra. The Operators + are defined by a list of polynomials in the base ring and the + parent ring of the Operator i.e. the algebra it belongs to. + + Explanation + =========== + + Takes a list of polynomials for each power of ``Dx`` and the + parent ring which must be an instance of DifferentialOperatorAlgebra. + + A Differential Operator can be created easily using + the operator ``Dx``. See examples below. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import DifferentialOperator, DifferentialOperators + >>> from sympy import ZZ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx') + + >>> DifferentialOperator([0, 1, x**2], R) + (1)*Dx + (x**2)*Dx**2 + + >>> (x*Dx*x + 1 - Dx**2)**2 + (2*x**2 + 2*x + 1) + (4*x**3 + 2*x**2 - 4)*Dx + (x**4 - 6*x - 2)*Dx**2 + (-2*x**2)*Dx**3 + (1)*Dx**4 + + See Also + ======== + + DifferentialOperatorAlgebra + """ + + _op_priority = 20 + + def __init__(self, list_of_poly, parent): + """ + Parameters + ========== + + list_of_poly: + List of polynomials belonging to the base ring of the algebra. + parent: + Parent algebra of the operator. + """ + + # the parent ring for this operator + # must be an DifferentialOperatorAlgebra object + self.parent = parent + base = self.parent.base + self.x = base.gens[0] if isinstance(base.gens[0], Symbol) else base.gens[0][0] + # sequence of polynomials in x for each power of Dx + # the list should not have trailing zeroes + # represents the operator + # convert the expressions into ring elements using from_sympy + for i, j in enumerate(list_of_poly): + if not isinstance(j, base.dtype): + list_of_poly[i] = base.from_sympy(sympify(j)) + else: + list_of_poly[i] = base.from_sympy(base.to_sympy(j)) + + self.listofpoly = list_of_poly + # highest power of `Dx` + self.order = len(self.listofpoly) - 1 + + def __mul__(self, other): + """ + Multiplies two DifferentialOperator and returns another + DifferentialOperator instance using the commutation rule + Dx*a = a*Dx + a' + """ + + listofself = self.listofpoly + if isinstance(other, DifferentialOperator): + listofother = other.listofpoly + elif isinstance(other, self.parent.base.dtype): + listofother = [other] + else: + listofother = [self.parent.base.from_sympy(sympify(other))] + + # multiplies a polynomial `b` with a list of polynomials + def _mul_dmp_diffop(b, listofother): + if isinstance(listofother, list): + return [i * b for i in listofother] + return [b * listofother] + + sol = _mul_dmp_diffop(listofself[0], listofother) + + # compute Dx^i * b + def _mul_Dxi_b(b): + sol1 = [self.parent.base.zero] + sol2 = [] + + if isinstance(b, list): + for i in b: + sol1.append(i) + sol2.append(i.diff()) + else: + sol1.append(self.parent.base.from_sympy(b)) + sol2.append(self.parent.base.from_sympy(b).diff()) + + return _add_lists(sol1, sol2) + + for i in range(1, len(listofself)): + # find Dx^i * b in ith iteration + listofother = _mul_Dxi_b(listofother) + # solution = solution + listofself[i] * (Dx^i * b) + sol = _add_lists(sol, _mul_dmp_diffop(listofself[i], listofother)) + + return DifferentialOperator(sol, self.parent) + + def __rmul__(self, other): + if not isinstance(other, DifferentialOperator): + + if not isinstance(other, self.parent.base.dtype): + other = (self.parent.base).from_sympy(sympify(other)) + + sol = [other * j for j in self.listofpoly] + return DifferentialOperator(sol, self.parent) + + def __add__(self, other): + if isinstance(other, DifferentialOperator): + + sol = _add_lists(self.listofpoly, other.listofpoly) + return DifferentialOperator(sol, self.parent) + + list_self = self.listofpoly + if not isinstance(other, self.parent.base.dtype): + list_other = [((self.parent).base).from_sympy(sympify(other))] + else: + list_other = [other] + sol = [list_self[0] + list_other[0]] + list_self[1:] + return DifferentialOperator(sol, self.parent) + + __radd__ = __add__ + + def __sub__(self, other): + return self + (-1) * other + + def __rsub__(self, other): + return (-1) * self + other + + def __neg__(self): + return -1 * self + + def __truediv__(self, other): + return self * (S.One / other) + + def __pow__(self, n): + if n == 1: + return self + result = DifferentialOperator([self.parent.base.one], self.parent) + if n == 0: + return result + # if self is `Dx` + if self.listofpoly == self.parent.derivative_operator.listofpoly: + sol = [self.parent.base.zero]*n + [self.parent.base.one] + return DifferentialOperator(sol, self.parent) + x = self + while True: + if n % 2: + result *= x + n >>= 1 + if not n: + break + x *= x + return result + + def __str__(self): + listofpoly = self.listofpoly + print_str = '' + + for i, j in enumerate(listofpoly): + if j == self.parent.base.zero: + continue + + j = self.parent.base.to_sympy(j) + + if i == 0: + print_str += '(' + sstr(j) + ')' + continue + + if print_str: + print_str += ' + ' + + if i == 1: + print_str += '(' + sstr(j) + ')*%s' %(self.parent.gen_symbol) + continue + + print_str += '(' + sstr(j) + ')' + '*%s**' %(self.parent.gen_symbol) + sstr(i) + + return print_str + + __repr__ = __str__ + + def __eq__(self, other): + if isinstance(other, DifferentialOperator): + return self.listofpoly == other.listofpoly and \ + self.parent == other.parent + return self.listofpoly[0] == other and \ + all(i is self.parent.base.zero for i in self.listofpoly[1:]) + + def is_singular(self, x0): + """ + Checks if the differential equation is singular at x0. + """ + + base = self.parent.base + return x0 in roots(base.to_sympy(self.listofpoly[-1]), self.x) + + +class HolonomicFunction: + r""" + A Holonomic Function is a solution to a linear homogeneous ordinary + differential equation with polynomial coefficients. This differential + equation can also be represented by an annihilator i.e. a Differential + Operator ``L`` such that :math:`L.f = 0`. For uniqueness of these functions, + initial conditions can also be provided along with the annihilator. + + Explanation + =========== + + Holonomic functions have closure properties and thus forms a ring. + Given two Holonomic Functions f and g, their sum, product, + integral and derivative is also a Holonomic Function. + + For ordinary points initial condition should be a vector of values of + the derivatives i.e. :math:`[y(x_0), y'(x_0), y''(x_0) ... ]`. + + For regular singular points initial conditions can also be provided in this + format: + :math:`{s0: [C_0, C_1, ...], s1: [C^1_0, C^1_1, ...], ...}` + where s0, s1, ... are the roots of indicial equation and vectors + :math:`[C_0, C_1, ...], [C^0_0, C^0_1, ...], ...` are the corresponding initial + terms of the associated power series. See Examples below. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import QQ + >>> from sympy import symbols, S + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx') + + >>> p = HolonomicFunction(Dx - 1, x, 0, [1]) # e^x + >>> q = HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]) # sin(x) + + >>> p + q # annihilator of e^x + sin(x) + HolonomicFunction((-1) + (1)*Dx + (-1)*Dx**2 + (1)*Dx**3, x, 0, [1, 2, 1]) + + >>> p * q # annihilator of e^x * sin(x) + HolonomicFunction((2) + (-2)*Dx + (1)*Dx**2, x, 0, [0, 1]) + + An example of initial conditions for regular singular points, + the indicial equation has only one root `1/2`. + + >>> HolonomicFunction(-S(1)/2 + x*Dx, x, 0, {S(1)/2: [1]}) + HolonomicFunction((-1/2) + (x)*Dx, x, 0, {1/2: [1]}) + + >>> HolonomicFunction(-S(1)/2 + x*Dx, x, 0, {S(1)/2: [1]}).to_expr() + sqrt(x) + + To plot a Holonomic Function, one can use `.evalf()` for numerical + computation. Here's an example on `sin(x)**2/x` using numpy and matplotlib. + + >>> import sympy.holonomic # doctest: +SKIP + >>> from sympy import var, sin # doctest: +SKIP + >>> import matplotlib.pyplot as plt # doctest: +SKIP + >>> import numpy as np # doctest: +SKIP + >>> var("x") # doctest: +SKIP + >>> r = np.linspace(1, 5, 100) # doctest: +SKIP + >>> y = sympy.holonomic.expr_to_holonomic(sin(x)**2/x, x0=1).evalf(r) # doctest: +SKIP + >>> plt.plot(r, y, label="holonomic function") # doctest: +SKIP + >>> plt.show() # doctest: +SKIP + + """ + + _op_priority = 20 + + def __init__(self, annihilator, x, x0=0, y0=None): + """ + + Parameters + ========== + + annihilator: + Annihilator of the Holonomic Function, represented by a + `DifferentialOperator` object. + x: + Variable of the function. + x0: + The point at which initial conditions are stored. + Generally an integer. + y0: + The initial condition. The proper format for the initial condition + is described in class docstring. To make the function unique, + length of the vector `y0` should be equal to or greater than the + order of differential equation. + """ + + # initial condition + self.y0 = y0 + # the point for initial conditions, default is zero. + self.x0 = x0 + # differential operator L such that L.f = 0 + self.annihilator = annihilator + self.x = x + + def __str__(self): + if self._have_init_cond(): + str_sol = 'HolonomicFunction(%s, %s, %s, %s)' % (str(self.annihilator),\ + sstr(self.x), sstr(self.x0), sstr(self.y0)) + else: + str_sol = 'HolonomicFunction(%s, %s)' % (str(self.annihilator),\ + sstr(self.x)) + + return str_sol + + __repr__ = __str__ + + def unify(self, other): + """ + Unifies the base polynomial ring of a given two Holonomic + Functions. + """ + + R1 = self.annihilator.parent.base + R2 = other.annihilator.parent.base + + dom1 = R1.dom + dom2 = R2.dom + + if R1 == R2: + return (self, other) + + R = (dom1.unify(dom2)).old_poly_ring(self.x) + + newparent, _ = DifferentialOperators(R, str(self.annihilator.parent.gen_symbol)) + + sol1 = [R1.to_sympy(i) for i in self.annihilator.listofpoly] + sol2 = [R2.to_sympy(i) for i in other.annihilator.listofpoly] + + sol1 = DifferentialOperator(sol1, newparent) + sol2 = DifferentialOperator(sol2, newparent) + + sol1 = HolonomicFunction(sol1, self.x, self.x0, self.y0) + sol2 = HolonomicFunction(sol2, other.x, other.x0, other.y0) + + return (sol1, sol2) + + def is_singularics(self): + """ + Returns True if the function have singular initial condition + in the dictionary format. + + Returns False if the function have ordinary initial condition + in the list format. + + Returns None for all other cases. + """ + + if isinstance(self.y0, dict): + return True + elif isinstance(self.y0, list): + return False + + def _have_init_cond(self): + """ + Checks if the function have initial condition. + """ + return bool(self.y0) + + def _singularics_to_ord(self): + """ + Converts a singular initial condition to ordinary if possible. + """ + a = list(self.y0)[0] + b = self.y0[a] + + if len(self.y0) == 1 and a == int(a) and a > 0: + a = int(a) + y0 = [S.Zero] * a + y0 += [j * factorial(a + i) for i, j in enumerate(b)] + + return HolonomicFunction(self.annihilator, self.x, self.x0, y0) + + def __add__(self, other): + # if the ground domains are different + if self.annihilator.parent.base != other.annihilator.parent.base: + a, b = self.unify(other) + return a + b + + deg1 = self.annihilator.order + deg2 = other.annihilator.order + dim = max(deg1, deg2) + R = self.annihilator.parent.base + K = R.get_field() + + rowsself = [self.annihilator] + rowsother = [other.annihilator] + gen = self.annihilator.parent.derivative_operator + + # constructing annihilators up to order dim + for i in range(dim - deg1): + diff1 = (gen * rowsself[-1]) + rowsself.append(diff1) + + for i in range(dim - deg2): + diff2 = (gen * rowsother[-1]) + rowsother.append(diff2) + + row = rowsself + rowsother + + # constructing the matrix of the ansatz + r = [] + + for expr in row: + p = [] + for i in range(dim + 1): + if i >= len(expr.listofpoly): + p.append(K.zero) + else: + p.append(K.new(expr.listofpoly[i].to_list())) + r.append(p) + + # solving the linear system using gauss jordan solver + r = DomainMatrix(r, (len(row), dim+1), K).transpose() + homosys = DomainMatrix.zeros((dim+1, 1), K) + sol = _find_nonzero_solution(r, homosys) + + # if a solution is not obtained then increasing the order by 1 in each + # iteration + while sol.is_zero_matrix: + dim += 1 + + diff1 = (gen * rowsself[-1]) + rowsself.append(diff1) + + diff2 = (gen * rowsother[-1]) + rowsother.append(diff2) + + row = rowsself + rowsother + r = [] + + for expr in row: + p = [] + for i in range(dim + 1): + if i >= len(expr.listofpoly): + p.append(K.zero) + else: + p.append(K.new(expr.listofpoly[i].to_list())) + r.append(p) + + # solving the linear system using gauss jordan solver + r = DomainMatrix(r, (len(row), dim+1), K).transpose() + homosys = DomainMatrix.zeros((dim+1, 1), K) + sol = _find_nonzero_solution(r, homosys) + + # taking only the coefficients needed to multiply with `self` + # can be also be done the other way by taking R.H.S and multiplying with + # `other` + sol = sol.flat()[:dim + 1 - deg1] + sol1 = _normalize(sol, self.annihilator.parent) + # annihilator of the solution + sol = sol1 * (self.annihilator) + sol = _normalize(sol.listofpoly, self.annihilator.parent, negative=False) + + if not (self._have_init_cond() and other._have_init_cond()): + return HolonomicFunction(sol, self.x) + + # both the functions have ordinary initial conditions + if self.is_singularics() == False and other.is_singularics() == False: + + # directly add the corresponding value + if self.x0 == other.x0: + # try to extended the initial conditions + # using the annihilator + y1 = _extend_y0(self, sol.order) + y2 = _extend_y0(other, sol.order) + y0 = [a + b for a, b in zip(y1, y2)] + return HolonomicFunction(sol, self.x, self.x0, y0) + + # change the initial conditions to a same point + selfat0 = self.annihilator.is_singular(0) + otherat0 = other.annihilator.is_singular(0) + if self.x0 == 0 and not selfat0 and not otherat0: + return self + other.change_ics(0) + if other.x0 == 0 and not selfat0 and not otherat0: + return self.change_ics(0) + other + + selfatx0 = self.annihilator.is_singular(self.x0) + otheratx0 = other.annihilator.is_singular(self.x0) + if not selfatx0 and not otheratx0: + return self + other.change_ics(self.x0) + return self.change_ics(other.x0) + other + + if self.x0 != other.x0: + return HolonomicFunction(sol, self.x) + + # if the functions have singular_ics + y1 = None + y2 = None + + if self.is_singularics() == False and other.is_singularics() == True: + # convert the ordinary initial condition to singular. + _y0 = [j / factorial(i) for i, j in enumerate(self.y0)] + y1 = {S.Zero: _y0} + y2 = other.y0 + elif self.is_singularics() == True and other.is_singularics() == False: + _y0 = [j / factorial(i) for i, j in enumerate(other.y0)] + y1 = self.y0 + y2 = {S.Zero: _y0} + elif self.is_singularics() == True and other.is_singularics() == True: + y1 = self.y0 + y2 = other.y0 + + # computing singular initial condition for the result + # taking union of the series terms of both functions + y0 = {} + for i in y1: + # add corresponding initial terms if the power + # on `x` is same + if i in y2: + y0[i] = [a + b for a, b in zip(y1[i], y2[i])] + else: + y0[i] = y1[i] + for i in y2: + if i not in y1: + y0[i] = y2[i] + return HolonomicFunction(sol, self.x, self.x0, y0) + + def integrate(self, limits, initcond=False): + """ + Integrates the given holonomic function. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import QQ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx') + >>> HolonomicFunction(Dx - 1, x, 0, [1]).integrate((x, 0, x)) # e^x - 1 + HolonomicFunction((-1)*Dx + (1)*Dx**2, x, 0, [0, 1]) + >>> HolonomicFunction(Dx**2 + 1, x, 0, [1, 0]).integrate((x, 0, x)) + HolonomicFunction((1)*Dx + (1)*Dx**3, x, 0, [0, 1, 0]) + """ + + # to get the annihilator, just multiply by Dx from right + D = self.annihilator.parent.derivative_operator + + # if the function have initial conditions of the series format + if self.is_singularics() == True: + + r = self._singularics_to_ord() + if r: + return r.integrate(limits, initcond=initcond) + + # computing singular initial condition for the function + # produced after integration. + y0 = {} + for i in self.y0: + c = self.y0[i] + c2 = [] + for j, cj in enumerate(c): + if cj == 0: + c2.append(S.Zero) + + # if power on `x` is -1, the integration becomes log(x) + # TODO: Implement this case + elif i + j + 1 == 0: + raise NotImplementedError("logarithmic terms in the series are not supported") + else: + c2.append(cj / S(i + j + 1)) + y0[i + 1] = c2 + + if hasattr(limits, "__iter__"): + raise NotImplementedError("Definite integration for singular initial conditions") + + return HolonomicFunction(self.annihilator * D, self.x, self.x0, y0) + + # if no initial conditions are available for the function + if not self._have_init_cond(): + if initcond: + return HolonomicFunction(self.annihilator * D, self.x, self.x0, [S.Zero]) + return HolonomicFunction(self.annihilator * D, self.x) + + # definite integral + # initial conditions for the answer will be stored at point `a`, + # where `a` is the lower limit of the integrand + if hasattr(limits, "__iter__"): + + if len(limits) == 3 and limits[0] == self.x: + x0 = self.x0 + a = limits[1] + b = limits[2] + definite = True + + else: + definite = False + + y0 = [S.Zero] + y0 += self.y0 + + indefinite_integral = HolonomicFunction(self.annihilator * D, self.x, self.x0, y0) + + if not definite: + return indefinite_integral + + # use evalf to get the values at `a` + if x0 != a: + try: + indefinite_expr = indefinite_integral.to_expr() + except (NotHyperSeriesError, NotPowerSeriesError): + indefinite_expr = None + + if indefinite_expr: + lower = indefinite_expr.subs(self.x, a) + if isinstance(lower, NaN): + lower = indefinite_expr.limit(self.x, a) + else: + lower = indefinite_integral.evalf(a) + + if b == self.x: + y0[0] = y0[0] - lower + return HolonomicFunction(self.annihilator * D, self.x, x0, y0) + + elif S(b).is_Number: + if indefinite_expr: + upper = indefinite_expr.subs(self.x, b) + if isinstance(upper, NaN): + upper = indefinite_expr.limit(self.x, b) + else: + upper = indefinite_integral.evalf(b) + + return upper - lower + + + # if the upper limit is `x`, the answer will be a function + if b == self.x: + return HolonomicFunction(self.annihilator * D, self.x, a, y0) + + # if the upper limits is a Number, a numerical value will be returned + elif S(b).is_Number: + try: + s = HolonomicFunction(self.annihilator * D, self.x, a,\ + y0).to_expr() + indefinite = s.subs(self.x, b) + if not isinstance(indefinite, NaN): + return indefinite + else: + return s.limit(self.x, b) + except (NotHyperSeriesError, NotPowerSeriesError): + return HolonomicFunction(self.annihilator * D, self.x, a, y0).evalf(b) + + return HolonomicFunction(self.annihilator * D, self.x) + + def diff(self, *args, **kwargs): + r""" + Differentiation of the given Holonomic function. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import ZZ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx') + >>> HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).diff().to_expr() + cos(x) + >>> HolonomicFunction(Dx - 2, x, 0, [1]).diff().to_expr() + 2*exp(2*x) + + See Also + ======== + + integrate + """ + kwargs.setdefault('evaluate', True) + if args: + if args[0] != self.x: + return S.Zero + elif len(args) == 2: + sol = self + for i in range(args[1]): + sol = sol.diff(args[0]) + return sol + + ann = self.annihilator + + # if the function is constant. + if ann.listofpoly[0] == ann.parent.base.zero and ann.order == 1: + return S.Zero + + # if the coefficient of y in the differential equation is zero. + # a shifting is done to compute the answer in this case. + elif ann.listofpoly[0] == ann.parent.base.zero: + + sol = DifferentialOperator(ann.listofpoly[1:], ann.parent) + + if self._have_init_cond(): + # if ordinary initial condition + if self.is_singularics() == False: + return HolonomicFunction(sol, self.x, self.x0, self.y0[1:]) + # TODO: support for singular initial condition + return HolonomicFunction(sol, self.x) + else: + return HolonomicFunction(sol, self.x) + + # the general algorithm + R = ann.parent.base + K = R.get_field() + + seq_dmf = [K.new(i.to_list()) for i in ann.listofpoly] + + # -y = a1*y'/a0 + a2*y''/a0 ... + an*y^n/a0 + rhs = [i / seq_dmf[0] for i in seq_dmf[1:]] + rhs.insert(0, K.zero) + + # differentiate both lhs and rhs + sol = _derivate_diff_eq(rhs, K) + + # add the term y' in lhs to rhs + sol = _add_lists(sol, [K.zero, K.one]) + + sol = _normalize(sol[1:], self.annihilator.parent, negative=False) + + if not self._have_init_cond() or self.is_singularics() == True: + return HolonomicFunction(sol, self.x) + + y0 = _extend_y0(self, sol.order + 1)[1:] + return HolonomicFunction(sol, self.x, self.x0, y0) + + def __eq__(self, other): + if self.annihilator != other.annihilator or self.x != other.x: + return False + if self._have_init_cond() and other._have_init_cond(): + return self.x0 == other.x0 and self.y0 == other.y0 + return True + + def __mul__(self, other): + ann_self = self.annihilator + + if not isinstance(other, HolonomicFunction): + other = sympify(other) + + if other.has(self.x): + raise NotImplementedError(" Can't multiply a HolonomicFunction and expressions/functions.") + + if not self._have_init_cond(): + return self + y0 = _extend_y0(self, ann_self.order) + y1 = [(Poly.new(j, self.x) * other).rep for j in y0] + return HolonomicFunction(ann_self, self.x, self.x0, y1) + + if self.annihilator.parent.base != other.annihilator.parent.base: + a, b = self.unify(other) + return a * b + + ann_other = other.annihilator + + a = ann_self.order + b = ann_other.order + + R = ann_self.parent.base + K = R.get_field() + + list_self = [K.new(j.to_list()) for j in ann_self.listofpoly] + list_other = [K.new(j.to_list()) for j in ann_other.listofpoly] + + # will be used to reduce the degree + self_red = [-list_self[i] / list_self[a] for i in range(a)] + + other_red = [-list_other[i] / list_other[b] for i in range(b)] + + # coeff_mull[i][j] is the coefficient of Dx^i(f).Dx^j(g) + coeff_mul = [[K.zero for i in range(b + 1)] for j in range(a + 1)] + coeff_mul[0][0] = K.one + + # making the ansatz + lin_sys_elements = [[coeff_mul[i][j] for i in range(a) for j in range(b)]] + lin_sys = DomainMatrix(lin_sys_elements, (1, a*b), K).transpose() + + homo_sys = DomainMatrix.zeros((a*b, 1), K) + + sol = _find_nonzero_solution(lin_sys, homo_sys) + + # until a non trivial solution is found + while sol.is_zero_matrix: + + # updating the coefficients Dx^i(f).Dx^j(g) for next degree + for i in range(a - 1, -1, -1): + for j in range(b - 1, -1, -1): + coeff_mul[i][j + 1] += coeff_mul[i][j] + coeff_mul[i + 1][j] += coeff_mul[i][j] + if isinstance(coeff_mul[i][j], K.dtype): + coeff_mul[i][j] = DMFdiff(coeff_mul[i][j], K) + else: + coeff_mul[i][j] = coeff_mul[i][j].diff(self.x) + + # reduce the terms to lower power using annihilators of f, g + for i in range(a + 1): + if coeff_mul[i][b].is_zero: + continue + for j in range(b): + coeff_mul[i][j] += other_red[j] * coeff_mul[i][b] + coeff_mul[i][b] = K.zero + + # not d2 + 1, as that is already covered in previous loop + for j in range(b): + if coeff_mul[a][j] == 0: + continue + for i in range(a): + coeff_mul[i][j] += self_red[i] * coeff_mul[a][j] + coeff_mul[a][j] = K.zero + + lin_sys_elements.append([coeff_mul[i][j] for i in range(a) for j in range(b)]) + lin_sys = DomainMatrix(lin_sys_elements, (len(lin_sys_elements), a*b), K).transpose() + + sol = _find_nonzero_solution(lin_sys, homo_sys) + + sol_ann = _normalize(sol.flat(), self.annihilator.parent, negative=False) + + if not (self._have_init_cond() and other._have_init_cond()): + return HolonomicFunction(sol_ann, self.x) + + if self.is_singularics() == False and other.is_singularics() == False: + + # if both the conditions are at same point + if self.x0 == other.x0: + + # try to find more initial conditions + y0_self = _extend_y0(self, sol_ann.order) + y0_other = _extend_y0(other, sol_ann.order) + # h(x0) = f(x0) * g(x0) + y0 = [y0_self[0] * y0_other[0]] + + # coefficient of Dx^j(f)*Dx^i(g) in Dx^i(fg) + for i in range(1, min(len(y0_self), len(y0_other))): + coeff = [[0 for i in range(i + 1)] for j in range(i + 1)] + for j in range(i + 1): + for k in range(i + 1): + if j + k == i: + coeff[j][k] = binomial(i, j) + + sol = 0 + for j in range(i + 1): + for k in range(i + 1): + sol += coeff[j][k]* y0_self[j] * y0_other[k] + + y0.append(sol) + + return HolonomicFunction(sol_ann, self.x, self.x0, y0) + + # if the points are different, consider one + selfat0 = self.annihilator.is_singular(0) + otherat0 = other.annihilator.is_singular(0) + + if self.x0 == 0 and not selfat0 and not otherat0: + return self * other.change_ics(0) + if other.x0 == 0 and not selfat0 and not otherat0: + return self.change_ics(0) * other + + selfatx0 = self.annihilator.is_singular(self.x0) + otheratx0 = other.annihilator.is_singular(self.x0) + if not selfatx0 and not otheratx0: + return self * other.change_ics(self.x0) + return self.change_ics(other.x0) * other + + if self.x0 != other.x0: + return HolonomicFunction(sol_ann, self.x) + + # if the functions have singular_ics + y1 = None + y2 = None + + if self.is_singularics() == False and other.is_singularics() == True: + _y0 = [j / factorial(i) for i, j in enumerate(self.y0)] + y1 = {S.Zero: _y0} + y2 = other.y0 + elif self.is_singularics() == True and other.is_singularics() == False: + _y0 = [j / factorial(i) for i, j in enumerate(other.y0)] + y1 = self.y0 + y2 = {S.Zero: _y0} + elif self.is_singularics() == True and other.is_singularics() == True: + y1 = self.y0 + y2 = other.y0 + + y0 = {} + # multiply every possible pair of the series terms + for i in y1: + for j in y2: + k = min(len(y1[i]), len(y2[j])) + c = [sum((y1[i][b] * y2[j][a - b] for b in range(a + 1)), + start=S.Zero) for a in range(k)] + if not i + j in y0: + y0[i + j] = c + else: + y0[i + j] = [a + b for a, b in zip(c, y0[i + j])] + return HolonomicFunction(sol_ann, self.x, self.x0, y0) + + __rmul__ = __mul__ + + def __sub__(self, other): + return self + other * -1 + + def __rsub__(self, other): + return self * -1 + other + + def __neg__(self): + return -1 * self + + def __truediv__(self, other): + return self * (S.One / other) + + def __pow__(self, n): + if self.annihilator.order <= 1: + ann = self.annihilator + parent = ann.parent + + if self.y0 is None: + y0 = None + else: + y0 = [list(self.y0)[0] ** n] + + p0 = ann.listofpoly[0] + p1 = ann.listofpoly[1] + + p0 = (Poly.new(p0, self.x) * n).rep + + sol = [parent.base.to_sympy(i) for i in [p0, p1]] + dd = DifferentialOperator(sol, parent) + return HolonomicFunction(dd, self.x, self.x0, y0) + if n < 0: + raise NotHolonomicError("Negative Power on a Holonomic Function") + Dx = self.annihilator.parent.derivative_operator + result = HolonomicFunction(Dx, self.x, S.Zero, [S.One]) + if n == 0: + return result + x = self + while True: + if n % 2: + result *= x + n >>= 1 + if not n: + break + x *= x + return result + + def degree(self): + """ + Returns the highest power of `x` in the annihilator. + """ + return max(i.degree() for i in self.annihilator.listofpoly) + + def composition(self, expr, *args, **kwargs): + """ + Returns function after composition of a holonomic + function with an algebraic function. The method cannot compute + initial conditions for the result by itself, so they can be also be + provided. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import QQ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx') + >>> HolonomicFunction(Dx - 1, x).composition(x**2, 0, [1]) # e^(x**2) + HolonomicFunction((-2*x) + (1)*Dx, x, 0, [1]) + >>> HolonomicFunction(Dx**2 + 1, x).composition(x**2 - 1, 1, [1, 0]) + HolonomicFunction((4*x**3) + (-1)*Dx + (x)*Dx**2, x, 1, [1, 0]) + + See Also + ======== + + from_hyper + """ + + R = self.annihilator.parent + a = self.annihilator.order + diff = expr.diff(self.x) + listofpoly = self.annihilator.listofpoly + + for i, j in enumerate(listofpoly): + if isinstance(j, self.annihilator.parent.base.dtype): + listofpoly[i] = self.annihilator.parent.base.to_sympy(j) + + r = listofpoly[a].subs({self.x:expr}) + subs = [-listofpoly[i].subs({self.x:expr}) / r for i in range (a)] + coeffs = [S.Zero for i in range(a)] # coeffs[i] == coeff of (D^i f)(a) in D^k (f(a)) + coeffs[0] = S.One + system = [coeffs] + homogeneous = Matrix([[S.Zero for i in range(a)]]).transpose() + while True: + coeffs_next = [p.diff(self.x) for p in coeffs] + for i in range(a - 1): + coeffs_next[i + 1] += (coeffs[i] * diff) + for i in range(a): + coeffs_next[i] += (coeffs[-1] * subs[i] * diff) + coeffs = coeffs_next + # check for linear relations + system.append(coeffs) + sol, taus = (Matrix(system).transpose() + ).gauss_jordan_solve(homogeneous) + if sol.is_zero_matrix is not True: + break + + tau = list(taus)[0] + sol = sol.subs(tau, 1) + sol = _normalize(sol[0:], R, negative=False) + + # if initial conditions are given for the resulting function + if args: + return HolonomicFunction(sol, self.x, args[0], args[1]) + return HolonomicFunction(sol, self.x) + + def to_sequence(self, lb=True): + r""" + Finds recurrence relation for the coefficients in the series expansion + of the function about :math:`x_0`, where :math:`x_0` is the point at + which the initial condition is stored. + + Explanation + =========== + + If the point :math:`x_0` is ordinary, solution of the form :math:`[(R, n_0)]` + is returned. Where :math:`R` is the recurrence relation and :math:`n_0` is the + smallest ``n`` for which the recurrence holds true. + + If the point :math:`x_0` is regular singular, a list of solutions in + the format :math:`(R, p, n_0)` is returned, i.e. `[(R, p, n_0), ... ]`. + Each tuple in this vector represents a recurrence relation :math:`R` + associated with a root of the indicial equation ``p``. Conditions of + a different format can also be provided in this case, see the + docstring of HolonomicFunction class. + + If it's not possible to numerically compute a initial condition, + it is returned as a symbol :math:`C_j`, denoting the coefficient of + :math:`(x - x_0)^j` in the power series about :math:`x_0`. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import QQ + >>> from sympy import symbols, S + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx') + >>> HolonomicFunction(Dx - 1, x, 0, [1]).to_sequence() + [(HolonomicSequence((-1) + (n + 1)Sn, n), u(0) = 1, 0)] + >>> HolonomicFunction((1 + x)*Dx**2 + Dx, x, 0, [0, 1]).to_sequence() + [(HolonomicSequence((n**2) + (n**2 + n)Sn, n), u(0) = 0, u(1) = 1, u(2) = -1/2, 2)] + >>> HolonomicFunction(-S(1)/2 + x*Dx, x, 0, {S(1)/2: [1]}).to_sequence() + [(HolonomicSequence((n), n), u(0) = 1, 1/2, 1)] + + See Also + ======== + + HolonomicFunction.series + + References + ========== + + .. [1] https://hal.inria.fr/inria-00070025/document + .. [2] https://www3.risc.jku.at/publications/download/risc_2244/DIPLFORM.pdf + + """ + + if self.x0 != 0: + return self.shift_x(self.x0).to_sequence() + + # check whether a power series exists if the point is singular + if self.annihilator.is_singular(self.x0): + return self._frobenius(lb=lb) + + dict1 = {} + n = Symbol('n', integer=True) + dom = self.annihilator.parent.base.dom + R, _ = RecurrenceOperators(dom.old_poly_ring(n), 'Sn') + + # substituting each term of the form `x^k Dx^j` in the + # annihilator, according to the formula below: + # x^k Dx^j = Sum(rf(n + 1 - k, j) * a(n + j - k) * x^n, (n, k, oo)) + # for explanation see [2]. + for i, j in enumerate(self.annihilator.listofpoly): + + listofdmp = j.all_coeffs() + degree = len(listofdmp) - 1 + + for k in range(degree + 1): + coeff = listofdmp[degree - k] + + if coeff == 0: + continue + + if (i - k, k) in dict1: + dict1[(i - k, k)] += (dom.to_sympy(coeff) * rf(n - k + 1, i)) + else: + dict1[(i - k, k)] = (dom.to_sympy(coeff) * rf(n - k + 1, i)) + + + sol = [] + keylist = [i[0] for i in dict1] + lower = min(keylist) + upper = max(keylist) + degree = self.degree() + + # the recurrence relation holds for all values of + # n greater than smallest_n, i.e. n >= smallest_n + smallest_n = lower + degree + dummys = {} + eqs = [] + unknowns = [] + + # an appropriate shift of the recurrence + for j in range(lower, upper + 1): + if j in keylist: + temp = sum((v.subs(n, n - lower) + for k, v in dict1.items() if k[0] == j), + start=S.Zero) + sol.append(temp) + else: + sol.append(S.Zero) + + # the recurrence relation + sol = RecurrenceOperator(sol, R) + + # computing the initial conditions for recurrence + order = sol.order + all_roots = roots(R.base.to_sympy(sol.listofpoly[-1]), n, filter='Z') + all_roots = all_roots.keys() + + if all_roots: + max_root = max(all_roots) + 1 + smallest_n = max(max_root, smallest_n) + order += smallest_n + + y0 = _extend_y0(self, order) + # u(n) = y^n(0)/factorial(n) + u0 = [j / factorial(i) for i, j in enumerate(y0)] + + # if sufficient conditions can't be computed then + # try to use the series method i.e. + # equate the coefficients of x^k in the equation formed by + # substituting the series in differential equation, to zero. + if len(u0) < order: + + for i in range(degree): + eq = S.Zero + + for j in dict1: + + if i + j[0] < 0: + dummys[i + j[0]] = S.Zero + + elif i + j[0] < len(u0): + dummys[i + j[0]] = u0[i + j[0]] + + elif not i + j[0] in dummys: + dummys[i + j[0]] = Symbol('C_%s' %(i + j[0])) + unknowns.append(dummys[i + j[0]]) + + if j[1] <= i: + eq += dict1[j].subs(n, i) * dummys[i + j[0]] + + eqs.append(eq) + + # solve the system of equations formed + soleqs = solve(eqs, *unknowns) + + if isinstance(soleqs, dict): + + for i in range(len(u0), order): + + if i not in dummys: + dummys[i] = Symbol('C_%s' %i) + + if dummys[i] in soleqs: + u0.append(soleqs[dummys[i]]) + + else: + u0.append(dummys[i]) + + if lb: + return [(HolonomicSequence(sol, u0), smallest_n)] + return [HolonomicSequence(sol, u0)] + + for i in range(len(u0), order): + + if i not in dummys: + dummys[i] = Symbol('C_%s' %i) + + s = False + for j in soleqs: + if dummys[i] in j: + u0.append(j[dummys[i]]) + s = True + if not s: + u0.append(dummys[i]) + + if lb: + return [(HolonomicSequence(sol, u0), smallest_n)] + + return [HolonomicSequence(sol, u0)] + + def _frobenius(self, lb=True): + # compute the roots of indicial equation + indicialroots = self._indicial() + + reals = [] + compl = [] + for i in ordered(indicialroots.keys()): + if i.is_real: + reals.extend([i] * indicialroots[i]) + else: + a, b = i.as_real_imag() + compl.extend([(i, a, b)] * indicialroots[i]) + + # sort the roots for a fixed ordering of solution + compl.sort(key=lambda x : x[1]) + compl.sort(key=lambda x : x[2]) + reals.sort() + + # grouping the roots, roots differ by an integer are put in the same group. + grp = [] + + for i in reals: + if len(grp) == 0: + grp.append([i]) + continue + for j in grp: + if int_valued(j[0] - i): + j.append(i) + break + else: + grp.append([i]) + + # True if none of the roots differ by an integer i.e. + # each element in group have only one member + independent = all(len(i) == 1 for i in grp) + + allpos = all(i >= 0 for i in reals) + allint = all(int_valued(i) for i in reals) + + # if initial conditions are provided + # then use them. + if self.is_singularics() == True: + rootstoconsider = [] + for i in ordered(self.y0.keys()): + for j in ordered(indicialroots.keys()): + if equal_valued(j, i): + rootstoconsider.append(i) + + elif allpos and allint: + rootstoconsider = [min(reals)] + + elif independent: + rootstoconsider = [i[0] for i in grp] + [j[0] for j in compl] + + elif not allint: + rootstoconsider = [i for i in reals if not int(i) == i] + + elif not allpos: + + if not self._have_init_cond() or S(self.y0[0]).is_finite == False: + rootstoconsider = [min(reals)] + + else: + posroots = [i for i in reals if i >= 0] + rootstoconsider = [min(posroots)] + + n = Symbol('n', integer=True) + dom = self.annihilator.parent.base.dom + R, _ = RecurrenceOperators(dom.old_poly_ring(n), 'Sn') + + finalsol = [] + char = ord('C') + + for p in rootstoconsider: + dict1 = {} + + for i, j in enumerate(self.annihilator.listofpoly): + + listofdmp = j.all_coeffs() + degree = len(listofdmp) - 1 + + for k in range(degree + 1): + coeff = listofdmp[degree - k] + + if coeff == 0: + continue + + if (i - k, k - i) in dict1: + dict1[(i - k, k - i)] += (dom.to_sympy(coeff) * rf(n - k + 1 + p, i)) + else: + dict1[(i - k, k - i)] = (dom.to_sympy(coeff) * rf(n - k + 1 + p, i)) + + sol = [] + keylist = [i[0] for i in dict1] + lower = min(keylist) + upper = max(keylist) + degree = max(i[1] for i in dict1) + degree2 = min(i[1] for i in dict1) + + smallest_n = lower + degree + dummys = {} + eqs = [] + unknowns = [] + + for j in range(lower, upper + 1): + if j in keylist: + temp = sum((v.subs(n, n - lower) + for k, v in dict1.items() if k[0] == j), + start=S.Zero) + sol.append(temp) + else: + sol.append(S.Zero) + + # the recurrence relation + sol = RecurrenceOperator(sol, R) + + # computing the initial conditions for recurrence + order = sol.order + all_roots = roots(R.base.to_sympy(sol.listofpoly[-1]), n, filter='Z') + all_roots = all_roots.keys() + + if all_roots: + max_root = max(all_roots) + 1 + smallest_n = max(max_root, smallest_n) + order += smallest_n + + u0 = [] + + if self.is_singularics() == True: + u0 = self.y0[p] + + elif self.is_singularics() == False and p >= 0 and int(p) == p and len(rootstoconsider) == 1: + y0 = _extend_y0(self, order + int(p)) + # u(n) = y^n(0)/factorial(n) + if len(y0) > int(p): + u0 = [y0[i] / factorial(i) for i in range(int(p), len(y0))] + + if len(u0) < order: + + for i in range(degree2, degree): + eq = S.Zero + + for j in dict1: + if i + j[0] < 0: + dummys[i + j[0]] = S.Zero + + elif i + j[0] < len(u0): + dummys[i + j[0]] = u0[i + j[0]] + + elif not i + j[0] in dummys: + letter = chr(char) + '_%s' %(i + j[0]) + dummys[i + j[0]] = Symbol(letter) + unknowns.append(dummys[i + j[0]]) + + if j[1] <= i: + eq += dict1[j].subs(n, i) * dummys[i + j[0]] + + eqs.append(eq) + + # solve the system of equations formed + soleqs = solve(eqs, *unknowns) + + if isinstance(soleqs, dict): + + for i in range(len(u0), order): + + if i not in dummys: + letter = chr(char) + '_%s' %i + dummys[i] = Symbol(letter) + + if dummys[i] in soleqs: + u0.append(soleqs[dummys[i]]) + + else: + u0.append(dummys[i]) + + if lb: + finalsol.append((HolonomicSequence(sol, u0), p, smallest_n)) + continue + else: + finalsol.append((HolonomicSequence(sol, u0), p)) + continue + + for i in range(len(u0), order): + + if i not in dummys: + letter = chr(char) + '_%s' %i + dummys[i] = Symbol(letter) + + s = False + for j in soleqs: + if dummys[i] in j: + u0.append(j[dummys[i]]) + s = True + if not s: + u0.append(dummys[i]) + if lb: + finalsol.append((HolonomicSequence(sol, u0), p, smallest_n)) + + else: + finalsol.append((HolonomicSequence(sol, u0), p)) + char += 1 + return finalsol + + def series(self, n=6, coefficient=False, order=True, _recur=None): + r""" + Finds the power series expansion of given holonomic function about :math:`x_0`. + + Explanation + =========== + + A list of series might be returned if :math:`x_0` is a regular point with + multiple roots of the indicial equation. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import QQ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx') + >>> HolonomicFunction(Dx - 1, x, 0, [1]).series() # e^x + 1 + x + x**2/2 + x**3/6 + x**4/24 + x**5/120 + O(x**6) + >>> HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).series(n=8) # sin(x) + x - x**3/6 + x**5/120 - x**7/5040 + O(x**8) + + See Also + ======== + + HolonomicFunction.to_sequence + """ + + if _recur is None: + recurrence = self.to_sequence() + else: + recurrence = _recur + + if isinstance(recurrence, tuple) and len(recurrence) == 2: + recurrence = recurrence[0] + constantpower = 0 + elif isinstance(recurrence, tuple) and len(recurrence) == 3: + constantpower = recurrence[1] + recurrence = recurrence[0] + + elif len(recurrence) == 1 and len(recurrence[0]) == 2: + recurrence = recurrence[0][0] + constantpower = 0 + elif len(recurrence) == 1 and len(recurrence[0]) == 3: + constantpower = recurrence[0][1] + recurrence = recurrence[0][0] + else: + return [self.series(_recur=i) for i in recurrence] + + n = n - int(constantpower) + l = len(recurrence.u0) - 1 + k = recurrence.recurrence.order + x = self.x + x0 = self.x0 + seq_dmp = recurrence.recurrence.listofpoly + R = recurrence.recurrence.parent.base + K = R.get_field() + seq = [K.new(j.to_list()) for j in seq_dmp] + sub = [-seq[i] / seq[k] for i in range(k)] + sol = list(recurrence.u0) + + if l + 1 < n: + # use the initial conditions to find the next term + for i in range(l + 1 - k, n - k): + coeff = sum((DMFsubs(sub[j], i) * sol[i + j] + for j in range(k) if i + j >= 0), start=S.Zero) + sol.append(coeff) + + if coefficient: + return sol + + ser = sum((x**(i + constantpower) * j for i, j in enumerate(sol)), + start=S.Zero) + if order: + ser += Order(x**(n + int(constantpower)), x) + if x0 != 0: + return ser.subs(x, x - x0) + return ser + + def _indicial(self): + """ + Computes roots of the Indicial equation. + """ + + if self.x0 != 0: + return self.shift_x(self.x0)._indicial() + + list_coeff = self.annihilator.listofpoly + R = self.annihilator.parent.base + x = self.x + s = R.zero + y = R.one + + def _pole_degree(poly): + root_all = roots(R.to_sympy(poly), x, filter='Z') + if 0 in root_all.keys(): + return root_all[0] + else: + return 0 + + degree = max(j.degree() for j in list_coeff) + inf = 10 * (max(1, degree) + max(1, self.annihilator.order)) + + deg = lambda q: inf if q.is_zero else _pole_degree(q) + b = min(deg(q) - j for j, q in enumerate(list_coeff)) + + for i, j in enumerate(list_coeff): + listofdmp = j.all_coeffs() + degree = len(listofdmp) - 1 + if 0 <= i + b <= degree: + s = s + listofdmp[degree - i - b] * y + y *= R.from_sympy(x - i) + + return roots(R.to_sympy(s), x) + + def evalf(self, points, method='RK4', h=0.05, derivatives=False): + r""" + Finds numerical value of a holonomic function using numerical methods. + (RK4 by default). A set of points (real or complex) must be provided + which will be the path for the numerical integration. + + Explanation + =========== + + The path should be given as a list :math:`[x_1, x_2, \dots x_n]`. The numerical + values will be computed at each point in this order + :math:`x_1 \rightarrow x_2 \rightarrow x_3 \dots \rightarrow x_n`. + + Returns values of the function at :math:`x_1, x_2, \dots x_n` in a list. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import QQ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(QQ.old_poly_ring(x),'Dx') + + A straight line on the real axis from (0 to 1) + + >>> r = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] + + Runge-Kutta 4th order on e^x from 0.1 to 1. + Exact solution at 1 is 2.71828182845905 + + >>> HolonomicFunction(Dx - 1, x, 0, [1]).evalf(r) + [1.10517083333333, 1.22140257085069, 1.34985849706254, 1.49182424008069, + 1.64872063859684, 1.82211796209193, 2.01375162659678, 2.22553956329232, + 2.45960141378007, 2.71827974413517] + + Euler's method for the same + + >>> HolonomicFunction(Dx - 1, x, 0, [1]).evalf(r, method='Euler') + [1.1, 1.21, 1.331, 1.4641, 1.61051, 1.771561, 1.9487171, 2.14358881, + 2.357947691, 2.5937424601] + + One can also observe that the value obtained using Runge-Kutta 4th order + is much more accurate than Euler's method. + """ + + from sympy.holonomic.numerical import _evalf + lp = False + + # if a point `b` is given instead of a mesh + if not hasattr(points, "__iter__"): + lp = True + b = S(points) + if self.x0 == b: + return _evalf(self, [b], method=method, derivatives=derivatives)[-1] + + if not b.is_Number: + raise NotImplementedError + + a = self.x0 + if a > b: + h = -h + n = int((b - a) / h) + points = [a + h] + for i in range(n - 1): + points.append(points[-1] + h) + + for i in roots(self.annihilator.parent.base.to_sympy(self.annihilator.listofpoly[-1]), self.x): + if i == self.x0 or i in points: + raise SingularityError(self, i) + + if lp: + return _evalf(self, points, method=method, derivatives=derivatives)[-1] + return _evalf(self, points, method=method, derivatives=derivatives) + + def change_x(self, z): + """ + Changes only the variable of Holonomic Function, for internal + purposes. For composition use HolonomicFunction.composition() + """ + + dom = self.annihilator.parent.base.dom + R = dom.old_poly_ring(z) + parent, _ = DifferentialOperators(R, 'Dx') + sol = [R(j.to_list()) for j in self.annihilator.listofpoly] + sol = DifferentialOperator(sol, parent) + return HolonomicFunction(sol, z, self.x0, self.y0) + + def shift_x(self, a): + """ + Substitute `x + a` for `x`. + """ + + x = self.x + listaftershift = self.annihilator.listofpoly + base = self.annihilator.parent.base + + sol = [base.from_sympy(base.to_sympy(i).subs(x, x + a)) for i in listaftershift] + sol = DifferentialOperator(sol, self.annihilator.parent) + x0 = self.x0 - a + if not self._have_init_cond(): + return HolonomicFunction(sol, x) + return HolonomicFunction(sol, x, x0, self.y0) + + def to_hyper(self, as_list=False, _recur=None): + r""" + Returns a hypergeometric function (or linear combination of them) + representing the given holonomic function. + + Explanation + =========== + + Returns an answer of the form: + `a_1 \cdot x^{b_1} \cdot{hyper()} + a_2 \cdot x^{b_2} \cdot{hyper()} \dots` + + This is very useful as one can now use ``hyperexpand`` to find the + symbolic expressions/functions. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import ZZ + >>> from sympy import symbols + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx') + >>> # sin(x) + >>> HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).to_hyper() + x*hyper((), (3/2,), -x**2/4) + >>> # exp(x) + >>> HolonomicFunction(Dx - 1, x, 0, [1]).to_hyper() + hyper((), (), x) + + See Also + ======== + + from_hyper, from_meijerg + """ + + if _recur is None: + recurrence = self.to_sequence() + else: + recurrence = _recur + + if isinstance(recurrence, tuple) and len(recurrence) == 2: + smallest_n = recurrence[1] + recurrence = recurrence[0] + constantpower = 0 + elif isinstance(recurrence, tuple) and len(recurrence) == 3: + smallest_n = recurrence[2] + constantpower = recurrence[1] + recurrence = recurrence[0] + elif len(recurrence) == 1 and len(recurrence[0]) == 2: + smallest_n = recurrence[0][1] + recurrence = recurrence[0][0] + constantpower = 0 + elif len(recurrence) == 1 and len(recurrence[0]) == 3: + smallest_n = recurrence[0][2] + constantpower = recurrence[0][1] + recurrence = recurrence[0][0] + else: + sol = self.to_hyper(as_list=as_list, _recur=recurrence[0]) + for i in recurrence[1:]: + sol += self.to_hyper(as_list=as_list, _recur=i) + return sol + + u0 = recurrence.u0 + r = recurrence.recurrence + x = self.x + x0 = self.x0 + + # order of the recurrence relation + m = r.order + + # when no recurrence exists, and the power series have finite terms + if m == 0: + nonzeroterms = roots(r.parent.base.to_sympy(r.listofpoly[0]), recurrence.n, filter='R') + + sol = S.Zero + for j, i in enumerate(nonzeroterms): + + if i < 0 or not int_valued(i): + continue + + i = int(i) + if i < len(u0): + if isinstance(u0[i], (PolyElement, FracElement)): + u0[i] = u0[i].as_expr() + sol += u0[i] * x**i + + else: + sol += Symbol('C_%s' %j) * x**i + + if isinstance(sol, (PolyElement, FracElement)): + sol = sol.as_expr() * x**constantpower + else: + sol = sol * x**constantpower + if as_list: + if x0 != 0: + return [(sol.subs(x, x - x0), )] + return [(sol, )] + if x0 != 0: + return sol.subs(x, x - x0) + return sol + + if smallest_n + m > len(u0): + raise NotImplementedError("Can't compute sufficient Initial Conditions") + + # check if the recurrence represents a hypergeometric series + if any(i != r.parent.base.zero for i in r.listofpoly[1:-1]): + raise NotHyperSeriesError(self, self.x0) + + a = r.listofpoly[0] + b = r.listofpoly[-1] + + # the constant multiple of argument of hypergeometric function + if isinstance(a.LC(), (PolyElement, FracElement)): + c = - (S(a.LC().as_expr()) * m**(a.degree())) / (S(b.LC().as_expr()) * m**(b.degree())) + else: + c = - (S(a.LC()) * m**(a.degree())) / (S(b.LC()) * m**(b.degree())) + + sol = 0 + + arg1 = roots(r.parent.base.to_sympy(a), recurrence.n) + arg2 = roots(r.parent.base.to_sympy(b), recurrence.n) + + # iterate through the initial conditions to find + # the hypergeometric representation of the given + # function. + # The answer will be a linear combination + # of different hypergeometric series which satisfies + # the recurrence. + if as_list: + listofsol = [] + for i in range(smallest_n + m): + + # if the recurrence relation doesn't hold for `n = i`, + # then a Hypergeometric representation doesn't exist. + # add the algebraic term a * x**i to the solution, + # where a is u0[i] + if i < smallest_n: + if as_list: + listofsol.append(((S(u0[i]) * x**(i+constantpower)).subs(x, x-x0), )) + else: + sol += S(u0[i]) * x**i + continue + + # if the coefficient u0[i] is zero, then the + # independent hypergeomtric series starting with + # x**i is not a part of the answer. + if S(u0[i]) == 0: + continue + + ap = [] + bq = [] + + # substitute m * n + i for n + for k in ordered(arg1.keys()): + ap.extend([nsimplify((i - k) / m)] * arg1[k]) + + for k in ordered(arg2.keys()): + bq.extend([nsimplify((i - k) / m)] * arg2[k]) + + # convention of (k + 1) in the denominator + if 1 in bq: + bq.remove(1) + else: + ap.append(1) + if as_list: + listofsol.append(((S(u0[i])*x**(i+constantpower)).subs(x, x-x0), (hyper(ap, bq, c*x**m)).subs(x, x-x0))) + else: + sol += S(u0[i]) * hyper(ap, bq, c * x**m) * x**i + if as_list: + return listofsol + sol = sol * x**constantpower + if x0 != 0: + return sol.subs(x, x - x0) + + return sol + + def to_expr(self): + """ + Converts a Holonomic Function back to elementary functions. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import HolonomicFunction, DifferentialOperators + >>> from sympy import ZZ + >>> from sympy import symbols, S + >>> x = symbols('x') + >>> R, Dx = DifferentialOperators(ZZ.old_poly_ring(x),'Dx') + >>> HolonomicFunction(x**2*Dx**2 + x*Dx + (x**2 - 1), x, 0, [0, S(1)/2]).to_expr() + besselj(1, x) + >>> HolonomicFunction((1 + x)*Dx**3 + Dx**2, x, 0, [1, 1, 1]).to_expr() + x*log(x + 1) + log(x + 1) + 1 + + """ + + return hyperexpand(self.to_hyper()).simplify() + + def change_ics(self, b, lenics=None): + """ + Changes the point `x0` to ``b`` for initial conditions. + + Examples + ======== + + >>> from sympy.holonomic import expr_to_holonomic + >>> from sympy import symbols, sin, exp + >>> x = symbols('x') + + >>> expr_to_holonomic(sin(x)).change_ics(1) + HolonomicFunction((1) + (1)*Dx**2, x, 1, [sin(1), cos(1)]) + + >>> expr_to_holonomic(exp(x)).change_ics(2) + HolonomicFunction((-1) + (1)*Dx, x, 2, [exp(2)]) + """ + + symbolic = True + + if lenics is None and len(self.y0) > self.annihilator.order: + lenics = len(self.y0) + dom = self.annihilator.parent.base.domain + + try: + sol = expr_to_holonomic(self.to_expr(), x=self.x, x0=b, lenics=lenics, domain=dom) + except (NotPowerSeriesError, NotHyperSeriesError): + symbolic = False + + if symbolic and sol.x0 == b: + return sol + + y0 = self.evalf(b, derivatives=True) + return HolonomicFunction(self.annihilator, self.x, b, y0) + + def to_meijerg(self): + """ + Returns a linear combination of Meijer G-functions. + + Examples + ======== + + >>> from sympy.holonomic import expr_to_holonomic + >>> from sympy import sin, cos, hyperexpand, log, symbols + >>> x = symbols('x') + >>> hyperexpand(expr_to_holonomic(cos(x) + sin(x)).to_meijerg()) + sin(x) + cos(x) + >>> hyperexpand(expr_to_holonomic(log(x)).to_meijerg()).simplify() + log(x) + + See Also + ======== + + to_hyper + """ + + # convert to hypergeometric first + rep = self.to_hyper(as_list=True) + sol = S.Zero + + for i in rep: + if len(i) == 1: + sol += i[0] + + elif len(i) == 2: + sol += i[0] * _hyper_to_meijerg(i[1]) + + return sol + + +def from_hyper(func, x0=0, evalf=False): + r""" + Converts a hypergeometric function to holonomic. + ``func`` is the Hypergeometric Function and ``x0`` is the point at + which initial conditions are required. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import from_hyper + >>> from sympy import symbols, hyper, S + >>> x = symbols('x') + >>> from_hyper(hyper([], [S(3)/2], x**2/4)) + HolonomicFunction((-x) + (2)*Dx + (x)*Dx**2, x, 1, [sinh(1), -sinh(1) + cosh(1)]) + """ + + a = func.ap + b = func.bq + z = func.args[2] + x = z.atoms(Symbol).pop() + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + + # generalized hypergeometric differential equation + xDx = x*Dx + r1 = 1 + for ai in a: # XXX gives sympify error if Mul is used with list of all factors + r1 *= xDx + ai + xDx_1 = xDx - 1 + # r2 = Mul(*([Dx] + [xDx_1 + bi for bi in b])) # XXX gives sympify error + r2 = Dx + for bi in b: + r2 *= xDx_1 + bi + sol = r1 - r2 + + simp = hyperexpand(func) + + if simp in (Infinity, NegativeInfinity): + return HolonomicFunction(sol, x).composition(z) + + # if the function is known symbolically + if not isinstance(simp, hyper): + y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False) + while not y0: + # if values don't exist at 0, then try to find initial + # conditions at 1. If it doesn't exist at 1 too then + # try 2 and so on. + x0 += 1 + y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False) + + return HolonomicFunction(sol, x).composition(z, x0, y0) + + if isinstance(simp, hyper): + x0 = 1 + # use evalf if the function can't be simplified + y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False) + while not y0: + x0 += 1 + y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False) + return HolonomicFunction(sol, x).composition(z, x0, y0) + + return HolonomicFunction(sol, x).composition(z) + + +def from_meijerg(func, x0=0, evalf=False, initcond=True, domain=QQ): + """ + Converts a Meijer G-function to Holonomic. + ``func`` is the G-Function and ``x0`` is the point at + which initial conditions are required. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import from_meijerg + >>> from sympy import symbols, meijerg, S + >>> x = symbols('x') + >>> from_meijerg(meijerg(([], []), ([S(1)/2], [0]), x**2/4)) + HolonomicFunction((1) + (1)*Dx**2, x, 0, [0, 1/sqrt(pi)]) + """ + + a = func.ap + b = func.bq + n = len(func.an) + m = len(func.bm) + p = len(a) + z = func.args[2] + x = z.atoms(Symbol).pop() + R, Dx = DifferentialOperators(domain.old_poly_ring(x), 'Dx') + + # compute the differential equation satisfied by the + # Meijer G-function. + xDx = x*Dx + xDx1 = xDx + 1 + r1 = x*(-1)**(m + n - p) + for ai in a: # XXX gives sympify error if args given in list + r1 *= xDx1 - ai + # r2 = Mul(*[xDx - bi for bi in b]) # gives sympify error + r2 = 1 + for bi in b: + r2 *= xDx - bi + sol = r1 - r2 + + if not initcond: + return HolonomicFunction(sol, x).composition(z) + + simp = hyperexpand(func) + + if simp in (Infinity, NegativeInfinity): + return HolonomicFunction(sol, x).composition(z) + + # computing initial conditions + if not isinstance(simp, meijerg): + y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False) + while not y0: + x0 += 1 + y0 = _find_conditions(simp, x, x0, sol.order, use_limit=False) + + return HolonomicFunction(sol, x).composition(z, x0, y0) + + if isinstance(simp, meijerg): + x0 = 1 + y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False) + while not y0: + x0 += 1 + y0 = _find_conditions(simp, x, x0, sol.order, evalf, use_limit=False) + + return HolonomicFunction(sol, x).composition(z, x0, y0) + + return HolonomicFunction(sol, x).composition(z) + + +x_1 = Dummy('x_1') +_lookup_table = None +domain_for_table = None +from sympy.integrals.meijerint import _mytype + + +def expr_to_holonomic(func, x=None, x0=0, y0=None, lenics=None, domain=None, initcond=True): + """ + Converts a function or an expression to a holonomic function. + + Parameters + ========== + + func: + The expression to be converted. + x: + variable for the function. + x0: + point at which initial condition must be computed. + y0: + One can optionally provide initial condition if the method + is not able to do it automatically. + lenics: + Number of terms in the initial condition. By default it is + equal to the order of the annihilator. + domain: + Ground domain for the polynomials in ``x`` appearing as coefficients + in the annihilator. + initcond: + Set it false if you do not want the initial conditions to be computed. + + Examples + ======== + + >>> from sympy.holonomic.holonomic import expr_to_holonomic + >>> from sympy import sin, exp, symbols + >>> x = symbols('x') + >>> expr_to_holonomic(sin(x)) + HolonomicFunction((1) + (1)*Dx**2, x, 0, [0, 1]) + >>> expr_to_holonomic(exp(x)) + HolonomicFunction((-1) + (1)*Dx, x, 0, [1]) + + See Also + ======== + + sympy.integrals.meijerint._rewrite1, _convert_poly_rat_alg, _create_table + """ + func = sympify(func) + syms = func.free_symbols + + if not x: + if len(syms) == 1: + x= syms.pop() + else: + raise ValueError("Specify the variable for the function") + elif x in syms: + syms.remove(x) + + extra_syms = list(syms) + + if domain is None: + if func.has(Float): + domain = RR + else: + domain = QQ + if len(extra_syms) != 0: + domain = domain[extra_syms].get_field() + + # try to convert if the function is polynomial or rational + solpoly = _convert_poly_rat_alg(func, x, x0=x0, y0=y0, lenics=lenics, domain=domain, initcond=initcond) + if solpoly: + return solpoly + + # create the lookup table + global _lookup_table, domain_for_table + if not _lookup_table: + domain_for_table = domain + _lookup_table = {} + _create_table(_lookup_table, domain=domain) + elif domain != domain_for_table: + domain_for_table = domain + _lookup_table = {} + _create_table(_lookup_table, domain=domain) + + # use the table directly to convert to Holonomic + if func.is_Function: + f = func.subs(x, x_1) + t = _mytype(f, x_1) + if t in _lookup_table: + l = _lookup_table[t] + sol = l[0][1].change_x(x) + else: + sol = _convert_meijerint(func, x, initcond=False, domain=domain) + if not sol: + raise NotImplementedError + if y0: + sol.y0 = y0 + if y0 or not initcond: + sol.x0 = x0 + return sol + if not lenics: + lenics = sol.annihilator.order + _y0 = _find_conditions(func, x, x0, lenics) + while not _y0: + x0 += 1 + _y0 = _find_conditions(func, x, x0, lenics) + return HolonomicFunction(sol.annihilator, x, x0, _y0) + + if y0 or not initcond: + sol = sol.composition(func.args[0]) + if y0: + sol.y0 = y0 + sol.x0 = x0 + return sol + if not lenics: + lenics = sol.annihilator.order + + _y0 = _find_conditions(func, x, x0, lenics) + while not _y0: + x0 += 1 + _y0 = _find_conditions(func, x, x0, lenics) + return sol.composition(func.args[0], x0, _y0) + + # iterate through the expression recursively + args = func.args + f = func.func + sol = expr_to_holonomic(args[0], x=x, initcond=False, domain=domain) + + if f is Add: + for i in range(1, len(args)): + sol += expr_to_holonomic(args[i], x=x, initcond=False, domain=domain) + + elif f is Mul: + for i in range(1, len(args)): + sol *= expr_to_holonomic(args[i], x=x, initcond=False, domain=domain) + + elif f is Pow: + sol = sol**args[1] + sol.x0 = x0 + if not sol: + raise NotImplementedError + if y0: + sol.y0 = y0 + if y0 or not initcond: + return sol + if sol.y0: + return sol + if not lenics: + lenics = sol.annihilator.order + if sol.annihilator.is_singular(x0): + r = sol._indicial() + l = list(r) + if len(r) == 1 and r[l[0]] == S.One: + r = l[0] + g = func / (x - x0)**r + singular_ics = _find_conditions(g, x, x0, lenics) + singular_ics = [j / factorial(i) for i, j in enumerate(singular_ics)] + y0 = {r:singular_ics} + return HolonomicFunction(sol.annihilator, x, x0, y0) + + _y0 = _find_conditions(func, x, x0, lenics) + while not _y0: + x0 += 1 + _y0 = _find_conditions(func, x, x0, lenics) + + return HolonomicFunction(sol.annihilator, x, x0, _y0) + + +## Some helper functions ## + +def _normalize(list_of, parent, negative=True): + """ + Normalize a given annihilator + """ + + num = [] + denom = [] + base = parent.base + K = base.get_field() + lcm_denom = base.from_sympy(S.One) + list_of_coeff = [] + + # convert polynomials to the elements of associated + # fraction field + for i, j in enumerate(list_of): + if isinstance(j, base.dtype): + list_of_coeff.append(K.new(j.to_list())) + elif not isinstance(j, K.dtype): + list_of_coeff.append(K.from_sympy(sympify(j))) + else: + list_of_coeff.append(j) + + # corresponding numerators of the sequence of polynomials + num.append(list_of_coeff[i].numer()) + + # corresponding denominators + denom.append(list_of_coeff[i].denom()) + + # lcm of denominators in the coefficients + for i in denom: + lcm_denom = i.lcm(lcm_denom) + + if negative: + lcm_denom = -lcm_denom + + lcm_denom = K.new(lcm_denom.to_list()) + + # multiply the coefficients with lcm + for i, j in enumerate(list_of_coeff): + list_of_coeff[i] = j * lcm_denom + + gcd_numer = base((list_of_coeff[-1].numer() / list_of_coeff[-1].denom()).to_list()) + + # gcd of numerators in the coefficients + for i in num: + gcd_numer = i.gcd(gcd_numer) + + gcd_numer = K.new(gcd_numer.to_list()) + + # divide all the coefficients by the gcd + for i, j in enumerate(list_of_coeff): + frac_ans = j / gcd_numer + list_of_coeff[i] = base((frac_ans.numer() / frac_ans.denom()).to_list()) + + return DifferentialOperator(list_of_coeff, parent) + + +def _derivate_diff_eq(listofpoly, K): + """ + Let a differential equation a0(x)y(x) + a1(x)y'(x) + ... = 0 + where a0, a1,... are polynomials or rational functions. The function + returns b0, b1, b2... such that the differential equation + b0(x)y(x) + b1(x)y'(x) +... = 0 is formed after differentiating the + former equation. + """ + + sol = [] + a = len(listofpoly) - 1 + sol.append(DMFdiff(listofpoly[0], K)) + + for i, j in enumerate(listofpoly[1:]): + sol.append(DMFdiff(j, K) + listofpoly[i]) + + sol.append(listofpoly[a]) + return sol + + +def _hyper_to_meijerg(func): + """ + Converts a `hyper` to meijerg. + """ + ap = func.ap + bq = func.bq + + if any(i <= 0 and int(i) == i for i in ap): + return hyperexpand(func) + + z = func.args[2] + + # parameters of the `meijerg` function. + an = (1 - i for i in ap) + anp = () + bm = (S.Zero, ) + bmq = (1 - i for i in bq) + + k = S.One + + for i in bq: + k = k * gamma(i) + + for i in ap: + k = k / gamma(i) + + return k * meijerg(an, anp, bm, bmq, -z) + + +def _add_lists(list1, list2): + """Takes polynomial sequences of two annihilators a and b and returns + the list of polynomials of sum of a and b. + """ + if len(list1) <= len(list2): + sol = [a + b for a, b in zip(list1, list2)] + list2[len(list1):] + else: + sol = [a + b for a, b in zip(list1, list2)] + list1[len(list2):] + return sol + + +def _extend_y0(Holonomic, n): + """ + Tries to find more initial conditions by substituting the initial + value point in the differential equation. + """ + + if Holonomic.annihilator.is_singular(Holonomic.x0) or Holonomic.is_singularics() == True: + return Holonomic.y0 + + annihilator = Holonomic.annihilator + a = annihilator.order + + listofpoly = [] + + y0 = Holonomic.y0 + R = annihilator.parent.base + K = R.get_field() + + for j in annihilator.listofpoly: + if isinstance(j, annihilator.parent.base.dtype): + listofpoly.append(K.new(j.to_list())) + + if len(y0) < a or n <= len(y0): + return y0 + list_red = [-listofpoly[i] / listofpoly[a] + for i in range(a)] + y1 = y0[:min(len(y0), a)] + for _ in range(n - a): + sol = 0 + for a, b in zip(y1, list_red): + r = DMFsubs(b, Holonomic.x0) + if not getattr(r, 'is_finite', True): + return y0 + if isinstance(r, (PolyElement, FracElement)): + r = r.as_expr() + sol += a * r + y1.append(sol) + list_red = _derivate_diff_eq(list_red, K) + return y0 + y1[len(y0):] + + +def DMFdiff(frac, K): + # differentiate a DMF object represented as p/q + if not isinstance(frac, DMF): + return frac.diff() + + p = K.numer(frac) + q = K.denom(frac) + sol_num = - p * q.diff() + q * p.diff() + sol_denom = q**2 + return K((sol_num.to_list(), sol_denom.to_list())) + + +def DMFsubs(frac, x0, mpm=False): + # substitute the point x0 in DMF object of the form p/q + if not isinstance(frac, DMF): + return frac + + p = frac.num + q = frac.den + sol_p = S.Zero + sol_q = S.Zero + + if mpm: + from mpmath import mp + + for i, j in enumerate(reversed(p)): + if mpm: + j = sympify(j)._to_mpmath(mp.prec) + sol_p += j * x0**i + + for i, j in enumerate(reversed(q)): + if mpm: + j = sympify(j)._to_mpmath(mp.prec) + sol_q += j * x0**i + + if isinstance(sol_p, (PolyElement, FracElement)): + sol_p = sol_p.as_expr() + if isinstance(sol_q, (PolyElement, FracElement)): + sol_q = sol_q.as_expr() + + return sol_p / sol_q + + +def _convert_poly_rat_alg(func, x, x0=0, y0=None, lenics=None, domain=QQ, initcond=True): + """ + Converts polynomials, rationals and algebraic functions to holonomic. + """ + + ispoly = func.is_polynomial() + if not ispoly: + israt = func.is_rational_function() + else: + israt = True + + if not (ispoly or israt): + basepoly, ratexp = func.as_base_exp() + if basepoly.is_polynomial() and ratexp.is_Number: + if isinstance(ratexp, Float): + ratexp = nsimplify(ratexp) + m, n = ratexp.p, ratexp.q + is_alg = True + else: + is_alg = False + else: + is_alg = True + + if not (ispoly or israt or is_alg): + return None + + R = domain.old_poly_ring(x) + _, Dx = DifferentialOperators(R, 'Dx') + + # if the function is constant + if not func.has(x): + return HolonomicFunction(Dx, x, 0, [func]) + + if ispoly: + # differential equation satisfied by polynomial + sol = func * Dx - func.diff(x) + sol = _normalize(sol.listofpoly, sol.parent, negative=False) + is_singular = sol.is_singular(x0) + + # try to compute the conditions for singular points + if y0 is None and x0 == 0 and is_singular: + rep = R.from_sympy(func).to_list() + for i, j in enumerate(reversed(rep)): + if j == 0: + continue + coeff = list(reversed(rep))[i:] + indicial = i + break + for i, j in enumerate(coeff): + if isinstance(j, (PolyElement, FracElement)): + coeff[i] = j.as_expr() + y0 = {indicial: S(coeff)} + + elif israt: + p, q = func.as_numer_denom() + # differential equation satisfied by rational + sol = p * q * Dx + p * q.diff(x) - q * p.diff(x) + sol = _normalize(sol.listofpoly, sol.parent, negative=False) + + elif is_alg: + sol = n * (x / m) * Dx - 1 + sol = HolonomicFunction(sol, x).composition(basepoly).annihilator + is_singular = sol.is_singular(x0) + + # try to compute the conditions for singular points + if y0 is None and x0 == 0 and is_singular and \ + (lenics is None or lenics <= 1): + rep = R.from_sympy(basepoly).to_list() + for i, j in enumerate(reversed(rep)): + if j == 0: + continue + if isinstance(j, (PolyElement, FracElement)): + j = j.as_expr() + + coeff = S(j)**ratexp + indicial = S(i) * ratexp + break + if isinstance(coeff, (PolyElement, FracElement)): + coeff = coeff.as_expr() + y0 = {indicial: S([coeff])} + + if y0 or not initcond: + return HolonomicFunction(sol, x, x0, y0) + + if not lenics: + lenics = sol.order + + if sol.is_singular(x0): + r = HolonomicFunction(sol, x, x0)._indicial() + l = list(r) + if len(r) == 1 and r[l[0]] == S.One: + r = l[0] + g = func / (x - x0)**r + singular_ics = _find_conditions(g, x, x0, lenics) + singular_ics = [j / factorial(i) for i, j in enumerate(singular_ics)] + y0 = {r:singular_ics} + return HolonomicFunction(sol, x, x0, y0) + + y0 = _find_conditions(func, x, x0, lenics) + while not y0: + x0 += 1 + y0 = _find_conditions(func, x, x0, lenics) + + return HolonomicFunction(sol, x, x0, y0) + + +def _convert_meijerint(func, x, initcond=True, domain=QQ): + args = meijerint._rewrite1(func, x) + + if args: + fac, po, g, _ = args + else: + return None + + # lists for sum of meijerg functions + fac_list = [fac * i[0] for i in g] + t = po.as_base_exp() + s = t[1] if t[0] == x else S.Zero + po_list = [s + i[1] for i in g] + G_list = [i[2] for i in g] + + # finds meijerg representation of x**s * meijerg(a1 ... ap, b1 ... bq, z) + def _shift(func, s): + z = func.args[-1] + if z.has(I): + z = z.subs(exp_polar, exp) + + d = z.collect(x, evaluate=False) + b = list(d)[0] + a = d[b] + + t = b.as_base_exp() + b = t[1] if t[0] == x else S.Zero + r = s / b + an = (i + r for i in func.args[0][0]) + ap = (i + r for i in func.args[0][1]) + bm = (i + r for i in func.args[1][0]) + bq = (i + r for i in func.args[1][1]) + + return a**-r, meijerg((an, ap), (bm, bq), z) + + coeff, m = _shift(G_list[0], po_list[0]) + sol = fac_list[0] * coeff * from_meijerg(m, initcond=initcond, domain=domain) + + # add all the meijerg functions after converting to holonomic + for i in range(1, len(G_list)): + coeff, m = _shift(G_list[i], po_list[i]) + sol += fac_list[i] * coeff * from_meijerg(m, initcond=initcond, domain=domain) + + return sol + + +def _create_table(table, domain=QQ): + """ + Creates the look-up table. For a similar implementation + see meijerint._create_lookup_table. + """ + + def add(formula, annihilator, arg, x0=0, y0=()): + """ + Adds a formula in the dictionary + """ + table.setdefault(_mytype(formula, x_1), []).append((formula, + HolonomicFunction(annihilator, arg, x0, y0))) + + R = domain.old_poly_ring(x_1) + _, Dx = DifferentialOperators(R, 'Dx') + + # add some basic functions + add(sin(x_1), Dx**2 + 1, x_1, 0, [0, 1]) + add(cos(x_1), Dx**2 + 1, x_1, 0, [1, 0]) + add(exp(x_1), Dx - 1, x_1, 0, 1) + add(log(x_1), Dx + x_1*Dx**2, x_1, 1, [0, 1]) + + add(erf(x_1), 2*x_1*Dx + Dx**2, x_1, 0, [0, 2/sqrt(pi)]) + add(erfc(x_1), 2*x_1*Dx + Dx**2, x_1, 0, [1, -2/sqrt(pi)]) + add(erfi(x_1), -2*x_1*Dx + Dx**2, x_1, 0, [0, 2/sqrt(pi)]) + + add(sinh(x_1), Dx**2 - 1, x_1, 0, [0, 1]) + add(cosh(x_1), Dx**2 - 1, x_1, 0, [1, 0]) + + add(sinc(x_1), x_1 + 2*Dx + x_1*Dx**2, x_1) + + add(Si(x_1), x_1*Dx + 2*Dx**2 + x_1*Dx**3, x_1) + add(Ci(x_1), x_1*Dx + 2*Dx**2 + x_1*Dx**3, x_1) + + add(Shi(x_1), -x_1*Dx + 2*Dx**2 + x_1*Dx**3, x_1) + + +def _find_conditions(func, x, x0, order, evalf=False, use_limit=True): + y0 = [] + for _ in range(order): + val = func.subs(x, x0) + if evalf: + val = val.evalf() + if use_limit and isinstance(val, NaN): + val = limit(func, x, x0) + if val.is_finite is False or isinstance(val, NaN): + return None + y0.append(val) + func = func.diff(x) + return y0 diff --git a/phivenv/Lib/site-packages/sympy/holonomic/holonomicerrors.py b/phivenv/Lib/site-packages/sympy/holonomic/holonomicerrors.py new file mode 100644 index 0000000000000000000000000000000000000000..459a94eb25b186e30b9577d972ebc62a36801f6f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/holonomicerrors.py @@ -0,0 +1,49 @@ +""" Common Exceptions for `holonomic` module. """ + +class BaseHolonomicError(Exception): + + def new(self, *args): + raise NotImplementedError("abstract base class") + +class NotPowerSeriesError(BaseHolonomicError): + + def __init__(self, holonomic, x0): + self.holonomic = holonomic + self.x0 = x0 + + def __str__(self): + s = 'A Power Series does not exists for ' + s += str(self.holonomic) + s += ' about %s.' %self.x0 + return s + +class NotHolonomicError(BaseHolonomicError): + + def __init__(self, m): + self.m = m + + def __str__(self): + return self.m + +class SingularityError(BaseHolonomicError): + + def __init__(self, holonomic, x0): + self.holonomic = holonomic + self.x0 = x0 + + def __str__(self): + s = str(self.holonomic) + s += ' has a singularity at %s.' %self.x0 + return s + +class NotHyperSeriesError(BaseHolonomicError): + + def __init__(self, holonomic, x0): + self.holonomic = holonomic + self.x0 = x0 + + def __str__(self): + s = 'Power series expansion of ' + s += str(self.holonomic) + s += ' about %s is not hypergeometric' %self.x0 + return s diff --git a/phivenv/Lib/site-packages/sympy/holonomic/numerical.py b/phivenv/Lib/site-packages/sympy/holonomic/numerical.py new file mode 100644 index 0000000000000000000000000000000000000000..418b7c627e2e3d74dcded080a9b6d351cdaa9f3f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/numerical.py @@ -0,0 +1,98 @@ +"""Numerical Methods for Holonomic Functions""" + +from sympy.core.sympify import sympify +from sympy.holonomic.holonomic import DMFsubs + +from mpmath import mp + + +def _evalf(func, points, derivatives=False, method='RK4'): + """ + Numerical methods for numerical integration along a given set of + points in the complex plane. + """ + + ann = func.annihilator + a = ann.order + R = ann.parent.base + K = R.get_field() + + if method == 'Euler': + meth = _euler + else: + meth = _rk4 + + dmf = [K.new(j.to_list()) for j in ann.listofpoly] + red = [-dmf[i] / dmf[a] for i in range(a)] + + y0 = func.y0 + if len(y0) < a: + raise TypeError("Not Enough Initial Conditions") + x0 = func.x0 + sol = [meth(red, x0, points[0], y0, a)] + + for i, j in enumerate(points[1:]): + sol.append(meth(red, points[i], j, sol[-1], a)) + + if not derivatives: + return [sympify(i[0]) for i in sol] + else: + return sympify(sol) + + +def _euler(red, x0, x1, y0, a): + """ + Euler's method for numerical integration. + From x0 to x1 with initial values given at x0 as vector y0. + """ + + A = sympify(x0)._to_mpmath(mp.prec) + B = sympify(x1)._to_mpmath(mp.prec) + y_0 = [sympify(i)._to_mpmath(mp.prec) for i in y0] + h = B - A + f_0 = y_0[1:] + f_0_n = 0 + + for i in range(a): + f_0_n += sympify(DMFsubs(red[i], A, mpm=True))._to_mpmath(mp.prec) * y_0[i] + f_0.append(f_0_n) + + return [y_0[i] + h * f_0[i] for i in range(a)] + + +def _rk4(red, x0, x1, y0, a): + """ + Runge-Kutta 4th order numerical method. + """ + + A = sympify(x0)._to_mpmath(mp.prec) + B = sympify(x1)._to_mpmath(mp.prec) + y_0 = [sympify(i)._to_mpmath(mp.prec) for i in y0] + h = B - A + + f_0_n = 0 + f_1_n = 0 + f_2_n = 0 + f_3_n = 0 + + f_0 = y_0[1:] + for i in range(a): + f_0_n += sympify(DMFsubs(red[i], A, mpm=True))._to_mpmath(mp.prec) * y_0[i] + f_0.append(f_0_n) + + f_1 = [y_0[i] + f_0[i]*h/2 for i in range(1, a)] + for i in range(a): + f_1_n += sympify(DMFsubs(red[i], A + h/2, mpm=True))._to_mpmath(mp.prec) * (y_0[i] + f_0[i]*h/2) + f_1.append(f_1_n) + + f_2 = [y_0[i] + f_1[i]*h/2 for i in range(1, a)] + for i in range(a): + f_2_n += sympify(DMFsubs(red[i], A + h/2, mpm=True))._to_mpmath(mp.prec) * (y_0[i] + f_1[i]*h/2) + f_2.append(f_2_n) + + f_3 = [y_0[i] + f_2[i]*h for i in range(1, a)] + for i in range(a): + f_3_n += sympify(DMFsubs(red[i], A + h, mpm=True))._to_mpmath(mp.prec) * (y_0[i] + f_2[i]*h) + f_3.append(f_3_n) + + return [y_0[i] + h*(f_0[i]+2*f_1[i]+2*f_2[i]+f_3[i])/6 for i in range(a)] diff --git a/phivenv/Lib/site-packages/sympy/holonomic/recurrence.py b/phivenv/Lib/site-packages/sympy/holonomic/recurrence.py new file mode 100644 index 0000000000000000000000000000000000000000..8c2c17ceda2d042c12778977cadd5ce9ee3b7479 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/recurrence.py @@ -0,0 +1,342 @@ +"""Recurrence Operators""" + +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.printing import sstr +from sympy.core.sympify import sympify + + +def RecurrenceOperators(base, generator): + """ + Returns an Algebra of Recurrence Operators and the operator for + shifting i.e. the `Sn` operator. + The first argument needs to be the base polynomial ring for the algebra + and the second argument must be a generator which can be either a + noncommutative Symbol or a string. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy import symbols + >>> from sympy.holonomic.recurrence import RecurrenceOperators + >>> n = symbols('n', integer=True) + >>> R, Sn = RecurrenceOperators(ZZ.old_poly_ring(n), 'Sn') + """ + + ring = RecurrenceOperatorAlgebra(base, generator) + return (ring, ring.shift_operator) + + +class RecurrenceOperatorAlgebra: + """ + A Recurrence Operator Algebra is a set of noncommutative polynomials + in intermediate `Sn` and coefficients in a base ring A. It follows the + commutation rule: + Sn * a(n) = a(n + 1) * Sn + + This class represents a Recurrence Operator Algebra and serves as the parent ring + for Recurrence Operators. + + Examples + ======== + + >>> from sympy import ZZ + >>> from sympy import symbols + >>> from sympy.holonomic.recurrence import RecurrenceOperators + >>> n = symbols('n', integer=True) + >>> R, Sn = RecurrenceOperators(ZZ.old_poly_ring(n), 'Sn') + >>> R + Univariate Recurrence Operator Algebra in intermediate Sn over the base ring + ZZ[n] + + See Also + ======== + + RecurrenceOperator + """ + + def __init__(self, base, generator): + # the base ring for the algebra + self.base = base + # the operator representing shift i.e. `Sn` + self.shift_operator = RecurrenceOperator( + [base.zero, base.one], self) + + if generator is None: + self.gen_symbol = symbols('Sn', commutative=False) + else: + if isinstance(generator, str): + self.gen_symbol = symbols(generator, commutative=False) + elif isinstance(generator, Symbol): + self.gen_symbol = generator + + def __str__(self): + string = 'Univariate Recurrence Operator Algebra in intermediate '\ + + sstr(self.gen_symbol) + ' over the base ring ' + \ + (self.base).__str__() + + return string + + __repr__ = __str__ + + def __eq__(self, other): + if self.base == other.base and self.gen_symbol == other.gen_symbol: + return True + else: + return False + + +def _add_lists(list1, list2): + if len(list1) <= len(list2): + sol = [a + b for a, b in zip(list1, list2)] + list2[len(list1):] + else: + sol = [a + b for a, b in zip(list1, list2)] + list1[len(list2):] + return sol + + +class RecurrenceOperator: + """ + The Recurrence Operators are defined by a list of polynomials + in the base ring and the parent ring of the Operator. + + Explanation + =========== + + Takes a list of polynomials for each power of Sn and the + parent ring which must be an instance of RecurrenceOperatorAlgebra. + + A Recurrence Operator can be created easily using + the operator `Sn`. See examples below. + + Examples + ======== + + >>> from sympy.holonomic.recurrence import RecurrenceOperator, RecurrenceOperators + >>> from sympy import ZZ + >>> from sympy import symbols + >>> n = symbols('n', integer=True) + >>> R, Sn = RecurrenceOperators(ZZ.old_poly_ring(n),'Sn') + + >>> RecurrenceOperator([0, 1, n**2], R) + (1)Sn + (n**2)Sn**2 + + >>> Sn*n + (n + 1)Sn + + >>> n*Sn*n + 1 - Sn**2*n + (1) + (n**2 + n)Sn + (-n - 2)Sn**2 + + See Also + ======== + + DifferentialOperatorAlgebra + """ + + _op_priority = 20 + + def __init__(self, list_of_poly, parent): + # the parent ring for this operator + # must be an RecurrenceOperatorAlgebra object + self.parent = parent + # sequence of polynomials in n for each power of Sn + # represents the operator + # convert the expressions into ring elements using from_sympy + if isinstance(list_of_poly, list): + for i, j in enumerate(list_of_poly): + if isinstance(j, int): + list_of_poly[i] = self.parent.base.from_sympy(S(j)) + elif not isinstance(j, self.parent.base.dtype): + list_of_poly[i] = self.parent.base.from_sympy(j) + + self.listofpoly = list_of_poly + self.order = len(self.listofpoly) - 1 + + def __mul__(self, other): + """ + Multiplies two Operators and returns another + RecurrenceOperator instance using the commutation rule + Sn * a(n) = a(n + 1) * Sn + """ + + listofself = self.listofpoly + base = self.parent.base + + if not isinstance(other, RecurrenceOperator): + if not isinstance(other, self.parent.base.dtype): + listofother = [self.parent.base.from_sympy(sympify(other))] + + else: + listofother = [other] + else: + listofother = other.listofpoly + # multiply a polynomial `b` with a list of polynomials + + def _mul_dmp_diffop(b, listofother): + if isinstance(listofother, list): + return [i * b for i in listofother] + return [b * listofother] + + sol = _mul_dmp_diffop(listofself[0], listofother) + + # compute Sn^i * b + def _mul_Sni_b(b): + sol = [base.zero] + + if isinstance(b, list): + for i in b: + j = base.to_sympy(i).subs(base.gens[0], base.gens[0] + S.One) + sol.append(base.from_sympy(j)) + + else: + j = b.subs(base.gens[0], base.gens[0] + S.One) + sol.append(base.from_sympy(j)) + + return sol + + for i in range(1, len(listofself)): + # find Sn^i * b in ith iteration + listofother = _mul_Sni_b(listofother) + # solution = solution + listofself[i] * (Sn^i * b) + sol = _add_lists(sol, _mul_dmp_diffop(listofself[i], listofother)) + + return RecurrenceOperator(sol, self.parent) + + def __rmul__(self, other): + if not isinstance(other, RecurrenceOperator): + + if isinstance(other, int): + other = S(other) + + if not isinstance(other, self.parent.base.dtype): + other = (self.parent.base).from_sympy(other) + + sol = [other * j for j in self.listofpoly] + return RecurrenceOperator(sol, self.parent) + + def __add__(self, other): + if isinstance(other, RecurrenceOperator): + + sol = _add_lists(self.listofpoly, other.listofpoly) + return RecurrenceOperator(sol, self.parent) + + else: + + if isinstance(other, int): + other = S(other) + list_self = self.listofpoly + if not isinstance(other, self.parent.base.dtype): + list_other = [((self.parent).base).from_sympy(other)] + else: + list_other = [other] + sol = [list_self[0] + list_other[0]] + list_self[1:] + + return RecurrenceOperator(sol, self.parent) + + __radd__ = __add__ + + def __sub__(self, other): + return self + (-1) * other + + def __rsub__(self, other): + return (-1) * self + other + + def __pow__(self, n): + if n == 1: + return self + result = RecurrenceOperator([self.parent.base.one], self.parent) + if n == 0: + return result + # if self is `Sn` + if self.listofpoly == self.parent.shift_operator.listofpoly: + sol = [self.parent.base.zero] * n + [self.parent.base.one] + return RecurrenceOperator(sol, self.parent) + x = self + while True: + if n % 2: + result *= x + n >>= 1 + if not n: + break + x *= x + return result + + def __str__(self): + listofpoly = self.listofpoly + print_str = '' + + for i, j in enumerate(listofpoly): + if j == self.parent.base.zero: + continue + + j = self.parent.base.to_sympy(j) + + if i == 0: + print_str += '(' + sstr(j) + ')' + continue + + if print_str: + print_str += ' + ' + + if i == 1: + print_str += '(' + sstr(j) + ')Sn' + continue + + print_str += '(' + sstr(j) + ')' + 'Sn**' + sstr(i) + + return print_str + + __repr__ = __str__ + + def __eq__(self, other): + if isinstance(other, RecurrenceOperator): + if self.listofpoly == other.listofpoly and self.parent == other.parent: + return True + else: + return False + return self.listofpoly[0] == other and \ + all(i is self.parent.base.zero for i in self.listofpoly[1:]) + + +class HolonomicSequence: + """ + A Holonomic Sequence is a type of sequence satisfying a linear homogeneous + recurrence relation with Polynomial coefficients. Alternatively, A sequence + is Holonomic if and only if its generating function is a Holonomic Function. + """ + + def __init__(self, recurrence, u0=[]): + self.recurrence = recurrence + if not isinstance(u0, list): + self.u0 = [u0] + else: + self.u0 = u0 + + if len(self.u0) == 0: + self._have_init_cond = False + else: + self._have_init_cond = True + self.n = recurrence.parent.base.gens[0] + + def __repr__(self): + str_sol = 'HolonomicSequence(%s, %s)' % ((self.recurrence).__repr__(), sstr(self.n)) + if not self._have_init_cond: + return str_sol + else: + cond_str = '' + seq_str = 0 + for i in self.u0: + cond_str += ', u(%s) = %s' % (sstr(seq_str), sstr(i)) + seq_str += 1 + + sol = str_sol + cond_str + return sol + + __str__ = __repr__ + + def __eq__(self, other): + if self.recurrence != other.recurrence or self.n != other.n: + return False + if self._have_init_cond and other._have_init_cond: + return self.u0 == other.u0 + return True diff --git a/phivenv/Lib/site-packages/sympy/holonomic/tests/__init__.py b/phivenv/Lib/site-packages/sympy/holonomic/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89a16c96f124f1d1330ca9bfdb5b608d46a3edd1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/test_holonomic.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/test_holonomic.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bbe325eec60cf98ecf1146ea6ff94b67dc9f3f4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/test_holonomic.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/test_recurrence.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/test_recurrence.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ef2b3c481dfa6a1d08c61756a4262ccdf1052f6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/holonomic/tests/__pycache__/test_recurrence.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/holonomic/tests/test_holonomic.py b/phivenv/Lib/site-packages/sympy/holonomic/tests/test_holonomic.py new file mode 100644 index 0000000000000000000000000000000000000000..49956419e917b3bc81a163d29862c539f33f6284 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/tests/test_holonomic.py @@ -0,0 +1,851 @@ +from sympy.holonomic import (DifferentialOperator, HolonomicFunction, + DifferentialOperators, from_hyper, + from_meijerg, expr_to_holonomic) +from sympy.holonomic.recurrence import RecurrenceOperators, HolonomicSequence +from sympy.core import EulerGamma +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, cosh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.bessel import besselj +from sympy.functions.special.beta_functions import beta +from sympy.functions.special.error_functions import (Ci, Si, erf, erfc) +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import (hyper, meijerg) +from sympy.printing.str import sstr +from sympy.series.order import O +from sympy.simplify.hyperexpand import hyperexpand +from sympy.polys.domains.integerring import ZZ +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.domains.realfield import RR + + +def test_DifferentialOperator(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + assert Dx == R.derivative_operator + assert Dx == DifferentialOperator([R.base.zero, R.base.one], R) + assert x * Dx + x**2 * Dx**2 == DifferentialOperator([0, x, x**2], R) + assert (x**2 + 1) + Dx + x * \ + Dx**5 == DifferentialOperator([x**2 + 1, 1, 0, 0, 0, x], R) + assert (x * Dx + x**2 + 1 - Dx * (x**3 + x))**3 == (-48 * x**6) + \ + (-57 * x**7) * Dx + (-15 * x**8) * Dx**2 + (-x**9) * Dx**3 + p = (x * Dx**2 + (x**2 + 3) * Dx**5) * (Dx + x**2) + q = (2 * x) + (4 * x**2) * Dx + (x**3) * Dx**2 + \ + (20 * x**2 + x + 60) * Dx**3 + (10 * x**3 + 30 * x) * Dx**4 + \ + (x**4 + 3 * x**2) * Dx**5 + (x**2 + 3) * Dx**6 + assert p == q + + +def test_HolonomicFunction_addition(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx**2 * x, x) + q = HolonomicFunction((2) * Dx + (x) * Dx**2, x) + assert p == q + p = HolonomicFunction(x * Dx + 1, x) + q = HolonomicFunction(Dx + 1, x) + r = HolonomicFunction((x - 2) + (x**2 - 2) * Dx + (x**2 - x) * Dx**2, x) + assert p + q == r + p = HolonomicFunction(x * Dx + Dx**2 * (x**2 + 2), x) + q = HolonomicFunction(Dx - 3, x) + r = HolonomicFunction((-54 * x**2 - 126 * x - 150) + (-135 * x**3 - 252 * x**2 - 270 * x + 140) * Dx +\ + (-27 * x**4 - 24 * x**2 + 14 * x - 150) * Dx**2 + \ + (9 * x**4 + 15 * x**3 + 38 * x**2 + 30 * x +40) * Dx**3, x) + assert p + q == r + p = HolonomicFunction(Dx**5 - 1, x) + q = HolonomicFunction(x**3 + Dx, x) + r = HolonomicFunction((-x**18 + 45*x**14 - 525*x**10 + 1575*x**6 - x**3 - 630*x**2) + \ + (-x**15 + 30*x**11 - 195*x**7 + 210*x**3 - 1)*Dx + (x**18 - 45*x**14 + 525*x**10 - \ + 1575*x**6 + x**3 + 630*x**2)*Dx**5 + (x**15 - 30*x**11 + 195*x**7 - 210*x**3 + \ + 1)*Dx**6, x) + assert p+q == r + + p = x**2 + 3*x + 8 + q = x**3 - 7*x + 5 + p = p*Dx - p.diff() + q = q*Dx - q.diff() + r = HolonomicFunction(p, x) + HolonomicFunction(q, x) + s = HolonomicFunction((6*x**2 + 18*x + 14) + (-4*x**3 - 18*x**2 - 62*x + 10)*Dx +\ + (x**4 + 6*x**3 + 31*x**2 - 10*x - 71)*Dx**2, x) + assert r == s + + +def test_HolonomicFunction_multiplication(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx+x+x*Dx**2, x) + q = HolonomicFunction(x*Dx+Dx*x+Dx**2, x) + r = HolonomicFunction((8*x**6 + 4*x**4 + 6*x**2 + 3) + (24*x**5 - 4*x**3 + 24*x)*Dx + \ + (8*x**6 + 20*x**4 + 12*x**2 + 2)*Dx**2 + (8*x**5 + 4*x**3 + 4*x)*Dx**3 + \ + (2*x**4 + x**2)*Dx**4, x) + assert p*q == r + p = HolonomicFunction(Dx**2+1, x) + q = HolonomicFunction(Dx-1, x) + r = HolonomicFunction((2) + (-2)*Dx + (1)*Dx**2, x) + assert p*q == r + p = HolonomicFunction(Dx**2+1+x+Dx, x) + q = HolonomicFunction((Dx*x-1)**2, x) + r = HolonomicFunction((4*x**7 + 11*x**6 + 16*x**5 + 4*x**4 - 6*x**3 - 7*x**2 - 8*x - 2) + \ + (8*x**6 + 26*x**5 + 24*x**4 - 3*x**3 - 11*x**2 - 6*x - 2)*Dx + \ + (8*x**6 + 18*x**5 + 15*x**4 - 3*x**3 - 6*x**2 - 6*x - 2)*Dx**2 + (8*x**5 + \ + 10*x**4 + 6*x**3 - 2*x**2 - 4*x)*Dx**3 + (4*x**5 + 3*x**4 - x**2)*Dx**4, x) + assert p*q == r + p = HolonomicFunction(x*Dx**2-1, x) + q = HolonomicFunction(Dx*x-x, x) + r = HolonomicFunction((x - 3) + (-2*x + 2)*Dx + (x)*Dx**2, x) + assert p*q == r + + +def test_HolonomicFunction_power(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx+x+x*Dx**2, x) + a = HolonomicFunction(Dx, x) + for n in range(10): + assert a == p**n + a *= p + + +def test_addition_initial_condition(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx-1, x, 0, [3]) + q = HolonomicFunction(Dx**2+1, x, 0, [1, 0]) + r = HolonomicFunction(-1 + Dx - Dx**2 + Dx**3, x, 0, [4, 3, 2]) + assert p + q == r + p = HolonomicFunction(Dx - x + Dx**2, x, 0, [1, 2]) + q = HolonomicFunction(Dx**2 + x, x, 0, [1, 0]) + r = HolonomicFunction((-x**4 - x**3/4 - x**2 + Rational(1, 4)) + (x**3 + x**2/4 + x*Rational(3, 4) + 1)*Dx + \ + (x*Rational(-3, 2) + Rational(7, 4))*Dx**2 + (x**2 - x*Rational(7, 4) + Rational(1, 4))*Dx**3 + (x**2 + x/4 + S.Half)*Dx**4, x, 0, [2, 2, -2, 2]) + assert p + q == r + p = HolonomicFunction(Dx**2 + 4*x*Dx + x**2, x, 0, [3, 4]) + q = HolonomicFunction(Dx**2 + 1, x, 0, [1, 1]) + r = HolonomicFunction((x**6 + 2*x**4 - 5*x**2 - 6) + (4*x**5 + 36*x**3 - 32*x)*Dx + \ + (x**6 + 3*x**4 + 5*x**2 - 9)*Dx**2 + (4*x**5 + 36*x**3 - 32*x)*Dx**3 + (x**4 + \ + 10*x**2 - 3)*Dx**4, x, 0, [4, 5, -1, -17]) + assert p + q == r + q = HolonomicFunction(Dx**3 + x, x, 2, [3, 0, 1]) + p = HolonomicFunction(Dx - 1, x, 2, [1]) + r = HolonomicFunction((-x**2 - x + 1) + (x**2 + x)*Dx + (-x - 2)*Dx**3 + \ + (x + 1)*Dx**4, x, 2, [4, 1, 2, -5 ]) + assert p + q == r + p = expr_to_holonomic(sin(x)) + q = expr_to_holonomic(1/x, x0=1) + r = HolonomicFunction((x**2 + 6) + (x**3 + 2*x)*Dx + (x**2 + 6)*Dx**2 + (x**3 + 2*x)*Dx**3, \ + x, 1, [sin(1) + 1, -1 + cos(1), -sin(1) + 2]) + assert p + q == r + C_1 = symbols('C_1') + p = expr_to_holonomic(sqrt(x)) + q = expr_to_holonomic(sqrt(x**2-x)) + r = (p + q).to_expr().subs(C_1, -I/2).expand() + assert r == I*sqrt(x)*sqrt(-x + 1) + sqrt(x) + + +def test_multiplication_initial_condition(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx**2 + x*Dx - 1, x, 0, [3, 1]) + q = HolonomicFunction(Dx**2 + 1, x, 0, [1, 1]) + r = HolonomicFunction((x**4 + 14*x**2 + 60) + 4*x*Dx + (x**4 + 9*x**2 + 20)*Dx**2 + \ + (2*x**3 + 18*x)*Dx**3 + (x**2 + 10)*Dx**4, x, 0, [3, 4, 2, 3]) + assert p * q == r + p = HolonomicFunction(Dx**2 + x, x, 0, [1, 0]) + q = HolonomicFunction(Dx**3 - x**2, x, 0, [3, 3, 3]) + r = HolonomicFunction((x**8 - 37*x**7/27 - 10*x**6/27 - 164*x**5/9 - 184*x**4/9 + \ + 160*x**3/27 + 404*x**2/9 + 8*x + Rational(40, 3)) + (6*x**7 - 128*x**6/9 - 98*x**5/9 - 28*x**4/9 + \ + 8*x**3/9 + 28*x**2 + x*Rational(40, 9) - 40)*Dx + (3*x**6 - 82*x**5/9 + 76*x**4/9 + 4*x**3/3 + \ + 220*x**2/9 - x*Rational(80, 3))*Dx**2 + (-2*x**6 + 128*x**5/27 - 2*x**4/3 -80*x**2/9 + Rational(200, 9))*Dx**3 + \ + (3*x**5 - 64*x**4/9 - 28*x**3/9 + 6*x**2 - x*Rational(20, 9) - Rational(20, 3))*Dx**4 + (-4*x**3 + 64*x**2/9 + \ + x*Rational(8, 3))*Dx**5 + (x**4 - 64*x**3/27 - 4*x**2/3 + Rational(20, 9))*Dx**6, x, 0, [3, 3, 3, -3, -12, -24]) + assert p * q == r + p = HolonomicFunction(Dx - 1, x, 0, [2]) + q = HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]) + r = HolonomicFunction(2 -2*Dx + Dx**2, x, 0, [0, 2]) + assert p * q == r + q = HolonomicFunction(x*Dx**2 + 1 + 2*Dx, x, 0,[0, 1]) + r = HolonomicFunction((x - 1) + (-2*x + 2)*Dx + x*Dx**2, x, 0, [0, 2]) + assert p * q == r + p = HolonomicFunction(Dx**2 - 1, x, 0, [1, 3]) + q = HolonomicFunction(Dx**3 + 1, x, 0, [1, 2, 1]) + r = HolonomicFunction(6*Dx + 3*Dx**2 + 2*Dx**3 - 3*Dx**4 + Dx**6, x, 0, [1, 5, 14, 17, 17, 2]) + assert p * q == r + p = expr_to_holonomic(sin(x)) + q = expr_to_holonomic(1/x, x0=1) + r = HolonomicFunction(x + 2*Dx + x*Dx**2, x, 1, [sin(1), -sin(1) + cos(1)]) + assert p * q == r + p = expr_to_holonomic(sqrt(x)) + q = expr_to_holonomic(sqrt(x**2-x)) + r = (p * q).to_expr() + assert r == I*x*sqrt(-x + 1) + + +def test_HolonomicFunction_composition(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx-1, x).composition(x**2+x) + r = HolonomicFunction((-2*x - 1) + Dx, x) + assert p == r + p = HolonomicFunction(Dx**2+1, x).composition(x**5+x**2+1) + r = HolonomicFunction((125*x**12 + 150*x**9 + 60*x**6 + 8*x**3) + (-20*x**3 - 2)*Dx + \ + (5*x**4 + 2*x)*Dx**2, x) + assert p == r + p = HolonomicFunction(Dx**2*x+x, x).composition(2*x**3+x**2+1) + r = HolonomicFunction((216*x**9 + 324*x**8 + 180*x**7 + 152*x**6 + 112*x**5 + \ + 36*x**4 + 4*x**3) + (24*x**4 + 16*x**3 + 3*x**2 - 6*x - 1)*Dx + (6*x**5 + 5*x**4 + \ + x**3 + 3*x**2 + x)*Dx**2, x) + assert p == r + p = HolonomicFunction(Dx**2+1, x).composition(1-x**2) + r = HolonomicFunction((4*x**3) - Dx + x*Dx**2, x) + assert p == r + p = HolonomicFunction(Dx**2+1, x).composition(x - 2/(x**2 + 1)) + r = HolonomicFunction((x**12 + 6*x**10 + 12*x**9 + 15*x**8 + 48*x**7 + 68*x**6 + \ + 72*x**5 + 111*x**4 + 112*x**3 + 54*x**2 + 12*x + 1) + (12*x**8 + 32*x**6 + \ + 24*x**4 - 4)*Dx + (x**12 + 6*x**10 + 4*x**9 + 15*x**8 + 16*x**7 + 20*x**6 + 24*x**5+ \ + 15*x**4 + 16*x**3 + 6*x**2 + 4*x + 1)*Dx**2, x) + assert p == r + + +def test_from_hyper(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + p = hyper([1, 1], [Rational(3, 2)], x**2/4) + q = HolonomicFunction((4*x) + (5*x**2 - 8)*Dx + (x**3 - 4*x)*Dx**2, x, 1, [2*sqrt(3)*pi/9, -4*sqrt(3)*pi/27 + Rational(4, 3)]) + r = from_hyper(p) + assert r == q + p = from_hyper(hyper([1], [Rational(3, 2)], x**2/4)) + q = HolonomicFunction(-x + (-x**2/2 + 2)*Dx + x*Dx**2, x) + # x0 = 1 + y0 = '[sqrt(pi)*exp(1/4)*erf(1/2), -sqrt(pi)*exp(1/4)*erf(1/2)/2 + 1]' + assert sstr(p.y0) == y0 + assert q.annihilator == p.annihilator + + +def test_from_meijerg(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + p = from_meijerg(meijerg(([], [Rational(3, 2)]), ([S.Half], [S.Half, 1]), x)) + q = HolonomicFunction(x/2 - Rational(1, 4) + (-x**2 + x/4)*Dx + x**2*Dx**2 + x**3*Dx**3, x, 1, \ + [1/sqrt(pi), 1/(2*sqrt(pi)), -1/(4*sqrt(pi))]) + assert p == q + p = from_meijerg(meijerg(([], []), ([0], []), x)) + q = HolonomicFunction(1 + Dx, x, 0, [1]) + assert p == q + p = from_meijerg(meijerg(([1], []), ([S.Half], [0]), x)) + q = HolonomicFunction((x + S.Half)*Dx + x*Dx**2, x, 1, [sqrt(pi)*erf(1), exp(-1)]) + assert p == q + p = from_meijerg(meijerg(([0], [1]), ([0], []), 2*x**2)) + q = HolonomicFunction((3*x**2 - 1)*Dx + x**3*Dx**2, x, 1, [-exp(Rational(-1, 2)) + 1, -exp(Rational(-1, 2))]) + assert p == q + + +def test_to_Sequence(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + n = symbols('n', integer=True) + _, Sn = RecurrenceOperators(ZZ.old_poly_ring(n), 'Sn') + p = HolonomicFunction(x**2*Dx**4 + x + Dx, x).to_sequence() + q = [(HolonomicSequence(1 + (n + 2)*Sn**2 + (n**4 + 6*n**3 + 11*n**2 + 6*n)*Sn**3), 0, 1)] + assert p == q + p = HolonomicFunction(x**2*Dx**4 + x**3 + Dx**2, x).to_sequence() + q = [(HolonomicSequence(1 + (n**4 + 14*n**3 + 72*n**2 + 163*n + 140)*Sn**5), 0, 0)] + assert p == q + p = HolonomicFunction(x**3*Dx**4 + 1 + Dx**2, x).to_sequence() + q = [(HolonomicSequence(1 + (n**4 - 2*n**3 - n**2 + 2*n)*Sn + (n**2 + 3*n + 2)*Sn**2), 0, 0)] + assert p == q + p = HolonomicFunction(3*x**3*Dx**4 + 2*x*Dx + x*Dx**3, x).to_sequence() + q = [(HolonomicSequence(2*n + (3*n**4 - 6*n**3 - 3*n**2 + 6*n)*Sn + (n**3 + 3*n**2 + 2*n)*Sn**2), 0, 1)] + assert p == q + + +def test_to_Sequence_Initial_Coniditons(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + n = symbols('n', integer=True) + _, Sn = RecurrenceOperators(QQ.old_poly_ring(n), 'Sn') + p = HolonomicFunction(Dx - 1, x, 0, [1]).to_sequence() + q = [(HolonomicSequence(-1 + (n + 1)*Sn, 1), 0)] + assert p == q + p = HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]).to_sequence() + q = [(HolonomicSequence(1 + (n**2 + 3*n + 2)*Sn**2, [0, 1]), 0)] + assert p == q + p = HolonomicFunction(Dx**2 + 1 + x**3*Dx, x, 0, [2, 3]).to_sequence() + q = [(HolonomicSequence(n + Sn**2 + (n**2 + 7*n + 12)*Sn**4, [2, 3, -1, Rational(-1, 2), Rational(1, 12)]), 1)] + assert p == q + p = HolonomicFunction(x**3*Dx**5 + 1 + Dx, x).to_sequence() + q = [(HolonomicSequence(1 + (n + 1)*Sn + (n**5 - 5*n**3 + 4*n)*Sn**2), 0, 3)] + assert p == q + C_0, C_1, C_2, C_3 = symbols('C_0, C_1, C_2, C_3') + p = expr_to_holonomic(log(1+x**2)) + q = [(HolonomicSequence(n**2 + (n**2 + 2*n)*Sn**2, [0, 0, C_2]), 0, 1)] + assert p.to_sequence() == q + p = p.diff() + q = [(HolonomicSequence((n + 2) + (n + 2)*Sn**2, [C_0, 0]), 1, 0)] + assert p.to_sequence() == q + p = expr_to_holonomic(erf(x) + x).to_sequence() + q = [(HolonomicSequence((2*n**2 - 2*n) + (n**3 + 2*n**2 - n - 2)*Sn**2, [0, 1 + 2/sqrt(pi), 0, C_3]), 0, 2)] + assert p == q + +def test_series(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx**2 + 2*x*Dx, x, 0, [0, 1]).series(n=10) + q = x - x**3/3 + x**5/10 - x**7/42 + x**9/216 + O(x**10) + assert p == q + p = HolonomicFunction(Dx - 1, x).composition(x**2, 0, [1]) # e^(x**2) + q = HolonomicFunction(Dx**2 + 1, x, 0, [1, 0]) # cos(x) + r = (p * q).series(n=10) # expansion of cos(x) * exp(x**2) + s = 1 + x**2/2 + x**4/24 - 31*x**6/720 - 179*x**8/8064 + O(x**10) + assert r == s + t = HolonomicFunction((1 + x)*Dx**2 + Dx, x, 0, [0, 1]) # log(1 + x) + r = (p * t + q).series(n=10) + s = 1 + x - x**2 + 4*x**3/3 - 17*x**4/24 + 31*x**5/30 - 481*x**6/720 +\ + 71*x**7/105 - 20159*x**8/40320 + 379*x**9/840 + O(x**10) + assert r == s + p = HolonomicFunction((6+6*x-3*x**2) - (10*x-3*x**2-3*x**3)*Dx + \ + (4-6*x**3+2*x**4)*Dx**2, x, 0, [0, 1]).series(n=7) + q = x + x**3/6 - 3*x**4/16 + x**5/20 - 23*x**6/960 + O(x**7) + assert p == q + p = HolonomicFunction((6+6*x-3*x**2) - (10*x-3*x**2-3*x**3)*Dx + \ + (4-6*x**3+2*x**4)*Dx**2, x, 0, [1, 0]).series(n=7) + q = 1 - 3*x**2/4 - x**3/4 - 5*x**4/32 - 3*x**5/40 - 17*x**6/384 + O(x**7) + assert p == q + p = expr_to_holonomic(erf(x) + x).series(n=10) + C_3 = symbols('C_3') + q = (erf(x) + x).series(n=10) + assert p.subs(C_3, -2/(3*sqrt(pi))) == q + assert expr_to_holonomic(sqrt(x**3 + x)).series(n=10) == sqrt(x**3 + x).series(n=10) + assert expr_to_holonomic((2*x - 3*x**2)**Rational(1, 3)).series() == ((2*x - 3*x**2)**Rational(1, 3)).series() + assert expr_to_holonomic(sqrt(x**2-x)).series() == (sqrt(x**2-x)).series() + assert expr_to_holonomic(cos(x)**2/x**2, y0={-2: [1, 0, -1]}).series(n=10) == (cos(x)**2/x**2).series(n=10) + assert expr_to_holonomic(cos(x)**2/x**2, x0=1).series(n=10).together() == (cos(x)**2/x**2).series(n=10, x0=1).together() + assert expr_to_holonomic(cos(x-1)**2/(x-1)**2, x0=1, y0={-2: [1, 0, -1]}).series(n=10) \ + == (cos(x-1)**2/(x-1)**2).series(x0=1, n=10) + +def test_evalf_euler(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + + # log(1+x) + p = HolonomicFunction((1 + x)*Dx**2 + Dx, x, 0, [0, 1]) + + # path taken is a straight line from 0 to 1, on the real axis + r = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] + s = '0.699525841805253' # approx. equal to log(2) i.e. 0.693147180559945 + assert sstr(p.evalf(r, method='Euler')[-1]) == s + + # path taken is a triangle 0-->1+i-->2 + r = [0.1 + 0.1*I] + for i in range(9): + r.append(r[-1]+0.1+0.1*I) + for i in range(10): + r.append(r[-1]+0.1-0.1*I) + + # close to the exact solution 1.09861228866811 + # imaginary part also close to zero + s = '1.07530466271334 - 0.0251200594793912*I' + assert sstr(p.evalf(r, method='Euler')[-1]) == s + + # sin(x) + p = HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]) + s = '0.905546532085401 - 6.93889390390723e-18*I' + assert sstr(p.evalf(r, method='Euler')[-1]) == s + + # computing sin(pi/2) using this method + # using a linear path from 0 to pi/2 + r = [0.1] + for i in range(14): + r.append(r[-1] + 0.1) + r.append(pi/2) + s = '1.08016557252834' # close to 1.0 (exact solution) + assert sstr(p.evalf(r, method='Euler')[-1]) == s + + # trying different path, a rectangle (0-->i-->pi/2 + i-->pi/2) + # computing the same value sin(pi/2) using different path + r = [0.1*I] + for i in range(9): + r.append(r[-1]+0.1*I) + for i in range(15): + r.append(r[-1]+0.1) + r.append(pi/2+I) + for i in range(10): + r.append(r[-1]-0.1*I) + + # close to 1.0 + s = '0.976882381836257 - 1.65557671738537e-16*I' + assert sstr(p.evalf(r, method='Euler')[-1]) == s + + # cos(x) + p = HolonomicFunction(Dx**2 + 1, x, 0, [1, 0]) + # compute cos(pi) along 0-->pi + r = [0.05] + for i in range(61): + r.append(r[-1]+0.05) + r.append(pi) + # close to -1 (exact answer) + s = '-1.08140824719196' + assert sstr(p.evalf(r, method='Euler')[-1]) == s + + # a rectangular path (0 -> i -> 2+i -> 2) + r = [0.1*I] + for i in range(9): + r.append(r[-1]+0.1*I) + for i in range(20): + r.append(r[-1]+0.1) + for i in range(10): + r.append(r[-1]-0.1*I) + + p = HolonomicFunction(Dx**2 + 1, x, 0, [1,1]).evalf(r, method='Euler') + s = '0.501421652861245 - 3.88578058618805e-16*I' + assert sstr(p[-1]) == s + +def test_evalf_rk4(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + + # log(1+x) + p = HolonomicFunction((1 + x)*Dx**2 + Dx, x, 0, [0, 1]) + + # path taken is a straight line from 0 to 1, on the real axis + r = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] + s = '0.693146363174626' # approx. equal to log(2) i.e. 0.693147180559945 + assert sstr(p.evalf(r)[-1]) == s + + # path taken is a triangle 0-->1+i-->2 + r = [0.1 + 0.1*I] + for i in range(9): + r.append(r[-1]+0.1+0.1*I) + for i in range(10): + r.append(r[-1]+0.1-0.1*I) + + # close to the exact solution 1.09861228866811 + # imaginary part also close to zero + s = '1.098616 + 1.36083e-7*I' + assert sstr(p.evalf(r)[-1].n(7)) == s + + # sin(x) + p = HolonomicFunction(Dx**2 + 1, x, 0, [0, 1]) + s = '0.90929463522785 + 1.52655665885959e-16*I' + assert sstr(p.evalf(r)[-1]) == s + + # computing sin(pi/2) using this method + # using a linear path from 0 to pi/2 + r = [0.1] + for i in range(14): + r.append(r[-1] + 0.1) + r.append(pi/2) + s = '0.999999895088917' # close to 1.0 (exact solution) + assert sstr(p.evalf(r)[-1]) == s + + # trying different path, a rectangle (0-->i-->pi/2 + i-->pi/2) + # computing the same value sin(pi/2) using different path + r = [0.1*I] + for i in range(9): + r.append(r[-1]+0.1*I) + for i in range(15): + r.append(r[-1]+0.1) + r.append(pi/2+I) + for i in range(10): + r.append(r[-1]-0.1*I) + + # close to 1.0 + s = '1.00000003415141 + 6.11940487991086e-16*I' + assert sstr(p.evalf(r)[-1]) == s + + # cos(x) + p = HolonomicFunction(Dx**2 + 1, x, 0, [1, 0]) + # compute cos(pi) along 0-->pi + r = [0.05] + for i in range(61): + r.append(r[-1]+0.05) + r.append(pi) + # close to -1 (exact answer) + s = '-0.999999993238714' + assert sstr(p.evalf(r)[-1]) == s + + # a rectangular path (0 -> i -> 2+i -> 2) + r = [0.1*I] + for i in range(9): + r.append(r[-1]+0.1*I) + for i in range(20): + r.append(r[-1]+0.1) + for i in range(10): + r.append(r[-1]-0.1*I) + + p = HolonomicFunction(Dx**2 + 1, x, 0, [1,1]).evalf(r) + s = '0.493152791638442 - 1.41553435639707e-15*I' + assert sstr(p[-1]) == s + + +def test_expr_to_holonomic(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + p = expr_to_holonomic((sin(x)/x)**2) + q = HolonomicFunction(8*x + (4*x**2 + 6)*Dx + 6*x*Dx**2 + x**2*Dx**3, x, 0, \ + [1, 0, Rational(-2, 3)]) + assert p == q + p = expr_to_holonomic(1/(1+x**2)**2) + q = HolonomicFunction(4*x + (x**2 + 1)*Dx, x, 0, [1]) + assert p == q + p = expr_to_holonomic(exp(x)*sin(x)+x*log(1+x)) + q = HolonomicFunction((2*x**3 + 10*x**2 + 20*x + 18) + (-2*x**4 - 10*x**3 - 20*x**2 \ + - 18*x)*Dx + (2*x**5 + 6*x**4 + 7*x**3 + 8*x**2 + 10*x - 4)*Dx**2 + \ + (-2*x**5 - 5*x**4 - 2*x**3 + 2*x**2 - x + 4)*Dx**3 + (x**5 + 2*x**4 - x**3 - \ + 7*x**2/2 + x + Rational(5, 2))*Dx**4, x, 0, [0, 1, 4, -1]) + assert p == q + p = expr_to_holonomic(x*exp(x)+cos(x)+1) + q = HolonomicFunction((-x - 3)*Dx + (x + 2)*Dx**2 + (-x - 3)*Dx**3 + (x + 2)*Dx**4, x, \ + 0, [2, 1, 1, 3]) + assert p == q + assert (x*exp(x)+cos(x)+1).series(n=10) == p.series(n=10) + p = expr_to_holonomic(log(1 + x)**2 + 1) + q = HolonomicFunction(Dx + (3*x + 3)*Dx**2 + (x**2 + 2*x + 1)*Dx**3, x, 0, [1, 0, 2]) + assert p == q + p = expr_to_holonomic(erf(x)**2 + x) + q = HolonomicFunction((8*x**4 - 2*x**2 + 2)*Dx**2 + (6*x**3 - x/2)*Dx**3 + \ + (x**2+ Rational(1, 4))*Dx**4, x, 0, [0, 1, 8/pi, 0]) + assert p == q + p = expr_to_holonomic(cosh(x)*x) + q = HolonomicFunction((-x**2 + 2) -2*x*Dx + x**2*Dx**2, x, 0, [0, 1]) + assert p == q + p = expr_to_holonomic(besselj(2, x)) + q = HolonomicFunction((x**2 - 4) + x*Dx + x**2*Dx**2, x, 0, [0, 0]) + assert p == q + p = expr_to_holonomic(besselj(0, x) + exp(x)) + q = HolonomicFunction((-x**2 - x/2 + S.Half) + (x**2 - x/2 - Rational(3, 2))*Dx + (-x**2 + x/2 + 1)*Dx**2 +\ + (x**2 + x/2)*Dx**3, x, 0, [2, 1, S.Half]) + assert p == q + p = expr_to_holonomic(sin(x)**2/x) + q = HolonomicFunction(4 + 4*x*Dx + 3*Dx**2 + x*Dx**3, x, 0, [0, 1, 0]) + assert p == q + p = expr_to_holonomic(sin(x)**2/x, x0=2) + q = HolonomicFunction((4) + (4*x)*Dx + (3)*Dx**2 + (x)*Dx**3, x, 2, [sin(2)**2/2, + sin(2)*cos(2) - sin(2)**2/4, -3*sin(2)**2/4 + cos(2)**2 - sin(2)*cos(2)]) + assert p == q + p = expr_to_holonomic(log(x)/2 - Ci(2*x)/2 + Ci(2)/2) + q = HolonomicFunction(4*Dx + 4*x*Dx**2 + 3*Dx**3 + x*Dx**4, x, 0, \ + [-log(2)/2 - EulerGamma/2 + Ci(2)/2, 0, 1, 0]) + assert p == q + p = p.to_expr() + q = log(x)/2 - Ci(2*x)/2 + Ci(2)/2 + assert p == q + p = expr_to_holonomic(x**S.Half, x0=1) + q = HolonomicFunction(x*Dx - S.Half, x, 1, [1]) + assert p == q + p = expr_to_holonomic(sqrt(1 + x**2)) + q = HolonomicFunction((-x) + (x**2 + 1)*Dx, x, 0, [1]) + assert p == q + assert (expr_to_holonomic(sqrt(x) + sqrt(2*x)).to_expr()-\ + (sqrt(x) + sqrt(2*x))).simplify() == 0 + assert expr_to_holonomic(3*x+2*sqrt(x)).to_expr() == 3*x+2*sqrt(x) + p = expr_to_holonomic((x**4+x**3+5*x**2+3*x+2)/x**2, lenics=3) + q = HolonomicFunction((-2*x**4 - x**3 + 3*x + 4) + (x**5 + x**4 + 5*x**3 + 3*x**2 + \ + 2*x)*Dx, x, 0, {-2: [2, 3, 5]}) + assert p == q + p = expr_to_holonomic(1/(x-1)**2, lenics=3, x0=1) + q = HolonomicFunction((2) + (x - 1)*Dx, x, 1, {-2: [1, 0, 0]}) + assert p == q + a = symbols("a") + p = expr_to_holonomic(sqrt(a*x), x=x) + assert p.to_expr() == sqrt(a)*sqrt(x) + +def test_to_hyper(): + x = symbols('x') + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx - 2, x, 0, [3]).to_hyper() + q = 3 * hyper([], [], 2*x) + assert p == q + p = hyperexpand(HolonomicFunction((1 + x) * Dx - 3, x, 0, [2]).to_hyper()).expand() + q = 2*x**3 + 6*x**2 + 6*x + 2 + assert p == q + p = HolonomicFunction((1 + x)*Dx**2 + Dx, x, 0, [0, 1]).to_hyper() + q = -x**2*hyper((2, 2, 1), (3, 2), -x)/2 + x + assert p == q + p = HolonomicFunction(2*x*Dx + Dx**2, x, 0, [0, 2/sqrt(pi)]).to_hyper() + q = 2*x*hyper((S.Half,), (Rational(3, 2),), -x**2)/sqrt(pi) + assert p == q + p = hyperexpand(HolonomicFunction(2*x*Dx + Dx**2, x, 0, [1, -2/sqrt(pi)]).to_hyper()) + q = erfc(x) + assert p.rewrite(erfc) == q + p = hyperexpand(HolonomicFunction((x**2 - 1) + x*Dx + x**2*Dx**2, + x, 0, [0, S.Half]).to_hyper()) + q = besselj(1, x) + assert p == q + p = hyperexpand(HolonomicFunction(x*Dx**2 + Dx + x, x, 0, [1, 0]).to_hyper()) + q = besselj(0, x) + assert p == q + +def test_to_expr(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(Dx - 1, x, 0, [1]).to_expr() + q = exp(x) + assert p == q + p = HolonomicFunction(Dx**2 + 1, x, 0, [1, 0]).to_expr() + q = cos(x) + assert p == q + p = HolonomicFunction(Dx**2 - 1, x, 0, [1, 0]).to_expr() + q = cosh(x) + assert p == q + p = HolonomicFunction(2 + (4*x - 1)*Dx + \ + (x**2 - x)*Dx**2, x, 0, [1, 2]).to_expr().expand() + q = 1/(x**2 - 2*x + 1) + assert p == q + p = expr_to_holonomic(sin(x)**2/x).integrate((x, 0, x)).to_expr() + q = (sin(x)**2/x).integrate((x, 0, x)) + assert p == q + C_0, C_1, C_2, C_3 = symbols('C_0, C_1, C_2, C_3') + p = expr_to_holonomic(log(1+x**2)).to_expr() + q = C_2*log(x**2 + 1) + assert p == q + p = expr_to_holonomic(log(1+x**2)).diff().to_expr() + q = C_0*x/(x**2 + 1) + assert p == q + p = expr_to_holonomic(erf(x) + x).to_expr() + q = 3*C_3*x - 3*sqrt(pi)*C_3*erf(x)/2 + x + 2*x/sqrt(pi) + assert p == q + p = expr_to_holonomic(sqrt(x), x0=1).to_expr() + assert p == sqrt(x) + assert expr_to_holonomic(sqrt(x)).to_expr() == sqrt(x) + p = expr_to_holonomic(sqrt(1 + x**2)).to_expr() + assert p == sqrt(1+x**2) + p = expr_to_holonomic((2*x**2 + 1)**Rational(2, 3)).to_expr() + assert p == (2*x**2 + 1)**Rational(2, 3) + p = expr_to_holonomic(sqrt(-x**2+2*x)).to_expr() + assert p == sqrt(x)*sqrt(-x + 2) + p = expr_to_holonomic((-2*x**3+7*x)**Rational(2, 3)).to_expr() + q = x**Rational(2, 3)*(-2*x**2 + 7)**Rational(2, 3) + assert p == q + p = from_hyper(hyper((-2, -3), (S.Half, ), x)) + s = hyperexpand(hyper((-2, -3), (S.Half, ), x)) + D_0 = Symbol('D_0') + C_0 = Symbol('C_0') + assert (p.to_expr().subs({C_0:1, D_0:0}) - s).simplify() == 0 + p.y0 = {0: [1], S.Half: [0]} + assert p.to_expr() == s + assert expr_to_holonomic(x**5).to_expr() == x**5 + assert expr_to_holonomic(2*x**3-3*x**2).to_expr().expand() == \ + 2*x**3-3*x**2 + a = symbols("a") + p = (expr_to_holonomic(1.4*x)*expr_to_holonomic(a*x, x)).to_expr() + q = 1.4*a*x**2 + assert p == q + p = (expr_to_holonomic(1.4*x)+expr_to_holonomic(a*x, x)).to_expr() + q = x*(a + 1.4) + assert p == q + p = (expr_to_holonomic(1.4*x)+expr_to_holonomic(x)).to_expr() + assert p == 2.4*x + + +def test_integrate(): + x = symbols('x') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = expr_to_holonomic(sin(x)**2/x, x0=1).integrate((x, 2, 3)) + q = '0.166270406994788' + assert sstr(p) == q + p = expr_to_holonomic(sin(x)).integrate((x, 0, x)).to_expr() + q = 1 - cos(x) + assert p == q + p = expr_to_holonomic(sin(x)).integrate((x, 0, 3)) + q = 1 - cos(3) + assert p == q + p = expr_to_holonomic(sin(x)/x, x0=1).integrate((x, 1, 2)) + q = '0.659329913368450' + assert sstr(p) == q + p = expr_to_holonomic(sin(x)**2/x, x0=1).integrate((x, 1, 0)) + q = '-0.423690480850035' + assert sstr(p) == q + p = expr_to_holonomic(sin(x)/x) + assert p.integrate(x).to_expr() == Si(x) + assert p.integrate((x, 0, 2)) == Si(2) + p = expr_to_holonomic(sin(x)**2/x) + q = p.to_expr() + assert p.integrate(x).to_expr() == q.integrate((x, 0, x)) + assert p.integrate((x, 0, 1)) == q.integrate((x, 0, 1)) + assert expr_to_holonomic(1/x, x0=1).integrate(x).to_expr() == log(x) + p = expr_to_holonomic((x + 1)**3*exp(-x), x0=-1).integrate(x).to_expr() + q = (-x**3 - 6*x**2 - 15*x + 6*exp(x + 1) - 16)*exp(-x) + assert p == q + p = expr_to_holonomic(cos(x)**2/x**2, y0={-2: [1, 0, -1]}).integrate(x).to_expr() + q = -Si(2*x) - cos(x)**2/x + assert p == q + p = expr_to_holonomic(sqrt(x**2+x)).integrate(x).to_expr() + q = (x**Rational(3, 2)*(2*x**2 + 3*x + 1) - x*sqrt(x + 1)*asinh(sqrt(x)))/(4*x*sqrt(x + 1)) + assert p == q + p = expr_to_holonomic(sqrt(x**2+1)).integrate(x).to_expr() + q = (sqrt(x**2+1)).integrate(x) + assert (p-q).simplify() == 0 + p = expr_to_holonomic(1/x**2, y0={-2:[1, 0, 0]}) + r = expr_to_holonomic(1/x**2, lenics=3) + assert p == r + q = expr_to_holonomic(cos(x)**2) + assert (r*q).integrate(x).to_expr() == -Si(2*x) - cos(x)**2/x + + +def test_diff(): + x, y = symbols('x, y') + R, Dx = DifferentialOperators(ZZ.old_poly_ring(x), 'Dx') + p = HolonomicFunction(x*Dx**2 + 1, x, 0, [0, 1]) + assert p.diff().to_expr() == p.to_expr().diff().simplify() + p = HolonomicFunction(Dx**2 - 1, x, 0, [1, 0]) + assert p.diff(x, 2).to_expr() == p.to_expr() + p = expr_to_holonomic(Si(x)) + assert p.diff().to_expr() == sin(x)/x + assert p.diff(y) == 0 + C_0, C_1, C_2, C_3 = symbols('C_0, C_1, C_2, C_3') + q = Si(x) + assert p.diff(x).to_expr() == q.diff() + assert p.diff(x, 2).to_expr().subs(C_0, Rational(-1, 3)).cancel() == q.diff(x, 2).cancel() + assert p.diff(x, 3).series().subs({C_3: Rational(-1, 3), C_0: 0}) == q.diff(x, 3).series() + + +def test_extended_domain_in_expr_to_holonomic(): + x = symbols('x') + p = expr_to_holonomic(1.2*cos(3.1*x)) + assert p.to_expr() == 1.2*cos(3.1*x) + assert sstr(p.integrate(x).to_expr()) == '0.387096774193548*sin(3.1*x)' + _, Dx = DifferentialOperators(RR.old_poly_ring(x), 'Dx') + p = expr_to_holonomic(1.1329138213*x) + q = HolonomicFunction((-1.1329138213) + (1.1329138213*x)*Dx, x, 0, {1: [1.1329138213]}) + assert p == q + assert p.to_expr() == 1.1329138213*x + assert sstr(p.integrate((x, 1, 2))) == sstr((1.1329138213*x).integrate((x, 1, 2))) + y, z = symbols('y, z') + p = expr_to_holonomic(sin(x*y*z), x=x) + assert p.to_expr() == sin(x*y*z) + assert p.integrate(x).to_expr() == (-cos(x*y*z) + 1)/(y*z) + p = expr_to_holonomic(sin(x*y + z), x=x).integrate(x).to_expr() + q = (cos(z) - cos(x*y + z))/y + assert p == q + a = symbols('a') + p = expr_to_holonomic(a*x, x) + assert p.to_expr() == a*x + assert p.integrate(x).to_expr() == a*x**2/2 + D_2, C_1 = symbols("D_2, C_1") + p = expr_to_holonomic(x) + expr_to_holonomic(1.2*cos(x)) + p = p.to_expr().subs(D_2, 0) + assert p - x - 1.2*cos(1.0*x) == 0 + p = expr_to_holonomic(x) * expr_to_holonomic(1.2*cos(x)) + p = p.to_expr().subs(C_1, 0) + assert p - 1.2*x*cos(1.0*x) == 0 + + +def test_to_meijerg(): + x = symbols('x') + assert hyperexpand(expr_to_holonomic(sin(x)).to_meijerg()) == sin(x) + assert hyperexpand(expr_to_holonomic(cos(x)).to_meijerg()) == cos(x) + assert hyperexpand(expr_to_holonomic(exp(x)).to_meijerg()) == exp(x) + assert hyperexpand(expr_to_holonomic(log(x)).to_meijerg()).simplify() == log(x) + assert expr_to_holonomic(4*x**2/3 + 7).to_meijerg() == 4*x**2/3 + 7 + assert hyperexpand(expr_to_holonomic(besselj(2, x), lenics=3).to_meijerg()) == besselj(2, x) + p = hyper((Rational(-1, 2), -3), (), x) + assert from_hyper(p).to_meijerg() == hyperexpand(p) + p = hyper((S.One, S(3)), (S(2), ), x) + assert (hyperexpand(from_hyper(p).to_meijerg()) - hyperexpand(p)).expand() == 0 + p = from_hyper(hyper((-2, -3), (S.Half, ), x)) + s = hyperexpand(hyper((-2, -3), (S.Half, ), x)) + C_0 = Symbol('C_0') + C_1 = Symbol('C_1') + D_0 = Symbol('D_0') + assert (hyperexpand(p.to_meijerg()).subs({C_0:1, D_0:0}) - s).simplify() == 0 + p.y0 = {0: [1], S.Half: [0]} + assert (hyperexpand(p.to_meijerg()) - s).simplify() == 0 + p = expr_to_holonomic(besselj(S.Half, x), initcond=False) + assert (p.to_expr() - (D_0*sin(x) + C_0*cos(x) + C_1*sin(x))/sqrt(x)).simplify() == 0 + p = expr_to_holonomic(besselj(S.Half, x), y0={Rational(-1, 2): [sqrt(2)/sqrt(pi), sqrt(2)/sqrt(pi)]}) + assert (p.to_expr() - besselj(S.Half, x) - besselj(Rational(-1, 2), x)).simplify() == 0 + + +def test_gaussian(): + mu, x = symbols("mu x") + sd = symbols("sd", positive=True) + Q = QQ[mu, sd].get_field() + e = sqrt(2)*exp(-(-mu + x)**2/(2*sd**2))/(2*sqrt(pi)*sd) + h1 = expr_to_holonomic(e, x, domain=Q) + + _, Dx = DifferentialOperators(Q.old_poly_ring(x), 'Dx') + h2 = HolonomicFunction((-mu/sd**2 + x/sd**2) + (1)*Dx, x) + + assert h1 == h2 + + +def test_beta(): + a, b, x = symbols("a b x", positive=True) + e = x**(a - 1)*(-x + 1)**(b - 1)/beta(a, b) + Q = QQ[a, b].get_field() + h1 = expr_to_holonomic(e, x, domain=Q) + + _, Dx = DifferentialOperators(Q.old_poly_ring(x), 'Dx') + h2 = HolonomicFunction((a + x*(-a - b + 2) - 1) + (x**2 - x)*Dx, x) + + assert h1 == h2 + + +def test_gamma(): + a, b, x = symbols("a b x", positive=True) + e = b**(-a)*x**(a - 1)*exp(-x/b)/gamma(a) + Q = QQ[a, b].get_field() + h1 = expr_to_holonomic(e, x, domain=Q) + + _, Dx = DifferentialOperators(Q.old_poly_ring(x), 'Dx') + h2 = HolonomicFunction((-a + 1 + x/b) + (x)*Dx, x) + + assert h1 == h2 + + +def test_symbolic_power(): + x, n = symbols("x n") + Q = QQ[n].get_field() + _, Dx = DifferentialOperators(Q.old_poly_ring(x), 'Dx') + h1 = HolonomicFunction((-1) + (x)*Dx, x) ** -n + h2 = HolonomicFunction((n) + (x)*Dx, x) + + assert h1 == h2 + + +def test_negative_power(): + x = symbols("x") + _, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + h1 = HolonomicFunction((-1) + (x)*Dx, x) ** -2 + h2 = HolonomicFunction((2) + (x)*Dx, x) + + assert h1 == h2 + + +def test_expr_in_power(): + x, n = symbols("x n") + Q = QQ[n].get_field() + _, Dx = DifferentialOperators(Q.old_poly_ring(x), 'Dx') + h1 = HolonomicFunction((-1) + (x)*Dx, x) ** (n - 3) + h2 = HolonomicFunction((-n + 3) + (x)*Dx, x) + + assert h1 == h2 + + +def test_DifferentialOperatorEqPoly(): + x = symbols('x', integer=True) + R, Dx = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + do = DifferentialOperator([x**2, R.base.zero, R.base.zero], R) + do2 = DifferentialOperator([x**2, 1, x], R) + assert not do == do2 + + # polynomial comparison issue, see https://github.com/sympy/sympy/pull/15799 + # should work once that is solved + # p = do.listofpoly[0] + # assert do == p + + p2 = do2.listofpoly[0] + assert not do2 == p2 + + +def test_DifferentialOperatorPow(): + x = symbols('x', integer=True) + R, _ = DifferentialOperators(QQ.old_poly_ring(x), 'Dx') + do = DifferentialOperator([x**2, R.base.zero, R.base.zero], R) + a = DifferentialOperator([R.base.one], R) + for n in range(10): + assert a == do**n + a *= do diff --git a/phivenv/Lib/site-packages/sympy/holonomic/tests/test_recurrence.py b/phivenv/Lib/site-packages/sympy/holonomic/tests/test_recurrence.py new file mode 100644 index 0000000000000000000000000000000000000000..526595e91c5fc507877275e3e53e78c6f3716095 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/holonomic/tests/test_recurrence.py @@ -0,0 +1,41 @@ +from sympy.holonomic.recurrence import RecurrenceOperators, RecurrenceOperator +from sympy.core.symbol import symbols +from sympy.polys.domains.rationalfield import QQ + + +def test_RecurrenceOperator(): + n = symbols('n', integer=True) + R, Sn = RecurrenceOperators(QQ.old_poly_ring(n), 'Sn') + assert Sn*n == (n + 1)*Sn + assert Sn*n**2 == (n**2+1+2*n)*Sn + assert Sn**2*n**2 == (n**2 + 4*n + 4)*Sn**2 + p = (Sn**3*n**2 + Sn*n)**2 + q = (n**2 + 3*n + 2)*Sn**2 + (2*n**3 + 19*n**2 + 57*n + 52)*Sn**4 + (n**4 + 18*n**3 + \ + 117*n**2 + 324*n + 324)*Sn**6 + assert p == q + + +def test_RecurrenceOperatorEqPoly(): + n = symbols('n', integer=True) + R, Sn = RecurrenceOperators(QQ.old_poly_ring(n), 'Sn') + rr = RecurrenceOperator([n**2, 0, 0], R) + rr2 = RecurrenceOperator([n**2, 1, n], R) + assert not rr == rr2 + + # polynomial comparison issue, see https://github.com/sympy/sympy/pull/15799 + # should work once that is solved + # d = rr.listofpoly[0] + # assert rr == d + + d2 = rr2.listofpoly[0] + assert not rr2 == d2 + + +def test_RecurrenceOperatorPow(): + n = symbols('n', integer=True) + R, _ = RecurrenceOperators(QQ.old_poly_ring(n), 'Sn') + rr = RecurrenceOperator([n**2, 0, 0], R) + a = RecurrenceOperator([R.base.one], R) + for m in range(10): + assert a == rr**m + a *= rr diff --git a/phivenv/Lib/site-packages/sympy/integrals/__init__.py b/phivenv/Lib/site-packages/sympy/integrals/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e78fe96f84d68e9b119571eb22dedb7033811b23 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/__init__.py @@ -0,0 +1,45 @@ +"""Integration functions that integrate a SymPy expression. + + Examples + ======== + + >>> from sympy import integrate, sin + >>> from sympy.abc import x + >>> integrate(1/x,x) + log(x) + >>> integrate(sin(x),x) + -cos(x) +""" +from .integrals import integrate, Integral, line_integrate +from .transforms import (mellin_transform, inverse_mellin_transform, + MellinTransform, InverseMellinTransform, + laplace_transform, inverse_laplace_transform, + laplace_correspondence, laplace_initial_conds, + LaplaceTransform, InverseLaplaceTransform, + fourier_transform, inverse_fourier_transform, + FourierTransform, InverseFourierTransform, + sine_transform, inverse_sine_transform, + SineTransform, InverseSineTransform, + cosine_transform, inverse_cosine_transform, + CosineTransform, InverseCosineTransform, + hankel_transform, inverse_hankel_transform, + HankelTransform, InverseHankelTransform) +from .singularityfunctions import singularityintegrate + +__all__ = [ + 'integrate', 'Integral', 'line_integrate', + + 'mellin_transform', 'inverse_mellin_transform', 'MellinTransform', + 'InverseMellinTransform', 'laplace_transform', + 'inverse_laplace_transform', 'LaplaceTransform', + 'laplace_correspondence', 'laplace_initial_conds', + 'InverseLaplaceTransform', 'fourier_transform', + 'inverse_fourier_transform', 'FourierTransform', + 'InverseFourierTransform', 'sine_transform', 'inverse_sine_transform', + 'SineTransform', 'InverseSineTransform', 'cosine_transform', + 'inverse_cosine_transform', 'CosineTransform', 'InverseCosineTransform', + 'hankel_transform', 'inverse_hankel_transform', 'HankelTransform', + 'InverseHankelTransform', + + 'singularityintegrate', +] diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c407954ecd952db27f1bf261b282f8887d3c4582 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/deltafunctions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/deltafunctions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adb7113613e3cab4d51335d9e1e9113cb14296c6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/deltafunctions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/heurisch.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/heurisch.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10ba7a74c4b86f83d5b9c597b8297a73ab19134f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/heurisch.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/integrals.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/integrals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff5379c187c293e21bd4ca8affea4c8b5126ab22 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/integrals.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/intpoly.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/intpoly.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41848b662e0dc03be7b96a648d16a75be00a3733 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/intpoly.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/laplace.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/laplace.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3a7e9d148e140b002f47e2eefd44886f643924b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/laplace.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/manualintegrate.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/manualintegrate.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b847d9e5403fbfd04ee310208abfaad2e4029c4f Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/manualintegrate.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/meijerint.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/meijerint.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d4fff449eaacf3f803b8d7931f60357f1759e98 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/meijerint.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/meijerint_doc.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/meijerint_doc.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8fe9ffb1bb10859420278875655a9048dbec489 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/meijerint_doc.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/prde.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/prde.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5db4aaf6ca5755e41367c917036766346fcda8a8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/prde.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/quadrature.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/quadrature.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..553b006ec583f80a43fb94737ac42548c61c9f37 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/quadrature.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/rationaltools.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/rationaltools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cae4a5a55e693177f89edca9a95593ad4a1f5d0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/rationaltools.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/rde.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/rde.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..963a99a5c9842889c7506344bbcaef44b3f50e14 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/rde.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/risch.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/risch.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7531dfb62185186542a88427a514dcec6c43dd53 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/risch.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/singularityfunctions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/singularityfunctions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10a537254cc37bd9678f44330a0f8daf6d7da587 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/singularityfunctions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/transforms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/transforms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4b5f4fdf3272e70eaf948f287d13e6601b2068e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/transforms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/__pycache__/trigonometry.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/trigonometry.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfe44b1f1b91a795341aed1925a0b8c9c6a98e16 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/__pycache__/trigonometry.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__init__.py b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3102eb4dc37f852c26b79c43787613c1d209afe Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/bench_integrate.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/bench_integrate.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23f2bdac2814350c9a8dbbdf0242436039261dce Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/bench_integrate.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/bench_trigintegrate.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/bench_trigintegrate.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0f8fb16395f0d4947b13fd456d09675ce3d1c76 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/__pycache__/bench_trigintegrate.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/benchmarks/bench_integrate.py b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/bench_integrate.py new file mode 100644 index 0000000000000000000000000000000000000000..833bc57403b34df1e75c798084ffc4d8afe9eae6 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/bench_integrate.py @@ -0,0 +1,21 @@ +from sympy.core.symbol import Symbol +from sympy.functions.elementary.trigonometric import sin +from sympy.integrals.integrals import integrate + +x = Symbol('x') + + +def bench_integrate_sin(): + integrate(sin(x), x) + + +def bench_integrate_x1sin(): + integrate(x**1*sin(x), x) + + +def bench_integrate_x2sin(): + integrate(x**2*sin(x), x) + + +def bench_integrate_x3sin(): + integrate(x**3*sin(x), x) diff --git a/phivenv/Lib/site-packages/sympy/integrals/benchmarks/bench_trigintegrate.py b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/bench_trigintegrate.py new file mode 100644 index 0000000000000000000000000000000000000000..403c5471b8048ff2aa97bf2f837b9ea05f0fd904 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/benchmarks/bench_trigintegrate.py @@ -0,0 +1,13 @@ +from sympy.core.symbol import Symbol +from sympy.functions.elementary.trigonometric import sin +from sympy.integrals.trigonometry import trigintegrate + +x = Symbol('x') + + +def timeit_trigintegrate_sin3x(): + trigintegrate(sin(x)**3, x) + + +def timeit_trigintegrate_x2(): + trigintegrate(x**2, x) # -> None diff --git a/phivenv/Lib/site-packages/sympy/integrals/deltafunctions.py b/phivenv/Lib/site-packages/sympy/integrals/deltafunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..ae9fef0b0010a313e0866a54d978024dd475f882 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/deltafunctions.py @@ -0,0 +1,201 @@ +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.functions import DiracDelta, Heaviside +from .integrals import Integral, integrate + + +def change_mul(node, x): + """change_mul(node, x) + + Rearranges the operands of a product, bringing to front any simple + DiracDelta expression. + + Explanation + =========== + + If no simple DiracDelta expression was found, then all the DiracDelta + expressions are simplified (using DiracDelta.expand(diracdelta=True, wrt=x)). + + Return: (dirac, new node) + Where: + o dirac is either a simple DiracDelta expression or None (if no simple + expression was found); + o new node is either a simplified DiracDelta expressions or None (if it + could not be simplified). + + Examples + ======== + + >>> from sympy import DiracDelta, cos + >>> from sympy.integrals.deltafunctions import change_mul + >>> from sympy.abc import x, y + >>> change_mul(x*y*DiracDelta(x)*cos(x), x) + (DiracDelta(x), x*y*cos(x)) + >>> change_mul(x*y*DiracDelta(x**2 - 1)*cos(x), x) + (None, x*y*cos(x)*DiracDelta(x - 1)/2 + x*y*cos(x)*DiracDelta(x + 1)/2) + >>> change_mul(x*y*DiracDelta(cos(x))*cos(x), x) + (None, None) + + See Also + ======== + + sympy.functions.special.delta_functions.DiracDelta + deltaintegrate + """ + + new_args = [] + dirac = None + + #Sorting is needed so that we consistently collapse the same delta; + #However, we must preserve the ordering of non-commutative terms + c, nc = node.args_cnc() + sorted_args = sorted(c, key=default_sort_key) + sorted_args.extend(nc) + + for arg in sorted_args: + if arg.is_Pow and isinstance(arg.base, DiracDelta): + new_args.append(arg.func(arg.base, arg.exp - 1)) + arg = arg.base + if dirac is None and (isinstance(arg, DiracDelta) and arg.is_simple(x)): + dirac = arg + else: + new_args.append(arg) + if not dirac: # there was no simple dirac + new_args = [] + for arg in sorted_args: + if isinstance(arg, DiracDelta): + new_args.append(arg.expand(diracdelta=True, wrt=x)) + elif arg.is_Pow and isinstance(arg.base, DiracDelta): + new_args.append(arg.func(arg.base.expand(diracdelta=True, wrt=x), arg.exp)) + else: + new_args.append(arg) + if new_args != sorted_args: + nnode = Mul(*new_args).expand() + else: # if the node didn't change there is nothing to do + nnode = None + return (None, nnode) + return (dirac, Mul(*new_args)) + + +def deltaintegrate(f, x): + """ + deltaintegrate(f, x) + + Explanation + =========== + + The idea for integration is the following: + + - If we are dealing with a DiracDelta expression, i.e. DiracDelta(g(x)), + we try to simplify it. + + If we could simplify it, then we integrate the resulting expression. + We already know we can integrate a simplified expression, because only + simple DiracDelta expressions are involved. + + If we couldn't simplify it, there are two cases: + + 1) The expression is a simple expression: we return the integral, + taking care if we are dealing with a Derivative or with a proper + DiracDelta. + + 2) The expression is not simple (i.e. DiracDelta(cos(x))): we can do + nothing at all. + + - If the node is a multiplication node having a DiracDelta term: + + First we expand it. + + If the expansion did work, then we try to integrate the expansion. + + If not, we try to extract a simple DiracDelta term, then we have two + cases: + + 1) We have a simple DiracDelta term, so we return the integral. + + 2) We didn't have a simple term, but we do have an expression with + simplified DiracDelta terms, so we integrate this expression. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.integrals.deltafunctions import deltaintegrate + >>> from sympy import sin, cos, DiracDelta + >>> deltaintegrate(x*sin(x)*cos(x)*DiracDelta(x - 1), x) + sin(1)*cos(1)*Heaviside(x - 1) + >>> deltaintegrate(y**2*DiracDelta(x - z)*DiracDelta(y - z), y) + z**2*DiracDelta(x - z)*Heaviside(y - z) + + See Also + ======== + + sympy.functions.special.delta_functions.DiracDelta + sympy.integrals.integrals.Integral + """ + if not f.has(DiracDelta): + return None + + # g(x) = DiracDelta(h(x)) + if f.func == DiracDelta: + h = f.expand(diracdelta=True, wrt=x) + if h == f: # can't simplify the expression + #FIXME: the second term tells whether is DeltaDirac or Derivative + #For integrating derivatives of DiracDelta we need the chain rule + if f.is_simple(x): + if (len(f.args) <= 1 or f.args[1] == 0): + return Heaviside(f.args[0]) + else: + return (DiracDelta(f.args[0], f.args[1] - 1) / + f.args[0].as_poly().LC()) + else: # let's try to integrate the simplified expression + fh = integrate(h, x) + return fh + elif f.is_Mul or f.is_Pow: # g(x) = a*b*c*f(DiracDelta(h(x)))*d*e + g = f.expand() + if f != g: # the expansion worked + fh = integrate(g, x) + if fh is not None and not isinstance(fh, Integral): + return fh + else: + # no expansion performed, try to extract a simple DiracDelta term + deltaterm, rest_mult = change_mul(f, x) + + if not deltaterm: + if rest_mult: + fh = integrate(rest_mult, x) + return fh + else: + from sympy.solvers import solve + deltaterm = deltaterm.expand(diracdelta=True, wrt=x) + if deltaterm.is_Mul: # Take out any extracted factors + deltaterm, rest_mult_2 = change_mul(deltaterm, x) + rest_mult = rest_mult*rest_mult_2 + point = solve(deltaterm.args[0], x)[0] + + # Return the largest hyperreal term left after + # repeated integration by parts. For example, + # + # integrate(y*DiracDelta(x, 1),x) == y*DiracDelta(x,0), not 0 + # + # This is so Integral(y*DiracDelta(x).diff(x),x).doit() + # will return y*DiracDelta(x) instead of 0 or DiracDelta(x), + # both of which are correct everywhere the value is defined + # but give wrong answers for nested integration. + n = (0 if len(deltaterm.args)==1 else deltaterm.args[1]) + m = 0 + while n >= 0: + r = S.NegativeOne**n*rest_mult.diff(x, n).subs(x, point) + if r.is_zero: + n -= 1 + m += 1 + else: + if m == 0: + return r*Heaviside(x - point) + else: + return r*DiracDelta(x,m-1) + # In some very weak sense, x=0 is still a singularity, + # but we hope will not be of any practical consequence. + return S.Zero + return None diff --git a/phivenv/Lib/site-packages/sympy/integrals/heurisch.py b/phivenv/Lib/site-packages/sympy/integrals/heurisch.py new file mode 100644 index 0000000000000000000000000000000000000000..a27e2700afd08db16c7a86020eabe5feeb6e1c85 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/heurisch.py @@ -0,0 +1,781 @@ +from __future__ import annotations + +from collections import defaultdict +from functools import reduce +from itertools import permutations + +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.mul import Mul +from sympy.core.symbol import Wild, Dummy, Symbol +from sympy.core.basic import sympify +from sympy.core.numbers import Rational, pi, I +from sympy.core.relational import Eq, Ne +from sympy.core.singleton import S +from sympy.core.sorting import ordered +from sympy.core.traversal import iterfreeargs + +from sympy.functions import exp, sin, cos, tan, cot, asin, atan +from sympy.functions import log, sinh, cosh, tanh, coth, asinh +from sympy.functions import sqrt, erf, erfi, li, Ei +from sympy.functions import besselj, bessely, besseli, besselk +from sympy.functions import hankel1, hankel2, jn, yn +from sympy.functions.elementary.complexes import Abs, re, im, sign, arg +from sympy.functions.elementary.exponential import LambertW +from sympy.functions.elementary.integers import floor, ceiling +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import Heaviside, DiracDelta + +from sympy.simplify.radsimp import collect + +from sympy.logic.boolalg import And, Or +from sympy.utilities.iterables import uniq + +from sympy.polys import quo, gcd, lcm, factor_list, cancel, PolynomialError +from sympy.polys.monomials import itermonomials +from sympy.polys.polyroots import root_factors + +from sympy.polys.rings import PolyRing +from sympy.polys.solvers import solve_lin_sys +from sympy.polys.constructor import construct_domain + +from sympy.integrals.integrals import integrate + + +def components(f, x): + """ + Returns a set of all functional components of the given expression + which includes symbols, function applications and compositions and + non-integer powers. Fractional powers are collected with + minimal, positive exponents. + + Examples + ======== + + >>> from sympy import cos, sin + >>> from sympy.abc import x + >>> from sympy.integrals.heurisch import components + + >>> components(sin(x)*cos(x)**2, x) + {x, sin(x), cos(x)} + + See Also + ======== + + heurisch + """ + result = set() + + if f.has_free(x): + if f.is_symbol and f.is_commutative: + result.add(f) + elif f.is_Function or f.is_Derivative: + for g in f.args: + result |= components(g, x) + + result.add(f) + elif f.is_Pow: + result |= components(f.base, x) + + if not f.exp.is_Integer: + if f.exp.is_Rational: + result.add(f.base**Rational(1, f.exp.q)) + else: + result |= components(f.exp, x) | {f} + else: + for g in f.args: + result |= components(g, x) + + return result + +# name -> [] of symbols +_symbols_cache: dict[str, list[Dummy]] = {} + + +# NB @cacheit is not convenient here +def _symbols(name, n): + """get vector of symbols local to this module""" + try: + lsyms = _symbols_cache[name] + except KeyError: + lsyms = [] + _symbols_cache[name] = lsyms + + while len(lsyms) < n: + lsyms.append( Dummy('%s%i' % (name, len(lsyms))) ) + + return lsyms[:n] + + +def heurisch_wrapper(f, x, rewrite=False, hints=None, mappings=None, retries=3, + degree_offset=0, unnecessary_permutations=None, + _try_heurisch=None): + """ + A wrapper around the heurisch integration algorithm. + + Explanation + =========== + + This method takes the result from heurisch and checks for poles in the + denominator. For each of these poles, the integral is reevaluated, and + the final integration result is given in terms of a Piecewise. + + Examples + ======== + + >>> from sympy import cos, symbols + >>> from sympy.integrals.heurisch import heurisch, heurisch_wrapper + >>> n, x = symbols('n x') + >>> heurisch(cos(n*x), x) + sin(n*x)/n + >>> heurisch_wrapper(cos(n*x), x) + Piecewise((sin(n*x)/n, Ne(n, 0)), (x, True)) + + See Also + ======== + + heurisch + """ + from sympy.solvers.solvers import solve, denoms + f = sympify(f) + if not f.has_free(x): + return f*x + + res = heurisch(f, x, rewrite, hints, mappings, retries, degree_offset, + unnecessary_permutations, _try_heurisch) + if not isinstance(res, Basic): + return res + + # We consider each denominator in the expression, and try to find + # cases where one or more symbolic denominator might be zero. The + # conditions for these cases are stored in the list slns. + # + # Since denoms returns a set we use ordered. This is important because the + # ordering of slns determines the order of the resulting Piecewise so we + # need a deterministic order here to make the output deterministic. + slns = [] + for d in ordered(denoms(res)): + try: + slns += solve([d], dict=True, exclude=(x,)) + except NotImplementedError: + pass + if not slns: + return res + slns = list(uniq(slns)) + # Remove the solutions corresponding to poles in the original expression. + slns0 = [] + for d in denoms(f): + try: + slns0 += solve([d], dict=True, exclude=(x,)) + except NotImplementedError: + pass + slns = [s for s in slns if s not in slns0] + if not slns: + return res + if len(slns) > 1: + eqs = [] + for sub_dict in slns: + eqs.extend([Eq(key, value) for key, value in sub_dict.items()]) + slns = solve(eqs, dict=True, exclude=(x,)) + slns + # For each case listed in the list slns, we reevaluate the integral. + pairs = [] + for sub_dict in slns: + expr = heurisch(f.subs(sub_dict), x, rewrite, hints, mappings, retries, + degree_offset, unnecessary_permutations, + _try_heurisch) + cond = And(*[Eq(key, value) for key, value in sub_dict.items()]) + generic = Or(*[Ne(key, value) for key, value in sub_dict.items()]) + if expr is None: + expr = integrate(f.subs(sub_dict),x) + pairs.append((expr, cond)) + # If there is one condition, put the generic case first. Otherwise, + # doing so may lead to longer Piecewise formulas + if len(pairs) == 1: + pairs = [(heurisch(f, x, rewrite, hints, mappings, retries, + degree_offset, unnecessary_permutations, + _try_heurisch), + generic), + (pairs[0][0], True)] + else: + pairs.append((heurisch(f, x, rewrite, hints, mappings, retries, + degree_offset, unnecessary_permutations, + _try_heurisch), + True)) + return Piecewise(*pairs) + +class BesselTable: + """ + Derivatives of Bessel functions of orders n and n-1 + in terms of each other. + + See the docstring of DiffCache. + """ + + def __init__(self): + self.table = {} + self.n = Dummy('n') + self.z = Dummy('z') + self._create_table() + + def _create_table(t): + table, n, z = t.table, t.n, t.z + for f in (besselj, bessely, hankel1, hankel2): + table[f] = (f(n-1, z) - n*f(n, z)/z, + (n-1)*f(n-1, z)/z - f(n, z)) + + f = besseli + table[f] = (f(n-1, z) - n*f(n, z)/z, + (n-1)*f(n-1, z)/z + f(n, z)) + f = besselk + table[f] = (-f(n-1, z) - n*f(n, z)/z, + (n-1)*f(n-1, z)/z - f(n, z)) + + for f in (jn, yn): + table[f] = (f(n-1, z) - (n+1)*f(n, z)/z, + (n-1)*f(n-1, z)/z - f(n, z)) + + def diffs(t, f, n, z): + if f in t.table: + diff0, diff1 = t.table[f] + repl = [(t.n, n), (t.z, z)] + return (diff0.subs(repl), diff1.subs(repl)) + + def has(t, f): + return f in t.table + +_bessel_table = None + +class DiffCache: + """ + Store for derivatives of expressions. + + Explanation + =========== + + The standard form of the derivative of a Bessel function of order n + contains two Bessel functions of orders n-1 and n+1, respectively. + Such forms cannot be used in parallel Risch algorithm, because + there is a linear recurrence relation between the three functions + while the algorithm expects that functions and derivatives are + represented in terms of algebraically independent transcendentals. + + The solution is to take two of the functions, e.g., those of orders + n and n-1, and to express the derivatives in terms of the pair. + To guarantee that the proper form is used the two derivatives are + cached as soon as one is encountered. + + Derivatives of other functions are also cached at no extra cost. + All derivatives are with respect to the same variable `x`. + """ + + def __init__(self, x): + self.cache = {} + self.x = x + + global _bessel_table + if not _bessel_table: + _bessel_table = BesselTable() + + def get_diff(self, f): + cache = self.cache + + if f in cache: + pass + elif (not hasattr(f, 'func') or + not _bessel_table.has(f.func)): + cache[f] = cancel(f.diff(self.x)) + else: + n, z = f.args + d0, d1 = _bessel_table.diffs(f.func, n, z) + dz = self.get_diff(z) + cache[f] = d0*dz + cache[f.func(n-1, z)] = d1*dz + + return cache[f] + +def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3, + degree_offset=0, unnecessary_permutations=None, + _try_heurisch=None): + """ + Compute indefinite integral using heuristic Risch algorithm. + + Explanation + =========== + + This is a heuristic approach to indefinite integration in finite + terms using the extended heuristic (parallel) Risch algorithm, based + on Manuel Bronstein's "Poor Man's Integrator". + + The algorithm supports various classes of functions including + transcendental elementary or special functions like Airy, + Bessel, Whittaker and Lambert. + + Note that this algorithm is not a decision procedure. If it isn't + able to compute the antiderivative for a given function, then this is + not a proof that such a functions does not exist. One should use + recursive Risch algorithm in such case. It's an open question if + this algorithm can be made a full decision procedure. + + This is an internal integrator procedure. You should use top level + 'integrate' function in most cases, as this procedure needs some + preprocessing steps and otherwise may fail. + + Specification + ============= + + heurisch(f, x, rewrite=False, hints=None) + + where + f : expression + x : symbol + + rewrite -> force rewrite 'f' in terms of 'tan' and 'tanh' + hints -> a list of functions that may appear in anti-derivate + + - hints = None --> no suggestions at all + - hints = [ ] --> try to figure out + - hints = [f1, ..., fn] --> we know better + + Examples + ======== + + >>> from sympy import tan + >>> from sympy.integrals.heurisch import heurisch + >>> from sympy.abc import x, y + + >>> heurisch(y*tan(x), x) + y*log(tan(x)**2 + 1)/2 + + See Manuel Bronstein's "Poor Man's Integrator": + + References + ========== + + .. [1] https://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/index.html + + For more information on the implemented algorithm refer to: + + .. [2] K. Geddes, L. Stefanus, On the Risch-Norman Integration + Method and its Implementation in Maple, Proceedings of + ISSAC'89, ACM Press, 212-217. + + .. [3] J. H. Davenport, On the Parallel Risch Algorithm (I), + Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. + + .. [4] J. H. Davenport, On the Parallel Risch Algorithm (III): + Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. + + .. [5] J. H. Davenport, B. M. Trager, On the Parallel Risch + Algorithm (II), ACM Transactions on Mathematical + Software 11 (1985), 356-362. + + See Also + ======== + + sympy.integrals.integrals.Integral.doit + sympy.integrals.integrals.Integral + sympy.integrals.heurisch.components + """ + f = sympify(f) + + # There are some functions that Heurisch cannot currently handle, + # so do not even try. + # Set _try_heurisch=True to skip this check + if _try_heurisch is not True: + if f.has(Abs, re, im, sign, Heaviside, DiracDelta, floor, ceiling, arg): + return + + if not f.has_free(x): + return f*x + + if not f.is_Add: + indep, f = f.as_independent(x) + else: + indep = S.One + + rewritables = { + (sin, cos, cot): tan, + (sinh, cosh, coth): tanh, + } + + if rewrite: + for candidates, rule in rewritables.items(): + f = f.rewrite(candidates, rule) + else: + for candidates in rewritables.keys(): + if f.has(*candidates): + break + else: + rewrite = True + + terms = components(f, x) + dcache = DiffCache(x) + + if hints is not None: + if not hints: + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + c = Wild('c', exclude=[x]) + + for g in set(terms): # using copy of terms + if g.is_Function: + if isinstance(g, li): + M = g.args[0].match(a*x**b) + + if M is not None: + terms.add( x*(li(M[a]*x**M[b]) - (M[a]*x**M[b])**(-1/M[b])*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) + #terms.add( x*(li(M[a]*x**M[b]) - (x**M[b])**(-1/M[b])*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) + #terms.add( x*(li(M[a]*x**M[b]) - x*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) + #terms.add( li(M[a]*x**M[b]) - Ei((M[b]+1)*log(M[a]*x**M[b])/M[b]) ) + + elif isinstance(g, exp): + M = g.args[0].match(a*x**2) + + if M is not None: + if M[a].is_positive: + terms.add(erfi(sqrt(M[a])*x)) + else: # M[a].is_negative or unknown + terms.add(erf(sqrt(-M[a])*x)) + + M = g.args[0].match(a*x**2 + b*x + c) + + if M is not None: + if M[a].is_positive: + terms.add(sqrt(pi/4*(-M[a]))*exp(M[c] - M[b]**2/(4*M[a]))* + erfi(sqrt(M[a])*x + M[b]/(2*sqrt(M[a])))) + elif M[a].is_negative: + terms.add(sqrt(pi/4*(-M[a]))*exp(M[c] - M[b]**2/(4*M[a]))* + erf(sqrt(-M[a])*x - M[b]/(2*sqrt(-M[a])))) + + M = g.args[0].match(a*log(x)**2) + + if M is not None: + if M[a].is_positive: + terms.add(erfi(sqrt(M[a])*log(x) + 1/(2*sqrt(M[a])))) + if M[a].is_negative: + terms.add(erf(sqrt(-M[a])*log(x) - 1/(2*sqrt(-M[a])))) + + elif g.is_Pow: + if g.exp.is_Rational and g.exp.q == 2: + M = g.base.match(a*x**2 + b) + + if M is not None and M[b].is_positive: + if M[a].is_positive: + terms.add(asinh(sqrt(M[a]/M[b])*x)) + elif M[a].is_negative: + terms.add(asin(sqrt(-M[a]/M[b])*x)) + + M = g.base.match(a*x**2 - b) + + if M is not None and M[b].is_positive: + if M[a].is_positive: + dF = 1/sqrt(M[a]*x**2 - M[b]) + F = log(2*sqrt(M[a])*sqrt(M[a]*x**2 - M[b]) + 2*M[a]*x)/sqrt(M[a]) + dcache.cache[F] = dF # hack: F.diff(x) doesn't automatically simplify to f + terms.add(F) + elif M[a].is_negative: + terms.add(-M[b]/2*sqrt(-M[a])* + atan(sqrt(-M[a])*x/sqrt(M[a]*x**2 - M[b]))) + + else: + terms |= set(hints) + + for g in set(terms): # using copy of terms + terms |= components(dcache.get_diff(g), x) + + # XXX: The commented line below makes heurisch more deterministic wrt + # PYTHONHASHSEED and the iteration order of sets. There are other places + # where sets are iterated over but this one is possibly the most important. + # Theoretically the order here should not matter but different orderings + # can expose potential bugs in the different code paths so potentially it + # is better to keep the non-determinism. + # + # terms = list(ordered(terms)) + + # TODO: caching is significant factor for why permutations work at all. Change this. + V = _symbols('x', len(terms)) + + + # sort mapping expressions from largest to smallest (last is always x). + mapping = list(reversed(list(zip(*ordered( # + [(a[0].as_independent(x)[1], a) for a in zip(terms, V)])))[1])) # + rev_mapping = {v: k for k, v in mapping} # + if mappings is None: # + # optimizing the number of permutations of mapping # + assert mapping[-1][0] == x # if not, find it and correct this comment + unnecessary_permutations = [mapping.pop(-1)] + # permute types of objects + types = defaultdict(list) + for i in mapping: + e, _ = i + types[type(e)].append(i) + mapping = [types[i] for i in types] + def _iter_mappings(): + for i in permutations(mapping): + # make the expression of a given type be ordered + yield [j for i in i for j in ordered(i)] + mappings = _iter_mappings() + else: + unnecessary_permutations = unnecessary_permutations or [] + + def _substitute(expr): + return expr.subs(mapping) + + for mapping in mappings: + mapping = list(mapping) + mapping = mapping + unnecessary_permutations + diffs = [ _substitute(dcache.get_diff(g)) for g in terms ] + denoms = [ g.as_numer_denom()[1] for g in diffs ] + if all(h.is_polynomial(*V) for h in denoms) and _substitute(f).is_rational_function(*V): + denom = reduce(lambda p, q: lcm(p, q, *V), denoms) + break + else: + if not rewrite: + result = heurisch(f, x, rewrite=True, hints=hints, + unnecessary_permutations=unnecessary_permutations) + + if result is not None: + return indep*result + return None + + numers = [ cancel(denom*g) for g in diffs ] + def _derivation(h): + return Add(*[ d * h.diff(v) for d, v in zip(numers, V) ]) + + def _deflation(p): + for y in V: + if not p.has(y): + continue + + if _derivation(p) is not S.Zero: + c, q = p.as_poly(y).primitive() + return _deflation(c)*gcd(q, q.diff(y)).as_expr() + + return p + + def _splitter(p): + for y in V: + if not p.has(y): + continue + + if _derivation(y) is not S.Zero: + c, q = p.as_poly(y).primitive() + + q = q.as_expr() + + h = gcd(q, _derivation(q), y) + s = quo(h, gcd(q, q.diff(y), y), y) + + c_split = _splitter(c) + + if s.as_poly(y).degree() == 0: + return (c_split[0], q * c_split[1]) + + q_split = _splitter(cancel(q / s)) + + return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) + + return (S.One, p) + + special = {} + + for term in terms: + if term.is_Function: + if isinstance(term, tan): + special[1 + _substitute(term)**2] = False + elif isinstance(term, tanh): + special[1 + _substitute(term)] = False + special[1 - _substitute(term)] = False + elif isinstance(term, LambertW): + special[_substitute(term)] = True + + F = _substitute(f) + + P, Q = F.as_numer_denom() + + u_split = _splitter(denom) + v_split = _splitter(Q) + + polys = set(list(v_split) + [ u_split[0] ] + list(special.keys())) + + s = u_split[0] * Mul(*[ k for k, v in special.items() if v ]) + polified = [ p.as_poly(*V) for p in [s, P, Q] ] + + if None in polified: + return None + + #--- definitions for _integrate + a, b, c = [ p.total_degree() for p in polified ] + + poly_denom = (s * v_split[0] * _deflation(v_split[1])).as_expr() + + def _exponent(g): + if g.is_Pow: + if g.exp.is_Rational and g.exp.q != 1: + if g.exp.p > 0: + return g.exp.p + g.exp.q - 1 + else: + return abs(g.exp.p + g.exp.q) + else: + return 1 + elif not g.is_Atom and g.args: + return max(_exponent(h) for h in g.args) + else: + return 1 + + A, B = _exponent(f), a + max(b, c) + + if A > 1 and B > 1: + monoms = tuple(ordered(itermonomials(V, A + B - 1 + degree_offset))) + else: + monoms = tuple(ordered(itermonomials(V, A + B + degree_offset))) + + poly_coeffs = _symbols('A', len(monoms)) + + poly_part = Add(*[ poly_coeffs[i]*monomial + for i, monomial in enumerate(monoms) ]) + + reducibles = set() + + for poly in ordered(polys): + coeff, factors = factor_list(poly, *V) + reducibles.add(coeff) + reducibles.update(fact for fact, mul in factors) + + def _integrate(field=None): + atans = set() + pairs = set() + + if field == 'Q': + irreducibles = set(reducibles) + else: + setV = set(V) + irreducibles = set() + for poly in ordered(reducibles): + zV = setV & set(iterfreeargs(poly)) + for z in ordered(zV): + s = set(root_factors(poly, z, filter=field)) + irreducibles |= s + break + + log_part, atan_part = [], [] + + for poly in ordered(irreducibles): + m = collect(poly, I, evaluate=False) + y = m.get(I, S.Zero) + if y: + x = m.get(S.One, S.Zero) + if x.has(I) or y.has(I): + continue # nontrivial x + I*y + pairs.add((x, y)) + irreducibles.remove(poly) + + while pairs: + x, y = pairs.pop() + if (x, -y) in pairs: + pairs.remove((x, -y)) + # Choosing b with no minus sign + if y.could_extract_minus_sign(): + y = -y + irreducibles.add(x*x + y*y) + atans.add(atan(x/y)) + else: + irreducibles.add(x + I*y) + + + B = _symbols('B', len(irreducibles)) + C = _symbols('C', len(atans)) + + # Note: the ordering matters here + for poly, b in reversed(list(zip(ordered(irreducibles), B))): + if poly.has(*V): + poly_coeffs.append(b) + log_part.append(b * log(poly)) + + for poly, c in reversed(list(zip(ordered(atans), C))): + if poly.has(*V): + poly_coeffs.append(c) + atan_part.append(c * poly) + + # TODO: Currently it's better to use symbolic expressions here instead + # of rational functions, because it's simpler and FracElement doesn't + # give big speed improvement yet. This is because cancellation is slow + # due to slow polynomial GCD algorithms. If this gets improved then + # revise this code. + candidate = poly_part/poly_denom + Add(*log_part) + Add(*atan_part) + h = F - _derivation(candidate) / denom + raw_numer = h.as_numer_denom()[0] + + # Rewrite raw_numer as a polynomial in K[coeffs][V] where K is a field + # that we have to determine. We can't use simply atoms() because log(3), + # sqrt(y) and similar expressions can appear, leading to non-trivial + # domains. + syms = set(poly_coeffs) | set(V) + non_syms = set() + + def find_non_syms(expr): + if expr.is_Integer or expr.is_Rational: + pass # ignore trivial numbers + elif expr in syms: + pass # ignore variables + elif not expr.has_free(*syms): + non_syms.add(expr) + elif expr.is_Add or expr.is_Mul or expr.is_Pow: + list(map(find_non_syms, expr.args)) + else: + # TODO: Non-polynomial expression. This should have been + # filtered out at an earlier stage. + raise PolynomialError + + try: + find_non_syms(raw_numer) + except PolynomialError: + return None + else: + ground, _ = construct_domain(non_syms, field=True) + + coeff_ring = PolyRing(poly_coeffs, ground) + ring = PolyRing(V, coeff_ring) + try: + numer = ring.from_expr(raw_numer) + except ValueError: + raise PolynomialError + solution = solve_lin_sys(numer.coeffs(), coeff_ring, _raw=False) + + if solution is None: + return None + else: + return candidate.xreplace(solution).xreplace( + dict(zip(poly_coeffs, [S.Zero]*len(poly_coeffs)))) + + if all(isinstance(_, Symbol) for _ in V): + more_free = F.free_symbols - set(V) + else: + Fd = F.as_dummy() + more_free = Fd.xreplace(dict(zip(V, (Dummy() for _ in V))) + ).free_symbols & Fd.free_symbols + if not more_free: + # all free generators are identified in V + solution = _integrate('Q') + + if solution is None: + solution = _integrate() + else: + solution = _integrate() + + if solution is not None: + antideriv = solution.subs(rev_mapping) + antideriv = cancel(antideriv).expand() + + if antideriv.is_Add: + antideriv = antideriv.as_independent(x)[1] + + return indep*antideriv + else: + if retries >= 0: + result = heurisch(f, x, mappings=mappings, rewrite=rewrite, hints=hints, retries=retries - 1, unnecessary_permutations=unnecessary_permutations) + + if result is not None: + return indep*result + + return None diff --git a/phivenv/Lib/site-packages/sympy/integrals/integrals.py b/phivenv/Lib/site-packages/sympy/integrals/integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..b9ed4a22802acc455f5162e109fc575223c97338 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/integrals.py @@ -0,0 +1,1640 @@ +from __future__ import annotations + +from sympy.concrete.expr_with_limits import AddWithLimits +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.exprtools import factor_terms +from sympy.core.function import diff +from sympy.core.logic import fuzzy_bool +from sympy.core.mul import Mul +from sympy.core.numbers import oo, pi +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, Wild) +from sympy.core.sympify import sympify +from sympy.functions import Piecewise, sqrt, piecewise_fold, tan, cot, atan +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.complexes import Abs, sign +from sympy.functions.elementary.miscellaneous import Min, Max +from sympy.functions.special.singularity_functions import Heaviside +from .rationaltools import ratint +from sympy.matrices import MatrixBase +from sympy.polys import Poly, PolynomialError +from sympy.series.formal import FormalPowerSeries +from sympy.series.limits import limit +from sympy.series.order import Order +from sympy.tensor.functions import shape +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence +from sympy.utilities.misc import filldedent + + +class Integral(AddWithLimits): + """Represents unevaluated integral.""" + + __slots__ = () + + args: tuple[Expr, Tuple] # type: ignore + + def __new__(cls, function, *symbols, **assumptions) -> Integral: + """Create an unevaluated integral. + + Explanation + =========== + + Arguments are an integrand followed by one or more limits. + + If no limits are given and there is only one free symbol in the + expression, that symbol will be used, otherwise an error will be + raised. + + >>> from sympy import Integral + >>> from sympy.abc import x, y + >>> Integral(x) + Integral(x, x) + >>> Integral(y) + Integral(y, y) + + When limits are provided, they are interpreted as follows (using + ``x`` as though it were the variable of integration): + + (x,) or x - indefinite integral + (x, a) - "evaluate at" integral is an abstract antiderivative + (x, a, b) - definite integral + + The ``as_dummy`` method can be used to see which symbols cannot be + targeted by subs: those with a prepended underscore cannot be + changed with ``subs``. (Also, the integration variables themselves -- + the first element of a limit -- can never be changed by subs.) + + >>> i = Integral(x, x) + >>> at = Integral(x, (x, x)) + >>> i.as_dummy() + Integral(x, x) + >>> at.as_dummy() + Integral(_0, (_0, x)) + + """ + + #This will help other classes define their own definitions + #of behaviour with Integral. + if hasattr(function, '_eval_Integral'): + return function._eval_Integral(*symbols, **assumptions) + + if isinstance(function, Poly): + sympy_deprecation_warning( + """ + integrate(Poly) and Integral(Poly) are deprecated. Instead, + use the Poly.integrate() method, or convert the Poly to an + Expr first with the Poly.as_expr() method. + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-integrate-poly") + + obj = AddWithLimits.__new__(cls, function, *symbols, **assumptions) + return obj + + def __getnewargs__(self): + return (self.function,) + tuple([tuple(xab) for xab in self.limits]) + + @property + def free_symbols(self): + """ + This method returns the symbols that will exist when the + integral is evaluated. This is useful if one is trying to + determine whether an integral depends on a certain + symbol or not. + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x, y + >>> Integral(x, (x, y, 1)).free_symbols + {y} + + See Also + ======== + + sympy.concrete.expr_with_limits.ExprWithLimits.function + sympy.concrete.expr_with_limits.ExprWithLimits.limits + sympy.concrete.expr_with_limits.ExprWithLimits.variables + """ + return super().free_symbols + + def _eval_is_zero(self): + # This is a very naive and quick test, not intended to do the integral to + # answer whether it is zero or not, e.g. Integral(sin(x), (x, 0, 2*pi)) + # is zero but this routine should return None for that case. But, like + # Mul, there are trivial situations for which the integral will be + # zero so we check for those. + if self.function.is_zero: + return True + got_none = False + for l in self.limits: + if len(l) == 3: + z = (l[1] == l[2]) or (l[1] - l[2]).is_zero + if z: + return True + elif z is None: + got_none = True + free = self.function.free_symbols + for xab in self.limits: + if len(xab) == 1: + free.add(xab[0]) + continue + if len(xab) == 2 and xab[0] not in free: + if xab[1].is_zero: + return True + elif xab[1].is_zero is None: + got_none = True + # take integration symbol out of free since it will be replaced + # with the free symbols in the limits + free.discard(xab[0]) + # add in the new symbols + for i in xab[1:]: + free.update(i.free_symbols) + if self.function.is_zero is False and got_none is False: + return False + + def transform(self, x, u): + r""" + Performs a change of variables from `x` to `u` using the relationship + given by `x` and `u` which will define the transformations `f` and `F` + (which are inverses of each other) as follows: + + 1) If `x` is a Symbol (which is a variable of integration) then `u` + will be interpreted as some function, f(u), with inverse F(u). + This, in effect, just makes the substitution of x with f(x). + + 2) If `u` is a Symbol then `x` will be interpreted as some function, + F(x), with inverse f(u). This is commonly referred to as + u-substitution. + + Once f and F have been identified, the transformation is made as + follows: + + .. math:: \int_a^b x \mathrm{d}x \rightarrow \int_{F(a)}^{F(b)} f(x) + \frac{\mathrm{d}}{\mathrm{d}x} + + where `F(x)` is the inverse of `f(x)` and the limits and integrand have + been corrected so as to retain the same value after integration. + + Notes + ===== + + The mappings, F(x) or f(u), must lead to a unique integral. Linear + or rational linear expression, ``2*x``, ``1/x`` and ``sqrt(x)``, will + always work; quadratic expressions like ``x**2 - 1`` are acceptable + as long as the resulting integrand does not depend on the sign of + the solutions (see examples). + + The integral will be returned unchanged if ``x`` is not a variable of + integration. + + ``x`` must be (or contain) only one of of the integration variables. If + ``u`` has more than one free symbol then it should be sent as a tuple + (``u``, ``uvar``) where ``uvar`` identifies which variable is replacing + the integration variable. + XXX can it contain another integration variable? + + Examples + ======== + + >>> from sympy.abc import a, x, u + >>> from sympy import Integral, cos, sqrt + + >>> i = Integral(x*cos(x**2 - 1), (x, 0, 1)) + + transform can change the variable of integration + + >>> i.transform(x, u) + Integral(u*cos(u**2 - 1), (u, 0, 1)) + + transform can perform u-substitution as long as a unique + integrand is obtained: + + >>> ui = i.transform(x**2 - 1, u) + >>> ui + Integral(cos(u)/2, (u, -1, 0)) + + This attempt fails because x = +/-sqrt(u + 1) and the + sign does not cancel out of the integrand: + + >>> Integral(cos(x**2 - 1), (x, 0, 1)).transform(x**2 - 1, u) + Traceback (most recent call last): + ... + ValueError: + The mapping between F(x) and f(u) did not give a unique integrand. + + transform can do a substitution. Here, the previous + result is transformed back into the original expression + using "u-substitution": + + >>> ui.transform(sqrt(u + 1), x) == i + True + + We can accomplish the same with a regular substitution: + + >>> ui.transform(u, x**2 - 1) == i + True + + If the `x` does not contain a symbol of integration then + the integral will be returned unchanged. Integral `i` does + not have an integration variable `a` so no change is made: + + >>> i.transform(a, x) == i + True + + When `u` has more than one free symbol the symbol that is + replacing `x` must be identified by passing `u` as a tuple: + + >>> Integral(x, (x, 0, 1)).transform(x, (u + a, u)) + Integral(a + u, (u, -a, 1 - a)) + >>> Integral(x, (x, 0, 1)).transform(x, (u + a, a)) + Integral(a + u, (a, -u, 1 - u)) + + See Also + ======== + + sympy.concrete.expr_with_limits.ExprWithLimits.variables : Lists the integration variables + as_dummy : Replace integration variables with dummy ones + """ + d = Dummy('d') + + xfree = x.free_symbols.intersection(self.variables) + if len(xfree) > 1: + raise ValueError( + 'F(x) can only contain one of: %s' % self.variables) + xvar = xfree.pop() if xfree else d + + if xvar not in self.variables: + return self + + u = sympify(u) + if isinstance(u, Expr): + ufree = u.free_symbols + if len(ufree) == 0: + raise ValueError(filldedent(''' + f(u) cannot be a constant''')) + if len(ufree) > 1: + raise ValueError(filldedent(''' + When f(u) has more than one free symbol, the one replacing x + must be identified: pass f(u) as (f(u), u)''')) + uvar = ufree.pop() + else: + u, uvar = u + if uvar not in u.free_symbols: + raise ValueError(filldedent(''' + Expecting a tuple (expr, symbol) where symbol identified + a free symbol in expr, but symbol is not in expr's free + symbols.''')) + if not isinstance(uvar, Symbol): + # This probably never evaluates to True + raise ValueError(filldedent(''' + Expecting a tuple (expr, symbol) but didn't get + a symbol; got %s''' % uvar)) + + if x.is_Symbol and u.is_Symbol: + return self.xreplace({x: u}) + + if not x.is_Symbol and not u.is_Symbol: + raise ValueError('either x or u must be a symbol') + + if uvar == xvar: + return self.transform(x, (u.subs(uvar, d), d)).xreplace({d: uvar}) + + if uvar in self.limits: + raise ValueError(filldedent(''' + u must contain the same variable as in x + or a variable that is not already an integration variable''')) + + from sympy.solvers.solvers import solve + if not x.is_Symbol: + F = [x.subs(xvar, d)] + soln = solve(u - x, xvar, check=False) + if not soln: + raise ValueError('no solution for solve(F(x) - f(u), x)') + f = [fi.subs(uvar, d) for fi in soln] + else: + f = [u.subs(uvar, d)] + from sympy.simplify.simplify import posify + pdiff, reps = posify(u - x) + puvar = uvar.subs([(v, k) for k, v in reps.items()]) + soln = [s.subs(reps) for s in solve(pdiff, puvar)] + if not soln: + raise ValueError('no solution for solve(F(x) - f(u), u)') + F = [fi.subs(xvar, d) for fi in soln] + + newfuncs = {(self.function.subs(xvar, fi)*fi.diff(d) + ).subs(d, uvar) for fi in f} + if len(newfuncs) > 1: + raise ValueError(filldedent(''' + The mapping between F(x) and f(u) did not give + a unique integrand.''')) + newfunc = newfuncs.pop() + + def _calc_limit_1(F, a, b): + """ + replace d with a, using subs if possible, otherwise limit + where sign of b is considered + """ + wok = F.subs(d, a) + if wok is S.NaN or wok.is_finite is False and a.is_finite: + return limit(sign(b)*F, d, a) + return wok + + def _calc_limit(a, b): + """ + replace d with a, using subs if possible, otherwise limit + where sign of b is considered + """ + avals = list({_calc_limit_1(Fi, a, b) for Fi in F}) + if len(avals) > 1: + raise ValueError(filldedent(''' + The mapping between F(x) and f(u) did not + give a unique limit.''')) + return avals[0] + + newlimits = [] + for xab in self.limits: + sym = xab[0] + if sym == xvar: + if len(xab) == 3: + a, b = xab[1:] + a, b = _calc_limit(a, b), _calc_limit(b, a) + if fuzzy_bool(a - b > 0): + a, b = b, a + newfunc = -newfunc + newlimits.append((uvar, a, b)) + elif len(xab) == 2: + a = _calc_limit(xab[1], 1) + newlimits.append((uvar, a)) + else: + newlimits.append(uvar) + else: + newlimits.append(xab) + + return self.func(newfunc, *newlimits) + + def doit(self, **hints): + """ + Perform the integration using any hints given. + + Examples + ======== + + >>> from sympy import Piecewise, S + >>> from sympy.abc import x, t + >>> p = x**2 + Piecewise((0, x/t < 0), (1, True)) + >>> p.integrate((t, S(4)/5, 1), (x, -1, 1)) + 1/3 + + See Also + ======== + + sympy.integrals.trigonometry.trigintegrate + sympy.integrals.heurisch.heurisch + sympy.integrals.rationaltools.ratint + as_sum : Approximate the integral using a sum + """ + if not hints.get('integrals', True): + return self + + deep = hints.get('deep', True) + meijerg = hints.get('meijerg', None) + conds = hints.get('conds', 'piecewise') + risch = hints.get('risch', None) + heurisch = hints.get('heurisch', None) + manual = hints.get('manual', None) + if len(list(filter(None, (manual, meijerg, risch, heurisch)))) > 1: + raise ValueError("At most one of manual, meijerg, risch, heurisch can be True") + elif manual: + meijerg = risch = heurisch = False + elif meijerg: + manual = risch = heurisch = False + elif risch: + manual = meijerg = heurisch = False + elif heurisch: + manual = meijerg = risch = False + eval_kwargs = {"meijerg": meijerg, "risch": risch, "manual": manual, "heurisch": heurisch, + "conds": conds} + + if conds not in ('separate', 'piecewise', 'none'): + raise ValueError('conds must be one of "separate", "piecewise", ' + '"none", got: %s' % conds) + + if risch and any(len(xab) > 1 for xab in self.limits): + raise ValueError('risch=True is only allowed for indefinite integrals.') + + # check for the trivial zero + if self.is_zero: + return S.Zero + + # hacks to handle integrals of + # nested summations + from sympy.concrete.summations import Sum + if isinstance(self.function, Sum): + if any(v in self.function.limits[0] for v in self.variables): + raise ValueError('Limit of the sum cannot be an integration variable.') + if any(l.is_infinite for l in self.function.limits[0][1:]): + return self + _i = self + _sum = self.function + return _sum.func(_i.func(_sum.function, *_i.limits).doit(), *_sum.limits).doit() + + # now compute and check the function + function = self.function + + # hack to use a consistent Heaviside(x, 1/2) + function = function.replace( + lambda x: isinstance(x, Heaviside) and x.args[1]*2 != 1, + lambda x: Heaviside(x.args[0])) + + if deep: + function = function.doit(**hints) + if function.is_zero: + return S.Zero + + # hacks to handle special cases + if isinstance(function, MatrixBase): + return function.applyfunc( + lambda f: self.func(f, *self.limits).doit(**hints)) + + if isinstance(function, FormalPowerSeries): + if len(self.limits) > 1: + raise NotImplementedError + xab = self.limits[0] + if len(xab) > 1: + return function.integrate(xab, **eval_kwargs) + else: + return function.integrate(xab[0], **eval_kwargs) + + # There is no trivial answer and special handling + # is done so continue + + # first make sure any definite limits have integration + # variables with matching assumptions + reps = {} + for xab in self.limits: + if len(xab) != 3: + # it makes sense to just make + # all x real but in practice with the + # current state of integration...this + # doesn't work out well + # x = xab[0] + # if x not in reps and not x.is_real: + # reps[x] = Dummy(real=True) + continue + x, a, b = xab + l = (a, b) + if all(i.is_nonnegative for i in l) and not x.is_nonnegative: + d = Dummy(positive=True) + elif all(i.is_nonpositive for i in l) and not x.is_nonpositive: + d = Dummy(negative=True) + elif all(i.is_real for i in l) and not x.is_real: + d = Dummy(real=True) + else: + d = None + if d: + reps[x] = d + if reps: + undo = {v: k for k, v in reps.items()} + did = self.xreplace(reps).doit(**hints) + if isinstance(did, tuple): # when separate=True + did = tuple([i.xreplace(undo) for i in did]) + else: + did = did.xreplace(undo) + return did + + # continue with existing assumptions + undone_limits = [] + # ulj = free symbols of any undone limits' upper and lower limits + ulj = set() + for xab in self.limits: + # compute uli, the free symbols in the + # Upper and Lower limits of limit I + if len(xab) == 1: + uli = set(xab[:1]) + elif len(xab) == 2: + uli = xab[1].free_symbols + elif len(xab) == 3: + uli = xab[1].free_symbols.union(xab[2].free_symbols) + # this integral can be done as long as there is no blocking + # limit that has been undone. An undone limit is blocking if + # it contains an integration variable that is in this limit's + # upper or lower free symbols or vice versa + if xab[0] in ulj or any(v[0] in uli for v in undone_limits): + undone_limits.append(xab) + ulj.update(uli) + function = self.func(*([function] + [xab])) + factored_function = function.factor() + if not isinstance(factored_function, Integral): + function = factored_function + continue + + if function.has(Abs, sign) and ( + (len(xab) < 3 and all(x.is_extended_real for x in xab)) or + (len(xab) == 3 and all(x.is_extended_real and not x.is_infinite for + x in xab[1:]))): + # some improper integrals are better off with Abs + xr = Dummy("xr", real=True) + function = (function.xreplace({xab[0]: xr}) + .rewrite(Piecewise).xreplace({xr: xab[0]})) + elif function.has(Min, Max): + function = function.rewrite(Piecewise) + if (function.has(Piecewise) and + not isinstance(function, Piecewise)): + function = piecewise_fold(function) + if isinstance(function, Piecewise): + if len(xab) == 1: + antideriv = function._eval_integral(xab[0], + **eval_kwargs) + else: + antideriv = self._eval_integral( + function, xab[0], **eval_kwargs) + else: + # There are a number of tradeoffs in using the + # Meijer G method. It can sometimes be a lot faster + # than other methods, and sometimes slower. And + # there are certain types of integrals for which it + # is more likely to work than others. These + # heuristics are incorporated in deciding what + # integration methods to try, in what order. See the + # integrate() docstring for details. + def try_meijerg(function, xab): + ret = None + if len(xab) == 3 and meijerg is not False: + x, a, b = xab + try: + res = meijerint_definite(function, x, a, b) + except NotImplementedError: + _debug('NotImplementedError ' + 'from meijerint_definite') + res = None + if res is not None: + f, cond = res + if conds == 'piecewise': + u = self.func(function, (x, a, b)) + # if Piecewise modifies cond too + # much it may not be recognized by + # _condsimp pattern matching so just + # turn off all evaluation + return Piecewise((f, cond), (u, True), + evaluate=False) + elif conds == 'separate': + if len(self.limits) != 1: + raise ValueError(filldedent(''' + conds=separate not supported in + multiple integrals''')) + ret = f, cond + else: + ret = f + return ret + + meijerg1 = meijerg + if (meijerg is not False and + len(xab) == 3 and xab[1].is_extended_real and xab[2].is_extended_real + and not function.is_Poly and + (xab[1].has(oo, -oo) or xab[2].has(oo, -oo))): + ret = try_meijerg(function, xab) + if ret is not None: + function = ret + continue + meijerg1 = False + # If the special meijerg code did not succeed in + # finding a definite integral, then the code using + # meijerint_indefinite will not either (it might + # find an antiderivative, but the answer is likely + # to be nonsensical). Thus if we are requested to + # only use Meijer G-function methods, we give up at + # this stage. Otherwise we just disable G-function + # methods. + if meijerg1 is False and meijerg is True: + antideriv = None + else: + antideriv = self._eval_integral( + function, xab[0], **eval_kwargs) + if antideriv is None and meijerg is True: + ret = try_meijerg(function, xab) + if ret is not None: + function = ret + continue + + final = hints.get('final', True) + # dotit may be iterated but floor terms making atan and acot + # continuous should only be added in the final round + if (final and not isinstance(antideriv, Integral) and + antideriv is not None): + for atan_term in antideriv.atoms(atan): + atan_arg = atan_term.args[0] + # Checking `atan_arg` to be linear combination of `tan` or `cot` + for tan_part in atan_arg.atoms(tan): + x1 = Dummy('x1') + tan_exp1 = atan_arg.subs(tan_part, x1) + # The coefficient of `tan` should be constant + coeff = tan_exp1.diff(x1) + if x1 not in coeff.free_symbols: + a = tan_part.args[0] + antideriv = antideriv.subs(atan_term, Add(atan_term, + sign(coeff)*pi*floor((a-pi/2)/pi))) + for cot_part in atan_arg.atoms(cot): + x1 = Dummy('x1') + cot_exp1 = atan_arg.subs(cot_part, x1) + # The coefficient of `cot` should be constant + coeff = cot_exp1.diff(x1) + if x1 not in coeff.free_symbols: + a = cot_part.args[0] + antideriv = antideriv.subs(atan_term, Add(atan_term, + sign(coeff)*pi*floor((a)/pi))) + + if antideriv is None: + undone_limits.append(xab) + function = self.func(*([function] + [xab])).factor() + factored_function = function.factor() + if not isinstance(factored_function, Integral): + function = factored_function + continue + else: + if len(xab) == 1: + function = antideriv + else: + if len(xab) == 3: + x, a, b = xab + elif len(xab) == 2: + x, b = xab + a = None + else: + raise NotImplementedError + + if deep: + if isinstance(a, Basic): + a = a.doit(**hints) + if isinstance(b, Basic): + b = b.doit(**hints) + + if antideriv.is_Poly: + gens = list(antideriv.gens) + gens.remove(x) + + antideriv = antideriv.as_expr() + + function = antideriv._eval_interval(x, a, b) + function = Poly(function, *gens) + else: + def is_indef_int(g, x): + return (isinstance(g, Integral) and + any(i == (x,) for i in g.limits)) + + def eval_factored(f, x, a, b): + # _eval_interval for integrals with + # (constant) factors + # a single indefinite integral is assumed + args = [] + for g in Mul.make_args(f): + if is_indef_int(g, x): + args.append(g._eval_interval(x, a, b)) + else: + args.append(g) + return Mul(*args) + + integrals, others, piecewises = [], [], [] + for f in Add.make_args(antideriv): + if any(is_indef_int(g, x) + for g in Mul.make_args(f)): + integrals.append(f) + elif any(isinstance(g, Piecewise) + for g in Mul.make_args(f)): + piecewises.append(piecewise_fold(f)) + else: + others.append(f) + uneval = Add(*[eval_factored(f, x, a, b) + for f in integrals]) + try: + evalued = Add(*others)._eval_interval(x, a, b) + evalued_pw = piecewise_fold(Add(*piecewises))._eval_interval(x, a, b) + function = uneval + evalued + evalued_pw + except NotImplementedError: + # This can happen if _eval_interval depends in a + # complicated way on limits that cannot be computed + undone_limits.append(xab) + function = self.func(*([function] + [xab])) + factored_function = function.factor() + if not isinstance(factored_function, Integral): + function = factored_function + return function + + def _eval_derivative(self, sym): + """Evaluate the derivative of the current Integral object by + differentiating under the integral sign [1], using the Fundamental + Theorem of Calculus [2] when possible. + + Explanation + =========== + + Whenever an Integral is encountered that is equivalent to zero or + has an integrand that is independent of the variable of integration + those integrals are performed. All others are returned as Integral + instances which can be resolved with doit() (provided they are integrable). + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Differentiation_under_the_integral_sign + .. [2] https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x, y + >>> i = Integral(x + y, y, (y, 1, x)) + >>> i.diff(x) + Integral(x + y, (y, x)) + Integral(1, y, (y, 1, x)) + >>> i.doit().diff(x) == i.diff(x).doit() + True + >>> i.diff(y) + 0 + + The previous must be true since there is no y in the evaluated integral: + + >>> i.free_symbols + {x} + >>> i.doit() + 2*x**3/3 - x/2 - 1/6 + + """ + + # differentiate under the integral sign; we do not + # check for regularity conditions (TODO), see issue 4215 + + # get limits and the function + f, limits = self.function, list(self.limits) + + # the order matters if variables of integration appear in the limits + # so work our way in from the outside to the inside. + limit = limits.pop(-1) + if len(limit) == 3: + x, a, b = limit + elif len(limit) == 2: + x, b = limit + a = None + else: + a = b = None + x = limit[0] + + if limits: # f is the argument to an integral + f = self.func(f, *tuple(limits)) + + # assemble the pieces + def _do(f, ab): + dab_dsym = diff(ab, sym) + if not dab_dsym: + return S.Zero + if isinstance(f, Integral): + limits = [(x, x) if (len(l) == 1 and l[0] == x) else l + for l in f.limits] + f = self.func(f.function, *limits) + return f.subs(x, ab)*dab_dsym + + rv = S.Zero + if b is not None: + rv += _do(f, b) + if a is not None: + rv -= _do(f, a) + if len(limit) == 1 and sym == x: + # the dummy variable *is* also the real-world variable + arg = f + rv += arg + else: + # the dummy variable might match sym but it's + # only a dummy and the actual variable is determined + # by the limits, so mask off the variable of integration + # while differentiating + u = Dummy('u') + arg = f.subs(x, u).diff(sym).subs(u, x) + if arg: + rv += self.func(arg, (x, a, b)) + return rv + + def _eval_integral(self, f, x, meijerg=None, risch=None, manual=None, + heurisch=None, conds='piecewise',final=None): + """ + Calculate the anti-derivative to the function f(x). + + Explanation + =========== + + The following algorithms are applied (roughly in this order): + + 1. Simple heuristics (based on pattern matching and integral table): + + - most frequently used functions (e.g. polynomials, products of + trig functions) + + 2. Integration of rational functions: + + - A complete algorithm for integrating rational functions is + implemented (the Lazard-Rioboo-Trager algorithm). The algorithm + also uses the partial fraction decomposition algorithm + implemented in apart() as a preprocessor to make this process + faster. Note that the integral of a rational function is always + elementary, but in general, it may include a RootSum. + + 3. Full Risch algorithm: + + - The Risch algorithm is a complete decision + procedure for integrating elementary functions, which means that + given any elementary function, it will either compute an + elementary antiderivative, or else prove that none exists. + Currently, part of transcendental case is implemented, meaning + elementary integrals containing exponentials, logarithms, and + (soon!) trigonometric functions can be computed. The algebraic + case, e.g., functions containing roots, is much more difficult + and is not implemented yet. + + - If the routine fails (because the integrand is not elementary, or + because a case is not implemented yet), it continues on to the + next algorithms below. If the routine proves that the integrals + is nonelementary, it still moves on to the algorithms below, + because we might be able to find a closed-form solution in terms + of special functions. If risch=True, however, it will stop here. + + 4. The Meijer G-Function algorithm: + + - This algorithm works by first rewriting the integrand in terms of + very general Meijer G-Function (meijerg in SymPy), integrating + it, and then rewriting the result back, if possible. This + algorithm is particularly powerful for definite integrals (which + is actually part of a different method of Integral), since it can + compute closed-form solutions of definite integrals even when no + closed-form indefinite integral exists. But it also is capable + of computing many indefinite integrals as well. + + - Another advantage of this method is that it can use some results + about the Meijer G-Function to give a result in terms of a + Piecewise expression, which allows to express conditionally + convergent integrals. + + - Setting meijerg=True will cause integrate() to use only this + method. + + 5. The "manual integration" algorithm: + + - This algorithm tries to mimic how a person would find an + antiderivative by hand, for example by looking for a + substitution or applying integration by parts. This algorithm + does not handle as many integrands but can return results in a + more familiar form. + + - Sometimes this algorithm can evaluate parts of an integral; in + this case integrate() will try to evaluate the rest of the + integrand using the other methods here. + + - Setting manual=True will cause integrate() to use only this + method. + + 6. The Heuristic Risch algorithm: + + - This is a heuristic version of the Risch algorithm, meaning that + it is not deterministic. This is tried as a last resort because + it can be very slow. It is still used because not enough of the + full Risch algorithm is implemented, so that there are still some + integrals that can only be computed using this method. The goal + is to implement enough of the Risch and Meijer G-function methods + so that this can be deleted. + + Setting heurisch=True will cause integrate() to use only this + method. Set heurisch=False to not use it. + + """ + + from sympy.integrals.risch import risch_integrate, NonElementaryIntegral + from sympy.integrals.manualintegrate import manualintegrate + + if risch: + try: + return risch_integrate(f, x, conds=conds) + except NotImplementedError: + return None + + if manual: + try: + result = manualintegrate(f, x) + if result is not None and result.func != Integral: + return result + except (ValueError, PolynomialError): + pass + + eval_kwargs = {"meijerg": meijerg, "risch": risch, "manual": manual, + "heurisch": heurisch, "conds": conds} + + # if it is a poly(x) then let the polynomial integrate itself (fast) + # + # It is important to make this check first, otherwise the other code + # will return a SymPy expression instead of a Polynomial. + # + # see Polynomial for details. + if isinstance(f, Poly) and not (manual or meijerg or risch): + # Note: this is deprecated, but the deprecation warning is already + # issued in the Integral constructor. + return f.integrate(x) + + # Piecewise antiderivatives need to call special integrate. + if isinstance(f, Piecewise): + return f.piecewise_integrate(x, **eval_kwargs) + + # let's cut it short if `f` does not depend on `x`; if + # x is only a dummy, that will be handled below + if not f.has(x): + return f*x + + # try to convert to poly(x) and then integrate if successful (fast) + poly = f.as_poly(x) + if poly is not None and not (manual or meijerg or risch): + return poly.integrate().as_expr() + + if risch is not False: + try: + result, i = risch_integrate(f, x, separate_integral=True, + conds=conds) + except NotImplementedError: + pass + else: + if i: + # There was a nonelementary integral. Try integrating it. + + # if no part of the NonElementaryIntegral is integrated by + # the Risch algorithm, then use the original function to + # integrate, instead of re-written one + if result == 0: + return NonElementaryIntegral(f, x).doit(risch=False) + else: + return result + i.doit(risch=False) + else: + return result + + # since Integral(f=g1+g2+...) == Integral(g1) + Integral(g2) + ... + # we are going to handle Add terms separately, + # if `f` is not Add -- we only have one term + + # Note that in general, this is a bad idea, because Integral(g1) + + # Integral(g2) might not be computable, even if Integral(g1 + g2) is. + # For example, Integral(x**x + x**x*log(x)). But many heuristics only + # work term-wise. So we compute this step last, after trying + # risch_integrate. We also try risch_integrate again in this loop, + # because maybe the integral is a sum of an elementary part and a + # nonelementary part (like erf(x) + exp(x)). risch_integrate() is + # quite fast, so this is acceptable. + from sympy.simplify.fu import sincos_to_sum + parts = [] + args = Add.make_args(f) + for g in args: + coeff, g = g.as_independent(x) + + # g(x) = const + if g is S.One and not meijerg: + parts.append(coeff*x) + continue + + # g(x) = expr + O(x**n) + order_term = g.getO() + + if order_term is not None: + h = self._eval_integral(g.removeO(), x, **eval_kwargs) + + if h is not None: + h_order_expr = self._eval_integral(order_term.expr, x, **eval_kwargs) + + if h_order_expr is not None: + h_order_term = order_term.func( + h_order_expr, *order_term.variables) + parts.append(coeff*(h + h_order_term)) + continue + + # NOTE: if there is O(x**n) and we fail to integrate then + # there is no point in trying other methods because they + # will fail, too. + return None + + # c + # g(x) = (a*x+b) + if g.is_Pow and not g.exp.has(x) and not meijerg: + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + + M = g.base.match(a*x + b) + + if M is not None: + if g.exp == -1: + h = log(g.base) + elif conds != 'piecewise': + h = g.base**(g.exp + 1) / (g.exp + 1) + else: + h1 = log(g.base) + h2 = g.base**(g.exp + 1) / (g.exp + 1) + h = Piecewise((h2, Ne(g.exp, -1)), (h1, True)) + + parts.append(coeff * h / M[a]) + continue + + # poly(x) + # g(x) = ------- + # poly(x) + if g.is_rational_function(x) and not (manual or meijerg or risch): + parts.append(coeff * ratint(g, x)) + continue + + if not (manual or meijerg or risch): + # g(x) = Mul(trig) + h = trigintegrate(g, x, conds=conds) + if h is not None: + parts.append(coeff * h) + continue + + # g(x) has at least a DiracDelta term + h = deltaintegrate(g, x) + if h is not None: + parts.append(coeff * h) + continue + + from .singularityfunctions import singularityintegrate + # g(x) has at least a Singularity Function term + h = singularityintegrate(g, x) + if h is not None: + parts.append(coeff * h) + continue + + # Try risch again. + if risch is not False: + try: + h, i = risch_integrate(g, x, + separate_integral=True, conds=conds) + except NotImplementedError: + h = None + else: + if i: + h = h + i.doit(risch=False) + + parts.append(coeff*h) + continue + + # fall back to heurisch + if heurisch is not False: + from sympy.integrals.heurisch import (heurisch as heurisch_, + heurisch_wrapper) + try: + if conds == 'piecewise': + h = heurisch_wrapper(g, x, hints=[]) + else: + h = heurisch_(g, x, hints=[]) + except PolynomialError: + # XXX: this exception means there is a bug in the + # implementation of heuristic Risch integration + # algorithm. + h = None + else: + h = None + + if meijerg is not False and h is None: + # rewrite using G functions + try: + h = meijerint_indefinite(g, x) + except NotImplementedError: + _debug('NotImplementedError from meijerint_definite') + if h is not None: + parts.append(coeff * h) + continue + + if h is None and manual is not False: + try: + result = manualintegrate(g, x) + if result is not None and not isinstance(result, Integral): + if result.has(Integral) and not manual: + # Try to have other algorithms do the integrals + # manualintegrate can't handle, + # unless we were asked to use manual only. + # Keep the rest of eval_kwargs in case another + # method was set to False already + new_eval_kwargs = eval_kwargs + new_eval_kwargs["manual"] = False + new_eval_kwargs["final"] = False + result = result.func(*[ + arg.doit(**new_eval_kwargs) if + arg.has(Integral) else arg + for arg in result.args + ]).expand(multinomial=False, + log=False, + power_exp=False, + power_base=False) + if not result.has(Integral): + parts.append(coeff * result) + continue + except (ValueError, PolynomialError): + # can't handle some SymPy expressions + pass + + # if we failed maybe it was because we had + # a product that could have been expanded, + # so let's try an expansion of the whole + # thing before giving up; we don't try this + # at the outset because there are things + # that cannot be solved unless they are + # NOT expanded e.g., x**x*(1+log(x)). There + # should probably be a checker somewhere in this + # routine to look for such cases and try to do + # collection on the expressions if they are already + # in an expanded form + if not h and len(args) == 1: + f = sincos_to_sum(f).expand(mul=True, deep=False) + if f.is_Add: + # Note: risch will be identical on the expanded + # expression, but maybe it will be able to pick out parts, + # like x*(exp(x) + erf(x)). + return self._eval_integral(f, x, **eval_kwargs) + + if h is not None: + parts.append(coeff * h) + else: + return None + + return Add(*parts) + + def _eval_lseries(self, x, logx=None, cdir=0): + expr = self.as_dummy() + symb = x + for l in expr.limits: + if x in l[1:]: + symb = l[0] + break + for term in expr.function.lseries(symb, logx): + yield integrate(term, *expr.limits) + + def _eval_nseries(self, x, n, logx=None, cdir=0): + symb = x + for l in self.limits: + if x in l[1:]: + symb = l[0] + break + terms, order = self.function.nseries( + x=symb, n=n, logx=logx).as_coeff_add(Order) + order = [o.subs(symb, x) for o in order] + return integrate(terms, *self.limits) + Add(*order)*x + + def _eval_as_leading_term(self, x, logx, cdir): + series_gen = self.args[0].lseries(x) + for leading_term in series_gen: + if leading_term != 0: + break + return integrate(leading_term, *self.args[1:]) + + def _eval_simplify(self, **kwargs): + expr = factor_terms(self) + if isinstance(expr, Integral): + from sympy.simplify.simplify import simplify + return expr.func(*[simplify(i, **kwargs) for i in expr.args]) + return expr.simplify(**kwargs) + + def as_sum(self, n=None, method="midpoint", evaluate=True): + """ + Approximates a definite integral by a sum. + + Parameters + ========== + + n : + The number of subintervals to use, optional. + method : + One of: 'left', 'right', 'midpoint', 'trapezoid'. + evaluate : bool + If False, returns an unevaluated Sum expression. The default + is True, evaluate the sum. + + Notes + ===== + + These methods of approximate integration are described in [1]. + + Examples + ======== + + >>> from sympy import Integral, sin, sqrt + >>> from sympy.abc import x, n + >>> e = Integral(sin(x), (x, 3, 7)) + >>> e + Integral(sin(x), (x, 3, 7)) + + For demonstration purposes, this interval will only be split into 2 + regions, bounded by [3, 5] and [5, 7]. + + The left-hand rule uses function evaluations at the left of each + interval: + + >>> e.as_sum(2, 'left') + 2*sin(5) + 2*sin(3) + + The midpoint rule uses evaluations at the center of each interval: + + >>> e.as_sum(2, 'midpoint') + 2*sin(4) + 2*sin(6) + + The right-hand rule uses function evaluations at the right of each + interval: + + >>> e.as_sum(2, 'right') + 2*sin(5) + 2*sin(7) + + The trapezoid rule uses function evaluations on both sides of the + intervals. This is equivalent to taking the average of the left and + right hand rule results: + + >>> s = e.as_sum(2, 'trapezoid') + >>> s + 2*sin(5) + sin(3) + sin(7) + >>> (e.as_sum(2, 'left') + e.as_sum(2, 'right'))/2 == s + True + + Here, the discontinuity at x = 0 can be avoided by using the + midpoint or right-hand method: + + >>> e = Integral(1/sqrt(x), (x, 0, 1)) + >>> e.as_sum(5).n(4) + 1.730 + >>> e.as_sum(10).n(4) + 1.809 + >>> e.doit().n(4) # the actual value is 2 + 2.000 + + The left- or trapezoid method will encounter the discontinuity and + return infinity: + + >>> e.as_sum(5, 'left') + zoo + + The number of intervals can be symbolic. If omitted, a dummy symbol + will be used for it. + + >>> e = Integral(x**2, (x, 0, 2)) + >>> e.as_sum(n, 'right').expand() + 8/3 + 4/n + 4/(3*n**2) + + This shows that the midpoint rule is more accurate, as its error + term decays as the square of n: + + >>> e.as_sum(method='midpoint').expand() + 8/3 - 2/(3*_n**2) + + A symbolic sum is returned with evaluate=False: + + >>> e.as_sum(n, 'midpoint', evaluate=False) + 2*Sum((2*_k/n - 1/n)**2, (_k, 1, n))/n + + See Also + ======== + + Integral.doit : Perform the integration using any hints + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Riemann_sum#Riemann_summation_methods + """ + + from sympy.concrete.summations import Sum + limits = self.limits + if len(limits) > 1: + raise NotImplementedError( + "Multidimensional midpoint rule not implemented yet") + else: + limit = limits[0] + if (len(limit) != 3 or limit[1].is_finite is False or + limit[2].is_finite is False): + raise ValueError("Expecting a definite integral over " + "a finite interval.") + if n is None: + n = Dummy('n', integer=True, positive=True) + else: + n = sympify(n) + if (n.is_positive is False or n.is_integer is False or + n.is_finite is False): + raise ValueError("n must be a positive integer, got %s" % n) + x, a, b = limit + dx = (b - a)/n + k = Dummy('k', integer=True, positive=True) + f = self.function + + if method == "left": + result = dx*Sum(f.subs(x, a + (k-1)*dx), (k, 1, n)) + elif method == "right": + result = dx*Sum(f.subs(x, a + k*dx), (k, 1, n)) + elif method == "midpoint": + result = dx*Sum(f.subs(x, a + k*dx - dx/2), (k, 1, n)) + elif method == "trapezoid": + result = dx*((f.subs(x, a) + f.subs(x, b))/2 + + Sum(f.subs(x, a + k*dx), (k, 1, n - 1))) + else: + raise ValueError("Unknown method %s" % method) + return result.doit() if evaluate else result + + def principal_value(self, **kwargs): + """ + Compute the Cauchy Principal Value of the definite integral of a real function in the given interval + on the real axis. + + Explanation + =========== + + In mathematics, the Cauchy principal value, is a method for assigning values to certain improper + integrals which would otherwise be undefined. + + Examples + ======== + + >>> from sympy import Integral, oo + >>> from sympy.abc import x + >>> Integral(x+1, (x, -oo, oo)).principal_value() + oo + >>> f = 1 / (x**3) + >>> Integral(f, (x, -oo, oo)).principal_value() + 0 + >>> Integral(f, (x, -10, 10)).principal_value() + 0 + >>> Integral(f, (x, -10, oo)).principal_value() + Integral(f, (x, -oo, 10)).principal_value() + 0 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Cauchy_principal_value + .. [2] https://mathworld.wolfram.com/CauchyPrincipalValue.html + """ + if len(self.limits) != 1 or len(list(self.limits[0])) != 3: + raise ValueError("You need to insert a variable, lower_limit, and upper_limit correctly to calculate " + "cauchy's principal value") + x, a, b = self.limits[0] + if not (a.is_comparable and b.is_comparable and a <= b): + raise ValueError("The lower_limit must be smaller than or equal to the upper_limit to calculate " + "cauchy's principal value. Also, a and b need to be comparable.") + if a == b: + return S.Zero + + from sympy.calculus.singularities import singularities + + r = Dummy('r') + f = self.function + singularities_list = [s for s in singularities(f, x) if s.is_comparable and a <= s <= b] + for i in singularities_list: + if i in (a, b): + raise ValueError( + 'The principal value is not defined in the given interval due to singularity at %d.' % (i)) + F = integrate(f, x, **kwargs) + if F.has(Integral): + return self + if a is -oo and b is oo: + I = limit(F - F.subs(x, -x), x, oo) + else: + I = limit(F, x, b, '-') - limit(F, x, a, '+') + for s in singularities_list: + I += limit(((F.subs(x, s - r)) - F.subs(x, s + r)), r, 0, '+') + return I + + + +def integrate(*args, meijerg=None, conds='piecewise', risch=None, heurisch=None, manual=None, **kwargs): + """integrate(f, var, ...) + + .. deprecated:: 1.6 + + Using ``integrate()`` with :class:`~.Poly` is deprecated. Use + :meth:`.Poly.integrate` instead. See :ref:`deprecated-integrate-poly`. + + Explanation + =========== + + Compute definite or indefinite integral of one or more variables + using Risch-Norman algorithm and table lookup. This procedure is + able to handle elementary algebraic and transcendental functions + and also a huge class of special functions, including Airy, + Bessel, Whittaker and Lambert. + + var can be: + + - a symbol -- indefinite integration + - a tuple (symbol, a) -- indefinite integration with result + given with ``a`` replacing ``symbol`` + - a tuple (symbol, a, b) -- definite integration + + Several variables can be specified, in which case the result is + multiple integration. (If var is omitted and the integrand is + univariate, the indefinite integral in that variable will be performed.) + + Indefinite integrals are returned without terms that are independent + of the integration variables. (see examples) + + Definite improper integrals often entail delicate convergence + conditions. Pass conds='piecewise', 'separate' or 'none' to have + these returned, respectively, as a Piecewise function, as a separate + result (i.e. result will be a tuple), or not at all (default is + 'piecewise'). + + **Strategy** + + SymPy uses various approaches to definite integration. One method is to + find an antiderivative for the integrand, and then use the fundamental + theorem of calculus. Various functions are implemented to integrate + polynomial, rational and trigonometric functions, and integrands + containing DiracDelta terms. + + SymPy also implements the part of the Risch algorithm, which is a decision + procedure for integrating elementary functions, i.e., the algorithm can + either find an elementary antiderivative, or prove that one does not + exist. There is also a (very successful, albeit somewhat slow) general + implementation of the heuristic Risch algorithm. This algorithm will + eventually be phased out as more of the full Risch algorithm is + implemented. See the docstring of Integral._eval_integral() for more + details on computing the antiderivative using algebraic methods. + + The option risch=True can be used to use only the (full) Risch algorithm. + This is useful if you want to know if an elementary function has an + elementary antiderivative. If the indefinite Integral returned by this + function is an instance of NonElementaryIntegral, that means that the + Risch algorithm has proven that integral to be non-elementary. Note that + by default, additional methods (such as the Meijer G method outlined + below) are tried on these integrals, as they may be expressible in terms + of special functions, so if you only care about elementary answers, use + risch=True. Also note that an unevaluated Integral returned by this + function is not necessarily a NonElementaryIntegral, even with risch=True, + as it may just be an indication that the particular part of the Risch + algorithm needed to integrate that function is not yet implemented. + + Another family of strategies comes from re-writing the integrand in + terms of so-called Meijer G-functions. Indefinite integrals of a + single G-function can always be computed, and the definite integral + of a product of two G-functions can be computed from zero to + infinity. Various strategies are implemented to rewrite integrands + as G-functions, and use this information to compute integrals (see + the ``meijerint`` module). + + The option manual=True can be used to use only an algorithm that tries + to mimic integration by hand. This algorithm does not handle as many + integrands as the other algorithms implemented but may return results in + a more familiar form. The ``manualintegrate`` module has functions that + return the steps used (see the module docstring for more information). + + In general, the algebraic methods work best for computing + antiderivatives of (possibly complicated) combinations of elementary + functions. The G-function methods work best for computing definite + integrals from zero to infinity of moderately complicated + combinations of special functions, or indefinite integrals of very + simple combinations of special functions. + + The strategy employed by the integration code is as follows: + + - If computing a definite integral, and both limits are real, + and at least one limit is +- oo, try the G-function method of + definite integration first. + + - Try to find an antiderivative, using all available methods, ordered + by performance (that is try fastest method first, slowest last; in + particular polynomial integration is tried first, Meijer + G-functions second to last, and heuristic Risch last). + + - If still not successful, try G-functions irrespective of the + limits. + + The option meijerg=True, False, None can be used to, respectively: + always use G-function methods and no others, never use G-function + methods, or use all available methods (in order as described above). + It defaults to None. + + Examples + ======== + + >>> from sympy import integrate, log, exp, oo + >>> from sympy.abc import a, x, y + + >>> integrate(x*y, x) + x**2*y/2 + + >>> integrate(log(x), x) + x*log(x) - x + + >>> integrate(log(x), (x, 1, a)) + a*log(a) - a + 1 + + >>> integrate(x) + x**2/2 + + Terms that are independent of x are dropped by indefinite integration: + + >>> from sympy import sqrt + >>> integrate(sqrt(1 + x), (x, 0, x)) + 2*(x + 1)**(3/2)/3 - 2/3 + >>> integrate(sqrt(1 + x), x) + 2*(x + 1)**(3/2)/3 + + >>> integrate(x*y) + Traceback (most recent call last): + ... + ValueError: specify integration variables to integrate x*y + + Note that ``integrate(x)`` syntax is meant only for convenience + in interactive sessions and should be avoided in library code. + + >>> integrate(x**a*exp(-x), (x, 0, oo)) # same as conds='piecewise' + Piecewise((gamma(a + 1), re(a) > -1), + (Integral(x**a*exp(-x), (x, 0, oo)), True)) + + >>> integrate(x**a*exp(-x), (x, 0, oo), conds='none') + gamma(a + 1) + + >>> integrate(x**a*exp(-x), (x, 0, oo), conds='separate') + (gamma(a + 1), re(a) > -1) + + See Also + ======== + + Integral, Integral.doit + + """ + doit_flags = { + 'deep': False, + 'meijerg': meijerg, + 'conds': conds, + 'risch': risch, + 'heurisch': heurisch, + 'manual': manual + } + + integral = Integral(*args, **kwargs) + + if isinstance(integral, Integral): + return integral.doit(**doit_flags) + else: + new_args = [a.doit(**doit_flags) if isinstance(a, Integral) else a + for a in integral.args] + return integral.func(*new_args) + +def line_integrate(field, curve, vars): + """line_integrate(field, Curve, variables) + + Compute the line integral. + + Examples + ======== + + >>> from sympy import Curve, line_integrate, E, ln + >>> from sympy.abc import x, y, t + >>> C = Curve([E**t + 1, E**t - 1], (t, 0, ln(2))) + >>> line_integrate(x + y, C, [x, y]) + 3*sqrt(2) + + See Also + ======== + + sympy.integrals.integrals.integrate, Integral + """ + from sympy.geometry import Curve + F = sympify(field) + if not F: + raise ValueError( + "Expecting function specifying field as first argument.") + if not isinstance(curve, Curve): + raise ValueError("Expecting Curve entity as second argument.") + if not is_sequence(vars): + raise ValueError("Expecting ordered iterable for variables.") + if len(curve.functions) != len(vars): + raise ValueError("Field variable size does not match curve dimension.") + + if curve.parameter in vars: + raise ValueError("Curve parameter clashes with field parameters.") + + # Calculate derivatives for line parameter functions + # F(r) -> F(r(t)) and finally F(r(t)*r'(t)) + Ft = F + dldt = 0 + for i, var in enumerate(vars): + _f = curve.functions[i] + _dn = diff(_f, curve.parameter) + # ...arc length + dldt = dldt + (_dn * _dn) + Ft = Ft.subs(var, _f) + Ft = Ft * sqrt(dldt) + + integral = Integral(Ft, curve.limits).doit(deep=False) + return integral + + +### Property function dispatching ### + +@shape.register(Integral) +def _(expr): + return shape(expr.function) + +# Delayed imports +from .deltafunctions import deltaintegrate +from .meijerint import meijerint_definite, meijerint_indefinite, _debug +from .trigonometry import trigintegrate diff --git a/phivenv/Lib/site-packages/sympy/integrals/intpoly.py b/phivenv/Lib/site-packages/sympy/integrals/intpoly.py new file mode 100644 index 0000000000000000000000000000000000000000..38fd071183fb2192f4c1443d04c8f0ecfb6cc4ea --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/intpoly.py @@ -0,0 +1,1302 @@ +""" +Module to implement integration of uni/bivariate polynomials over +2D Polytopes and uni/bi/trivariate polynomials over 3D Polytopes. + +Uses evaluation techniques as described in Chin et al. (2015) [1]. + + +References +=========== + +.. [1] Chin, Eric B., Jean B. Lasserre, and N. Sukumar. "Numerical integration +of homogeneous functions on convex and nonconvex polygons and polyhedra." +Computational Mechanics 56.6 (2015): 967-981 + +PDF link : http://dilbert.engr.ucdavis.edu/~suku/quadrature/cls-integration.pdf +""" + +from functools import cmp_to_key + +from sympy.abc import x, y, z +from sympy.core import S, diff, Expr, Symbol +from sympy.core.sympify import _sympify +from sympy.geometry import Segment2D, Polygon, Point, Point2D +from sympy.polys.polytools import LC, gcd_list, degree_list, Poly +from sympy.simplify.simplify import nsimplify + + +def polytope_integrate(poly, expr=None, *, clockwise=False, max_degree=None): + """Integrates polynomials over 2/3-Polytopes. + + Explanation + =========== + + This function accepts the polytope in ``poly`` and the function in ``expr`` + (uni/bi/trivariate polynomials are implemented) and returns + the exact integral of ``expr`` over ``poly``. + + Parameters + ========== + + poly : The input Polygon. + + expr : The input polynomial. + + clockwise : Binary value to sort input points of 2-Polytope clockwise.(Optional) + + max_degree : The maximum degree of any monomial of the input polynomial.(Optional) + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import Point, Polygon + >>> from sympy.integrals.intpoly import polytope_integrate + >>> polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) + >>> polys = [1, x, y, x*y, x**2*y, x*y**2] + >>> expr = x*y + >>> polytope_integrate(polygon, expr) + 1/4 + >>> polytope_integrate(polygon, polys, max_degree=3) + {1: 1, x: 1/2, y: 1/2, x*y: 1/4, x*y**2: 1/6, x**2*y: 1/6} + """ + if clockwise: + if isinstance(poly, Polygon): + poly = Polygon(*point_sort(poly.vertices), evaluate=False) + else: + raise TypeError("clockwise=True works for only 2-Polytope" + "V-representation input") + + if isinstance(poly, Polygon): + # For Vertex Representation(2D case) + hp_params = hyperplane_parameters(poly) + facets = poly.sides + elif len(poly[0]) == 2: + # For Hyperplane Representation(2D case) + plen = len(poly) + if len(poly[0][0]) == 2: + intersections = [intersection(poly[(i - 1) % plen], poly[i], + "plane2D") + for i in range(0, plen)] + hp_params = poly + lints = len(intersections) + facets = [Segment2D(intersections[i], + intersections[(i + 1) % lints]) + for i in range(lints)] + else: + raise NotImplementedError("Integration for H-representation 3D" + "case not implemented yet.") + else: + # For Vertex Representation(3D case) + vertices = poly[0] + facets = poly[1:] + hp_params = hyperplane_parameters(facets, vertices) + + if max_degree is None: + if expr is None: + raise TypeError('Input expression must be a valid SymPy expression') + return main_integrate3d(expr, facets, vertices, hp_params) + + if max_degree is not None: + result = {} + if expr is not None: + f_expr = [] + for e in expr: + _ = decompose(e) + if len(_) == 1 and not _.popitem()[0]: + f_expr.append(e) + elif Poly(e).total_degree() <= max_degree: + f_expr.append(e) + expr = f_expr + + if not isinstance(expr, list) and expr is not None: + raise TypeError('Input polynomials must be list of expressions') + + if len(hp_params[0][0]) == 3: + result_dict = main_integrate3d(0, facets, vertices, hp_params, + max_degree) + else: + result_dict = main_integrate(0, facets, hp_params, max_degree) + + if expr is None: + return result_dict + + for poly in expr: + poly = _sympify(poly) + if poly not in result: + if poly.is_zero: + result[S.Zero] = S.Zero + continue + integral_value = S.Zero + monoms = decompose(poly, separate=True) + for monom in monoms: + monom = nsimplify(monom) + coeff, m = strip(monom) + integral_value += result_dict[m] * coeff + result[poly] = integral_value + return result + + if expr is None: + raise TypeError('Input expression must be a valid SymPy expression') + + return main_integrate(expr, facets, hp_params) + + +def strip(monom): + if monom.is_zero: + return S.Zero, S.Zero + elif monom.is_number: + return monom, S.One + else: + coeff = LC(monom) + return coeff, monom / coeff + +def _polynomial_integrate(polynomials, facets, hp_params): + dims = (x, y) + dim_length = len(dims) + integral_value = S.Zero + for deg in polynomials: + poly_contribute = S.Zero + facet_count = 0 + for hp in hp_params: + value_over_boundary = integration_reduction(facets, + facet_count, + hp[0], hp[1], + polynomials[deg], + dims, deg) + poly_contribute += value_over_boundary * (hp[1] / norm(hp[0])) + facet_count += 1 + poly_contribute /= (dim_length + deg) + integral_value += poly_contribute + + return integral_value + + +def main_integrate3d(expr, facets, vertices, hp_params, max_degree=None): + """Function to translate the problem of integrating uni/bi/tri-variate + polynomials over a 3-Polytope to integrating over its faces. + This is done using Generalized Stokes' Theorem and Euler's Theorem. + + Parameters + ========== + + expr : + The input polynomial. + facets : + Faces of the 3-Polytope(expressed as indices of `vertices`). + vertices : + Vertices that constitute the Polytope. + hp_params : + Hyperplane Parameters of the facets. + max_degree : optional + Max degree of constituent monomial in given list of polynomial. + + Examples + ======== + + >>> from sympy.integrals.intpoly import main_integrate3d, \ + hyperplane_parameters + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> vertices = cube[0] + >>> faces = cube[1:] + >>> hp_params = hyperplane_parameters(faces, vertices) + >>> main_integrate3d(1, faces, vertices, hp_params) + -125 + """ + result = {} + dims = (x, y, z) + dim_length = len(dims) + if max_degree: + grad_terms = gradient_terms(max_degree, 3) + flat_list = [term for z_terms in grad_terms + for x_term in z_terms + for term in x_term] + + for term in flat_list: + result[term[0]] = 0 + + for facet_count, hp in enumerate(hp_params): + a, b = hp[0], hp[1] + x0 = vertices[facets[facet_count][0]] + + for i, monom in enumerate(flat_list): + # Every monomial is a tuple : + # (term, x_degree, y_degree, z_degree, value over boundary) + expr, x_d, y_d, z_d, z_index, y_index, x_index, _ = monom + degree = x_d + y_d + z_d + if b.is_zero: + value_over_face = S.Zero + else: + value_over_face = \ + integration_reduction_dynamic(facets, facet_count, a, + b, expr, degree, dims, + x_index, y_index, + z_index, x0, grad_terms, + i, vertices, hp) + monom[7] = value_over_face + result[expr] += value_over_face * \ + (b / norm(a)) / (dim_length + x_d + y_d + z_d) + return result + else: + integral_value = S.Zero + polynomials = decompose(expr) + for deg in polynomials: + poly_contribute = S.Zero + facet_count = 0 + for i, facet in enumerate(facets): + hp = hp_params[i] + if hp[1].is_zero: + continue + pi = polygon_integrate(facet, hp, i, facets, vertices, expr, deg) + poly_contribute += pi *\ + (hp[1] / norm(tuple(hp[0]))) + facet_count += 1 + poly_contribute /= (dim_length + deg) + integral_value += poly_contribute + return integral_value + + +def main_integrate(expr, facets, hp_params, max_degree=None): + """Function to translate the problem of integrating univariate/bivariate + polynomials over a 2-Polytope to integrating over its boundary facets. + This is done using Generalized Stokes's Theorem and Euler's Theorem. + + Parameters + ========== + + expr : + The input polynomial. + facets : + Facets(Line Segments) of the 2-Polytope. + hp_params : + Hyperplane Parameters of the facets. + max_degree : optional + The maximum degree of any monomial of the input polynomial. + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import main_integrate,\ + hyperplane_parameters + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> hp_params = hyperplane_parameters(triangle) + >>> main_integrate(x**2 + y**2, facets, hp_params) + 325/6 + """ + dims = (x, y) + dim_length = len(dims) + result = {} + + if max_degree: + grad_terms = [[0, 0, 0, 0]] + gradient_terms(max_degree) + + for facet_count, hp in enumerate(hp_params): + a, b = hp[0], hp[1] + x0 = facets[facet_count].points[0] + + for i, monom in enumerate(grad_terms): + # Every monomial is a tuple : + # (term, x_degree, y_degree, value over boundary) + m, x_d, y_d, _ = monom + value = result.get(m, None) + degree = S.Zero + if b.is_zero: + value_over_boundary = S.Zero + else: + degree = x_d + y_d + value_over_boundary = \ + integration_reduction_dynamic(facets, facet_count, a, + b, m, degree, dims, x_d, + y_d, max_degree, x0, + grad_terms, i) + monom[3] = value_over_boundary + if value is not None: + result[m] += value_over_boundary * \ + (b / norm(a)) / (dim_length + degree) + else: + result[m] = value_over_boundary * \ + (b / norm(a)) / (dim_length + degree) + return result + else: + if not isinstance(expr, list): + polynomials = decompose(expr) + return _polynomial_integrate(polynomials, facets, hp_params) + else: + return {e: _polynomial_integrate(decompose(e), facets, hp_params) for e in expr} + + +def polygon_integrate(facet, hp_param, index, facets, vertices, expr, degree): + """Helper function to integrate the input uni/bi/trivariate polynomial + over a certain face of the 3-Polytope. + + Parameters + ========== + + facet : + Particular face of the 3-Polytope over which ``expr`` is integrated. + index : + The index of ``facet`` in ``facets``. + facets : + Faces of the 3-Polytope(expressed as indices of `vertices`). + vertices : + Vertices that constitute the facet. + expr : + The input polynomial. + degree : + Degree of ``expr``. + + Examples + ======== + + >>> from sympy.integrals.intpoly import polygon_integrate + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> facet = cube[1] + >>> facets = cube[1:] + >>> vertices = cube[0] + >>> polygon_integrate(facet, [(0, 1, 0), 5], 0, facets, vertices, 1, 0) + -25 + """ + expr = S(expr) + if expr.is_zero: + return S.Zero + result = S.Zero + x0 = vertices[facet[0]] + facet_len = len(facet) + for i, fac in enumerate(facet): + side = (vertices[fac], vertices[facet[(i + 1) % facet_len]]) + result += distance_to_side(x0, side, hp_param[0]) *\ + lineseg_integrate(facet, i, side, expr, degree) + if not expr.is_number: + expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\ + diff(expr, z) * x0[2] + result += polygon_integrate(facet, hp_param, index, facets, vertices, + expr, degree - 1) + result /= (degree + 2) + return result + + +def distance_to_side(point, line_seg, A): + """Helper function to compute the signed distance between given 3D point + and a line segment. + + Parameters + ========== + + point : 3D Point + line_seg : Line Segment + + Examples + ======== + + >>> from sympy.integrals.intpoly import distance_to_side + >>> point = (0, 0, 0) + >>> distance_to_side(point, [(0, 0, 1), (0, 1, 0)], (1, 0, 0)) + -sqrt(2)/2 + """ + x1, x2 = line_seg + rev_normal = [-1 * S(i)/norm(A) for i in A] + vector = [x2[i] - x1[i] for i in range(0, 3)] + vector = [vector[i]/norm(vector) for i in range(0, 3)] + + n_side = cross_product((0, 0, 0), rev_normal, vector) + vectorx0 = [line_seg[0][i] - point[i] for i in range(0, 3)] + dot_product = sum(vectorx0[i] * n_side[i] for i in range(0, 3)) + + return dot_product + + +def lineseg_integrate(polygon, index, line_seg, expr, degree): + """Helper function to compute the line integral of ``expr`` over ``line_seg``. + + Parameters + =========== + + polygon : + Face of a 3-Polytope. + index : + Index of line_seg in polygon. + line_seg : + Line Segment. + + Examples + ======== + + >>> from sympy.integrals.intpoly import lineseg_integrate + >>> polygon = [(0, 5, 0), (5, 5, 0), (5, 5, 5), (0, 5, 5)] + >>> line_seg = [(0, 5, 0), (5, 5, 0)] + >>> lineseg_integrate(polygon, 0, line_seg, 1, 0) + 5 + """ + expr = _sympify(expr) + if expr.is_zero: + return S.Zero + result = S.Zero + x0 = line_seg[0] + distance = norm(tuple([line_seg[1][i] - line_seg[0][i] for i in + range(3)])) + if isinstance(expr, Expr): + expr_dict = {x: line_seg[1][0], + y: line_seg[1][1], + z: line_seg[1][2]} + result += distance * expr.subs(expr_dict) + else: + result += distance * expr + + expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\ + diff(expr, z) * x0[2] + + result += lineseg_integrate(polygon, index, line_seg, expr, degree - 1) + result /= (degree + 1) + return result + + +def integration_reduction(facets, index, a, b, expr, dims, degree): + """Helper method for main_integrate. Returns the value of the input + expression evaluated over the polytope facet referenced by a given index. + + Parameters + =========== + + facets : + List of facets of the polytope. + index : + Index referencing the facet to integrate the expression over. + a : + Hyperplane parameter denoting direction. + b : + Hyperplane parameter denoting distance. + expr : + The expression to integrate over the facet. + dims : + List of symbols denoting axes. + degree : + Degree of the homogeneous polynomial. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import integration_reduction,\ + hyperplane_parameters + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> a, b = hyperplane_parameters(triangle)[0] + >>> integration_reduction(facets, 0, a, b, 1, (x, y), 0) + 5 + """ + expr = _sympify(expr) + if expr.is_zero: + return expr + + value = S.Zero + x0 = facets[index].points[0] + m = len(facets) + gens = (x, y) + + inner_product = diff(expr, gens[0]) * x0[0] + diff(expr, gens[1]) * x0[1] + + if inner_product != 0: + value += integration_reduction(facets, index, a, b, + inner_product, dims, degree - 1) + + value += left_integral2D(m, index, facets, x0, expr, gens) + + return value/(len(dims) + degree - 1) + + +def left_integral2D(m, index, facets, x0, expr, gens): + """Computes the left integral of Eq 10 in Chin et al. + For the 2D case, the integral is just an evaluation of the polynomial + at the intersection of two facets which is multiplied by the distance + between the first point of facet and that intersection. + + Parameters + ========== + + m : + No. of hyperplanes. + index : + Index of facet to find intersections with. + facets : + List of facets(Line Segments in 2D case). + x0 : + First point on facet referenced by index. + expr : + Input polynomial + gens : + Generators which generate the polynomial + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import left_integral2D + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> left_integral2D(3, 0, facets, facets[0].points[0], 1, (x, y)) + 5 + """ + value = S.Zero + for j in range(m): + intersect = () + if j in ((index - 1) % m, (index + 1) % m): + intersect = intersection(facets[index], facets[j], "segment2D") + if intersect: + distance_origin = norm(tuple(map(lambda x, y: x - y, + intersect, x0))) + if is_vertex(intersect): + if isinstance(expr, Expr): + if len(gens) == 3: + expr_dict = {gens[0]: intersect[0], + gens[1]: intersect[1], + gens[2]: intersect[2]} + else: + expr_dict = {gens[0]: intersect[0], + gens[1]: intersect[1]} + value += distance_origin * expr.subs(expr_dict) + else: + value += distance_origin * expr + return value + + +def integration_reduction_dynamic(facets, index, a, b, expr, degree, dims, + x_index, y_index, max_index, x0, + monomial_values, monom_index, vertices=None, + hp_param=None): + """The same integration_reduction function which uses a dynamic + programming approach to compute terms by using the values of the integral + of previously computed terms. + + Parameters + ========== + + facets : + Facets of the Polytope. + index : + Index of facet to find intersections with.(Used in left_integral()). + a, b : + Hyperplane parameters. + expr : + Input monomial. + degree : + Total degree of ``expr``. + dims : + Tuple denoting axes variables. + x_index : + Exponent of 'x' in ``expr``. + y_index : + Exponent of 'y' in ``expr``. + max_index : + Maximum exponent of any monomial in ``monomial_values``. + x0 : + First point on ``facets[index]``. + monomial_values : + List of monomial values constituting the polynomial. + monom_index : + Index of monomial whose integration is being found. + vertices : optional + Coordinates of vertices constituting the 3-Polytope. + hp_param : optional + Hyperplane Parameter of the face of the facets[index]. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import (integration_reduction_dynamic, \ + hyperplane_parameters) + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> a, b = hyperplane_parameters(triangle)[0] + >>> x0 = facets[0].points[0] + >>> monomial_values = [[0, 0, 0, 0], [1, 0, 0, 5],\ + [y, 0, 1, 15], [x, 1, 0, None]] + >>> integration_reduction_dynamic(facets, 0, a, b, x, 1, (x, y), 1, 0, 1,\ + x0, monomial_values, 3) + 25/2 + """ + value = S.Zero + m = len(facets) + + if expr == S.Zero: + return expr + + if len(dims) == 2: + if not expr.is_number: + _, x_degree, y_degree, _ = monomial_values[monom_index] + x_index = monom_index - max_index + \ + x_index - 2 if x_degree > 0 else 0 + y_index = monom_index - 1 if y_degree > 0 else 0 + x_value, y_value =\ + monomial_values[x_index][3], monomial_values[y_index][3] + + value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] + + value += left_integral2D(m, index, facets, x0, expr, dims) + else: + # For 3D use case the max_index contains the z_degree of the term + z_index = max_index + if not expr.is_number: + x_degree, y_degree, z_degree = y_index,\ + z_index - x_index - y_index, x_index + x_value = monomial_values[z_index - 1][y_index - 1][x_index][7]\ + if x_degree > 0 else 0 + y_value = monomial_values[z_index - 1][y_index][x_index][7]\ + if y_degree > 0 else 0 + z_value = monomial_values[z_index - 1][y_index][x_index - 1][7]\ + if z_degree > 0 else 0 + + value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] \ + + z_degree * z_value * x0[2] + + value += left_integral3D(facets, index, expr, + vertices, hp_param, degree) + return value / (len(dims) + degree - 1) + + +def left_integral3D(facets, index, expr, vertices, hp_param, degree): + """Computes the left integral of Eq 10 in Chin et al. + + Explanation + =========== + + For the 3D case, this is the sum of the integral values over constituting + line segments of the face (which is accessed by facets[index]) multiplied + by the distance between the first point of facet and that line segment. + + Parameters + ========== + + facets : + List of faces of the 3-Polytope. + index : + Index of face over which integral is to be calculated. + expr : + Input polynomial. + vertices : + List of vertices that constitute the 3-Polytope. + hp_param : + The hyperplane parameters of the face. + degree : + Degree of the ``expr``. + + Examples + ======== + + >>> from sympy.integrals.intpoly import left_integral3D + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> facets = cube[1:] + >>> vertices = cube[0] + >>> left_integral3D(facets, 3, 1, vertices, ([0, -1, 0], -5), 0) + -50 + """ + value = S.Zero + facet = facets[index] + x0 = vertices[facet[0]] + facet_len = len(facet) + for i, fac in enumerate(facet): + side = (vertices[fac], vertices[facet[(i + 1) % facet_len]]) + value += distance_to_side(x0, side, hp_param[0]) * \ + lineseg_integrate(facet, i, side, expr, degree) + return value + + +def gradient_terms(binomial_power=0, no_of_gens=2): + """Returns a list of all the possible monomials between + 0 and y**binomial_power for 2D case and z**binomial_power + for 3D case. + + Parameters + ========== + + binomial_power : + Power upto which terms are generated. + no_of_gens : + Denotes whether terms are being generated for 2D or 3D case. + + Examples + ======== + + >>> from sympy.integrals.intpoly import gradient_terms + >>> gradient_terms(2) + [[1, 0, 0, 0], [y, 0, 1, 0], [y**2, 0, 2, 0], [x, 1, 0, 0], + [x*y, 1, 1, 0], [x**2, 2, 0, 0]] + >>> gradient_terms(2, 3) + [[[[1, 0, 0, 0, 0, 0, 0, 0]]], [[[y, 0, 1, 0, 1, 0, 0, 0], + [z, 0, 0, 1, 1, 0, 1, 0]], [[x, 1, 0, 0, 1, 1, 0, 0]]], + [[[y**2, 0, 2, 0, 2, 0, 0, 0], [y*z, 0, 1, 1, 2, 0, 1, 0], + [z**2, 0, 0, 2, 2, 0, 2, 0]], [[x*y, 1, 1, 0, 2, 1, 0, 0], + [x*z, 1, 0, 1, 2, 1, 1, 0]], [[x**2, 2, 0, 0, 2, 2, 0, 0]]]] + """ + if no_of_gens == 2: + count = 0 + terms = [None] * int((binomial_power ** 2 + 3 * binomial_power + 2) / 2) + for x_count in range(0, binomial_power + 1): + for y_count in range(0, binomial_power - x_count + 1): + terms[count] = [x**x_count*y**y_count, + x_count, y_count, 0] + count += 1 + else: + terms = [[[[x ** x_count * y ** y_count * + z ** (z_count - y_count - x_count), + x_count, y_count, z_count - y_count - x_count, + z_count, x_count, z_count - y_count - x_count, 0] + for y_count in range(z_count - x_count, -1, -1)] + for x_count in range(0, z_count + 1)] + for z_count in range(0, binomial_power + 1)] + return terms + + +def hyperplane_parameters(poly, vertices=None): + """A helper function to return the hyperplane parameters + of which the facets of the polytope are a part of. + + Parameters + ========== + + poly : + The input 2/3-Polytope. + vertices : + Vertex indices of 3-Polytope. + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> from sympy.integrals.intpoly import hyperplane_parameters + >>> hyperplane_parameters(Polygon(Point(0, 3), Point(5, 3), Point(1, 1))) + [((0, 1), 3), ((1, -2), -1), ((-2, -1), -3)] + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> hyperplane_parameters(cube[1:], cube[0]) + [([0, -1, 0], -5), ([0, 0, -1], -5), ([-1, 0, 0], -5), + ([0, 1, 0], 0), ([1, 0, 0], 0), ([0, 0, 1], 0)] + """ + if isinstance(poly, Polygon): + vertices = list(poly.vertices) + [poly.vertices[0]] # Close the polygon + params = [None] * (len(vertices) - 1) + + for i in range(len(vertices) - 1): + v1 = vertices[i] + v2 = vertices[i + 1] + + a1 = v1[1] - v2[1] + a2 = v2[0] - v1[0] + b = v2[0] * v1[1] - v2[1] * v1[0] + + factor = gcd_list([a1, a2, b]) + + b = S(b) / factor + a = (S(a1) / factor, S(a2) / factor) + params[i] = (a, b) + else: + params = [None] * len(poly) + for i, polygon in enumerate(poly): + v1, v2, v3 = [vertices[vertex] for vertex in polygon[:3]] + normal = cross_product(v1, v2, v3) + b = sum(normal[j] * v1[j] for j in range(0, 3)) + fac = gcd_list(normal) + if fac.is_zero: + fac = 1 + normal = [j / fac for j in normal] + b = b / fac + params[i] = (normal, b) + return params + + +def cross_product(v1, v2, v3): + """Returns the cross-product of vectors (v2 - v1) and (v3 - v1) + That is : (v2 - v1) X (v3 - v1) + """ + v2 = [v2[j] - v1[j] for j in range(0, 3)] + v3 = [v3[j] - v1[j] for j in range(0, 3)] + return [v3[2] * v2[1] - v3[1] * v2[2], + v3[0] * v2[2] - v3[2] * v2[0], + v3[1] * v2[0] - v3[0] * v2[1]] + + +def best_origin(a, b, lineseg, expr): + """Helper method for polytope_integrate. Currently not used in the main + algorithm. + + Explanation + =========== + + Returns a point on the lineseg whose vector inner product with the + divergence of `expr` yields an expression with the least maximum + total power. + + Parameters + ========== + + a : + Hyperplane parameter denoting direction. + b : + Hyperplane parameter denoting distance. + lineseg : + Line segment on which to find the origin. + expr : + The expression which determines the best point. + + Algorithm(currently works only for 2D use case) + =============================================== + + 1 > Firstly, check for edge cases. Here that would refer to vertical + or horizontal lines. + + 2 > If input expression is a polynomial containing more than one generator + then find out the total power of each of the generators. + + x**2 + 3 + x*y + x**4*y**5 ---> {x: 7, y: 6} + + If expression is a constant value then pick the first boundary point + of the line segment. + + 3 > First check if a point exists on the line segment where the value of + the highest power generator becomes 0. If not check if the value of + the next highest becomes 0. If none becomes 0 within line segment + constraints then pick the first boundary point of the line segment. + Actually, any point lying on the segment can be picked as best origin + in the last case. + + Examples + ======== + + >>> from sympy.integrals.intpoly import best_origin + >>> from sympy.abc import x, y + >>> from sympy import Point, Segment2D + >>> l = Segment2D(Point(0, 3), Point(1, 1)) + >>> expr = x**3*y**7 + >>> best_origin((2, 1), 3, l, expr) + (0, 3.0) + """ + a1, b1 = lineseg.points[0] + + def x_axis_cut(ls): + """Returns the point where the input line segment + intersects the x-axis. + + Parameters + ========== + + ls : + Line segment + """ + p, q = ls.points + if p.y.is_zero: + return tuple(p) + elif q.y.is_zero: + return tuple(q) + elif p.y/q.y < S.Zero: + return p.y * (p.x - q.x)/(q.y - p.y) + p.x, S.Zero + else: + return () + + def y_axis_cut(ls): + """Returns the point where the input line segment + intersects the y-axis. + + Parameters + ========== + + ls : + Line segment + """ + p, q = ls.points + if p.x.is_zero: + return tuple(p) + elif q.x.is_zero: + return tuple(q) + elif p.x/q.x < S.Zero: + return S.Zero, p.x * (p.y - q.y)/(q.x - p.x) + p.y + else: + return () + + gens = (x, y) + power_gens = {} + + for i in gens: + power_gens[i] = S.Zero + + if len(gens) > 1: + # Special case for vertical and horizontal lines + if len(gens) == 2: + if a[0] == 0: + if y_axis_cut(lineseg): + return S.Zero, b/a[1] + else: + return a1, b1 + elif a[1] == 0: + if x_axis_cut(lineseg): + return b/a[0], S.Zero + else: + return a1, b1 + + if isinstance(expr, Expr): # Find the sum total of power of each + if expr.is_Add: # generator and store in a dictionary. + for monomial in expr.args: + if monomial.is_Pow: + if monomial.args[0] in gens: + power_gens[monomial.args[0]] += monomial.args[1] + else: + for univariate in monomial.args: + term_type = len(univariate.args) + if term_type == 0 and univariate in gens: + power_gens[univariate] += 1 + elif term_type == 2 and univariate.args[0] in gens: + power_gens[univariate.args[0]] +=\ + univariate.args[1] + elif expr.is_Mul: + for term in expr.args: + term_type = len(term.args) + if term_type == 0 and term in gens: + power_gens[term] += 1 + elif term_type == 2 and term.args[0] in gens: + power_gens[term.args[0]] += term.args[1] + elif expr.is_Pow: + power_gens[expr.args[0]] = expr.args[1] + elif expr.is_Symbol: + power_gens[expr] += 1 + else: # If `expr` is a constant take first vertex of the line segment. + return a1, b1 + + # TODO : This part is quite hacky. Should be made more robust with + # TODO : respect to symbol names and scalable w.r.t higher dimensions. + power_gens = sorted(power_gens.items(), key=lambda k: str(k[0])) + if power_gens[0][1] >= power_gens[1][1]: + if y_axis_cut(lineseg): + x0 = (S.Zero, b / a[1]) + elif x_axis_cut(lineseg): + x0 = (b / a[0], S.Zero) + else: + x0 = (a1, b1) + else: + if x_axis_cut(lineseg): + x0 = (b/a[0], S.Zero) + elif y_axis_cut(lineseg): + x0 = (S.Zero, b/a[1]) + else: + x0 = (a1, b1) + else: + x0 = (b/a[0]) + return x0 + + +def decompose(expr, separate=False): + """Decomposes an input polynomial into homogeneous ones of + smaller or equal degree. + + Explanation + =========== + + Returns a dictionary with keys as the degree of the smaller + constituting polynomials. Values are the constituting polynomials. + + Parameters + ========== + + expr : Expr + Polynomial(SymPy expression). + separate : bool + If True then simply return a list of the constituent monomials + If not then break up the polynomial into constituent homogeneous + polynomials. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import decompose + >>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5) + {1: x + y, 2: x**2 + x*y, 5: x**3*y**2 + y**5} + >>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5, True) + {x, x**2, y, y**5, x*y, x**3*y**2} + """ + poly_dict = {} + + if isinstance(expr, Expr) and not expr.is_number: + if expr.is_Symbol: + poly_dict[1] = expr + elif expr.is_Add: + symbols = expr.atoms(Symbol) + degrees = [(sum(degree_list(monom, *symbols)), monom) + for monom in expr.args] + if separate: + return {monom[1] for monom in degrees} + else: + for monom in degrees: + degree, term = monom + if poly_dict.get(degree): + poly_dict[degree] += term + else: + poly_dict[degree] = term + elif expr.is_Pow: + _, degree = expr.args + poly_dict[degree] = expr + else: # Now expr can only be of `Mul` type + degree = 0 + for term in expr.args: + term_type = len(term.args) + if term_type == 0 and term.is_Symbol: + degree += 1 + elif term_type == 2: + degree += term.args[1] + poly_dict[degree] = expr + else: + poly_dict[0] = expr + + if separate: + return set(poly_dict.values()) + return poly_dict + + +def point_sort(poly, normal=None, clockwise=True): + """Returns the same polygon with points sorted in clockwise or + anti-clockwise order. + + Note that it's necessary for input points to be sorted in some order + (clockwise or anti-clockwise) for the integration algorithm to work. + As a convention algorithm has been implemented keeping clockwise + orientation in mind. + + Parameters + ========== + + poly: + 2D or 3D Polygon. + normal : optional + The normal of the plane which the 3-Polytope is a part of. + clockwise : bool, optional + Returns points sorted in clockwise order if True and + anti-clockwise if False. + + Examples + ======== + + >>> from sympy.integrals.intpoly import point_sort + >>> from sympy import Point + >>> point_sort([Point(0, 0), Point(1, 0), Point(1, 1)]) + [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)] + """ + pts = poly.vertices if isinstance(poly, Polygon) else poly + n = len(pts) + if n < 2: + return list(pts) + + order = S.One if clockwise else S.NegativeOne + dim = len(pts[0]) + if dim == 2: + center = Point(sum((vertex.x for vertex in pts)) / n, + sum((vertex.y for vertex in pts)) / n) + else: + center = Point(sum((vertex.x for vertex in pts)) / n, + sum((vertex.y for vertex in pts)) / n, + sum((vertex.z for vertex in pts)) / n) + + def compare(a, b): + if a.x - center.x >= S.Zero and b.x - center.x < S.Zero: + return -order + elif a.x - center.x < 0 and b.x - center.x >= 0: + return order + elif a.x - center.x == 0 and b.x - center.x == 0: + if a.y - center.y >= 0 or b.y - center.y >= 0: + return -order if a.y > b.y else order + return -order if b.y > a.y else order + + det = (a.x - center.x) * (b.y - center.y) -\ + (b.x - center.x) * (a.y - center.y) + if det < 0: + return -order + elif det > 0: + return order + + first = (a.x - center.x) * (a.x - center.x) +\ + (a.y - center.y) * (a.y - center.y) + second = (b.x - center.x) * (b.x - center.x) +\ + (b.y - center.y) * (b.y - center.y) + return -order if first > second else order + + def compare3d(a, b): + det = cross_product(center, a, b) + dot_product = sum(det[i] * normal[i] for i in range(0, 3)) + if dot_product < 0: + return -order + elif dot_product > 0: + return order + + return sorted(pts, key=cmp_to_key(compare if dim==2 else compare3d)) + + +def norm(point): + """Returns the Euclidean norm of a point from origin. + + Parameters + ========== + + point: + This denotes a point in the dimension_al spac_e. + + Examples + ======== + + >>> from sympy.integrals.intpoly import norm + >>> from sympy import Point + >>> norm(Point(2, 7)) + sqrt(53) + """ + half = S.Half + if isinstance(point, (list, tuple)): + return sum(coord ** 2 for coord in point) ** half + elif isinstance(point, Point): + if isinstance(point, Point2D): + return (point.x ** 2 + point.y ** 2) ** half + else: + return (point.x ** 2 + point.y ** 2 + point.z) ** half + elif isinstance(point, dict): + return sum(i**2 for i in point.values()) ** half + + +def intersection(geom_1, geom_2, intersection_type): + """Returns intersection between geometric objects. + + Explanation + =========== + + Note that this function is meant for use in integration_reduction and + at that point in the calling function the lines denoted by the segments + surely intersect within segment boundaries. Coincident lines are taken + to be non-intersecting. Also, the hyperplane intersection for 2D case is + also implemented. + + Parameters + ========== + + geom_1, geom_2: + The input line segments. + + Examples + ======== + + >>> from sympy.integrals.intpoly import intersection + >>> from sympy import Point, Segment2D + >>> l1 = Segment2D(Point(1, 1), Point(3, 5)) + >>> l2 = Segment2D(Point(2, 0), Point(2, 5)) + >>> intersection(l1, l2, "segment2D") + (2, 3) + >>> p1 = ((-1, 0), 0) + >>> p2 = ((0, 1), 1) + >>> intersection(p1, p2, "plane2D") + (0, 1) + """ + if intersection_type[:-2] == "segment": + if intersection_type == "segment2D": + x1, y1 = geom_1.points[0] + x2, y2 = geom_1.points[1] + x3, y3 = geom_2.points[0] + x4, y4 = geom_2.points[1] + elif intersection_type == "segment3D": + x1, y1, z1 = geom_1.points[0] + x2, y2, z2 = geom_1.points[1] + x3, y3, z3 = geom_2.points[0] + x4, y4, z4 = geom_2.points[1] + + denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + if denom: + t1 = x1 * y2 - y1 * x2 + t2 = x3 * y4 - x4 * y3 + return (S(t1 * (x3 - x4) - t2 * (x1 - x2)) / denom, + S(t1 * (y3 - y4) - t2 * (y1 - y2)) / denom) + if intersection_type[:-2] == "plane": + if intersection_type == "plane2D": # Intersection of hyperplanes + a1x, a1y = geom_1[0] + a2x, a2y = geom_2[0] + b1, b2 = geom_1[1], geom_2[1] + + denom = a1x * a2y - a2x * a1y + if denom: + return (S(b1 * a2y - b2 * a1y) / denom, + S(b2 * a1x - b1 * a2x) / denom) + + +def is_vertex(ent): + """If the input entity is a vertex return True. + + Parameter + ========= + + ent : + Denotes a geometric entity representing a point. + + Examples + ======== + + >>> from sympy import Point + >>> from sympy.integrals.intpoly import is_vertex + >>> is_vertex((2, 3)) + True + >>> is_vertex((2, 3, 6)) + True + >>> is_vertex(Point(2, 3)) + True + """ + if isinstance(ent, tuple): + if len(ent) in [2, 3]: + return True + elif isinstance(ent, Point): + return True + return False + + +def plot_polytope(poly): + """Plots the 2D polytope using the functions written in plotting + module which in turn uses matplotlib backend. + + Parameter + ========= + + poly: + Denotes a 2-Polytope. + """ + from sympy.plotting.plot import Plot, List2DSeries + + xl = [vertex.x for vertex in poly.vertices] + yl = [vertex.y for vertex in poly.vertices] + + xl.append(poly.vertices[0].x) # Closing the polygon + yl.append(poly.vertices[0].y) + + l2ds = List2DSeries(xl, yl) + p = Plot(l2ds, axes='label_axes=True') + p.show() + + +def plot_polynomial(expr): + """Plots the polynomial using the functions written in + plotting module which in turn uses matplotlib backend. + + Parameter + ========= + + expr: + Denotes a polynomial(SymPy expression). + """ + from sympy.plotting.plot import plot3d, plot + gens = expr.free_symbols + if len(gens) == 2: + plot3d(expr) + else: + plot(expr) diff --git a/phivenv/Lib/site-packages/sympy/integrals/laplace.py b/phivenv/Lib/site-packages/sympy/integrals/laplace.py new file mode 100644 index 0000000000000000000000000000000000000000..604c4a2711440f13c62f0d778e382e4daaf33148 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/laplace.py @@ -0,0 +1,2377 @@ +"""Laplace Transforms""" +import sys +import sympy +from sympy.core import S, pi, I +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.expr import Expr +from sympy.core.function import ( + AppliedUndef, Derivative, expand, expand_complex, expand_mul, expand_trig, + Lambda, WildFunction, diff, Subs) +from sympy.core.mul import Mul, prod +from sympy.core.relational import ( + _canonical, Ge, Gt, Lt, Unequality, Eq, Ne, Relational) +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy, symbols, Wild +from sympy.functions.elementary.complexes import ( + re, im, arg, Abs, polar_lift, periodic_argument) +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, asinh +from sympy.functions.elementary.miscellaneous import Max, Min, sqrt +from sympy.functions.elementary.piecewise import ( + Piecewise, piecewise_exclusive) +from sympy.functions.elementary.trigonometric import cos, sin, atan, sinc +from sympy.functions.special.bessel import besseli, besselj, besselk, bessely +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.error_functions import erf, erfc, Ei +from sympy.functions.special.gamma_functions import ( + digamma, gamma, lowergamma, uppergamma) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.integrals import integrate, Integral +from sympy.integrals.transforms import ( + _simplify, IntegralTransform, IntegralTransformError) +from sympy.logic.boolalg import to_cnf, conjuncts, disjuncts, Or, And +from sympy.matrices.matrixbase import MatrixBase +from sympy.polys.matrices.linsolve import _lin_eq2dict +from sympy.polys.polyerrors import PolynomialError +from sympy.polys.polyroots import roots +from sympy.polys.polytools import Poly +from sympy.polys.rationaltools import together +from sympy.polys.rootoftools import RootSum +from sympy.utilities.exceptions import ( + sympy_deprecation_warning, SymPyDeprecationWarning, ignore_warnings) +from sympy.utilities.misc import debugf + +_LT_level = 0 + + +def DEBUG_WRAP(func): + def wrap(*args, **kwargs): + from sympy import SYMPY_DEBUG + global _LT_level + + if not SYMPY_DEBUG: + return func(*args, **kwargs) + + if _LT_level == 0: + print('\n' + '-'*78, file=sys.stderr) + print('-LT- %s%s%s' % (' '*_LT_level, func.__name__, args), + file=sys.stderr) + _LT_level += 1 + if ( + func.__name__ == '_laplace_transform_integration' or + func.__name__ == '_inverse_laplace_transform_integration'): + sympy.SYMPY_DEBUG = False + print('**** %sIntegrating ...' % (' '*_LT_level), file=sys.stderr) + result = func(*args, **kwargs) + sympy.SYMPY_DEBUG = True + else: + result = func(*args, **kwargs) + _LT_level -= 1 + print('-LT- %s---> %s' % (' '*_LT_level, result), file=sys.stderr) + if _LT_level == 0: + print('-'*78 + '\n', file=sys.stderr) + return result + return wrap + + +def _debug(text): + from sympy import SYMPY_DEBUG + + if SYMPY_DEBUG: + print('-LT- %s%s' % (' '*_LT_level, text), file=sys.stderr) + + +def _simplifyconds(expr, s, a): + r""" + Naively simplify some conditions occurring in ``expr``, + given that `\operatorname{Re}(s) > a`. + + Examples + ======== + + >>> from sympy.integrals.laplace import _simplifyconds + >>> from sympy.abc import x + >>> from sympy import sympify as S + >>> _simplifyconds(abs(x**2) < 1, x, 1) + False + >>> _simplifyconds(abs(x**2) < 1, x, 2) + False + >>> _simplifyconds(abs(x**2) < 1, x, 0) + Abs(x**2) < 1 + >>> _simplifyconds(abs(1/x**2) < 1, x, 1) + True + >>> _simplifyconds(S(1) < abs(x), x, 1) + True + >>> _simplifyconds(S(1) < abs(1/x), x, 1) + False + + >>> from sympy import Ne + >>> _simplifyconds(Ne(1, x**3), x, 1) + True + >>> _simplifyconds(Ne(1, x**3), x, 2) + True + >>> _simplifyconds(Ne(1, x**3), x, 0) + Ne(1, x**3) + """ + + def power(ex): + if ex == s: + return 1 + if ex.is_Pow and ex.base == s: + return ex.exp + return None + + def bigger(ex1, ex2): + """ Return True only if |ex1| > |ex2|, False only if |ex1| < |ex2|. + Else return None. """ + if ex1.has(s) and ex2.has(s): + return None + if isinstance(ex1, Abs): + ex1 = ex1.args[0] + if isinstance(ex2, Abs): + ex2 = ex2.args[0] + if ex1.has(s): + return bigger(1/ex2, 1/ex1) + n = power(ex2) + if n is None: + return None + try: + if n > 0 and (Abs(ex1) <= Abs(a)**n) == S.true: + return False + if n < 0 and (Abs(ex1) >= Abs(a)**n) == S.true: + return True + except TypeError: + return None + + def replie(x, y): + """ simplify x < y """ + if (not (x.is_positive or isinstance(x, Abs)) + or not (y.is_positive or isinstance(y, Abs))): + return (x < y) + r = bigger(x, y) + if r is not None: + return not r + return (x < y) + + def replue(x, y): + b = bigger(x, y) + if b in (True, False): + return True + return Unequality(x, y) + + def repl(ex, *args): + if ex in (True, False): + return bool(ex) + return ex.replace(*args) + + from sympy.simplify.radsimp import collect_abs + expr = collect_abs(expr) + expr = repl(expr, Lt, replie) + expr = repl(expr, Gt, lambda x, y: replie(y, x)) + expr = repl(expr, Unequality, replue) + return S(expr) + + +@DEBUG_WRAP +def expand_dirac_delta(expr): + """ + Expand an expression involving DiractDelta to get it as a linear + combination of DiracDelta functions. + """ + return _lin_eq2dict(expr, expr.atoms(DiracDelta)) + + +@DEBUG_WRAP +def _laplace_transform_integration(f, t, s_, *, simplify): + """ The backend function for doing Laplace transforms by integration. + + This backend assumes that the frontend has already split sums + such that `f` is to an addition anymore. + """ + s = Dummy('s') + + if f.has(DiracDelta): + return None + + F = integrate(f*exp(-s*t), (t, S.Zero, S.Infinity)) + + if not F.has(Integral): + return _simplify(F.subs(s, s_), simplify), S.NegativeInfinity, S.true + + if not F.is_Piecewise: + return None + + F, cond = F.args[0] + if F.has(Integral): + return None + + def process_conds(conds): + """ Turn ``conds`` into a strip and auxiliary conditions. """ + from sympy.solvers.inequalities import _solve_inequality + a = S.NegativeInfinity + aux = S.true + conds = conjuncts(to_cnf(conds)) + p, q, w1, w2, w3, w4, w5 = symbols( + 'p q w1 w2 w3 w4 w5', cls=Wild, exclude=[s]) + patterns = ( + p*Abs(arg((s + w3)*q)) < w2, + p*Abs(arg((s + w3)*q)) <= w2, + Abs(periodic_argument((s + w3)**p*q, w1)) < w2, + Abs(periodic_argument((s + w3)**p*q, w1)) <= w2, + Abs(periodic_argument((polar_lift(s + w3))**p*q, w1)) < w2, + Abs(periodic_argument((polar_lift(s + w3))**p*q, w1)) <= w2) + for c in conds: + a_ = S.Infinity + aux_ = [] + for d in disjuncts(c): + if d.is_Relational and s in d.rhs.free_symbols: + d = d.reversed + if d.is_Relational and isinstance(d, (Ge, Gt)): + d = d.reversedsign + for pat in patterns: + m = d.match(pat) + if m: + break + if m and m[q].is_positive and m[w2]/m[p] == pi/2: + d = -re(s + m[w3]) < 0 + m = d.match(p - cos(w1*Abs(arg(s*w5))*w2)*Abs(s**w3)**w4 < 0) + if not m: + m = d.match( + cos(p - Abs(periodic_argument(s**w1*w5, q))*w2) * + Abs(s**w3)**w4 < 0) + if not m: + m = d.match( + p - cos( + Abs(periodic_argument(polar_lift(s)**w1*w5, q))*w2 + )*Abs(s**w3)**w4 < 0) + if m and all(m[wild].is_positive for wild in [ + w1, w2, w3, w4, w5]): + d = re(s) > m[p] + d_ = d.replace( + re, lambda x: x.expand().as_real_imag()[0]).subs(re(s), t) + if ( + not d.is_Relational or d.rel_op in ('==', '!=') + or d_.has(s) or not d_.has(t)): + aux_ += [d] + continue + soln = _solve_inequality(d_, t) + if not soln.is_Relational or soln.rel_op in ('==', '!='): + aux_ += [d] + continue + if soln.lts == t: + return None + else: + a_ = Min(soln.lts, a_) + if a_ is not S.Infinity: + a = Max(a_, a) + else: + aux = And(aux, Or(*aux_)) + return a, aux.canonical if aux.is_Relational else aux + + conds = [process_conds(c) for c in disjuncts(cond)] + conds2 = [x for x in conds if x[1] != + S.false and x[0] is not S.NegativeInfinity] + if not conds2: + conds2 = [x for x in conds if x[1] != S.false] + conds = list(ordered(conds2)) + + def cnt(expr): + if expr in (True, False): + return 0 + return expr.count_ops() + conds.sort(key=lambda x: (-x[0], cnt(x[1]))) + + if not conds: + return None + a, aux = conds[0] # XXX is [0] always the right one? + + def sbs(expr): + return expr.subs(s, s_) + if simplify: + F = _simplifyconds(F, s, a) + aux = _simplifyconds(aux, s, a) + return _simplify(F.subs(s, s_), simplify), sbs(a), _canonical(sbs(aux)) + + +@DEBUG_WRAP +def _laplace_deep_collect(f, t): + """ + This is an internal helper function that traverses through the expression + tree of `f(t)` and collects arguments. The purpose of it is that + anything like `f(w*t-1*t-c)` will be written as `f((w-1)*t-c)` such that + it can match `f(a*t+b)`. + """ + if not isinstance(f, Expr): + return f + if (p := f.as_poly(t)) is not None: + return p.as_expr() + func = f.func + args = [_laplace_deep_collect(arg, t) for arg in f.args] + return func(*args) + + +@cacheit +def _laplace_build_rules(): + """ + This is an internal helper function that returns the table of Laplace + transform rules in terms of the time variable `t` and the frequency + variable `s`. It is used by ``_laplace_apply_rules``. Each entry is a + tuple containing: + + (time domain pattern, + frequency-domain replacement, + condition for the rule to be applied, + convergence plane, + preparation function) + + The preparation function is a function with one argument that is applied + to the expression before matching. For most rules it should be + ``_laplace_deep_collect``. + """ + t = Dummy('t') + s = Dummy('s') + a = Wild('a', exclude=[t]) + b = Wild('b', exclude=[t]) + n = Wild('n', exclude=[t]) + tau = Wild('tau', exclude=[t]) + omega = Wild('omega', exclude=[t]) + def dco(f): return _laplace_deep_collect(f, t) + _debug('_laplace_build_rules is building rules') + + laplace_transform_rules = [ + (a, a/s, + S.true, S.Zero, dco), # 4.2.1 + (DiracDelta(a*t-b), exp(-s*b/a)/Abs(a), + Or(And(a > 0, b >= 0), And(a < 0, b <= 0)), + S.NegativeInfinity, dco), # Not in Bateman54 + (DiracDelta(a*t-b), S(0), + Or(And(a < 0, b >= 0), And(a > 0, b <= 0)), + S.NegativeInfinity, dco), # Not in Bateman54 + (Heaviside(a*t-b), exp(-s*b/a)/s, + And(a > 0, b > 0), S.Zero, dco), # 4.4.1 + (Heaviside(a*t-b), (1-exp(-s*b/a))/s, + And(a < 0, b < 0), S.Zero, dco), # 4.4.1 + (Heaviside(a*t-b), 1/s, + And(a > 0, b <= 0), S.Zero, dco), # 4.4.1 + (Heaviside(a*t-b), 0, + And(a < 0, b > 0), S.Zero, dco), # 4.4.1 + (t, 1/s**2, + S.true, S.Zero, dco), # 4.2.3 + (1/(a*t+b), -exp(-b/a*s)*Ei(-b/a*s)/a, + Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.6 + (1/sqrt(a*t+b), sqrt(a*pi/s)*exp(b/a*s)*erfc(sqrt(b/a*s))/a, + Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.18 + ((a*t+b)**(-S(3)/2), + 2*b**(-S(1)/2)-2*(pi*s/a)**(S(1)/2)*exp(b/a*s) * erfc(sqrt(b/a*s))/a, + Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.20 + (sqrt(t)/(t+b), sqrt(pi/s)-pi*sqrt(b)*exp(b*s)*erfc(sqrt(b*s)), + Abs(arg(b)) < pi, S.Zero, dco), # 4.2.22 + (1/(a*sqrt(t) + t**(3/2)), pi*a**(S(1)/2)*exp(a*s)*erfc(sqrt(a*s)), + S.true, S.Zero, dco), # Not in Bateman54 + (t**n, gamma(n+1)/s**(n+1), + n > -1, S.Zero, dco), # 4.3.1 + ((a*t+b)**n, uppergamma(n+1, b/a*s)*exp(-b/a*s)/s**(n+1)/a, + And(n > -1, Abs(arg(b/a)) < pi), S.Zero, dco), # 4.3.4 + (t**n/(t+a), a**n*gamma(n+1)*uppergamma(-n, a*s), + And(n > -1, Abs(arg(a)) < pi), S.Zero, dco), # 4.3.7 + (exp(a*t-tau), exp(-tau)/(s-a), + S.true, re(a), dco), # 4.5.1 + (t*exp(a*t-tau), exp(-tau)/(s-a)**2, + S.true, re(a), dco), # 4.5.2 + (t**n*exp(a*t), gamma(n+1)/(s-a)**(n+1), + re(n) > -1, re(a), dco), # 4.5.3 + (exp(-a*t**2), sqrt(pi/4/a)*exp(s**2/4/a)*erfc(s/sqrt(4*a)), + re(a) > 0, S.Zero, dco), # 4.5.21 + (t*exp(-a*t**2), + 1/(2*a)-2/sqrt(pi)/(4*a)**(S(3)/2)*s*erfc(s/sqrt(4*a)), + re(a) > 0, S.Zero, dco), # 4.5.22 + (exp(-a/t), 2*sqrt(a/s)*besselk(1, 2*sqrt(a*s)), + re(a) >= 0, S.Zero, dco), # 4.5.25 + (sqrt(t)*exp(-a/t), + S(1)/2*sqrt(pi/s**3)*(1+2*sqrt(a*s))*exp(-2*sqrt(a*s)), + re(a) >= 0, S.Zero, dco), # 4.5.26 + (exp(-a/t)/sqrt(t), sqrt(pi/s)*exp(-2*sqrt(a*s)), + re(a) >= 0, S.Zero, dco), # 4.5.27 + (exp(-a/t)/(t*sqrt(t)), sqrt(pi/a)*exp(-2*sqrt(a*s)), + re(a) > 0, S.Zero, dco), # 4.5.28 + (t**n*exp(-a/t), 2*(a/s)**((n+1)/2)*besselk(n+1, 2*sqrt(a*s)), + re(a) > 0, S.Zero, dco), # 4.5.29 + # TODO: rules with sqrt(a*t) and sqrt(a/t) have stopped working after + # changes to as_base_exp + # (exp(-2*sqrt(a*t)), + # s**(-1)-sqrt(pi*a)*s**(-S(3)/2)*exp(a/s) * erfc(sqrt(a/s)), + # Abs(arg(a)) < pi, S.Zero, dco), # 4.5.31 + # (exp(-2*sqrt(a*t))/sqrt(t), (pi/s)**(S(1)/2)*exp(a/s)*erfc(sqrt(a/s)), + # Abs(arg(a)) < pi, S.Zero, dco), # 4.5.33 + (exp(-a*exp(-t)), a**(-s)*lowergamma(s, a), + S.true, S.Zero, dco), # 4.5.36 + (exp(-a*exp(t)), a**s*uppergamma(-s, a), + re(a) > 0, S.Zero, dco), # 4.5.37 + (log(a*t), -log(exp(S.EulerGamma)*s/a)/s, + a > 0, S.Zero, dco), # 4.6.1 + (log(1+a*t), -exp(s/a)/s*Ei(-s/a), + Abs(arg(a)) < pi, S.Zero, dco), # 4.6.4 + (log(a*t+b), (log(b)-exp(s/b/a)/s*a*Ei(-s/b))/s*a, + And(a > 0, Abs(arg(b)) < pi), S.Zero, dco), # 4.6.5 + (log(t)/sqrt(t), -sqrt(pi/s)*log(4*s*exp(S.EulerGamma)), + S.true, S.Zero, dco), # 4.6.9 + (t**n*log(t), gamma(n+1)*s**(-n-1)*(digamma(n+1)-log(s)), + re(n) > -1, S.Zero, dco), # 4.6.11 + (log(a*t)**2, (log(exp(S.EulerGamma)*s/a)**2+pi**2/6)/s, + a > 0, S.Zero, dco), # 4.6.13 + (sin(omega*t), omega/(s**2+omega**2), + S.true, Abs(im(omega)), dco), # 4,7,1 + (Abs(sin(omega*t)), omega/(s**2+omega**2)*coth(pi*s/2/omega), + omega > 0, S.Zero, dco), # 4.7.2 + (sin(omega*t)/t, atan(omega/s), + S.true, Abs(im(omega)), dco), # 4.7.16 + (sin(omega*t)**2/t, log(1+4*omega**2/s**2)/4, + S.true, 2*Abs(im(omega)), dco), # 4.7.17 + (sin(omega*t)**2/t**2, + omega*atan(2*omega/s)-s*log(1+4*omega**2/s**2)/4, + S.true, 2*Abs(im(omega)), dco), # 4.7.20 + # (sin(2*sqrt(a*t)), sqrt(pi*a)/s/sqrt(s)*exp(-a/s), + # S.true, S.Zero, dco), # 4.7.32 + # (sin(2*sqrt(a*t))/t, pi*erf(sqrt(a/s)), + # S.true, S.Zero, dco), # 4.7.34 + (cos(omega*t), s/(s**2+omega**2), + S.true, Abs(im(omega)), dco), # 4.7.43 + (cos(omega*t)**2, (s**2+2*omega**2)/(s**2+4*omega**2)/s, + S.true, 2*Abs(im(omega)), dco), # 4.7.45 + # (sqrt(t)*cos(2*sqrt(a*t)), sqrt(pi)/2*s**(-S(5)/2)*(s-2*a)*exp(-a/s), + # S.true, S.Zero, dco), # 4.7.66 + # (cos(2*sqrt(a*t))/sqrt(t), sqrt(pi/s)*exp(-a/s), + # S.true, S.Zero, dco), # 4.7.67 + (sin(a*t)*sin(b*t), 2*a*b*s/(s**2+(a+b)**2)/(s**2+(a-b)**2), + S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.78 + (cos(a*t)*sin(b*t), b*(s**2-a**2+b**2)/(s**2+(a+b)**2)/(s**2+(a-b)**2), + S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.79 + (cos(a*t)*cos(b*t), s*(s**2+a**2+b**2)/(s**2+(a+b)**2)/(s**2+(a-b)**2), + S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.80 + (sinh(a*t), a/(s**2-a**2), + S.true, Abs(re(a)), dco), # 4.9.1 + (cosh(a*t), s/(s**2-a**2), + S.true, Abs(re(a)), dco), # 4.9.2 + (sinh(a*t)**2, 2*a**2/(s**3-4*a**2*s), + S.true, 2*Abs(re(a)), dco), # 4.9.3 + (cosh(a*t)**2, (s**2-2*a**2)/(s**3-4*a**2*s), + S.true, 2*Abs(re(a)), dco), # 4.9.4 + (sinh(a*t)/t, log((s+a)/(s-a))/2, + S.true, Abs(re(a)), dco), # 4.9.12 + (t**n*sinh(a*t), gamma(n+1)/2*((s-a)**(-n-1)-(s+a)**(-n-1)), + n > -2, Abs(a), dco), # 4.9.18 + (t**n*cosh(a*t), gamma(n+1)/2*((s-a)**(-n-1)+(s+a)**(-n-1)), + n > -1, Abs(a), dco), # 4.9.19 + # TODO + # (sinh(2*sqrt(a*t)), sqrt(pi*a)/s/sqrt(s)*exp(a/s), + # S.true, S.Zero, dco), # 4.9.34 + # (cosh(2*sqrt(a*t)), 1/s+sqrt(pi*a)/s/sqrt(s)*exp(a/s)*erf(sqrt(a/s)), + # S.true, S.Zero, dco), # 4.9.35 + # ( + # sqrt(t)*sinh(2*sqrt(a*t)), + # pi**(S(1)/2)*s**(-S(5)/2)*(s/2+a) * + # exp(a/s)*erf(sqrt(a/s))-a**(S(1)/2)*s**(-2), + # S.true, S.Zero, dco), # 4.9.36 + # (sqrt(t)*cosh(2*sqrt(a*t)), pi**(S(1)/2)*s**(-S(5)/2)*(s/2+a)*exp(a/s), + # S.true, S.Zero, dco), # 4.9.37 + # (sinh(2*sqrt(a*t))/sqrt(t), + # pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s) * erf(sqrt(a/s)), + # S.true, S.Zero, dco), # 4.9.38 + # (cosh(2*sqrt(a*t))/sqrt(t), pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s), + # S.true, S.Zero, dco), # 4.9.39 + # (sinh(sqrt(a*t))**2/sqrt(t), pi**(S(1)/2)/2*s**(-S(1)/2)*(exp(a/s)-1), + # S.true, S.Zero, dco), # 4.9.40 + # (cosh(sqrt(a*t))**2/sqrt(t), pi**(S(1)/2)/2*s**(-S(1)/2)*(exp(a/s)+1), + # S.true, S.Zero, dco), # 4.9.41 + (erf(a*t), exp(s**2/(2*a)**2)*erfc(s/(2*a))/s, + 4*Abs(arg(a)) < pi, S.Zero, dco), # 4.12.2 + # (erf(sqrt(a*t)), sqrt(a)/sqrt(s+a)/s, + # S.true, Max(S.Zero, -re(a)), dco), # 4.12.4 + # (exp(a*t)*erf(sqrt(a*t)), sqrt(a)/sqrt(s)/(s-a), + # S.true, Max(S.Zero, re(a)), dco), # 4.12.5 + # (erf(sqrt(a/t)/2), (1-exp(-sqrt(a*s)))/s, + # re(a) > 0, S.Zero, dco), # 4.12.6 + # (erfc(sqrt(a*t)), (sqrt(s+a)-sqrt(a))/sqrt(s+a)/s, + # S.true, -re(a), dco), # 4.12.9 + # (exp(a*t)*erfc(sqrt(a*t)), 1/(s+sqrt(a*s)), + # S.true, S.Zero, dco), # 4.12.10 + # (erfc(sqrt(a/t)/2), exp(-sqrt(a*s))/s, + # re(a) > 0, S.Zero, dco), # 4.2.11 + (besselj(n, a*t), a**n/(sqrt(s**2+a**2)*(s+sqrt(s**2+a**2))**n), + re(n) > -1, Abs(im(a)), dco), # 4.14.1 + (t**b*besselj(n, a*t), + 2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2+a**2)**(-n-S.Half), + And(re(n) > -S.Half, Eq(b, n)), Abs(im(a)), dco), # 4.14.7 + (t**b*besselj(n, a*t), + 2**(n+1)/sqrt(pi)*gamma(n+S(3)/2)*a**n*s*(s**2+a**2)**(-n-S(3)/2), + And(re(n) > -1, Eq(b, n+1)), Abs(im(a)), dco), # 4.14.8 + # (besselj(0, 2*sqrt(a*t)), exp(-a/s)/s, + # S.true, S.Zero, dco), # 4.14.25 + # (t**(b)*besselj(n, 2*sqrt(a*t)), a**(n/2)*s**(-n-1)*exp(-a/s), + # And(re(n) > -1, Eq(b, n*S.Half)), S.Zero, dco), # 4.14.30 + (besselj(0, a*sqrt(t**2+b*t)), + exp(b*s-b*sqrt(s**2+a**2))/sqrt(s**2+a**2), + Abs(arg(b)) < pi, Abs(im(a)), dco), # 4.15.19 + (besseli(n, a*t), a**n/(sqrt(s**2-a**2)*(s+sqrt(s**2-a**2))**n), + re(n) > -1, Abs(re(a)), dco), # 4.16.1 + (t**b*besseli(n, a*t), + 2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2-a**2)**(-n-S.Half), + And(re(n) > -S.Half, Eq(b, n)), Abs(re(a)), dco), # 4.16.6 + (t**b*besseli(n, a*t), + 2**(n+1)/sqrt(pi)*gamma(n+S(3)/2)*a**n*s*(s**2-a**2)**(-n-S(3)/2), + And(re(n) > -1, Eq(b, n+1)), Abs(re(a)), dco), # 4.16.7 + # (t**(b)*besseli(n, 2*sqrt(a*t)), a**(n/2)*s**(-n-1)*exp(a/s), + # And(re(n) > -1, Eq(b, n*S.Half)), S.Zero, dco), # 4.16.18 + (bessely(0, a*t), -2/pi*asinh(s/a)/sqrt(s**2+a**2), + S.true, Abs(im(a)), dco), # 4.15.44 + (besselk(0, a*t), log((s + sqrt(s**2-a**2))/a)/(sqrt(s**2-a**2)), + S.true, -re(a), dco) # 4.16.23 + ] + return laplace_transform_rules, t, s + + +@DEBUG_WRAP +def _laplace_rule_timescale(f, t, s): + """ + This function applies the time-scaling rule of the Laplace transform in + a straight-forward way. For example, if it gets ``(f(a*t), t, s)``, it will + compute ``LaplaceTransform(f(t)/a, t, s/a)`` if ``a>0``. + """ + + a = Wild('a', exclude=[t]) + g = WildFunction('g', nargs=1) + ma1 = f.match(g) + if ma1: + arg = ma1[g].args[0].collect(t) + ma2 = arg.match(a*t) + if ma2 and ma2[a].is_positive and ma2[a] != 1: + _debug(' rule: time scaling (4.1.4)') + r, pr, cr = _laplace_transform( + 1/ma2[a]*ma1[g].func(t), t, s/ma2[a], simplify=False) + return (r, pr, cr) + return None + + +@DEBUG_WRAP +def _laplace_rule_heaviside(f, t, s): + """ + This function deals with time-shifted Heaviside step functions. If the time + shift is positive, it applies the time-shift rule of the Laplace transform. + For example, if it gets ``(Heaviside(t-a)*f(t), t, s)``, it will compute + ``exp(-a*s)*LaplaceTransform(f(t+a), t, s)``. + + If the time shift is negative, the Heaviside function is simply removed + as it means nothing to the Laplace transform. + + The function does not remove a factor ``Heaviside(t)``; this is done by + the simple rules. + """ + + a = Wild('a', exclude=[t]) + y = Wild('y') + g = Wild('g') + if ma1 := f.match(Heaviside(y) * g): + if ma2 := ma1[y].match(t - a): + if ma2[a].is_positive: + _debug(' rule: time shift (4.1.4)') + r, pr, cr = _laplace_transform( + ma1[g].subs(t, t + ma2[a]), t, s, simplify=False) + return (exp(-ma2[a] * s) * r, pr, cr) + if ma2[a].is_negative: + _debug( + ' rule: Heaviside factor; negative time shift (4.1.4)') + r, pr, cr = _laplace_transform(ma1[g], t, s, simplify=False) + return (r, pr, cr) + if ma2 := ma1[y].match(a - t): + if ma2[a].is_positive: + _debug(' rule: Heaviside window open') + r, pr, cr = _laplace_transform( + (1 - Heaviside(t - ma2[a])) * ma1[g], t, s, simplify=False) + return (r, pr, cr) + if ma2[a].is_negative: + _debug(' rule: Heaviside window closed') + return (0, 0, S.true) + return None + + +@DEBUG_WRAP +def _laplace_rule_exp(f, t, s): + """ + If this function finds a factor ``exp(a*t)``, it applies the + frequency-shift rule of the Laplace transform and adjusts the convergence + plane accordingly. For example, if it gets ``(exp(-a*t)*f(t), t, s)``, it + will compute ``LaplaceTransform(f(t), t, s+a)``. + """ + + a = Wild('a', exclude=[t]) + y = Wild('y') + z = Wild('z') + ma1 = f.match(exp(y)*z) + if ma1: + ma2 = ma1[y].collect(t).match(a*t) + if ma2: + _debug(' rule: multiply with exp (4.1.5)') + r, pr, cr = _laplace_transform(ma1[z], t, s-ma2[a], + simplify=False) + return (r, pr+re(ma2[a]), cr) + return None + + +@DEBUG_WRAP +def _laplace_rule_delta(f, t, s): + """ + If this function finds a factor ``DiracDelta(b*t-a)``, it applies the + masking property of the delta distribution. For example, if it gets + ``(DiracDelta(t-a)*f(t), t, s)``, it will return + ``(f(a)*exp(-a*s), -a, True)``. + """ + # This rule is not in Bateman54 + + a = Wild('a', exclude=[t]) + b = Wild('b', exclude=[t]) + + y = Wild('y') + z = Wild('z') + ma1 = f.match(DiracDelta(y)*z) + if ma1 and not ma1[z].has(DiracDelta): + ma2 = ma1[y].collect(t).match(b*t-a) + if ma2: + _debug(' rule: multiply with DiracDelta') + loc = ma2[a]/ma2[b] + if re(loc) >= 0 and im(loc) == 0: + fn = exp(-ma2[a]/ma2[b]*s)*ma1[z] + if fn.has(sin, cos): + # Then it may be possible that a sinc() is present in the + # term; let's try this: + fn = fn.rewrite(sinc).ratsimp() + n, d = [x.subs(t, ma2[a]/ma2[b]) for x in fn.as_numer_denom()] + if d != 0: + return (n/d/ma2[b], S.NegativeInfinity, S.true) + else: + return None + else: + return (0, S.NegativeInfinity, S.true) + if ma1[y].is_polynomial(t): + ro = roots(ma1[y], t) + if ro != {} and set(ro.values()) == {1}: + slope = diff(ma1[y], t) + r = Add( + *[exp(-x*s)*ma1[z].subs(t, s)/slope.subs(t, x) + for x in list(ro.keys()) if im(x) == 0 and re(x) >= 0]) + return (r, S.NegativeInfinity, S.true) + return None + + +@DEBUG_WRAP +def _laplace_trig_split(fn): + """ + Helper function for `_laplace_rule_trig`. This function returns two terms + `f` and `g`. `f` contains all product terms with sin, cos, sinh, cosh in + them; `g` contains everything else. + """ + trigs = [S.One] + other = [S.One] + for term in Mul.make_args(fn): + if term.has(sin, cos, sinh, cosh, exp): + trigs.append(term) + else: + other.append(term) + f = Mul(*trigs) + g = Mul(*other) + return f, g + + +@DEBUG_WRAP +def _laplace_trig_expsum(f, t): + """ + Helper function for `_laplace_rule_trig`. This function expects the `f` + from `_laplace_trig_split`. It returns two lists `xm` and `xn`. `xm` is + a list of dictionaries with keys `k` and `a` representing a function + `k*exp(a*t)`. `xn` is a list of all terms that cannot be brought into + that form, which may happen, e.g., when a trigonometric function has + another function in its argument. + """ + c1 = Wild('c1', exclude=[t]) + c0 = Wild('c0', exclude=[t]) + p = Wild('p', exclude=[t]) + xm = [] + xn = [] + + x1 = f.rewrite(exp).expand() + + for term in Add.make_args(x1): + if not term.has(t): + xm.append({'k': term, 'a': 0, re: 0, im: 0}) + continue + term = _laplace_deep_collect(term.powsimp(combine='exp'), t) + + if (r := term.match(p*exp(c1*t+c0))) is not None: + xm.append({ + 'k': r[p]*exp(r[c0]), 'a': r[c1], + re: re(r[c1]), im: im(r[c1])}) + else: + xn.append(term) + return xm, xn + + +@DEBUG_WRAP +def _laplace_trig_ltex(xm, t, s): + """ + Helper function for `_laplace_rule_trig`. This function takes the list of + exponentials `xm` from `_laplace_trig_expsum` and simplifies complex + conjugate and real symmetric poles. It returns the result as a sum and + the convergence plane. + """ + results = [] + planes = [] + + def _simpc(coeffs): + nc = coeffs.copy() + for k in range(len(nc)): + ri = nc[k].as_real_imag() + if ri[0].has(im): + nc[k] = nc[k].rewrite(cos) + else: + nc[k] = (ri[0] + I*ri[1]).rewrite(cos) + return nc + + def _quadpole(t1, k1, k2, k3, s): + a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im] + nc = [ + k0 + k1 + k2 + k3, + a*(k0 + k1 - k2 - k3) - 2*I*a_i*k1 + 2*I*a_i*k2, + ( + a**2*(-k0 - k1 - k2 - k3) + + a*(4*I*a_i*k0 + 4*I*a_i*k3) + + 4*a_i**2*k0 + 4*a_i**2*k3), + ( + a**3*(-k0 - k1 + k2 + k3) + + a**2*(4*I*a_i*k0 + 2*I*a_i*k1 - 2*I*a_i*k2 - 4*I*a_i*k3) + + a*(4*a_i**2*k0 - 4*a_i**2*k3)) + ] + dc = [ + S.One, S.Zero, 2*a_i**2 - 2*a_r**2, + S.Zero, a_i**4 + 2*a_i**2*a_r**2 + a_r**4] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _ccpole(t1, k1, s): + a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im] + nc = [k0 + k1, -a*k0 - a*k1 + 2*I*a_i*k0] + dc = [S.One, -2*a_r, a_i**2 + a_r**2] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _rspole(t1, k2, s): + a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im] + nc = [k0 + k2, a*k0 - a*k2 - 2*I*a_i*k0] + dc = [S.One, -2*I*a_i, -a_i**2 - a_r**2] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _sypole(t1, k3, s): + a, k0 = t1['a'], t1['k'] + nc = [k0 + k3, a*(k0 - k3)] + dc = [S.One, S.Zero, -a**2] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _simplepole(t1, s): + a, k0 = t1['a'], t1['k'] + n = k0 + d = s - a + return n/d + + while len(xm) > 0: + t1 = xm.pop() + i_imagsym = None + i_realsym = None + i_pointsym = None + # The following code checks all remaining poles. If t1 is a pole at + # a+b*I, then we check for a-b*I, -a+b*I, and -a-b*I, and + # assign the respective indices to i_imagsym, i_realsym, i_pointsym. + # -a-b*I / i_pointsym only applies if both a and b are != 0. + for i in range(len(xm)): + real_eq = t1[re] == xm[i][re] + realsym = t1[re] == -xm[i][re] + imag_eq = t1[im] == xm[i][im] + imagsym = t1[im] == -xm[i][im] + if realsym and imagsym and t1[re] != 0 and t1[im] != 0: + i_pointsym = i + elif realsym and imag_eq and t1[re] != 0: + i_realsym = i + elif real_eq and imagsym and t1[im] != 0: + i_imagsym = i + + # The next part looks for four possible pole constellations: + # quad: a+b*I, a-b*I, -a+b*I, -a-b*I + # cc: a+b*I, a-b*I (a may be zero) + # quad: a+b*I, -a+b*I (b may be zero) + # point: a+b*I, -a-b*I (a!=0 and b!=0 is needed, but that has been + # asserted when finding i_pointsym above.) + # If none apply, then t1 is a simple pole. + if ( + i_imagsym is not None and i_realsym is not None + and i_pointsym is not None): + results.append( + _quadpole(t1, + xm[i_imagsym]['k'], xm[i_realsym]['k'], + xm[i_pointsym]['k'], s)) + planes.append(Abs(re(t1['a']))) + # The three additional poles have now been used; to pop them + # easily we have to do it from the back. + indices_to_pop = [i_imagsym, i_realsym, i_pointsym] + indices_to_pop.sort(reverse=True) + for i in indices_to_pop: + xm.pop(i) + elif i_imagsym is not None: + results.append(_ccpole(t1, xm[i_imagsym]['k'], s)) + planes.append(t1[re]) + xm.pop(i_imagsym) + elif i_realsym is not None: + results.append(_rspole(t1, xm[i_realsym]['k'], s)) + planes.append(Abs(t1[re])) + xm.pop(i_realsym) + elif i_pointsym is not None: + results.append(_sypole(t1, xm[i_pointsym]['k'], s)) + planes.append(Abs(t1[re])) + xm.pop(i_pointsym) + else: + results.append(_simplepole(t1, s)) + planes.append(t1[re]) + + return Add(*results), Max(*planes) + + +@DEBUG_WRAP +def _laplace_rule_trig(fn, t_, s): + """ + This rule covers trigonometric factors by splitting everything into a + sum of exponential functions and collecting complex conjugate poles and + real symmetric poles. + """ + t = Dummy('t', real=True) + + if not fn.has(sin, cos, sinh, cosh): + return None + + f, g = _laplace_trig_split(fn.subs(t_, t)) + xm, xn = _laplace_trig_expsum(f, t) + + if len(xn) > 0: + # TODO not implemented yet, but also not important + return None + + if not g.has(t): + r, p = _laplace_trig_ltex(xm, t, s) + return g*r, p, S.true + else: + # Just transform `g` and make frequency-shifted copies + planes = [] + results = [] + G, G_plane, G_cond = _laplace_transform(g, t, s, simplify=False) + for x1 in xm: + results.append(x1['k']*G.subs(s, s-x1['a'])) + planes.append(G_plane+re(x1['a'])) + return Add(*results).subs(t, t_), Max(*planes), G_cond + + +@DEBUG_WRAP +def _laplace_rule_diff(f, t, s): + """ + This function looks for derivatives in the time domain and replaces it + by factors of `s` and initial conditions in the frequency domain. For + example, if it gets ``(diff(f(t), t), t, s)``, it will compute + ``s*LaplaceTransform(f(t), t, s) - f(0)``. + """ + + a = Wild('a', exclude=[t]) + n = Wild('n', exclude=[t]) + g = WildFunction('g') + ma1 = f.match(a*Derivative(g, (t, n))) + if ma1 and ma1[n].is_integer: + m = [z.has(t) for z in ma1[g].args] + if sum(m) == 1: + _debug(' rule: time derivative (4.1.8)') + d = [] + for k in range(ma1[n]): + if k == 0: + y = ma1[g].subs(t, 0) + else: + y = Derivative(ma1[g], (t, k)).subs(t, 0) + d.append(s**(ma1[n]-k-1)*y) + r, pr, cr = _laplace_transform(ma1[g], t, s, simplify=False) + return (ma1[a]*(s**ma1[n]*r - Add(*d)), pr, cr) + return None + + +@DEBUG_WRAP +def _laplace_rule_sdiff(f, t, s): + """ + This function looks for multiplications with polynoimials in `t` as they + correspond to differentiation in the frequency domain. For example, if it + gets ``(t*f(t), t, s)``, it will compute + ``-Derivative(LaplaceTransform(f(t), t, s), s)``. + """ + + if f.is_Mul: + pfac = [1] + ofac = [1] + for fac in Mul.make_args(f): + if fac.is_polynomial(t): + pfac.append(fac) + else: + ofac.append(fac) + if len(pfac) > 1: + pex = prod(pfac) + pc = Poly(pex, t).all_coeffs() + N = len(pc) + if N > 1: + oex = prod(ofac) + r_, p_, c_ = _laplace_transform(oex, t, s, simplify=False) + deri = [r_] + d1 = False + try: + d1 = -diff(deri[-1], s) + except ValueError: + d1 = False + if r_.has(LaplaceTransform): + for k in range(N-1): + deri.append((-1)**(k+1)*Derivative(r_, s, k+1)) + elif d1: + deri.append(d1) + for k in range(N-2): + deri.append(-diff(deri[-1], s)) + if d1: + r = Add(*[pc[N-n-1]*deri[n] for n in range(N)]) + return (r, p_, c_) + # We still have to cover the possibility that there is a symbolic positive + # integer exponent. + n = Wild('n', exclude=[t]) + g = Wild('g') + if ma1 := f.match(t**n*g): + if ma1[n].is_integer and ma1[n].is_positive: + r_, p_, c_ = _laplace_transform(ma1[g], t, s, simplify=False) + return (-1)**ma1[n]*diff(r_, (s, ma1[n])), p_, c_ + return None + + +@DEBUG_WRAP +def _laplace_expand(f, t, s): + """ + This function tries to expand its argument with successively stronger + methods: first it will expand on the top level, then it will expand any + multiplications in depth, then it will try all available expansion methods, + and finally it will try to expand trigonometric functions. + + If it can expand, it will then compute the Laplace transform of the + expanded term. + """ + + r = expand(f, deep=False) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + r = expand_mul(f) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + r = expand(f) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + if r != f: + return _laplace_transform(r, t, s, simplify=False) + r = expand(expand_trig(f)) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + return None + + +@DEBUG_WRAP +def _laplace_apply_prog_rules(f, t, s): + """ + This function applies all program rules and returns the result if one + of them gives a result. + """ + + prog_rules = [_laplace_rule_heaviside, _laplace_rule_delta, + _laplace_rule_timescale, _laplace_rule_exp, + _laplace_rule_trig, + _laplace_rule_diff, _laplace_rule_sdiff] + + for p_rule in prog_rules: + if (L := p_rule(f, t, s)) is not None: + return L + return None + + +@DEBUG_WRAP +def _laplace_apply_simple_rules(f, t, s): + """ + This function applies all simple rules and returns the result if one + of them gives a result. + """ + simple_rules, t_, s_ = _laplace_build_rules() + prep_old = '' + prep_f = '' + for t_dom, s_dom, check, plane, prep in simple_rules: + if prep_old != prep: + prep_f = prep(f.subs({t: t_})) + prep_old = prep + ma = prep_f.match(t_dom) + if ma: + try: + c = check.xreplace(ma) + except TypeError: + # This may happen if the time function has imaginary + # numbers in it. Then we give up. + continue + if c == S.true: + return (s_dom.xreplace(ma).subs({s_: s}), + plane.xreplace(ma), S.true) + return None + + +@DEBUG_WRAP +def _piecewise_to_heaviside(f, t): + """ + This function converts a Piecewise expression to an expression written + with Heaviside. It is not exact, but valid in the context of the Laplace + transform. + """ + if not t.is_real: + r = Dummy('r', real=True) + return _piecewise_to_heaviside(f.xreplace({t: r}), r).xreplace({r: t}) + x = piecewise_exclusive(f) + r = [] + for fn, cond in x.args: + # Here we do not need to do many checks because piecewise_exclusive + # has a clearly predictable output. However, if any of the conditions + # is not relative to t, this function just returns the input argument. + if isinstance(cond, Relational) and t in cond.args: + if isinstance(cond, (Eq, Ne)): + # We do not cover this case; these would be single-point + # exceptions that do not play a role in Laplace practice, + # except if they contain Dirac impulses, and then we can + # expect users to not try to use Piecewise for writing it. + return f + else: + r.append(Heaviside(cond.gts - cond.lts)*fn) + elif isinstance(cond, Or) and len(cond.args) == 2: + # Or(t<2, t>4), Or(t>4, t<=2), ... in any order with any <= >= + for c2 in cond.args: + if c2.lhs == t: + r.append(Heaviside(c2.gts - c2.lts)*fn) + else: + return f + elif isinstance(cond, And) and len(cond.args) == 2: + # And(t>2, t<4), And(t>4, t<=2), ... in any order with any <= >= + c0, c1 = cond.args + if c0.lhs == t and c1.lhs == t: + if '>' in c0.rel_op: + c0, c1 = c1, c0 + r.append( + (Heaviside(c1.gts - c1.lts) - + Heaviside(c0.lts - c0.gts))*fn) + else: + return f + else: + return f + return Add(*r) + + +def laplace_correspondence(f, fdict, /): + """ + This helper function takes a function `f` that is the result of a + ``laplace_transform`` or an ``inverse_laplace_transform``. It replaces all + unevaluated ``LaplaceTransform(y(t), t, s)`` by `Y(s)` for any `s` and + all ``InverseLaplaceTransform(Y(s), s, t)`` by `y(t)` for any `t` if + ``fdict`` contains a correspondence ``{y: Y}``. + + Parameters + ========== + + f : sympy expression + Expression containing unevaluated ``LaplaceTransform`` or + ``LaplaceTransform`` objects. + fdict : dictionary + Dictionary containing one or more function correspondences, + e.g., ``{x: X, y: Y}`` meaning that ``X`` and ``Y`` are the + Laplace transforms of ``x`` and ``y``, respectively. + + Examples + ======== + + >>> from sympy import laplace_transform, diff, Function + >>> from sympy import laplace_correspondence, inverse_laplace_transform + >>> from sympy.abc import t, s + >>> y = Function("y") + >>> Y = Function("Y") + >>> z = Function("z") + >>> Z = Function("Z") + >>> f = laplace_transform(diff(y(t), t, 1) + z(t), t, s, noconds=True) + >>> laplace_correspondence(f, {y: Y, z: Z}) + s*Y(s) + Z(s) - y(0) + >>> f = inverse_laplace_transform(Y(s), s, t) + >>> laplace_correspondence(f, {y: Y}) + y(t) + """ + p = Wild('p') + s = Wild('s') + t = Wild('t') + a = Wild('a') + if ( + not isinstance(f, Expr) + or (not f.has(LaplaceTransform) + and not f.has(InverseLaplaceTransform))): + return f + for y, Y in fdict.items(): + if ( + (m := f.match(LaplaceTransform(y(a), t, s))) is not None + and m[a] == m[t]): + return Y(m[s]) + if ( + (m := f.match(InverseLaplaceTransform(Y(a), s, t, p))) + is not None + and m[a] == m[s]): + return y(m[t]) + func = f.func + args = [laplace_correspondence(arg, fdict) for arg in f.args] + return func(*args) + + +def laplace_initial_conds(f, t, fdict, /): + """ + This helper function takes a function `f` that is the result of a + ``laplace_transform``. It takes an fdict of the form ``{y: [1, 4, 2]}``, + where the values in the list are the initial value, the initial slope, the + initial second derivative, etc., of the function `y(t)`, and replaces all + unevaluated initial conditions. + + Parameters + ========== + + f : sympy expression + Expression containing initial conditions of unevaluated functions. + t : sympy expression + Variable for which the initial conditions are to be applied. + fdict : dictionary + Dictionary containing a list of initial conditions for every + function, e.g., ``{y: [0, 1, 2], x: [3, 4, 5]}``. The order + of derivatives is ascending, so `0`, `1`, `2` are `y(0)`, `y'(0)`, + and `y''(0)`, respectively. + + Examples + ======== + + >>> from sympy import laplace_transform, diff, Function + >>> from sympy import laplace_correspondence, laplace_initial_conds + >>> from sympy.abc import t, s + >>> y = Function("y") + >>> Y = Function("Y") + >>> f = laplace_transform(diff(y(t), t, 3), t, s, noconds=True) + >>> g = laplace_correspondence(f, {y: Y}) + >>> laplace_initial_conds(g, t, {y: [2, 4, 8, 16, 32]}) + s**3*Y(s) - 2*s**2 - 4*s - 8 + """ + for y, ic in fdict.items(): + for k in range(len(ic)): + if k == 0: + f = f.replace(y(0), ic[0]) + elif k == 1: + f = f.replace(Subs(Derivative(y(t), t), t, 0), ic[1]) + else: + f = f.replace(Subs(Derivative(y(t), (t, k)), t, 0), ic[k]) + return f + + +@DEBUG_WRAP +def _laplace_transform(fn, t_, s_, *, simplify): + """ + Front-end function of the Laplace transform. It tries to apply all known + rules recursively, and if everything else fails, it tries to integrate. + """ + + terms_t = Add.make_args(fn) + terms_s = [] + terms = [] + planes = [] + conditions = [] + + for ff in terms_t: + k, ft = ff.as_independent(t_, as_Add=False) + if ft.has(SingularityFunction): + _terms = Add.make_args(ft.rewrite(Heaviside)) + for _term in _terms: + k1, f1 = _term.as_independent(t_, as_Add=False) + terms.append((k*k1, f1)) + elif ft.func == Piecewise and not ft.has(DiracDelta(t_)): + _terms = Add.make_args(_piecewise_to_heaviside(ft, t_)) + for _term in _terms: + k1, f1 = _term.as_independent(t_, as_Add=False) + terms.append((k*k1, f1)) + else: + terms.append((k, ft)) + + for k, ft in terms: + if ft.has(SingularityFunction): + r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True) + else: + if ft.has(Heaviside(t_)) and not ft.has(DiracDelta(t_)): + # For t>=0, Heaviside(t)=1 can be used, except if there is also + # a DiracDelta(t) present, in which case removing Heaviside(t) + # is unnecessary because _laplace_rule_delta can deal with it. + ft = ft.subs(Heaviside(t_), 1) + if ( + (r := _laplace_apply_simple_rules(ft, t_, s_)) + is not None or + (r := _laplace_apply_prog_rules(ft, t_, s_)) + is not None or + (r := _laplace_expand(ft, t_, s_)) is not None): + pass + elif any(undef.has(t_) for undef in ft.atoms(AppliedUndef)): + # If there are undefined functions f(t) then integration is + # unlikely to do anything useful so we skip it and given an + # unevaluated LaplaceTransform. + r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True) + elif (r := _laplace_transform_integration( + ft, t_, s_, simplify=simplify)) is not None: + pass + else: + r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True) + (ri_, pi_, ci_) = r + terms_s.append(k*ri_) + planes.append(pi_) + conditions.append(ci_) + + result = Add(*terms_s) + if simplify: + result = result.simplify(doit=False) + plane = Max(*planes) + condition = And(*conditions) + + return result, plane, condition + + +class LaplaceTransform(IntegralTransform): + """ + Class representing unevaluated Laplace transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Laplace transforms, see the :func:`laplace_transform` + docstring. + + If this is called with ``.doit()``, it returns the Laplace transform as an + expression. If it is called with ``.doit(noconds=False)``, it returns a + tuple containing the same expression, a convergence plane, and conditions. + """ + + _name = 'Laplace' + + def _compute_transform(self, f, t, s, **hints): + _simplify = hints.get('simplify', False) + LT = _laplace_transform_integration(f, t, s, simplify=_simplify) + return LT + + def _as_integral(self, f, t, s): + return Integral(f*exp(-s*t), (t, S.Zero, S.Infinity)) + + def doit(self, **hints): + """ + Try to evaluate the transform in closed form. + + Explanation + =========== + + Standard hints are the following: + - ``noconds``: if True, do not return convergence conditions. The + default setting is `True`. + - ``simplify``: if True, it simplifies the final result. The + default setting is `False`. + """ + _noconds = hints.get('noconds', True) + _simplify = hints.get('simplify', False) + + debugf('[LT doit] (%s, %s, %s)', (self.function, + self.function_variable, + self.transform_variable)) + + t_ = self.function_variable + s_ = self.transform_variable + fn = self.function + + r = _laplace_transform(fn, t_, s_, simplify=_simplify) + + if _noconds: + return r[0] + else: + return r + + +def laplace_transform(f, t, s, legacy_matrix=True, **hints): + r""" + Compute the Laplace Transform `F(s)` of `f(t)`, + + .. math :: F(s) = \int_{0^{-}}^\infty e^{-st} f(t) \mathrm{d}t. + + Explanation + =========== + + For all sensible functions, this converges absolutely in a + half-plane + + .. math :: a < \operatorname{Re}(s) + + This function returns ``(F, a, cond)`` where ``F`` is the Laplace + transform of ``f``, `a` is the half-plane of convergence, and `cond` are + auxiliary convergence conditions. + + The implementation is rule-based, and if you are interested in which + rules are applied, and whether integration is attempted, you can switch + debug information on by setting ``sympy.SYMPY_DEBUG=True``. The numbers + of the rules in the debug information (and the code) refer to Bateman's + Tables of Integral Transforms [1]. + + The lower bound is `0-`, meaning that this bound should be approached + from the lower side. This is only necessary if distributions are involved. + At present, it is only done if `f(t)` contains ``DiracDelta``, in which + case the Laplace transform is computed implicitly as + + .. math :: + F(s) = \lim_{\tau\to 0^{-}} \int_{\tau}^\infty e^{-st} + f(t) \mathrm{d}t + + by applying rules. + + If the Laplace transform cannot be fully computed in closed form, this + function returns expressions containing unevaluated + :class:`LaplaceTransform` objects. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. If + ``noconds=True``, only `F` will be returned (i.e. not ``cond``, and also + not the plane ``a``). + + .. deprecated:: 1.9 + Legacy behavior for matrices where ``laplace_transform`` with + ``noconds=False`` (the default) returns a Matrix whose elements are + tuples. The behavior of ``laplace_transform`` for matrices will change + in a future release of SymPy to return a tuple of the transformed + Matrix and the convergence conditions for the matrix as a whole. Use + ``legacy_matrix=False`` to enable the new behavior. + + Examples + ======== + + >>> from sympy import DiracDelta, exp, laplace_transform + >>> from sympy.abc import t, s, a + >>> laplace_transform(t**4, t, s) + (24/s**5, 0, True) + >>> laplace_transform(t**a, t, s) + (gamma(a + 1)/(s*s**a), 0, re(a) > -1) + >>> laplace_transform(DiracDelta(t)-a*exp(-a*t), t, s, simplify=True) + (s/(a + s), -re(a), True) + + There are also helper functions that make it easy to solve differential + equations by Laplace transform. For example, to solve + + .. math :: m x''(t) + d x'(t) + k x(t) = 0 + + with initial value `0` and initial derivative `v`: + + >>> from sympy import Function, laplace_correspondence, diff, solve + >>> from sympy import laplace_initial_conds, inverse_laplace_transform + >>> from sympy.abc import d, k, m, v + >>> x = Function('x') + >>> X = Function('X') + >>> f = m*diff(x(t), t, 2) + d*diff(x(t), t) + k*x(t) + >>> F = laplace_transform(f, t, s, noconds=True) + >>> F = laplace_correspondence(F, {x: X}) + >>> F = laplace_initial_conds(F, t, {x: [0, v]}) + >>> F + d*s*X(s) + k*X(s) + m*(s**2*X(s) - v) + >>> Xs = solve(F, X(s))[0] + >>> Xs + m*v/(d*s + k + m*s**2) + >>> inverse_laplace_transform(Xs, s, t) + 2*v*exp(-d*t/(2*m))*sin(t*sqrt((-d**2 + 4*k*m)/m**2)/2)*Heaviside(t)/sqrt((-d**2 + 4*k*m)/m**2) + + References + ========== + + .. [1] Erdelyi, A. (ed.), Tables of Integral Transforms, Volume 1, + Bateman Manuscript Prooject, McGraw-Hill (1954), available: + https://resolver.caltech.edu/CaltechAUTHORS:20140123-101456353 + + See Also + ======== + + inverse_laplace_transform, mellin_transform, fourier_transform + hankel_transform, inverse_hankel_transform + + """ + + _noconds = hints.get('noconds', False) + _simplify = hints.get('simplify', False) + + if isinstance(f, MatrixBase) and hasattr(f, 'applyfunc'): + + conds = not hints.get('noconds', False) + + if conds and legacy_matrix: + adt = 'deprecated-laplace-transform-matrix' + sympy_deprecation_warning( + """ +Calling laplace_transform() on a Matrix with noconds=False (the default) is +deprecated. Either noconds=True or use legacy_matrix=False to get the new +behavior. + """, + deprecated_since_version='1.9', + active_deprecations_target=adt, + ) + # Temporarily disable the deprecation warning for non-Expr objects + # in Matrix + with ignore_warnings(SymPyDeprecationWarning): + return f.applyfunc( + lambda fij: laplace_transform(fij, t, s, **hints)) + else: + elements_trans = [laplace_transform( + fij, t, s, **hints) for fij in f] + if conds: + elements, avals, conditions = zip(*elements_trans) + f_laplace = type(f)(*f.shape, elements) + return f_laplace, Max(*avals), And(*conditions) + else: + return type(f)(*f.shape, elements_trans) + + LT, p, c = LaplaceTransform(f, t, s).doit(noconds=False, + simplify=_simplify) + + if not _noconds: + return LT, p, c + else: + return LT + + +@DEBUG_WRAP +def _inverse_laplace_transform_integration(F, s, t_, plane, *, simplify): + """ The backend function for inverse Laplace transforms. """ + from sympy.integrals.meijerint import meijerint_inversion, _get_coeff_exp + from sympy.integrals.transforms import inverse_mellin_transform + + # There are two strategies we can try: + # 1) Use inverse mellin transform, related by a simple change of variables. + # 2) Use the inversion integral. + + t = Dummy('t', real=True) + + def pw_simp(*args): + """ Simplify a piecewise expression from hyperexpand. """ + if len(args) != 3: + return Piecewise(*args) + arg = args[2].args[0].argument + coeff, exponent = _get_coeff_exp(arg, t) + e1 = args[0].args[0] + e2 = args[1].args[0] + return ( + Heaviside(1/Abs(coeff) - t**exponent)*e1 + + Heaviside(t**exponent - 1/Abs(coeff))*e2) + + if F.is_rational_function(s): + F = F.apart(s) + + if F.is_Add: + f = Add( + *[_inverse_laplace_transform_integration(X, s, t, plane, simplify) + for X in F.args]) + return _simplify(f.subs(t, t_), simplify), True + + try: + f, cond = inverse_mellin_transform(F, s, exp(-t), (None, S.Infinity), + needeval=True, noconds=False) + except IntegralTransformError: + f = None + + if f is None: + f = meijerint_inversion(F, s, t) + if f is None: + return None + if f.is_Piecewise: + f, cond = f.args[0] + if f.has(Integral): + return None + else: + cond = S.true + f = f.replace(Piecewise, pw_simp) + + if f.is_Piecewise: + # many of the functions called below can't work with piecewise + # (b/c it has a bool in args) + return f.subs(t, t_), cond + + u = Dummy('u') + + def simp_heaviside(arg, H0=S.Half): + a = arg.subs(exp(-t), u) + if a.has(t): + return Heaviside(arg, H0) + from sympy.solvers.inequalities import _solve_inequality + rel = _solve_inequality(a > 0, u) + if rel.lts == u: + k = log(rel.gts) + return Heaviside(t + k, H0) + else: + k = log(rel.lts) + return Heaviside(-(t + k), H0) + + f = f.replace(Heaviside, simp_heaviside) + + def simp_exp(arg): + return expand_complex(exp(arg)) + + f = f.replace(exp, simp_exp) + + return _simplify(f.subs(t, t_), simplify), cond + + +@DEBUG_WRAP +def _complete_the_square_in_denom(f, s): + from sympy.simplify.radsimp import fraction + [n, d] = fraction(f) + if d.is_polynomial(s): + cf = d.as_poly(s).all_coeffs() + if len(cf) == 3: + a, b, c = cf + d = a*((s+b/(2*a))**2+c/a-(b/(2*a))**2) + return n/d + + +@cacheit +def _inverse_laplace_build_rules(): + """ + This is an internal helper function that returns the table of inverse + Laplace transform rules in terms of the time variable `t` and the + frequency variable `s`. It is used by `_inverse_laplace_apply_rules`. + """ + s = Dummy('s') + t = Dummy('t') + a = Wild('a', exclude=[s]) + b = Wild('b', exclude=[s]) + c = Wild('c', exclude=[s]) + + _debug('_inverse_laplace_build_rules is building rules') + + def _frac(f, s): + try: + return f.factor(s) + except PolynomialError: + return f + + def same(f): return f + # This list is sorted according to the prep function needed. + _ILT_rules = [ + (a/s, a, S.true, same, 1), + ( + b*(s+a)**(-c), t**(c-1)*exp(-a*t)/gamma(c), + S.true, same, 1), + (1/(s**2+a**2)**2, (sin(a*t) - a*t*cos(a*t))/(2*a**3), + S.true, same, 1), + # The next two rules must be there in that order. For the second + # one, the condition would be a != 0 or, respectively, to take the + # limit a -> 0 after the transform if a == 0. It is much simpler if + # the case a == 0 has its own rule. + (1/(s**b), t**(b - 1)/gamma(b), S.true, same, 1), + (1/(s*(s+a)**b), lowergamma(b, a*t)/(a**b*gamma(b)), + S.true, same, 1) + ] + return _ILT_rules, s, t + + +@DEBUG_WRAP +def _inverse_laplace_apply_simple_rules(f, s, t): + """ + Helper function for the class InverseLaplaceTransform. + """ + if f == 1: + _debug(' rule: 1 o---o DiracDelta()') + return DiracDelta(t), S.true + + _ILT_rules, s_, t_ = _inverse_laplace_build_rules() + _prep = '' + fsubs = f.subs({s: s_}) + + for s_dom, t_dom, check, prep, fac in _ILT_rules: + if _prep != (prep, fac): + _F = prep(fsubs*fac) + _prep = (prep, fac) + ma = _F.match(s_dom) + if ma: + c = check + if c is not S.true: + args = [x.xreplace(ma) for x in c[0]] + c = c[1](*args) + if c == S.true: + return Heaviside(t)*t_dom.xreplace(ma).subs({t_: t}), S.true + + return None + + +@DEBUG_WRAP +def _inverse_laplace_diff(f, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + a = Wild('a', exclude=[s]) + n = Wild('n', exclude=[s]) + g = Wild('g') + ma = f.match(a*Derivative(g, (s, n))) + if ma and ma[n].is_integer: + _debug(' rule: t**n*f(t) o---o (-1)**n*diff(F(s), s, n)') + r, c = _inverse_laplace_transform( + ma[g], s, t, plane, simplify=False, dorational=False) + return (-t)**ma[n]*r, c + return None + + +@DEBUG_WRAP +def _inverse_laplace_time_shift(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + a = Wild('a', exclude=[s]) + g = Wild('g') + + if not F.has(s): + return F*DiracDelta(t), S.true + if not F.has(exp): + return None + + ma1 = F.match(exp(a*s)) + if ma1: + if ma1[a].is_negative: + _debug(' rule: exp(-a*s) o---o DiracDelta(t-a)') + return DiracDelta(t+ma1[a]), S.true + else: + return InverseLaplaceTransform(F, s, t, plane), S.true + + ma1 = F.match(exp(a*s)*g) + if ma1: + if ma1[a].is_negative: + _debug(' rule: exp(-a*s)*F(s) o---o Heaviside(t-a)*f(t-a)') + return _inverse_laplace_transform( + ma1[g], s, t+ma1[a], plane, simplify=False, dorational=True) + else: + return InverseLaplaceTransform(F, s, t, plane), S.true + return None + + +@DEBUG_WRAP +def _inverse_laplace_freq_shift(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + if not F.has(s): + return F*DiracDelta(t), S.true + if len(args := F.args) == 1: + a = Wild('a', exclude=[s]) + if (ma := args[0].match(s-a)) and re(ma[a]).is_positive: + _debug(' rule: F(s-a) o---o exp(-a*t)*f(t)') + return ( + exp(-ma[a]*t) * + InverseLaplaceTransform(F.func(s), s, t, plane), S.true) + return None + + +@DEBUG_WRAP +def _inverse_laplace_time_diff(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + n = Wild('n', exclude=[s]) + g = Wild('g') + + ma1 = F.match(s**n*g) + if ma1 and ma1[n].is_integer and ma1[n].is_positive: + _debug(' rule: s**n*F(s) o---o diff(f(t), t, n)') + r, c = _inverse_laplace_transform( + ma1[g], s, t, plane, simplify=False, dorational=True) + r = r.replace(Heaviside(t), 1) + if r.has(InverseLaplaceTransform): + return diff(r, t, ma1[n]), c + else: + return Heaviside(t)*diff(r, t, ma1[n]), c + return None + + +@DEBUG_WRAP +def _inverse_laplace_irrational(fn, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + + a = Wild('a', exclude=[s]) + b = Wild('b', exclude=[s]) + m = Wild('m', exclude=[s]) + n = Wild('n', exclude=[s]) + + result = None + condition = S.true + + fa = fn.as_ordered_factors() + + ma = [x.match((a*s**m+b)**n) for x in fa] + + if None in ma: + return None + + constants = S.One + zeros = [] + poles = [] + rest = [] + + for term in ma: + if term[a] == 0: + constants = constants*term + elif term[n].is_positive: + zeros.append(term) + elif term[n].is_negative: + poles.append(term) + else: + rest.append(term) + + # The code below assumes that the poles are sorted in a specific way: + poles = sorted(poles, key=lambda x: (x[n], x[b] != 0, x[b])) + zeros = sorted(zeros, key=lambda x: (x[n], x[b] != 0, x[b])) + + if len(rest) != 0: + return None + + if len(poles) == 1 and len(zeros) == 0: + if poles[0][n] == -1 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0) == 1/a0 * 1/(sqrt(s)+b0/a0) + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]*constants + if a_.is_positive: + result = ( + k_/sqrt(pi)/sqrt(t) - + k_*a_*exp(a_**2*t)*erfc(a_*sqrt(t))) + _debug(' rule 5.3.4') + elif poles[0][n] == -2 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0)**2 == 1/a0**2 * 1/(sqrt(s)+b0/a0)**2 + a_sq = poles[0][b]/poles[0][a] + a_ = a_sq**2 + k_ = 1/poles[0][a]**2*constants + if a_sq.is_positive: + result = ( + k_*(1 - 2/sqrt(pi)*sqrt(a_)*sqrt(t) + + (1-2*a_*t)*exp(a_*t)*(erf(sqrt(a_)*sqrt(t))-1))) + _debug(' rule 5.3.10') + elif poles[0][n] == -3 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0)**3 == 1/a0**3 * 1/(sqrt(s)+b0/a0)**3 + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]**3*constants + if a_.is_positive: + result = ( + k_*(2/sqrt(pi)*(a_**2*t+1)*sqrt(t) - + a_*t*exp(a_**2*t)*(2*a_**2*t+3)*erfc(a_*sqrt(t)))) + _debug(' rule 5.3.13') + elif poles[0][n] == -4 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0)**4 == 1/a0**4 * 1/(sqrt(s)+b0/a0)**4 + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]**4*constants/3 + if a_.is_positive: + result = ( + k_*(t*(4*a_**4*t**2+12*a_**2*t+3)*exp(a_**2*t) * + erfc(a_*sqrt(t)) - + 2/sqrt(pi)*a_**3*t**(S(5)/2)*(2*a_**2*t+5))) + _debug(' rule 5.3.16') + elif poles[0][n] == -S.Half and poles[0][m] == 2: + # 1/sqrt(a0*s**2+b0) == 1/sqrt(a0) * 1/sqrt(s**2+b0/a0) + a_ = sqrt(poles[0][b]/poles[0][a]) + k_ = 1/sqrt(poles[0][a])*constants + result = (k_*(besselj(0, a_*t))) + _debug(' rule 5.3.35/44') + + elif len(poles) == 1 and len(zeros) == 1: + if ( + poles[0][n] == -3 and poles[0][m] == S.Half and + zeros[0][n] == S.Half and zeros[0][b] == 0): + # sqrt(az*s)/(ap*sqrt(s+bp)**3) + # == sqrt(az)/ap * sqrt(s)/(sqrt(s+bp)**3) + a_ = poles[0][b] + k_ = sqrt(zeros[0][a])/poles[0][a]*constants + result = ( + k_*(2*a_**4*t**2+5*a_**2*t+1)*exp(a_**2*t) * + erfc(a_*sqrt(t)) - 2/sqrt(pi)*a_*(a_**2*t+2)*sqrt(t)) + _debug(' rule 5.3.14') + if ( + poles[0][n] == -1 and poles[0][m] == 1 and + zeros[0][n] == S.Half and zeros[0][m] == 1): + # sqrt(az*s+bz)/(ap*s+bp) + # == sqrt(az)/ap * (sqrt(s+bz/az)/(s+bp/ap)) + a_ = zeros[0][b]/zeros[0][a] + b_ = poles[0][b]/poles[0][a] + k_ = sqrt(zeros[0][a])/poles[0][a]*constants + result = ( + k_*(exp(-a_*t)/sqrt(t)/sqrt(pi)+sqrt(a_-b_) * + exp(-b_*t)*erf(sqrt(a_-b_)*sqrt(t)))) + _debug(' rule 5.3.22') + + elif len(poles) == 2 and len(zeros) == 0: + if ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*s+b0)*sqrt(a1*s)) + # == 1/(a0*sqrt(a1)) * 1/((s+b0/a0)*sqrt(s)) + a_ = -poles[0][b]/poles[0][a] + k_ = 1/sqrt(poles[1][a])/poles[0][a]*constants + if a_.is_positive: + result = (k_/sqrt(a_)*exp(a_*t)*erf(sqrt(a_)*sqrt(t))) + _debug(' rule 5.3.1') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and poles[0][b] == 0 and + poles[1][n] == -1 and poles[1][m] == S.Half): + # 1/(a0*s*(a1*sqrt(s)+b1)) + # == 1/(a0*a1) * 1/(s*(sqrt(s)+b1/a1)) + a_ = poles[1][b]/poles[1][a] + k_ = 1/poles[0][a]/poles[1][a]/a_*constants + if a_.is_positive: + result = k_*(1-exp(a_**2*t)*erfc(a_*sqrt(t))) + _debug(' rule 5.3.5') + elif ( + poles[0][n] == -1 and poles[0][m] == S.Half and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*sqrt(s)+b0)*(sqrt(a1*s)) + # == 1/(a0*sqrt(a1)) * 1/((sqrt(s)+b0/a0)"sqrt(s)) + a_ = poles[0][b]/poles[0][a] + k_ = 1/(poles[0][a]*sqrt(poles[1][a]))*constants + if a_.is_positive: + result = k_*exp(a_**2*t)*erfc(a_*sqrt(t)) + _debug(' rule 5.3.7') + elif ( + poles[0][n] == -S(3)/2 and poles[0][m] == 1 and + poles[0][b] == 0 and poles[1][n] == -1 and + poles[1][m] == S.Half): + # 1/((a0**(3/2)*s**(3/2))*(a1*sqrt(s)+b1)) + # == 1/(a0**(3/2)*a1) 1/((s**(3/2))*(sqrt(s)+b1/a1)) + # Note that Bateman54 5.3 (8) is incorrect; there (sqrt(p)+a) + # should be (sqrt(p)+a)**(-1). + a_ = poles[1][b]/poles[1][a] + k_ = 1/(poles[0][a]**(S(3)/2)*poles[1][a])/a_**2*constants + if a_.is_positive: + result = ( + k_*(2/sqrt(pi)*a_*sqrt(t)+exp(a_**2*t)*erfc(a_*sqrt(t))-1)) + _debug(' rule 5.3.8') + elif ( + poles[0][n] == -2 and poles[0][m] == S.Half and + poles[1][n] == -1 and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*sqrt(s)+b0)**2*a1*s) + # == 1/a0**2/a1 * 1/(sqrt(s)+b0/a0)**2/s + a_sq = poles[0][b]/poles[0][a] + a_ = a_sq**2 + k_ = 1/poles[0][a]**2/poles[1][a]*constants + if a_sq.is_positive: + result = ( + k_*(1/a_ + (2*t-1/a_)*exp(a_*t)*erfc(sqrt(a_)*sqrt(t)) - + 2/sqrt(pi)/sqrt(a_)*sqrt(t))) + _debug(' rule 5.3.11') + elif ( + poles[0][n] == -2 and poles[0][m] == S.Half and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*sqrt(s)+b0)**2*sqrt(a1*s)) + # == 1/a0**2/sqrt(a1) * 1/(sqrt(s)+b0/a0)**2/sqrt(s) + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]**2/sqrt(poles[1][a])*constants + if a_.is_positive: + result = ( + k_*(2/sqrt(pi)*sqrt(t) - + 2*a_*t*exp(a_**2*t)*erfc(a_*sqrt(t)))) + _debug(' rule 5.3.12') + elif ( + poles[0][n] == -3 and poles[0][m] == S.Half and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1 / (sqrt(a1*s)*(a0*sqrt(s+b0)**3)) + # == 1/(sqrt(a1)*a0) * 1/(sqrt(s)*(sqrt(s+b0)**3)) + a_ = poles[0][b] + k_ = constants/sqrt(poles[1][a])/poles[0][a] + result = k_*( + (2*a_**2*t+1)*t*exp(a_**2*t)*erfc(a_*sqrt(t)) - + 2/sqrt(pi)*a_*t**(S(3)/2)) + _debug(' rule 5.3.15') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -S.Half and poles[1][m] == 1): + # 1 / ( (a0*s+b0)* sqrt(a1*s+b1) ) + # == 1/(sqrt(a1)*a0) * 1 / ( (s+b0/a0)* sqrt(s+b1/a1) ) + a_ = poles[0][b]/poles[0][a] + b_ = poles[1][b]/poles[1][a] + k_ = constants/sqrt(poles[1][a])/poles[0][a] + result = k_*( + 1/sqrt(b_-a_)*exp(-a_*t)*erf(sqrt(b_-a_)*sqrt(t))) + _debug(' rule 5.3.23') + + elif len(poles) == 2 and len(zeros) == 1: + if ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -1 and poles[1][m] == S.Half and + zeros[0][n] == S.Half and zeros[0][m] == 1 and + zeros[0][b] == 0): + # sqrt(za0*s)/((a0*s+b0)*(a1*sqrt(s)+b1)) + # == sqrt(za0)/(a0*a1) * s/((s+b0/a0)*(sqrt(s)+b1/a1)) + a_sq = poles[1][b]/poles[1][a] + a_ = a_sq**2 + b_ = -poles[0][b]/poles[0][a] + k_ = sqrt(zeros[0][a])/poles[0][a]/poles[1][a]/(a_-b_)*constants + if a_sq.is_positive and b_.is_positive: + result = k_*( + a_*exp(a_*t)*erfc(sqrt(a_)*sqrt(t)) + + sqrt(a_)*sqrt(b_)*exp(b_*t)*erfc(sqrt(b_)*sqrt(t)) - + b_*exp(b_*t)) + _debug(' rule 5.3.6') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[0][b] == 0 and poles[1][n] == -1 and + poles[1][m] == S.Half and zeros[0][n] == 1 and + zeros[0][m] == S.Half): + # (az*sqrt(s)+bz)/(a0*s*(a1*sqrt(s)+b1)) + # == az/a0/a1 * (sqrt(z)+bz/az)/(s*(sqrt(s)+b1/a1)) + a_num = zeros[0][b]/zeros[0][a] + a_ = poles[1][b]/poles[1][a] + if a_+a_num == 0: + k_ = zeros[0][a]/poles[0][a]/poles[1][a]*constants + result = k_*( + 2*exp(a_**2*t)*erfc(a_*sqrt(t))-1) + _debug(' rule 5.3.17') + elif ( + poles[1][n] == -1 and poles[1][m] == 1 and + poles[1][b] == 0 and poles[0][n] == -2 and + poles[0][m] == S.Half and zeros[0][n] == 2 and + zeros[0][m] == S.Half): + # (az*sqrt(s)+bz)**2/(a1*s*(a0*sqrt(s)+b0)**2) + # == az**2/a1/a0**2 * (sqrt(z)+bz/az)**2/(s*(sqrt(s)+b0/a0)**2) + a_num = zeros[0][b]/zeros[0][a] + a_ = poles[0][b]/poles[0][a] + if a_+a_num == 0: + k_ = zeros[0][a]**2/poles[1][a]/poles[0][a]**2*constants + result = k_*( + 1 + 8*a_**2*t*exp(a_**2*t)*erfc(a_*sqrt(t)) - + 8/sqrt(pi)*a_*sqrt(t)) + _debug(' rule 5.3.18') + elif ( + poles[1][n] == -1 and poles[1][m] == 1 and + poles[1][b] == 0 and poles[0][n] == -3 and + poles[0][m] == S.Half and zeros[0][n] == 3 and + zeros[0][m] == S.Half): + # (az*sqrt(s)+bz)**3/(a1*s*(a0*sqrt(s)+b0)**3) + # == az**3/a1/a0**3 * (sqrt(z)+bz/az)**3/(s*(sqrt(s)+b0/a0)**3) + a_num = zeros[0][b]/zeros[0][a] + a_ = poles[0][b]/poles[0][a] + if a_+a_num == 0: + k_ = zeros[0][a]**3/poles[1][a]/poles[0][a]**3*constants + result = k_*( + 2*(8*a_**4*t**2+8*a_**2*t+1)*exp(a_**2*t) * + erfc(a_*sqrt(t))-8/sqrt(pi)*a_*sqrt(t)*(2*a_**2*t+1)-1) + _debug(' rule 5.3.19') + + elif len(poles) == 3 and len(zeros) == 0: + if ( + poles[0][n] == -1 and poles[0][b] == 0 and poles[0][m] == 1 and + poles[1][n] == -1 and poles[1][m] == 1 and + poles[2][n] == -S.Half and poles[2][m] == 1): + # 1/((a0*s)*(a1*s+b1)*sqrt(a2*s)) + # == 1/(a0*a1*sqrt(a2)) * 1/((s)*(s+b1/a1)*sqrt(s)) + a_ = -poles[1][b]/poles[1][a] + k_ = 1/poles[0][a]/poles[1][a]/sqrt(poles[2][a])*constants + if a_.is_positive: + result = k_ * ( + a_**(-S(3)/2) * exp(a_*t) * erf(sqrt(a_)*sqrt(t)) - + 2/a_/sqrt(pi)*sqrt(t)) + _debug(' rule 5.3.2') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -1 and poles[1][m] == S.Half and + poles[2][n] == -S.Half and poles[2][m] == 1 and + poles[2][b] == 0): + # 1/((a0*s+b0)*(a1*sqrt(s)+b1)*(sqrt(a2)*sqrt(s))) + # == 1/(a0*a1*sqrt(a2)) * 1/((s+b0/a0)*(sqrt(s)+b1/a1)*sqrt(s)) + a_sq = poles[1][b]/poles[1][a] + a_ = a_sq**2 + b_ = -poles[0][b]/poles[0][a] + k_ = ( + 1/poles[0][a]/poles[1][a]/sqrt(poles[2][a]) / + (sqrt(b_)*(a_-b_))) + if a_sq.is_positive and b_.is_positive: + result = k_ * ( + sqrt(b_)*exp(a_*t)*erfc(sqrt(a_)*sqrt(t)) + + sqrt(a_)*exp(b_*t)*erf(sqrt(b_)*sqrt(t)) - + sqrt(b_)*exp(b_*t)) + _debug(' rule 5.3.9') + + if result is None: + return None + else: + return Heaviside(t)*result, condition + + +@DEBUG_WRAP +def _inverse_laplace_early_prog_rules(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + prog_rules = [_inverse_laplace_irrational] + + for p_rule in prog_rules: + if (r := p_rule(F, s, t, plane)) is not None: + return r + return None + + +@DEBUG_WRAP +def _inverse_laplace_apply_prog_rules(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + prog_rules = [_inverse_laplace_time_shift, _inverse_laplace_freq_shift, + _inverse_laplace_time_diff, _inverse_laplace_diff, + _inverse_laplace_irrational] + + for p_rule in prog_rules: + if (r := p_rule(F, s, t, plane)) is not None: + return r + return None + + +@DEBUG_WRAP +def _inverse_laplace_expand(fn, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + if fn.is_Add: + return None + r = expand(fn, deep=False) + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + r = expand_mul(fn) + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + r = expand(fn) + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + if fn.is_rational_function(s): + r = fn.apart(s).doit() + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + return None + + +@DEBUG_WRAP +def _inverse_laplace_rational(fn, s, t, plane, *, simplify): + """ + Helper function for the class InverseLaplaceTransform. + """ + x_ = symbols('x_') + f = fn.apart(s) + terms = Add.make_args(f) + terms_t = [] + conditions = [S.true] + for term in terms: + [n, d] = term.as_numer_denom() + dc = d.as_poly(s).all_coeffs() + dc_lead = dc[0] + dc = [x/dc_lead for x in dc] + nc = [x/dc_lead for x in n.as_poly(s).all_coeffs()] + if len(dc) == 1: + N = len(nc)-1 + for c in enumerate(nc): + r = c[1]*DiracDelta(t, N-c[0]) + terms_t.append(r) + elif len(dc) == 2: + r = nc[0]*exp(-dc[1]*t) + terms_t.append(Heaviside(t)*r) + elif len(dc) == 3: + a = dc[1]/2 + b = (dc[2]-a**2).factor() + if len(nc) == 1: + nc = [S.Zero] + nc + l, m = tuple(nc) + if b == 0: + r = (m*t+l*(1-a*t))*exp(-a*t) + else: + hyp = False + if b.is_negative: + b = -b + hyp = True + b2 = list(roots(x_**2-b, x_).keys())[0] + bs = sqrt(b).simplify() + if hyp: + r = ( + l*exp(-a*t)*cosh(b2*t) + (m-a*l) / + bs*exp(-a*t)*sinh(bs*t)) + else: + r = l*exp(-a*t)*cos(b2*t) + (m-a*l)/bs*exp(-a*t)*sin(bs*t) + terms_t.append(Heaviside(t)*r) + else: + ft, cond = _inverse_laplace_transform( + term, s, t, plane, simplify=simplify, dorational=False) + terms_t.append(ft) + conditions.append(cond) + + result = Add(*terms_t) + if simplify: + result = result.simplify(doit=False) + return result, And(*conditions) + + +@DEBUG_WRAP +def _inverse_laplace_transform(fn, s_, t_, plane, *, simplify, dorational): + """ + Front-end function of the inverse Laplace transform. It tries to apply all + known rules recursively. If everything else fails, it tries to integrate. + """ + terms = Add.make_args(fn) + terms_t = [] + conditions = [] + + for term in terms: + if term.has(exp): + # Simplify expressions with exp() such that time-shifted + # expressions have negative exponents in the numerator instead of + # positive exponents in the numerator and denominator; this is a + # (necessary) trick. It will, for example, convert + # (s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1)) into + # (s**2 + 4*exp(-s) - 4*exp(-2*s))/(s*(s**2 + 1)) + term = term.subs(s_, -s_).together().subs(s_, -s_) + k, f = term.as_independent(s_, as_Add=False) + if ( + dorational and term.is_rational_function(s_) and + (r := _inverse_laplace_rational( + f, s_, t_, plane, simplify=simplify)) + is not None or + (r := _inverse_laplace_apply_simple_rules(f, s_, t_)) + is not None or + (r := _inverse_laplace_early_prog_rules(f, s_, t_, plane)) + is not None or + (r := _inverse_laplace_expand(f, s_, t_, plane)) + is not None or + (r := _inverse_laplace_apply_prog_rules(f, s_, t_, plane)) + is not None): + pass + elif any(undef.has(s_) for undef in f.atoms(AppliedUndef)): + # If there are undefined functions f(t) then integration is + # unlikely to do anything useful so we skip it and given an + # unevaluated LaplaceTransform. + r = (InverseLaplaceTransform(f, s_, t_, plane), S.true) + elif ( + r := _inverse_laplace_transform_integration( + f, s_, t_, plane, simplify=simplify)) is not None: + pass + else: + r = (InverseLaplaceTransform(f, s_, t_, plane), S.true) + (ri_, ci_) = r + terms_t.append(k*ri_) + conditions.append(ci_) + + result = Add(*terms_t) + if simplify: + result = result.simplify(doit=False) + condition = And(*conditions) + + return result, condition + + +class InverseLaplaceTransform(IntegralTransform): + """ + Class representing unevaluated inverse Laplace transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Laplace transforms, see the + :func:`inverse_laplace_transform` docstring. + """ + + _name = 'Inverse Laplace' + _none_sentinel = Dummy('None') + _c = Dummy('c') + + def __new__(cls, F, s, x, plane, **opts): + if plane is None: + plane = InverseLaplaceTransform._none_sentinel + return IntegralTransform.__new__(cls, F, s, x, plane, **opts) + + @property + def fundamental_plane(self): + plane = self.args[3] + if plane is InverseLaplaceTransform._none_sentinel: + plane = None + return plane + + def _compute_transform(self, F, s, t, **hints): + return _inverse_laplace_transform_integration( + F, s, t, self.fundamental_plane, **hints) + + def _as_integral(self, F, s, t): + c = self.__class__._c + return ( + Integral(exp(s*t)*F, (s, c - S.ImaginaryUnit*S.Infinity, + c + S.ImaginaryUnit*S.Infinity)) / + (2*S.Pi*S.ImaginaryUnit)) + + def doit(self, **hints): + """ + Try to evaluate the transform in closed form. + + Explanation + =========== + + Standard hints are the following: + - ``noconds``: if True, do not return convergence conditions. The + default setting is `True`. + - ``simplify``: if True, it simplifies the final result. The + default setting is `False`. + """ + _noconds = hints.get('noconds', True) + _simplify = hints.get('simplify', False) + + debugf('[ILT doit] (%s, %s, %s)', (self.function, + self.function_variable, + self.transform_variable)) + + s_ = self.function_variable + t_ = self.transform_variable + fn = self.function + plane = self.fundamental_plane + + r = _inverse_laplace_transform( + fn, s_, t_, plane, simplify=_simplify, dorational=True) + + if _noconds: + return r[0] + else: + return r + + +def inverse_laplace_transform(F, s, t, plane=None, **hints): + r""" + Compute the inverse Laplace transform of `F(s)`, defined as + + .. math :: + f(t) = \frac{1}{2\pi i} \int_{c-i\infty}^{c+i\infty} e^{st} + F(s) \mathrm{d}s, + + for `c` so large that `F(s)` has no singularites in the + half-plane `\operatorname{Re}(s) > c-\epsilon`. + + Explanation + =========== + + The plane can be specified by + argument ``plane``, but will be inferred if passed as None. + + Under certain regularity conditions, this recovers `f(t)` from its + Laplace Transform `F(s)`, for non-negative `t`, and vice + versa. + + If the integral cannot be computed in closed form, this function returns + an unevaluated :class:`InverseLaplaceTransform` object. + + Note that this function will always assume `t` to be real, + regardless of the SymPy assumption on `t`. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + + Examples + ======== + + >>> from sympy import inverse_laplace_transform, exp, Symbol + >>> from sympy.abc import s, t + >>> a = Symbol('a', positive=True) + >>> inverse_laplace_transform(exp(-a*s)/s, s, t) + Heaviside(-a + t) + + See Also + ======== + + laplace_transform + hankel_transform, inverse_hankel_transform + """ + _noconds = hints.get('noconds', True) + _simplify = hints.get('simplify', False) + + if isinstance(F, MatrixBase) and hasattr(F, 'applyfunc'): + return F.applyfunc( + lambda Fij: inverse_laplace_transform(Fij, s, t, plane, **hints)) + + r, c = InverseLaplaceTransform(F, s, t, plane).doit( + noconds=False, simplify=_simplify) + + if _noconds: + return r + else: + return r, c + + +def _fast_inverse_laplace(e, s, t): + """Fast inverse Laplace transform of rational function including RootSum""" + a, b, n = symbols('a, b, n', cls=Wild, exclude=[s]) + + def _ilt(e): + if not e.has(s): + return e + elif e.is_Add: + return _ilt_add(e) + elif e.is_Mul: + return _ilt_mul(e) + elif e.is_Pow: + return _ilt_pow(e) + elif isinstance(e, RootSum): + return _ilt_rootsum(e) + else: + raise NotImplementedError + + def _ilt_add(e): + return e.func(*map(_ilt, e.args)) + + def _ilt_mul(e): + coeff, expr = e.as_independent(s) + if expr.is_Mul: + raise NotImplementedError + return coeff * _ilt(expr) + + def _ilt_pow(e): + match = e.match((a*s + b)**n) + if match is not None: + nm, am, bm = match[n], match[a], match[b] + if nm.is_Integer and nm < 0: + return t**(-nm-1)*exp(-(bm/am)*t)/(am**-nm*gamma(-nm)) + if nm == 1: + return exp(-(bm/am)*t) / am + raise NotImplementedError + + def _ilt_rootsum(e): + expr = e.fun.expr + [variable] = e.fun.variables + return RootSum(e.poly, Lambda(variable, together(_ilt(expr)))) + + return _ilt(e) diff --git a/phivenv/Lib/site-packages/sympy/integrals/manualintegrate.py b/phivenv/Lib/site-packages/sympy/integrals/manualintegrate.py new file mode 100644 index 0000000000000000000000000000000000000000..2908fb33003ba9c22da47e550edcfea2b41a26a0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/manualintegrate.py @@ -0,0 +1,2174 @@ +"""Integration method that emulates by-hand techniques. + +This module also provides functionality to get the steps used to evaluate a +particular integral, in the ``integral_steps`` function. This will return +nested ``Rule`` s representing the integration rules used. + +Each ``Rule`` class represents a (maybe parametrized) integration rule, e.g. +``SinRule`` for integrating ``sin(x)`` and ``ReciprocalSqrtQuadraticRule`` +for integrating ``1/sqrt(a+b*x+c*x**2)``. The ``eval`` method returns the +integration result. + +The ``manualintegrate`` function computes the integral by calling ``eval`` +on the rule returned by ``integral_steps``. + +The integrator can be extended with new heuristics and evaluation +techniques. To do so, extend the ``Rule`` class, implement ``eval`` method, +then write a function that accepts an ``IntegralInfo`` object and returns +either a ``Rule`` instance or ``None``. If the new technique requires a new +match, add the key and call to the antiderivative function to integral_steps. +To enable simple substitutions, add the match to find_substitutions. + +""" + +from __future__ import annotations +from typing import NamedTuple, Type, Callable, Sequence +from abc import ABC, abstractmethod +from dataclasses import dataclass +from collections import defaultdict +from collections.abc import Mapping + +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.containers import Dict +from sympy.core.expr import Expr +from sympy.core.function import Derivative +from sympy.core.logic import fuzzy_not +from sympy.core.mul import Mul +from sympy.core.numbers import Integer, Number, E +from sympy.core.power import Pow +from sympy.core.relational import Eq, Ne, Boolean +from sympy.core.singleton import S +from sympy.core.symbol import Dummy, Symbol, Wild +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.hyperbolic import (HyperbolicFunction, csch, + cosh, coth, sech, sinh, tanh, asinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (TrigonometricFunction, + cos, sin, tan, cot, csc, sec, acos, asin, atan, acot, acsc, asec) +from sympy.functions.special.delta_functions import Heaviside, DiracDelta +from sympy.functions.special.error_functions import (erf, erfi, fresnelc, + fresnels, Ci, Chi, Si, Shi, Ei, li) +from sympy.functions.special.gamma_functions import uppergamma +from sympy.functions.special.elliptic_integrals import elliptic_e, elliptic_f +from sympy.functions.special.polynomials import (chebyshevt, chebyshevu, + legendre, hermite, laguerre, assoc_laguerre, gegenbauer, jacobi, + OrthogonalPolynomial) +from sympy.functions.special.zeta_functions import polylog +from .integrals import Integral +from sympy.logic.boolalg import And +from sympy.ntheory.factor_ import primefactors +from sympy.polys.polytools import degree, lcm_list, gcd_list, Poly +from sympy.simplify.radsimp import fraction +from sympy.simplify.simplify import simplify +from sympy.solvers.solvers import solve +from sympy.strategies.core import switch, do_one, null_safe, condition +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import debug + + +@dataclass +class Rule(ABC): + integrand: Expr + variable: Symbol + + @abstractmethod + def eval(self) -> Expr: + pass + + @abstractmethod + def contains_dont_know(self) -> bool: + pass + + +@dataclass +class AtomicRule(Rule, ABC): + """A simple rule that does not depend on other rules""" + def contains_dont_know(self) -> bool: + return False + + +@dataclass +class ConstantRule(AtomicRule): + """integrate(a, x) -> a*x""" + def eval(self) -> Expr: + return self.integrand * self.variable + + +@dataclass +class ConstantTimesRule(Rule): + """integrate(a*f(x), x) -> a*integrate(f(x), x)""" + constant: Expr + other: Expr + substep: Rule + + def eval(self) -> Expr: + return self.constant * self.substep.eval() + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class PowerRule(AtomicRule): + """integrate(x**a, x)""" + base: Expr + exp: Expr + + def eval(self) -> Expr: + return Piecewise( + ((self.base**(self.exp + 1))/(self.exp + 1), Ne(self.exp, -1)), + (log(self.base), True), + ) + + +@dataclass +class NestedPowRule(AtomicRule): + """integrate((x**a)**b, x)""" + base: Expr + exp: Expr + + def eval(self) -> Expr: + m = self.base * self.integrand + return Piecewise((m / (self.exp + 1), Ne(self.exp, -1)), + (m * log(self.base), True)) + + +@dataclass +class AddRule(Rule): + """integrate(f(x) + g(x), x) -> integrate(f(x), x) + integrate(g(x), x)""" + substeps: list[Rule] + + def eval(self) -> Expr: + return Add(*(substep.eval() for substep in self.substeps)) + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep in self.substeps) + + +@dataclass +class URule(Rule): + """integrate(f(g(x))*g'(x), x) -> integrate(f(u), u), u = g(x)""" + u_var: Symbol + u_func: Expr + substep: Rule + + def eval(self) -> Expr: + result = self.substep.eval() + if self.u_func.is_Pow: + base, exp_ = self.u_func.as_base_exp() + if exp_ == -1: + # avoid needless -log(1/x) from substitution + result = result.subs(log(self.u_var), -log(base)) + return result.subs(self.u_var, self.u_func) + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class PartsRule(Rule): + """integrate(u(x)*v'(x), x) -> u(x)*v(x) - integrate(u'(x)*v(x), x)""" + u: Symbol + dv: Expr + v_step: Rule + second_step: Rule | None # None when is a substep of CyclicPartsRule + + def eval(self) -> Expr: + assert self.second_step is not None + v = self.v_step.eval() + return self.u * v - self.second_step.eval() + + def contains_dont_know(self) -> bool: + return self.v_step.contains_dont_know() or ( + self.second_step is not None and self.second_step.contains_dont_know()) + + +@dataclass +class CyclicPartsRule(Rule): + """Apply PartsRule multiple times to integrate exp(x)*sin(x)""" + parts_rules: list[PartsRule] + coefficient: Expr + + def eval(self) -> Expr: + result = [] + sign = 1 + for rule in self.parts_rules: + result.append(sign * rule.u * rule.v_step.eval()) + sign *= -1 + return Add(*result) / (1 - self.coefficient) + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep in self.parts_rules) + + +@dataclass +class TrigRule(AtomicRule, ABC): + pass + + +@dataclass +class SinRule(TrigRule): + """integrate(sin(x), x) -> -cos(x)""" + def eval(self) -> Expr: + return -cos(self.variable) + + +@dataclass +class CosRule(TrigRule): + """integrate(cos(x), x) -> sin(x)""" + def eval(self) -> Expr: + return sin(self.variable) + + +@dataclass +class SecTanRule(TrigRule): + """integrate(sec(x)*tan(x), x) -> sec(x)""" + def eval(self) -> Expr: + return sec(self.variable) + + +@dataclass +class CscCotRule(TrigRule): + """integrate(csc(x)*cot(x), x) -> -csc(x)""" + def eval(self) -> Expr: + return -csc(self.variable) + + +@dataclass +class Sec2Rule(TrigRule): + """integrate(sec(x)**2, x) -> tan(x)""" + def eval(self) -> Expr: + return tan(self.variable) + + +@dataclass +class Csc2Rule(TrigRule): + """integrate(csc(x)**2, x) -> -cot(x)""" + def eval(self) -> Expr: + return -cot(self.variable) + + +@dataclass +class HyperbolicRule(AtomicRule, ABC): + pass + + +@dataclass +class SinhRule(HyperbolicRule): + """integrate(sinh(x), x) -> cosh(x)""" + def eval(self) -> Expr: + return cosh(self.variable) + + +@dataclass +class CoshRule(HyperbolicRule): + """integrate(cosh(x), x) -> sinh(x)""" + def eval(self): + return sinh(self.variable) + + +@dataclass +class ExpRule(AtomicRule): + """integrate(a**x, x) -> a**x/ln(a)""" + base: Expr + exp: Expr + + def eval(self) -> Expr: + return self.integrand / log(self.base) + + +@dataclass +class ReciprocalRule(AtomicRule): + """integrate(1/x, x) -> ln(x)""" + base: Expr + + def eval(self) -> Expr: + return log(self.base) + + +@dataclass +class ArcsinRule(AtomicRule): + """integrate(1/sqrt(1-x**2), x) -> asin(x)""" + def eval(self) -> Expr: + return asin(self.variable) + + +@dataclass +class ArcsinhRule(AtomicRule): + """integrate(1/sqrt(1+x**2), x) -> asin(x)""" + def eval(self) -> Expr: + return asinh(self.variable) + + +@dataclass +class ReciprocalSqrtQuadraticRule(AtomicRule): + """integrate(1/sqrt(a+b*x+c*x**2), x) -> log(2*sqrt(c)*sqrt(a+b*x+c*x**2)+b+2*c*x)/sqrt(c)""" + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return log(2*sqrt(c)*sqrt(a+b*x+c*x**2)+b+2*c*x)/sqrt(c) + + +@dataclass +class SqrtQuadraticDenomRule(AtomicRule): + """integrate(poly(x)/sqrt(a+b*x+c*x**2), x)""" + a: Expr + b: Expr + c: Expr + coeffs: list[Expr] + + def eval(self) -> Expr: + a, b, c, coeffs, x = self.a, self.b, self.c, self.coeffs.copy(), self.variable + # Integrate poly/sqrt(a+b*x+c*x**2) using recursion. + # coeffs are coefficients of the polynomial. + # Let I_n = x**n/sqrt(a+b*x+c*x**2), then + # I_n = A * x**(n-1)*sqrt(a+b*x+c*x**2) - B * I_{n-1} - C * I_{n-2} + # where A = 1/(n*c), B = (2*n-1)*b/(2*n*c), C = (n-1)*a/(n*c) + # See https://github.com/sympy/sympy/pull/23608 for proof. + result_coeffs = [] + coeffs = coeffs.copy() + for i in range(len(coeffs)-2): + n = len(coeffs)-1-i + coeff = coeffs[i]/(c*n) + result_coeffs.append(coeff) + coeffs[i+1] -= (2*n-1)*b/2*coeff + coeffs[i+2] -= (n-1)*a*coeff + d, e = coeffs[-1], coeffs[-2] + s = sqrt(a+b*x+c*x**2) + constant = d-b*e/(2*c) + if constant == 0: + I0 = 0 + else: + step = inverse_trig_rule(IntegralInfo(1/s, x), degenerate=False) + I0 = constant*step.eval() + return Add(*(result_coeffs[i]*x**(len(coeffs)-2-i) + for i in range(len(result_coeffs))), e/c)*s + I0 + + +@dataclass +class SqrtQuadraticRule(AtomicRule): + """integrate(sqrt(a+b*x+c*x**2), x)""" + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + step = sqrt_quadratic_rule(IntegralInfo(self.integrand, self.variable), degenerate=False) + return step.eval() + + +@dataclass +class AlternativeRule(Rule): + """Multiple ways to do integration.""" + alternatives: list[Rule] + + def eval(self) -> Expr: + return self.alternatives[0].eval() + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep in self.alternatives) + + +@dataclass +class DontKnowRule(Rule): + """Leave the integral as is.""" + def eval(self) -> Expr: + return Integral(self.integrand, self.variable) + + def contains_dont_know(self) -> bool: + return True + + +@dataclass +class DerivativeRule(AtomicRule): + """integrate(f'(x), x) -> f(x)""" + def eval(self) -> Expr: + assert isinstance(self.integrand, Derivative) + variable_count = list(self.integrand.variable_count) + for i, (var, count) in enumerate(variable_count): + if var == self.variable: + variable_count[i] = (var, count - 1) + break + return Derivative(self.integrand.expr, *variable_count) + + +@dataclass +class RewriteRule(Rule): + """Rewrite integrand to another form that is easier to handle.""" + rewritten: Expr + substep: Rule + + def eval(self) -> Expr: + return self.substep.eval() + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class CompleteSquareRule(RewriteRule): + """Rewrite a+b*x+c*x**2 to a-b**2/(4*c) + c*(x+b/(2*c))**2""" + pass + + +@dataclass +class PiecewiseRule(Rule): + subfunctions: Sequence[tuple[Rule, bool | Boolean]] + + def eval(self) -> Expr: + return Piecewise(*[(substep.eval(), cond) + for substep, cond in self.subfunctions]) + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep, _ in self.subfunctions) + + +@dataclass +class HeavisideRule(Rule): + harg: Expr + ibnd: Expr + substep: Rule + + def eval(self) -> Expr: + # If we are integrating over x and the integrand has the form + # Heaviside(m*x+b)*g(x) == Heaviside(harg)*g(symbol) + # then there needs to be continuity at -b/m == ibnd, + # so we subtract the appropriate term. + result = self.substep.eval() + return Heaviside(self.harg) * (result - result.subs(self.variable, self.ibnd)) + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class DiracDeltaRule(AtomicRule): + n: Expr + a: Expr + b: Expr + + def eval(self) -> Expr: + n, a, b, x = self.n, self.a, self.b, self.variable + if n == 0: + return Heaviside(a+b*x)/b + return DiracDelta(a+b*x, n-1)/b + + +@dataclass +class TrigSubstitutionRule(Rule): + theta: Expr + func: Expr + rewritten: Expr + substep: Rule + restriction: bool | Boolean + + def eval(self) -> Expr: + theta, func, x = self.theta, self.func, self.variable + func = func.subs(sec(theta), 1/cos(theta)) + func = func.subs(csc(theta), 1/sin(theta)) + func = func.subs(cot(theta), 1/tan(theta)) + + trig_function = list(func.find(TrigonometricFunction)) + assert len(trig_function) == 1 + trig_function = trig_function[0] + relation = solve(x - func, trig_function) + assert len(relation) == 1 + numer, denom = fraction(relation[0]) + + if isinstance(trig_function, sin): + opposite = numer + hypotenuse = denom + adjacent = sqrt(denom**2 - numer**2) + inverse = asin(relation[0]) + elif isinstance(trig_function, cos): + adjacent = numer + hypotenuse = denom + opposite = sqrt(denom**2 - numer**2) + inverse = acos(relation[0]) + else: # tan + opposite = numer + adjacent = denom + hypotenuse = sqrt(denom**2 + numer**2) + inverse = atan(relation[0]) + + substitution = [ + (sin(theta), opposite/hypotenuse), + (cos(theta), adjacent/hypotenuse), + (tan(theta), opposite/adjacent), + (theta, inverse) + ] + return Piecewise( + (self.substep.eval().subs(substitution).trigsimp(), self.restriction) # type: ignore + ) + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class ArctanRule(AtomicRule): + """integrate(a/(b*x**2+c), x) -> a/b / sqrt(c/b) * atan(x/sqrt(c/b))""" + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return a/b / sqrt(c/b) * atan(x/sqrt(c/b)) + + +@dataclass +class OrthogonalPolyRule(AtomicRule, ABC): + n: Expr + + +@dataclass +class JacobiRule(OrthogonalPolyRule): + a: Expr + b: Expr + + def eval(self) -> Expr: + n, a, b, x = self.n, self.a, self.b, self.variable + return Piecewise( + (2*jacobi(n + 1, a - 1, b - 1, x)/(n + a + b), Ne(n + a + b, 0)), + (x, Eq(n, 0)), + ((a + b + 2)*x**2/4 + (a - b)*x/2, Eq(n, 1))) + + +@dataclass +class GegenbauerRule(OrthogonalPolyRule): + a: Expr + + def eval(self) -> Expr: + n, a, x = self.n, self.a, self.variable + return Piecewise( + (gegenbauer(n + 1, a - 1, x)/(2*(a - 1)), Ne(a, 1)), + (chebyshevt(n + 1, x)/(n + 1), Ne(n, -1)), + (S.Zero, True)) + + +@dataclass +class ChebyshevTRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return Piecewise( + ((chebyshevt(n + 1, x)/(n + 1) - + chebyshevt(n - 1, x)/(n - 1))/2, Ne(Abs(n), 1)), + (x**2/2, True)) + + +@dataclass +class ChebyshevURule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return Piecewise( + (chebyshevt(n + 1, x)/(n + 1), Ne(n, -1)), + (S.Zero, True)) + + +@dataclass +class LegendreRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return(legendre(n + 1, x) - legendre(n - 1, x))/(2*n + 1) + + +@dataclass +class HermiteRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return hermite(n + 1, x)/(2*(n + 1)) + + +@dataclass +class LaguerreRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return laguerre(n, x) - laguerre(n + 1, x) + + +@dataclass +class AssocLaguerreRule(OrthogonalPolyRule): + a: Expr + + def eval(self) -> Expr: + return -assoc_laguerre(self.n + 1, self.a - 1, self.variable) + + +@dataclass +class IRule(AtomicRule, ABC): + a: Expr + b: Expr + + +@dataclass +class CiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return cos(b)*Ci(a*x) - sin(b)*Si(a*x) + + +@dataclass +class ChiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return cosh(b)*Chi(a*x) + sinh(b)*Shi(a*x) + + +@dataclass +class EiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return exp(b)*Ei(a*x) + + +@dataclass +class SiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return sin(b)*Ci(a*x) + cos(b)*Si(a*x) + + +@dataclass +class ShiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return sinh(b)*Chi(a*x) + cosh(b)*Shi(a*x) + + +@dataclass +class LiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return li(a*x + b)/a + + +@dataclass +class ErfRule(AtomicRule): + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + if a.is_extended_real: + return Piecewise( + (sqrt(S.Pi)/sqrt(-a)/2 * exp(c - b**2/(4*a)) * + erf((-2*a*x - b)/(2*sqrt(-a))), a < 0), + (sqrt(S.Pi)/sqrt(a)/2 * exp(c - b**2/(4*a)) * + erfi((2*a*x + b)/(2*sqrt(a))), True)) + return sqrt(S.Pi)/sqrt(a)/2 * exp(c - b**2/(4*a)) * \ + erfi((2*a*x + b)/(2*sqrt(a))) + + +@dataclass +class FresnelCRule(AtomicRule): + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return sqrt(S.Pi)/sqrt(2*a) * ( + cos(b**2/(4*a) - c)*fresnelc((2*a*x + b)/sqrt(2*a*S.Pi)) + + sin(b**2/(4*a) - c)*fresnels((2*a*x + b)/sqrt(2*a*S.Pi))) + + +@dataclass +class FresnelSRule(AtomicRule): + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return sqrt(S.Pi)/sqrt(2*a) * ( + cos(b**2/(4*a) - c)*fresnels((2*a*x + b)/sqrt(2*a*S.Pi)) - + sin(b**2/(4*a) - c)*fresnelc((2*a*x + b)/sqrt(2*a*S.Pi))) + + +@dataclass +class PolylogRule(AtomicRule): + a: Expr + b: Expr + + def eval(self) -> Expr: + return polylog(self.b + 1, self.a * self.variable) + + +@dataclass +class UpperGammaRule(AtomicRule): + a: Expr + e: Expr + + def eval(self) -> Expr: + a, e, x = self.a, self.e, self.variable + return x**e * (-a*x)**(-e) * uppergamma(e + 1, -a*x)/a + + +@dataclass +class EllipticFRule(AtomicRule): + a: Expr + d: Expr + + def eval(self) -> Expr: + return elliptic_f(self.variable, self.d/self.a)/sqrt(self.a) + + +@dataclass +class EllipticERule(AtomicRule): + a: Expr + d: Expr + + def eval(self) -> Expr: + return elliptic_e(self.variable, self.d/self.a)*sqrt(self.a) + + +class IntegralInfo(NamedTuple): + integrand: Expr + symbol: Symbol + + +def manual_diff(f, symbol): + """Derivative of f in form expected by find_substitutions + + SymPy's derivatives for some trig functions (like cot) are not in a form + that works well with finding substitutions; this replaces the + derivatives for those particular forms with something that works better. + + """ + if f.args: + arg = f.args[0] + if isinstance(f, tan): + return arg.diff(symbol) * sec(arg)**2 + elif isinstance(f, cot): + return -arg.diff(symbol) * csc(arg)**2 + elif isinstance(f, sec): + return arg.diff(symbol) * sec(arg) * tan(arg) + elif isinstance(f, csc): + return -arg.diff(symbol) * csc(arg) * cot(arg) + elif isinstance(f, Add): + return sum(manual_diff(arg, symbol) for arg in f.args) + elif isinstance(f, Mul): + if len(f.args) == 2 and isinstance(f.args[0], Number): + return f.args[0] * manual_diff(f.args[1], symbol) + return f.diff(symbol) + +def manual_subs(expr, *args): + """ + A wrapper for `expr.subs(*args)` with additional logic for substitution + of invertible functions. + """ + if len(args) == 1: + sequence = args[0] + if isinstance(sequence, (Dict, Mapping)): + sequence = sequence.items() + elif not iterable(sequence): + raise ValueError("Expected an iterable of (old, new) pairs") + elif len(args) == 2: + sequence = [args] + else: + raise ValueError("subs accepts either 1 or 2 arguments") + + new_subs = [] + for old, new in sequence: + if isinstance(old, log): + # If log(x) = y, then exp(a*log(x)) = exp(a*y) + # that is, x**a = exp(a*y). Replace nontrivial powers of x + # before subs turns them into `exp(y)**a`, but + # do not replace x itself yet, to avoid `log(exp(y))`. + x0 = old.args[0] + expr = expr.replace(lambda x: x.is_Pow and x.base == x0, + lambda x: exp(x.exp*new)) + new_subs.append((x0, exp(new))) + + return expr.subs(list(sequence) + new_subs) + +# Method based on that on SIN, described in "Symbolic Integration: The +# Stormy Decade" + +inverse_trig_functions = (atan, asin, acos, acot, acsc, asec) + + +def find_substitutions(integrand, symbol, u_var): + results = [] + + def test_subterm(u, u_diff): + if u_diff == 0: + return False + substituted = integrand / u_diff + debug("substituted: {}, u: {}, u_var: {}".format(substituted, u, u_var)) + substituted = manual_subs(substituted, u, u_var).cancel() + + if substituted.has_free(symbol): + return False + # avoid increasing the degree of a rational function + if integrand.is_rational_function(symbol) and substituted.is_rational_function(u_var): + deg_before = max(degree(t, symbol) for t in integrand.as_numer_denom()) + deg_after = max(degree(t, u_var) for t in substituted.as_numer_denom()) + if deg_after > deg_before: + return False + return substituted.as_independent(u_var, as_Add=False) + + def exp_subterms(term: Expr): + linear_coeffs = [] + terms = [] + n = Wild('n', properties=[lambda n: n.is_Integer]) + for exp_ in term.find(exp): + arg = exp_.args[0] + if symbol not in arg.free_symbols: + continue + match = arg.match(n*symbol) + if match: + linear_coeffs.append(match[n]) + else: + terms.append(exp_) + if linear_coeffs: + terms.append(exp(gcd_list(linear_coeffs)*symbol)) + return terms + + def possible_subterms(term): + if isinstance(term, (TrigonometricFunction, HyperbolicFunction, + *inverse_trig_functions, + exp, log, Heaviside)): + return [term.args[0]] + elif isinstance(term, (chebyshevt, chebyshevu, + legendre, hermite, laguerre)): + return [term.args[1]] + elif isinstance(term, (gegenbauer, assoc_laguerre)): + return [term.args[2]] + elif isinstance(term, jacobi): + return [term.args[3]] + elif isinstance(term, Mul): + r = [] + for u in term.args: + r.append(u) + r.extend(possible_subterms(u)) + return r + elif isinstance(term, Pow): + r = [arg for arg in term.args if arg.has(symbol)] + if term.exp.is_Integer: + r.extend([term.base**d for d in primefactors(term.exp) + if 1 < d < abs(term.args[1])]) + if term.base.is_Add: + r.extend([t for t in possible_subterms(term.base) + if t.is_Pow]) + return r + elif isinstance(term, Add): + r = [] + for arg in term.args: + r.append(arg) + r.extend(possible_subterms(arg)) + return r + return [] + + for u in list(dict.fromkeys(possible_subterms(integrand) + exp_subterms(integrand))): + if u == symbol: + continue + u_diff = manual_diff(u, symbol) + new_integrand = test_subterm(u, u_diff) + if new_integrand is not False: + constant, new_integrand = new_integrand + if new_integrand == integrand.subs(symbol, u_var): + continue + substitution = (u, constant, new_integrand) + if substitution not in results: + results.append(substitution) + + return results + +def rewriter(condition, rewrite): + """Strategy that rewrites an integrand.""" + def _rewriter(integral): + integrand, symbol = integral + debug("Integral: {} is rewritten with {} on symbol: {}".format(integrand, rewrite, symbol)) + if condition(*integral): + rewritten = rewrite(*integral) + if rewritten != integrand: + substep = integral_steps(rewritten, symbol) + if not isinstance(substep, DontKnowRule) and substep: + return RewriteRule(integrand, symbol, rewritten, substep) + return _rewriter + +def proxy_rewriter(condition, rewrite): + """Strategy that rewrites an integrand based on some other criteria.""" + def _proxy_rewriter(criteria): + criteria, integral = criteria + integrand, symbol = integral + debug("Integral: {} is rewritten with {} on symbol: {} and criteria: {}".format(integrand, rewrite, symbol, criteria)) + args = criteria + list(integral) + if condition(*args): + rewritten = rewrite(*args) + if rewritten != integrand: + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + return _proxy_rewriter + +def multiplexer(conditions): + """Apply the rule that matches the condition, else None""" + def multiplexer_rl(expr): + for key, rule in conditions.items(): + if key(expr): + return rule(expr) + return multiplexer_rl + +def alternatives(*rules): + """Strategy that makes an AlternativeRule out of multiple possible results.""" + def _alternatives(integral): + alts = [] + count = 0 + debug("List of Alternative Rules") + for rule in rules: + count = count + 1 + debug("Rule {}: {}".format(count, rule)) + + result = rule(integral) + if (result and not isinstance(result, DontKnowRule) and + result != integral and result not in alts): + alts.append(result) + if len(alts) == 1: + return alts[0] + elif alts: + doable = [rule for rule in alts if not rule.contains_dont_know()] + if doable: + return AlternativeRule(*integral, doable) + else: + return AlternativeRule(*integral, alts) + return _alternatives + +def constant_rule(integral): + return ConstantRule(*integral) + +def power_rule(integral): + integrand, symbol = integral + base, expt = integrand.as_base_exp() + + if symbol not in expt.free_symbols and isinstance(base, Symbol): + if simplify(expt + 1) == 0: + return ReciprocalRule(integrand, symbol, base) + return PowerRule(integrand, symbol, base, expt) + elif symbol not in base.free_symbols and isinstance(expt, Symbol): + rule = ExpRule(integrand, symbol, base, expt) + + if fuzzy_not(log(base).is_zero): + return rule + elif log(base).is_zero: + return ConstantRule(1, symbol) + + return PiecewiseRule(integrand, symbol, [ + (rule, Ne(log(base), 0)), + (ConstantRule(1, symbol), True) + ]) + +def exp_rule(integral): + integrand, symbol = integral + if isinstance(integrand.args[0], Symbol): + return ExpRule(integrand, symbol, E, integrand.args[0]) + + +def orthogonal_poly_rule(integral): + orthogonal_poly_classes = { + jacobi: JacobiRule, + gegenbauer: GegenbauerRule, + chebyshevt: ChebyshevTRule, + chebyshevu: ChebyshevURule, + legendre: LegendreRule, + hermite: HermiteRule, + laguerre: LaguerreRule, + assoc_laguerre: AssocLaguerreRule + } + orthogonal_poly_var_index = { + jacobi: 3, + gegenbauer: 2, + assoc_laguerre: 2 + } + integrand, symbol = integral + for klass in orthogonal_poly_classes: + if isinstance(integrand, klass): + var_index = orthogonal_poly_var_index.get(klass, 1) + if (integrand.args[var_index] is symbol and not + any(v.has(symbol) for v in integrand.args[:var_index])): + return orthogonal_poly_classes[klass](integrand, symbol, *integrand.args[:var_index]) + + +_special_function_patterns: list[tuple[Type, Expr, Callable | None, tuple]] = [] +_wilds = [] +_symbol = Dummy('x') + + +def special_function_rule(integral): + integrand, symbol = integral + if not _special_function_patterns: + a = Wild('a', exclude=[_symbol], properties=[lambda x: not x.is_zero]) + b = Wild('b', exclude=[_symbol]) + c = Wild('c', exclude=[_symbol]) + d = Wild('d', exclude=[_symbol], properties=[lambda x: not x.is_zero]) + e = Wild('e', exclude=[_symbol], properties=[ + lambda x: not (x.is_nonnegative and x.is_integer)]) + _wilds.extend((a, b, c, d, e)) + # patterns consist of a SymPy class, a wildcard expr, an optional + # condition coded as a lambda (when Wild properties are not enough), + # followed by an applicable rule + linear_pattern = a*_symbol + b + quadratic_pattern = a*_symbol**2 + b*_symbol + c + _special_function_patterns.extend(( + (Mul, exp(linear_pattern, evaluate=False)/_symbol, None, EiRule), + (Mul, cos(linear_pattern, evaluate=False)/_symbol, None, CiRule), + (Mul, cosh(linear_pattern, evaluate=False)/_symbol, None, ChiRule), + (Mul, sin(linear_pattern, evaluate=False)/_symbol, None, SiRule), + (Mul, sinh(linear_pattern, evaluate=False)/_symbol, None, ShiRule), + (Pow, 1/log(linear_pattern, evaluate=False), None, LiRule), + (exp, exp(quadratic_pattern, evaluate=False), None, ErfRule), + (sin, sin(quadratic_pattern, evaluate=False), None, FresnelSRule), + (cos, cos(quadratic_pattern, evaluate=False), None, FresnelCRule), + (Mul, _symbol**e*exp(a*_symbol, evaluate=False), None, UpperGammaRule), + (Mul, polylog(b, a*_symbol, evaluate=False)/_symbol, None, PolylogRule), + (Pow, 1/sqrt(a - d*sin(_symbol, evaluate=False)**2), + lambda a, d: a != d, EllipticFRule), + (Pow, sqrt(a - d*sin(_symbol, evaluate=False)**2), + lambda a, d: a != d, EllipticERule), + )) + _integrand = integrand.subs(symbol, _symbol) + for type_, pattern, constraint, rule in _special_function_patterns: + if isinstance(_integrand, type_): + match = _integrand.match(pattern) + if match: + wild_vals = tuple(match.get(w) for w in _wilds + if match.get(w) is not None) + if constraint is None or constraint(*wild_vals): + return rule(integrand, symbol, *wild_vals) + + +def _add_degenerate_step(generic_cond, generic_step: Rule, degenerate_step: Rule | None) -> Rule: + if degenerate_step is None: + return generic_step + if isinstance(generic_step, PiecewiseRule): + subfunctions = [(substep, (cond & generic_cond).simplify()) + for substep, cond in generic_step.subfunctions] + else: + subfunctions = [(generic_step, generic_cond)] + if isinstance(degenerate_step, PiecewiseRule): + subfunctions += degenerate_step.subfunctions + else: + subfunctions.append((degenerate_step, S.true)) + return PiecewiseRule(generic_step.integrand, generic_step.variable, subfunctions) + + +def nested_pow_rule(integral: IntegralInfo): + # nested (c*(a+b*x)**d)**e + integrand, x = integral + + a_ = Wild('a', exclude=[x]) + b_ = Wild('b', exclude=[x, 0]) + pattern = a_+b_*x + generic_cond = S.true + + class NoMatch(Exception): + pass + + def _get_base_exp(expr: Expr) -> tuple[Expr, Expr]: + if not expr.has_free(x): + return S.One, S.Zero + if expr.is_Mul: + _, terms = expr.as_coeff_mul() + if not terms: + return S.One, S.Zero + results = [_get_base_exp(term) for term in terms] + bases = {b for b, _ in results} + bases.discard(S.One) + if len(bases) == 1: + return bases.pop(), Add(*(e for _, e in results)) + raise NoMatch + if expr.is_Pow: + b, e = expr.base, expr.exp # type: ignore + if e.has_free(x): + raise NoMatch + base_, sub_exp = _get_base_exp(b) + return base_, sub_exp * e + match = expr.match(pattern) + if match: + a, b = match[a_], match[b_] + base_ = x + a/b + nonlocal generic_cond + generic_cond = Ne(b, 0) + return base_, S.One + raise NoMatch + + try: + base, exp_ = _get_base_exp(integrand) + except NoMatch: + return + if generic_cond is S.true: + degenerate_step = None + else: + # equivalent with subs(b, 0) but no need to find b + degenerate_step = ConstantRule(integrand.subs(x, 0), x) + generic_step = NestedPowRule(integrand, x, base, exp_) + return _add_degenerate_step(generic_cond, generic_step, degenerate_step) + + +def inverse_trig_rule(integral: IntegralInfo, degenerate=True): + """ + Set degenerate=False on recursive call where coefficient of quadratic term + is assumed non-zero. + """ + integrand, symbol = integral + base, exp = integrand.as_base_exp() + a = Wild('a', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + c = Wild('c', exclude=[symbol, 0]) + match = base.match(a + b*symbol + c*symbol**2) + + if not match: + return + + def make_inverse_trig(RuleClass, a, sign_a, c, sign_c, h) -> Rule: + u_var = Dummy("u") + rewritten = 1/sqrt(sign_a*a + sign_c*c*(symbol-h)**2) # a>0, c>0 + quadratic_base = sqrt(c/a)*(symbol-h) + constant = 1/sqrt(c) + u_func = None + if quadratic_base is not symbol: + u_func = quadratic_base + quadratic_base = u_var + standard_form = 1/sqrt(sign_a + sign_c*quadratic_base**2) + substep = RuleClass(standard_form, quadratic_base) + if constant != 1: + substep = ConstantTimesRule(constant*standard_form, symbol, constant, standard_form, substep) + if u_func is not None: + substep = URule(rewritten, symbol, u_var, u_func, substep) + if h != 0: + substep = CompleteSquareRule(integrand, symbol, rewritten, substep) + return substep + + a, b, c = [match.get(i, S.Zero) for i in (a, b, c)] + generic_cond = Ne(c, 0) + if not degenerate or generic_cond is S.true: + degenerate_step = None + elif b.is_zero: + degenerate_step = ConstantRule(a ** exp, symbol) + else: + degenerate_step = sqrt_linear_rule(IntegralInfo((a + b * symbol) ** exp, symbol)) + + if simplify(2*exp + 1) == 0: + h, k = -b/(2*c), a - b**2/(4*c) # rewrite base to k + c*(symbol-h)**2 + non_square_cond = Ne(k, 0) + square_step = None + if non_square_cond is not S.true: + square_step = NestedPowRule(1/sqrt(c*(symbol-h)**2), symbol, symbol-h, S.NegativeOne) + if non_square_cond is S.false: + return square_step + generic_step = ReciprocalSqrtQuadraticRule(integrand, symbol, a, b, c) + step = _add_degenerate_step(non_square_cond, generic_step, square_step) + if k.is_real and c.is_real: + # list of ((rule, base_exp, a, sign_a, b, sign_b), condition) + rules = [] + for args, cond in ( # don't apply ArccoshRule to x**2-1 + ((ArcsinRule, k, 1, -c, -1, h), And(k > 0, c < 0)), # 1-x**2 + ((ArcsinhRule, k, 1, c, 1, h), And(k > 0, c > 0)), # 1+x**2 + ): + if cond is S.true: + return make_inverse_trig(*args) + if cond is not S.false: + rules.append((make_inverse_trig(*args), cond)) + if rules: + if not k.is_positive: # conditions are not thorough, need fall back rule + rules.append((generic_step, S.true)) + step = PiecewiseRule(integrand, symbol, rules) + else: + step = generic_step + return _add_degenerate_step(generic_cond, step, degenerate_step) + if exp == S.Half: + step = SqrtQuadraticRule(integrand, symbol, a, b, c) + return _add_degenerate_step(generic_cond, step, degenerate_step) + + +def add_rule(integral): + integrand, symbol = integral + results = [integral_steps(g, symbol) + for g in integrand.as_ordered_terms()] + return None if None in results else AddRule(integrand, symbol, results) + + +def mul_rule(integral: IntegralInfo): + integrand, symbol = integral + + # Constant times function case + coeff, f = integrand.as_independent(symbol) + if coeff != 1: + next_step = integral_steps(f, symbol) + if next_step is not None: + return ConstantTimesRule(integrand, symbol, coeff, f, next_step) + + +def _parts_rule(integrand, symbol) -> tuple[Expr, Expr, Expr, Expr, Rule] | None: + # LIATE rule: + # log, inverse trig, algebraic, trigonometric, exponential + def pull_out_algebraic(integrand): + integrand = integrand.cancel().together() + # iterating over Piecewise args would not work here + algebraic = ([] if isinstance(integrand, Piecewise) or not integrand.is_Mul + else [arg for arg in integrand.args if arg.is_algebraic_expr(symbol)]) + if algebraic: + u = Mul(*algebraic) + dv = (integrand / u).cancel() + return u, dv + + def pull_out_u(*functions) -> Callable[[Expr], tuple[Expr, Expr] | None]: + def pull_out_u_rl(integrand: Expr) -> tuple[Expr, Expr] | None: + if any(integrand.has(f) for f in functions): + args = [arg for arg in integrand.args + if any(isinstance(arg, cls) for cls in functions)] + if args: + u = Mul(*args) # type: ignore + dv = integrand / u + return u, dv + return None + + return pull_out_u_rl + + liate_rules = [pull_out_u(log), pull_out_u(*inverse_trig_functions), + pull_out_algebraic, pull_out_u(sin, cos), + pull_out_u(exp)] + + + dummy = Dummy("temporary") + # we can integrate log(x) and atan(x) by setting dv = 1 + if isinstance(integrand, (log, *inverse_trig_functions)): + integrand = dummy * integrand + + for index, rule in enumerate(liate_rules): + result = rule(integrand) + + if result: + u, dv = result + + # Don't pick u to be a constant if possible + if symbol not in u.free_symbols and not u.has(dummy): + return None + + u = u.subs(dummy, 1) + dv = dv.subs(dummy, 1) + + # Don't pick a non-polynomial algebraic to be differentiated + if rule == pull_out_algebraic and not u.is_polynomial(symbol): + return None + # Don't trade one logarithm for another + if isinstance(u, log): + rec_dv = 1/dv + if (rec_dv.is_polynomial(symbol) and + degree(rec_dv, symbol) == 1): + return None + + # Can integrate a polynomial times OrthogonalPolynomial + if rule == pull_out_algebraic: + if dv.is_Derivative or dv.has(TrigonometricFunction) or \ + isinstance(dv, OrthogonalPolynomial): + v_step = integral_steps(dv, symbol) + if v_step.contains_dont_know(): + return None + else: + du = u.diff(symbol) + v = v_step.eval() + return u, dv, v, du, v_step + + # make sure dv is amenable to integration + accept = False + if index < 2: # log and inverse trig are usually worth trying + accept = True + elif (rule == pull_out_algebraic and dv.args and + all(isinstance(a, (sin, cos, exp)) + for a in dv.args)): + accept = True + else: + for lrule in liate_rules[index + 1:]: + r = lrule(integrand) + if r and r[0].subs(dummy, 1).equals(dv): + accept = True + break + + if accept: + du = u.diff(symbol) + v_step = integral_steps(simplify(dv), symbol) + if not v_step.contains_dont_know(): + v = v_step.eval() + return u, dv, v, du, v_step + return None + + +def parts_rule(integral): + integrand, symbol = integral + constant, integrand = integrand.as_coeff_Mul() + + result = _parts_rule(integrand, symbol) + + steps = [] + if result: + u, dv, v, du, v_step = result + debug("u : {}, dv : {}, v : {}, du : {}, v_step: {}".format(u, dv, v, du, v_step)) + steps.append(result) + + if isinstance(v, Integral): + return + + # Set a limit on the number of times u can be used + if isinstance(u, (sin, cos, exp, sinh, cosh)): + cachekey = u.xreplace({symbol: _cache_dummy}) + if _parts_u_cache[cachekey] > 2: + return + _parts_u_cache[cachekey] += 1 + + # Try cyclic integration by parts a few times + for _ in range(4): + debug("Cyclic integration {} with v: {}, du: {}, integrand: {}".format(_, v, du, integrand)) + coefficient = ((v * du) / integrand).cancel() + if coefficient == 1: + break + if symbol not in coefficient.free_symbols: + rule = CyclicPartsRule(integrand, symbol, + [PartsRule(None, None, u, dv, v_step, None) + for (u, dv, v, du, v_step) in steps], + (-1) ** len(steps) * coefficient) + if (constant != 1) and rule: + rule = ConstantTimesRule(constant * integrand, symbol, constant, integrand, rule) + return rule + + # _parts_rule is sensitive to constants, factor it out + next_constant, next_integrand = (v * du).as_coeff_Mul() + result = _parts_rule(next_integrand, symbol) + + if result: + u, dv, v, du, v_step = result + u *= next_constant + du *= next_constant + steps.append((u, dv, v, du, v_step)) + else: + break + + def make_second_step(steps, integrand): + if steps: + u, dv, v, du, v_step = steps[0] + return PartsRule(integrand, symbol, u, dv, v_step, make_second_step(steps[1:], v * du)) + return integral_steps(integrand, symbol) + + if steps: + u, dv, v, du, v_step = steps[0] + rule = PartsRule(integrand, symbol, u, dv, v_step, make_second_step(steps[1:], v * du)) + if (constant != 1) and rule: + rule = ConstantTimesRule(constant * integrand, symbol, constant, integrand, rule) + return rule + + +def trig_rule(integral): + integrand, symbol = integral + if integrand == sin(symbol): + return SinRule(integrand, symbol) + if integrand == cos(symbol): + return CosRule(integrand, symbol) + if integrand == sec(symbol)**2: + return Sec2Rule(integrand, symbol) + if integrand == csc(symbol)**2: + return Csc2Rule(integrand, symbol) + + if isinstance(integrand, tan): + rewritten = sin(*integrand.args) / cos(*integrand.args) + elif isinstance(integrand, cot): + rewritten = cos(*integrand.args) / sin(*integrand.args) + elif isinstance(integrand, sec): + arg = integrand.args[0] + rewritten = ((sec(arg)**2 + tan(arg) * sec(arg)) / + (sec(arg) + tan(arg))) + elif isinstance(integrand, csc): + arg = integrand.args[0] + rewritten = ((csc(arg)**2 + cot(arg) * csc(arg)) / + (csc(arg) + cot(arg))) + else: + return + + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + +def trig_product_rule(integral: IntegralInfo): + integrand, symbol = integral + if integrand == sec(symbol) * tan(symbol): + return SecTanRule(integrand, symbol) + if integrand == csc(symbol) * cot(symbol): + return CscCotRule(integrand, symbol) + + +def quadratic_denom_rule(integral): + integrand, symbol = integral + a = Wild('a', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + c = Wild('c', exclude=[symbol]) + + match = integrand.match(a / (b * symbol ** 2 + c)) + + if match: + a, b, c = match[a], match[b], match[c] + general_rule = ArctanRule(integrand, symbol, a, b, c) + if b.is_extended_real and c.is_extended_real: + positive_cond = c/b > 0 + if positive_cond is S.true: + return general_rule + coeff = a/(2*sqrt(-c)*sqrt(b)) + constant = sqrt(-c/b) + r1 = 1/(symbol-constant) + r2 = 1/(symbol+constant) + log_steps = [ReciprocalRule(r1, symbol, symbol-constant), + ConstantTimesRule(-r2, symbol, -1, r2, ReciprocalRule(r2, symbol, symbol+constant))] + rewritten = sub = r1 - r2 + negative_step = AddRule(sub, symbol, log_steps) + if coeff != 1: + rewritten = Mul(coeff, sub, evaluate=False) + negative_step = ConstantTimesRule(rewritten, symbol, coeff, sub, negative_step) + negative_step = RewriteRule(integrand, symbol, rewritten, negative_step) + if positive_cond is S.false: + return negative_step + return PiecewiseRule(integrand, symbol, [(general_rule, positive_cond), (negative_step, S.true)]) + + power = PowerRule(integrand, symbol, symbol, -2) + if b != 1: + power = ConstantTimesRule(integrand, symbol, 1/b, symbol**-2, power) + + return PiecewiseRule(integrand, symbol, [(general_rule, Ne(c, 0)), (power, True)]) + + d = Wild('d', exclude=[symbol]) + match2 = integrand.match(a / (b * symbol ** 2 + c * symbol + d)) + if match2: + b, c = match2[b], match2[c] + if b.is_zero: + return + u = Dummy('u') + u_func = symbol + c/(2*b) + integrand2 = integrand.subs(symbol, u - c / (2*b)) + next_step = integral_steps(integrand2, u) + if next_step: + return URule(integrand2, symbol, u, u_func, next_step) + else: + return + e = Wild('e', exclude=[symbol]) + match3 = integrand.match((a* symbol + b) / (c * symbol ** 2 + d * symbol + e)) + if match3: + a, b, c, d, e = match3[a], match3[b], match3[c], match3[d], match3[e] + if c.is_zero: + return + denominator = c * symbol**2 + d * symbol + e + const = a/(2*c) + numer1 = (2*c*symbol+d) + numer2 = - const*d + b + u = Dummy('u') + step1 = URule(integrand, symbol, + u, denominator, integral_steps(u**(-1), u)) + if const != 1: + step1 = ConstantTimesRule(const*numer1/denominator, symbol, + const, numer1/denominator, step1) + if numer2.is_zero: + return step1 + step2 = integral_steps(numer2/denominator, symbol) + substeps = AddRule(integrand, symbol, [step1, step2]) + rewriten = const*numer1/denominator+numer2/denominator + return RewriteRule(integrand, symbol, rewriten, substeps) + + return + + +def sqrt_linear_rule(integral: IntegralInfo): + """ + Substitute common (a+b*x)**(1/n) + """ + integrand, x = integral + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x, 0]) + a0 = b0 = 0 + bases, qs, bs = [], [], [] + for pow_ in integrand.find(Pow): # collect all (a+b*x)**(p/q) + base, exp_ = pow_.base, pow_.exp + if exp_.is_Integer or x not in base.free_symbols: # skip 1/x and sqrt(2) + continue + if not exp_.is_Rational: # exclude x**pi + return + match = base.match(a+b*x) + if not match: # skip non-linear + continue # for sqrt(x+sqrt(x)), although base is non-linear, we can still substitute sqrt(x) + a1, b1 = match[a], match[b] + if a0*b1 != a1*b0 or not (b0/b1).is_nonnegative: # cannot transform sqrt(x) to sqrt(x+1) or sqrt(-x) + return + if b0 == 0 or (b0/b1 > 1) is S.true: # choose the latter of sqrt(2*x) and sqrt(x) as representative + a0, b0 = a1, b1 + bases.append(base) + bs.append(b1) + qs.append(exp_.q) + if b0 == 0: # no such pattern found + return + q0: Integer = lcm_list(qs) + u_x = (a0 + b0*x)**(1/q0) + u = Dummy("u") + substituted = integrand.subs({base**(S.One/q): (b/b0)**(S.One/q)*u**(q0/q) + for base, b, q in zip(bases, bs, qs)}).subs(x, (u**q0-a0)/b0) + substep = integral_steps(substituted*u**(q0-1)*q0/b0, u) + if not substep.contains_dont_know(): + step: Rule = URule(integrand, x, u, u_x, substep) + generic_cond = Ne(b0, 0) + if generic_cond is not S.true: # possible degenerate case + simplified = integrand.subs(dict.fromkeys(bs, 0)) + degenerate_step = integral_steps(simplified, x) + step = PiecewiseRule(integrand, x, [(step, generic_cond), (degenerate_step, S.true)]) + return step + + +def sqrt_quadratic_rule(integral: IntegralInfo, degenerate=True): + integrand, x = integral + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + c = Wild('c', exclude=[x, 0]) + f = Wild('f') + n = Wild('n', properties=[lambda n: n.is_Integer and n.is_odd]) + match = integrand.match(f*sqrt(a+b*x+c*x**2)**n) + if not match: + return + a, b, c, f, n = match[a], match[b], match[c], match[f], match[n] + f_poly = f.as_poly(x) + if f_poly is None: + return + + generic_cond = Ne(c, 0) + if not degenerate or generic_cond is S.true: + degenerate_step = None + elif b.is_zero: + degenerate_step = integral_steps(f*sqrt(a)**n, x) + else: + degenerate_step = sqrt_linear_rule(IntegralInfo(f*sqrt(a+b*x)**n, x)) + + def sqrt_quadratic_denom_rule(numer_poly: Poly, integrand: Expr): + denom = sqrt(a+b*x+c*x**2) + deg = numer_poly.degree() + if deg <= 1: + # integrand == (d+e*x)/sqrt(a+b*x+c*x**2) + e, d = numer_poly.all_coeffs() if deg == 1 else (S.Zero, numer_poly.as_expr()) + # rewrite numerator to A*(2*c*x+b) + B + A = e/(2*c) + B = d-A*b + pre_substitute = (2*c*x+b)/denom + constant_step: Rule | None = None + linear_step: Rule | None = None + if A != 0: + u = Dummy("u") + pow_rule = PowerRule(1/sqrt(u), u, u, -S.Half) + linear_step = URule(pre_substitute, x, u, a+b*x+c*x**2, pow_rule) + if A != 1: + linear_step = ConstantTimesRule(A*pre_substitute, x, A, pre_substitute, linear_step) + if B != 0: + constant_step = inverse_trig_rule(IntegralInfo(1/denom, x), degenerate=False) + if B != 1: + constant_step = ConstantTimesRule(B/denom, x, B, 1/denom, constant_step) # type: ignore + if linear_step and constant_step: + add = Add(A*pre_substitute, B/denom, evaluate=False) + step: Rule | None = RewriteRule(integrand, x, add, AddRule(add, x, [linear_step, constant_step])) + else: + step = linear_step or constant_step + else: + coeffs = numer_poly.all_coeffs() + step = SqrtQuadraticDenomRule(integrand, x, a, b, c, coeffs) + return step + + if n > 0: # rewrite poly * sqrt(s)**(2*k-1) to poly*s**k / sqrt(s) + numer_poly = f_poly * (a+b*x+c*x**2)**((n+1)/2) + rewritten = numer_poly.as_expr()/sqrt(a+b*x+c*x**2) + substep = sqrt_quadratic_denom_rule(numer_poly, rewritten) + generic_step = RewriteRule(integrand, x, rewritten, substep) + elif n == -1: + generic_step = sqrt_quadratic_denom_rule(f_poly, integrand) + else: + return # todo: handle n < -1 case + return _add_degenerate_step(generic_cond, generic_step, degenerate_step) + + +def hyperbolic_rule(integral: tuple[Expr, Symbol]): + integrand, symbol = integral + if isinstance(integrand, HyperbolicFunction) and integrand.args[0] == symbol: + if integrand.func == sinh: + return SinhRule(integrand, symbol) + if integrand.func == cosh: + return CoshRule(integrand, symbol) + u = Dummy('u') + if integrand.func == tanh: + rewritten = sinh(symbol)/cosh(symbol) + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, cosh(symbol), ReciprocalRule(1/u, u, u))) + if integrand.func == coth: + rewritten = cosh(symbol)/sinh(symbol) + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, sinh(symbol), ReciprocalRule(1/u, u, u))) + else: + rewritten = integrand.rewrite(tanh) + if integrand.func == sech: + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, tanh(symbol/2), + ArctanRule(2/(u**2 + 1), u, S(2), S.One, S.One))) + if integrand.func == csch: + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, tanh(symbol/2), + ReciprocalRule(1/u, u, u))) + +@cacheit +def make_wilds(symbol): + a = Wild('a', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + m = Wild('m', exclude=[symbol], properties=[lambda n: isinstance(n, Integer)]) + n = Wild('n', exclude=[symbol], properties=[lambda n: isinstance(n, Integer)]) + + return a, b, m, n + +@cacheit +def sincos_pattern(symbol): + a, b, m, n = make_wilds(symbol) + pattern = sin(a*symbol)**m * cos(b*symbol)**n + + return pattern, a, b, m, n + +@cacheit +def tansec_pattern(symbol): + a, b, m, n = make_wilds(symbol) + pattern = tan(a*symbol)**m * sec(b*symbol)**n + + return pattern, a, b, m, n + +@cacheit +def cotcsc_pattern(symbol): + a, b, m, n = make_wilds(symbol) + pattern = cot(a*symbol)**m * csc(b*symbol)**n + + return pattern, a, b, m, n + +@cacheit +def heaviside_pattern(symbol): + m = Wild('m', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + g = Wild('g') + pattern = Heaviside(m*symbol + b) * g + + return pattern, m, b, g + +def uncurry(func): + def uncurry_rl(args): + return func(*args) + return uncurry_rl + +def trig_rewriter(rewrite): + def trig_rewriter_rl(args): + a, b, m, n, integrand, symbol = args + rewritten = rewrite(a, b, m, n, integrand, symbol) + if rewritten != integrand: + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + return trig_rewriter_rl + +sincos_botheven_condition = uncurry( + lambda a, b, m, n, i, s: m.is_even and n.is_even and + m.is_nonnegative and n.is_nonnegative) + +sincos_botheven = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (((1 - cos(2*a*symbol)) / 2) ** (m / 2)) * + (((1 + cos(2*b*symbol)) / 2) ** (n / 2)) )) + +sincos_sinodd_condition = uncurry(lambda a, b, m, n, i, s: m.is_odd and m >= 3) + +sincos_sinodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 - cos(a*symbol)**2)**((m - 1) / 2) * + sin(a*symbol) * + cos(b*symbol) ** n)) + +sincos_cosodd_condition = uncurry(lambda a, b, m, n, i, s: n.is_odd and n >= 3) + +sincos_cosodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 - sin(b*symbol)**2)**((n - 1) / 2) * + cos(b*symbol) * + sin(a*symbol) ** m)) + +tansec_seceven_condition = uncurry(lambda a, b, m, n, i, s: n.is_even and n >= 4) +tansec_seceven = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 + tan(b*symbol)**2) ** (n/2 - 1) * + sec(b*symbol)**2 * + tan(a*symbol) ** m )) + +tansec_tanodd_condition = uncurry(lambda a, b, m, n, i, s: m.is_odd) +tansec_tanodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (sec(a*symbol)**2 - 1) ** ((m - 1) / 2) * + tan(a*symbol) * + sec(b*symbol) ** n )) + +tan_tansquared_condition = uncurry(lambda a, b, m, n, i, s: m == 2 and n == 0) +tan_tansquared = trig_rewriter( + lambda a, b, m, n, i, symbol: ( sec(a*symbol)**2 - 1)) + +cotcsc_csceven_condition = uncurry(lambda a, b, m, n, i, s: n.is_even and n >= 4) +cotcsc_csceven = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 + cot(b*symbol)**2) ** (n/2 - 1) * + csc(b*symbol)**2 * + cot(a*symbol) ** m )) + +cotcsc_cotodd_condition = uncurry(lambda a, b, m, n, i, s: m.is_odd) +cotcsc_cotodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (csc(a*symbol)**2 - 1) ** ((m - 1) / 2) * + cot(a*symbol) * + csc(b*symbol) ** n )) + +def trig_sincos_rule(integral): + integrand, symbol = integral + + if any(integrand.has(f) for f in (sin, cos)): + pattern, a, b, m, n = sincos_pattern(symbol) + match = integrand.match(pattern) + if not match: + return + + return multiplexer({ + sincos_botheven_condition: sincos_botheven, + sincos_sinodd_condition: sincos_sinodd, + sincos_cosodd_condition: sincos_cosodd + })(tuple( + [match.get(i, S.Zero) for i in (a, b, m, n)] + + [integrand, symbol])) + +def trig_tansec_rule(integral): + integrand, symbol = integral + + integrand = integrand.subs({ + 1 / cos(symbol): sec(symbol) + }) + + if any(integrand.has(f) for f in (tan, sec)): + pattern, a, b, m, n = tansec_pattern(symbol) + match = integrand.match(pattern) + if not match: + return + + return multiplexer({ + tansec_tanodd_condition: tansec_tanodd, + tansec_seceven_condition: tansec_seceven, + tan_tansquared_condition: tan_tansquared + })(tuple( + [match.get(i, S.Zero) for i in (a, b, m, n)] + + [integrand, symbol])) + +def trig_cotcsc_rule(integral): + integrand, symbol = integral + integrand = integrand.subs({ + 1 / sin(symbol): csc(symbol), + 1 / tan(symbol): cot(symbol), + cos(symbol) / tan(symbol): cot(symbol) + }) + + if any(integrand.has(f) for f in (cot, csc)): + pattern, a, b, m, n = cotcsc_pattern(symbol) + match = integrand.match(pattern) + if not match: + return + + return multiplexer({ + cotcsc_cotodd_condition: cotcsc_cotodd, + cotcsc_csceven_condition: cotcsc_csceven + })(tuple( + [match.get(i, S.Zero) for i in (a, b, m, n)] + + [integrand, symbol])) + +def trig_sindouble_rule(integral): + integrand, symbol = integral + a = Wild('a', exclude=[sin(2*symbol)]) + match = integrand.match(sin(2*symbol)*a) + if match: + sin_double = 2*sin(symbol)*cos(symbol)/sin(2*symbol) + return integral_steps(integrand * sin_double, symbol) + +def trig_powers_products_rule(integral): + return do_one(null_safe(trig_sincos_rule), + null_safe(trig_tansec_rule), + null_safe(trig_cotcsc_rule), + null_safe(trig_sindouble_rule))(integral) + +def trig_substitution_rule(integral): + integrand, symbol = integral + A = Wild('a', exclude=[0, symbol]) + B = Wild('b', exclude=[0, symbol]) + theta = Dummy("theta") + target_pattern = A + B*symbol**2 + + matches = integrand.find(target_pattern) + for expr in matches: + match = expr.match(target_pattern) + a = match.get(A, S.Zero) + b = match.get(B, S.Zero) + + a_positive = ((a.is_number and a > 0) or a.is_positive) + b_positive = ((b.is_number and b > 0) or b.is_positive) + a_negative = ((a.is_number and a < 0) or a.is_negative) + b_negative = ((b.is_number and b < 0) or b.is_negative) + x_func = None + if a_positive and b_positive: + # a**2 + b*x**2. Assume sec(theta) > 0, -pi/2 < theta < pi/2 + x_func = (sqrt(a)/sqrt(b)) * tan(theta) + # Do not restrict the domain: tan(theta) takes on any real + # value on the interval -pi/2 < theta < pi/2 so x takes on + # any value + restriction = True + elif a_positive and b_negative: + # a**2 - b*x**2. Assume cos(theta) > 0, -pi/2 < theta < pi/2 + constant = sqrt(a)/sqrt(-b) + x_func = constant * sin(theta) + restriction = And(symbol > -constant, symbol < constant) + elif a_negative and b_positive: + # b*x**2 - a**2. Assume sin(theta) > 0, 0 < theta < pi + constant = sqrt(-a)/sqrt(b) + x_func = constant * sec(theta) + restriction = And(symbol > -constant, symbol < constant) + if x_func: + # Manually simplify sqrt(trig(theta)**2) to trig(theta) + # Valid due to assumed domain restriction + substitutions = {} + for f in [sin, cos, tan, + sec, csc, cot]: + substitutions[sqrt(f(theta)**2)] = f(theta) + substitutions[sqrt(f(theta)**(-2))] = 1/f(theta) + + replaced = integrand.subs(symbol, x_func).trigsimp() + replaced = manual_subs(replaced, substitutions) + if not replaced.has(symbol): + replaced *= manual_diff(x_func, theta) + replaced = replaced.trigsimp() + secants = replaced.find(1/cos(theta)) + if secants: + replaced = replaced.xreplace({ + 1/cos(theta): sec(theta) + }) + + substep = integral_steps(replaced, theta) + if not substep.contains_dont_know(): + return TrigSubstitutionRule(integrand, symbol, + theta, x_func, replaced, substep, restriction) + +def heaviside_rule(integral): + integrand, symbol = integral + pattern, m, b, g = heaviside_pattern(symbol) + match = integrand.match(pattern) + if match and 0 != match[g]: + # f = Heaviside(m*x + b)*g + substep = integral_steps(match[g], symbol) + m, b = match[m], match[b] + return HeavisideRule(integrand, symbol, m*symbol + b, -b/m, substep) + + +def dirac_delta_rule(integral: IntegralInfo): + integrand, x = integral + if len(integrand.args) == 1: + n = S.Zero + else: + n = integrand.args[1] # type: ignore + if not n.is_Integer or n < 0: + return + a, b = Wild('a', exclude=[x]), Wild('b', exclude=[x, 0]) + match = integrand.args[0].match(a+b*x) + if not match: + return + a, b = match[a], match[b] + generic_cond = Ne(b, 0) + if generic_cond is S.true: + degenerate_step = None + else: + degenerate_step = ConstantRule(DiracDelta(a, n), x) + generic_step = DiracDeltaRule(integrand, x, n, a, b) + return _add_degenerate_step(generic_cond, generic_step, degenerate_step) + + +def substitution_rule(integral): + integrand, symbol = integral + + u_var = Dummy("u") + substitutions = find_substitutions(integrand, symbol, u_var) + count = 0 + if substitutions: + debug("List of Substitution Rules") + ways = [] + for u_func, c, substituted in substitutions: + subrule = integral_steps(substituted, u_var) + count = count + 1 + debug("Rule {}: {}".format(count, subrule)) + + if subrule.contains_dont_know(): + continue + + if simplify(c - 1) != 0: + _, denom = c.as_numer_denom() + if subrule: + subrule = ConstantTimesRule(c * substituted, u_var, c, substituted, subrule) + + if denom.free_symbols: + piecewise = [] + could_be_zero = [] + + if isinstance(denom, Mul): + could_be_zero = denom.args + else: + could_be_zero.append(denom) + + for expr in could_be_zero: + if not fuzzy_not(expr.is_zero): + substep = integral_steps(manual_subs(integrand, expr, 0), symbol) + + if substep: + piecewise.append(( + substep, + Eq(expr, 0) + )) + piecewise.append((subrule, True)) + subrule = PiecewiseRule(substituted, symbol, piecewise) + + ways.append(URule(integrand, symbol, u_var, u_func, subrule)) + + if len(ways) > 1: + return AlternativeRule(integrand, symbol, ways) + elif ways: + return ways[0] + + +partial_fractions_rule = rewriter( + lambda integrand, symbol: integrand.is_rational_function(), + lambda integrand, symbol: integrand.apart(symbol)) + +cancel_rule = rewriter( + # lambda integrand, symbol: integrand.is_algebraic_expr(), + # lambda integrand, symbol: isinstance(integrand, Mul), + lambda integrand, symbol: True, + lambda integrand, symbol: integrand.cancel()) + +distribute_expand_rule = rewriter( + lambda integrand, symbol: ( + isinstance(integrand, (Pow, Mul)) or all(arg.is_Pow or arg.is_polynomial(symbol) for arg in integrand.args)), + lambda integrand, symbol: integrand.expand()) + +trig_expand_rule = rewriter( + # If there are trig functions with different arguments, expand them + lambda integrand, symbol: ( + len({a.args[0] for a in integrand.atoms(TrigonometricFunction)}) > 1), + lambda integrand, symbol: integrand.expand(trig=True)) + +def derivative_rule(integral): + integrand = integral[0] + diff_variables = integrand.variables + undifferentiated_function = integrand.expr + integrand_variables = undifferentiated_function.free_symbols + + if integral.symbol in integrand_variables: + if integral.symbol in diff_variables: + return DerivativeRule(*integral) + else: + return DontKnowRule(integrand, integral.symbol) + else: + return ConstantRule(*integral) + +def rewrites_rule(integral): + integrand, symbol = integral + + if integrand.match(1/cos(symbol)): + rewritten = integrand.subs(1/cos(symbol), sec(symbol)) + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + +def fallback_rule(integral): + return DontKnowRule(*integral) + +# Cache is used to break cyclic integrals. +# Need to use the same dummy variable in cached expressions for them to match. +# Also record "u" of integration by parts, to avoid infinite repetition. +_integral_cache: dict[Expr, Expr | None] = {} +_parts_u_cache: dict[Expr, int] = defaultdict(int) +_cache_dummy = Dummy("z") + +def integral_steps(integrand, symbol, **options): + """Returns the steps needed to compute an integral. + + Explanation + =========== + + This function attempts to mirror what a student would do by hand as + closely as possible. + + SymPy Gamma uses this to provide a step-by-step explanation of an + integral. The code it uses to format the results of this function can be + found at + https://github.com/sympy/sympy_gamma/blob/master/app/logic/intsteps.py. + + Examples + ======== + + >>> from sympy import exp, sin + >>> from sympy.integrals.manualintegrate import integral_steps + >>> from sympy.abc import x + >>> print(repr(integral_steps(exp(x) / (1 + exp(2 * x)), x))) \ + # doctest: +NORMALIZE_WHITESPACE + URule(integrand=exp(x)/(exp(2*x) + 1), variable=x, u_var=_u, u_func=exp(x), + substep=ArctanRule(integrand=1/(_u**2 + 1), variable=_u, a=1, b=1, c=1)) + >>> print(repr(integral_steps(sin(x), x))) \ + # doctest: +NORMALIZE_WHITESPACE + SinRule(integrand=sin(x), variable=x) + >>> print(repr(integral_steps((x**2 + 3)**2, x))) \ + # doctest: +NORMALIZE_WHITESPACE + RewriteRule(integrand=(x**2 + 3)**2, variable=x, rewritten=x**4 + 6*x**2 + 9, + substep=AddRule(integrand=x**4 + 6*x**2 + 9, variable=x, + substeps=[PowerRule(integrand=x**4, variable=x, base=x, exp=4), + ConstantTimesRule(integrand=6*x**2, variable=x, constant=6, other=x**2, + substep=PowerRule(integrand=x**2, variable=x, base=x, exp=2)), + ConstantRule(integrand=9, variable=x)])) + + Returns + ======= + + rule : Rule + The first step; most rules have substeps that must also be + considered. These substeps can be evaluated using ``manualintegrate`` + to obtain a result. + + """ + cachekey = integrand.xreplace({symbol: _cache_dummy}) + if cachekey in _integral_cache: + if _integral_cache[cachekey] is None: + # Stop this attempt, because it leads around in a loop + return DontKnowRule(integrand, symbol) + else: + # TODO: This is for future development, as currently + # _integral_cache gets no values other than None + return (_integral_cache[cachekey].xreplace(_cache_dummy, symbol), + symbol) + else: + _integral_cache[cachekey] = None + + integral = IntegralInfo(integrand, symbol) + + def key(integral): + integrand = integral.integrand + + if symbol not in integrand.free_symbols: + return Number + for cls in (Symbol, TrigonometricFunction, OrthogonalPolynomial): + if isinstance(integrand, cls): + return cls + return type(integrand) + + def integral_is_subclass(*klasses): + def _integral_is_subclass(integral): + k = key(integral) + return k and issubclass(k, klasses) + return _integral_is_subclass + + result = do_one( + null_safe(special_function_rule), + null_safe(switch(key, { + Pow: do_one(null_safe(power_rule), null_safe(inverse_trig_rule), + null_safe(sqrt_linear_rule), + null_safe(quadratic_denom_rule)), + Symbol: power_rule, + exp: exp_rule, + Add: add_rule, + Mul: do_one(null_safe(mul_rule), null_safe(trig_product_rule), + null_safe(heaviside_rule), null_safe(quadratic_denom_rule), + null_safe(sqrt_linear_rule), + null_safe(sqrt_quadratic_rule)), + Derivative: derivative_rule, + TrigonometricFunction: trig_rule, + Heaviside: heaviside_rule, + DiracDelta: dirac_delta_rule, + OrthogonalPolynomial: orthogonal_poly_rule, + Number: constant_rule + })), + do_one( + null_safe(trig_rule), + null_safe(hyperbolic_rule), + null_safe(alternatives( + rewrites_rule, + substitution_rule, + condition( + integral_is_subclass(Mul, Pow), + partial_fractions_rule), + condition( + integral_is_subclass(Mul, Pow), + cancel_rule), + condition( + integral_is_subclass(Mul, log, + *inverse_trig_functions), + parts_rule), + condition( + integral_is_subclass(Mul, Pow), + distribute_expand_rule), + trig_powers_products_rule, + trig_expand_rule + )), + null_safe(condition(integral_is_subclass(Mul, Pow), nested_pow_rule)), + null_safe(trig_substitution_rule) + ), + fallback_rule)(integral) + del _integral_cache[cachekey] + return result + + +def manualintegrate(f, var): + """manualintegrate(f, var) + + Explanation + =========== + + Compute indefinite integral of a single variable using an algorithm that + resembles what a student would do by hand. + + Unlike :func:`~.integrate`, var can only be a single symbol. + + Examples + ======== + + >>> from sympy import sin, cos, tan, exp, log, integrate + >>> from sympy.integrals.manualintegrate import manualintegrate + >>> from sympy.abc import x + >>> manualintegrate(1 / x, x) + log(x) + >>> integrate(1/x) + log(x) + >>> manualintegrate(log(x), x) + x*log(x) - x + >>> integrate(log(x)) + x*log(x) - x + >>> manualintegrate(exp(x) / (1 + exp(2 * x)), x) + atan(exp(x)) + >>> integrate(exp(x) / (1 + exp(2 * x))) + RootSum(4*_z**2 + 1, Lambda(_i, _i*log(2*_i + exp(x)))) + >>> manualintegrate(cos(x)**4 * sin(x), x) + -cos(x)**5/5 + >>> integrate(cos(x)**4 * sin(x), x) + -cos(x)**5/5 + >>> manualintegrate(cos(x)**4 * sin(x)**3, x) + cos(x)**7/7 - cos(x)**5/5 + >>> integrate(cos(x)**4 * sin(x)**3, x) + cos(x)**7/7 - cos(x)**5/5 + >>> manualintegrate(tan(x), x) + -log(cos(x)) + >>> integrate(tan(x), x) + -log(cos(x)) + + See Also + ======== + + sympy.integrals.integrals.integrate + sympy.integrals.integrals.Integral.doit + sympy.integrals.integrals.Integral + """ + result = integral_steps(f, var).eval() + # Clear the cache of u-parts + _parts_u_cache.clear() + # If we got Piecewise with two parts, put generic first + if isinstance(result, Piecewise) and len(result.args) == 2: + cond = result.args[0][1] + if isinstance(cond, Eq) and result.args[1][1] == True: + result = result.func( + (result.args[1][0], Ne(*cond.args)), + (result.args[0][0], True)) + return result diff --git a/phivenv/Lib/site-packages/sympy/integrals/meijerint.py b/phivenv/Lib/site-packages/sympy/integrals/meijerint.py new file mode 100644 index 0000000000000000000000000000000000000000..89d8401c7eee0147df2e824dc1dc50b59ca7f0be --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/meijerint.py @@ -0,0 +1,2191 @@ +""" +Integrate functions by rewriting them as Meijer G-functions. + +There are three user-visible functions that can be used by other parts of the +sympy library to solve various integration problems: + +- meijerint_indefinite +- meijerint_definite +- meijerint_inversion + +They can be used to compute, respectively, indefinite integrals, definite +integrals over intervals of the real line, and inverse laplace-type integrals +(from c-I*oo to c+I*oo). See the respective docstrings for details. + +The main references for this are: + +[L] Luke, Y. L. (1969), The Special Functions and Their Approximations, + Volume 1 + +[R] Kelly B. Roach. Meijer G Function Representations. + In: Proceedings of the 1997 International Symposium on Symbolic and + Algebraic Computation, pages 205-211, New York, 1997. ACM. + +[P] A. P. Prudnikov, Yu. A. Brychkov and O. I. Marichev (1990). + Integrals and Series: More Special Functions, Vol. 3,. + Gordon and Breach Science Publisher +""" + +from __future__ import annotations +import itertools + +from sympy import SYMPY_DEBUG +from sympy.core import S, Expr +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.containers import Tuple +from sympy.core.exprtools import factor_terms +from sympy.core.function import (expand, expand_mul, expand_power_base, + expand_trig, Function) +from sympy.core.mul import Mul +from sympy.core.intfunc import ilcm +from sympy.core.numbers import Rational, pi +from sympy.core.relational import Eq, Ne, _canonical_coeff +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import Dummy, symbols, Wild, Symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (re, im, arg, Abs, sign, + unpolarify, polarify, polar_lift, principal_branch, unbranched_argument, + periodic_argument) +from sympy.functions.elementary.exponential import exp, exp_polar, log +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.hyperbolic import (cosh, sinh, + _rewrite_hyperbolics_as_exp, HyperbolicFunction) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold +from sympy.functions.elementary.trigonometric import (cos, sin, sinc, + TrigonometricFunction) +from sympy.functions.special.bessel import besselj, bessely, besseli, besselk +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.elliptic_integrals import elliptic_k, elliptic_e +from sympy.functions.special.error_functions import (erf, erfc, erfi, Ei, + expint, Si, Ci, Shi, Chi, fresnels, fresnelc) +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import hyper, meijerg +from sympy.functions.special.singularity_functions import SingularityFunction +from .integrals import Integral +from sympy.logic.boolalg import And, Or, BooleanAtom, Not, BooleanFunction +from sympy.polys import cancel, factor +from sympy.utilities.iterables import multiset_partitions +from sympy.utilities.misc import debug as _debug +from sympy.utilities.misc import debugf as _debugf + +# keep this at top for easy reference +z = Dummy('z') + + +def _has(res, *f): + # return True if res has f; in the case of Piecewise + # only return True if *all* pieces have f + res = piecewise_fold(res) + if getattr(res, 'is_Piecewise', False): + return all(_has(i, *f) for i in res.args) + return res.has(*f) + + +def _create_lookup_table(table): + """ Add formulae for the function -> meijerg lookup table. """ + def wild(n): + return Wild(n, exclude=[z]) + p, q, a, b, c = list(map(wild, 'pqabc')) + n = Wild('n', properties=[lambda x: x.is_Integer and x > 0]) + t = p*z**q + + def add(formula, an, ap, bm, bq, arg=t, fac=S.One, cond=True, hint=True): + table.setdefault(_mytype(formula, z), []).append((formula, + [(fac, meijerg(an, ap, bm, bq, arg))], cond, hint)) + + def addi(formula, inst, cond, hint=True): + table.setdefault( + _mytype(formula, z), []).append((formula, inst, cond, hint)) + + def constant(a): + return [(a, meijerg([1], [], [], [0], z)), + (a, meijerg([], [1], [0], [], z))] + table[()] = [(a, constant(a), True, True)] + + # [P], Section 8. + class IsNonPositiveInteger(Function): + + @classmethod + def eval(cls, arg): + arg = unpolarify(arg) + if arg.is_Integer is True: + return arg <= 0 + + # Section 8.4.2 + # TODO this needs more polar_lift (c/f entry for exp) + add(Heaviside(t - b)*(t - b)**(a - 1), [a], [], [], [0], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add(Heaviside(b - t)*(b - t)**(a - 1), [], [a], [0], [], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add(Heaviside(z - (b/p)**(1/q))*(t - b)**(a - 1), [a], [], [], [0], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add(Heaviside((b/p)**(1/q) - z)*(b - t)**(a - 1), [], [a], [0], [], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add((b + t)**(-a), [1 - a], [], [0], [], t/b, b**(-a)/gamma(a), + hint=Not(IsNonPositiveInteger(a))) + add(Abs(b - t)**(-a), [1 - a], [(1 - a)/2], [0], [(1 - a)/2], t/b, + 2*sin(pi*a/2)*gamma(1 - a)*Abs(b)**(-a), re(a) < 1) + add((t**a - b**a)/(t - b), [0, a], [], [0, a], [], t/b, + b**(a - 1)*sin(a*pi)/pi) + + # 12 + def A1(r, sign, nu): + return pi**Rational(-1, 2)*(-sign*nu/2)**(1 - 2*r) + + def tmpadd(r, sgn): + # XXX the a**2 is bad for matching + add((sqrt(a**2 + t) + sgn*a)**b/(a**2 + t)**r, + [(1 + b)/2, 1 - 2*r + b/2], [], + [(b - sgn*b)/2], [(b + sgn*b)/2], t/a**2, + a**(b - 2*r)*A1(r, sgn, b)) + tmpadd(0, 1) + tmpadd(0, -1) + tmpadd(S.Half, 1) + tmpadd(S.Half, -1) + + # 13 + def tmpadd(r, sgn): + add((sqrt(a + p*z**q) + sgn*sqrt(p)*z**(q/2))**b/(a + p*z**q)**r, + [1 - r + sgn*b/2], [1 - r - sgn*b/2], [0, S.Half], [], + p*z**q/a, a**(b/2 - r)*A1(r, sgn, b)) + tmpadd(0, 1) + tmpadd(0, -1) + tmpadd(S.Half, 1) + tmpadd(S.Half, -1) + # (those after look obscure) + + # Section 8.4.3 + add(exp(polar_lift(-1)*t), [], [], [0], []) + + # TODO can do sin^n, sinh^n by expansion ... where? + # 8.4.4 (hyperbolic functions) + add(sinh(t), [], [1], [S.Half], [1, 0], t**2/4, pi**Rational(3, 2)) + add(cosh(t), [], [S.Half], [0], [S.Half, S.Half], t**2/4, pi**Rational(3, 2)) + + # Section 8.4.5 + # TODO can do t + a. but can also do by expansion... (XXX not really) + add(sin(t), [], [], [S.Half], [0], t**2/4, sqrt(pi)) + add(cos(t), [], [], [0], [S.Half], t**2/4, sqrt(pi)) + + # Section 8.4.6 (sinc function) + add(sinc(t), [], [], [0], [Rational(-1, 2)], t**2/4, sqrt(pi)/2) + + # Section 8.5.5 + def make_log1(subs): + N = subs[n] + return [(S.NegativeOne**N*factorial(N), + meijerg([], [1]*(N + 1), [0]*(N + 1), [], t))] + + def make_log2(subs): + N = subs[n] + return [(factorial(N), + meijerg([1]*(N + 1), [], [], [0]*(N + 1), t))] + # TODO these only hold for positive p, and can be made more general + # but who uses log(x)*Heaviside(a-x) anyway ... + # TODO also it would be nice to derive them recursively ... + addi(log(t)**n*Heaviside(1 - t), make_log1, True) + addi(log(t)**n*Heaviside(t - 1), make_log2, True) + + def make_log3(subs): + return make_log1(subs) + make_log2(subs) + addi(log(t)**n, make_log3, True) + addi(log(t + a), + constant(log(a)) + [(S.One, meijerg([1, 1], [], [1], [0], t/a))], + True) + addi(log(Abs(t - a)), constant(log(Abs(a))) + + [(pi, meijerg([1, 1], [S.Half], [1], [0, S.Half], t/a))], + True) + # TODO log(x)/(x+a) and log(x)/(x-1) can also be done. should they + # be derivable? + # TODO further formulae in this section seem obscure + + # Sections 8.4.9-10 + # TODO + + # Section 8.4.11 + addi(Ei(t), + constant(-S.ImaginaryUnit*pi) + [(S.NegativeOne, meijerg([], [1], [0, 0], [], + t*polar_lift(-1)))], + True) + + # Section 8.4.12 + add(Si(t), [1], [], [S.Half], [0, 0], t**2/4, sqrt(pi)/2) + add(Ci(t), [], [1], [0, 0], [S.Half], t**2/4, -sqrt(pi)/2) + + # Section 8.4.13 + add(Shi(t), [S.Half], [], [0], [Rational(-1, 2), Rational(-1, 2)], polar_lift(-1)*t**2/4, + t*sqrt(pi)/4) + add(Chi(t), [], [S.Half, 1], [0, 0], [S.Half, S.Half], t**2/4, - + pi**S('3/2')/2) + + # generalized exponential integral + add(expint(a, t), [], [a], [a - 1, 0], [], t) + + # Section 8.4.14 + add(erf(t), [1], [], [S.Half], [0], t**2, 1/sqrt(pi)) + # TODO exp(-x)*erf(I*x) does not work + add(erfc(t), [], [1], [0, S.Half], [], t**2, 1/sqrt(pi)) + # This formula for erfi(z) yields a wrong(?) minus sign + #add(erfi(t), [1], [], [S.Half], [0], -t**2, I/sqrt(pi)) + add(erfi(t), [S.Half], [], [0], [Rational(-1, 2)], -t**2, t/sqrt(pi)) + + # Fresnel Integrals + add(fresnels(t), [1], [], [Rational(3, 4)], [0, Rational(1, 4)], pi**2*t**4/16, S.Half) + add(fresnelc(t), [1], [], [Rational(1, 4)], [0, Rational(3, 4)], pi**2*t**4/16, S.Half) + + ##### bessel-type functions ##### + # Section 8.4.19 + add(besselj(a, t), [], [], [a/2], [-a/2], t**2/4) + + # all of the following are derivable + #add(sin(t)*besselj(a, t), [Rational(1, 4), Rational(3, 4)], [], [(1+a)/2], + # [-a/2, a/2, (1-a)/2], t**2, 1/sqrt(2)) + #add(cos(t)*besselj(a, t), [Rational(1, 4), Rational(3, 4)], [], [a/2], + # [-a/2, (1+a)/2, (1-a)/2], t**2, 1/sqrt(2)) + #add(besselj(a, t)**2, [S.Half], [], [a], [-a, 0], t**2, 1/sqrt(pi)) + #add(besselj(a, t)*besselj(b, t), [0, S.Half], [], [(a + b)/2], + # [-(a+b)/2, (a - b)/2, (b - a)/2], t**2, 1/sqrt(pi)) + + # Section 8.4.20 + add(bessely(a, t), [], [-(a + 1)/2], [a/2, -a/2], [-(a + 1)/2], t**2/4) + + # TODO all of the following should be derivable + #add(sin(t)*bessely(a, t), [Rational(1, 4), Rational(3, 4)], [(1 - a - 1)/2], + # [(1 + a)/2, (1 - a)/2], [(1 - a - 1)/2, (1 - 1 - a)/2, (1 - 1 + a)/2], + # t**2, 1/sqrt(2)) + #add(cos(t)*bessely(a, t), [Rational(1, 4), Rational(3, 4)], [(0 - a - 1)/2], + # [(0 + a)/2, (0 - a)/2], [(0 - a - 1)/2, (1 - 0 - a)/2, (1 - 0 + a)/2], + # t**2, 1/sqrt(2)) + #add(besselj(a, t)*bessely(b, t), [0, S.Half], [(a - b - 1)/2], + # [(a + b)/2, (a - b)/2], [(a - b - 1)/2, -(a + b)/2, (b - a)/2], + # t**2, 1/sqrt(pi)) + #addi(bessely(a, t)**2, + # [(2/sqrt(pi), meijerg([], [S.Half, S.Half - a], [0, a, -a], + # [S.Half - a], t**2)), + # (1/sqrt(pi), meijerg([S.Half], [], [a], [-a, 0], t**2))], + # True) + #addi(bessely(a, t)*bessely(b, t), + # [(2/sqrt(pi), meijerg([], [0, S.Half, (1 - a - b)/2], + # [(a + b)/2, (a - b)/2, (b - a)/2, -(a + b)/2], + # [(1 - a - b)/2], t**2)), + # (1/sqrt(pi), meijerg([0, S.Half], [], [(a + b)/2], + # [-(a + b)/2, (a - b)/2, (b - a)/2], t**2))], + # True) + + # Section 8.4.21 ? + # Section 8.4.22 + add(besseli(a, t), [], [(1 + a)/2], [a/2], [-a/2, (1 + a)/2], t**2/4, pi) + # TODO many more formulas. should all be derivable + + # Section 8.4.23 + add(besselk(a, t), [], [], [a/2, -a/2], [], t**2/4, S.Half) + # TODO many more formulas. should all be derivable + + # Complete elliptic integrals K(z) and E(z) + add(elliptic_k(t), [S.Half, S.Half], [], [0], [0], -t, S.Half) + add(elliptic_e(t), [S.Half, 3*S.Half], [], [0], [0], -t, Rational(-1, 2)/2) + + +#################################################################### +# First some helper functions. +#################################################################### + +from sympy.utilities.timeutils import timethis +timeit = timethis('meijerg') + + +def _mytype(f: Basic, x: Symbol) -> tuple[type[Basic], ...]: + """ Create a hashable entity describing the type of f. """ + def key(x: type[Basic]) -> tuple[int, int, str]: + return x.class_key() + + if x not in f.free_symbols: + return () + elif f.is_Function: + return type(f), + return tuple(sorted((t for a in f.args for t in _mytype(a, x)), key=key)) + + +class _CoeffExpValueError(ValueError): + """ + Exception raised by _get_coeff_exp, for internal use only. + """ + pass + + +def _get_coeff_exp(expr, x): + """ + When expr is known to be of the form c*x**b, with c and/or b possibly 1, + return c, b. + + Examples + ======== + + >>> from sympy.abc import x, a, b + >>> from sympy.integrals.meijerint import _get_coeff_exp + >>> _get_coeff_exp(a*x**b, x) + (a, b) + >>> _get_coeff_exp(x, x) + (1, 1) + >>> _get_coeff_exp(2*x, x) + (2, 1) + >>> _get_coeff_exp(x**3, x) + (1, 3) + """ + from sympy.simplify import powsimp + (c, m) = expand_power_base(powsimp(expr)).as_coeff_mul(x) + if not m: + return c, S.Zero + [m] = m + if m.is_Pow: + if m.base != x: + raise _CoeffExpValueError('expr not of form a*x**b') + return c, m.exp + elif m == x: + return c, S.One + else: + raise _CoeffExpValueError('expr not of form a*x**b: %s' % expr) + + +def _exponents(expr, x): + """ + Find the exponents of ``x`` (not including zero) in ``expr``. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _exponents + >>> from sympy.abc import x, y + >>> from sympy import sin + >>> _exponents(x, x) + {1} + >>> _exponents(x**2, x) + {2} + >>> _exponents(x**2 + x, x) + {1, 2} + >>> _exponents(x**3*sin(x + x**y) + 1/x, x) + {-1, 1, 3, y} + """ + def _exponents_(expr, x, res): + if expr == x: + res.update([1]) + return + if expr.is_Pow and expr.base == x: + res.update([expr.exp]) + return + for argument in expr.args: + _exponents_(argument, x, res) + res = set() + _exponents_(expr, x, res) + return res + + +def _functions(expr, x): + """ Find the types of functions in expr, to estimate the complexity. """ + return {e.func for e in expr.atoms(Function) if x in e.free_symbols} + + +def _find_splitting_points(expr, x): + """ + Find numbers a such that a linear substitution x -> x + a would + (hopefully) simplify expr. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _find_splitting_points as fsp + >>> from sympy import sin + >>> from sympy.abc import x + >>> fsp(x, x) + {0} + >>> fsp((x-1)**3, x) + {1} + >>> fsp(sin(x+3)*x, x) + {-3, 0} + """ + p, q = [Wild(n, exclude=[x]) for n in 'pq'] + + def compute_innermost(expr, res): + if not isinstance(expr, Expr): + return + m = expr.match(p*x + q) + if m and m[p] != 0: + res.add(-m[q]/m[p]) + return + if expr.is_Atom: + return + for argument in expr.args: + compute_innermost(argument, res) + innermost = set() + compute_innermost(expr, innermost) + return innermost + + +def _split_mul(f, x): + """ + Split expression ``f`` into fac, po, g, where fac is a constant factor, + po = x**s for some s independent of s, and g is "the rest". + + Examples + ======== + + >>> from sympy.integrals.meijerint import _split_mul + >>> from sympy import sin + >>> from sympy.abc import s, x + >>> _split_mul((3*x)**s*sin(x**2)*x, x) + (3**s, x*x**s, sin(x**2)) + """ + fac = S.One + po = S.One + g = S.One + f = expand_power_base(f) + + args = Mul.make_args(f) + for a in args: + if a == x: + po *= x + elif x not in a.free_symbols: + fac *= a + else: + if a.is_Pow and x not in a.exp.free_symbols: + c, t = a.base.as_coeff_mul(x) + if t != (x,): + c, t = expand_mul(a.base).as_coeff_mul(x) + if t == (x,): + po *= x**a.exp + fac *= unpolarify(polarify(c**a.exp, subs=False)) + continue + g *= a + + return fac, po, g + + +def _mul_args(f): + """ + Return a list ``L`` such that ``Mul(*L) == f``. + + If ``f`` is not a ``Mul`` or ``Pow``, ``L=[f]``. + If ``f=g**n`` for an integer ``n``, ``L=[g]*n``. + If ``f`` is a ``Mul``, ``L`` comes from applying ``_mul_args`` to all factors of ``f``. + """ + args = Mul.make_args(f) + gs = [] + for g in args: + if g.is_Pow and g.exp.is_Integer: + n = g.exp + base = g.base + if n < 0: + n = -n + base = 1/base + gs += [base]*n + else: + gs.append(g) + return gs + + +def _mul_as_two_parts(f): + """ + Find all the ways to split ``f`` into a product of two terms. + Return None on failure. + + Explanation + =========== + + Although the order is canonical from multiset_partitions, this is + not necessarily the best order to process the terms. For example, + if the case of len(gs) == 2 is removed and multiset is allowed to + sort the terms, some tests fail. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _mul_as_two_parts + >>> from sympy import sin, exp, ordered + >>> from sympy.abc import x + >>> list(ordered(_mul_as_two_parts(x*sin(x)*exp(x)))) + [(x, exp(x)*sin(x)), (x*exp(x), sin(x)), (x*sin(x), exp(x))] + """ + + gs = _mul_args(f) + if len(gs) < 2: + return None + if len(gs) == 2: + return [tuple(gs)] + return [(Mul(*x), Mul(*y)) for (x, y) in multiset_partitions(gs, 2)] + + +def _inflate_g(g, n): + """ Return C, h such that h is a G function of argument z**n and + g = C*h. """ + # TODO should this be a method of meijerg? + # See: [L, page 150, equation (5)] + def inflate(params, n): + """ (a1, .., ak) -> (a1/n, (a1+1)/n, ..., (ak + n-1)/n) """ + return [(a + i)/n for a, i in itertools.product(params, range(n))] + v = S(len(g.ap) - len(g.bq)) + C = n**(1 + g.nu + v/2) + C /= (2*pi)**((n - 1)*g.delta) + return C, meijerg(inflate(g.an, n), inflate(g.aother, n), + inflate(g.bm, n), inflate(g.bother, n), + g.argument**n * n**(n*v)) + + +def _flip_g(g): + """ Turn the G function into one of inverse argument + (i.e. G(1/x) -> G'(x)) """ + # See [L], section 5.2 + def tr(l): + return [1 - a for a in l] + return meijerg(tr(g.bm), tr(g.bother), tr(g.an), tr(g.aother), 1/g.argument) + + +def _inflate_fox_h(g, a): + r""" + Let d denote the integrand in the definition of the G function ``g``. + Consider the function H which is defined in the same way, but with + integrand d/Gamma(a*s) (contour conventions as usual). + + If ``a`` is rational, the function H can be written as C*G, for a constant C + and a G-function G. + + This function returns C, G. + """ + if a < 0: + return _inflate_fox_h(_flip_g(g), -a) + p = S(a.p) + q = S(a.q) + # We use the substitution s->qs, i.e. inflate g by q. We are left with an + # extra factor of Gamma(p*s), for which we use Gauss' multiplication + # theorem. + D, g = _inflate_g(g, q) + z = g.argument + D /= (2*pi)**((1 - p)/2)*p**Rational(-1, 2) + z /= p**p + bs = [(n + 1)/p for n in range(p)] + return D, meijerg(g.an, g.aother, g.bm, list(g.bother) + bs, z) + + +_dummies: dict[tuple[str, str], Dummy] = {} + + +def _dummy(name, token, expr, **kwargs): + """ + Return a dummy. This will return the same dummy if the same token+name is + requested more than once, and it is not already in expr. + This is for being cache-friendly. + """ + d = _dummy_(name, token, **kwargs) + if d in expr.free_symbols: + return Dummy(name, **kwargs) + return d + + +def _dummy_(name, token, **kwargs): + """ + Return a dummy associated to name and token. Same effect as declaring + it globally. + """ + if not (name, token) in _dummies: + _dummies[(name, token)] = Dummy(name, **kwargs) + return _dummies[(name, token)] + + +def _is_analytic(f, x): + """ Check if f(x), when expressed using G functions on the positive reals, + will in fact agree with the G functions almost everywhere """ + return not any(x in expr.free_symbols for expr in f.atoms(Heaviside, Abs)) + + +def _condsimp(cond, first=True): + """ + Do naive simplifications on ``cond``. + + Explanation + =========== + + Note that this routine is completely ad-hoc, simplification rules being + added as need arises rather than following any logical pattern. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _condsimp as simp + >>> from sympy import Or, Eq + >>> from sympy.abc import x, y + >>> simp(Or(x < y, Eq(x, y))) + x <= y + """ + if first: + cond = cond.replace(lambda _: _.is_Relational, _canonical_coeff) + first = False + if not isinstance(cond, BooleanFunction): + return cond + p, q, r = symbols('p q r', cls=Wild) + # transforms tests use 0, 4, 5 and 11-14 + # meijer tests use 0, 2, 11, 14 + # joint_rv uses 6, 7 + rules = [ + (Or(p < q, Eq(p, q)), p <= q), # 0 + # The next two obviously are instances of a general pattern, but it is + # easier to spell out the few cases we care about. + (And(Abs(arg(p)) <= pi, Abs(arg(p) - 2*pi) <= pi), + Eq(arg(p) - pi, 0)), # 1 + (And(Abs(2*arg(p) + pi) <= pi, Abs(2*arg(p) - pi) <= pi), + Eq(arg(p), 0)), # 2 + (And(Abs(2*arg(p) + pi) < pi, Abs(2*arg(p) - pi) <= pi), + S.false), # 3 + (And(Abs(arg(p) - pi/2) <= pi/2, Abs(arg(p) + pi/2) <= pi/2), + Eq(arg(p), 0)), # 4 + (And(Abs(arg(p) - pi/2) <= pi/2, Abs(arg(p) + pi/2) < pi/2), + S.false), # 5 + (And(Abs(arg(p**2/2 + 1)) < pi, Ne(Abs(arg(p**2/2 + 1)), pi)), + S.true), # 6 + (Or(Abs(arg(p**2/2 + 1)) < pi, Ne(1/(p**2/2 + 1), 0)), + S.true), # 7 + (And(Abs(unbranched_argument(p)) <= pi, + Abs(unbranched_argument(exp_polar(-2*pi*S.ImaginaryUnit)*p)) <= pi), + Eq(unbranched_argument(exp_polar(-S.ImaginaryUnit*pi)*p), 0)), # 8 + (And(Abs(unbranched_argument(p)) <= pi/2, + Abs(unbranched_argument(exp_polar(-pi*S.ImaginaryUnit)*p)) <= pi/2), + Eq(unbranched_argument(exp_polar(-S.ImaginaryUnit*pi/2)*p), 0)), # 9 + (Or(p <= q, And(p < q, r)), p <= q), # 10 + (Ne(p**2, 1) & (p**2 > 1), p**2 > 1), # 11 + (Ne(1/p, 1) & (cos(Abs(arg(p)))*Abs(p) > 1), Abs(p) > 1), # 12 + (Ne(p, 2) & (cos(Abs(arg(p)))*Abs(p) > 2), Abs(p) > 2), # 13 + ((Abs(arg(p)) < pi/2) & (cos(Abs(arg(p)))*sqrt(Abs(p**2)) > 1), p**2 > 1), # 14 + ] + cond = cond.func(*[_condsimp(_, first) for _ in cond.args]) + change = True + while change: + change = False + for irule, (fro, to) in enumerate(rules): + if fro.func != cond.func: + continue + for n, arg1 in enumerate(cond.args): + if r in fro.args[0].free_symbols: + m = arg1.match(fro.args[1]) + num = 1 + else: + num = 0 + m = arg1.match(fro.args[0]) + if not m: + continue + otherargs = [x.subs(m) for x in fro.args[:num] + fro.args[num + 1:]] + otherlist = [n] + for arg2 in otherargs: + for k, arg3 in enumerate(cond.args): + if k in otherlist: + continue + if arg2 == arg3: + otherlist += [k] + break + if isinstance(arg3, And) and arg2.args[1] == r and \ + isinstance(arg2, And) and arg2.args[0] in arg3.args: + otherlist += [k] + break + if isinstance(arg3, And) and arg2.args[0] == r and \ + isinstance(arg2, And) and arg2.args[1] in arg3.args: + otherlist += [k] + break + if len(otherlist) != len(otherargs) + 1: + continue + newargs = [arg_ for (k, arg_) in enumerate(cond.args) + if k not in otherlist] + [to.subs(m)] + if SYMPY_DEBUG: + if irule not in (0, 2, 4, 5, 6, 7, 11, 12, 13, 14): + print('used new rule:', irule) + cond = cond.func(*newargs) + change = True + break + + # final tweak + def rel_touchup(rel): + if rel.rel_op != '==' or rel.rhs != 0: + return rel + + # handle Eq(*, 0) + LHS = rel.lhs + m = LHS.match(arg(p)**q) + if not m: + m = LHS.match(unbranched_argument(polar_lift(p)**q)) + if not m: + if isinstance(LHS, periodic_argument) and not LHS.args[0].is_polar \ + and LHS.args[1] is S.Infinity: + return (LHS.args[0] > 0) + return rel + return (m[p] > 0) + cond = cond.replace(lambda _: _.is_Relational, rel_touchup) + if SYMPY_DEBUG: + print('_condsimp: ', cond) + return cond + +def _eval_cond(cond): + """ Re-evaluate the conditions. """ + if isinstance(cond, bool): + return cond + return _condsimp(cond.doit()) + +#################################################################### +# Now the "backbone" functions to do actual integration. +#################################################################### + + +def _my_principal_branch(expr, period, full_pb=False): + """ Bring expr nearer to its principal branch by removing superfluous + factors. + This function does *not* guarantee to yield the principal branch, + to avoid introducing opaque principal_branch() objects, + unless full_pb=True. """ + res = principal_branch(expr, period) + if not full_pb: + res = res.replace(principal_branch, lambda x, y: x) + return res + + +def _rewrite_saxena_1(fac, po, g, x): + """ + Rewrite the integral fac*po*g dx, from zero to infinity, as + integral fac*G, where G has argument a*x. Note po=x**s. + Return fac, G. + """ + _, s = _get_coeff_exp(po, x) + a, b = _get_coeff_exp(g.argument, x) + period = g.get_period() + a = _my_principal_branch(a, period) + + # We substitute t = x**b. + C = fac/(Abs(b)*a**((s + 1)/b - 1)) + # Absorb a factor of (at)**((1 + s)/b - 1). + + def tr(l): + return [a + (1 + s)/b - 1 for a in l] + return C, meijerg(tr(g.an), tr(g.aother), tr(g.bm), tr(g.bother), + a*x) + + +def _check_antecedents_1(g, x, helper=False): + r""" + Return a condition under which the mellin transform of g exists. + Any power of x has already been absorbed into the G function, + so this is just $\int_0^\infty g\, dx$. + + See [L, section 5.6.1]. (Note that s=1.) + + If ``helper`` is True, only check if the MT exists at infinity, i.e. if + $\int_1^\infty g\, dx$ exists. + """ + # NOTE if you update these conditions, please update the documentation as well + delta = g.delta + eta, _ = _get_coeff_exp(g.argument, x) + m, n, p, q = S([len(g.bm), len(g.an), len(g.ap), len(g.bq)]) + + if p > q: + def tr(l): + return [1 - x for x in l] + return _check_antecedents_1(meijerg(tr(g.bm), tr(g.bother), + tr(g.an), tr(g.aother), x/eta), + x) + + tmp = [-re(b) < 1 for b in g.bm] + [1 < 1 - re(a) for a in g.an] + cond_3 = And(*tmp) + + tmp += [-re(b) < 1 for b in g.bother] + tmp += [1 < 1 - re(a) for a in g.aother] + cond_3_star = And(*tmp) + + cond_4 = (-re(g.nu) + (q + 1 - p)/2 > q - p) + + def debug(*msg): + _debug(*msg) + + def debugf(string, arg): + _debugf(string, arg) + + debug('Checking antecedents for 1 function:') + debugf(' delta=%s, eta=%s, m=%s, n=%s, p=%s, q=%s', + (delta, eta, m, n, p, q)) + debugf(' ap = %s, %s', (list(g.an), list(g.aother))) + debugf(' bq = %s, %s', (list(g.bm), list(g.bother))) + debugf(' cond_3=%s, cond_3*=%s, cond_4=%s', (cond_3, cond_3_star, cond_4)) + + conds = [] + + # case 1 + case1 = [] + tmp1 = [1 <= n, p < q, 1 <= m] + tmp2 = [1 <= p, 1 <= m, Eq(q, p + 1), Not(And(Eq(n, 0), Eq(m, p + 1)))] + tmp3 = [1 <= p, Eq(q, p)] + for k in range(ceiling(delta/2) + 1): + tmp3 += [Ne(Abs(unbranched_argument(eta)), (delta - 2*k)*pi)] + tmp = [delta > 0, Abs(unbranched_argument(eta)) < delta*pi] + extra = [Ne(eta, 0), cond_3] + if helper: + extra = [] + for t in [tmp1, tmp2, tmp3]: + case1 += [And(*(t + tmp + extra))] + conds += case1 + debug(' case 1:', case1) + + # case 2 + extra = [cond_3] + if helper: + extra = [] + case2 = [And(Eq(n, 0), p + 1 <= m, m <= q, + Abs(unbranched_argument(eta)) < delta*pi, *extra)] + conds += case2 + debug(' case 2:', case2) + + # case 3 + extra = [cond_3, cond_4] + if helper: + extra = [] + case3 = [And(p < q, 1 <= m, delta > 0, Eq(Abs(unbranched_argument(eta)), delta*pi), + *extra)] + case3 += [And(p <= q - 2, Eq(delta, 0), Eq(Abs(unbranched_argument(eta)), 0), *extra)] + conds += case3 + debug(' case 3:', case3) + + # TODO altered cases 4-7 + + # extra case from wofram functions site: + # (reproduced verbatim from Prudnikov, section 2.24.2) + # https://functions.wolfram.com/HypergeometricFunctions/MeijerG/21/02/01/ + case_extra = [] + case_extra += [Eq(p, q), Eq(delta, 0), Eq(unbranched_argument(eta), 0), Ne(eta, 0)] + if not helper: + case_extra += [cond_3] + s = [] + for a, b in zip(g.ap, g.bq): + s += [b - a] + case_extra += [re(Add(*s)) < 0] + case_extra = And(*case_extra) + conds += [case_extra] + debug(' extra case:', [case_extra]) + + case_extra_2 = [And(delta > 0, Abs(unbranched_argument(eta)) < delta*pi)] + if not helper: + case_extra_2 += [cond_3] + case_extra_2 = And(*case_extra_2) + conds += [case_extra_2] + debug(' second extra case:', [case_extra_2]) + + # TODO This leaves only one case from the three listed by Prudnikov. + # Investigate if these indeed cover everything; if so, remove the rest. + + return Or(*conds) + + +def _int0oo_1(g, x): + r""" + Evaluate $\int_0^\infty g\, dx$ using G functions, + assuming the necessary conditions are fulfilled. + + Examples + ======== + + >>> from sympy.abc import a, b, c, d, x, y + >>> from sympy import meijerg + >>> from sympy.integrals.meijerint import _int0oo_1 + >>> _int0oo_1(meijerg([a], [b], [c], [d], x*y), x) + gamma(-a)*gamma(c + 1)/(y*gamma(-d)*gamma(b + 1)) + """ + from sympy.simplify import gammasimp + # See [L, section 5.6.1]. Note that s=1. + eta, _ = _get_coeff_exp(g.argument, x) + res = 1/eta + # XXX TODO we should reduce order first + for b in g.bm: + res *= gamma(b + 1) + for a in g.an: + res *= gamma(1 - a - 1) + for b in g.bother: + res /= gamma(1 - b - 1) + for a in g.aother: + res /= gamma(a + 1) + return gammasimp(unpolarify(res)) + + +def _rewrite_saxena(fac, po, g1, g2, x, full_pb=False): + """ + Rewrite the integral ``fac*po*g1*g2`` from 0 to oo in terms of G + functions with argument ``c*x``. + + Explanation + =========== + + Return C, f1, f2 such that integral C f1 f2 from 0 to infinity equals + integral fac ``po``, ``g1``, ``g2`` from 0 to infinity. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _rewrite_saxena + >>> from sympy.abc import s, t, m + >>> from sympy import meijerg + >>> g1 = meijerg([], [], [0], [], s*t) + >>> g2 = meijerg([], [], [m/2], [-m/2], t**2/4) + >>> r = _rewrite_saxena(1, t**0, g1, g2, t) + >>> r[0] + s/(4*sqrt(pi)) + >>> r[1] + meijerg(((), ()), ((-1/2, 0), ()), s**2*t/4) + >>> r[2] + meijerg(((), ()), ((m/2,), (-m/2,)), t/4) + """ + def pb(g): + a, b = _get_coeff_exp(g.argument, x) + per = g.get_period() + return meijerg(g.an, g.aother, g.bm, g.bother, + _my_principal_branch(a, per, full_pb)*x**b) + + _, s = _get_coeff_exp(po, x) + _, b1 = _get_coeff_exp(g1.argument, x) + _, b2 = _get_coeff_exp(g2.argument, x) + if (b1 < 0) == True: + b1 = -b1 + g1 = _flip_g(g1) + if (b2 < 0) == True: + b2 = -b2 + g2 = _flip_g(g2) + if not b1.is_Rational or not b2.is_Rational: + return + m1, n1 = b1.p, b1.q + m2, n2 = b2.p, b2.q + tau = ilcm(m1*n2, m2*n1) + r1 = tau//(m1*n2) + r2 = tau//(m2*n1) + + C1, g1 = _inflate_g(g1, r1) + C2, g2 = _inflate_g(g2, r2) + g1 = pb(g1) + g2 = pb(g2) + + fac *= C1*C2 + a1, b = _get_coeff_exp(g1.argument, x) + a2, _ = _get_coeff_exp(g2.argument, x) + + # arbitrarily tack on the x**s part to g1 + # TODO should we try both? + exp = (s + 1)/b - 1 + fac = fac/(Abs(b) * a1**exp) + + def tr(l): + return [a + exp for a in l] + g1 = meijerg(tr(g1.an), tr(g1.aother), tr(g1.bm), tr(g1.bother), a1*x) + g2 = meijerg(g2.an, g2.aother, g2.bm, g2.bother, a2*x) + + from sympy.simplify import powdenest + return powdenest(fac, polar=True), g1, g2 + + +def _check_antecedents(g1, g2, x): + """ Return a condition under which the integral theorem applies. """ + # Yes, this is madness. + # XXX TODO this is a testing *nightmare* + # NOTE if you update these conditions, please update the documentation as well + + # The following conditions are found in + # [P], Section 2.24.1 + # + # They are also reproduced (verbatim!) at + # https://functions.wolfram.com/HypergeometricFunctions/MeijerG/21/02/03/ + # + # Note: k=l=r=alpha=1 + sigma, _ = _get_coeff_exp(g1.argument, x) + omega, _ = _get_coeff_exp(g2.argument, x) + s, t, u, v = S([len(g1.bm), len(g1.an), len(g1.ap), len(g1.bq)]) + m, n, p, q = S([len(g2.bm), len(g2.an), len(g2.ap), len(g2.bq)]) + bstar = s + t - (u + v)/2 + cstar = m + n - (p + q)/2 + rho = g1.nu + (u - v)/2 + 1 + mu = g2.nu + (p - q)/2 + 1 + phi = q - p - (v - u) + eta = 1 - (v - u) - mu - rho + psi = (pi*(q - m - n) + Abs(unbranched_argument(omega)))/(q - p) + theta = (pi*(v - s - t) + Abs(unbranched_argument(sigma)))/(v - u) + + _debug('Checking antecedents:') + _debugf(' sigma=%s, s=%s, t=%s, u=%s, v=%s, b*=%s, rho=%s', + (sigma, s, t, u, v, bstar, rho)) + _debugf(' omega=%s, m=%s, n=%s, p=%s, q=%s, c*=%s, mu=%s,', + (omega, m, n, p, q, cstar, mu)) + _debugf(' phi=%s, eta=%s, psi=%s, theta=%s', (phi, eta, psi, theta)) + + def _c1(): + for g in [g1, g2]: + for i, j in itertools.product(g.an, g.bm): + diff = i - j + if diff.is_integer and diff.is_positive: + return False + return True + c1 = _c1() + c2 = And(*[re(1 + i + j) > 0 for i in g1.bm for j in g2.bm]) + c3 = And(*[re(1 + i + j) < 1 + 1 for i in g1.an for j in g2.an]) + c4 = And(*[(p - q)*re(1 + i - 1) - re(mu) > Rational(-3, 2) for i in g1.an]) + c5 = And(*[(p - q)*re(1 + i) - re(mu) > Rational(-3, 2) for i in g1.bm]) + c6 = And(*[(u - v)*re(1 + i - 1) - re(rho) > Rational(-3, 2) for i in g2.an]) + c7 = And(*[(u - v)*re(1 + i) - re(rho) > Rational(-3, 2) for i in g2.bm]) + c8 = (Abs(phi) + 2*re((rho - 1)*(q - p) + (v - u)*(q - p) + (mu - + 1)*(v - u)) > 0) + c9 = (Abs(phi) - 2*re((rho - 1)*(q - p) + (v - u)*(q - p) + (mu - + 1)*(v - u)) > 0) + c10 = (Abs(unbranched_argument(sigma)) < bstar*pi) + c11 = Eq(Abs(unbranched_argument(sigma)), bstar*pi) + c12 = (Abs(unbranched_argument(omega)) < cstar*pi) + c13 = Eq(Abs(unbranched_argument(omega)), cstar*pi) + + # The following condition is *not* implemented as stated on the wolfram + # function site. In the book of Prudnikov there is an additional part + # (the And involving re()). However, I only have this book in russian, and + # I don't read any russian. The following condition is what other people + # have told me it means. + # Worryingly, it is different from the condition implemented in REDUCE. + # The REDUCE implementation: + # https://reduce-algebra.svn.sourceforge.net/svnroot/reduce-algebra/trunk/packages/defint/definta.red + # (search for tst14) + # The Wolfram alpha version: + # https://functions.wolfram.com/HypergeometricFunctions/MeijerG/21/02/03/03/0014/ + z0 = exp(-(bstar + cstar)*pi*S.ImaginaryUnit) + zos = unpolarify(z0*omega/sigma) + zso = unpolarify(z0*sigma/omega) + if zos == 1/zso: + c14 = And(Eq(phi, 0), bstar + cstar <= 1, + Or(Ne(zos, 1), re(mu + rho + v - u) < 1, + re(mu + rho + q - p) < 1)) + else: + def _cond(z): + '''Returns True if abs(arg(1-z)) < pi, avoiding arg(0). + + Explanation + =========== + + If ``z`` is 1 then arg is NaN. This raises a + TypeError on `NaN < pi`. Previously this gave `False` so + this behavior has been hardcoded here but someone should + check if this NaN is more serious! This NaN is triggered by + test_meijerint() in test_meijerint.py: + `meijerint_definite(exp(x), x, 0, I)` + ''' + return z != 1 and Abs(arg(1 - z)) < pi + + c14 = And(Eq(phi, 0), bstar - 1 + cstar <= 0, + Or(And(Ne(zos, 1), _cond(zos)), + And(re(mu + rho + v - u) < 1, Eq(zos, 1)))) + + c14_alt = And(Eq(phi, 0), cstar - 1 + bstar <= 0, + Or(And(Ne(zso, 1), _cond(zso)), + And(re(mu + rho + q - p) < 1, Eq(zso, 1)))) + + # Since r=k=l=1, in our case there is c14_alt which is the same as calling + # us with (g1, g2) = (g2, g1). The conditions below enumerate all cases + # (i.e. we don't have to try arguments reversed by hand), and indeed try + # all symmetric cases. (i.e. whenever there is a condition involving c14, + # there is also a dual condition which is exactly what we would get when g1, + # g2 were interchanged, *but c14 was unaltered*). + # Hence the following seems correct: + c14 = Or(c14, c14_alt) + + ''' + When `c15` is NaN (e.g. from `psi` being NaN as happens during + 'test_issue_4992' and/or `theta` is NaN as in 'test_issue_6253', + both in `test_integrals.py`) the comparison to 0 formerly gave False + whereas now an error is raised. To keep the old behavior, the value + of NaN is replaced with False but perhaps a closer look at this condition + should be made: XXX how should conditions leading to c15=NaN be handled? + ''' + try: + lambda_c = (q - p)*Abs(omega)**(1/(q - p))*cos(psi) \ + + (v - u)*Abs(sigma)**(1/(v - u))*cos(theta) + # the TypeError might be raised here, e.g. if lambda_c is NaN + if _eval_cond(lambda_c > 0) != False: + c15 = (lambda_c > 0) + else: + def lambda_s0(c1, c2): + return c1*(q - p)*Abs(omega)**(1/(q - p))*sin(psi) \ + + c2*(v - u)*Abs(sigma)**(1/(v - u))*sin(theta) + lambda_s = Piecewise( + ((lambda_s0(+1, +1)*lambda_s0(-1, -1)), + And(Eq(unbranched_argument(sigma), 0), Eq(unbranched_argument(omega), 0))), + (lambda_s0(sign(unbranched_argument(omega)), +1)*lambda_s0(sign(unbranched_argument(omega)), -1), + And(Eq(unbranched_argument(sigma), 0), Ne(unbranched_argument(omega), 0))), + (lambda_s0(+1, sign(unbranched_argument(sigma)))*lambda_s0(-1, sign(unbranched_argument(sigma))), + And(Ne(unbranched_argument(sigma), 0), Eq(unbranched_argument(omega), 0))), + (lambda_s0(sign(unbranched_argument(omega)), sign(unbranched_argument(sigma))), True)) + tmp = [lambda_c > 0, + And(Eq(lambda_c, 0), Ne(lambda_s, 0), re(eta) > -1), + And(Eq(lambda_c, 0), Eq(lambda_s, 0), re(eta) > 0)] + c15 = Or(*tmp) + except TypeError: + c15 = False + for cond, i in [(c1, 1), (c2, 2), (c3, 3), (c4, 4), (c5, 5), (c6, 6), + (c7, 7), (c8, 8), (c9, 9), (c10, 10), (c11, 11), + (c12, 12), (c13, 13), (c14, 14), (c15, 15)]: + _debugf(' c%s: %s', (i, cond)) + + # We will return Or(*conds) + conds = [] + + def pr(count): + _debugf(' case %s: %s', (count, conds[-1])) + conds += [And(m*n*s*t != 0, bstar.is_positive is True, cstar.is_positive is True, c1, c2, c3, c10, + c12)] # 1 + pr(1) + conds += [And(Eq(u, v), Eq(bstar, 0), cstar.is_positive is True, sigma.is_positive is True, re(rho) < 1, + c1, c2, c3, c12)] # 2 + pr(2) + conds += [And(Eq(p, q), Eq(cstar, 0), bstar.is_positive is True, omega.is_positive is True, re(mu) < 1, + c1, c2, c3, c10)] # 3 + pr(3) + conds += [And(Eq(p, q), Eq(u, v), Eq(bstar, 0), Eq(cstar, 0), + sigma.is_positive is True, omega.is_positive is True, re(mu) < 1, re(rho) < 1, + Ne(sigma, omega), c1, c2, c3)] # 4 + pr(4) + conds += [And(Eq(p, q), Eq(u, v), Eq(bstar, 0), Eq(cstar, 0), + sigma.is_positive is True, omega.is_positive is True, re(mu + rho) < 1, + Ne(omega, sigma), c1, c2, c3)] # 5 + pr(5) + conds += [And(p > q, s.is_positive is True, bstar.is_positive is True, cstar >= 0, + c1, c2, c3, c5, c10, c13)] # 6 + pr(6) + conds += [And(p < q, t.is_positive is True, bstar.is_positive is True, cstar >= 0, + c1, c2, c3, c4, c10, c13)] # 7 + pr(7) + conds += [And(u > v, m.is_positive is True, cstar.is_positive is True, bstar >= 0, + c1, c2, c3, c7, c11, c12)] # 8 + pr(8) + conds += [And(u < v, n.is_positive is True, cstar.is_positive is True, bstar >= 0, + c1, c2, c3, c6, c11, c12)] # 9 + pr(9) + conds += [And(p > q, Eq(u, v), Eq(bstar, 0), cstar >= 0, sigma.is_positive is True, + re(rho) < 1, c1, c2, c3, c5, c13)] # 10 + pr(10) + conds += [And(p < q, Eq(u, v), Eq(bstar, 0), cstar >= 0, sigma.is_positive is True, + re(rho) < 1, c1, c2, c3, c4, c13)] # 11 + pr(11) + conds += [And(Eq(p, q), u > v, bstar >= 0, Eq(cstar, 0), omega.is_positive is True, + re(mu) < 1, c1, c2, c3, c7, c11)] # 12 + pr(12) + conds += [And(Eq(p, q), u < v, bstar >= 0, Eq(cstar, 0), omega.is_positive is True, + re(mu) < 1, c1, c2, c3, c6, c11)] # 13 + pr(13) + conds += [And(p < q, u > v, bstar >= 0, cstar >= 0, + c1, c2, c3, c4, c7, c11, c13)] # 14 + pr(14) + conds += [And(p > q, u < v, bstar >= 0, cstar >= 0, + c1, c2, c3, c5, c6, c11, c13)] # 15 + pr(15) + conds += [And(p > q, u > v, bstar >= 0, cstar >= 0, + c1, c2, c3, c5, c7, c8, c11, c13, c14)] # 16 + pr(16) + conds += [And(p < q, u < v, bstar >= 0, cstar >= 0, + c1, c2, c3, c4, c6, c9, c11, c13, c14)] # 17 + pr(17) + conds += [And(Eq(t, 0), s.is_positive is True, bstar.is_positive is True, phi.is_positive is True, c1, c2, c10)] # 18 + pr(18) + conds += [And(Eq(s, 0), t.is_positive is True, bstar.is_positive is True, phi.is_negative is True, c1, c3, c10)] # 19 + pr(19) + conds += [And(Eq(n, 0), m.is_positive is True, cstar.is_positive is True, phi.is_negative is True, c1, c2, c12)] # 20 + pr(20) + conds += [And(Eq(m, 0), n.is_positive is True, cstar.is_positive is True, phi.is_positive is True, c1, c3, c12)] # 21 + pr(21) + conds += [And(Eq(s*t, 0), bstar.is_positive is True, cstar.is_positive is True, + c1, c2, c3, c10, c12)] # 22 + pr(22) + conds += [And(Eq(m*n, 0), bstar.is_positive is True, cstar.is_positive is True, + c1, c2, c3, c10, c12)] # 23 + pr(23) + + # The following case is from [Luke1969]. As far as I can tell, it is *not* + # covered by Prudnikov's. + # Let G1 and G2 be the two G-functions. Suppose the integral exists from + # 0 to a > 0 (this is easy the easy part), that G1 is exponential decay at + # infinity, and that the mellin transform of G2 exists. + # Then the integral exists. + mt1_exists = _check_antecedents_1(g1, x, helper=True) + mt2_exists = _check_antecedents_1(g2, x, helper=True) + conds += [And(mt2_exists, Eq(t, 0), u < s, bstar.is_positive is True, c10, c1, c2, c3)] + pr('E1') + conds += [And(mt2_exists, Eq(s, 0), v < t, bstar.is_positive is True, c10, c1, c2, c3)] + pr('E2') + conds += [And(mt1_exists, Eq(n, 0), p < m, cstar.is_positive is True, c12, c1, c2, c3)] + pr('E3') + conds += [And(mt1_exists, Eq(m, 0), q < n, cstar.is_positive is True, c12, c1, c2, c3)] + pr('E4') + + # Let's short-circuit if this worked ... + # the rest is corner-cases and terrible to read. + r = Or(*conds) + if _eval_cond(r) != False: + return r + + conds += [And(m + n > p, Eq(t, 0), Eq(phi, 0), s.is_positive is True, bstar.is_positive is True, cstar.is_negative is True, + Abs(unbranched_argument(omega)) < (m + n - p + 1)*pi, + c1, c2, c10, c14, c15)] # 24 + pr(24) + conds += [And(m + n > q, Eq(s, 0), Eq(phi, 0), t.is_positive is True, bstar.is_positive is True, cstar.is_negative is True, + Abs(unbranched_argument(omega)) < (m + n - q + 1)*pi, + c1, c3, c10, c14, c15)] # 25 + pr(25) + conds += [And(Eq(p, q - 1), Eq(t, 0), Eq(phi, 0), s.is_positive is True, bstar.is_positive is True, + cstar >= 0, cstar*pi < Abs(unbranched_argument(omega)), + c1, c2, c10, c14, c15)] # 26 + pr(26) + conds += [And(Eq(p, q + 1), Eq(s, 0), Eq(phi, 0), t.is_positive is True, bstar.is_positive is True, + cstar >= 0, cstar*pi < Abs(unbranched_argument(omega)), + c1, c3, c10, c14, c15)] # 27 + pr(27) + conds += [And(p < q - 1, Eq(t, 0), Eq(phi, 0), s.is_positive is True, bstar.is_positive is True, + cstar >= 0, cstar*pi < Abs(unbranched_argument(omega)), + Abs(unbranched_argument(omega)) < (m + n - p + 1)*pi, + c1, c2, c10, c14, c15)] # 28 + pr(28) + conds += [And( + p > q + 1, Eq(s, 0), Eq(phi, 0), t.is_positive is True, bstar.is_positive is True, cstar >= 0, + cstar*pi < Abs(unbranched_argument(omega)), + Abs(unbranched_argument(omega)) < (m + n - q + 1)*pi, + c1, c3, c10, c14, c15)] # 29 + pr(29) + conds += [And(Eq(n, 0), Eq(phi, 0), s + t > 0, m.is_positive is True, cstar.is_positive is True, bstar.is_negative is True, + Abs(unbranched_argument(sigma)) < (s + t - u + 1)*pi, + c1, c2, c12, c14, c15)] # 30 + pr(30) + conds += [And(Eq(m, 0), Eq(phi, 0), s + t > v, n.is_positive is True, cstar.is_positive is True, bstar.is_negative is True, + Abs(unbranched_argument(sigma)) < (s + t - v + 1)*pi, + c1, c3, c12, c14, c15)] # 31 + pr(31) + conds += [And(Eq(n, 0), Eq(phi, 0), Eq(u, v - 1), m.is_positive is True, cstar.is_positive is True, + bstar >= 0, bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (bstar + 1)*pi, + c1, c2, c12, c14, c15)] # 32 + pr(32) + conds += [And(Eq(m, 0), Eq(phi, 0), Eq(u, v + 1), n.is_positive is True, cstar.is_positive is True, + bstar >= 0, bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (bstar + 1)*pi, + c1, c3, c12, c14, c15)] # 33 + pr(33) + conds += [And( + Eq(n, 0), Eq(phi, 0), u < v - 1, m.is_positive is True, cstar.is_positive is True, bstar >= 0, + bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (s + t - u + 1)*pi, + c1, c2, c12, c14, c15)] # 34 + pr(34) + conds += [And( + Eq(m, 0), Eq(phi, 0), u > v + 1, n.is_positive is True, cstar.is_positive is True, bstar >= 0, + bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (s + t - v + 1)*pi, + c1, c3, c12, c14, c15)] # 35 + pr(35) + + return Or(*conds) + + # NOTE An alternative, but as far as I can tell weaker, set of conditions + # can be found in [L, section 5.6.2]. + + +def _int0oo(g1, g2, x): + """ + Express integral from zero to infinity g1*g2 using a G function, + assuming the necessary conditions are fulfilled. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _int0oo + >>> from sympy.abc import s, t, m + >>> from sympy import meijerg, S + >>> g1 = meijerg([], [], [-S(1)/2, 0], [], s**2*t/4) + >>> g2 = meijerg([], [], [m/2], [-m/2], t/4) + >>> _int0oo(g1, g2, t) + 4*meijerg(((0, 1/2), ()), ((m/2,), (-m/2,)), s**(-2))/s**2 + """ + # See: [L, section 5.6.2, equation (1)] + eta, _ = _get_coeff_exp(g1.argument, x) + omega, _ = _get_coeff_exp(g2.argument, x) + + def neg(l): + return [-x for x in l] + a1 = neg(g1.bm) + list(g2.an) + a2 = list(g2.aother) + neg(g1.bother) + b1 = neg(g1.an) + list(g2.bm) + b2 = list(g2.bother) + neg(g1.aother) + return meijerg(a1, a2, b1, b2, omega/eta)/eta + + +def _rewrite_inversion(fac, po, g, x): + """ Absorb ``po`` == x**s into g. """ + _, s = _get_coeff_exp(po, x) + a, b = _get_coeff_exp(g.argument, x) + + def tr(l): + return [t + s/b for t in l] + from sympy.simplify import powdenest + return (powdenest(fac/a**(s/b), polar=True), + meijerg(tr(g.an), tr(g.aother), tr(g.bm), tr(g.bother), g.argument)) + + +def _check_antecedents_inversion(g, x): + """ Check antecedents for the laplace inversion integral. """ + _debug('Checking antecedents for inversion:') + z = g.argument + _, e = _get_coeff_exp(z, x) + if e < 0: + _debug(' Flipping G.') + # We want to assume that argument gets large as |x| -> oo + return _check_antecedents_inversion(_flip_g(g), x) + + def statement_half(a, b, c, z, plus): + coeff, exponent = _get_coeff_exp(z, x) + a *= exponent + b *= coeff**c + c *= exponent + conds = [] + wp = b*exp(S.ImaginaryUnit*re(c)*pi/2) + wm = b*exp(-S.ImaginaryUnit*re(c)*pi/2) + if plus: + w = wp + else: + w = wm + conds += [And(Or(Eq(b, 0), re(c) <= 0), re(a) <= -1)] + conds += [And(Ne(b, 0), Eq(im(c), 0), re(c) > 0, re(w) < 0)] + conds += [And(Ne(b, 0), Eq(im(c), 0), re(c) > 0, re(w) <= 0, + re(a) <= -1)] + return Or(*conds) + + def statement(a, b, c, z): + """ Provide a convergence statement for z**a * exp(b*z**c), + c/f sphinx docs. """ + return And(statement_half(a, b, c, z, True), + statement_half(a, b, c, z, False)) + + # Notations from [L], section 5.7-10 + m, n, p, q = S([len(g.bm), len(g.an), len(g.ap), len(g.bq)]) + tau = m + n - p + nu = q - m - n + rho = (tau - nu)/2 + sigma = q - p + if sigma == 1: + epsilon = S.Half + elif sigma > 1: + epsilon = 1 + else: + epsilon = S.NaN + theta = ((1 - sigma)/2 + Add(*g.bq) - Add(*g.ap))/sigma + delta = g.delta + _debugf(' m=%s, n=%s, p=%s, q=%s, tau=%s, nu=%s, rho=%s, sigma=%s', + (m, n, p, q, tau, nu, rho, sigma)) + _debugf(' epsilon=%s, theta=%s, delta=%s', (epsilon, theta, delta)) + + # First check if the computation is valid. + if not (g.delta >= e/2 or (p >= 1 and p >= q)): + _debug(' Computation not valid for these parameters.') + return False + + # Now check if the inversion integral exists. + + # Test "condition A" + for a, b in itertools.product(g.an, g.bm): + if (a - b).is_integer and a > b: + _debug(' Not a valid G function.') + return False + + # There are two cases. If p >= q, we can directly use a slater expansion + # like [L], 5.2 (11). Note in particular that the asymptotics of such an + # expansion even hold when some of the parameters differ by integers, i.e. + # the formula itself would not be valid! (b/c G functions are cts. in their + # parameters) + # When p < q, we need to use the theorems of [L], 5.10. + + if p >= q: + _debug(' Using asymptotic Slater expansion.') + return And(*[statement(a - 1, 0, 0, z) for a in g.an]) + + def E(z): + return And(*[statement(a - 1, 0, 0, z) for a in g.an]) + + def H(z): + return statement(theta, -sigma, 1/sigma, z) + + def Hp(z): + return statement_half(theta, -sigma, 1/sigma, z, True) + + def Hm(z): + return statement_half(theta, -sigma, 1/sigma, z, False) + + # [L], section 5.10 + conds = [] + # Theorem 1 -- p < q from test above + conds += [And(1 <= n, 1 <= m, rho*pi - delta >= pi/2, delta > 0, + E(z*exp(S.ImaginaryUnit*pi*(nu + 1))))] + # Theorem 2, statements (2) and (3) + conds += [And(p + 1 <= m, m + 1 <= q, delta > 0, delta < pi/2, n == 0, + (m - p + 1)*pi - delta >= pi/2, + Hp(z*exp(S.ImaginaryUnit*pi*(q - m))), + Hm(z*exp(-S.ImaginaryUnit*pi*(q - m))))] + # Theorem 2, statement (5) -- p < q from test above + conds += [And(m == q, n == 0, delta > 0, + (sigma + epsilon)*pi - delta >= pi/2, H(z))] + # Theorem 3, statements (6) and (7) + conds += [And(Or(And(p <= q - 2, 1 <= tau, tau <= sigma/2), + And(p + 1 <= m + n, m + n <= (p + q)/2)), + delta > 0, delta < pi/2, (tau + 1)*pi - delta >= pi/2, + Hp(z*exp(S.ImaginaryUnit*pi*nu)), + Hm(z*exp(-S.ImaginaryUnit*pi*nu)))] + # Theorem 4, statements (10) and (11) -- p < q from test above + conds += [And(1 <= m, rho > 0, delta > 0, delta + rho*pi < pi/2, + (tau + epsilon)*pi - delta >= pi/2, + Hp(z*exp(S.ImaginaryUnit*pi*nu)), + Hm(z*exp(-S.ImaginaryUnit*pi*nu)))] + # Trivial case + conds += [m == 0] + + # TODO + # Theorem 5 is quite general + # Theorem 6 contains special cases for q=p+1 + + return Or(*conds) + + +def _int_inversion(g, x, t): + """ + Compute the laplace inversion integral, assuming the formula applies. + """ + b, a = _get_coeff_exp(g.argument, x) + C, g = _inflate_fox_h(meijerg(g.an, g.aother, g.bm, g.bother, b/t**a), -a) + return C/t*g + + +#################################################################### +# Finally, the real meat. +#################################################################### + +_lookup_table = None + + +@cacheit +@timeit +def _rewrite_single(f, x, recursive=True): + """ + Try to rewrite f as a sum of single G functions of the form + C*x**s*G(a*x**b), where b is a rational number and C is independent of x. + We guarantee that result.argument.as_coeff_mul(x) returns (a, (x**b,)) + or (a, ()). + Returns a list of tuples (C, s, G) and a condition cond. + Returns None on failure. + """ + from .transforms import (mellin_transform, inverse_mellin_transform, + IntegralTransformError, MellinTransformStripError) + + global _lookup_table + if not _lookup_table: + _lookup_table = {} + _create_lookup_table(_lookup_table) + + if isinstance(f, meijerg): + coeff, m = factor(f.argument, x).as_coeff_mul(x) + if len(m) > 1: + return None + m = m[0] + if m.is_Pow: + if m.base != x or not m.exp.is_Rational: + return None + elif m != x: + return None + return [(1, 0, meijerg(f.an, f.aother, f.bm, f.bother, coeff*m))], True + + f_ = f + f = f.subs(x, z) + t = _mytype(f, z) + if t in _lookup_table: + l = _lookup_table[t] + for formula, terms, cond, hint in l: + subs = f.match(formula, old=True) + if subs: + subs_ = {} + for fro, to in subs.items(): + subs_[fro] = unpolarify(polarify(to, lift=True), + exponents_only=True) + subs = subs_ + if not isinstance(hint, bool): + hint = hint.subs(subs) + if hint == False: + continue + if not isinstance(cond, (bool, BooleanAtom)): + cond = unpolarify(cond.subs(subs)) + if _eval_cond(cond) == False: + continue + if not isinstance(terms, list): + terms = terms(subs) + res = [] + for fac, g in terms: + r1 = _get_coeff_exp(unpolarify(fac.subs(subs).subs(z, x), + exponents_only=True), x) + try: + g = g.subs(subs).subs(z, x) + except ValueError: + continue + # NOTE these substitutions can in principle introduce oo, + # zoo and other absurdities. It shouldn't matter, + # but better be safe. + if Tuple(*(r1 + (g,))).has(S.Infinity, S.ComplexInfinity, S.NegativeInfinity): + continue + g = meijerg(g.an, g.aother, g.bm, g.bother, + unpolarify(g.argument, exponents_only=True)) + res.append(r1 + (g,)) + if res: + return res, cond + + # try recursive mellin transform + if not recursive: + return None + _debug('Trying recursive Mellin transform method.') + + def my_imt(F, s, x, strip): + """ Calling simplify() all the time is slow and not helpful, since + most of the time it only factors things in a way that has to be + un-done anyway. But sometimes it can remove apparent poles. """ + # XXX should this be in inverse_mellin_transform? + try: + return inverse_mellin_transform(F, s, x, strip, + as_meijerg=True, needeval=True) + except MellinTransformStripError: + from sympy.simplify import simplify + return inverse_mellin_transform( + simplify(cancel(expand(F))), s, x, strip, + as_meijerg=True, needeval=True) + f = f_ + s = _dummy('s', 'rewrite-single', f) + # to avoid infinite recursion, we have to force the two g functions case + + def my_integrator(f, x): + r = _meijerint_definite_4(f, x, only_double=True) + if r is not None: + from sympy.simplify import hyperexpand + res, cond = r + res = _my_unpolarify(hyperexpand(res, rewrite='nonrepsmall')) + return Piecewise((res, cond), + (Integral(f, (x, S.Zero, S.Infinity)), True)) + return Integral(f, (x, S.Zero, S.Infinity)) + try: + F, strip, _ = mellin_transform(f, x, s, integrator=my_integrator, + simplify=False, needeval=True) + g = my_imt(F, s, x, strip) + except IntegralTransformError: + g = None + if g is None: + # We try to find an expression by analytic continuation. + # (also if the dummy is already in the expression, there is no point in + # putting in another one) + a = _dummy_('a', 'rewrite-single') + if a not in f.free_symbols and _is_analytic(f, x): + try: + F, strip, _ = mellin_transform(f.subs(x, a*x), x, s, + integrator=my_integrator, + needeval=True, simplify=False) + g = my_imt(F, s, x, strip).subs(a, 1) + except IntegralTransformError: + g = None + if g is None or g.has(S.Infinity, S.NaN, S.ComplexInfinity): + _debug('Recursive Mellin transform failed.') + return None + args = Add.make_args(g) + res = [] + for f in args: + c, m = f.as_coeff_mul(x) + if len(m) > 1: + raise NotImplementedError('Unexpected form...') + g = m[0] + a, b = _get_coeff_exp(g.argument, x) + res += [(c, 0, meijerg(g.an, g.aother, g.bm, g.bother, + unpolarify(polarify( + a, lift=True), exponents_only=True) + *x**b))] + _debug('Recursive Mellin transform worked:', g) + return res, True + + +def _rewrite1(f, x, recursive=True): + """ + Try to rewrite ``f`` using a (sum of) single G functions with argument a*x**b. + Return fac, po, g such that f = fac*po*g, fac is independent of ``x``. + and po = x**s. + Here g is a result from _rewrite_single. + Return None on failure. + """ + fac, po, g = _split_mul(f, x) + g = _rewrite_single(g, x, recursive) + if g: + return fac, po, g[0], g[1] + + +def _rewrite2(f, x): + """ + Try to rewrite ``f`` as a product of two G functions of arguments a*x**b. + Return fac, po, g1, g2 such that f = fac*po*g1*g2, where fac is + independent of x and po is x**s. + Here g1 and g2 are results of _rewrite_single. + Returns None on failure. + """ + fac, po, g = _split_mul(f, x) + if any(_rewrite_single(expr, x, False) is None for expr in _mul_args(g)): + return None + l = _mul_as_two_parts(g) + if not l: + return None + l = list(ordered(l, [ + lambda p: max(len(_exponents(p[0], x)), len(_exponents(p[1], x))), + lambda p: max(len(_functions(p[0], x)), len(_functions(p[1], x))), + lambda p: max(len(_find_splitting_points(p[0], x)), + len(_find_splitting_points(p[1], x)))])) + + for recursive, (fac1, fac2) in itertools.product((False, True), l): + g1 = _rewrite_single(fac1, x, recursive) + g2 = _rewrite_single(fac2, x, recursive) + if g1 and g2: + cond = And(g1[1], g2[1]) + if cond != False: + return fac, po, g1[0], g2[0], cond + + +def meijerint_indefinite(f, x): + """ + Compute an indefinite integral of ``f`` by rewriting it as a G function. + + Examples + ======== + + >>> from sympy.integrals.meijerint import meijerint_indefinite + >>> from sympy import sin + >>> from sympy.abc import x + >>> meijerint_indefinite(sin(x), x) + -cos(x) + """ + f = sympify(f) + results = [] + for a in sorted(_find_splitting_points(f, x) | {S.Zero}, key=default_sort_key): + res = _meijerint_indefinite_1(f.subs(x, x + a), x) + if not res: + continue + res = res.subs(x, x - a) + if _has(res, hyper, meijerg): + results.append(res) + else: + return res + if f.has(HyperbolicFunction): + _debug('Try rewriting hyperbolics in terms of exp.') + rv = meijerint_indefinite( + _rewrite_hyperbolics_as_exp(f), x) + if rv: + if not isinstance(rv, list): + from sympy.simplify.radsimp import collect + return collect(factor_terms(rv), rv.atoms(exp)) + results.extend(rv) + if results: + return next(ordered(results)) + + +def _meijerint_indefinite_1(f, x): + """ Helper that does not attempt any substitution. """ + _debug('Trying to compute the indefinite integral of', f, 'wrt', x) + from sympy.simplify import hyperexpand, powdenest + + gs = _rewrite1(f, x) + if gs is None: + # Note: the code that calls us will do expand() and try again + return None + + fac, po, gl, cond = gs + _debug(' could rewrite:', gs) + res = S.Zero + for C, s, g in gl: + a, b = _get_coeff_exp(g.argument, x) + _, c = _get_coeff_exp(po, x) + c += s + + # we do a substitution t=a*x**b, get integrand fac*t**rho*g + fac_ = fac * C * x**(1 + c) / b + rho = (c + 1)/b + + # we now use t**rho*G(params, t) = G(params + rho, t) + # [L, page 150, equation (4)] + # and integral G(params, t) dt = G(1, params+1, 0, t) + # (or a similar expression with 1 and 0 exchanged ... pick the one + # which yields a well-defined function) + # [R, section 5] + # (Note that this dummy will immediately go away again, so we + # can safely pass S.One for ``expr``.) + t = _dummy('t', 'meijerint-indefinite', S.One) + + def tr(p): + return [a + rho for a in p] + if any(b.is_integer and (b <= 0) == True for b in tr(g.bm)): + r = -meijerg( + list(g.an), list(g.aother) + [1-rho], list(g.bm) + [-rho], list(g.bother), t) + else: + r = meijerg( + list(g.an) + [1-rho], list(g.aother), list(g.bm), list(g.bother) + [-rho], t) + # The antiderivative is most often expected to be defined + # in the neighborhood of x = 0. + if b.is_extended_nonnegative and not f.subs(x, 0).has(S.NaN, S.ComplexInfinity): + place = 0 # Assume we can expand at zero + else: + place = None + r = hyperexpand(r.subs(t, a*x**b), place=place) + + # now substitute back + # Note: we really do want the powers of x to combine. + res += powdenest(fac_*r, polar=True) + + def _clean(res): + """This multiplies out superfluous powers of x we created, and chops off + constants: + + >> _clean(x*(exp(x)/x - 1/x) + 3) + exp(x) + + cancel is used before mul_expand since it is possible for an + expression to have an additive constant that does not become isolated + with simple expansion. Such a situation was identified in issue 6369: + + Examples + ======== + + >>> from sympy import sqrt, cancel + >>> from sympy.abc import x + >>> a = sqrt(2*x + 1) + >>> bad = (3*x*a**5 + 2*x - a**5 + 1)/a**2 + >>> bad.expand().as_independent(x)[0] + 0 + >>> cancel(bad).expand().as_independent(x)[0] + 1 + """ + res = expand_mul(cancel(res), deep=False) + return Add._from_args(res.as_coeff_add(x)[1]) + + res = piecewise_fold(res, evaluate=None) + if res.is_Piecewise: + newargs = [] + for e, c in res.args: + e = _my_unpolarify(_clean(e)) + newargs += [(e, c)] + res = Piecewise(*newargs, evaluate=False) + else: + res = _my_unpolarify(_clean(res)) + return Piecewise((res, _my_unpolarify(cond)), (Integral(f, x), True)) + + +@timeit +def meijerint_definite(f, x, a, b): + """ + Integrate ``f`` over the interval [``a``, ``b``], by rewriting it as a product + of two G functions, or as a single G function. + + Return res, cond, where cond are convergence conditions. + + Examples + ======== + + >>> from sympy.integrals.meijerint import meijerint_definite + >>> from sympy import exp, oo + >>> from sympy.abc import x + >>> meijerint_definite(exp(-x**2), x, -oo, oo) + (sqrt(pi), True) + + This function is implemented as a succession of functions + meijerint_definite, _meijerint_definite_2, _meijerint_definite_3, + _meijerint_definite_4. Each function in the list calls the next one + (presumably) several times. This means that calling meijerint_definite + can be very costly. + """ + # This consists of three steps: + # 1) Change the integration limits to 0, oo + # 2) Rewrite in terms of G functions + # 3) Evaluate the integral + # + # There are usually several ways of doing this, and we want to try all. + # This function does (1), calls _meijerint_definite_2 for step (2). + _debugf('Integrating %s wrt %s from %s to %s.', (f, x, a, b)) + f = sympify(f) + if f.has(DiracDelta): + _debug('Integrand has DiracDelta terms - giving up.') + return None + + if f.has(SingularityFunction): + _debug('Integrand has Singularity Function terms - giving up.') + return None + + f_, x_, a_, b_ = f, x, a, b + + # Let's use a dummy in case any of the boundaries has x. + d = Dummy('x') + f = f.subs(x, d) + x = d + + if a == b: + return (S.Zero, True) + + results = [] + if a is S.NegativeInfinity and b is not S.Infinity: + return meijerint_definite(f.subs(x, -x), x, -b, -a) + + elif a is S.NegativeInfinity: + # Integrating -oo to oo. We need to find a place to split the integral. + _debug(' Integrating -oo to +oo.') + innermost = _find_splitting_points(f, x) + _debug(' Sensible splitting points:', innermost) + for c in sorted(innermost, key=default_sort_key, reverse=True) + [S.Zero]: + _debug(' Trying to split at', c) + if not c.is_extended_real: + _debug(' Non-real splitting point.') + continue + res1 = _meijerint_definite_2(f.subs(x, x + c), x) + if res1 is None: + _debug(' But could not compute first integral.') + continue + res2 = _meijerint_definite_2(f.subs(x, c - x), x) + if res2 is None: + _debug(' But could not compute second integral.') + continue + res1, cond1 = res1 + res2, cond2 = res2 + cond = _condsimp(And(cond1, cond2)) + if cond == False: + _debug(' But combined condition is always false.') + continue + res = res1 + res2 + return res, cond + + elif a is S.Infinity: + res = meijerint_definite(f, x, b, S.Infinity) + return -res[0], res[1] + + elif (a, b) == (S.Zero, S.Infinity): + # This is a common case - try it directly first. + res = _meijerint_definite_2(f, x) + if res: + if _has(res[0], meijerg): + results.append(res) + else: + return res + + else: + if b is S.Infinity: + for split in _find_splitting_points(f, x): + if (a - split >= 0) == True: + _debugf('Trying x -> x + %s', split) + res = _meijerint_definite_2(f.subs(x, x + split) + *Heaviside(x + split - a), x) + if res: + if _has(res[0], meijerg): + results.append(res) + else: + return res + + f = f.subs(x, x + a) + b = b - a + a = 0 + if b is not S.Infinity: + phi = exp(S.ImaginaryUnit*arg(b)) + b = Abs(b) + f = f.subs(x, phi*x) + f *= Heaviside(b - x)*phi + b = S.Infinity + + _debug('Changed limits to', a, b) + _debug('Changed function to', f) + res = _meijerint_definite_2(f, x) + if res: + if _has(res[0], meijerg): + results.append(res) + else: + return res + if f_.has(HyperbolicFunction): + _debug('Try rewriting hyperbolics in terms of exp.') + rv = meijerint_definite( + _rewrite_hyperbolics_as_exp(f_), x_, a_, b_) + if rv: + if not isinstance(rv, list): + from sympy.simplify.radsimp import collect + rv = (collect(factor_terms(rv[0]), rv[0].atoms(exp)),) + rv[1:] + return rv + results.extend(rv) + if results: + return next(ordered(results)) + + +def _guess_expansion(f, x): + """ Try to guess sensible rewritings for integrand f(x). """ + res = [(f, 'original integrand')] + + orig = res[-1][0] + saw = {orig} + expanded = expand_mul(orig) + if expanded not in saw: + res += [(expanded, 'expand_mul')] + saw.add(expanded) + + expanded = expand(orig) + if expanded not in saw: + res += [(expanded, 'expand')] + saw.add(expanded) + + if orig.has(TrigonometricFunction, HyperbolicFunction): + expanded = expand_mul(expand_trig(orig)) + if expanded not in saw: + res += [(expanded, 'expand_trig, expand_mul')] + saw.add(expanded) + + if orig.has(cos, sin): + from sympy.simplify.fu import sincos_to_sum + reduced = sincos_to_sum(orig) + if reduced not in saw: + res += [(reduced, 'trig power reduction')] + saw.add(reduced) + + return res + + +def _meijerint_definite_2(f, x): + """ + Try to integrate f dx from zero to infinity. + + The body of this function computes various 'simplifications' + f1, f2, ... of f (e.g. by calling expand_mul(), trigexpand() + - see _guess_expansion) and calls _meijerint_definite_3 with each of + these in succession. + If _meijerint_definite_3 succeeds with any of the simplified functions, + returns this result. + """ + # This function does preparation for (2), calls + # _meijerint_definite_3 for (2) and (3) combined. + + # use a positive dummy - we integrate from 0 to oo + # XXX if a nonnegative symbol is used there will be test failures + dummy = _dummy('x', 'meijerint-definite2', f, positive=True) + f = f.subs(x, dummy) + x = dummy + + if f == 0: + return S.Zero, True + + for g, explanation in _guess_expansion(f, x): + _debug('Trying', explanation) + res = _meijerint_definite_3(g, x) + if res: + return res + + +def _meijerint_definite_3(f, x): + """ + Try to integrate f dx from zero to infinity. + + This function calls _meijerint_definite_4 to try to compute the + integral. If this fails, it tries using linearity. + """ + res = _meijerint_definite_4(f, x) + if res and res[1] != False: + return res + if f.is_Add: + _debug('Expanding and evaluating all terms.') + ress = [_meijerint_definite_4(g, x) for g in f.args] + if all(r is not None for r in ress): + conds = [] + res = S.Zero + for r, c in ress: + res += r + conds += [c] + c = And(*conds) + if c != False: + return res, c + + +def _my_unpolarify(f): + return _eval_cond(unpolarify(f)) + + +@timeit +def _meijerint_definite_4(f, x, only_double=False): + """ + Try to integrate f dx from zero to infinity. + + Explanation + =========== + + This function tries to apply the integration theorems found in literature, + i.e. it tries to rewrite f as either one or a product of two G-functions. + + The parameter ``only_double`` is used internally in the recursive algorithm + to disable trying to rewrite f as a single G-function. + """ + from sympy.simplify import hyperexpand + # This function does (2) and (3) + _debug('Integrating', f) + # Try single G function. + if not only_double: + gs = _rewrite1(f, x, recursive=False) + if gs is not None: + fac, po, g, cond = gs + _debug('Could rewrite as single G function:', fac, po, g) + res = S.Zero + for C, s, f in g: + if C == 0: + continue + C, f = _rewrite_saxena_1(fac*C, po*x**s, f, x) + res += C*_int0oo_1(f, x) + cond = And(cond, _check_antecedents_1(f, x)) + if cond == False: + break + cond = _my_unpolarify(cond) + if cond == False: + _debug('But cond is always False.') + else: + _debug('Result before branch substitutions is:', res) + return _my_unpolarify(hyperexpand(res)), cond + + # Try two G functions. + gs = _rewrite2(f, x) + if gs is not None: + for full_pb in [False, True]: + fac, po, g1, g2, cond = gs + _debug('Could rewrite as two G functions:', fac, po, g1, g2) + res = S.Zero + for C1, s1, f1 in g1: + for C2, s2, f2 in g2: + r = _rewrite_saxena(fac*C1*C2, po*x**(s1 + s2), + f1, f2, x, full_pb) + if r is None: + _debug('Non-rational exponents.') + return + C, f1_, f2_ = r + _debug('Saxena subst for yielded:', C, f1_, f2_) + cond = And(cond, _check_antecedents(f1_, f2_, x)) + if cond == False: + break + res += C*_int0oo(f1_, f2_, x) + else: + continue + break + cond = _my_unpolarify(cond) + if cond == False: + _debugf('But cond is always False (full_pb=%s).', full_pb) + else: + _debugf('Result before branch substitutions is: %s', (res, )) + if only_double: + return res, cond + return _my_unpolarify(hyperexpand(res)), cond + + +def meijerint_inversion(f, x, t): + r""" + Compute the inverse laplace transform + $\int_{c+i\infty}^{c-i\infty} f(x) e^{tx}\, dx$, + for real c larger than the real part of all singularities of ``f``. + + Note that ``t`` is always assumed real and positive. + + Return None if the integral does not exist or could not be evaluated. + + Examples + ======== + + >>> from sympy.abc import x, t + >>> from sympy.integrals.meijerint import meijerint_inversion + >>> meijerint_inversion(1/x, x, t) + Heaviside(t) + """ + f_ = f + t_ = t + t = Dummy('t', polar=True) # We don't want sqrt(t**2) = abs(t) etc + f = f.subs(t_, t) + _debug('Laplace-inverting', f) + if not _is_analytic(f, x): + _debug('But expression is not analytic.') + return None + # Exponentials correspond to shifts; we filter them out and then + # shift the result later. If we are given an Add this will not + # work, but the calling code will take care of that. + shift = S.Zero + + if f.is_Mul: + args = list(f.args) + elif isinstance(f, exp): + args = [f] + else: + args = None + + if args: + newargs = [] + exponentials = [] + while args: + arg = args.pop() + if isinstance(arg, exp): + arg2 = expand(arg) + if arg2.is_Mul: + args += arg2.args + continue + try: + a, b = _get_coeff_exp(arg.args[0], x) + except _CoeffExpValueError: + b = 0 + if b == 1: + exponentials.append(a) + else: + newargs.append(arg) + elif arg.is_Pow: + arg2 = expand(arg) + if arg2.is_Mul: + args += arg2.args + continue + if x not in arg.base.free_symbols: + try: + a, b = _get_coeff_exp(arg.exp, x) + except _CoeffExpValueError: + b = 0 + if b == 1: + exponentials.append(a*log(arg.base)) + newargs.append(arg) + else: + newargs.append(arg) + shift = Add(*exponentials) + f = Mul(*newargs) + + if x not in f.free_symbols: + _debug('Expression consists of constant and exp shift:', f, shift) + cond = Eq(im(shift), 0) + if cond == False: + _debug('but shift is nonreal, cannot be a Laplace transform') + return None + res = f*DiracDelta(t + shift) + _debug('Result is a delta function, possibly conditional:', res, cond) + # cond is True or Eq + return Piecewise((res.subs(t, t_), cond)) + + gs = _rewrite1(f, x) + if gs is not None: + fac, po, g, cond = gs + _debug('Could rewrite as single G function:', fac, po, g) + res = S.Zero + for C, s, f in g: + C, f = _rewrite_inversion(fac*C, po*x**s, f, x) + res += C*_int_inversion(f, x, t) + cond = And(cond, _check_antecedents_inversion(f, x)) + if cond == False: + break + cond = _my_unpolarify(cond) + if cond == False: + _debug('But cond is always False.') + else: + _debug('Result before branch substitution:', res) + from sympy.simplify import hyperexpand + res = _my_unpolarify(hyperexpand(res)) + if not res.has(Heaviside): + res *= Heaviside(t) + res = res.subs(t, t + shift) + if not isinstance(cond, bool): + cond = cond.subs(t, t + shift) + from .transforms import InverseLaplaceTransform + return Piecewise((res.subs(t, t_), cond), + (InverseLaplaceTransform(f_.subs(t, t_), x, t_, None), True)) diff --git a/phivenv/Lib/site-packages/sympy/integrals/meijerint_doc.py b/phivenv/Lib/site-packages/sympy/integrals/meijerint_doc.py new file mode 100644 index 0000000000000000000000000000000000000000..1df385db6b6fe6d46d4a14217aa53d1fdd9670b5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/meijerint_doc.py @@ -0,0 +1,38 @@ +""" This module cooks up a docstring when imported. Its only purpose is to + be displayed in the sphinx documentation. """ + +from __future__ import annotations +from typing import Any + +from sympy.integrals.meijerint import _create_lookup_table +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.expr import Expr +from sympy.core.relational import Eq +from sympy.core.symbol import Symbol +from sympy.printing.latex import latex + +t: dict[tuple[type[Basic], ...], list[Any]] = {} +_create_lookup_table(t) + + +doc = "" +for about, category in t.items(): + if about == (): + doc += 'Elementary functions:\n\n' + else: + doc += 'Functions involving ' + ', '.join('`%s`' % latex( + list(category[0][0].atoms(func))[0]) for func in about) + ':\n\n' + for formula, gs, cond, hint in category: + if not isinstance(gs, list): + g: Expr = Symbol('\\text{generated}') + else: + g = Add(*[fac*f for (fac, f) in gs]) + obj = Eq(formula, g) + if cond is True: + cond = "" + else: + cond = ',\\text{ if } %s' % latex(cond) + doc += ".. math::\n %s%s\n\n" % (latex(obj), cond) + +__doc__ = doc diff --git a/phivenv/Lib/site-packages/sympy/integrals/prde.py b/phivenv/Lib/site-packages/sympy/integrals/prde.py new file mode 100644 index 0000000000000000000000000000000000000000..28e91ea0ff3a82cbeca24c6ed4267503ceb758b5 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/prde.py @@ -0,0 +1,1333 @@ +""" +Algorithms for solving Parametric Risch Differential Equations. + +The methods used for solving Parametric Risch Differential Equations parallel +those for solving Risch Differential Equations. See the outline in the +docstring of rde.py for more information. + +The Parametric Risch Differential Equation problem is, given f, g1, ..., gm in +K(t), to determine if there exist y in K(t) and c1, ..., cm in Const(K) such +that Dy + f*y == Sum(ci*gi, (i, 1, m)), and to find such y and ci if they exist. + +For the algorithms here G is a list of tuples of factions of the terms on the +right hand side of the equation (i.e., gi in k(t)), and Q is a list of terms on +the right hand side of the equation (i.e., qi in k[t]). See the docstring of +each function for more information. +""" +import itertools +from functools import reduce + +from sympy.core.intfunc import ilcm +from sympy.core import Dummy, Add, Mul, Pow, S +from sympy.integrals.rde import (order_at, order_at_oo, weak_normalizer, + bound_degree) +from sympy.integrals.risch import (gcdex_diophantine, frac_in, derivation, + residue_reduce, splitfactor, residue_reduce_derivation, DecrementLevel, + recognize_log_derivative) +from sympy.polys import Poly, lcm, cancel, sqf_list +from sympy.polys.polymatrix import PolyMatrix as Matrix +from sympy.solvers import solve + +zeros = Matrix.zeros +eye = Matrix.eye + + +def prde_normal_denom(fa, fd, G, DE): + """ + Parametric Risch Differential Equation - Normal part of the denominator. + + Explanation + =========== + + Given a derivation D on k[t] and f, g1, ..., gm in k(t) with f weakly + normalized with respect to t, return the tuple (a, b, G, h) such that + a, h in k[t], b in k, G = [g1, ..., gm] in k(t)^m, and for any solution + c1, ..., cm in Const(k) and y in k(t) of Dy + f*y == Sum(ci*gi, (i, 1, m)), + q == y*h in k satisfies a*Dq + b*q == Sum(ci*Gi, (i, 1, m)). + """ + dn, ds = splitfactor(fd, DE) + Gas, Gds = list(zip(*G)) + gd = reduce(lambda i, j: i.lcm(j), Gds, Poly(1, DE.t)) + en, es = splitfactor(gd, DE) + + p = dn.gcd(en) + h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t))) + + a = dn*h + c = a*h + + ba = a*fa - dn*derivation(h, DE)*fd + ba, bd = ba.cancel(fd, include=True) + + G = [(c*A).cancel(D, include=True) for A, D in G] + + return (a, (ba, bd), G, h) + +def real_imag(ba, bd, gen): + """ + Helper function, to get the real and imaginary part of a rational function + evaluated at sqrt(-1) without actually evaluating it at sqrt(-1). + + Explanation + =========== + + Separates the even and odd power terms by checking the degree of terms wrt + mod 4. Returns a tuple (ba[0], ba[1], bd) where ba[0] is real part + of the numerator ba[1] is the imaginary part and bd is the denominator + of the rational function. + """ + bd = bd.as_poly(gen).as_dict() + ba = ba.as_poly(gen).as_dict() + denom_real = [value if key[0] % 4 == 0 else -value if key[0] % 4 == 2 else 0 for key, value in bd.items()] + denom_imag = [value if key[0] % 4 == 1 else -value if key[0] % 4 == 3 else 0 for key, value in bd.items()] + bd_real = sum(r for r in denom_real) + bd_imag = sum(r for r in denom_imag) + num_real = [value if key[0] % 4 == 0 else -value if key[0] % 4 == 2 else 0 for key, value in ba.items()] + num_imag = [value if key[0] % 4 == 1 else -value if key[0] % 4 == 3 else 0 for key, value in ba.items()] + ba_real = sum(r for r in num_real) + ba_imag = sum(r for r in num_imag) + ba = ((ba_real*bd_real + ba_imag*bd_imag).as_poly(gen), (ba_imag*bd_real - ba_real*bd_imag).as_poly(gen)) + bd = (bd_real*bd_real + bd_imag*bd_imag).as_poly(gen) + return (ba[0], ba[1], bd) + + +def prde_special_denom(a, ba, bd, G, DE, case='auto'): + """ + Parametric Risch Differential Equation - Special part of the denominator. + + Explanation + =========== + + Case is one of {'exp', 'tan', 'primitive'} for the hyperexponential, + hypertangent, and primitive cases, respectively. For the hyperexponential + (resp. hypertangent) case, given a derivation D on k[t] and a in k[t], + b in k, and g1, ..., gm in k(t) with Dt/t in k (resp. Dt/(t**2 + 1) in + k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp. + gcd(a, t**2 + 1) == 1), return the tuple (A, B, GG, h) such that A, B, h in + k[t], GG = [gg1, ..., ggm] in k(t)^m, and for any solution c1, ..., cm in + Const(k) and q in k of a*Dq + b*q == Sum(ci*gi, (i, 1, m)), r == q*h in + k[t] satisfies A*Dr + B*r == Sum(ci*ggi, (i, 1, m)). + + For case == 'primitive', k == k[t], so it returns (a, b, G, 1) in this + case. + """ + # TODO: Merge this with the very similar special_denom() in rde.py + if case == 'auto': + case = DE.case + + if case == 'exp': + p = Poly(DE.t, DE.t) + elif case == 'tan': + p = Poly(DE.t**2 + 1, DE.t) + elif case in ('primitive', 'base'): + B = ba.quo(bd) + return (a, B, G, Poly(1, DE.t)) + else: + raise ValueError("case must be one of {'exp', 'tan', 'primitive', " + "'base'}, not %s." % case) + + nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t) + nc = min(order_at(Ga, p, DE.t) - order_at(Gd, p, DE.t) for Ga, Gd in G) + n = min(0, nc - min(0, nb)) + if not nb: + # Possible cancellation. + if case == 'exp': + dcoeff = DE.d.quo(Poly(DE.t, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + alphaa, alphad = frac_in(-ba.eval(0)/bd.eval(0)/a.eval(0), DE.t) + etaa, etad = frac_in(dcoeff, DE.t) + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + if A is not None: + Q, m, z = A + if Q == 1: + n = min(n, m) + + elif case == 'tan': + dcoeff = DE.d.quo(Poly(DE.t**2 + 1, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + betaa, alphaa, alphad = real_imag(ba, bd*a, DE.t) + betad = alphad + etaa, etad = frac_in(dcoeff, DE.t) + if recognize_log_derivative(Poly(2, DE.t)*betaa, betad, DE): + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + B = parametric_log_deriv(betaa, betad, etaa, etad, DE) + if A is not None and B is not None: + Q, s, z = A + # TODO: Add test + if Q == 1: + n = min(n, s/2) + + N = max(0, -nb) + pN = p**N + pn = p**-n # This is 1/h + + A = a*pN + B = ba*pN.quo(bd) + Poly(n, DE.t)*a*derivation(p, DE).quo(p)*pN + G = [(Ga*pN*pn).cancel(Gd, include=True) for Ga, Gd in G] + h = pn + + # (a*p**N, (b + n*a*Dp/p)*p**N, g1*p**(N - n), ..., gm*p**(N - n), p**-n) + return (A, B, G, h) + + +def prde_linear_constraints(a, b, G, DE): + """ + Parametric Risch Differential Equation - Generate linear constraints on the constants. + + Explanation + =========== + + Given a derivation D on k[t], a, b, in k[t] with gcd(a, b) == 1, and + G = [g1, ..., gm] in k(t)^m, return Q = [q1, ..., qm] in k[t]^m and a + matrix M with entries in k(t) such that for any solution c1, ..., cm in + Const(k) and p in k[t] of a*Dp + b*p == Sum(ci*gi, (i, 1, m)), + (c1, ..., cm) is a solution of Mx == 0, and p and the ci satisfy + a*Dp + b*p == Sum(ci*qi, (i, 1, m)). + + Because M has entries in k(t), and because Matrix does not play well with + Poly, M will be a Matrix of Basic expressions. + """ + m = len(G) + + Gns, Gds = list(zip(*G)) + d = reduce(lambda i, j: i.lcm(j), Gds) + d = Poly(d, field=True) + Q = [(ga*(d).quo(gd)).div(d) for ga, gd in G] + + if not all(ri.is_zero for _, ri in Q): + N = max(ri.degree(DE.t) for _, ri in Q) + M = Matrix(N + 1, m, lambda i, j: Q[j][1].nth(i), DE.t) + else: + M = Matrix(0, m, [], DE.t) # No constraints, return the empty matrix. + + qs, _ = list(zip(*Q)) + return (qs, M) + +def poly_linear_constraints(p, d): + """ + Given p = [p1, ..., pm] in k[t]^m and d in k[t], return + q = [q1, ..., qm] in k[t]^m and a matrix M with entries in k such + that Sum(ci*pi, (i, 1, m)), for c1, ..., cm in k, is divisible + by d if and only if (c1, ..., cm) is a solution of Mx = 0, in + which case the quotient is Sum(ci*qi, (i, 1, m)). + """ + m = len(p) + q, r = zip(*[pi.div(d) for pi in p]) + + if not all(ri.is_zero for ri in r): + n = max(ri.degree() for ri in r) + M = Matrix(n + 1, m, lambda i, j: r[j].nth(i), d.gens) + else: + M = Matrix(0, m, [], d.gens) # No constraints. + + return q, M + +def constant_system(A, u, DE): + """ + Generate a system for the constant solutions. + + Explanation + =========== + + Given a differential field (K, D) with constant field C = Const(K), a Matrix + A, and a vector (Matrix) u with coefficients in K, returns the tuple + (B, v, s), where B is a Matrix with coefficients in C and v is a vector + (Matrix) such that either v has coefficients in C, in which case s is True + and the solutions in C of Ax == u are exactly all the solutions of Bx == v, + or v has a non-constant coefficient, in which case s is False Ax == u has no + constant solution. + + This algorithm is used both in solving parametric problems and in + determining if an element a of K is a derivative of an element of K or the + logarithmic derivative of a K-radical using the structure theorem approach. + + Because Poly does not play well with Matrix yet, this algorithm assumes that + all matrix entries are Basic expressions. + """ + if not A: + return A, u + Au = A.row_join(u) + Au, _ = Au.rref() + # Warning: This will NOT return correct results if cancel() cannot reduce + # an identically zero expression to 0. The danger is that we might + # incorrectly prove that an integral is nonelementary (such as + # risch_integrate(exp((sin(x)**2 + cos(x)**2 - 1)*x**2), x). + # But this is a limitation in computer algebra in general, and implicit + # in the correctness of the Risch Algorithm is the computability of the + # constant field (actually, this same correctness problem exists in any + # algorithm that uses rref()). + # + # We therefore limit ourselves to constant fields that are computable + # via the cancel() function, in order to prevent a speed bottleneck from + # calling some more complex simplification function (rational function + # coefficients will fall into this class). Furthermore, (I believe) this + # problem will only crop up if the integral explicitly contains an + # expression in the constant field that is identically zero, but cannot + # be reduced to such by cancel(). Therefore, a careful user can avoid this + # problem entirely by being careful with the sorts of expressions that + # appear in his integrand in the variables other than the integration + # variable (the structure theorems should be able to completely decide these + # problems in the integration variable). + + A, u = Au[:, :-1], Au[:, -1] + + D = lambda x: derivation(x, DE, basic=True) + + for j, i in itertools.product(range(A.cols), range(A.rows)): + if A[i, j].expr.has(*DE.T): + # This assumes that const(F(t0, ..., tn) == const(K) == F + Ri = A[i, :] + # Rm+1; m = A.rows + DAij = D(A[i, j]) + Rm1 = Ri.applyfunc(lambda x: D(x) / DAij) + um1 = D(u[i]) / DAij + + Aj = A[:, j] + A = A - Aj * Rm1 + u = u - Aj * um1 + + A = A.col_join(Rm1) + u = u.col_join(Matrix([um1], u.gens)) + + return (A, u) + + +def prde_spde(a, b, Q, n, DE): + """ + Special Polynomial Differential Equation algorithm: Parametric Version. + + Explanation + =========== + + Given a derivation D on k[t], an integer n, and a, b, q1, ..., qm in k[t] + with deg(a) > 0 and gcd(a, b) == 1, return (A, B, Q, R, n1), with + Qq = [q1, ..., qm] and R = [r1, ..., rm], such that for any solution + c1, ..., cm in Const(k) and q in k[t] of degree at most n of + a*Dq + b*q == Sum(ci*gi, (i, 1, m)), p = (q - Sum(ci*ri, (i, 1, m)))/a has + degree at most n1 and satisfies A*Dp + B*p == Sum(ci*qi, (i, 1, m)) + """ + R, Z = list(zip(*[gcdex_diophantine(b, a, qi) for qi in Q])) + + A = a + B = b + derivation(a, DE) + Qq = [zi - derivation(ri, DE) for ri, zi in zip(R, Z)] + R = list(R) + n1 = n - a.degree(DE.t) + + return (A, B, Qq, R, n1) + + +def prde_no_cancel_b_large(b, Q, n, DE): + """ + Parametric Poly Risch Differential Equation - No cancellation: deg(b) large enough. + + Explanation + =========== + + Given a derivation D on k[t], n in ZZ, and b, q1, ..., qm in k[t] with + b != 0 and either D == d/dt or deg(b) > max(0, deg(D) - 1), returns + h1, ..., hr in k[t] and a matrix A with coefficients in Const(k) such that + if c1, ..., cm in Const(k) and q in k[t] satisfy deg(q) <= n and + Dq + b*q == Sum(ci*qi, (i, 1, m)), then q = Sum(dj*hj, (j, 1, r)), where + d1, ..., dr in Const(k) and A*Matrix([[c1, ..., cm, d1, ..., dr]]).T == 0. + """ + db = b.degree(DE.t) + m = len(Q) + H = [Poly(0, DE.t)]*m + + for N, i in itertools.product(range(n, -1, -1), range(m)): # [n, ..., 0] + si = Q[i].nth(N + db)/b.LC() + sitn = Poly(si*DE.t**N, DE.t) + H[i] = H[i] + sitn + Q[i] = Q[i] - derivation(sitn, DE) - b*sitn + + if all(qi.is_zero for qi in Q): + dc = -1 + else: + dc = max(qi.degree(DE.t) for qi in Q) + M = Matrix(dc + 1, m, lambda i, j: Q[j].nth(i), DE.t) + A, u = constant_system(M, zeros(dc + 1, 1, DE.t), DE) + c = eye(m, DE.t) + A = A.row_join(zeros(A.rows, m, DE.t)).col_join(c.row_join(-c)) + + return (H, A) + + +def prde_no_cancel_b_small(b, Q, n, DE): + """ + Parametric Poly Risch Differential Equation - No cancellation: deg(b) small enough. + + Explanation + =========== + + Given a derivation D on k[t], n in ZZ, and b, q1, ..., qm in k[t] with + deg(b) < deg(D) - 1 and either D == d/dt or deg(D) >= 2, returns + h1, ..., hr in k[t] and a matrix A with coefficients in Const(k) such that + if c1, ..., cm in Const(k) and q in k[t] satisfy deg(q) <= n and + Dq + b*q == Sum(ci*qi, (i, 1, m)) then q = Sum(dj*hj, (j, 1, r)) where + d1, ..., dr in Const(k) and A*Matrix([[c1, ..., cm, d1, ..., dr]]).T == 0. + """ + m = len(Q) + H = [Poly(0, DE.t)]*m + + for N, i in itertools.product(range(n, 0, -1), range(m)): # [n, ..., 1] + si = Q[i].nth(N + DE.d.degree(DE.t) - 1)/(N*DE.d.LC()) + sitn = Poly(si*DE.t**N, DE.t) + H[i] = H[i] + sitn + Q[i] = Q[i] - derivation(sitn, DE) - b*sitn + + if b.degree(DE.t) > 0: + for i in range(m): + si = Poly(Q[i].nth(b.degree(DE.t))/b.LC(), DE.t) + H[i] = H[i] + si + Q[i] = Q[i] - derivation(si, DE) - b*si + if all(qi.is_zero for qi in Q): + dc = -1 + else: + dc = max(qi.degree(DE.t) for qi in Q) + M = Matrix(dc + 1, m, lambda i, j: Q[j].nth(i), DE.t) + A, u = constant_system(M, zeros(dc + 1, 1, DE.t), DE) + c = eye(m, DE.t) + A = A.row_join(zeros(A.rows, m, DE.t)).col_join(c.row_join(-c)) + return (H, A) + + # else: b is in k, deg(qi) < deg(Dt) + + t = DE.t + if DE.case != 'base': + with DecrementLevel(DE): + t0 = DE.t # k = k0(t0) + ba, bd = frac_in(b, t0, field=True) + Q0 = [frac_in(qi.TC(), t0, field=True) for qi in Q] + f, B = param_rischDE(ba, bd, Q0, DE) + + # f = [f1, ..., fr] in k^r and B is a matrix with + # m + r columns and entries in Const(k) = Const(k0) + # such that Dy0 + b*y0 = Sum(ci*qi, (i, 1, m)) has + # a solution y0 in k with c1, ..., cm in Const(k) + # if and only y0 = Sum(dj*fj, (j, 1, r)) where + # d1, ..., dr ar in Const(k) and + # B*Matrix([c1, ..., cm, d1, ..., dr]) == 0. + + # Transform fractions (fa, fd) in f into constant + # polynomials fa/fd in k[t]. + # (Is there a better way?) + f = [Poly(fa.as_expr()/fd.as_expr(), t, field=True) + for fa, fd in f] + B = Matrix.from_Matrix(B.to_Matrix(), t) + else: + # Base case. Dy == 0 for all y in k and b == 0. + # Dy + b*y = Sum(ci*qi) is solvable if and only if + # Sum(ci*qi) == 0 in which case the solutions are + # y = d1*f1 for f1 = 1 and any d1 in Const(k) = k. + + f = [Poly(1, t, field=True)] # r = 1 + B = Matrix([[qi.TC() for qi in Q] + [S.Zero]], DE.t) + # The condition for solvability is + # B*Matrix([c1, ..., cm, d1]) == 0 + # There are no constraints on d1. + + # Coefficients of t^j (j > 0) in Sum(ci*qi) must be zero. + d = max(qi.degree(DE.t) for qi in Q) + if d > 0: + M = Matrix(d, m, lambda i, j: Q[j].nth(i + 1), DE.t) + A, _ = constant_system(M, zeros(d, 1, DE.t), DE) + else: + # No constraints on the hj. + A = Matrix(0, m, [], DE.t) + + # Solutions of the original equation are + # y = Sum(dj*fj, (j, 1, r) + Sum(ei*hi, (i, 1, m)), + # where ei == ci (i = 1, ..., m), when + # A*Matrix([c1, ..., cm]) == 0 and + # B*Matrix([c1, ..., cm, d1, ..., dr]) == 0 + + # Build combined constraint matrix with m + r + m columns. + + r = len(f) + I = eye(m, DE.t) + A = A.row_join(zeros(A.rows, r + m, DE.t)) + B = B.row_join(zeros(B.rows, m, DE.t)) + C = I.row_join(zeros(m, r, DE.t)).row_join(-I) + + return f + H, A.col_join(B).col_join(C) + + +def prde_cancel_liouvillian(b, Q, n, DE): + """ + Pg, 237. + """ + H = [] + + # Why use DecrementLevel? Below line answers that: + # Assuming that we can solve such problems over 'k' (not k[t]) + if DE.case == 'primitive': + with DecrementLevel(DE): + ba, bd = frac_in(b, DE.t, field=True) + + for i in range(n, -1, -1): + if DE.case == 'exp': # this re-checking can be avoided + with DecrementLevel(DE): + ba, bd = frac_in(b + (i*(derivation(DE.t, DE)/DE.t)).as_poly(b.gens), + DE.t, field=True) + with DecrementLevel(DE): + Qy = [frac_in(q.nth(i), DE.t, field=True) for q in Q] + fi, Ai = param_rischDE(ba, bd, Qy, DE) + fi = [Poly(fa.as_expr()/fd.as_expr(), DE.t, field=True) + for fa, fd in fi] + Ai = Ai.set_gens(DE.t) + + ri = len(fi) + + if i == n: + M = Ai + else: + M = Ai.col_join(M.row_join(zeros(M.rows, ri, DE.t))) + + Fi, hi = [None]*ri, [None]*ri + + # from eq. on top of p.238 (unnumbered) + for j in range(ri): + hji = fi[j] * (DE.t**i).as_poly(fi[j].gens) + hi[j] = hji + # building up Sum(djn*(D(fjn*t^n) - b*fjnt^n)) + Fi[j] = -(derivation(hji, DE) - b*hji) + + H += hi + # in the next loop instead of Q it has + # to be Q + Fi taking its place + Q = Q + Fi + + return (H, M) + + +def param_poly_rischDE(a, b, q, n, DE): + """Polynomial solutions of a parametric Risch differential equation. + + Explanation + =========== + + Given a derivation D in k[t], a, b in k[t] relatively prime, and q + = [q1, ..., qm] in k[t]^m, return h = [h1, ..., hr] in k[t]^r and + a matrix A with m + r columns and entries in Const(k) such that + a*Dp + b*p = Sum(ci*qi, (i, 1, m)) has a solution p of degree <= n + in k[t] with c1, ..., cm in Const(k) if and only if p = Sum(dj*hj, + (j, 1, r)) where d1, ..., dr are in Const(k) and (c1, ..., cm, + d1, ..., dr) is a solution of Ax == 0. + """ + m = len(q) + if n < 0: + # Only the trivial zero solution is possible. + # Find relations between the qi. + if all(qi.is_zero for qi in q): + return [], zeros(1, m, DE.t) # No constraints. + + N = max(qi.degree(DE.t) for qi in q) + M = Matrix(N + 1, m, lambda i, j: q[j].nth(i), DE.t) + A, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE) + + return [], A + + if a.is_ground: + # Normalization: a = 1. + a = a.LC() + b, q = b.to_field().exquo_ground(a), [qi.to_field().exquo_ground(a) for qi in q] + + if not b.is_zero and (DE.case == 'base' or + b.degree() > max(0, DE.d.degree() - 1)): + return prde_no_cancel_b_large(b, q, n, DE) + + elif ((b.is_zero or b.degree() < DE.d.degree() - 1) + and (DE.case == 'base' or DE.d.degree() >= 2)): + return prde_no_cancel_b_small(b, q, n, DE) + + elif (DE.d.degree() >= 2 and + b.degree() == DE.d.degree() - 1 and + n > -b.as_poly().LC()/DE.d.as_poly().LC()): + raise NotImplementedError("prde_no_cancel_b_equal() is " + "not yet implemented.") + + else: + # Liouvillian cases + if DE.case in ('primitive', 'exp'): + return prde_cancel_liouvillian(b, q, n, DE) + else: + raise NotImplementedError("non-linear and hypertangent " + "cases have not yet been implemented") + + # else: deg(a) > 0 + + # Iterate SPDE as long as possible cumulating coefficient + # and terms for the recovery of original solutions. + alpha, beta = a.one, [a.zero]*m + while n >= 0: # and a, b relatively prime + a, b, q, r, n = prde_spde(a, b, q, n, DE) + beta = [betai + alpha*ri for betai, ri in zip(beta, r)] + alpha *= a + # Solutions p of a*Dp + b*p = Sum(ci*qi) correspond to + # solutions alpha*p + Sum(ci*betai) of the initial equation. + d = a.gcd(b) + if not d.is_ground: + break + + # a*Dp + b*p = Sum(ci*qi) may have a polynomial solution + # only if the sum is divisible by d. + + qq, M = poly_linear_constraints(q, d) + # qq = [qq1, ..., qqm] where qqi = qi.quo(d). + # M is a matrix with m columns an entries in k. + # Sum(fi*qi, (i, 1, m)), where f1, ..., fm are elements of k, is + # divisible by d if and only if M*Matrix([f1, ..., fm]) == 0, + # in which case the quotient is Sum(fi*qqi). + + A, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE) + # A is a matrix with m columns and entries in Const(k). + # Sum(ci*qqi) is Sum(ci*qi).quo(d), and the remainder is zero + # for c1, ..., cm in Const(k) if and only if + # A*Matrix([c1, ...,cm]) == 0. + + V = A.nullspace() + # V = [v1, ..., vu] where each vj is a column matrix with + # entries aj1, ..., ajm in Const(k). + # Sum(aji*qi) is divisible by d with exact quotient Sum(aji*qqi). + # Sum(ci*qi) is divisible by d if and only if ci = Sum(dj*aji) + # (i = 1, ..., m) for some d1, ..., du in Const(k). + # In that case, solutions of + # a*Dp + b*p = Sum(ci*qi) = Sum(dj*Sum(aji*qi)) + # are the same as those of + # (a/d)*Dp + (b/d)*p = Sum(dj*rj) + # where rj = Sum(aji*qqi). + + if not V: # No non-trivial solution. + return [], eye(m, DE.t) # Could return A, but this has + # the minimum number of rows. + + Mqq = Matrix([qq]) # A single row. + r = [(Mqq*vj)[0] for vj in V] # [r1, ..., ru] + + # Solutions of (a/d)*Dp + (b/d)*p = Sum(dj*rj) correspond to + # solutions alpha*p + Sum(Sum(dj*aji)*betai) of the initial + # equation. These are equal to alpha*p + Sum(dj*fj) where + # fj = Sum(aji*betai). + Mbeta = Matrix([beta]) + f = [(Mbeta*vj)[0] for vj in V] # [f1, ..., fu] + + # + # Solve the reduced equation recursively. + # + g, B = param_poly_rischDE(a.quo(d), b.quo(d), r, n, DE) + + # g = [g1, ..., gv] in k[t]^v and and B is a matrix with u + v + # columns and entries in Const(k) such that + # (a/d)*Dp + (b/d)*p = Sum(dj*rj) has a solution p of degree <= n + # in k[t] if and only if p = Sum(ek*gk) where e1, ..., ev are in + # Const(k) and B*Matrix([d1, ..., du, e1, ..., ev]) == 0. + # The solutions of the original equation are then + # Sum(dj*fj, (j, 1, u)) + alpha*Sum(ek*gk, (k, 1, v)). + + # Collect solution components. + h = f + [alpha*gk for gk in g] + + # Build combined relation matrix. + A = -eye(m, DE.t) + for vj in V: + A = A.row_join(vj) + A = A.row_join(zeros(m, len(g), DE.t)) + A = A.col_join(zeros(B.rows, m, DE.t).row_join(B)) + + return h, A + + +def param_rischDE(fa, fd, G, DE): + """ + Solve a Parametric Risch Differential Equation: Dy + f*y == Sum(ci*Gi, (i, 1, m)). + + Explanation + =========== + + Given a derivation D in k(t), f in k(t), and G + = [G1, ..., Gm] in k(t)^m, return h = [h1, ..., hr] in k(t)^r and + a matrix A with m + r columns and entries in Const(k) such that + Dy + f*y = Sum(ci*Gi, (i, 1, m)) has a solution y + in k(t) with c1, ..., cm in Const(k) if and only if y = Sum(dj*hj, + (j, 1, r)) where d1, ..., dr are in Const(k) and (c1, ..., cm, + d1, ..., dr) is a solution of Ax == 0. + + Elements of k(t) are tuples (a, d) with a and d in k[t]. + """ + m = len(G) + q, (fa, fd) = weak_normalizer(fa, fd, DE) + # Solutions of the weakly normalized equation Dz + f*z = q*Sum(ci*Gi) + # correspond to solutions y = z/q of the original equation. + gamma = q + G = [(q*ga).cancel(gd, include=True) for ga, gd in G] + + a, (ba, bd), G, hn = prde_normal_denom(fa, fd, G, DE) + # Solutions q in k of a*Dq + b*q = Sum(ci*Gi) correspond + # to solutions z = q/hn of the weakly normalized equation. + gamma *= hn + + A, B, G, hs = prde_special_denom(a, ba, bd, G, DE) + # Solutions p in k[t] of A*Dp + B*p = Sum(ci*Gi) correspond + # to solutions q = p/hs of the previous equation. + gamma *= hs + + g = A.gcd(B) + a, b, g = A.quo(g), B.quo(g), [gia.cancel(gid*g, include=True) for + gia, gid in G] + + # a*Dp + b*p = Sum(ci*gi) may have a polynomial solution + # only if the sum is in k[t]. + + q, M = prde_linear_constraints(a, b, g, DE) + + # q = [q1, ..., qm] where qi in k[t] is the polynomial component + # of the partial fraction expansion of gi. + # M is a matrix with m columns and entries in k. + # Sum(fi*gi, (i, 1, m)), where f1, ..., fm are elements of k, + # is a polynomial if and only if M*Matrix([f1, ..., fm]) == 0, + # in which case the sum is equal to Sum(fi*qi). + + M, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE) + # M is a matrix with m columns and entries in Const(k). + # Sum(ci*gi) is in k[t] for c1, ..., cm in Const(k) + # if and only if M*Matrix([c1, ..., cm]) == 0, + # in which case the sum is Sum(ci*qi). + + ## Reduce number of constants at this point + + V = M.nullspace() + # V = [v1, ..., vu] where each vj is a column matrix with + # entries aj1, ..., ajm in Const(k). + # Sum(aji*gi) is in k[t] and equal to Sum(aji*qi) (j = 1, ..., u). + # Sum(ci*gi) is in k[t] if and only is ci = Sum(dj*aji) + # (i = 1, ..., m) for some d1, ..., du in Const(k). + # In that case, + # Sum(ci*gi) = Sum(ci*qi) = Sum(dj*Sum(aji*qi)) = Sum(dj*rj) + # where rj = Sum(aji*qi) (j = 1, ..., u) in k[t]. + + if not V: # No non-trivial solution + return [], eye(m, DE.t) + + Mq = Matrix([q]) # A single row. + r = [(Mq*vj)[0] for vj in V] # [r1, ..., ru] + + # Solutions of a*Dp + b*p = Sum(dj*rj) correspond to solutions + # y = p/gamma of the initial equation with ci = Sum(dj*aji). + + try: + # We try n=5. At least for prde_spde, it will always + # terminate no matter what n is. + n = bound_degree(a, b, r, DE, parametric=True) + except NotImplementedError: + # A temporary bound is set. Eventually, it will be removed. + # the currently added test case takes large time + # even with n=5, and much longer with large n's. + n = 5 + + h, B = param_poly_rischDE(a, b, r, n, DE) + + # h = [h1, ..., hv] in k[t]^v and and B is a matrix with u + v + # columns and entries in Const(k) such that + # a*Dp + b*p = Sum(dj*rj) has a solution p of degree <= n + # in k[t] if and only if p = Sum(ek*hk) where e1, ..., ev are in + # Const(k) and B*Matrix([d1, ..., du, e1, ..., ev]) == 0. + # The solutions of the original equation for ci = Sum(dj*aji) + # (i = 1, ..., m) are then y = Sum(ek*hk, (k, 1, v))/gamma. + + ## Build combined relation matrix with m + u + v columns. + + A = -eye(m, DE.t) + for vj in V: + A = A.row_join(vj) + A = A.row_join(zeros(m, len(h), DE.t)) + A = A.col_join(zeros(B.rows, m, DE.t).row_join(B)) + + ## Eliminate d1, ..., du. + + W = A.nullspace() + + # W = [w1, ..., wt] where each wl is a column matrix with + # entries blk (k = 1, ..., m + u + v) in Const(k). + # The vectors (bl1, ..., blm) generate the space of those + # constant families (c1, ..., cm) for which a solution of + # the equation Dy + f*y == Sum(ci*Gi) exists. They generate + # the space and form a basis except possibly when Dy + f*y == 0 + # is solvable in k(t}. The corresponding solutions are + # y = Sum(blk'*hk, (k, 1, v))/gamma, where k' = k + m + u. + + v = len(h) + shape = (len(W), m+v) + elements = [wl[:m] + wl[-v:] for wl in W] # excise dj's. + items = [e for row in elements for e in row] + + # Need to set the shape in case W is empty + M = Matrix(*shape, items, DE.t) + N = M.nullspace() + + # N = [n1, ..., ns] where the ni in Const(k)^(m + v) are column + # vectors generating the space of linear relations between + # c1, ..., cm, e1, ..., ev. + + C = Matrix([ni[:] for ni in N], DE.t) # rows n1, ..., ns. + + return [hk.cancel(gamma, include=True) for hk in h], C + + +def limited_integrate_reduce(fa, fd, G, DE): + """ + Simpler version of step 1 & 2 for the limited integration problem. + + Explanation + =========== + + Given a derivation D on k(t) and f, g1, ..., gn in k(t), return + (a, b, h, N, g, V) such that a, b, h in k[t], N is a non-negative integer, + g in k(t), V == [v1, ..., vm] in k(t)^m, and for any solution v in k(t), + c1, ..., cm in C of f == Dv + Sum(ci*wi, (i, 1, m)), p = v*h is in k, and + p and the ci satisfy a*Dp + b*p == g + Sum(ci*vi, (i, 1, m)). Furthermore, + if S1irr == Sirr, then p is in k[t], and if t is nonlinear or Liouvillian + over k, then deg(p) <= N. + + So that the special part is always computed, this function calls the more + general prde_special_denom() automatically if it cannot determine that + S1irr == Sirr. Furthermore, it will automatically call bound_degree() when + t is linear and non-Liouvillian, which for the transcendental case, implies + that Dt == a*t + b with for some a, b in k*. + """ + dn, ds = splitfactor(fd, DE) + E = [splitfactor(gd, DE) for _, gd in G] + En, Es = list(zip(*E)) + c = reduce(lambda i, j: i.lcm(j), (dn,) + En) # lcm(dn, en1, ..., enm) + hn = c.gcd(c.diff(DE.t)) + a = hn + b = -derivation(hn, DE) + N = 0 + + # These are the cases where we know that S1irr = Sirr, but there could be + # others, and this algorithm will need to be extended to handle them. + if DE.case in ('base', 'primitive', 'exp', 'tan'): + hs = reduce(lambda i, j: i.lcm(j), (ds,) + Es) # lcm(ds, es1, ..., esm) + a = hn*hs + b -= (hn*derivation(hs, DE)).quo(hs) + mu = min(order_at_oo(fa, fd, DE.t), min(order_at_oo(ga, gd, DE.t) for + ga, gd in G)) + # So far, all the above are also nonlinear or Liouvillian, but if this + # changes, then this will need to be updated to call bound_degree() + # as per the docstring of this function (DE.case == 'other_linear'). + N = hn.degree(DE.t) + hs.degree(DE.t) + max(0, 1 - DE.d.degree(DE.t) - mu) + else: + # TODO: implement this + raise NotImplementedError + + V = [(-a*hn*ga).cancel(gd, include=True) for ga, gd in G] + return (a, b, a, N, (a*hn*fa).cancel(fd, include=True), V) + + +def limited_integrate(fa, fd, G, DE): + """ + Solves the limited integration problem: f = Dv + Sum(ci*wi, (i, 1, n)) + """ + fa, fd = fa*Poly(1/fd.LC(), DE.t), fd.monic() + # interpreting limited integration problem as a + # parametric Risch DE problem + Fa = Poly(0, DE.t) + Fd = Poly(1, DE.t) + G = [(fa, fd)] + G + h, A = param_rischDE(Fa, Fd, G, DE) + V = A.nullspace() + V = [v for v in V if v[0] != 0] + if not V: + return None + else: + # we can take any vector from V, we take V[0] + c0 = V[0][0] + # v = [-1, c1, ..., cm, d1, ..., dr] + v = V[0]/(-c0) + r = len(h) + m = len(v) - r - 1 + C = list(v[1: m + 1]) + y = -sum(v[m + 1 + i]*h[i][0].as_expr()/h[i][1].as_expr() \ + for i in range(r)) + y_num, y_den = y.as_numer_denom() + Ya, Yd = Poly(y_num, DE.t), Poly(y_den, DE.t) + Y = Ya*Poly(1/Yd.LC(), DE.t), Yd.monic() + return Y, C + + +def parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None): + """ + Parametric logarithmic derivative heuristic. + + Explanation + =========== + + Given a derivation D on k[t], f in k(t), and a hyperexponential monomial + theta over k(t), raises either NotImplementedError, in which case the + heuristic failed, or returns None, in which case it has proven that no + solution exists, or returns a solution (n, m, v) of the equation + n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0. + + If this heuristic fails, the structure theorem approach will need to be + used. + + The argument w == Dtheta/theta + """ + # TODO: finish writing this and write tests + c1 = c1 or Dummy('c1') + + p, a = fa.div(fd) + q, b = wa.div(wd) + + B = max(0, derivation(DE.t, DE).degree(DE.t) - 1) + C = max(p.degree(DE.t), q.degree(DE.t)) + + if q.degree(DE.t) > B: + eqs = [p.nth(i) - c1*q.nth(i) for i in range(B + 1, C + 1)] + s = solve(eqs, c1) + if not s or not s[c1].is_Rational: + # deg(q) > B, no solution for c. + return None + + M, N = s[c1].as_numer_denom() + M_poly = M.as_poly(q.gens) + N_poly = N.as_poly(q.gens) + + nfmwa = N_poly*fa*wd - M_poly*wa*fd + nfmwd = fd*wd + Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE, 'auto') + if Qv is None: + # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. + return None + + Q, v = Qv + + if Q.is_zero or v.is_zero: + return None + + return (Q*N, Q*M, v) + + if p.degree(DE.t) > B: + return None + + c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC()) + l = fd.monic().lcm(wd.monic())*Poly(c, DE.t) + ln, ls = splitfactor(l, DE) + z = ls*ln.gcd(ln.diff(DE.t)) + + if not z.has(DE.t): + # TODO: We treat this as 'no solution', until the structure + # theorem version of parametric_log_deriv is implemented. + return None + + u1, r1 = (fa*l.quo(fd)).div(z) # (l*f).div(z) + u2, r2 = (wa*l.quo(wd)).div(z) # (l*w).div(z) + + eqs = [r1.nth(i) - c1*r2.nth(i) for i in range(z.degree(DE.t))] + s = solve(eqs, c1) + if not s or not s[c1].is_Rational: + # deg(q) <= B, no solution for c. + return None + + M, N = s[c1].as_numer_denom() + + nfmwa = N.as_poly(DE.t)*fa*wd - M.as_poly(DE.t)*wa*fd + nfmwd = fd*wd + Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE) + if Qv is None: + # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. + return None + + Q, v = Qv + + if Q.is_zero or v.is_zero: + return None + + return (Q*N, Q*M, v) + + +def parametric_log_deriv(fa, fd, wa, wd, DE): + # TODO: Write the full algorithm using the structure theorems. +# try: + A = parametric_log_deriv_heu(fa, fd, wa, wd, DE) +# except NotImplementedError: + # Heuristic failed, we have to use the full method. + # TODO: This could be implemented more efficiently. + # It isn't too worrisome, because the heuristic handles most difficult + # cases. + return A + + +def is_deriv_k(fa, fd, DE): + r""" + Checks if Df/f is the derivative of an element of k(t). + + Explanation + =========== + + a in k(t) is the derivative of an element of k(t) if there exists b in k(t) + such that a = Db. Either returns (ans, u), such that Df/f == Du, or None, + which means that Df/f is not the derivative of an element of k(t). ans is + a list of tuples such that Add(*[i*j for i, j in ans]) == u. This is useful + for seeing exactly which elements of k(t) produce u. + + This function uses the structure theorem approach, which says that for any + f in K, Df/f is the derivative of a element of K if and only if there are ri + in QQ such that:: + + --- --- Dt + \ r * Dt + \ r * i Df + / i i / i --- = --. + --- --- t f + i in L i in E i + K/C(x) K/C(x) + + + Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is + transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i + in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic + monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i + is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some + a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of + hyperexponential monomials of K over C(x)). If K is an elementary extension + over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the + transcendence degree of K over C(x). Furthermore, because Const_D(K) == + Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and + deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) + and L_K/C(x) are disjoint. + + The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed + recursively using this same function. Therefore, it is required to pass + them as indices to D (or T). E_args are the arguments of the + hyperexponentials indexed by E_K (i.e., if i is in E_K, then T[i] == + exp(E_args[i])). This is needed to compute the final answer u such that + Df/f == Du. + + log(f) will be the same as u up to a additive constant. This is because + they will both behave the same as monomials. For example, both log(x) and + log(2*x) == log(x) + log(2) satisfy Dt == 1/x, because log(2) is constant. + Therefore, the term const is returned. const is such that + log(const) + f == u. This is calculated by dividing the arguments of one + logarithm from the other. Therefore, it is necessary to pass the arguments + of the logarithmic terms in L_args. + + To handle the case where we are given Df/f, not f, use is_deriv_k_in_field(). + + See also + ======== + is_log_deriv_k_t_radical_in_field, is_log_deriv_k_t_radical + + """ + # Compute Df/f + dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)), fd*fa + dfa, dfd = dfa.cancel(dfd, include=True) + + # Our assumption here is that each monomial is recursively transcendental + if len(DE.exts) != len(DE.D): + if [i for i in DE.cases if i == 'tan'] or \ + ({i for i in DE.cases if i == 'primitive'} - + set(DE.indices('log'))): + raise NotImplementedError("Real version of the structure " + "theorems with hypertangent support is not yet implemented.") + + # TODO: What should really be done in this case? + raise NotImplementedError("Nonelementary extensions not supported " + "in the structure theorems.") + + E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.indices('exp')] + L_part = [DE.D[i].as_expr() for i in DE.indices('log')] + + # The expression dfa/dfd might not be polynomial in any of its symbols so we + # use a Dummy as the generator for PolyMatrix. + dum = Dummy() + lhs = Matrix([E_part + L_part], dum) + rhs = Matrix([dfa.as_expr()/dfd.as_expr()], dum) + + A, u = constant_system(lhs, rhs, DE) + + u = u.to_Matrix() # Poly to Expr + + if not A or not all(derivation(i, DE, basic=True).is_zero for i in u): + # If the elements of u are not all constant + # Note: See comment in constant_system + + # Also note: derivation(basic=True) calls cancel() + return None + else: + if not all(i.is_Rational for i in u): + raise NotImplementedError("Cannot work with non-rational " + "coefficients in this case.") + else: + terms = ([DE.extargs[i] for i in DE.indices('exp')] + + [DE.T[i] for i in DE.indices('log')]) + ans = list(zip(terms, u)) + result = Add(*[Mul(i, j) for i, j in ans]) + argterms = ([DE.T[i] for i in DE.indices('exp')] + + [DE.extargs[i] for i in DE.indices('log')]) + l = [] + ld = [] + for i, j in zip(argterms, u): + # We need to get around things like sqrt(x**2) != x + # and also sqrt(x**2 + 2*x + 1) != x + 1 + # Issue 10798: i need not be a polynomial + i, d = i.as_numer_denom() + icoeff, iterms = sqf_list(i) + l.append(Mul(*([Pow(icoeff, j)] + [Pow(b, e*j) for b, e in iterms]))) + dcoeff, dterms = sqf_list(d) + ld.append(Mul(*([Pow(dcoeff, j)] + [Pow(b, e*j) for b, e in dterms]))) + const = cancel(fa.as_expr()/fd.as_expr()/Mul(*l)*Mul(*ld)) + + return (ans, result, const) + + +def is_log_deriv_k_t_radical(fa, fd, DE, Df=True): + r""" + Checks if Df is the logarithmic derivative of a k(t)-radical. + + Explanation + =========== + + b in k(t) can be written as the logarithmic derivative of a k(t) radical if + there exist n in ZZ and u in k(t) with n, u != 0 such that n*b == Du/u. + Either returns (ans, u, n, const) or None, which means that Df cannot be + written as the logarithmic derivative of a k(t)-radical. ans is a list of + tuples such that Mul(*[i**j for i, j in ans]) == u. This is useful for + seeing exactly what elements of k(t) produce u. + + This function uses the structure theorem approach, which says that for any + f in K, Df is the logarithmic derivative of a K-radical if and only if there + are ri in QQ such that:: + + --- --- Dt + \ r * Dt + \ r * i + / i i / i --- = Df. + --- --- t + i in L i in E i + K/C(x) K/C(x) + + + Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is + transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i + in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic + monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i + is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some + a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of + hyperexponential monomials of K over C(x)). If K is an elementary extension + over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the + transcendence degree of K over C(x). Furthermore, because Const_D(K) == + Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and + deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) + and L_K/C(x) are disjoint. + + The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed + recursively using this same function. Therefore, it is required to pass + them as indices to D (or T). L_args are the arguments of the logarithms + indexed by L_K (i.e., if i is in L_K, then T[i] == log(L_args[i])). This is + needed to compute the final answer u such that n*f == Du/u. + + exp(f) will be the same as u up to a multiplicative constant. This is + because they will both behave the same as monomials. For example, both + exp(x) and exp(x + 1) == E*exp(x) satisfy Dt == t. Therefore, the term const + is returned. const is such that exp(const)*f == u. This is calculated by + subtracting the arguments of one exponential from the other. Therefore, it + is necessary to pass the arguments of the exponential terms in E_args. + + To handle the case where we are given Df, not f, use + is_log_deriv_k_t_radical_in_field(). + + See also + ======== + + is_log_deriv_k_t_radical_in_field, is_deriv_k + + """ + if Df: + dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)).cancel(fd**2, + include=True) + else: + dfa, dfd = fa, fd + + # Our assumption here is that each monomial is recursively transcendental + if len(DE.exts) != len(DE.D): + if [i for i in DE.cases if i == 'tan'] or \ + ({i for i in DE.cases if i == 'primitive'} - + set(DE.indices('log'))): + raise NotImplementedError("Real version of the structure " + "theorems with hypertangent support is not yet implemented.") + + # TODO: What should really be done in this case? + raise NotImplementedError("Nonelementary extensions not supported " + "in the structure theorems.") + + E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.indices('exp')] + L_part = [DE.D[i].as_expr() for i in DE.indices('log')] + + # The expression dfa/dfd might not be polynomial in any of its symbols so we + # use a Dummy as the generator for PolyMatrix. + dum = Dummy() + lhs = Matrix([E_part + L_part], dum) + rhs = Matrix([dfa.as_expr()/dfd.as_expr()], dum) + + A, u = constant_system(lhs, rhs, DE) + + u = u.to_Matrix() # Poly to Expr + + if not A or not all(derivation(i, DE, basic=True).is_zero for i in u): + # If the elements of u are not all constant + # Note: See comment in constant_system + + # Also note: derivation(basic=True) calls cancel() + return None + else: + if not all(i.is_Rational for i in u): + # TODO: But maybe we can tell if they're not rational, like + # log(2)/log(3). Also, there should be an option to continue + # anyway, even if the result might potentially be wrong. + raise NotImplementedError("Cannot work with non-rational " + "coefficients in this case.") + else: + n = S.One*reduce(ilcm, [i.as_numer_denom()[1] for i in u]) + u *= n + terms = ([DE.T[i] for i in DE.indices('exp')] + + [DE.extargs[i] for i in DE.indices('log')]) + ans = list(zip(terms, u)) + result = Mul(*[Pow(i, j) for i, j in ans]) + + # exp(f) will be the same as result up to a multiplicative + # constant. We now find the log of that constant. + argterms = ([DE.extargs[i] for i in DE.indices('exp')] + + [DE.T[i] for i in DE.indices('log')]) + const = cancel(fa.as_expr()/fd.as_expr() - + Add(*[Mul(i, j/n) for i, j in zip(argterms, u)])) + + return (ans, result, n, const) + + +def is_log_deriv_k_t_radical_in_field(fa, fd, DE, case='auto', z=None): + """ + Checks if f can be written as the logarithmic derivative of a k(t)-radical. + + Explanation + =========== + + It differs from is_log_deriv_k_t_radical(fa, fd, DE, Df=False) + for any given fa, fd, DE in that it finds the solution in the + given field not in some (possibly unspecified extension) and + "in_field" with the function name is used to indicate that. + + f in k(t) can be written as the logarithmic derivative of a k(t) radical if + there exist n in ZZ and u in k(t) with n, u != 0 such that n*f == Du/u. + Either returns (n, u) or None, which means that f cannot be written as the + logarithmic derivative of a k(t)-radical. + + case is one of {'primitive', 'exp', 'tan', 'auto'} for the primitive, + hyperexponential, and hypertangent cases, respectively. If case is 'auto', + it will attempt to determine the type of the derivation automatically. + + See also + ======== + is_log_deriv_k_t_radical, is_deriv_k + + """ + fa, fd = fa.cancel(fd, include=True) + + # f must be simple + n, s = splitfactor(fd, DE) + if not s.is_one: + pass + + z = z or Dummy('z') + H, b = residue_reduce(fa, fd, DE, z=z) + if not b: + # I will have to verify, but I believe that the answer should be + # None in this case. This should never happen for the + # functions given when solving the parametric logarithmic + # derivative problem when integration elementary functions (see + # Bronstein's book, page 255), so most likely this indicates a bug. + return None + + roots = [(i, i.real_roots()) for i, _ in H] + if not all(len(j) == i.degree() and all(k.is_Rational for k in j) for + i, j in roots): + # If f is the logarithmic derivative of a k(t)-radical, then all the + # roots of the resultant must be rational numbers. + return None + + # [(a, i), ...], where i*log(a) is a term in the log-part of the integral + # of f + respolys, residues = list(zip(*roots)) or [[], []] + # Note: this might be empty, but everything below should work find in that + # case (it should be the same as if it were [[1, 1]]) + residueterms = [(H[j][1].subs(z, i), i) for j in range(len(H)) for + i in residues[j]] + + # TODO: finish writing this and write tests + + p = cancel(fa.as_expr()/fd.as_expr() - residue_reduce_derivation(H, DE, z)) + + p = p.as_poly(DE.t) + if p is None: + # f - Dg will be in k[t] if f is the logarithmic derivative of a k(t)-radical + return None + + if p.degree(DE.t) >= max(1, DE.d.degree(DE.t)): + return None + + if case == 'auto': + case = DE.case + + if case == 'exp': + wa, wd = derivation(DE.t, DE).cancel(Poly(DE.t, DE.t), include=True) + with DecrementLevel(DE): + pa, pd = frac_in(p, DE.t, cancel=True) + wa, wd = frac_in((wa, wd), DE.t) + A = parametric_log_deriv(pa, pd, wa, wd, DE) + if A is None: + return None + n, e, u = A + u *= DE.t**e + + elif case == 'primitive': + with DecrementLevel(DE): + pa, pd = frac_in(p, DE.t) + A = is_log_deriv_k_t_radical_in_field(pa, pd, DE, case='auto') + if A is None: + return None + n, u = A + + elif case == 'base': + # TODO: we can use more efficient residue reduction from ratint() + if not fd.is_sqf or fa.degree() >= fd.degree(): + # f is the logarithmic derivative in the base case if and only if + # f = fa/fd, fd is square-free, deg(fa) < deg(fd), and + # gcd(fa, fd) == 1. The last condition is handled by cancel() above. + return None + # Note: if residueterms = [], returns (1, 1) + # f had better be 0 in that case. + n = S.One*reduce(ilcm, [i.as_numer_denom()[1] for _, i in residueterms], 1) + u = Mul(*[Pow(i, j*n) for i, j in residueterms]) + return (n, u) + + elif case == 'tan': + raise NotImplementedError("The hypertangent case is " + "not yet implemented for is_log_deriv_k_t_radical_in_field()") + + elif case in ('other_linear', 'other_nonlinear'): + # XXX: If these are supported by the structure theorems, change to NotImplementedError. + raise ValueError("The %s case is not supported in this function." % case) + + else: + raise ValueError("case must be one of {'primitive', 'exp', 'tan', " + "'base', 'auto'}, not %s" % case) + + common_denom = S.One*reduce(ilcm, [i.as_numer_denom()[1] for i in [j for _, j in + residueterms]] + [n], 1) + residueterms = [(i, j*common_denom) for i, j in residueterms] + m = common_denom//n + if common_denom != n*m: # Verify exact division + raise ValueError("Inexact division") + u = cancel(u**m*Mul(*[Pow(i, j) for i, j in residueterms])) + + return (common_denom, u) diff --git a/phivenv/Lib/site-packages/sympy/integrals/quadrature.py b/phivenv/Lib/site-packages/sympy/integrals/quadrature.py new file mode 100644 index 0000000000000000000000000000000000000000..b518bd427dc9980d6a941d2e1ef4d139c5f0f5f9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/quadrature.py @@ -0,0 +1,617 @@ +from sympy.core import S, Dummy, pi +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.trigonometric import sin, cos +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.gamma_functions import gamma +from sympy.polys.orthopolys import (legendre_poly, laguerre_poly, + hermite_poly, jacobi_poly) +from sympy.polys.rootoftools import RootOf + + +def gauss_legendre(n, n_digits): + r""" + Computes the Gauss-Legendre quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Legendre quadrature approximates the integral: + + .. math:: + \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `P_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{2}{\left(1-x_i^2\right) \left(P'_n(x_i)\right)^2} + + Parameters + ========== + + n : + The order of quadrature. + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_legendre + >>> x, w = gauss_legendre(3, 5) + >>> x + [-0.7746, 0, 0.7746] + >>> w + [0.55556, 0.88889, 0.55556] + >>> x, w = gauss_legendre(4, 5) + >>> x + [-0.86114, -0.33998, 0.33998, 0.86114] + >>> w + [0.34785, 0.65215, 0.65215, 0.34785] + + See Also + ======== + + gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gaussian_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/legendre_rule/legendre_rule.html + """ + x = Dummy("x") + p = legendre_poly(n, x, polys=True) + pd = p.diff(x) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((2/((1-r**2) * pd.subs(x, r)**2)).n(n_digits)) + return xi, w + + +def gauss_laguerre(n, n_digits): + r""" + Computes the Gauss-Laguerre quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Laguerre quadrature approximates the integral: + + .. math:: + \int_0^{\infty} e^{-x} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + + The nodes `x_i` of an order `n` quadrature rule are the roots of `L_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{x_i}{(n+1)^2 \left(L_{n+1}(x_i)\right)^2} + + Parameters + ========== + + n : + The order of quadrature. + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : The ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_laguerre + >>> x, w = gauss_laguerre(3, 5) + >>> x + [0.41577, 2.2943, 6.2899] + >>> w + [0.71109, 0.27852, 0.010389] + >>> x, w = gauss_laguerre(6, 5) + >>> x + [0.22285, 1.1889, 2.9927, 5.7751, 9.8375, 15.983] + >>> w + [0.45896, 0.417, 0.11337, 0.010399, 0.00026102, 8.9855e-7] + + See Also + ======== + + gauss_legendre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/laguerre_rule/laguerre_rule.html + """ + x = Dummy("x") + p = laguerre_poly(n, x, polys=True) + p1 = laguerre_poly(n+1, x, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((r/((n+1)**2 * p1.subs(x, r)**2)).n(n_digits)) + return xi, w + + +def gauss_hermite(n, n_digits): + r""" + Computes the Gauss-Hermite quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Hermite quadrature approximates the integral: + + .. math:: + \int_{-\infty}^{\infty} e^{-x^2} f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `H_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{2^{n-1} n! \sqrt{\pi}}{n^2 \left(H_{n-1}(x_i)\right)^2} + + Parameters + ========== + + n : + The order of quadrature. + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : The ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_hermite + >>> x, w = gauss_hermite(3, 5) + >>> x + [-1.2247, 0, 1.2247] + >>> w + [0.29541, 1.1816, 0.29541] + + >>> x, w = gauss_hermite(6, 5) + >>> x + [-2.3506, -1.3358, -0.43608, 0.43608, 1.3358, 2.3506] + >>> w + [0.00453, 0.15707, 0.72463, 0.72463, 0.15707, 0.00453] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_gen_laguerre, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss-Hermite_Quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/hermite_rule/hermite_rule.html + .. [3] https://people.sc.fsu.edu/~jburkardt/cpp_src/gen_hermite_rule/gen_hermite_rule.html + """ + x = Dummy("x") + p = hermite_poly(n, x, polys=True) + p1 = hermite_poly(n-1, x, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append(((2**(n-1) * factorial(n) * sqrt(pi)) / + (n**2 * p1.subs(x, r)**2)).n(n_digits)) + return xi, w + + +def gauss_gen_laguerre(n, alpha, n_digits): + r""" + Computes the generalized Gauss-Laguerre quadrature [1]_ points and weights. + + Explanation + =========== + + The generalized Gauss-Laguerre quadrature approximates the integral: + + .. math:: + \int_{0}^\infty x^{\alpha} e^{-x} f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of + `L^{\alpha}_n` and the weights `w_i` are given by: + + .. math:: + w_i = \frac{\Gamma(\alpha+n)} + {n \Gamma(n) L^{\alpha}_{n-1}(x_i) L^{\alpha+1}_{n-1}(x_i)} + + Parameters + ========== + + n : + The order of quadrature. + + alpha : + The exponent of the singularity, `\alpha > -1`. + + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy import S + >>> from sympy.integrals.quadrature import gauss_gen_laguerre + >>> x, w = gauss_gen_laguerre(3, -S.Half, 5) + >>> x + [0.19016, 1.7845, 5.5253] + >>> w + [1.4493, 0.31413, 0.00906] + + >>> x, w = gauss_gen_laguerre(4, 3*S.Half, 5) + >>> x + [0.97851, 2.9904, 6.3193, 11.712] + >>> w + [0.53087, 0.67721, 0.11895, 0.0023152] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/gen_laguerre_rule/gen_laguerre_rule.html + """ + x = Dummy("x") + p = laguerre_poly(n, x, alpha=alpha, polys=True) + p1 = laguerre_poly(n-1, x, alpha=alpha, polys=True) + p2 = laguerre_poly(n-1, x, alpha=alpha+1, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((gamma(alpha+n) / + (n*gamma(n)*p1.subs(x, r)*p2.subs(x, r))).n(n_digits)) + return xi, w + + +def gauss_chebyshev_t(n, n_digits): + r""" + Computes the Gauss-Chebyshev quadrature [1]_ points and weights of + the first kind. + + Explanation + =========== + + The Gauss-Chebyshev quadrature of the first kind approximates the integral: + + .. math:: + \int_{-1}^{1} \frac{1}{\sqrt{1-x^2}} f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `T_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{\pi}{n} + + Parameters + ========== + + n : + The order of quadrature. + + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_chebyshev_t + >>> x, w = gauss_chebyshev_t(3, 5) + >>> x + [0.86602, 0, -0.86602] + >>> w + [1.0472, 1.0472, 1.0472] + + >>> x, w = gauss_chebyshev_t(6, 5) + >>> x + [0.96593, 0.70711, 0.25882, -0.25882, -0.70711, -0.96593] + >>> w + [0.5236, 0.5236, 0.5236, 0.5236, 0.5236, 0.5236] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chebyshev%E2%80%93Gauss_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/chebyshev1_rule/chebyshev1_rule.html + """ + xi = [] + w = [] + for i in range(1, n+1): + xi.append((cos((2*i-S.One)/(2*n)*S.Pi)).n(n_digits)) + w.append((S.Pi/n).n(n_digits)) + return xi, w + + +def gauss_chebyshev_u(n, n_digits): + r""" + Computes the Gauss-Chebyshev quadrature [1]_ points and weights of + the second kind. + + Explanation + =========== + + The Gauss-Chebyshev quadrature of the second kind approximates the + integral: + + .. math:: + \int_{-1}^{1} \sqrt{1-x^2} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `U_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{\pi}{n+1} \sin^2 \left(\frac{i}{n+1}\pi\right) + + Parameters + ========== + + n : the order of quadrature + + n_digits : number of significant digits of the points and weights to return + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_chebyshev_u + >>> x, w = gauss_chebyshev_u(3, 5) + >>> x + [0.70711, 0, -0.70711] + >>> w + [0.3927, 0.7854, 0.3927] + + >>> x, w = gauss_chebyshev_u(6, 5) + >>> x + [0.90097, 0.62349, 0.22252, -0.22252, -0.62349, -0.90097] + >>> w + [0.084489, 0.27433, 0.42658, 0.42658, 0.27433, 0.084489] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, gauss_chebyshev_t, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chebyshev%E2%80%93Gauss_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/chebyshev2_rule/chebyshev2_rule.html + """ + xi = [] + w = [] + for i in range(1, n+1): + xi.append((cos(i/(n+S.One)*S.Pi)).n(n_digits)) + w.append((S.Pi/(n+S.One)*sin(i*S.Pi/(n+S.One))**2).n(n_digits)) + return xi, w + + +def gauss_jacobi(n, alpha, beta, n_digits): + r""" + Computes the Gauss-Jacobi quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Jacobi quadrature of the first kind approximates the integral: + + .. math:: + \int_{-1}^1 (1-x)^\alpha (1+x)^\beta f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of + `P^{(\alpha,\beta)}_n` and the weights `w_i` are given by: + + .. math:: + w_i = -\frac{2n+\alpha+\beta+2}{n+\alpha+\beta+1} + \frac{\Gamma(n+\alpha+1)\Gamma(n+\beta+1)} + {\Gamma(n+\alpha+\beta+1)(n+1)!} + \frac{2^{\alpha+\beta}}{P'_n(x_i) + P^{(\alpha,\beta)}_{n+1}(x_i)} + + Parameters + ========== + + n : the order of quadrature + + alpha : the first parameter of the Jacobi Polynomial, `\alpha > -1` + + beta : the second parameter of the Jacobi Polynomial, `\beta > -1` + + n_digits : number of significant digits of the points and weights to return + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy import S + >>> from sympy.integrals.quadrature import gauss_jacobi + >>> x, w = gauss_jacobi(3, S.Half, -S.Half, 5) + >>> x + [-0.90097, -0.22252, 0.62349] + >>> w + [1.7063, 1.0973, 0.33795] + + >>> x, w = gauss_jacobi(6, 1, 1, 5) + >>> x + [-0.87174, -0.5917, -0.2093, 0.2093, 0.5917, 0.87174] + >>> w + [0.050584, 0.22169, 0.39439, 0.39439, 0.22169, 0.050584] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, + gauss_chebyshev_t, gauss_chebyshev_u, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Jacobi_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/jacobi_rule/jacobi_rule.html + .. [3] https://people.sc.fsu.edu/~jburkardt/cpp_src/gegenbauer_rule/gegenbauer_rule.html + """ + x = Dummy("x") + p = jacobi_poly(n, alpha, beta, x, polys=True) + pd = p.diff(x) + pn = jacobi_poly(n+1, alpha, beta, x, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append(( + - (2*n+alpha+beta+2) / (n+alpha+beta+S.One) * + (gamma(n+alpha+1)*gamma(n+beta+1)) / + (gamma(n+alpha+beta+S.One)*gamma(n+2)) * + 2**(alpha+beta) / (pd.subs(x, r) * pn.subs(x, r))).n(n_digits)) + return xi, w + + +def gauss_lobatto(n, n_digits): + r""" + Computes the Gauss-Lobatto quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Lobatto quadrature approximates the integral: + + .. math:: + \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `P'_(n-1)` + and the weights `w_i` are given by: + + .. math:: + &w_i = \frac{2}{n(n-1) \left[P_{n-1}(x_i)\right]^2},\quad x\neq\pm 1\\ + &w_i = \frac{2}{n(n-1)},\quad x=\pm 1 + + Parameters + ========== + + n : the order of quadrature + + n_digits : number of significant digits of the points and weights to return + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_lobatto + >>> x, w = gauss_lobatto(3, 5) + >>> x + [-1, 0, 1] + >>> w + [0.33333, 1.3333, 0.33333] + >>> x, w = gauss_lobatto(4, 5) + >>> x + [-1, -0.44721, 0.44721, 1] + >>> w + [0.16667, 0.83333, 0.83333, 0.16667] + + See Also + ======== + + gauss_legendre,gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Lobatto_rules + .. [2] https://web.archive.org/web/20200118141346/http://people.math.sfu.ca/~cbm/aands/page_888.htm + """ + x = Dummy("x") + p = legendre_poly(n-1, x, polys=True) + pd = p.diff(x) + xi = [] + w = [] + for r in pd.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((2/(n*(n-1) * p.subs(x, r)**2)).n(n_digits)) + + xi.insert(0, -1) + xi.append(1) + w.insert(0, (S(2)/(n*(n-1))).n(n_digits)) + w.append((S(2)/(n*(n-1))).n(n_digits)) + return xi, w diff --git a/phivenv/Lib/site-packages/sympy/integrals/rationaltools.py b/phivenv/Lib/site-packages/sympy/integrals/rationaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..e95ff5da2e9d1be6f07d8fe6e9c572f692e92efb --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/rationaltools.py @@ -0,0 +1,445 @@ +"""This module implements tools for integrating rational functions. """ + +from sympy.core.function import Lambda +from sympy.core.numbers import I +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.trigonometric import atan +from sympy.polys.polyerrors import DomainError +from sympy.polys.polyroots import roots +from sympy.polys.polytools import cancel +from sympy.polys.rootoftools import RootSum +from sympy.polys import Poly, resultant, ZZ + + +def ratint(f, x, **flags): + """ + Performs indefinite integration of rational functions. + + Explanation + =========== + + Given a field :math:`K` and a rational function :math:`f = p/q`, + where :math:`p` and :math:`q` are polynomials in :math:`K[x]`, + returns a function :math:`g` such that :math:`f = g'`. + + Examples + ======== + + >>> from sympy.integrals.rationaltools import ratint + >>> from sympy.abc import x + + >>> ratint(36/(x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2), x) + (12*x + 6)/(x**2 - 1) + 4*log(x - 2) - 4*log(x + 1) + + References + ========== + + .. [1] M. Bronstein, Symbolic Integration I: Transcendental + Functions, Second Edition, Springer-Verlag, 2005, pp. 35-70 + + See Also + ======== + + sympy.integrals.integrals.Integral.doit + sympy.integrals.rationaltools.ratint_logpart + sympy.integrals.rationaltools.ratint_ratpart + + """ + if isinstance(f, tuple): + p, q = f + else: + p, q = f.as_numer_denom() + + p, q = Poly(p, x, composite=False, field=True), Poly(q, x, composite=False, field=True) + + coeff, p, q = p.cancel(q) + poly, p = p.div(q) + + result = poly.integrate(x).as_expr() + + if p.is_zero: + return coeff*result + + g, h = ratint_ratpart(p, q, x) + + P, Q = h.as_numer_denom() + + P = Poly(P, x) + Q = Poly(Q, x) + + q, r = P.div(Q) + + result += g + q.integrate(x).as_expr() + + if not r.is_zero: + symbol = flags.get('symbol', 't') + + if not isinstance(symbol, Symbol): + t = Dummy(symbol) + else: + t = symbol.as_dummy() + + L = ratint_logpart(r, Q, x, t) + + real = flags.get('real') + + if real is None: + if isinstance(f, tuple): + p, q = f + atoms = p.atoms() | q.atoms() + else: + atoms = f.atoms() + + for elt in atoms - {x}: + if not elt.is_extended_real: + real = False + break + else: + real = True + + eps = S.Zero + + if not real: + for h, q in L: + _, h = h.primitive() + eps += RootSum( + q, Lambda(t, t*log(h.as_expr())), quadratic=True) + else: + for h, q in L: + _, h = h.primitive() + R = log_to_real(h, q, x, t) + + if R is not None: + eps += R + else: + eps += RootSum( + q, Lambda(t, t*log(h.as_expr())), quadratic=True) + + result += eps + + return coeff*result + + +def ratint_ratpart(f, g, x): + """ + Horowitz-Ostrogradsky algorithm. + + Explanation + =========== + + Given a field K and polynomials f and g in K[x], such that f and g + are coprime and deg(f) < deg(g), returns fractions A and B in K(x), + such that f/g = A' + B and B has square-free denominator. + + Examples + ======== + + >>> from sympy.integrals.rationaltools import ratint_ratpart + >>> from sympy.abc import x, y + >>> from sympy import Poly + >>> ratint_ratpart(Poly(1, x, domain='ZZ'), + ... Poly(x + 1, x, domain='ZZ'), x) + (0, 1/(x + 1)) + >>> ratint_ratpart(Poly(1, x, domain='EX'), + ... Poly(x**2 + y**2, x, domain='EX'), x) + (0, 1/(x**2 + y**2)) + >>> ratint_ratpart(Poly(36, x, domain='ZZ'), + ... Poly(x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2, x, domain='ZZ'), x) + ((12*x + 6)/(x**2 - 1), 12/(x**2 - x - 2)) + + See Also + ======== + + ratint, ratint_logpart + """ + from sympy.solvers.solvers import solve + + f = Poly(f, x) + g = Poly(g, x) + + u, v, _ = g.cofactors(g.diff()) + + n = u.degree() + m = v.degree() + + A_coeffs = [ Dummy('a' + str(n - i)) for i in range(0, n) ] + B_coeffs = [ Dummy('b' + str(m - i)) for i in range(0, m) ] + + C_coeffs = A_coeffs + B_coeffs + + A = Poly(A_coeffs, x, domain=ZZ[C_coeffs]) + B = Poly(B_coeffs, x, domain=ZZ[C_coeffs]) + + H = f - A.diff()*v + A*(u.diff()*v).quo(u) - B*u + + result = solve(H.coeffs(), C_coeffs) + + A = A.as_expr().subs(result) + B = B.as_expr().subs(result) + + rat_part = cancel(A/u.as_expr(), x) + log_part = cancel(B/v.as_expr(), x) + + return rat_part, log_part + + +def ratint_logpart(f, g, x, t=None): + r""" + Lazard-Rioboo-Trager algorithm. + + Explanation + =========== + + Given a field K and polynomials f and g in K[x], such that f and g + are coprime, deg(f) < deg(g) and g is square-free, returns a list + of tuples (s_i, q_i) of polynomials, for i = 1..n, such that s_i + in K[t, x] and q_i in K[t], and:: + + ___ ___ + d f d \ ` \ ` + -- - = -- ) ) a log(s_i(a, x)) + dx g dx /__, /__, + i=1..n a | q_i(a) = 0 + + Examples + ======== + + >>> from sympy.integrals.rationaltools import ratint_logpart + >>> from sympy.abc import x + >>> from sympy import Poly + >>> ratint_logpart(Poly(1, x, domain='ZZ'), + ... Poly(x**2 + x + 1, x, domain='ZZ'), x) + [(Poly(x + 3*_t/2 + 1/2, x, domain='QQ[_t]'), + ...Poly(3*_t**2 + 1, _t, domain='ZZ'))] + >>> ratint_logpart(Poly(12, x, domain='ZZ'), + ... Poly(x**2 - x - 2, x, domain='ZZ'), x) + [(Poly(x - 3*_t/8 - 1/2, x, domain='QQ[_t]'), + ...Poly(-_t**2 + 16, _t, domain='ZZ'))] + + See Also + ======== + + ratint, ratint_ratpart + """ + f, g = Poly(f, x), Poly(g, x) + + t = t or Dummy('t') + a, b = g, f - g.diff()*Poly(t, x) + + res, R = resultant(a, b, includePRS=True) + res = Poly(res, t, composite=False) + + assert res, "BUG: resultant(%s, %s) cannot be zero" % (a, b) + + R_map, H = {}, [] + + for r in R: + R_map[r.degree()] = r + + def _include_sign(c, sqf): + if c.is_extended_real and (c < 0) == True: + h, k = sqf[0] + c_poly = c.as_poly(h.gens) + sqf[0] = h*c_poly, k + + C, res_sqf = res.sqf_list() + _include_sign(C, res_sqf) + + for q, i in res_sqf: + _, q = q.primitive() + + if g.degree() == i: + H.append((g, q)) + else: + h = R_map[i] + h_lc = Poly(h.LC(), t, field=True) + + c, h_lc_sqf = h_lc.sqf_list(all=True) + _include_sign(c, h_lc_sqf) + + for a, j in h_lc_sqf: + h = h.quo(Poly(a.gcd(q)**j, x)) + + inv, coeffs = h_lc.invert(q), [S.One] + + for coeff in h.coeffs()[1:]: + coeff = coeff.as_poly(inv.gens) + T = (inv*coeff).rem(q) + coeffs.append(T.as_expr()) + + h = Poly(dict(list(zip(h.monoms(), coeffs))), x) + + H.append((h, q)) + + return H + + +def log_to_atan(f, g): + """ + Convert complex logarithms to real arctangents. + + Explanation + =========== + + Given a real field K and polynomials f and g in K[x], with g != 0, + returns a sum h of arctangents of polynomials in K[x], such that: + + dh d f + I g + -- = -- I log( ------- ) + dx dx f - I g + + Examples + ======== + + >>> from sympy.integrals.rationaltools import log_to_atan + >>> from sympy.abc import x + >>> from sympy import Poly, sqrt, S + >>> log_to_atan(Poly(x, x, domain='ZZ'), Poly(1, x, domain='ZZ')) + 2*atan(x) + >>> log_to_atan(Poly(x + S(1)/2, x, domain='QQ'), + ... Poly(sqrt(3)/2, x, domain='EX')) + 2*atan(2*sqrt(3)*x/3 + sqrt(3)/3) + + See Also + ======== + + log_to_real + """ + if f.degree() < g.degree(): + f, g = -g, f + + f = f.to_field() + g = g.to_field() + + p, q = f.div(g) + + if q.is_zero: + return 2*atan(p.as_expr()) + else: + s, t, h = g.gcdex(-f) + u = (f*s + g*t).quo(h) + A = 2*atan(u.as_expr()) + + return A + log_to_atan(s, t) + + +def _get_real_roots(f, x): + """get real roots of f if possible""" + rs = roots(f, filter='R') + + try: + num_roots = f.count_roots() + except DomainError: + return rs + else: + if len(rs) == num_roots: + return rs + else: + return None + + +def log_to_real(h, q, x, t): + r""" + Convert complex logarithms to real functions. + + Explanation + =========== + + Given real field K and polynomials h in K[t,x] and q in K[t], + returns real function f such that: + ___ + df d \ ` + -- = -- ) a log(h(a, x)) + dx dx /__, + a | q(a) = 0 + + Examples + ======== + + >>> from sympy.integrals.rationaltools import log_to_real + >>> from sympy.abc import x, y + >>> from sympy import Poly, S + >>> log_to_real(Poly(x + 3*y/2 + S(1)/2, x, domain='QQ[y]'), + ... Poly(3*y**2 + 1, y, domain='ZZ'), x, y) + 2*sqrt(3)*atan(2*sqrt(3)*x/3 + sqrt(3)/3)/3 + >>> log_to_real(Poly(x**2 - 1, x, domain='ZZ'), + ... Poly(-2*y + 1, y, domain='ZZ'), x, y) + log(x**2 - 1)/2 + + See Also + ======== + + log_to_atan + """ + from sympy.simplify.radsimp import collect + u, v = symbols('u,v', cls=Dummy) + + H = h.as_expr().xreplace({t: u + I*v}).expand() + Q = q.as_expr().xreplace({t: u + I*v}).expand() + + H_map = collect(H, I, evaluate=False) + Q_map = collect(Q, I, evaluate=False) + + a, b = H_map.get(S.One, S.Zero), H_map.get(I, S.Zero) + c, d = Q_map.get(S.One, S.Zero), Q_map.get(I, S.Zero) + + R = Poly(resultant(c, d, v), u) + + R_u = _get_real_roots(R, u) + + if R_u is None: + return None + + result = S.Zero + + for r_u in R_u.keys(): + C = Poly(c.xreplace({u: r_u}), v) + if not C: + # t was split into real and imaginary parts + # and denom Q(u, v) = c + I*d. We just found + # that c(r_u) is 0 so the roots are in d + C = Poly(d.xreplace({u: r_u}), v) + # we were going to reject roots from C that + # did not set d to zero, but since we are now + # using C = d and c is already 0, there is + # nothing to check + d = S.Zero + + R_v = _get_real_roots(C, v) + + if R_v is None: + return None + + R_v_paired = [] # take one from each pair of conjugate roots + for r_v in R_v: + if r_v not in R_v_paired and -r_v not in R_v_paired: + if r_v.is_negative or r_v.could_extract_minus_sign(): + R_v_paired.append(-r_v) + elif not r_v.is_zero: + R_v_paired.append(r_v) + + for r_v in R_v_paired: + + D = d.xreplace({u: r_u, v: r_v}) + + if D.evalf(chop=True) != 0: + continue + + A = Poly(a.xreplace({u: r_u, v: r_v}), x) + B = Poly(b.xreplace({u: r_u, v: r_v}), x) + + AB = (A**2 + B**2).as_expr() + + result += r_u*log(AB) + r_v*log_to_atan(A, B) + + R_q = _get_real_roots(q, t) + + if R_q is None: + return None + + for r in R_q.keys(): + result += r*log(h.as_expr().subs(t, r)) + + return result diff --git a/phivenv/Lib/site-packages/sympy/integrals/rde.py b/phivenv/Lib/site-packages/sympy/integrals/rde.py new file mode 100644 index 0000000000000000000000000000000000000000..9fb14a1d14b743b1e0885bf25a0d4b409e8a610d --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/rde.py @@ -0,0 +1,800 @@ +""" +Algorithms for solving the Risch differential equation. + +Given a differential field K of characteristic 0 that is a simple +monomial extension of a base field k and f, g in K, the Risch +Differential Equation problem is to decide if there exist y in K such +that Dy + f*y == g and to find one if there are some. If t is a +monomial over k and the coefficients of f and g are in k(t), then y is +in k(t), and the outline of the algorithm here is given as: + +1. Compute the normal part n of the denominator of y. The problem is +then reduced to finding y' in k, where y == y'/n. +2. Compute the special part s of the denominator of y. The problem is +then reduced to finding y'' in k[t], where y == y''/(n*s) +3. Bound the degree of y''. +4. Reduce the equation Dy + f*y == g to a similar equation with f, g in +k[t]. +5. Find the solutions in k[t] of bounded degree of the reduced equation. + +See Chapter 6 of "Symbolic Integration I: Transcendental Functions" by +Manuel Bronstein. See also the docstring of risch.py. +""" + +from operator import mul +from functools import reduce + +from sympy.core import oo +from sympy.core.symbol import Dummy + +from sympy.polys import Poly, gcd, ZZ, cancel + +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.miscellaneous import sqrt + +from sympy.integrals.risch import (gcdex_diophantine, frac_in, derivation, + splitfactor, NonElementaryIntegralException, DecrementLevel, recognize_log_derivative) + +# TODO: Add messages to NonElementaryIntegralException errors + + +def order_at(a, p, t): + """ + Computes the order of a at p, with respect to t. + + Explanation + =========== + + For a, p in k[t], the order of a at p is defined as nu_p(a) = max({n + in Z+ such that p**n|a}), where a != 0. If a == 0, nu_p(a) = +oo. + + To compute the order at a rational function, a/b, use the fact that + nu_p(a/b) == nu_p(a) - nu_p(b). + """ + if a.is_zero: + return oo + if p == Poly(t, t): + return a.as_poly(t).ET()[0][0] + + # Uses binary search for calculating the power. power_list collects the tuples + # (p^k,k) where each k is some power of 2. After deciding the largest k + # such that k is power of 2 and p^k|a the loop iteratively calculates + # the actual power. + power_list = [] + p1 = p + r = a.rem(p1) + tracks_power = 1 + while r.is_zero: + power_list.append((p1,tracks_power)) + p1 = p1*p1 + tracks_power *= 2 + r = a.rem(p1) + n = 0 + product = Poly(1, t) + while len(power_list) != 0: + final = power_list.pop() + productf = product*final[0] + r = a.rem(productf) + if r.is_zero: + n += final[1] + product = productf + return n + + +def order_at_oo(a, d, t): + """ + Computes the order of a/d at oo (infinity), with respect to t. + + For f in k(t), the order or f at oo is defined as deg(d) - deg(a), where + f == a/d. + """ + if a.is_zero: + return oo + return d.degree(t) - a.degree(t) + + +def weak_normalizer(a, d, DE, z=None): + """ + Weak normalization. + + Explanation + =========== + + Given a derivation D on k[t] and f == a/d in k(t), return q in k[t] + such that f - Dq/q is weakly normalized with respect to t. + + f in k(t) is said to be "weakly normalized" with respect to t if + residue_p(f) is not a positive integer for any normal irreducible p + in k[t] such that f is in R_p (Definition 6.1.1). If f has an + elementary integral, this is equivalent to no logarithm of + integral(f) whose argument depends on t has a positive integer + coefficient, where the arguments of the logarithms not in k(t) are + in k[t]. + + Returns (q, f - Dq/q) + """ + z = z or Dummy('z') + dn, ds = splitfactor(d, DE) + + # Compute d1, where dn == d1*d2**2*...*dn**n is a square-free + # factorization of d. + g = gcd(dn, dn.diff(DE.t)) + d_sqf_part = dn.quo(g) + d1 = d_sqf_part.quo(gcd(d_sqf_part, g)) + + a1, b = gcdex_diophantine(d.quo(d1).as_poly(DE.t), d1.as_poly(DE.t), + a.as_poly(DE.t)) + r = (a - Poly(z, DE.t)*derivation(d1, DE)).as_poly(DE.t).resultant( + d1.as_poly(DE.t)) + r = Poly(r, z) + + if not r.expr.has(z): + return (Poly(1, DE.t), (a, d)) + + N = [i for i in r.real_roots() if i in ZZ and i > 0] + + q = reduce(mul, [gcd(a - Poly(n, DE.t)*derivation(d1, DE), d1) for n in N], + Poly(1, DE.t)) + + dq = derivation(q, DE) + sn = q*a - d*dq + sd = q*d + sn, sd = sn.cancel(sd, include=True) + + return (q, (sn, sd)) + + +def normal_denom(fa, fd, ga, gd, DE): + """ + Normal part of the denominator. + + Explanation + =========== + + Given a derivation D on k[t] and f, g in k(t) with f weakly + normalized with respect to t, either raise NonElementaryIntegralException, + in which case the equation Dy + f*y == g has no solution in k(t), or the + quadruplet (a, b, c, h) such that a, h in k[t], b, c in k, and for any + solution y in k(t) of Dy + f*y == g, q = y*h in k satisfies + a*Dq + b*q == c. + + This constitutes step 1 in the outline given in the rde.py docstring. + """ + dn, ds = splitfactor(fd, DE) + en, es = splitfactor(gd, DE) + + p = dn.gcd(en) + h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t))) + + a = dn*h + c = a*h + if c.div(en)[1]: + # en does not divide dn*h**2 + raise NonElementaryIntegralException + ca = c*ga + ca, cd = ca.cancel(gd, include=True) + + ba = a*fa - dn*derivation(h, DE)*fd + ba, bd = ba.cancel(fd, include=True) + + # (dn*h, dn*h*f - dn*Dh, dn*h**2*g, h) + return (a, (ba, bd), (ca, cd), h) + + +def special_denom(a, ba, bd, ca, cd, DE, case='auto'): + """ + Special part of the denominator. + + Explanation + =========== + + case is one of {'exp', 'tan', 'primitive'} for the hyperexponential, + hypertangent, and primitive cases, respectively. For the + hyperexponential (resp. hypertangent) case, given a derivation D on + k[t] and a in k[t], b, c, in k with Dt/t in k (resp. Dt/(t**2 + 1) in + k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp. + gcd(a, t**2 + 1) == 1), return the quadruplet (A, B, C, 1/h) such that + A, B, C, h in k[t] and for any solution q in k of a*Dq + b*q == c, + r = qh in k[t] satisfies A*Dr + B*r == C. + + For ``case == 'primitive'``, k == k[t], so it returns (a, b, c, 1) in + this case. + + This constitutes step 2 of the outline given in the rde.py docstring. + """ + # TODO: finish writing this and write tests + + if case == 'auto': + case = DE.case + + if case == 'exp': + p = Poly(DE.t, DE.t) + elif case == 'tan': + p = Poly(DE.t**2 + 1, DE.t) + elif case in ('primitive', 'base'): + B = ba.to_field().quo(bd) + C = ca.to_field().quo(cd) + return (a, B, C, Poly(1, DE.t)) + else: + raise ValueError("case must be one of {'exp', 'tan', 'primitive', " + "'base'}, not %s." % case) + + nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t) + nc = order_at(ca, p, DE.t) - order_at(cd, p, DE.t) + + n = min(0, nc - min(0, nb)) + if not nb: + # Possible cancellation. + from .prde import parametric_log_deriv + if case == 'exp': + dcoeff = DE.d.quo(Poly(DE.t, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + alphaa, alphad = frac_in(-ba.eval(0)/bd.eval(0)/a.eval(0), DE.t) + etaa, etad = frac_in(dcoeff, DE.t) + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + if A is not None: + Q, m, z = A + if Q == 1: + n = min(n, m) + + elif case == 'tan': + dcoeff = DE.d.quo(Poly(DE.t**2+1, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + alphaa, alphad = frac_in(im(-ba.eval(sqrt(-1))/bd.eval(sqrt(-1))/a.eval(sqrt(-1))), DE.t) + betaa, betad = frac_in(re(-ba.eval(sqrt(-1))/bd.eval(sqrt(-1))/a.eval(sqrt(-1))), DE.t) + etaa, etad = frac_in(dcoeff, DE.t) + + if recognize_log_derivative(Poly(2, DE.t)*betaa, betad, DE): + A = parametric_log_deriv(alphaa*Poly(sqrt(-1), DE.t)*betad+alphad*betaa, alphad*betad, etaa, etad, DE) + if A is not None: + Q, m, z = A + if Q == 1: + n = min(n, m) + N = max(0, -nb, n - nc) + pN = p**N + pn = p**-n + + A = a*pN + B = ba*pN.quo(bd) + Poly(n, DE.t)*a*derivation(p, DE).quo(p)*pN + C = (ca*pN*pn).quo(cd) + h = pn + + # (a*p**N, (b + n*a*Dp/p)*p**N, c*p**(N - n), p**-n) + return (A, B, C, h) + + +def bound_degree(a, b, cQ, DE, case='auto', parametric=False): + """ + Bound on polynomial solutions. + + Explanation + =========== + + Given a derivation D on k[t] and ``a``, ``b``, ``c`` in k[t] with ``a != 0``, return + n in ZZ such that deg(q) <= n for any solution q in k[t] of + a*Dq + b*q == c, when parametric=False, or deg(q) <= n for any solution + c1, ..., cm in Const(k) and q in k[t] of a*Dq + b*q == Sum(ci*gi, (i, 1, m)) + when parametric=True. + + For ``parametric=False``, ``cQ`` is ``c``, a ``Poly``; for ``parametric=True``, ``cQ`` is Q == + [q1, ..., qm], a list of Polys. + + This constitutes step 3 of the outline given in the rde.py docstring. + """ + # TODO: finish writing this and write tests + + if case == 'auto': + case = DE.case + + da = a.degree(DE.t) + db = b.degree(DE.t) + + # The parametric and regular cases are identical, except for this part + if parametric: + dc = max(i.degree(DE.t) for i in cQ) + else: + dc = cQ.degree(DE.t) + + alpha = cancel(-b.as_poly(DE.t).LC().as_expr()/ + a.as_poly(DE.t).LC().as_expr()) + + if case == 'base': + n = max(0, dc - max(db, da - 1)) + if db == da - 1 and alpha.is_Integer: + n = max(0, alpha, dc - db) + + elif case == 'primitive': + if db > da: + n = max(0, dc - db) + else: + n = max(0, dc - da + 1) + + etaa, etad = frac_in(DE.d, DE.T[DE.level - 1]) + + t1 = DE.t + with DecrementLevel(DE): + alphaa, alphad = frac_in(alpha, DE.t) + if db == da - 1: + from .prde import limited_integrate + # if alpha == m*Dt + Dz for z in k and m in ZZ: + try: + (za, zd), m = limited_integrate(alphaa, alphad, [(etaa, etad)], + DE) + except NonElementaryIntegralException: + pass + else: + if len(m) != 1: + raise ValueError("Length of m should be 1") + n = max(n, m[0]) + + elif db == da: + # if alpha == Dz/z for z in k*: + # beta = -lc(a*Dz + b*z)/(z*lc(a)) + # if beta == m*Dt + Dw for w in k and m in ZZ: + # n = max(n, m) + from .prde import is_log_deriv_k_t_radical_in_field + A = is_log_deriv_k_t_radical_in_field(alphaa, alphad, DE) + if A is not None: + aa, z = A + if aa == 1: + beta = -(a*derivation(z, DE).as_poly(t1) + + b*z.as_poly(t1)).LC()/(z.as_expr()*a.LC()) + betaa, betad = frac_in(beta, DE.t) + from .prde import limited_integrate + try: + (za, zd), m = limited_integrate(betaa, betad, + [(etaa, etad)], DE) + except NonElementaryIntegralException: + pass + else: + if len(m) != 1: + raise ValueError("Length of m should be 1") + n = max(n, m[0].as_expr()) + + elif case == 'exp': + from .prde import parametric_log_deriv + + n = max(0, dc - max(db, da)) + if da == db: + etaa, etad = frac_in(DE.d.quo(Poly(DE.t, DE.t)), DE.T[DE.level - 1]) + with DecrementLevel(DE): + alphaa, alphad = frac_in(alpha, DE.t) + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + if A is not None: + # if alpha == m*Dt/t + Dz/z for z in k* and m in ZZ: + # n = max(n, m) + a, m, z = A + if a == 1: + n = max(n, m) + + elif case in ('tan', 'other_nonlinear'): + delta = DE.d.degree(DE.t) + lam = DE.d.LC() + alpha = cancel(alpha/lam) + n = max(0, dc - max(da + delta - 1, db)) + if db == da + delta - 1 and alpha.is_Integer: + n = max(0, alpha, dc - db) + + else: + raise ValueError("case must be one of {'exp', 'tan', 'primitive', " + "'other_nonlinear', 'base'}, not %s." % case) + + return n + + +def spde(a, b, c, n, DE): + """ + Rothstein's Special Polynomial Differential Equation algorithm. + + Explanation + =========== + + Given a derivation D on k[t], an integer n and ``a``,``b``,``c`` in k[t] with + ``a != 0``, either raise NonElementaryIntegralException, in which case the + equation a*Dq + b*q == c has no solution of degree at most ``n`` in + k[t], or return the tuple (B, C, m, alpha, beta) such that B, C, + alpha, beta in k[t], m in ZZ, and any solution q in k[t] of degree + at most n of a*Dq + b*q == c must be of the form + q == alpha*h + beta, where h in k[t], deg(h) <= m, and Dh + B*h == C. + + This constitutes step 4 of the outline given in the rde.py docstring. + """ + zero = Poly(0, DE.t) + + alpha = Poly(1, DE.t) + beta = Poly(0, DE.t) + + while True: + if c.is_zero: + return (zero, zero, 0, zero, beta) # -1 is more to the point + if (n < 0) is True: + raise NonElementaryIntegralException + + g = a.gcd(b) + if not c.rem(g).is_zero: # g does not divide c + raise NonElementaryIntegralException + + a, b, c = a.quo(g), b.quo(g), c.quo(g) + + if a.degree(DE.t) == 0: + b = b.to_field().quo(a) + c = c.to_field().quo(a) + return (b, c, n, alpha, beta) + + r, z = gcdex_diophantine(b, a, c) + b += derivation(a, DE) + c = z - derivation(r, DE) + n -= a.degree(DE.t) + + beta += alpha * r + alpha *= a + +def no_cancel_b_large(b, c, n, DE): + """ + Poly Risch Differential Equation - No cancellation: deg(b) large enough. + + Explanation + =========== + + Given a derivation D on k[t], ``n`` either an integer or +oo, and ``b``,``c`` + in k[t] with ``b != 0`` and either D == d/dt or + deg(b) > max(0, deg(D) - 1), either raise NonElementaryIntegralException, in + which case the equation ``Dq + b*q == c`` has no solution of degree at + most n in k[t], or a solution q in k[t] of this equation with + ``deg(q) < n``. + """ + q = Poly(0, DE.t) + + while not c.is_zero: + m = c.degree(DE.t) - b.degree(DE.t) + if not 0 <= m <= n: # n < 0 or m < 0 or m > n + raise NonElementaryIntegralException + + p = Poly(c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC()*DE.t**m, DE.t, + expand=False) + q = q + p + n = m - 1 + c = c - derivation(p, DE) - b*p + + return q + + +def no_cancel_b_small(b, c, n, DE): + """ + Poly Risch Differential Equation - No cancellation: deg(b) small enough. + + Explanation + =========== + + Given a derivation D on k[t], ``n`` either an integer or +oo, and ``b``,``c`` + in k[t] with deg(b) < deg(D) - 1 and either D == d/dt or + deg(D) >= 2, either raise NonElementaryIntegralException, in which case the + equation Dq + b*q == c has no solution of degree at most n in k[t], + or a solution q in k[t] of this equation with deg(q) <= n, or the + tuple (h, b0, c0) such that h in k[t], b0, c0, in k, and for any + solution q in k[t] of degree at most n of Dq + bq == c, y == q - h + is a solution in k of Dy + b0*y == c0. + """ + q = Poly(0, DE.t) + + while not c.is_zero: + if n == 0: + m = 0 + else: + m = c.degree(DE.t) - DE.d.degree(DE.t) + 1 + + if not 0 <= m <= n: # n < 0 or m < 0 or m > n + raise NonElementaryIntegralException + + if m > 0: + p = Poly(c.as_poly(DE.t).LC()/(m*DE.d.as_poly(DE.t).LC())*DE.t**m, + DE.t, expand=False) + else: + if b.degree(DE.t) != c.degree(DE.t): + raise NonElementaryIntegralException + if b.degree(DE.t) == 0: + return (q, b.as_poly(DE.T[DE.level - 1]), + c.as_poly(DE.T[DE.level - 1])) + p = Poly(c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC(), DE.t, + expand=False) + + q = q + p + n = m - 1 + c = c - derivation(p, DE) - b*p + + return q + + +# TODO: better name for this function +def no_cancel_equal(b, c, n, DE): + """ + Poly Risch Differential Equation - No cancellation: deg(b) == deg(D) - 1 + + Explanation + =========== + + Given a derivation D on k[t] with deg(D) >= 2, n either an integer + or +oo, and b, c in k[t] with deg(b) == deg(D) - 1, either raise + NonElementaryIntegralException, in which case the equation Dq + b*q == c has + no solution of degree at most n in k[t], or a solution q in k[t] of + this equation with deg(q) <= n, or the tuple (h, m, C) such that h + in k[t], m in ZZ, and C in k[t], and for any solution q in k[t] of + degree at most n of Dq + b*q == c, y == q - h is a solution in k[t] + of degree at most m of Dy + b*y == C. + """ + q = Poly(0, DE.t) + lc = cancel(-b.as_poly(DE.t).LC()/DE.d.as_poly(DE.t).LC()) + if lc.is_Integer and lc.is_positive: + M = lc + else: + M = -1 + + while not c.is_zero: + m = max(M, c.degree(DE.t) - DE.d.degree(DE.t) + 1) + + if not 0 <= m <= n: # n < 0 or m < 0 or m > n + raise NonElementaryIntegralException + + u = cancel(m*DE.d.as_poly(DE.t).LC() + b.as_poly(DE.t).LC()) + if u.is_zero: + return (q, m, c) + if m > 0: + p = Poly(c.as_poly(DE.t).LC()/u*DE.t**m, DE.t, expand=False) + else: + if c.degree(DE.t) != DE.d.degree(DE.t) - 1: + raise NonElementaryIntegralException + else: + p = c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC() + + q = q + p + n = m - 1 + c = c - derivation(p, DE) - b*p + + return q + + +def cancel_primitive(b, c, n, DE): + """ + Poly Risch Differential Equation - Cancellation: Primitive case. + + Explanation + =========== + + Given a derivation D on k[t], n either an integer or +oo, ``b`` in k, and + ``c`` in k[t] with Dt in k and ``b != 0``, either raise + NonElementaryIntegralException, in which case the equation Dq + b*q == c + has no solution of degree at most n in k[t], or a solution q in k[t] of + this equation with deg(q) <= n. + """ + # Delayed imports + from .prde import is_log_deriv_k_t_radical_in_field + with DecrementLevel(DE): + ba, bd = frac_in(b, DE.t) + A = is_log_deriv_k_t_radical_in_field(ba, bd, DE) + if A is not None: + n, z = A + if n == 1: # b == Dz/z + raise NotImplementedError("is_deriv_in_field() is required to " + " solve this problem.") + # if z*c == Dp for p in k[t] and deg(p) <= n: + # return p/z + # else: + # raise NonElementaryIntegralException + + if c.is_zero: + return c # return 0 + + if n < c.degree(DE.t): + raise NonElementaryIntegralException + + q = Poly(0, DE.t) + while not c.is_zero: + m = c.degree(DE.t) + if n < m: + raise NonElementaryIntegralException + with DecrementLevel(DE): + a2a, a2d = frac_in(c.LC(), DE.t) + sa, sd = rischDE(ba, bd, a2a, a2d, DE) + stm = Poly(sa.as_expr()/sd.as_expr()*DE.t**m, DE.t, expand=False) + q += stm + n = m - 1 + c -= b*stm + derivation(stm, DE) + + return q + + +def cancel_exp(b, c, n, DE): + """ + Poly Risch Differential Equation - Cancellation: Hyperexponential case. + + Explanation + =========== + + Given a derivation D on k[t], n either an integer or +oo, ``b`` in k, and + ``c`` in k[t] with Dt/t in k and ``b != 0``, either raise + NonElementaryIntegralException, in which case the equation Dq + b*q == c + has no solution of degree at most n in k[t], or a solution q in k[t] of + this equation with deg(q) <= n. + """ + from .prde import parametric_log_deriv + eta = DE.d.quo(Poly(DE.t, DE.t)).as_expr() + + with DecrementLevel(DE): + etaa, etad = frac_in(eta, DE.t) + ba, bd = frac_in(b, DE.t) + A = parametric_log_deriv(ba, bd, etaa, etad, DE) + if A is not None: + a, m, z = A + if a == 1: + raise NotImplementedError("is_deriv_in_field() is required to " + "solve this problem.") + # if c*z*t**m == Dp for p in k and q = p/(z*t**m) in k[t] and + # deg(q) <= n: + # return q + # else: + # raise NonElementaryIntegralException + + if c.is_zero: + return c # return 0 + + if n < c.degree(DE.t): + raise NonElementaryIntegralException + + q = Poly(0, DE.t) + while not c.is_zero: + m = c.degree(DE.t) + if n < m: + raise NonElementaryIntegralException + # a1 = b + m*Dt/t + a1 = b.as_expr() + with DecrementLevel(DE): + # TODO: Write a dummy function that does this idiom + a1a, a1d = frac_in(a1, DE.t) + a1a = a1a*etad + etaa*a1d*Poly(m, DE.t) + a1d = a1d*etad + + a2a, a2d = frac_in(c.LC(), DE.t) + + sa, sd = rischDE(a1a, a1d, a2a, a2d, DE) + stm = Poly(sa.as_expr()/sd.as_expr()*DE.t**m, DE.t, expand=False) + q += stm + n = m - 1 + c -= b*stm + derivation(stm, DE) # deg(c) becomes smaller + return q + + +def solve_poly_rde(b, cQ, n, DE, parametric=False): + """ + Solve a Polynomial Risch Differential Equation with degree bound ``n``. + + This constitutes step 4 of the outline given in the rde.py docstring. + + For parametric=False, cQ is c, a Poly; for parametric=True, cQ is Q == + [q1, ..., qm], a list of Polys. + """ + # No cancellation + if not b.is_zero and (DE.case == 'base' or + b.degree(DE.t) > max(0, DE.d.degree(DE.t) - 1)): + + if parametric: + # Delayed imports + from .prde import prde_no_cancel_b_large + return prde_no_cancel_b_large(b, cQ, n, DE) + return no_cancel_b_large(b, cQ, n, DE) + + elif (b.is_zero or b.degree(DE.t) < DE.d.degree(DE.t) - 1) and \ + (DE.case == 'base' or DE.d.degree(DE.t) >= 2): + + if parametric: + from .prde import prde_no_cancel_b_small + return prde_no_cancel_b_small(b, cQ, n, DE) + + R = no_cancel_b_small(b, cQ, n, DE) + + if isinstance(R, Poly): + return R + else: + # XXX: Might k be a field? (pg. 209) + h, b0, c0 = R + with DecrementLevel(DE): + b0, c0 = b0.as_poly(DE.t), c0.as_poly(DE.t) + if b0 is None: # See above comment + raise ValueError("b0 should be a non-Null value") + if c0 is None: + raise ValueError("c0 should be a non-Null value") + y = solve_poly_rde(b0, c0, n, DE).as_poly(DE.t) + return h + y + + elif DE.d.degree(DE.t) >= 2 and b.degree(DE.t) == DE.d.degree(DE.t) - 1 and \ + n > -b.as_poly(DE.t).LC()/DE.d.as_poly(DE.t).LC(): + + # TODO: Is this check necessary, and if so, what should it do if it fails? + # b comes from the first element returned from spde() + if not b.as_poly(DE.t).LC().is_number: + raise TypeError("Result should be a number") + + if parametric: + raise NotImplementedError("prde_no_cancel_b_equal() is not yet " + "implemented.") + + R = no_cancel_equal(b, cQ, n, DE) + + if isinstance(R, Poly): + return R + else: + h, m, C = R + # XXX: Or should it be rischDE()? + y = solve_poly_rde(b, C, m, DE) + return h + y + + else: + # Cancellation + if b.is_zero: + raise NotImplementedError("Remaining cases for Poly (P)RDE are " + "not yet implemented (is_deriv_in_field() required).") + else: + if DE.case == 'exp': + if parametric: + raise NotImplementedError("Parametric RDE cancellation " + "hyperexponential case is not yet implemented.") + return cancel_exp(b, cQ, n, DE) + + elif DE.case == 'primitive': + if parametric: + raise NotImplementedError("Parametric RDE cancellation " + "primitive case is not yet implemented.") + return cancel_primitive(b, cQ, n, DE) + + else: + raise NotImplementedError("Other Poly (P)RDE cancellation " + "cases are not yet implemented (%s)." % DE.case) + + if parametric: + raise NotImplementedError("Remaining cases for Poly PRDE not yet " + "implemented.") + raise NotImplementedError("Remaining cases for Poly RDE not yet " + "implemented.") + + +def rischDE(fa, fd, ga, gd, DE): + """ + Solve a Risch Differential Equation: Dy + f*y == g. + + Explanation + =========== + + See the outline in the docstring of rde.py for more information + about the procedure used. Either raise NonElementaryIntegralException, in + which case there is no solution y in the given differential field, + or return y in k(t) satisfying Dy + f*y == g, or raise + NotImplementedError, in which case, the algorithms necessary to + solve the given Risch Differential Equation have not yet been + implemented. + """ + _, (fa, fd) = weak_normalizer(fa, fd, DE) + a, (ba, bd), (ca, cd), hn = normal_denom(fa, fd, ga, gd, DE) + A, B, C, hs = special_denom(a, ba, bd, ca, cd, DE) + try: + # Until this is fully implemented, use oo. Note that this will almost + # certainly cause non-termination in spde() (unless A == 1), and + # *might* lead to non-termination in the next step for a nonelementary + # integral (I don't know for certain yet). Fortunately, spde() is + # currently written recursively, so this will just give + # RuntimeError: maximum recursion depth exceeded. + n = bound_degree(A, B, C, DE) + except NotImplementedError: + # Useful for debugging: + # import warnings + # warnings.warn("rischDE: Proceeding with n = oo; may cause " + # "non-termination.") + n = oo + + B, C, m, alpha, beta = spde(A, B, C, n, DE) + if C.is_zero: + y = C + else: + y = solve_poly_rde(B, C, m, DE) + + return (alpha*y + beta, hn*hs) diff --git a/phivenv/Lib/site-packages/sympy/integrals/risch.py b/phivenv/Lib/site-packages/sympy/integrals/risch.py new file mode 100644 index 0000000000000000000000000000000000000000..89e5f10bbb1d011d98a5884ce74ab25b615e1c51 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/risch.py @@ -0,0 +1,1851 @@ +""" +The Risch Algorithm for transcendental function integration. + +The core algorithms for the Risch algorithm are here. The subproblem +algorithms are in the rde.py and prde.py files for the Risch +Differential Equation solver and the parametric problems solvers, +respectively. All important information concerning the differential extension +for an integrand is stored in a DifferentialExtension object, which in the code +is usually called DE. Throughout the code and Inside the DifferentialExtension +object, the conventions/attribute names are that the base domain is QQ and each +differential extension is x, t0, t1, ..., tn-1 = DE.t. DE.x is the variable of +integration (Dx == 1), DE.D is a list of the derivatives of +x, t1, t2, ..., tn-1 = t, DE.T is the list [x, t1, t2, ..., tn-1], DE.t is the +outer-most variable of the differential extension at the given level (the level +can be adjusted using DE.increment_level() and DE.decrement_level()), +k is the field C(x, t0, ..., tn-2), where C is the constant field. The +numerator of a fraction is denoted by a and the denominator by +d. If the fraction is named f, fa == numer(f) and fd == denom(f). +Fractions are returned as tuples (fa, fd). DE.d and DE.t are used to +represent the topmost derivation and extension variable, respectively. +The docstring of a function signifies whether an argument is in k[t], in +which case it will just return a Poly in t, or in k(t), in which case it +will return the fraction (fa, fd). Other variable names probably come +from the names used in Bronstein's book. +""" +from types import GeneratorType +from functools import reduce + +from sympy.core.function import Lambda +from sympy.core.mul import Mul +from sympy.core.intfunc import ilcm +from sympy.core.numbers import I +from sympy.core.power import Pow +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.sorting import ordered, default_sort_key +from sympy.core.symbol import Dummy, Symbol +from sympy.functions.elementary.exponential import log, exp +from sympy.functions.elementary.hyperbolic import (cosh, coth, sinh, + tanh) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (atan, sin, cos, + tan, acot, cot, asin, acos) +from .integrals import integrate, Integral +from .heurisch import _symbols +from sympy.polys.polyerrors import PolynomialError +from sympy.polys.polytools import (real_roots, cancel, Poly, gcd, + reduced) +from sympy.polys.rootoftools import RootSum +from sympy.utilities.iterables import numbered_symbols + + +def integer_powers(exprs): + """ + Rewrites a list of expressions as integer multiples of each other. + + Explanation + =========== + + For example, if you have [x, x/2, x**2 + 1, 2*x/3], then you can rewrite + this as [(x/6) * 6, (x/6) * 3, (x**2 + 1) * 1, (x/6) * 4]. This is useful + in the Risch integration algorithm, where we must write exp(x) + exp(x/2) + as (exp(x/2))**2 + exp(x/2), but not as exp(x) + sqrt(exp(x)) (this is + because only the transcendental case is implemented and we therefore cannot + integrate algebraic extensions). The integer multiples returned by this + function for each term are the smallest possible (their content equals 1). + + Returns a list of tuples where the first element is the base term and the + second element is a list of `(item, factor)` terms, where `factor` is the + integer multiplicative factor that must multiply the base term to obtain + the original item. + + The easiest way to understand this is to look at an example: + + >>> from sympy.abc import x + >>> from sympy.integrals.risch import integer_powers + >>> integer_powers([x, x/2, x**2 + 1, 2*x/3]) + [(x/6, [(x, 6), (x/2, 3), (2*x/3, 4)]), (x**2 + 1, [(x**2 + 1, 1)])] + + We can see how this relates to the example at the beginning of the + docstring. It chose x/6 as the first base term. Then, x can be written as + (x/2) * 2, so we get (0, 2), and so on. Now only element (x**2 + 1) + remains, and there are no other terms that can be written as a rational + multiple of that, so we get that it can be written as (x**2 + 1) * 1. + + """ + # Here is the strategy: + + # First, go through each term and determine if it can be rewritten as a + # rational multiple of any of the terms gathered so far. + # cancel(a/b).is_Rational is sufficient for this. If it is a multiple, we + # add its multiple to the dictionary. + + terms = {} + for term in exprs: + for trm, trm_list in terms.items(): + a = cancel(term/trm) + if a.is_Rational: + trm_list.append((term, a)) + break + else: + terms[term] = [(term, S.One)] + + # After we have done this, we have all the like terms together, so we just + # need to find a common denominator so that we can get the base term and + # integer multiples such that each term can be written as an integer + # multiple of the base term, and the content of the integers is 1. + + newterms = {} + for term, term_list in terms.items(): + common_denom = reduce(ilcm, [i.as_numer_denom()[1] for _, i in + term_list]) + newterm = term/common_denom + newmults = [(i, j*common_denom) for i, j in term_list] + newterms[newterm] = newmults + + return sorted(iter(newterms.items()), key=lambda item: item[0].sort_key()) + + +class DifferentialExtension: + """ + A container for all the information relating to a differential extension. + + Explanation + =========== + + The attributes of this object are (see also the docstring of __init__): + + - f: The original (Expr) integrand. + - x: The variable of integration. + - T: List of variables in the extension. + - D: List of derivations in the extension; corresponds to the elements of T. + - fa: Poly of the numerator of the integrand. + - fd: Poly of the denominator of the integrand. + - Tfuncs: Lambda() representations of each element of T (except for x). + For back-substitution after integration. + - backsubs: A (possibly empty) list of further substitutions to be made on + the final integral to make it look more like the integrand. + - exts: + - extargs: + - cases: List of string representations of the cases of T. + - t: The top level extension variable, as defined by the current level + (see level below). + - d: The top level extension derivation, as defined by the current + derivation (see level below). + - case: The string representation of the case of self.d. + (Note that self.T and self.D will always contain the complete extension, + regardless of the level. Therefore, you should ALWAYS use DE.t and DE.d + instead of DE.T[-1] and DE.D[-1]. If you want to have a list of the + derivations or variables only up to the current level, use + DE.D[:len(DE.D) + DE.level + 1] and DE.T[:len(DE.T) + DE.level + 1]. Note + that, in particular, the derivation() function does this.) + + The following are also attributes, but will probably not be useful other + than in internal use: + - newf: Expr form of fa/fd. + - level: The number (between -1 and -len(self.T)) such that + self.T[self.level] == self.t and self.D[self.level] == self.d. + Use the methods self.increment_level() and self.decrement_level() to change + the current level. + """ + # __slots__ is defined mainly so we can iterate over all the attributes + # of the class easily (the memory use doesn't matter too much, since we + # only create one DifferentialExtension per integration). Also, it's nice + # to have a safeguard when debugging. + __slots__ = ('f', 'x', 'T', 'D', 'fa', 'fd', 'Tfuncs', 'backsubs', + 'exts', 'extargs', 'cases', 'case', 't', 'd', 'newf', 'level', + 'ts', 'dummy') + + def __init__(self, f=None, x=None, handle_first='log', dummy=False, extension=None, rewrite_complex=None): + """ + Tries to build a transcendental extension tower from ``f`` with respect to ``x``. + + Explanation + =========== + + If it is successful, creates a DifferentialExtension object with, among + others, the attributes fa, fd, D, T, Tfuncs, and backsubs such that + fa and fd are Polys in T[-1] with rational coefficients in T[:-1], + fa/fd == f, and D[i] is a Poly in T[i] with rational coefficients in + T[:i] representing the derivative of T[i] for each i from 1 to len(T). + Tfuncs is a list of Lambda objects for back replacing the functions + after integrating. Lambda() is only used (instead of lambda) to make + them easier to test and debug. Note that Tfuncs corresponds to the + elements of T, except for T[0] == x, but they should be back-substituted + in reverse order. backsubs is a (possibly empty) back-substitution list + that should be applied on the completed integral to make it look more + like the original integrand. + + If it is unsuccessful, it raises NotImplementedError. + + You can also create an object by manually setting the attributes as a + dictionary to the extension keyword argument. You must include at least + D. Warning, any attribute that is not given will be set to None. The + attributes T, t, d, cases, case, x, and level are set automatically and + do not need to be given. The functions in the Risch Algorithm will NOT + check to see if an attribute is None before using it. This also does not + check to see if the extension is valid (non-algebraic) or even if it is + self-consistent. Therefore, this should only be used for + testing/debugging purposes. + """ + # XXX: If you need to debug this function, set the break point here + + if extension: + if 'D' not in extension: + raise ValueError("At least the key D must be included with " + "the extension flag to DifferentialExtension.") + for attr in extension: + setattr(self, attr, extension[attr]) + + self._auto_attrs() + + return + elif f is None or x is None: + raise ValueError("Either both f and x or a manual extension must " + "be given.") + + if handle_first not in ('log', 'exp'): + raise ValueError("handle_first must be 'log' or 'exp', not %s." % + str(handle_first)) + + # f will be the original function, self.f might change if we reset + # (e.g., we pull out a constant from an exponential) + self.f = f + self.x = x + # setting the default value 'dummy' + self.dummy = dummy + self.reset() + exp_new_extension, log_new_extension = True, True + + # case of 'automatic' choosing + if rewrite_complex is None: + rewrite_complex = I in self.f.atoms() + + if rewrite_complex: + rewritables = { + (sin, cos, cot, tan, sinh, cosh, coth, tanh): exp, + (asin, acos, acot, atan): log, + } + # rewrite the trigonometric components + for candidates, rule in rewritables.items(): + self.newf = self.newf.rewrite(candidates, rule) + self.newf = cancel(self.newf) + else: + if any(i.has(x) for i in self.f.atoms(sin, cos, tan, atan, asin, acos)): + raise NotImplementedError("Trigonometric extensions are not " + "supported (yet!)") + + exps = set() + pows = set() + numpows = set() + sympows = set() + logs = set() + symlogs = set() + + while True: + if self.newf.is_rational_function(*self.T): + break + + if not exp_new_extension and not log_new_extension: + # We couldn't find a new extension on the last pass, so I guess + # we can't do it. + raise NotImplementedError("Couldn't find an elementary " + "transcendental extension for %s. Try using a " % str(f) + + "manual extension with the extension flag.") + + exps, pows, numpows, sympows, log_new_extension = \ + self._rewrite_exps_pows(exps, pows, numpows, sympows, log_new_extension) + + logs, symlogs = self._rewrite_logs(logs, symlogs) + + if handle_first == 'exp' or not log_new_extension: + exp_new_extension = self._exp_part(exps) + if exp_new_extension is None: + # reset and restart + self.f = self.newf + self.reset() + exp_new_extension = True + continue + + if handle_first == 'log' or not exp_new_extension: + log_new_extension = self._log_part(logs) + + self.fa, self.fd = frac_in(self.newf, self.t) + self._auto_attrs() + + return + + def __getattr__(self, attr): + # Avoid AttributeErrors when debugging + if attr not in self.__slots__: + raise AttributeError("%s has no attribute %s" % (repr(self), repr(attr))) + return None + + def _rewrite_exps_pows(self, exps, pows, numpows, + sympows, log_new_extension): + """ + Rewrite exps/pows for better processing. + """ + from .prde import is_deriv_k + + # Pre-preparsing. + ################# + # Get all exp arguments, so we can avoid ahead of time doing + # something like t1 = exp(x), t2 = exp(x/2) == sqrt(t1). + + # Things like sqrt(exp(x)) do not automatically simplify to + # exp(x/2), so they will be viewed as algebraic. The easiest way + # to handle this is to convert all instances of exp(a)**Rational + # to exp(Rational*a) before doing anything else. Note that the + # _exp_part code can generate terms of this form, so we do need to + # do this at each pass (or else modify it to not do that). + + ratpows = [i for i in self.newf.atoms(Pow) + if (isinstance(i.base, exp) and i.exp.is_Rational)] + + ratpows_repl = [ + (i, i.base.base**(i.exp*i.base.exp)) for i in ratpows] + self.backsubs += [(j, i) for i, j in ratpows_repl] + self.newf = self.newf.xreplace(dict(ratpows_repl)) + + # To make the process deterministic, the args are sorted + # so that functions with smaller op-counts are processed first. + # Ties are broken with the default_sort_key. + + # XXX Although the method is deterministic no additional work + # has been done to guarantee that the simplest solution is + # returned and that it would be affected be using different + # variables. Though it is possible that this is the case + # one should know that it has not been done intentionally, so + # further improvements may be possible. + + # TODO: This probably doesn't need to be completely recomputed at + # each pass. + exps = update_sets(exps, self.newf.atoms(exp), + lambda i: i.exp.is_rational_function(*self.T) and + i.exp.has(*self.T)) + pows = update_sets(pows, self.newf.atoms(Pow), + lambda i: i.exp.is_rational_function(*self.T) and + i.exp.has(*self.T)) + numpows = update_sets(numpows, set(pows), + lambda i: not i.base.has(*self.T)) + sympows = update_sets(sympows, set(pows) - set(numpows), + lambda i: i.base.is_rational_function(*self.T) and + not i.exp.is_Integer) + + # The easiest way to deal with non-base E powers is to convert them + # into base E, integrate, and then convert back. + for i in ordered(pows): + old = i + new = exp(i.exp*log(i.base)) + # If exp is ever changed to automatically reduce exp(x*log(2)) + # to 2**x, then this will break. The solution is to not change + # exp to do that :) + if i in sympows: + if i.exp.is_Rational: + raise NotImplementedError("Algebraic extensions are " + "not supported (%s)." % str(i)) + # We can add a**b only if log(a) in the extension, because + # a**b == exp(b*log(a)). + basea, based = frac_in(i.base, self.t) + A = is_deriv_k(basea, based, self) + if A is None: + # Nonelementary monomial (so far) + + # TODO: Would there ever be any benefit from just + # adding log(base) as a new monomial? + # ANSWER: Yes, otherwise we can't integrate x**x (or + # rather prove that it has no elementary integral) + # without first manually rewriting it as exp(x*log(x)) + self.newf = self.newf.xreplace({old: new}) + self.backsubs += [(new, old)] + log_new_extension = self._log_part([log(i.base)]) + exps = update_sets(exps, self.newf.atoms(exp), lambda i: + i.exp.is_rational_function(*self.T) and i.exp.has(*self.T)) + continue + ans, u, const = A + newterm = exp(i.exp*(log(const) + u)) + # Under the current implementation, exp kills terms + # only if they are of the form a*log(x), where a is a + # Number. This case should have already been killed by the + # above tests. Again, if this changes to kill more than + # that, this will break, which maybe is a sign that you + # shouldn't be changing that. Actually, if anything, this + # auto-simplification should be removed. See + # https://groups.google.com/group/sympy/browse_thread/thread/a61d48235f16867f + + self.newf = self.newf.xreplace({i: newterm}) + + elif i not in numpows: + continue + else: + # i in numpows + newterm = new + # TODO: Just put it in self.Tfuncs + self.backsubs.append((new, old)) + self.newf = self.newf.xreplace({old: newterm}) + exps.append(newterm) + + return exps, pows, numpows, sympows, log_new_extension + + def _rewrite_logs(self, logs, symlogs): + """ + Rewrite logs for better processing. + """ + atoms = self.newf.atoms(log) + logs = update_sets(logs, atoms, + lambda i: i.args[0].is_rational_function(*self.T) and + i.args[0].has(*self.T)) + symlogs = update_sets(symlogs, atoms, + lambda i: i.has(*self.T) and i.args[0].is_Pow and + i.args[0].base.is_rational_function(*self.T) and + not i.args[0].exp.is_Integer) + + # We can handle things like log(x**y) by converting it to y*log(x) + # This will fix not only symbolic exponents of the argument, but any + # non-Integer exponent, like log(sqrt(x)). The exponent can also + # depend on x, like log(x**x). + for i in ordered(symlogs): + # Unlike in the exponential case above, we do not ever + # potentially add new monomials (above we had to add log(a)). + # Therefore, there is no need to run any is_deriv functions + # here. Just convert log(a**b) to b*log(a) and let + # log_new_extension() handle it from there. + lbase = log(i.args[0].base) + logs.append(lbase) + new = i.args[0].exp*lbase + self.newf = self.newf.xreplace({i: new}) + self.backsubs.append((new, i)) + + # remove any duplicates + logs = sorted(set(logs), key=default_sort_key) + + return logs, symlogs + + def _auto_attrs(self): + """ + Set attributes that are generated automatically. + """ + if not self.T: + # i.e., when using the extension flag and T isn't given + self.T = [i.gen for i in self.D] + if not self.x: + self.x = self.T[0] + self.cases = [get_case(d, t) for d, t in zip(self.D, self.T)] + self.level = -1 + self.t = self.T[self.level] + self.d = self.D[self.level] + self.case = self.cases[self.level] + + def _exp_part(self, exps): + """ + Try to build an exponential extension. + + Returns + ======= + + Returns True if there was a new extension, False if there was no new + extension but it was able to rewrite the given exponentials in terms + of the existing extension, and None if the entire extension building + process should be restarted. If the process fails because there is no + way around an algebraic extension (e.g., exp(log(x)/2)), it will raise + NotImplementedError. + """ + from .prde import is_log_deriv_k_t_radical + new_extension = False + restart = False + expargs = [i.exp for i in exps] + ip = integer_powers(expargs) + for arg, others in ip: + # Minimize potential problems with algebraic substitution + others.sort(key=lambda i: i[1]) + + arga, argd = frac_in(arg, self.t) + A = is_log_deriv_k_t_radical(arga, argd, self) + + if A is not None: + ans, u, n, const = A + # if n is 1 or -1, it's algebraic, but we can handle it + if n == -1: + # This probably will never happen, because + # Rational.as_numer_denom() returns the negative term in + # the numerator. But in case that changes, reduce it to + # n == 1. + n = 1 + u **= -1 + const *= -1 + ans = [(i, -j) for i, j in ans] + + if n == 1: + # Example: exp(x + x**2) over QQ(x, exp(x), exp(x**2)) + self.newf = self.newf.xreplace({exp(arg): exp(const)*Mul(*[ + u**power for u, power in ans])}) + self.newf = self.newf.xreplace({exp(p*exparg): + exp(const*p) * Mul(*[u**power for u, power in ans]) + for exparg, p in others}) + # TODO: Add something to backsubs to put exp(const*p) + # back together. + + continue + + else: + # Bad news: we have an algebraic radical. But maybe we + # could still avoid it by choosing a different extension. + # For example, integer_powers() won't handle exp(x/2 + 1) + # over QQ(x, exp(x)), but if we pull out the exp(1), it + # will. Or maybe we have exp(x + x**2/2), over + # QQ(x, exp(x), exp(x**2)), which is exp(x)*sqrt(exp(x**2)), + # but if we use QQ(x, exp(x), exp(x**2/2)), then they will + # all work. + # + # So here is what we do: If there is a non-zero const, pull + # it out and retry. Also, if len(ans) > 1, then rewrite + # exp(arg) as the product of exponentials from ans, and + # retry that. If const == 0 and len(ans) == 1, then we + # assume that it would have been handled by either + # integer_powers() or n == 1 above if it could be handled, + # so we give up at that point. For example, you can never + # handle exp(log(x)/2) because it equals sqrt(x). + + if const or len(ans) > 1: + rad = Mul(*[term**(power/n) for term, power in ans]) + self.newf = self.newf.xreplace({exp(p*exparg): + exp(const*p)*rad for exparg, p in others}) + self.newf = self.newf.xreplace(dict(list(zip(reversed(self.T), + reversed([f(self.x) for f in self.Tfuncs]))))) + restart = True + break + else: + # TODO: give algebraic dependence in error string + raise NotImplementedError("Cannot integrate over " + "algebraic extensions.") + + else: + arga, argd = frac_in(arg, self.t) + darga = (argd*derivation(Poly(arga, self.t), self) - + arga*derivation(Poly(argd, self.t), self)) + dargd = argd**2 + darga, dargd = darga.cancel(dargd, include=True) + darg = darga.as_expr()/dargd.as_expr() + self.t = next(self.ts) + self.T.append(self.t) + self.extargs.append(arg) + self.exts.append('exp') + self.D.append(darg.as_poly(self.t, expand=False)*Poly(self.t, + self.t, expand=False)) + if self.dummy: + i = Dummy("i") + else: + i = Symbol('i') + self.Tfuncs += [Lambda(i, exp(arg.subs(self.x, i)))] + self.newf = self.newf.xreplace( + {exp(exparg): self.t**p for exparg, p in others}) + new_extension = True + + if restart: + return None + return new_extension + + def _log_part(self, logs): + """ + Try to build a logarithmic extension. + + Returns + ======= + + Returns True if there was a new extension and False if there was no new + extension but it was able to rewrite the given logarithms in terms + of the existing extension. Unlike with exponential extensions, there + is no way that a logarithm is not transcendental over and cannot be + rewritten in terms of an already existing extension in a non-algebraic + way, so this function does not ever return None or raise + NotImplementedError. + """ + from .prde import is_deriv_k + new_extension = False + logargs = [i.args[0] for i in logs] + for arg in ordered(logargs): + # The log case is easier, because whenever a logarithm is algebraic + # over the base field, it is of the form a1*t1 + ... an*tn + c, + # which is a polynomial, so we can just replace it with that. + # In other words, we don't have to worry about radicals. + arga, argd = frac_in(arg, self.t) + A = is_deriv_k(arga, argd, self) + if A is not None: + ans, u, const = A + newterm = log(const) + u + self.newf = self.newf.xreplace({log(arg): newterm}) + continue + + else: + arga, argd = frac_in(arg, self.t) + darga = (argd*derivation(Poly(arga, self.t), self) - + arga*derivation(Poly(argd, self.t), self)) + dargd = argd**2 + darg = darga.as_expr()/dargd.as_expr() + self.t = next(self.ts) + self.T.append(self.t) + self.extargs.append(arg) + self.exts.append('log') + self.D.append(cancel(darg.as_expr()/arg).as_poly(self.t, + expand=False)) + if self.dummy: + i = Dummy("i") + else: + i = Symbol('i') + self.Tfuncs += [Lambda(i, log(arg.subs(self.x, i)))] + self.newf = self.newf.xreplace({log(arg): self.t}) + new_extension = True + + return new_extension + + @property + def _important_attrs(self): + """ + Returns some of the more important attributes of self. + + Explanation + =========== + + Used for testing and debugging purposes. + + The attributes are (fa, fd, D, T, Tfuncs, backsubs, + exts, extargs). + """ + return (self.fa, self.fd, self.D, self.T, self.Tfuncs, + self.backsubs, self.exts, self.extargs) + + # NOTE: this printing doesn't follow the Python's standard + # eval(repr(DE)) == DE, where DE is the DifferentialExtension object, + # also this printing is supposed to contain all the important + # attributes of a DifferentialExtension object + def __repr__(self): + # no need to have GeneratorType object printed in it + r = [(attr, getattr(self, attr)) for attr in self.__slots__ + if not isinstance(getattr(self, attr), GeneratorType)] + return self.__class__.__name__ + '(dict(%r))' % (r) + + # fancy printing of DifferentialExtension object + def __str__(self): + return (self.__class__.__name__ + '({fa=%s, fd=%s, D=%s})' % + (self.fa, self.fd, self.D)) + + # should only be used for debugging purposes, internally + # f1 = f2 = log(x) at different places in code execution + # may return D1 != D2 as True, since 'level' or other attribute + # may differ + def __eq__(self, other): + for attr in self.__class__.__slots__: + d1, d2 = getattr(self, attr), getattr(other, attr) + if not (isinstance(d1, GeneratorType) or d1 == d2): + return False + return True + + def reset(self): + """ + Reset self to an initial state. Used by __init__. + """ + self.t = self.x + self.T = [self.x] + self.D = [Poly(1, self.x)] + self.level = -1 + self.exts = [None] + self.extargs = [None] + if self.dummy: + self.ts = numbered_symbols('t', cls=Dummy) + else: + # For testing + self.ts = numbered_symbols('t') + # For various things that we change to make things work that we need to + # change back when we are done. + self.backsubs = [] + self.Tfuncs = [] + self.newf = self.f + + def indices(self, extension): + """ + Parameters + ========== + + extension : str + Represents a valid extension type. + + Returns + ======= + + list: A list of indices of 'exts' where extension of + type 'extension' is present. + + Examples + ======== + + >>> from sympy.integrals.risch import DifferentialExtension + >>> from sympy import log, exp + >>> from sympy.abc import x + >>> DE = DifferentialExtension(log(x) + exp(x), x, handle_first='exp') + >>> DE.indices('log') + [2] + >>> DE.indices('exp') + [1] + + """ + return [i for i, ext in enumerate(self.exts) if ext == extension] + + def increment_level(self): + """ + Increment the level of self. + + Explanation + =========== + + This makes the working differential extension larger. self.level is + given relative to the end of the list (-1, -2, etc.), so we do not need + do worry about it when building the extension. + """ + if self.level >= -1: + raise ValueError("The level of the differential extension cannot " + "be incremented any further.") + + self.level += 1 + self.t = self.T[self.level] + self.d = self.D[self.level] + self.case = self.cases[self.level] + return None + + def decrement_level(self): + """ + Decrease the level of self. + + Explanation + =========== + + This makes the working differential extension smaller. self.level is + given relative to the end of the list (-1, -2, etc.), so we do not need + do worry about it when building the extension. + """ + if self.level <= -len(self.T): + raise ValueError("The level of the differential extension cannot " + "be decremented any further.") + + self.level -= 1 + self.t = self.T[self.level] + self.d = self.D[self.level] + self.case = self.cases[self.level] + return None + + +def update_sets(seq, atoms, func): + s = set(seq) + s = atoms.intersection(s) + new = atoms - s + s.update(list(filter(func, new))) + return list(s) + + +class DecrementLevel: + """ + A context manager for decrementing the level of a DifferentialExtension. + """ + __slots__ = ('DE',) + + def __init__(self, DE): + self.DE = DE + return + + def __enter__(self): + self.DE.decrement_level() + + def __exit__(self, exc_type, exc_value, traceback): + self.DE.increment_level() + + +class NonElementaryIntegralException(Exception): + """ + Exception used by subroutines within the Risch algorithm to indicate to one + another that the function being integrated does not have an elementary + integral in the given differential field. + """ + # TODO: Rewrite algorithms below to use this (?) + + # TODO: Pass through information about why the integral was nonelementary, + # and store that in the resulting NonElementaryIntegral somehow. + pass + + +def gcdex_diophantine(a, b, c): + """ + Extended Euclidean Algorithm, Diophantine version. + + Explanation + =========== + + Given ``a``, ``b`` in K[x] and ``c`` in (a, b), the ideal generated by ``a`` and + ``b``, return (s, t) such that s*a + t*b == c and either s == 0 or s.degree() + < b.degree(). + """ + # Extended Euclidean Algorithm (Diophantine Version) pg. 13 + # TODO: This should go in densetools.py. + # XXX: Better name? + + s, g = a.half_gcdex(b) + s *= c.exquo(g) # Inexact division means c is not in (a, b) + if s and s.degree() >= b.degree(): + _, s = s.div(b) + t = (c - s*a).exquo(b) + return (s, t) + + +def frac_in(f, t, *, cancel=False, **kwargs): + """ + Returns the tuple (fa, fd), where fa and fd are Polys in t. + + Explanation + =========== + + This is a common idiom in the Risch Algorithm functions, so we abstract + it out here. ``f`` should be a basic expression, a Poly, or a tuple (fa, fd), + where fa and fd are either basic expressions or Polys, and f == fa/fd. + **kwargs are applied to Poly. + """ + if isinstance(f, tuple): + fa, fd = f + f = fa.as_expr()/fd.as_expr() + fa, fd = f.as_expr().as_numer_denom() + fa, fd = fa.as_poly(t, **kwargs), fd.as_poly(t, **kwargs) + if cancel: + fa, fd = fa.cancel(fd, include=True) + if fa is None or fd is None: + raise ValueError("Could not turn %s into a fraction in %s." % (f, t)) + return (fa, fd) + + +def as_poly_1t(p, t, z): + """ + (Hackish) way to convert an element ``p`` of K[t, 1/t] to K[t, z]. + + In other words, ``z == 1/t`` will be a dummy variable that Poly can handle + better. + + See issue 5131. + + Examples + ======== + + >>> from sympy import random_poly + >>> from sympy.integrals.risch import as_poly_1t + >>> from sympy.abc import x, z + + >>> p1 = random_poly(x, 10, -10, 10) + >>> p2 = random_poly(x, 10, -10, 10) + >>> p = p1 + p2.subs(x, 1/x) + >>> as_poly_1t(p, x, z).as_expr().subs(z, 1/x) == p + True + """ + # TODO: Use this on the final result. That way, we can avoid answers like + # (...)*exp(-x). + pa, pd = frac_in(p, t, cancel=True) + if not pd.is_monomial: + # XXX: Is there a better Poly exception that we could raise here? + # Either way, if you see this (from the Risch Algorithm) it indicates + # a bug. + raise PolynomialError("%s is not an element of K[%s, 1/%s]." % (p, t, t)) + + t_part, remainder = pa.div(pd) + + ans = t_part.as_poly(t, z, expand=False) + + if remainder: + one = remainder.one + tp = t*one + r = pd.degree() - remainder.degree() + z_part = remainder.transform(one, tp) * tp**r + z_part = z_part.replace(t, z).to_field().quo_ground(pd.LC()) + ans += z_part.as_poly(t, z, expand=False) + + return ans + + +def derivation(p, DE, coefficientD=False, basic=False): + """ + Computes Dp. + + Explanation + =========== + + Given the derivation D with D = d/dx and p is a polynomial in t over + K(x), return Dp. + + If coefficientD is True, it computes the derivation kD + (kappaD), which is defined as kD(sum(ai*Xi**i, (i, 0, n))) == + sum(Dai*Xi**i, (i, 1, n)) (Definition 3.2.2, page 80). X in this case is + T[-1], so coefficientD computes the derivative just with respect to T[:-1], + with T[-1] treated as a constant. + + If ``basic=True``, the returns a Basic expression. Elements of D can still be + instances of Poly. + """ + if basic: + r = 0 + else: + r = Poly(0, DE.t) + + t = DE.t + if coefficientD: + if DE.level <= -len(DE.T): + # 'base' case, the answer is 0. + return r + DE.decrement_level() + + D = DE.D[:len(DE.D) + DE.level + 1] + T = DE.T[:len(DE.T) + DE.level + 1] + + for d, v in zip(D, T): + pv = p.as_poly(v) + if pv is None or basic: + pv = p.as_expr() + + if basic: + r += d.as_expr()*pv.diff(v) + else: + r += (d.as_expr()*pv.diff(v).as_expr()).as_poly(t) + + if basic: + r = cancel(r) + if coefficientD: + DE.increment_level() + + return r + + +def get_case(d, t): + """ + Returns the type of the derivation d. + + Returns one of {'exp', 'tan', 'base', 'primitive', 'other_linear', + 'other_nonlinear'}. + """ + if not d.expr.has(t): + if d.is_one: + return 'base' + return 'primitive' + if d.rem(Poly(t, t)).is_zero: + return 'exp' + if d.rem(Poly(1 + t**2, t)).is_zero: + return 'tan' + if d.degree(t) > 1: + return 'other_nonlinear' + return 'other_linear' + + +def splitfactor(p, DE, coefficientD=False, z=None): + """ + Splitting factorization. + + Explanation + =========== + + Given a derivation D on k[t] and ``p`` in k[t], return (p_n, p_s) in + k[t] x k[t] such that p = p_n*p_s, p_s is special, and each square + factor of p_n is normal. + + Page. 100 + """ + kinv = [1/x for x in DE.T[:DE.level]] + if z: + kinv.append(z) + + One = Poly(1, DE.t, domain=p.get_domain()) + Dp = derivation(p, DE, coefficientD=coefficientD) + # XXX: Is this right? + if p.is_zero: + return (p, One) + + if not p.expr.has(DE.t): + s = p.as_poly(*kinv).gcd(Dp.as_poly(*kinv)).as_poly(DE.t) + n = p.exquo(s) + return (n, s) + + if not Dp.is_zero: + h = p.gcd(Dp).to_field() + g = p.gcd(p.diff(DE.t)).to_field() + s = h.exquo(g) + + if s.degree(DE.t) == 0: + return (p, One) + + q_split = splitfactor(p.exquo(s), DE, coefficientD=coefficientD) + + return (q_split[0], q_split[1]*s) + else: + return (p, One) + + +def splitfactor_sqf(p, DE, coefficientD=False, z=None, basic=False): + """ + Splitting Square-free Factorization. + + Explanation + =========== + + Given a derivation D on k[t] and ``p`` in k[t], returns (N1, ..., Nm) + and (S1, ..., Sm) in k[t]^m such that p = + (N1*N2**2*...*Nm**m)*(S1*S2**2*...*Sm**m) is a splitting + factorization of ``p`` and the Ni and Si are square-free and coprime. + """ + # TODO: This algorithm appears to be faster in every case + # TODO: Verify this and splitfactor() for multiple extensions + kkinv = [1/x for x in DE.T[:DE.level]] + DE.T[:DE.level] + if z: + kkinv = [z] + + S = [] + N = [] + p_sqf = p.sqf_list_include() + if p.is_zero: + return (((p, 1),), ()) + + for pi, i in p_sqf: + Si = pi.as_poly(*kkinv).gcd(derivation(pi, DE, + coefficientD=coefficientD,basic=basic).as_poly(*kkinv)).as_poly(DE.t) + pi = Poly(pi, DE.t) + Si = Poly(Si, DE.t) + Ni = pi.exquo(Si) + if not Si.is_one: + S.append((Si, i)) + if not Ni.is_one: + N.append((Ni, i)) + + return (tuple(N), tuple(S)) + + +def canonical_representation(a, d, DE): + """ + Canonical Representation. + + Explanation + =========== + + Given a derivation D on k[t] and f = a/d in k(t), return (f_p, f_s, + f_n) in k[t] x k(t) x k(t) such that f = f_p + f_s + f_n is the + canonical representation of f (f_p is a polynomial, f_s is reduced + (has a special denominator), and f_n is simple (has a normal + denominator). + """ + # Make d monic + l = Poly(1/d.LC(), DE.t) + a, d = a.mul(l), d.mul(l) + + q, r = a.div(d) + dn, ds = splitfactor(d, DE) + + b, c = gcdex_diophantine(dn.as_poly(DE.t), ds.as_poly(DE.t), r.as_poly(DE.t)) + b, c = b.as_poly(DE.t), c.as_poly(DE.t) + + return (q, (b, ds), (c, dn)) + + +def hermite_reduce(a, d, DE): + """ + Hermite Reduction - Mack's Linear Version. + + Given a derivation D on k(t) and f = a/d in k(t), returns g, h, r in + k(t) such that f = Dg + h + r, h is simple, and r is reduced. + + """ + # Make d monic + l = Poly(1/d.LC(), DE.t) + a, d = a.mul(l), d.mul(l) + + fp, fs, fn = canonical_representation(a, d, DE) + a, d = fn + l = Poly(1/d.LC(), DE.t) + a, d = a.mul(l), d.mul(l) + + ga = Poly(0, DE.t) + gd = Poly(1, DE.t) + + dd = derivation(d, DE) + dm = gcd(d.to_field(), dd.to_field()).as_poly(DE.t) + ds, _ = d.div(dm) + + while dm.degree(DE.t) > 0: + + ddm = derivation(dm, DE) + dm2 = gcd(dm.to_field(), ddm.to_field()) + dms, _ = dm.div(dm2) + ds_ddm = ds.mul(ddm) + ds_ddm_dm, _ = ds_ddm.div(dm) + + b, c = gcdex_diophantine(-ds_ddm_dm.as_poly(DE.t), + dms.as_poly(DE.t), a.as_poly(DE.t)) + b, c = b.as_poly(DE.t), c.as_poly(DE.t) + + db = derivation(b, DE).as_poly(DE.t) + ds_dms, _ = ds.div(dms) + a = c.as_poly(DE.t) - db.mul(ds_dms).as_poly(DE.t) + + ga = ga*dm + b*gd + gd = gd*dm + ga, gd = ga.cancel(gd, include=True) + dm = dm2 + + q, r = a.div(ds) + ga, gd = ga.cancel(gd, include=True) + + r, d = r.cancel(ds, include=True) + rra = q*fs[1] + fp*fs[1] + fs[0] + rrd = fs[1] + rra, rrd = rra.cancel(rrd, include=True) + + return ((ga, gd), (r, d), (rra, rrd)) + + +def polynomial_reduce(p, DE): + """ + Polynomial Reduction. + + Explanation + =========== + + Given a derivation D on k(t) and p in k[t] where t is a nonlinear + monomial over k, return q, r in k[t] such that p = Dq + r, and + deg(r) < deg_t(Dt). + """ + q = Poly(0, DE.t) + while p.degree(DE.t) >= DE.d.degree(DE.t): + m = p.degree(DE.t) - DE.d.degree(DE.t) + 1 + q0 = Poly(DE.t**m, DE.t).mul(Poly(p.as_poly(DE.t).LC()/ + (m*DE.d.LC()), DE.t)) + q += q0 + p = p - derivation(q0, DE) + + return (q, p) + + +def laurent_series(a, d, F, n, DE): + """ + Contribution of ``F`` to the full partial fraction decomposition of A/D. + + Explanation + =========== + + Given a field K of characteristic 0 and ``A``,``D``,``F`` in K[x] with D monic, + nonzero, coprime with A, and ``F`` the factor of multiplicity n in the square- + free factorization of D, return the principal parts of the Laurent series of + A/D at all the zeros of ``F``. + """ + if F.degree()==0: + return 0 + Z = _symbols('z', n) + z = Symbol('z') + Z.insert(0, z) + delta_a = Poly(0, DE.t) + delta_d = Poly(1, DE.t) + + E = d.quo(F**n) + ha, hd = (a, E*Poly(z**n, DE.t)) + dF = derivation(F,DE) + B, _ = gcdex_diophantine(E, F, Poly(1,DE.t)) + C, _ = gcdex_diophantine(dF, F, Poly(1,DE.t)) + + # initialization + F_store = F + V, DE_D_list, H_list= [], [], [] + + for j in range(0, n): + # jth derivative of z would be substituted with dfnth/(j+1) where dfnth =(d^n)f/(dx)^n + F_store = derivation(F_store, DE) + v = (F_store.as_expr())/(j + 1) + V.append(v) + DE_D_list.append(Poly(Z[j + 1],Z[j])) + + DE_new = DifferentialExtension(extension = {'D': DE_D_list}) #a differential indeterminate + for j in range(0, n): + zEha = Poly(z**(n + j), DE.t)*E**(j + 1)*ha + zEhd = hd + Pa, Pd = cancel((zEha, zEhd))[1], cancel((zEha, zEhd))[2] + Q = Pa.quo(Pd) + for i in range(0, j + 1): + Q = Q.subs(Z[i], V[i]) + Dha = (hd*derivation(ha, DE, basic=True).as_poly(DE.t) + + ha*derivation(hd, DE, basic=True).as_poly(DE.t) + + hd*derivation(ha, DE_new, basic=True).as_poly(DE.t) + + ha*derivation(hd, DE_new, basic=True).as_poly(DE.t)) + Dhd = Poly(j + 1, DE.t)*hd**2 + ha, hd = Dha, Dhd + + Ff, _ = F.div(gcd(F, Q)) + F_stara, F_stard = frac_in(Ff, DE.t) + if F_stara.degree(DE.t) - F_stard.degree(DE.t) > 0: + QBC = Poly(Q, DE.t)*B**(1 + j)*C**(n + j) + H = QBC + H_list.append(H) + H = (QBC*F_stard).rem(F_stara) + alphas = real_roots(F_stara) + for alpha in list(alphas): + delta_a = delta_a*Poly((DE.t - alpha)**(n - j), DE.t) + Poly(H.eval(alpha), DE.t) + delta_d = delta_d*Poly((DE.t - alpha)**(n - j), DE.t) + return (delta_a, delta_d, H_list) + + +def recognize_derivative(a, d, DE, z=None): + """ + Compute the squarefree factorization of the denominator of f + and for each Di the polynomial H in K[x] (see Theorem 2.7.1), using the + LaurentSeries algorithm. Write Di = GiEi where Gj = gcd(Hn, Di) and + gcd(Ei,Hn) = 1. Since the residues of f at the roots of Gj are all 0, and + the residue of f at a root alpha of Ei is Hi(a) != 0, f is the derivative of a + rational function if and only if Ei = 1 for each i, which is equivalent to + Di | H[-1] for each i. + """ + flag =True + a, d = a.cancel(d, include=True) + _, r = a.div(d) + Np, Sp = splitfactor_sqf(d, DE, coefficientD=True, z=z) + + j = 1 + for s, _ in Sp: + delta_a, delta_d, H = laurent_series(r, d, s, j, DE) + g = gcd(d, H[-1]).as_poly() + if g is not d: + flag = False + break + j = j + 1 + return flag + + +def recognize_log_derivative(a, d, DE, z=None): + """ + There exists a v in K(x)* such that f = dv/v + where f a rational function if and only if f can be written as f = A/D + where D is squarefree,deg(A) < deg(D), gcd(A, D) = 1, + and all the roots of the Rothstein-Trager resultant are integers. In that case, + any of the Rothstein-Trager, Lazard-Rioboo-Trager or Czichowski algorithm + produces u in K(x) such that du/dx = uf. + """ + + z = z or Dummy('z') + a, d = a.cancel(d, include=True) + _, a = a.div(d) + + pz = Poly(z, DE.t) + Dd = derivation(d, DE) + q = a - pz*Dd + r, _ = d.resultant(q, includePRS=True) + r = Poly(r, z) + Np, Sp = splitfactor_sqf(r, DE, coefficientD=True, z=z) + + for s, _ in Sp: + # TODO also consider the complex roots which should + # turn the flag false + a = real_roots(s.as_poly(z)) + + if not all(j.is_Integer for j in a): + return False + return True + +def residue_reduce(a, d, DE, z=None, invert=True): + """ + Lazard-Rioboo-Rothstein-Trager resultant reduction. + + Explanation + =========== + + Given a derivation ``D`` on k(t) and f in k(t) simple, return g + elementary over k(t) and a Boolean b in {True, False} such that f - + Dg in k[t] if b == True or f + h and f + h - Dg do not have an + elementary integral over k(t) for any h in k (reduced) if b == + False. + + Returns (G, b), where G is a tuple of tuples of the form (s_i, S_i), + such that g = Add(*[RootSum(s_i, lambda z: z*log(S_i(z, t))) for + S_i, s_i in G]). f - Dg is the remaining integral, which is elementary + only if b == True, and hence the integral of f is elementary only if + b == True. + + f - Dg is not calculated in this function because that would require + explicitly calculating the RootSum. Use residue_reduce_derivation(). + """ + # TODO: Use log_to_atan() from rationaltools.py + # If r = residue_reduce(...), then the logarithmic part is given by: + # sum([RootSum(a[0].as_poly(z), lambda i: i*log(a[1].as_expr()).subs(z, + # i)).subs(t, log(x)) for a in r[0]]) + + z = z or Dummy('z') + a, d = a.cancel(d, include=True) + a, d = a.to_field().mul_ground(1/d.LC()), d.to_field().mul_ground(1/d.LC()) + kkinv = [1/x for x in DE.T[:DE.level]] + DE.T[:DE.level] + + if a.is_zero: + return ([], True) + _, a = a.div(d) + + pz = Poly(z, DE.t) + + Dd = derivation(d, DE) + q = a - pz*Dd + + if Dd.degree(DE.t) <= d.degree(DE.t): + r, R = d.resultant(q, includePRS=True) + else: + r, R = q.resultant(d, includePRS=True) + + R_map, H = {}, [] + for i in R: + R_map[i.degree()] = i + + r = Poly(r, z) + Np, Sp = splitfactor_sqf(r, DE, coefficientD=True, z=z) + + for s, i in Sp: + if i == d.degree(DE.t): + s = Poly(s, z).monic() + H.append((s, d)) + else: + h = R_map.get(i) + if h is None: + continue + h_lc = Poly(h.as_poly(DE.t).LC(), DE.t, field=True) + + h_lc_sqf = h_lc.sqf_list_include(all=True) + + for a, j in h_lc_sqf: + h = Poly(h, DE.t, field=True).exquo(Poly(gcd(a, s**j, *kkinv), + DE.t)) + + s = Poly(s, z).monic() + + if invert: + h_lc = Poly(h.as_poly(DE.t).LC(), DE.t, field=True, expand=False) + inv, coeffs = h_lc.as_poly(z, field=True).invert(s), [S.One] + + for coeff in h.coeffs()[1:]: + L = reduced(inv*coeff.as_poly(inv.gens), [s])[1] + coeffs.append(L.as_expr()) + + h = Poly(dict(list(zip(h.monoms(), coeffs))), DE.t) + + H.append((s, h)) + + b = not any(cancel(i.as_expr()).has(DE.t, z) for i, _ in Np) + + return (H, b) + + +def residue_reduce_to_basic(H, DE, z): + """ + Converts the tuple returned by residue_reduce() into a Basic expression. + """ + # TODO: check what Lambda does with RootOf + i = Dummy('i') + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + return sum(RootSum(a[0].as_poly(z), Lambda(i, i*log(a[1].as_expr()).subs( + {z: i}).subs(s))) for a in H) + + +def residue_reduce_derivation(H, DE, z): + """ + Computes the derivation of an expression returned by residue_reduce(). + + In general, this is a rational function in t, so this returns an + as_expr() result. + """ + # TODO: verify that this is correct for multiple extensions + i = Dummy('i') + return S(sum(RootSum(a[0].as_poly(z), Lambda(i, i*derivation(a[1], + DE).as_expr().subs(z, i)/a[1].as_expr().subs(z, i))) for a in H)) + + +def integrate_primitive_polynomial(p, DE): + """ + Integration of primitive polynomials. + + Explanation + =========== + + Given a primitive monomial t over k, and ``p`` in k[t], return q in k[t], + r in k, and a bool b in {True, False} such that r = p - Dq is in k if b is + True, or r = p - Dq does not have an elementary integral over k(t) if b is + False. + """ + Zero = Poly(0, DE.t) + q = Poly(0, DE.t) + + if not p.expr.has(DE.t): + return (Zero, p, True) + + from .prde import limited_integrate + while True: + if not p.expr.has(DE.t): + return (q, p, True) + + Dta, Dtb = frac_in(DE.d, DE.T[DE.level - 1]) + + with DecrementLevel(DE): # We had better be integrating the lowest extension (x) + # with ratint(). + a = p.LC() + aa, ad = frac_in(a, DE.t) + + try: + rv = limited_integrate(aa, ad, [(Dta, Dtb)], DE) + if rv is None: + raise NonElementaryIntegralException + (ba, bd), c = rv + except NonElementaryIntegralException: + return (q, p, False) + + m = p.degree(DE.t) + q0 = c[0].as_poly(DE.t)*Poly(DE.t**(m + 1)/(m + 1), DE.t) + \ + (ba.as_expr()/bd.as_expr()).as_poly(DE.t)*Poly(DE.t**m, DE.t) + + p = p - derivation(q0, DE) + q = q + q0 + + +def integrate_primitive(a, d, DE, z=None): + """ + Integration of primitive functions. + + Explanation + =========== + + Given a primitive monomial t over k and f in k(t), return g elementary over + k(t), i in k(t), and b in {True, False} such that i = f - Dg is in k if b + is True or i = f - Dg does not have an elementary integral over k(t) if b + is False. + + This function returns a Basic expression for the first argument. If b is + True, the second argument is Basic expression in k to recursively integrate. + If b is False, the second argument is an unevaluated Integral, which has + been proven to be nonelementary. + """ + # XXX: a and d must be canceled, or this might return incorrect results + z = z or Dummy("z") + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + g1, h, r = hermite_reduce(a, d, DE) + g2, b = residue_reduce(h[0], h[1], DE, z=z) + if not b: + i = cancel(a.as_expr()/d.as_expr() - (g1[1]*derivation(g1[0], DE) - + g1[0]*derivation(g1[1], DE)).as_expr()/(g1[1]**2).as_expr() - + residue_reduce_derivation(g2, DE, z)) + i = NonElementaryIntegral(cancel(i).subs(s), DE.x) + return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z), i, b) + + # h - Dg2 + r + p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2, + DE, z) + r[0].as_expr()/r[1].as_expr()) + p = p.as_poly(DE.t) + + q, i, b = integrate_primitive_polynomial(p, DE) + + ret = ((g1[0].as_expr()/g1[1].as_expr() + q.as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z)) + if not b: + # TODO: This does not do the right thing when b is False + i = NonElementaryIntegral(cancel(i.as_expr()).subs(s), DE.x) + else: + i = cancel(i.as_expr()) + + return (ret, i, b) + + +def integrate_hyperexponential_polynomial(p, DE, z): + """ + Integration of hyperexponential polynomials. + + Explanation + =========== + + Given a hyperexponential monomial t over k and ``p`` in k[t, 1/t], return q in + k[t, 1/t] and a bool b in {True, False} such that p - Dq in k if b is True, + or p - Dq does not have an elementary integral over k(t) if b is False. + """ + t1 = DE.t + dtt = DE.d.exquo(Poly(DE.t, DE.t)) + qa = Poly(0, DE.t) + qd = Poly(1, DE.t) + b = True + + if p.is_zero: + return(qa, qd, b) + + from sympy.integrals.rde import rischDE + + with DecrementLevel(DE): + for i in range(-p.degree(z), p.degree(t1) + 1): + if not i: + continue + elif i < 0: + # If you get AttributeError: 'NoneType' object has no attribute 'nth' + # then this should really not have expand=False + # But it shouldn't happen because p is already a Poly in t and z + a = p.as_poly(z, expand=False).nth(-i) + else: + # If you get AttributeError: 'NoneType' object has no attribute 'nth' + # then this should really not have expand=False + a = p.as_poly(t1, expand=False).nth(i) + + aa, ad = frac_in(a, DE.t, field=True) + aa, ad = aa.cancel(ad, include=True) + iDt = Poly(i, t1)*dtt + iDta, iDtd = frac_in(iDt, DE.t, field=True) + try: + va, vd = rischDE(iDta, iDtd, Poly(aa, DE.t), Poly(ad, DE.t), DE) + va, vd = frac_in((va, vd), t1, cancel=True) + except NonElementaryIntegralException: + b = False + else: + qa = qa*vd + va*Poly(t1**i)*qd + qd *= vd + + return (qa, qd, b) + + +def integrate_hyperexponential(a, d, DE, z=None, conds='piecewise'): + """ + Integration of hyperexponential functions. + + Explanation + =========== + + Given a hyperexponential monomial t over k and f in k(t), return g + elementary over k(t), i in k(t), and a bool b in {True, False} such that + i = f - Dg is in k if b is True or i = f - Dg does not have an elementary + integral over k(t) if b is False. + + This function returns a Basic expression for the first argument. If b is + True, the second argument is Basic expression in k to recursively integrate. + If b is False, the second argument is an unevaluated Integral, which has + been proven to be nonelementary. + """ + # XXX: a and d must be canceled, or this might return incorrect results + z = z or Dummy("z") + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + g1, h, r = hermite_reduce(a, d, DE) + g2, b = residue_reduce(h[0], h[1], DE, z=z) + if not b: + i = cancel(a.as_expr()/d.as_expr() - (g1[1]*derivation(g1[0], DE) - + g1[0]*derivation(g1[1], DE)).as_expr()/(g1[1]**2).as_expr() - + residue_reduce_derivation(g2, DE, z)) + i = NonElementaryIntegral(cancel(i.subs(s)), DE.x) + return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z), i, b) + + # p should be a polynomial in t and 1/t, because Sirr == k[t, 1/t] + # h - Dg2 + r + p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2, + DE, z) + r[0].as_expr()/r[1].as_expr()) + pp = as_poly_1t(p, DE.t, z) + + qa, qd, b = integrate_hyperexponential_polynomial(pp, DE, z) + + i = pp.nth(0, 0) + + ret = ((g1[0].as_expr()/g1[1].as_expr()).subs(s) \ + + residue_reduce_to_basic(g2, DE, z)) + + qas = qa.as_expr().subs(s) + qds = qd.as_expr().subs(s) + if conds == 'piecewise' and DE.x not in qds.free_symbols: + # We have to be careful if the exponent is S.Zero! + + # XXX: Does qd = 0 always necessarily correspond to the exponential + # equaling 1? + ret += Piecewise( + (qas/qds, Ne(qds, 0)), + (integrate((p - i).subs(DE.t, 1).subs(s), DE.x), True) + ) + else: + ret += qas/qds + + if not b: + i = p - (qd*derivation(qa, DE) - qa*derivation(qd, DE)).as_expr()/\ + (qd**2).as_expr() + i = NonElementaryIntegral(cancel(i).subs(s), DE.x) + return (ret, i, b) + + +def integrate_hypertangent_polynomial(p, DE): + """ + Integration of hypertangent polynomials. + + Explanation + =========== + + Given a differential field k such that sqrt(-1) is not in k, a + hypertangent monomial t over k, and p in k[t], return q in k[t] and + c in k such that p - Dq - c*D(t**2 + 1)/(t**1 + 1) is in k and p - + Dq does not have an elementary integral over k(t) if Dc != 0. + """ + # XXX: Make sure that sqrt(-1) is not in k. + q, r = polynomial_reduce(p, DE) + a = DE.d.exquo(Poly(DE.t**2 + 1, DE.t)) + c = Poly(r.nth(1)/(2*a.as_expr()), DE.t) + return (q, c) + + +def integrate_nonlinear_no_specials(a, d, DE, z=None): + """ + Integration of nonlinear monomials with no specials. + + Explanation + =========== + + Given a nonlinear monomial t over k such that Sirr ({p in k[t] | p is + special, monic, and irreducible}) is empty, and f in k(t), returns g + elementary over k(t) and a Boolean b in {True, False} such that f - Dg is + in k if b == True, or f - Dg does not have an elementary integral over k(t) + if b == False. + + This function is applicable to all nonlinear extensions, but in the case + where it returns b == False, it will only have proven that the integral of + f - Dg is nonelementary if Sirr is empty. + + This function returns a Basic expression. + """ + # TODO: Integral from k? + # TODO: split out nonelementary integral + # XXX: a and d must be canceled, or this might not return correct results + z = z or Dummy("z") + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + g1, h, r = hermite_reduce(a, d, DE) + g2, b = residue_reduce(h[0], h[1], DE, z=z) + if not b: + return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z), b) + + # Because f has no specials, this should be a polynomial in t, or else + # there is a bug. + p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2, + DE, z).as_expr() + r[0].as_expr()/r[1].as_expr()).as_poly(DE.t) + q1, q2 = polynomial_reduce(p, DE) + + if q2.expr.has(DE.t): + b = False + else: + b = True + + ret = (cancel(g1[0].as_expr()/g1[1].as_expr() + q1.as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z)) + return (ret, b) + + +class NonElementaryIntegral(Integral): + """ + Represents a nonelementary Integral. + + Explanation + =========== + + If the result of integrate() is an instance of this class, it is + guaranteed to be nonelementary. Note that integrate() by default will try + to find any closed-form solution, even in terms of special functions which + may themselves not be elementary. To make integrate() only give + elementary solutions, or, in the cases where it can prove the integral to + be nonelementary, instances of this class, use integrate(risch=True). + In this case, integrate() may raise NotImplementedError if it cannot make + such a determination. + + integrate() uses the deterministic Risch algorithm to integrate elementary + functions or prove that they have no elementary integral. In some cases, + this algorithm can split an integral into an elementary and nonelementary + part, so that the result of integrate will be the sum of an elementary + expression and a NonElementaryIntegral. + + Examples + ======== + + >>> from sympy import integrate, exp, log, Integral + >>> from sympy.abc import x + + >>> a = integrate(exp(-x**2), x, risch=True) + >>> print(a) + Integral(exp(-x**2), x) + >>> type(a) + + + >>> expr = (2*log(x)**2 - log(x) - x**2)/(log(x)**3 - x**2*log(x)) + >>> b = integrate(expr, x, risch=True) + >>> print(b) + -log(-x + log(x))/2 + log(x + log(x))/2 + Integral(1/log(x), x) + >>> type(b.atoms(Integral).pop()) + + + """ + # TODO: This is useful in and of itself, because isinstance(result, + # NonElementaryIntegral) will tell if the integral has been proven to be + # elementary. But should we do more? Perhaps a no-op .doit() if + # elementary=True? Or maybe some information on why the integral is + # nonelementary. + pass + + +def risch_integrate(f, x, extension=None, handle_first='log', + separate_integral=False, rewrite_complex=None, + conds='piecewise'): + r""" + The Risch Integration Algorithm. + + Explanation + =========== + + Only transcendental functions are supported. Currently, only exponentials + and logarithms are supported, but support for trigonometric functions is + forthcoming. + + If this function returns an unevaluated Integral in the result, it means + that it has proven that integral to be nonelementary. Any errors will + result in raising NotImplementedError. The unevaluated Integral will be + an instance of NonElementaryIntegral, a subclass of Integral. + + handle_first may be either 'exp' or 'log'. This changes the order in + which the extension is built, and may result in a different (but + equivalent) solution (for an example of this, see issue 5109). It is also + possible that the integral may be computed with one but not the other, + because not all cases have been implemented yet. It defaults to 'log' so + that the outer extension is exponential when possible, because more of the + exponential case has been implemented. + + If ``separate_integral`` is ``True``, the result is returned as a tuple (ans, i), + where the integral is ans + i, ans is elementary, and i is either a + NonElementaryIntegral or 0. This useful if you want to try further + integrating the NonElementaryIntegral part using other algorithms to + possibly get a solution in terms of special functions. It is False by + default. + + Examples + ======== + + >>> from sympy.integrals.risch import risch_integrate + >>> from sympy import exp, log, pprint + >>> from sympy.abc import x + + First, we try integrating exp(-x**2). Except for a constant factor of + 2/sqrt(pi), this is the famous error function. + + >>> pprint(risch_integrate(exp(-x**2), x)) + / + | + | 2 + | -x + | e dx + | + / + + The unevaluated Integral in the result means that risch_integrate() has + proven that exp(-x**2) does not have an elementary anti-derivative. + + In many cases, risch_integrate() can split out the elementary + anti-derivative part from the nonelementary anti-derivative part. + For example, + + >>> pprint(risch_integrate((2*log(x)**2 - log(x) - x**2)/(log(x)**3 - + ... x**2*log(x)), x)) + / + | + log(-x + log(x)) log(x + log(x)) | 1 + - ---------------- + --------------- + | ------ dx + 2 2 | log(x) + | + / + + This means that it has proven that the integral of 1/log(x) is + nonelementary. This function is also known as the logarithmic integral, + and is often denoted as Li(x). + + risch_integrate() currently only accepts purely transcendental functions + with exponentials and logarithms, though note that this can include + nested exponentials and logarithms, as well as exponentials with bases + other than E. + + >>> pprint(risch_integrate(exp(x)*exp(exp(x)), x)) + / x\ + \e / + e + >>> pprint(risch_integrate(exp(exp(x)), x)) + / + | + | / x\ + | \e / + | e dx + | + / + + >>> pprint(risch_integrate(x*x**x*log(x) + x**x + x*x**x, x)) + x + x*x + >>> pprint(risch_integrate(x**x, x)) + / + | + | x + | x dx + | + / + + >>> pprint(risch_integrate(-1/(x*log(x)*log(log(x))**2), x)) + 1 + ----------- + log(log(x)) + + """ + f = S(f) + + DE = extension or DifferentialExtension(f, x, handle_first=handle_first, + dummy=True, rewrite_complex=rewrite_complex) + fa, fd = DE.fa, DE.fd + + result = S.Zero + for case in reversed(DE.cases): + if not fa.expr.has(DE.t) and not fd.expr.has(DE.t) and not case == 'base': + DE.decrement_level() + fa, fd = frac_in((fa, fd), DE.t) + continue + + fa, fd = fa.cancel(fd, include=True) + if case == 'exp': + ans, i, b = integrate_hyperexponential(fa, fd, DE, conds=conds) + elif case == 'primitive': + ans, i, b = integrate_primitive(fa, fd, DE) + elif case == 'base': + # XXX: We can't call ratint() directly here because it doesn't + # handle polynomials correctly. + ans = integrate(fa.as_expr()/fd.as_expr(), DE.x, risch=False) + b = False + i = S.Zero + else: + raise NotImplementedError("Only exponential and logarithmic " + "extensions are currently supported.") + + result += ans + if b: + DE.decrement_level() + fa, fd = frac_in(i, DE.t) + else: + result = result.subs(DE.backsubs) + if not i.is_zero: + i = NonElementaryIntegral(i.function.subs(DE.backsubs),i.limits) + if not separate_integral: + result += i + return result + else: + + if isinstance(i, NonElementaryIntegral): + return (result, i) + else: + return (result, 0) diff --git a/phivenv/Lib/site-packages/sympy/integrals/singularityfunctions.py b/phivenv/Lib/site-packages/sympy/integrals/singularityfunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..3e33d0542c45b67b193f17e00c25837f3a82109a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/singularityfunctions.py @@ -0,0 +1,63 @@ +from sympy.functions import SingularityFunction, DiracDelta +from sympy.integrals import integrate + + +def singularityintegrate(f, x): + """ + This function handles the indefinite integrations of Singularity functions. + The ``integrate`` function calls this function internally whenever an + instance of SingularityFunction is passed as argument. + + Explanation + =========== + + The idea for integration is the following: + + - If we are dealing with a SingularityFunction expression, + i.e. ``SingularityFunction(x, a, n)``, we just return + ``SingularityFunction(x, a, n + 1)/(n + 1)`` if ``n >= 0`` and + ``SingularityFunction(x, a, n + 1)`` if ``n < 0``. + + - If the node is a multiplication or power node having a + SingularityFunction term we rewrite the whole expression in terms of + Heaviside and DiracDelta and then integrate the output. Lastly, we + rewrite the output of integration back in terms of SingularityFunction. + + - If none of the above case arises, we return None. + + Examples + ======== + + >>> from sympy.integrals.singularityfunctions import singularityintegrate + >>> from sympy import SingularityFunction, symbols, Function + >>> x, a, n, y = symbols('x a n y') + >>> f = Function('f') + >>> singularityintegrate(SingularityFunction(x, a, 3), x) + SingularityFunction(x, a, 4)/4 + >>> singularityintegrate(5*SingularityFunction(x, 5, -2), x) + 5*SingularityFunction(x, 5, -1) + >>> singularityintegrate(6*SingularityFunction(x, 5, -1), x) + 6*SingularityFunction(x, 5, 0) + >>> singularityintegrate(x*SingularityFunction(x, 0, -1), x) + 0 + >>> singularityintegrate(SingularityFunction(x, 1, -1) * f(x), x) + f(1)*SingularityFunction(x, 1, 0) + + """ + + if not f.has(SingularityFunction): + return None + + if isinstance(f, SingularityFunction): + x, a, n = f.args + if n.is_positive or n.is_zero: + return SingularityFunction(x, a, n + 1)/(n + 1) + elif n in (-1, -2, -3, -4): + return SingularityFunction(x, a, n + 1) + + if f.is_Mul or f.is_Pow: + + expr = f.rewrite(DiracDelta) + expr = integrate(expr, x) + return expr.rewrite(SingularityFunction) + return None diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__init__.py b/phivenv/Lib/site-packages/sympy/integrals/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8fc775b9f9760737c916c7f75d3780dc921222e Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_deltafunctions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_deltafunctions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af3500e6ccd649367e8f73aa41d7a15b6a693018 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_deltafunctions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_failing_integrals.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_failing_integrals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f67a0b54d4c6bdbc56a5747409075365534c7d8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_failing_integrals.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_heurisch.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_heurisch.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83d0db5036e961d9515fc293fa2aa11b9acbb6b2 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_heurisch.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_integrals.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_integrals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..794cb6cb5396c9fed1135ca83c49c59bb8d4f214 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_integrals.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_intpoly.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_intpoly.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4065c8d501ba69256bb562fbf8a81db1ff98ff27 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_intpoly.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_laplace.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_laplace.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3001659642da6511d55a2419cd28920231aeccf1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_laplace.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_lineintegrals.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_lineintegrals.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..168778073582859cd123382553744f428324d554 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_lineintegrals.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_manual.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_manual.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccc79a61d16bd8bbcd66a1c78f3fcd4376cbb6b4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_manual.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_meijerint.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_meijerint.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..778676729af2cc4591413110988c450b41334722 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_meijerint.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_prde.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_prde.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8daf56107c6b02c6f4a32247d811669ac3209260 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_prde.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_quadrature.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_quadrature.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7cff9fec57b7882a4c14eef3f943204dc0ac340 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_quadrature.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_rationaltools.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_rationaltools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b754d5ca6ab108f7e05b636f60006b5b706f3706 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_rationaltools.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_rde.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_rde.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b82cd3594cbbbe61b96b9c7bfe611ef0a49647ea Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_rde.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_risch.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_risch.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a1a8f4967eaa0aa158d2c48ef333eed1ea077e4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_risch.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_singularityfunctions.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_singularityfunctions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1a6911a6bd9c459385814fb1ae0168353ae5be5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_singularityfunctions.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_transforms.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_transforms.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b503e5df137263bf8eac0314006c0dcebe99771 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_transforms.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_trigonometry.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_trigonometry.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49348cac825169f0f9a9d0e777062a7ee81bbeb3 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/integrals/tests/__pycache__/test_trigonometry.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_deltafunctions.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_deltafunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..d4fd567349b50f795e08d583fd08db67b1596577 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_deltafunctions.py @@ -0,0 +1,79 @@ +from sympy.core.function import Function +from sympy.core.numbers import (Rational, pi) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.delta_functions import (DiracDelta, Heaviside) +from sympy.integrals.deltafunctions import change_mul, deltaintegrate + +f = Function("f") +x_1, x_2, x, y, z = symbols("x_1 x_2 x y z") + + +def test_change_mul(): + assert change_mul(x, x) == (None, None) + assert change_mul(x*y, x) == (None, None) + assert change_mul(x*y*DiracDelta(x), x) == (DiracDelta(x), x*y) + assert change_mul(x*y*DiracDelta(x)*DiracDelta(y), x) == \ + (DiracDelta(x), x*y*DiracDelta(y)) + assert change_mul(DiracDelta(x)**2, x) == \ + (DiracDelta(x), DiracDelta(x)) + assert change_mul(y*DiracDelta(x)**2, x) == \ + (DiracDelta(x), y*DiracDelta(x)) + + +def test_deltaintegrate(): + assert deltaintegrate(x, x) is None + assert deltaintegrate(x + DiracDelta(x), x) is None + assert deltaintegrate(DiracDelta(x, 0), x) == Heaviside(x) + for n in range(10): + assert deltaintegrate(DiracDelta(x, n + 1), x) == DiracDelta(x, n) + assert deltaintegrate(DiracDelta(x), x) == Heaviside(x) + assert deltaintegrate(DiracDelta(-x), x) == Heaviside(x) + assert deltaintegrate(DiracDelta(x - y), x) == Heaviside(x - y) + assert deltaintegrate(DiracDelta(y - x), x) == Heaviside(x - y) + + assert deltaintegrate(x*DiracDelta(x), x) == 0 + assert deltaintegrate((x - y)*DiracDelta(x - y), x) == 0 + + assert deltaintegrate(DiracDelta(x)**2, x) == DiracDelta(0)*Heaviside(x) + assert deltaintegrate(y*DiracDelta(x)**2, x) == \ + y*DiracDelta(0)*Heaviside(x) + assert deltaintegrate(DiracDelta(x, 1), x) == DiracDelta(x, 0) + assert deltaintegrate(y*DiracDelta(x, 1), x) == y*DiracDelta(x, 0) + assert deltaintegrate(DiracDelta(x, 1)**2, x) == -DiracDelta(0, 2)*Heaviside(x) + assert deltaintegrate(y*DiracDelta(x, 1)**2, x) == -y*DiracDelta(0, 2)*Heaviside(x) + + + assert deltaintegrate(DiracDelta(x) * f(x), x) == f(0) * Heaviside(x) + assert deltaintegrate(DiracDelta(-x) * f(x), x) == f(0) * Heaviside(x) + assert deltaintegrate(DiracDelta(x - 1) * f(x), x) == f(1) * Heaviside(x - 1) + assert deltaintegrate(DiracDelta(1 - x) * f(x), x) == f(1) * Heaviside(x - 1) + assert deltaintegrate(DiracDelta(x**2 + x - 2), x) == \ + Heaviside(x - 1)/3 + Heaviside(x + 2)/3 + + p = cos(x)*(DiracDelta(x) + DiracDelta(x**2 - 1))*sin(x)*(x - pi) + assert deltaintegrate(p, x) - (-pi*(cos(1)*Heaviside(-1 + x)*sin(1)/2 - \ + cos(1)*Heaviside(1 + x)*sin(1)/2) + \ + cos(1)*Heaviside(1 + x)*sin(1)/2 + \ + cos(1)*Heaviside(-1 + x)*sin(1)/2) == 0 + + p = x_2*DiracDelta(x - x_2)*DiracDelta(x_2 - x_1) + assert deltaintegrate(p, x_2) == x*DiracDelta(x - x_1)*Heaviside(x_2 - x) + + p = x*y**2*z*DiracDelta(y - x)*DiracDelta(y - z)*DiracDelta(x - z) + assert deltaintegrate(p, y) == x**3*z*DiracDelta(x - z)**2*Heaviside(y - x) + assert deltaintegrate((x + 1)*DiracDelta(2*x), x) == S.Half * Heaviside(x) + assert deltaintegrate((x + 1)*DiracDelta(x*Rational(2, 3) + Rational(4, 9)), x) == \ + S.Half * Heaviside(x + Rational(2, 3)) + + a, b, c = symbols('a b c', commutative=False) + assert deltaintegrate(DiracDelta(x - y)*f(x - b)*f(x - a), x) == \ + f(y - b)*f(y - a)*Heaviside(x - y) + + p = f(x - a)*DiracDelta(x - y)*f(x - c)*f(x - b) + assert deltaintegrate(p, x) == f(y - a)*f(y - c)*f(y - b)*Heaviside(x - y) + + p = DiracDelta(x - z)*f(x - b)*f(x - a)*DiracDelta(x - y) + assert deltaintegrate(p, x) == DiracDelta(y - z)*f(y - b)*f(y - a) * \ + Heaviside(x - y) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_failing_integrals.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_failing_integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..9bb434cf0009cd96ad2b7882d109b7fbe23193c2 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_failing_integrals.py @@ -0,0 +1,277 @@ +# A collection of failing integrals from the issues. + +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (sech, sinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, atan, cos, sin, tan) +from sympy.functions.special.delta_functions import DiracDelta +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import (Integral, integrate) +from sympy.simplify.fu import fu + + +from sympy.testing.pytest import XFAIL, slow, tooslow + +from sympy.abc import x, k, c, y, b, h, a, m, z, n, t + + +@tooslow +@XFAIL +def test_issue_3880(): + # integrate_hyperexponential(Poly(t*2*(1 - t0**2)*t0*(x**3 + x**2), t), Poly((1 + t0**2)**2*2*(x**2 + x + 1), t), [Poly(1, x), Poly(1 + t0**2, t0), Poly(t, t)], [x, t0, t], [exp, tan]) + assert not integrate(exp(x)*cos(2*x)*sin(2*x) * (x**3 + x**2)/(2*(x**2 + x + 1)), x).has(Integral) + + +def test_issue_4212_real(): + xr = symbols('xr', real=True) + negabsx = Piecewise((-xr, xr < 0), (xr, True)) + assert integrate(sign(xr), xr) == negabsx + + +@XFAIL +def test_issue_4212(): + # XXX: Maybe this should be expected to fail without real assumptions on x. + # As a complex function sign(x) is not analytic and so there is no complex + # function whose complex derivative is sign(x). With real assumptions this + # works (see test_issue_4212_real above). + assert not integrate(sign(x), x).has(Integral) + + +def test_issue_4511(): + # This works, but gives a slightly over-complicated answer. + f = integrate(cos(x)**2 / (1 - sin(x)), x) + assert fu(f) == x - cos(x) - 1 + assert f == ((x*tan(x/2)**2 + x - 2)/(tan(x/2)**2 + 1)).expand() + + +def test_integrate_DiracDelta_no_meijerg(): + assert integrate(integrate(integrate( + DiracDelta(x - y - z), (z, 0, oo)), (y, 0, 1), meijerg=False), (x, 0, 1)) == S.Half + + +@XFAIL +def test_integrate_DiracDelta_fails(): + # issue 6427 + # works without meijerg. See test_integrate_DiracDelta_no_meijerg above. + assert integrate(integrate(integrate( + DiracDelta(x - y - z), (z, 0, oo)), (y, 0, 1)), (x, 0, 1)) == S.Half + + +@XFAIL +@slow +def test_issue_4525(): + # Warning: takes a long time + assert not integrate((x**m * (1 - x)**n * (a + b*x + c*x**2))/(1 + x**2), (x, 0, 1)).has(Integral) + + +@XFAIL +@tooslow +def test_issue_4540(): + # Note, this integral is probably nonelementary + assert not integrate( + (sin(1/x) - x*exp(x)) / + ((-sin(1/x) + x*exp(x))*x + x*sin(1/x)), x).has(Integral) + + +@XFAIL +@slow +def test_issue_4891(): + # Requires the hypergeometric function. + assert not integrate(cos(x)**y, x).has(Integral) + + +@XFAIL +@slow +def test_issue_1796a(): + assert not integrate(exp(2*b*x)*exp(-a*x**2), x).has(Integral) + + +@XFAIL +def test_issue_4895b(): + assert not integrate(exp(2*b*x)*exp(-a*x**2), (x, -oo, 0)).has(Integral) + + +@XFAIL +def test_issue_4895c(): + assert not integrate(exp(2*b*x)*exp(-a*x**2), (x, -oo, oo)).has(Integral) + + +@XFAIL +def test_issue_4895d(): + assert not integrate(exp(2*b*x)*exp(-a*x**2), (x, 0, oo)).has(Integral) + + +@XFAIL +@slow +def test_issue_4941(): + assert not integrate(sqrt(1 + sinh(x/20)**2), (x, -25, 25)).has(Integral) + + +@XFAIL +def test_issue_4992(): + # Nonelementary integral. Requires hypergeometric/Meijer-G handling. + assert not integrate(log(x) * x**(k - 1) * exp(-x) / gamma(k), (x, 0, oo)).has(Integral) + + +@XFAIL +def test_issue_16396a(): + i = integrate(1/(1+sqrt(tan(x))), (x, pi/3, pi/6)) + assert not i.has(Integral) + + +@XFAIL +def test_issue_16396b(): + i = integrate(x*sin(x)/(1+cos(x)**2), (x, 0, pi)) + assert not i.has(Integral) + + +@XFAIL +def test_issue_16046(): + assert integrate(exp(exp(I*x)), [x, 0, 2*pi]) == 2*pi + + +@XFAIL +def test_issue_15925a(): + assert not integrate(sqrt((1+sin(x))**2+(cos(x))**2), (x, -pi/2, pi/2)).has(Integral) + + +def test_issue_15925b(): + f = sqrt((-12*cos(x)**2*sin(x))**2+(12*cos(x)*sin(x)**2)**2) + assert integrate(f, (x, 0, pi/6)) == Rational(3, 2) + + +@XFAIL +def test_issue_15925b_manual(): + assert not integrate(sqrt((-12*cos(x)**2*sin(x))**2+(12*cos(x)*sin(x)**2)**2), + (x, 0, pi/6), manual=True).has(Integral) + + +@XFAIL +@tooslow +def test_issue_15227(): + i = integrate(log(1-x)*log((1+x)**2)/x, (x, 0, 1)) + assert not i.has(Integral) + # assert i == -5*zeta(3)/4 + + +@XFAIL +@slow +def test_issue_14716(): + i = integrate(log(x + 5)*cos(pi*x),(x, S.Half, 1)) + assert not i.has(Integral) + # Mathematica can not solve it either, but + # integrate(log(x + 5)*cos(pi*x),(x, S.Half, 1)).transform(x, y - 5).doit() + # works + # assert i == -log(Rational(11, 2))/pi - Si(pi*Rational(11, 2))/pi + Si(6*pi)/pi + + +@XFAIL +def test_issue_14709a(): + i = integrate(x*acos(1 - 2*x/h), (x, 0, h)) + assert not i.has(Integral) + # assert i == 5*h**2*pi/16 + + +@slow +@XFAIL +def test_issue_14398(): + assert not integrate(exp(x**2)*cos(x), x).has(Integral) + + +@XFAIL +def test_issue_14074(): + i = integrate(log(sin(x)), (x, 0, pi/2)) + assert not i.has(Integral) + # assert i == -pi*log(2)/2 + + +@XFAIL +@slow +def test_issue_14078b(): + i = integrate((atan(4*x)-atan(2*x))/x, (x, 0, oo)) + assert not i.has(Integral) + # assert i == pi*log(2)/2 + + +@XFAIL +def test_issue_13792(): + i = integrate(log(1/x) / (1 - x), (x, 0, 1)) + assert not i.has(Integral) + # assert i in [polylog(2, -exp_polar(I*pi)), pi**2/6] + + +@XFAIL +def test_issue_11845a(): + assert not integrate(exp(y - x**3), (x, 0, 1)).has(Integral) + + +@XFAIL +def test_issue_11845b(): + assert not integrate(exp(-y - x**3), (x, 0, 1)).has(Integral) + + +@XFAIL +def test_issue_11813(): + assert not integrate((a - x)**Rational(-1, 2)*x, (x, 0, a)).has(Integral) + + +@XFAIL +def test_issue_11254c(): + assert not integrate(sech(x)**2, (x, 0, 1)).has(Integral) + + +@XFAIL +def test_issue_10584(): + assert not integrate(sqrt(x**2 + 1/x**2), x).has(Integral) + + +@XFAIL +def test_issue_9101(): + assert not integrate(log(x + sqrt(x**2 + y**2 + z**2)), z).has(Integral) + + +@XFAIL +def test_issue_7147(): + assert not integrate(x/sqrt(a*x**2 + b*x + c)**3, x).has(Integral) + + +@XFAIL +def test_issue_7109(): + assert not integrate(sqrt(a**2/(a**2 - x**2)), x).has(Integral) + + +@XFAIL +def test_integrate_Piecewise_rational_over_reals(): + f = Piecewise( + (0, t - 478.515625*pi < 0), + (13.2075145209219*pi/(0.000871222*t + 0.995)**2, t - 478.515625*pi >= 0)) + + assert abs((integrate(f, (t, 0, oo)) - 15235.9375*pi).evalf()) <= 1e-7 + + +@XFAIL +def test_issue_4311_slow(): + # Not slow when bypassing heurish + assert not integrate(x*abs(9-x**2), x).has(Integral) + +@XFAIL +def test_issue_20370(): + a = symbols('a', positive=True) + assert integrate((1 + a * cos(x))**-1, (x, 0, 2 * pi)) == (2 * pi / sqrt(1 - a**2)) + + +@XFAIL +def test_polylog(): + # log(1/x)*log(x+1)-polylog(2, -x) + assert not integrate(log(1/x)/(x + 1), x).has(Integral) + + +@XFAIL +def test_polylog_manual(): + # Make sure _parts_rule does not go into an infinite loop here + assert not integrate(log(1/x)/(x + 1), x, manual=True).has(Integral) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_heurisch.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_heurisch.py new file mode 100644 index 0000000000000000000000000000000000000000..f02556dedd597721529cab47bf53609110e0ce2a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_heurisch.py @@ -0,0 +1,417 @@ +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Eq, Ne +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, cosh, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, tan) +from sympy.functions.special.bessel import (besselj, besselk, bessely, jn) +from sympy.functions.special.error_functions import erf +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import And +from sympy.matrices import Matrix +from sympy.simplify.ratsimp import ratsimp +from sympy.simplify.simplify import simplify +from sympy.integrals.heurisch import components, heurisch, heurisch_wrapper +from sympy.testing.pytest import XFAIL, slow +from sympy.integrals.integrals import integrate +from sympy import S + +x, y, z, nu = symbols('x,y,z,nu') +f = Function('f') + + +def test_components(): + assert components(x*y, x) == {x} + assert components(1/(x + y), x) == {x} + assert components(sin(x), x) == {sin(x), x} + assert components(sin(x)*sqrt(log(x)), x) == \ + {log(x), sin(x), sqrt(log(x)), x} + assert components(x*sin(exp(x)*y), x) == \ + {sin(y*exp(x)), x, exp(x)} + assert components(x**Rational(17, 54)/sqrt(sin(x)), x) == \ + {sin(x), x**Rational(1, 54), sqrt(sin(x)), x} + + assert components(f(x), x) == \ + {x, f(x)} + assert components(Derivative(f(x), x), x) == \ + {x, f(x), Derivative(f(x), x)} + assert components(f(x)*diff(f(x), x), x) == \ + {x, f(x), Derivative(f(x), x), Derivative(f(x), x)} + + +def test_issue_10680(): + assert isinstance(integrate(x**log(x**log(x**log(x))),x), Integral) + + +def test_issue_21166(): + assert integrate(sin(x/sqrt(abs(x))), (x, -1, 1)) == 0 + + +def test_heurisch_polynomials(): + assert heurisch(1, x) == x + assert heurisch(x, x) == x**2/2 + assert heurisch(x**17, x) == x**18/18 + # For coverage + assert heurisch_wrapper(y, x) == y*x + + +def test_heurisch_fractions(): + assert heurisch(1/x, x) == log(x) + assert heurisch(1/(2 + x), x) == log(x + 2) + assert heurisch(1/(x + sin(y)), x) == log(x + sin(y)) + + # Up to a constant, where C = pi*I*Rational(5, 12), Mathematica gives identical + # result in the first case. The difference is because SymPy changes + # signs of expressions without any care. + # XXX ^ ^ ^ is this still correct? + assert heurisch(5*x**5/( + 2*x**6 - 5), x) in [5*log(2*x**6 - 5) / 12, 5*log(-2*x**6 + 5) / 12] + assert heurisch(5*x**5/(2*x**6 + 5), x) == 5*log(2*x**6 + 5) / 12 + + assert heurisch(1/x**2, x) == -1/x + assert heurisch(-1/x**5, x) == 1/(4*x**4) + + +def test_heurisch_log(): + assert heurisch(log(x), x) == x*log(x) - x + assert heurisch(log(3*x), x) == -x + x*log(3) + x*log(x) + assert heurisch(log(x**2), x) in [x*log(x**2) - 2*x, 2*x*log(x) - 2*x] + + +def test_heurisch_exp(): + assert heurisch(exp(x), x) == exp(x) + assert heurisch(exp(-x), x) == -exp(-x) + assert heurisch(exp(17*x), x) == exp(17*x) / 17 + assert heurisch(x*exp(x), x) == x*exp(x) - exp(x) + assert heurisch(x*exp(x**2), x) == exp(x**2) / 2 + + assert heurisch(exp(-x**2), x) is None + + assert heurisch(2**x, x) == 2**x/log(2) + assert heurisch(x*2**x, x) == x*2**x/log(2) - 2**x*log(2)**(-2) + + assert heurisch(Integral(x**z*y, (y, 1, 2), (z, 2, 3)).function, x) == (x*x**z*y)/(z+1) + assert heurisch(Sum(x**z, (z, 1, 2)).function, z) == x**z/log(x) + + # https://github.com/sympy/sympy/issues/23707 + anti = -exp(z)/(sqrt(x - y)*exp(z*sqrt(x - y)) - exp(z*sqrt(x - y))) + assert heurisch(exp(z)*exp(-z*sqrt(x - y)), z) == anti + + +def test_heurisch_trigonometric(): + assert heurisch(sin(x), x) == -cos(x) + assert heurisch(pi*sin(x) + 1, x) == x - pi*cos(x) + + assert heurisch(cos(x), x) == sin(x) + assert heurisch(tan(x), x) in [ + log(1 + tan(x)**2)/2, + log(tan(x) + I) + I*x, + log(tan(x) - I) - I*x, + ] + + assert heurisch(sin(x)*sin(y), x) == -cos(x)*sin(y) + assert heurisch(sin(x)*sin(y), y) == -cos(y)*sin(x) + + # gives sin(x) in answer when run via setup.py and cos(x) when run via py.test + assert heurisch(sin(x)*cos(x), x) in [sin(x)**2 / 2, -cos(x)**2 / 2] + assert heurisch(cos(x)/sin(x), x) == log(sin(x)) + + assert heurisch(x*sin(7*x), x) == sin(7*x) / 49 - x*cos(7*x) / 7 + assert heurisch(1/pi/4 * x**2*cos(x), x) == 1/pi/4*(x**2*sin(x) - + 2*sin(x) + 2*x*cos(x)) + + assert heurisch(acos(x/4) * asin(x/4), x) == 2*x - (sqrt(16 - x**2))*asin(x/4) \ + + (sqrt(16 - x**2))*acos(x/4) + x*asin(x/4)*acos(x/4) + + assert heurisch(sin(x)/(cos(x)**2+1), x) == -atan(cos(x)) #fixes issue 13723 + assert heurisch(1/(cos(x)+2), x) == 2*sqrt(3)*atan(sqrt(3)*tan(x/2)/3)/3 + assert heurisch(2*sin(x)*cos(x)/(sin(x)**4 + 1), x) == atan(sqrt(2)*sin(x) + - 1) - atan(sqrt(2)*sin(x) + 1) + + assert heurisch(1/cosh(x), x) == 2*atan(tanh(x/2)) + + +def test_heurisch_hyperbolic(): + assert heurisch(sinh(x), x) == cosh(x) + assert heurisch(cosh(x), x) == sinh(x) + + assert heurisch(x*sinh(x), x) == x*cosh(x) - sinh(x) + assert heurisch(x*cosh(x), x) == x*sinh(x) - cosh(x) + + assert heurisch( + x*asinh(x/2), x) == x**2*asinh(x/2)/2 + asinh(x/2) - x*sqrt(4 + x**2)/4 + + +def test_heurisch_mixed(): + assert heurisch(sin(x)*exp(x), x) == exp(x)*sin(x)/2 - exp(x)*cos(x)/2 + assert heurisch(sin(x/sqrt(-x)), x) == 2*x*cos(x/sqrt(-x))/sqrt(-x) - 2*sin(x/sqrt(-x)) + + +def test_heurisch_radicals(): + assert heurisch(1/sqrt(x), x) == 2*sqrt(x) + assert heurisch(1/sqrt(x)**3, x) == -2/sqrt(x) + assert heurisch(sqrt(x)**3, x) == 2*sqrt(x)**5/5 + + assert heurisch(sin(x)*sqrt(cos(x)), x) == -2*sqrt(cos(x))**3/3 + y = Symbol('y') + assert heurisch(sin(y*sqrt(x)), x) == 2/y**2*sin(y*sqrt(x)) - \ + 2*sqrt(x)*cos(y*sqrt(x))/y + assert heurisch_wrapper(sin(y*sqrt(x)), x) == Piecewise( + (-2*sqrt(x)*cos(sqrt(x)*y)/y + 2*sin(sqrt(x)*y)/y**2, Ne(y, 0)), + (0, True)) + y = Symbol('y', positive=True) + assert heurisch_wrapper(sin(y*sqrt(x)), x) == 2/y**2*sin(y*sqrt(x)) - \ + 2*sqrt(x)*cos(y*sqrt(x))/y + + +def test_heurisch_special(): + assert heurisch(erf(x), x) == x*erf(x) + exp(-x**2)/sqrt(pi) + assert heurisch(exp(-x**2)*erf(x), x) == sqrt(pi)*erf(x)**2 / 4 + + +def test_heurisch_symbolic_coeffs(): + assert heurisch(1/(x + y), x) == log(x + y) + assert heurisch(1/(x + sqrt(2)), x) == log(x + sqrt(2)) + assert simplify(diff(heurisch(log(x + y + z), y), y)) == log(x + y + z) + + +def test_heurisch_symbolic_coeffs_1130(): + y = Symbol('y') + assert heurisch_wrapper(1/(x**2 + y), x) == Piecewise( + (log(x - sqrt(-y))/(2*sqrt(-y)) - log(x + sqrt(-y))/(2*sqrt(-y)), + Ne(y, 0)), (-1/x, True)) + y = Symbol('y', positive=True) + assert heurisch_wrapper(1/(x**2 + y), x) == (atan(x/sqrt(y))/sqrt(y)) + + +def test_heurisch_hacking(): + assert heurisch(sqrt(1 + 7*x**2), x, hints=[]) == \ + x*sqrt(1 + 7*x**2)/2 + sqrt(7)*asinh(sqrt(7)*x)/14 + assert heurisch(sqrt(1 - 7*x**2), x, hints=[]) == \ + x*sqrt(1 - 7*x**2)/2 + sqrt(7)*asin(sqrt(7)*x)/14 + + assert heurisch(1/sqrt(1 + 7*x**2), x, hints=[]) == \ + sqrt(7)*asinh(sqrt(7)*x)/7 + assert heurisch(1/sqrt(1 - 7*x**2), x, hints=[]) == \ + sqrt(7)*asin(sqrt(7)*x)/7 + + assert heurisch(exp(-7*x**2), x, hints=[]) == \ + sqrt(7*pi)*erf(sqrt(7)*x)/14 + + assert heurisch(1/sqrt(9 - 4*x**2), x, hints=[]) == \ + asin(x*Rational(2, 3))/2 + + assert heurisch(1/sqrt(9 + 4*x**2), x, hints=[]) == \ + asinh(x*Rational(2, 3))/2 + + assert heurisch(1/sqrt(3*x**2-4), x, hints=[]) == \ + sqrt(3)*log(3*x + sqrt(3)*sqrt(3*x**2 - 4))/3 + + +def test_heurisch_function(): + assert heurisch(f(x), x) is None + +@XFAIL +def test_heurisch_function_derivative(): + # TODO: it looks like this used to work just by coincindence and + # thanks to sloppy implementation. Investigate why this used to + # work at all and if support for this can be restored. + + df = diff(f(x), x) + + assert heurisch(f(x)*df, x) == f(x)**2/2 + assert heurisch(f(x)**2*df, x) == f(x)**3/3 + assert heurisch(df/f(x), x) == log(f(x)) + + +def test_heurisch_wrapper(): + f = 1/(y + x) + assert heurisch_wrapper(f, x) == log(x + y) + f = 1/(y - x) + assert heurisch_wrapper(f, x) == -log(x - y) + f = 1/((y - x)*(y + x)) + assert heurisch_wrapper(f, x) == Piecewise( + (-log(x - y)/(2*y) + log(x + y)/(2*y), Ne(y, 0)), (1/x, True)) + # issue 6926 + f = sqrt(x**2/((y - x)*(y + x))) + assert heurisch_wrapper(f, x) == x*sqrt(-x**2/(x**2 - y**2)) \ + - y**2*sqrt(-x**2/(x**2 - y**2))/x + + +def test_issue_3609(): + assert heurisch(1/(x * (1 + log(x)**2)), x) == atan(log(x)) + +### These are examples from the Poor Man's Integrator +### http://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/examples/ + + +def test_pmint_rat(): + # TODO: heurisch() is off by a constant: -3/4. Possibly different permutation + # would give the optimal result? + + def drop_const(expr, x): + if expr.is_Add: + return Add(*[ arg for arg in expr.args if arg.has(x) ]) + else: + return expr + + f = (x**7 - 24*x**4 - 4*x**2 + 8*x - 8)/(x**8 + 6*x**6 + 12*x**4 + 8*x**2) + g = (4 + 8*x**2 + 6*x + 3*x**3)/(x**5 + 4*x**3 + 4*x) + log(x) + + assert drop_const(ratsimp(heurisch(f, x)), x) == g + + +def test_pmint_trig(): + f = (x - tan(x)) / tan(x)**2 + tan(x) + g = -x**2/2 - x/tan(x) + log(tan(x)**2 + 1)/2 + + assert heurisch(f, x) == g + + +def test_pmint_logexp(): + f = (1 + x + x*exp(x))*(x + log(x) + exp(x) - 1)/(x + log(x) + exp(x))**2/x + g = log(x + exp(x) + log(x)) + 1/(x + exp(x) + log(x)) + + assert ratsimp(heurisch(f, x)) == g + + +def test_pmint_erf(): + f = exp(-x**2)*erf(x)/(erf(x)**3 - erf(x)**2 - erf(x) + 1) + g = sqrt(pi)*log(erf(x) - 1)/8 - sqrt(pi)*log(erf(x) + 1)/8 - sqrt(pi)/(4*erf(x) - 4) + + assert ratsimp(heurisch(f, x)) == g + + +def test_pmint_LambertW(): + f = LambertW(x) + g = x*LambertW(x) - x + x/LambertW(x) + + assert heurisch(f, x) == g + + +def test_pmint_besselj(): + f = besselj(nu + 1, x)/besselj(nu, x) + g = nu*log(x) - log(besselj(nu, x)) + + assert heurisch(f, x) == g + + f = (nu*besselj(nu, x) - x*besselj(nu + 1, x))/x + g = besselj(nu, x) + + assert heurisch(f, x) == g + + f = jn(nu + 1, x)/jn(nu, x) + g = nu*log(x) - log(jn(nu, x)) + + assert heurisch(f, x) == g + + +@slow +def test_pmint_bessel_products(): + f = x*besselj(nu, x)*bessely(nu, 2*x) + g = -2*x*besselj(nu, x)*bessely(nu - 1, 2*x)/3 + x*besselj(nu - 1, x)*bessely(nu, 2*x)/3 + + assert heurisch(f, x) == g + + f = x*besselj(nu, x)*besselk(nu, 2*x) + g = -2*x*besselj(nu, x)*besselk(nu - 1, 2*x)/5 - x*besselj(nu - 1, x)*besselk(nu, 2*x)/5 + + assert heurisch(f, x) == g + + +def test_pmint_WrightOmega(): + def omega(x): + return LambertW(exp(x)) + + f = (1 + omega(x) * (2 + cos(omega(x)) * (x + omega(x))))/(1 + omega(x))/(x + omega(x)) + g = log(x + LambertW(exp(x))) + sin(LambertW(exp(x))) + + assert heurisch(f, x) == g + + +def test_RR(): + # Make sure the algorithm does the right thing if the ring is RR. See + # issue 8685. + assert heurisch(sqrt(1 + 0.25*x**2), x, hints=[]) == \ + 0.5*x*sqrt(0.25*x**2 + 1) + 1.0*asinh(0.5*x) + +# TODO: convert the rest of PMINT tests: +# Airy functions +# f = (x - AiryAi(x)*AiryAi(1, x)) / (x**2 - AiryAi(x)**2) +# g = Rational(1,2)*ln(x + AiryAi(x)) + Rational(1,2)*ln(x - AiryAi(x)) +# f = x**2 * AiryAi(x) +# g = -AiryAi(x) + AiryAi(1, x)*x +# Whittaker functions +# f = WhittakerW(mu + 1, nu, x) / (WhittakerW(mu, nu, x) * x) +# g = x/2 - mu*ln(x) - ln(WhittakerW(mu, nu, x)) + + +def test_issue_22527(): + t, R = symbols(r't R') + z = Function('z')(t) + def f(x): + return x/sqrt(R**2 - x**2) + Uz = integrate(f(z), z) + Ut = integrate(f(t), t) + assert Ut == Uz.subs(z, t) + + +def test_heurisch_complex_erf_issue_26338(): + r = symbols('r', real=True) + a = sqrt(pi)*erf((1 + I)/2)/2 + assert integrate(exp(-I*r**2/2), (r, 0, 1)) == a - I*a + + a = exp(-x**2/(2*(2 - I)**2)) + assert heurisch(a, x, hints=[]) is None # None, not a wrong soln + a = exp(-r**2/(2*(2 - I)**2)) + assert heurisch(a, r, hints=[]) is None + a = sqrt(pi)*erf((1 + I)/2)/2 + assert integrate(exp(-I*x**2/2), (x, 0, 1)) == a - I*a + + +def test_issue_15498(): + Z0 = Function('Z0') + k01, k10, t, s= symbols('k01 k10 t s', real=True, positive=True) + m = Matrix([[exp(-k10*t)]]) + _83 = Rational(83, 100) # 0.83 works, too + [a, b, c, d, e, f, g] = [100, 0.5, _83, 50, 0.6, 2, 120] + AIF_btf = a*(d*e*(1 - exp(-(t - b)/e)) + f*g*(1 - exp(-(t - b)/g))) + AIF_atf = a*(d*e*exp(-(t - b)/e)*(exp((c - b)/e) - 1 + ) + f*g*exp(-(t - b)/g)*(exp((c - b)/g) - 1)) + AIF_sym = Piecewise((0, t < b), (AIF_btf, And(b <= t, t < c)), (AIF_atf, c <= t)) + aif_eq = Eq(Z0(t), AIF_sym) + f_vec = Matrix([[k01*Z0(t)]]) + integrand = m*m.subs(t, s)**-1*f_vec.subs(aif_eq.lhs, aif_eq.rhs).subs(t, s) + solution = integrate(integrand[0], (s, 0, t)) + assert solution is not None # does not hang and takes less than 10 s + + +@slow +def test_heurisch_issue_26930(): + integrand = x**Rational(4, 3)*log(x) + anti = 3*x**(S(7)/3)*log(x)/7 - 9*x**(S(7)/3)/49 + assert heurisch(integrand, x) == anti + assert integrate(integrand, x) == anti + assert integrate(integrand, (x, 0, 1)) == -S(9)/49 + + +def test_heurisch_issue_26922(): + + a, b, x = symbols("a, b, x", real=True, positive=True) + C = symbols("C", real=True) + i1 = -C*x*exp(-a*x**2 - sqrt(b)*x) + i2 = C*x*exp(-a*x**2 + sqrt(b)*x) + i = Integral(i1, x) + Integral(i2, x) + res = ( + -C*exp(-a*x**2)*exp(sqrt(b)*x)/(2*a) + + C*exp(-a*x**2)*exp(-sqrt(b)*x)/(2*a) + + sqrt(pi)*C*sqrt(b)*exp(b/(4*a))*erf(sqrt(a)*x - sqrt(b)/(2*sqrt(a)))/(4*a**(S(3)/2)) + + sqrt(pi)*C*sqrt(b)*exp(b/(4*a))*erf(sqrt(a)*x + sqrt(b)/(2*sqrt(a)))/(4*a**(S(3)/2)) + ) + + assert i.doit(heurisch=False).expand() == res diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_integrals.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..41e1ef3aa36334189f14cf734ac2ad26d001b506 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_integrals.py @@ -0,0 +1,2187 @@ +import math +from sympy.concrete.summations import (Sum, summation) +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.function import (Derivative, Function, Lambda, diff) +from sympy.core import EulerGamma +from sympy.core.numbers import (E, I, Rational, nan, oo, pi, zoo, all_close) +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.elementary.complexes import (Abs, im, polar_lift, re, sign) +from sympy.functions.elementary.exponential import (LambertW, exp, exp_polar, log) +from sympy.functions.elementary.hyperbolic import (acosh, asinh, cosh, coth, csch, sinh, tanh, sech) +from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, sinc, tan, sec) +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.error_functions import (Ci, Ei, Si, erf, erfc, erfi, fresnelc, li) +from sympy.functions.special.gamma_functions import (gamma, polygamma) +from sympy.functions.special.hyper import (hyper, meijerg) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.functions.special.zeta_functions import lerchphi +from sympy.integrals.integrals import integrate +from sympy.logic.boolalg import And +from sympy.matrices.dense import Matrix +from sympy.polys.polytools import (Poly, factor) +from sympy.printing.str import sstr +from sympy.series.order import O +from sympy.sets.sets import Interval +from sympy.simplify.gammasimp import gammasimp +from sympy.simplify.simplify import simplify +from sympy.simplify.trigsimp import trigsimp +from sympy.tensor.indexed import (Idx, IndexedBase) +from sympy.core.expr import unchanged +from sympy.functions.elementary.integers import floor +from sympy.integrals.integrals import Integral +from sympy.integrals.risch import NonElementaryIntegral +from sympy.physics import units +from sympy.testing.pytest import raises, slow, warns_deprecated_sympy, warns +from sympy.utilities.exceptions import SymPyDeprecationWarning +from sympy.core.random import verify_numerically + + +x, y, z, a, b, c, d, e, s, t, x_1, x_2 = symbols('x y z a b c d e s t x_1 x_2') +n = Symbol('n', integer=True) +f = Function('f') + + +def NS(e, n=15, **options): + return sstr(sympify(e).evalf(n, **options), full_prec=True) + + +def test_poly_deprecated(): + p = Poly(2*x, x) + assert p.integrate(x) == Poly(x**2, x, domain='QQ') + # The stacklevel is based on Integral(Poly) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + integrate(p, x) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + Integral(p, (x,)) + + +@slow +def test_principal_value(): + g = 1 / x + assert Integral(g, (x, -oo, oo)).principal_value() == 0 + assert Integral(g, (y, -oo, oo)).principal_value() == oo * sign(1 / x) + raises(ValueError, lambda: Integral(g, (x)).principal_value()) + raises(ValueError, lambda: Integral(g).principal_value()) + + l = 1 / ((x ** 3) - 1) + assert Integral(l, (x, -oo, oo)).principal_value().together() == -sqrt(3)*pi/3 + raises(ValueError, lambda: Integral(l, (x, -oo, 1)).principal_value()) + + d = 1 / (x ** 2 - 1) + assert Integral(d, (x, -oo, oo)).principal_value() == 0 + assert Integral(d, (x, -2, 2)).principal_value() == -log(3) + + v = x / (x ** 2 - 1) + assert Integral(v, (x, -oo, oo)).principal_value() == 0 + assert Integral(v, (x, -2, 2)).principal_value() == 0 + + s = x ** 2 / (x ** 2 - 1) + assert Integral(s, (x, -oo, oo)).principal_value() is oo + assert Integral(s, (x, -2, 2)).principal_value() == -log(3) + 4 + + f = 1 / ((x ** 2 - 1) * (1 + x ** 2)) + assert Integral(f, (x, -oo, oo)).principal_value() == -pi / 2 + assert Integral(f, (x, -2, 2)).principal_value() == -atan(2) - log(3) / 2 + + +def diff_test(i): + """Return the set of symbols, s, which were used in testing that + i.diff(s) agrees with i.doit().diff(s). If there is an error then + the assertion will fail, causing the test to fail.""" + syms = i.free_symbols + for s in syms: + assert (i.diff(s).doit() - i.doit().diff(s)).expand() == 0 + return syms + + +def test_improper_integral(): + assert integrate(log(x), (x, 0, 1)) == -1 + assert integrate(x**(-2), (x, 1, oo)) == 1 + assert integrate(1/(1 + exp(x)), (x, 0, oo)) == log(2) + + +def test_constructor(): + # this is shared by Sum, so testing Integral's constructor + # is equivalent to testing Sum's + s1 = Integral(n, n) + assert s1.limits == (Tuple(n),) + s2 = Integral(n, (n,)) + assert s2.limits == (Tuple(n),) + s3 = Integral(Sum(x, (x, 1, y))) + assert s3.limits == (Tuple(y),) + s4 = Integral(n, Tuple(n,)) + assert s4.limits == (Tuple(n),) + + s5 = Integral(n, (n, Interval(1, 2))) + assert s5.limits == (Tuple(n, 1, 2),) + + # Testing constructor with inequalities: + s6 = Integral(n, n > 10) + assert s6.limits == (Tuple(n, 10, oo),) + s7 = Integral(n, (n > 2) & (n < 5)) + assert s7.limits == (Tuple(n, 2, 5),) + + +def test_basics(): + + assert Integral(0, x) != 0 + assert Integral(x, (x, 1, 1)) != 0 + assert Integral(oo, x) != oo + assert Integral(S.NaN, x) is S.NaN + + assert diff(Integral(y, y), x) == 0 + assert diff(Integral(x, (x, 0, 1)), x) == 0 + assert diff(Integral(x, x), x) == x + assert diff(Integral(t, (t, 0, x)), x) == x + + e = (t + 1)**2 + assert diff(integrate(e, (t, 0, x)), x) == \ + diff(Integral(e, (t, 0, x)), x).doit().expand() == \ + ((1 + x)**2).expand() + assert diff(integrate(e, (t, 0, x)), t) == \ + diff(Integral(e, (t, 0, x)), t) == 0 + assert diff(integrate(e, (t, 0, x)), a) == \ + diff(Integral(e, (t, 0, x)), a) == 0 + assert diff(integrate(e, t), a) == diff(Integral(e, t), a) == 0 + + assert integrate(e, (t, a, x)).diff(x) == \ + Integral(e, (t, a, x)).diff(x).doit().expand() + assert Integral(e, (t, a, x)).diff(x).doit() == ((1 + x)**2) + assert integrate(e, (t, x, a)).diff(x).doit() == (-(1 + x)**2).expand() + + assert integrate(t**2, (t, x, 2*x)).diff(x) == 7*x**2 + + assert Integral(x, x).atoms() == {x} + assert Integral(f(x), (x, 0, 1)).atoms() == {S.Zero, S.One, x} + + assert diff_test(Integral(x, (x, 3*y))) == {y} + assert diff_test(Integral(x, (a, 3*y))) == {x, y} + + assert integrate(x, (x, oo, oo)) == 0 #issue 8171 + assert integrate(x, (x, -oo, -oo)) == 0 + + # sum integral of terms + assert integrate(y + x + exp(x), x) == x*y + x**2/2 + exp(x) + + assert Integral(x).is_commutative + n = Symbol('n', commutative=False) + assert Integral(n + x, x).is_commutative is False + + +def test_diff_wrt(): + class Test(Expr): + _diff_wrt = True + is_commutative = True + + t = Test() + assert integrate(t + 1, t) == t**2/2 + t + assert integrate(t + 1, (t, 0, 1)) == Rational(3, 2) + + raises(ValueError, lambda: integrate(x + 1, x + 1)) + raises(ValueError, lambda: integrate(x + 1, (x + 1, 0, 1))) + + +def test_basics_multiple(): + assert diff_test(Integral(x, (x, 3*x, 5*y), (y, x, 2*x))) == {x} + assert diff_test(Integral(x, (x, 5*y), (y, x, 2*x))) == {x} + assert diff_test(Integral(x, (x, 5*y), (y, y, 2*x))) == {x, y} + assert diff_test(Integral(y, y, x)) == {x, y} + assert diff_test(Integral(y*x, x, y)) == {x, y} + assert diff_test(Integral(x + y, y, (y, 1, x))) == {x} + assert diff_test(Integral(x + y, (x, x, y), (y, y, x))) == {x, y} + + +def test_conjugate_transpose(): + A, B = symbols("A B", commutative=False) + + x = Symbol("x", complex=True) + p = Integral(A*B, (x,)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + x = Symbol("x", real=True) + p = Integral(A*B, (x,)) + assert p.adjoint().doit() == p.doit().adjoint() + assert p.conjugate().doit() == p.doit().conjugate() + assert p.transpose().doit() == p.doit().transpose() + + +def test_integration(): + assert integrate(0, (t, 0, x)) == 0 + assert integrate(3, (t, 0, x)) == 3*x + assert integrate(t, (t, 0, x)) == x**2/2 + assert integrate(3*t, (t, 0, x)) == 3*x**2/2 + assert integrate(3*t**2, (t, 0, x)) == x**3 + assert integrate(1/t, (t, 1, x)) == log(x) + assert integrate(-1/t**2, (t, 1, x)) == 1/x - 1 + assert integrate(t**2 + 5*t - 8, (t, 0, x)) == x**3/3 + 5*x**2/2 - 8*x + assert integrate(x**2, x) == x**3/3 + assert integrate((3*t*x)**5, x) == (3*t)**5 * x**6 / 6 + + b = Symbol("b") + c = Symbol("c") + assert integrate(a*t, (t, 0, x)) == a*x**2/2 + assert integrate(a*t**4, (t, 0, x)) == a*x**5/5 + assert integrate(a*t**2 + b*t + c, (t, 0, x)) == a*x**3/3 + b*x**2/2 + c*x + + +def test_multiple_integration(): + assert integrate((x**2)*(y**2), (x, 0, 1), (y, -1, 2)) == Rational(1) + assert integrate((y**2)*(x**2), x, y) == Rational(1, 9)*(x**3)*(y**3) + assert integrate(1/(x + 3)/(1 + x)**3, x) == \ + log(3 + x)*Rational(-1, 8) + log(1 + x)*Rational(1, 8) + x/(4 + 8*x + 4*x**2) + assert integrate(sin(x*y)*y, (x, 0, 1), (y, 0, 1)) == -sin(1) + 1 + + +def test_issue_3532(): + assert integrate(exp(-x), (x, 0, oo)) == 1 + + +def test_issue_3560(): + assert integrate(sqrt(x)**3, x) == 2*sqrt(x)**5/5 + assert integrate(sqrt(x), x) == 2*sqrt(x)**3/3 + assert integrate(1/sqrt(x)**3, x) == -2/sqrt(x) + + +def test_issue_18038(): + raises(AttributeError, lambda: integrate((x, x))) + + +def test_integrate_poly(): + p = Poly(x + x**2*y + y**3, x, y) + + # The stacklevel is based on Integral(Poly) + with warns_deprecated_sympy(): + qx = Integral(p, x) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + qx = integrate(p, x) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + qy = integrate(p, y) + + assert isinstance(qx, Poly) is True + assert isinstance(qy, Poly) is True + + assert qx.gens == (x, y) + assert qy.gens == (x, y) + + assert qx.as_expr() == x**2/2 + x**3*y/3 + x*y**3 + assert qy.as_expr() == x*y + x**2*y**2/2 + y**4/4 + + +def test_integrate_poly_definite(): + p = Poly(x + x**2*y + y**3, x, y) + + with warns_deprecated_sympy(): + Qx = Integral(p, (x, 0, 1)) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + Qx = integrate(p, (x, 0, 1)) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + Qy = integrate(p, (y, 0, pi)) + + assert isinstance(Qx, Poly) is True + assert isinstance(Qy, Poly) is True + + assert Qx.gens == (y,) + assert Qy.gens == (x,) + + assert Qx.as_expr() == S.Half + y/3 + y**3 + assert Qy.as_expr() == pi**4/4 + pi*x + pi**2*x**2/2 + + +def test_integrate_omit_var(): + y = Symbol('y') + + assert integrate(x) == x**2/2 + + raises(ValueError, lambda: integrate(2)) + raises(ValueError, lambda: integrate(x*y)) + + +def test_integrate_poly_accurately(): + y = Symbol('y') + assert integrate(x*sin(y), x) == x**2*sin(y)/2 + + # when passed to risch_norman, this will be a CPU hog, so this really + # checks, that integrated function is recognized as polynomial + assert integrate(x**1000*sin(y), x) == x**1001*sin(y)/1001 + + +def test_issue_3635(): + y = Symbol('y') + assert integrate(x**2, y) == x**2*y + assert integrate(x**2, (y, -1, 1)) == 2*x**2 + +# works in SymPy and py.test but hangs in `setup.py test` + + +def test_integrate_linearterm_pow(): + # check integrate((a*x+b)^c, x) -- issue 3499 + y = Symbol('y', positive=True) + # TODO: Remove conds='none' below, let the assumption take care of it. + assert integrate(x**y, x, conds='none') == x**(y + 1)/(y + 1) + assert integrate((exp(y)*x + 1/y)**(1 + sin(y)), x, conds='none') == \ + exp(-y)*(exp(y)*x + 1/y)**(2 + sin(y)) / (2 + sin(y)) + + +def test_issue_3618(): + assert integrate(pi*sqrt(x), x) == 2*pi*sqrt(x)**3/3 + assert integrate(pi*sqrt(x) + E*sqrt(x)**3, x) == \ + 2*pi*sqrt(x)**3/3 + 2*E *sqrt(x)**5/5 + + +def test_issue_3623(): + assert integrate(cos((n + 1)*x), x) == Piecewise( + (sin(x*(n + 1))/(n + 1), Ne(n + 1, 0)), (x, True)) + assert integrate(cos((n - 1)*x), x) == Piecewise( + (sin(x*(n - 1))/(n - 1), Ne(n - 1, 0)), (x, True)) + assert integrate(cos((n + 1)*x) + cos((n - 1)*x), x) == \ + Piecewise((sin(x*(n - 1))/(n - 1), Ne(n - 1, 0)), (x, True)) + \ + Piecewise((sin(x*(n + 1))/(n + 1), Ne(n + 1, 0)), (x, True)) + + +def test_issue_3664(): + n = Symbol('n', integer=True, nonzero=True) + assert integrate(-1./2 * x * sin(n * pi * x/2), [x, -2, 0]) == \ + 2.0*cos(pi*n)/(pi*n) + assert integrate(x * sin(n * pi * x/2) * Rational(-1, 2), [x, -2, 0]) == \ + 2*cos(pi*n)/(pi*n) + + +def test_issue_3679(): + # definite integration of rational functions gives wrong answers + assert NS(Integral(1/(x**2 - 8*x + 17), (x, 2, 4))) == '1.10714871779409' + + +def test_issue_3686(): # remove this when fresnel integrals are implemented + from sympy.core.function import expand_func + from sympy.functions.special.error_functions import fresnels + assert expand_func(integrate(sin(x**2), x)) == \ + sqrt(2)*sqrt(pi)*fresnels(sqrt(2)*x/sqrt(pi))/2 + + +def test_integrate_units(): + m = units.m + s = units.s + assert integrate(x * m/s, (x, 1*s, 5*s)) == 12*m*s + + +def test_transcendental_functions(): + assert integrate(LambertW(2*x), x) == \ + -x + x*LambertW(2*x) + x/LambertW(2*x) + + +def test_log_polylog(): + assert integrate(log(1 - x)/x, (x, 0, 1)) == -pi**2/6 + assert integrate(log(x)*(1 - x)**(-1), (x, 0, 1)) == -pi**2/6 + + +def test_issue_3740(): + f = 4*log(x) - 2*log(x)**2 + fid = diff(integrate(f, x), x) + assert abs(f.subs(x, 42).evalf() - fid.subs(x, 42).evalf()) < 1e-10 + + +def test_issue_3788(): + assert integrate(1/(1 + x**2), x) == atan(x) + + +def test_issue_3952(): + f = sin(x) + assert integrate(f, x) == -cos(x) + raises(ValueError, lambda: integrate(f, 2*x)) + + +def test_issue_4516(): + assert integrate(2**x - 2*x, x) == 2**x/log(2) - x**2 + + +def test_issue_7450(): + ans = integrate(exp(-(1 + I)*x), (x, 0, oo)) + assert re(ans) == S.Half and im(ans) == Rational(-1, 2) + + +def test_issue_8623(): + assert integrate((1 + cos(2*x)) / (3 - 2*cos(2*x)), (x, 0, pi)) == -pi/2 + sqrt(5)*pi/2 + assert integrate((1 + cos(2*x))/(3 - 2*cos(2*x))) == -x/2 + sqrt(5)*(atan(sqrt(5)*tan(x)) + \ + pi*floor((x - pi/2)/pi))/2 + + +def test_issue_9569(): + assert integrate(1 / (2 - cos(x)), (x, 0, pi)) == pi/sqrt(3) + assert integrate(1/(2 - cos(x))) == 2*sqrt(3)*(atan(sqrt(3)*tan(x/2)) + pi*floor((x/2 - pi/2)/pi))/3 + + +def test_issue_13733(): + s = Symbol('s', positive=True) + pz = exp(-(z - y)**2/(2*s*s))/sqrt(2*pi*s*s) + pzgx = integrate(pz, (z, x, oo)) + assert integrate(pzgx, (x, 0, oo)) == sqrt(2)*s*exp(-y**2/(2*s**2))/(2*sqrt(pi)) + \ + y*erf(sqrt(2)*y/(2*s))/2 + y/2 + + +def test_issue_13749(): + assert integrate(1 / (2 + cos(x)), (x, 0, pi)) == pi/sqrt(3) + assert integrate(1/(2 + cos(x))) == 2*sqrt(3)*(atan(sqrt(3)*tan(x/2)/3) + pi*floor((x/2 - pi/2)/pi))/3 + + +def test_issue_18133(): + assert integrate(exp(x)/(1 + x)**2, x) == NonElementaryIntegral(exp(x)/(x + 1)**2, x) + + +def test_issue_21741(): + a = 4e6 + b = 2.5e-7 + r = Piecewise((b*I*exp(-a*I*pi*t*y)*exp(-a*I*pi*x*z)/(pi*x), Ne(x, 0)), + (z*exp(-a*I*pi*t*y), True)) + fun = E**((-2*I*pi*(z*x+t*y))/(500*10**(-9))) + assert all_close(integrate(fun, z), r) + + +def test_matrices(): + M = Matrix(2, 2, lambda i, j: (i + j + 1)*sin((i + j + 1)*x)) + + assert integrate(M, x) == Matrix([ + [-cos(x), -cos(2*x)], + [-cos(2*x), -cos(3*x)], + ]) + + +def test_integrate_functions(): + # issue 4111 + assert integrate(f(x), x) == Integral(f(x), x) + assert integrate(f(x), (x, 0, 1)) == Integral(f(x), (x, 0, 1)) + assert integrate(f(x)*diff(f(x), x), x) == f(x)**2/2 + assert integrate(diff(f(x), x) / f(x), x) == log(f(x)) + + +def test_integrate_derivatives(): + assert integrate(Derivative(f(x), x), x) == f(x) + assert integrate(Derivative(f(y), y), x) == x*Derivative(f(y), y) + assert integrate(Derivative(f(x), x)**2, x) == \ + Integral(Derivative(f(x), x)**2, x) + + +def test_transform(): + a = Integral(x**2 + 1, (x, -1, 2)) + fx = x + fy = 3*y + 1 + assert a.doit() == a.transform(fx, fy).doit() + assert a.transform(fx, fy).transform(fy, fx) == a + fx = 3*x + 1 + fy = y + assert a.transform(fx, fy).transform(fy, fx) == a + a = Integral(sin(1/x), (x, 0, 1)) + assert a.transform(x, 1/y) == Integral(sin(y)/y**2, (y, 1, oo)) + assert a.transform(x, 1/y).transform(y, 1/x) == a + a = Integral(exp(-x**2), (x, -oo, oo)) + assert a.transform(x, 2*y) == Integral(2*exp(-4*y**2), (y, -oo, oo)) + # < 3 arg limit handled properly + assert Integral(x, x).transform(x, a*y).doit() == \ + Integral(y*a**2, y).doit() + _3 = S(3) + assert Integral(x, (x, 0, -_3)).transform(x, 1/y).doit() == \ + Integral(-1/x**3, (x, -oo, -1/_3)).doit() + assert Integral(x, (x, 0, _3)).transform(x, 1/y) == \ + Integral(y**(-3), (y, 1/_3, oo)) + # issue 8400 + i = Integral(x + y, (x, 1, 2), (y, 1, 2)) + assert i.transform(x, (x + 2*y, x)).doit() == \ + i.transform(x, (x + 2*z, x)).doit() == 3 + + i = Integral(x, (x, a, b)) + assert i.transform(x, 2*s) == Integral(4*s, (s, a/2, b/2)) + raises(ValueError, lambda: i.transform(x, 1)) + raises(ValueError, lambda: i.transform(x, s*t)) + raises(ValueError, lambda: i.transform(x, -s)) + raises(ValueError, lambda: i.transform(x, (s, t))) + raises(ValueError, lambda: i.transform(2*x, 2*s)) + + i = Integral(x**2, (x, 1, 2)) + raises(ValueError, lambda: i.transform(x**2, s)) + + am = Symbol('a', negative=True) + bp = Symbol('b', positive=True) + i = Integral(x, (x, bp, am)) + i.transform(x, 2*s) + assert i.transform(x, 2*s) == Integral(-4*s, (s, am/2, bp/2)) + + i = Integral(x, (x, a)) + assert i.transform(x, 2*s) == Integral(4*s, (s, a/2)) + + +def test_issue_4052(): + f = S.Half*asin(x) + x*sqrt(1 - x**2)/2 + + assert integrate(cos(asin(x)), x) == f + assert integrate(sin(acos(x)), x) == f + + +@slow +def test_evalf_integrals(): + assert NS(Integral(x, (x, 2, 5)), 15) == '10.5000000000000' + gauss = Integral(exp(-x**2), (x, -oo, oo)) + assert NS(gauss, 15) == '1.77245385090552' + assert NS(gauss**2 - pi + E*Rational( + 1, 10**20), 15) in ('2.71828182845904e-20', '2.71828182845905e-20') + # A monster of an integral from http://mathworld.wolfram.com/DefiniteIntegral.html + t = Symbol('t') + a = 8*sqrt(3)/(1 + 3*t**2) + b = 16*sqrt(2)*(3*t + 1)*sqrt(4*t**2 + t + 1)**3 + c = (3*t**2 + 1)*(11*t**2 + 2*t + 3)**2 + d = sqrt(2)*(249*t**2 + 54*t + 65)/(11*t**2 + 2*t + 3)**2 + f = a - b/c - d + assert NS(Integral(f, (t, 0, 1)), 50) == \ + NS((3*sqrt(2) - 49*pi + 162*atan(sqrt(2)))/12, 50) + # http://mathworld.wolfram.com/VardisIntegral.html + assert NS(Integral(log(log(1/x))/(1 + x + x**2), (x, 0, 1)), 15) == \ + NS('pi/sqrt(3) * log(2*pi**(5/6) / gamma(1/6))', 15) + # http://mathworld.wolfram.com/AhmedsIntegral.html + assert NS(Integral(atan(sqrt(x**2 + 2))/(sqrt(x**2 + 2)*(x**2 + 1)), (x, + 0, 1)), 15) == NS(5*pi**2/96, 15) + # http://mathworld.wolfram.com/AbelsIntegral.html + assert NS(Integral(x/((exp(pi*x) - exp( + -pi*x))*(x**2 + 1)), (x, 0, oo)), 15) == NS('log(2)/2-1/4', 15) + # Complex part trimming + # http://mathworld.wolfram.com/VardisIntegral.html + assert NS(Integral(log(log(sin(x)/cos(x))), (x, pi/4, pi/2)), 15, chop=True) == \ + NS('pi/4*log(4*pi**3/gamma(1/4)**4)', 15) + # + # Endpoints causing trouble (rounding error in integration points -> complex log) + assert NS( + 2 + Integral(log(2*cos(x/2)), (x, -pi, pi)), 17, chop=True) == NS(2, 17) + assert NS( + 2 + Integral(log(2*cos(x/2)), (x, -pi, pi)), 20, chop=True) == NS(2, 20) + assert NS( + 2 + Integral(log(2*cos(x/2)), (x, -pi, pi)), 22, chop=True) == NS(2, 22) + # Needs zero handling + assert NS(pi - 4*Integral( + 'sqrt(1-x**2)', (x, 0, 1)), 15, maxn=30, chop=True) in ('0.0', '0') + # Oscillatory quadrature + a = Integral(sin(x)/x**2, (x, 1, oo)).evalf(maxn=15) + assert 0.49 < a < 0.51 + assert NS( + Integral(sin(x)/x**2, (x, 1, oo)), quad='osc') == '0.504067061906928' + assert NS(Integral( + cos(pi*x + 1)/x, (x, -oo, -1)), quad='osc') == '0.276374705640365' + # indefinite integrals aren't evaluated + assert NS(Integral(x, x)) == 'Integral(x, x)' + assert NS(Integral(x, (x, y))) == 'Integral(x, (x, y))' + + +def test_evalf_issue_939(): + # https://github.com/sympy/sympy/issues/4038 + + # The output form of an integral may differ by a step function between + # revisions, making this test a bit useless. This can't be said about + # other two tests. For now, all values of this evaluation are used here, + # but in future this should be reconsidered. + assert NS(integrate(1/(x**5 + 1), x).subs(x, 4), chop=True) in \ + ['-0.000976138910649103', '0.965906660135753', '1.93278945918216'] + + assert NS(Integral(1/(x**5 + 1), (x, 2, 4))) == '0.0144361088886740' + assert NS( + integrate(1/(x**5 + 1), (x, 2, 4)), chop=True) == '0.0144361088886740' + + +def test_double_previously_failing_integrals(): + # Double integrals not implemented <- Sure it is! + res = integrate(sqrt(x) + x*y, (x, 1, 2), (y, -1, 1)) + # Old numerical test + assert NS(res, 15) == '2.43790283299492' + # Symbolic test + assert res == Rational(-4, 3) + 8*sqrt(2)/3 + # double integral + zero detection + assert integrate(sin(x + x*y), (x, -1, 1), (y, -1, 1)) is S.Zero + + +def test_integrate_SingularityFunction(): + in_1 = SingularityFunction(x, a, 3) + SingularityFunction(x, 5, -1) + out_1 = SingularityFunction(x, a, 4)/4 + SingularityFunction(x, 5, 0) + assert integrate(in_1, x) == out_1 + + in_2 = 10*SingularityFunction(x, 4, 0) - 5*SingularityFunction(x, -6, -2) + out_2 = 10*SingularityFunction(x, 4, 1) - 5*SingularityFunction(x, -6, -1) + assert integrate(in_2, x) == out_2 + + in_3 = 2*x**2*y -10*SingularityFunction(x, -4, 7) - 2*SingularityFunction(y, 10, -2) + out_3_1 = 2*x**3*y/3 - 2*x*SingularityFunction(y, 10, -2) - 5*SingularityFunction(x, -4, 8)/4 + out_3_2 = x**2*y**2 - 10*y*SingularityFunction(x, -4, 7) - 2*SingularityFunction(y, 10, -1) + assert integrate(in_3, x) == out_3_1 + assert integrate(in_3, y) == out_3_2 + + assert unchanged(Integral, in_3, (x,)) + assert Integral(in_3, x) == Integral(in_3, (x,)) + assert Integral(in_3, x).doit() == out_3_1 + + in_4 = 10*SingularityFunction(x, -4, 7) - 2*SingularityFunction(x, 10, -2) + out_4 = 5*SingularityFunction(x, -4, 8)/4 - 2*SingularityFunction(x, 10, -1) + assert integrate(in_4, (x, -oo, x)) == out_4 + + assert integrate(SingularityFunction(x, 5, -1), x) == SingularityFunction(x, 5, 0) + assert integrate(SingularityFunction(x, 0, -1), (x, -oo, oo)) == 1 + assert integrate(5*SingularityFunction(x, 5, -1), (x, -oo, oo)) == 5 + assert integrate(SingularityFunction(x, 5, -1) * f(x), (x, -oo, oo)) == f(5) + + +def test_integrate_DiracDelta(): + # This is here to check that deltaintegrate is being called, but also + # to test definite integrals. More tests are in test_deltafunctions.py + assert integrate(DiracDelta(x) * f(x), (x, -oo, oo)) == f(0) + assert integrate(DiracDelta(x)**2, (x, -oo, oo)) == DiracDelta(0) + # issue 4522 + assert integrate(integrate((4 - 4*x + x*y - 4*y) * \ + DiracDelta(x)*DiracDelta(y - 1), (x, 0, 1)), (y, 0, 1)) == 0 + # issue 5729 + p = exp(-(x**2 + y**2))/pi + assert integrate(p*DiracDelta(x - 10*y), (x, -oo, oo), (y, -oo, oo)) == \ + integrate(p*DiracDelta(x - 10*y), (y, -oo, oo), (x, -oo, oo)) == \ + integrate(p*DiracDelta(10*x - y), (x, -oo, oo), (y, -oo, oo)) == \ + integrate(p*DiracDelta(10*x - y), (y, -oo, oo), (x, -oo, oo)) == \ + 1/sqrt(101*pi) + + +def test_integrate_returns_piecewise(): + assert integrate(x**y, x) == Piecewise( + (x**(y + 1)/(y + 1), Ne(y, -1)), (log(x), True)) + assert integrate(x**y, y) == Piecewise( + (x**y/log(x), Ne(log(x), 0)), (y, True)) + assert integrate(exp(n*x), x) == Piecewise( + (exp(n*x)/n, Ne(n, 0)), (x, True)) + assert integrate(x*exp(n*x), x) == Piecewise( + ((n*x - 1)*exp(n*x)/n**2, Ne(n**2, 0)), (x**2/2, True)) + assert integrate(x**(n*y), x) == Piecewise( + (x**(n*y + 1)/(n*y + 1), Ne(n*y, -1)), (log(x), True)) + assert integrate(x**(n*y), y) == Piecewise( + (x**(n*y)/(n*log(x)), Ne(n*log(x), 0)), (y, True)) + assert integrate(cos(n*x), x) == Piecewise( + (sin(n*x)/n, Ne(n, 0)), (x, True)) + assert integrate(cos(n*x)**2, x) == Piecewise( + ((n*x/2 + sin(n*x)*cos(n*x)/2)/n, Ne(n, 0)), (x, True)) + assert integrate(x*cos(n*x), x) == Piecewise( + (x*sin(n*x)/n + cos(n*x)/n**2, Ne(n, 0)), (x**2/2, True)) + assert integrate(sin(n*x), x) == Piecewise( + (-cos(n*x)/n, Ne(n, 0)), (0, True)) + assert integrate(sin(n*x)**2, x) == Piecewise( + ((n*x/2 - sin(n*x)*cos(n*x)/2)/n, Ne(n, 0)), (0, True)) + assert integrate(x*sin(n*x), x) == Piecewise( + (-x*cos(n*x)/n + sin(n*x)/n**2, Ne(n, 0)), (0, True)) + assert integrate(exp(x*y), (x, 0, z)) == Piecewise( + (exp(y*z)/y - 1/y, (y > -oo) & (y < oo) & Ne(y, 0)), (z, True)) + # https://github.com/sympy/sympy/issues/23707 + assert integrate(exp(t)*exp(-t*sqrt(x - y)), t) == Piecewise( + (-exp(t)/(sqrt(x - y)*exp(t*sqrt(x - y)) - exp(t*sqrt(x - y))), + Ne(x, y + 1)), (t, True)) + + +def test_integrate_max_min(): + x = symbols('x', real=True) + assert integrate(Min(x, 2), (x, 0, 3)) == 4 + assert integrate(Max(x**2, x**3), (x, 0, 2)) == Rational(49, 12) + assert integrate(Min(exp(x), exp(-x))**2, x) == Piecewise( \ + (exp(2*x)/2, x <= 0), (1 - exp(-2*x)/2, True)) + # issue 7907 + c = symbols('c', extended_real=True) + int1 = integrate(Max(c, x)*exp(-x**2), (x, -oo, oo)) + int2 = integrate(c*exp(-x**2), (x, -oo, c)) + int3 = integrate(x*exp(-x**2), (x, c, oo)) + assert int1 == int2 + int3 == sqrt(pi)*c*erf(c)/2 + \ + sqrt(pi)*c/2 + exp(-c**2)/2 + + +def test_integrate_Abs_sign(): + assert integrate(Abs(x), (x, -2, 1)) == Rational(5, 2) + assert integrate(Abs(x), (x, 0, 1)) == S.Half + assert integrate(Abs(x + 1), (x, 0, 1)) == Rational(3, 2) + assert integrate(Abs(x**2 - 1), (x, -2, 2)) == 4 + assert integrate(Abs(x**2 - 3*x), (x, -15, 15)) == 2259 + assert integrate(sign(x), (x, -1, 2)) == 1 + assert integrate(sign(x)*sin(x), (x, -pi, pi)) == 4 + assert integrate(sign(x - 2) * x**2, (x, 0, 3)) == Rational(11, 3) + + t, s = symbols('t s', real=True) + assert integrate(Abs(t), t) == Piecewise( + (-t**2/2, t <= 0), (t**2/2, True)) + assert integrate(Abs(2*t - 6), t) == Piecewise( + (-t**2 + 6*t, t <= 3), (t**2 - 6*t + 18, True)) + assert (integrate(abs(t - s**2), (t, 0, 2)) == + 2*s**2*Min(2, s**2) - 2*s**2 - Min(2, s**2)**2 + 2) + assert integrate(exp(-Abs(t)), t) == Piecewise( + (exp(t), t <= 0), (2 - exp(-t), True)) + assert integrate(sign(2*t - 6), t) == Piecewise( + (-t, t < 3), (t - 6, True)) + assert integrate(2*t*sign(t**2 - 1), t) == Piecewise( + (t**2, t < -1), (-t**2 + 2, t < 1), (t**2, True)) + assert integrate(sign(t), (t, s + 1)) == Piecewise( + (s + 1, s + 1 > 0), (-s - 1, s + 1 < 0), (0, True)) + + +def test_subs1(): + e = Integral(exp(x - y), x) + assert e.subs(y, 3) == Integral(exp(x - 3), x) + e = Integral(exp(x - y), (x, 0, 1)) + assert e.subs(y, 3) == Integral(exp(x - 3), (x, 0, 1)) + f = Lambda(x, exp(-x**2)) + conv = Integral(f(x - y)*f(y), (y, -oo, oo)) + assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo)) + + +def test_subs2(): + e = Integral(exp(x - y), x, t) + assert e.subs(y, 3) == Integral(exp(x - 3), x, t) + e = Integral(exp(x - y), (x, 0, 1), (t, 0, 1)) + assert e.subs(y, 3) == Integral(exp(x - 3), (x, 0, 1), (t, 0, 1)) + f = Lambda(x, exp(-x**2)) + conv = Integral(f(x - y)*f(y), (y, -oo, oo), (t, 0, 1)) + assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo), (t, 0, 1)) + + +def test_subs3(): + e = Integral(exp(x - y), (x, 0, y), (t, y, 1)) + assert e.subs(y, 3) == Integral(exp(x - 3), (x, 0, 3), (t, 3, 1)) + f = Lambda(x, exp(-x**2)) + conv = Integral(f(x - y)*f(y), (y, -oo, oo), (t, x, 1)) + assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo), (t, 0, 1)) + + +def test_subs4(): + e = Integral(exp(x), (x, 0, y), (t, y, 1)) + assert e.subs(y, 3) == Integral(exp(x), (x, 0, 3), (t, 3, 1)) + f = Lambda(x, exp(-x**2)) + conv = Integral(f(y)*f(y), (y, -oo, oo), (t, x, 1)) + assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo), (t, 0, 1)) + + +def test_subs5(): + e = Integral(exp(-x**2), (x, -oo, oo)) + assert e.subs(x, 5) == e + e = Integral(exp(-x**2 + y), x) + assert e.subs(y, 5) == Integral(exp(-x**2 + 5), x) + e = Integral(exp(-x**2 + y), (x, x)) + assert e.subs(x, 5) == Integral(exp(y - x**2), (x, 5)) + assert e.subs(y, 5) == Integral(exp(-x**2 + 5), x) + e = Integral(exp(-x**2 + y), (y, -oo, oo), (x, -oo, oo)) + assert e.subs(x, 5) == e + assert e.subs(y, 5) == e + # Test evaluation of antiderivatives + e = Integral(exp(-x**2), (x, x)) + assert e.subs(x, 5) == Integral(exp(-x**2), (x, 5)) + e = Integral(exp(x), x) + assert (e.subs(x,1) - e.subs(x,0) - Integral(exp(x), (x, 0, 1)) + ).doit().is_zero + + +def test_subs6(): + a, b = symbols('a b') + e = Integral(x*y, (x, f(x), f(y))) + assert e.subs(x, 1) == Integral(x*y, (x, f(1), f(y))) + assert e.subs(y, 1) == Integral(x, (x, f(x), f(1))) + e = Integral(x*y, (x, f(x), f(y)), (y, f(x), f(y))) + assert e.subs(x, 1) == Integral(x*y, (x, f(1), f(y)), (y, f(1), f(y))) + assert e.subs(y, 1) == Integral(x*y, (x, f(x), f(y)), (y, f(x), f(1))) + e = Integral(x*y, (x, f(x), f(a)), (y, f(x), f(a))) + assert e.subs(a, 1) == Integral(x*y, (x, f(x), f(1)), (y, f(x), f(1))) + + +def test_subs7(): + e = Integral(x, (x, 1, y), (y, 1, 2)) + assert e.subs({x: 1, y: 2}) == e + e = Integral(sin(x) + sin(y), (x, sin(x), sin(y)), + (y, 1, 2)) + assert e.subs(sin(y), 1) == e + assert e.subs(sin(x), 1) == Integral(sin(x) + sin(y), (x, 1, sin(y)), + (y, 1, 2)) + +def test_expand(): + e = Integral(f(x)+f(x**2), (x, 1, y)) + assert e.expand() == Integral(f(x), (x, 1, y)) + Integral(f(x**2), (x, 1, y)) + e = Integral(f(x)+f(x**2), (x, 1, oo)) + assert e.expand() == e + assert e.expand(force=True) == Integral(f(x), (x, 1, oo)) + \ + Integral(f(x**2), (x, 1, oo)) + + +def test_integration_variable(): + raises(ValueError, lambda: Integral(exp(-x**2), 3)) + raises(ValueError, lambda: Integral(exp(-x**2), (3, -oo, oo))) + + +def test_expand_integral(): + assert Integral(cos(x**2)*(sin(x**2) + 1), (x, 0, 1)).expand() == \ + Integral(cos(x**2)*sin(x**2), (x, 0, 1)) + \ + Integral(cos(x**2), (x, 0, 1)) + assert Integral(cos(x**2)*(sin(x**2) + 1), x).expand() == \ + Integral(cos(x**2)*sin(x**2), x) + \ + Integral(cos(x**2), x) + + +def test_as_sum_midpoint1(): + e = Integral(sqrt(x**3 + 1), (x, 2, 10)) + assert e.as_sum(1, method="midpoint") == 8*sqrt(217) + assert e.as_sum(2, method="midpoint") == 4*sqrt(65) + 12*sqrt(57) + assert e.as_sum(3, method="midpoint") == 8*sqrt(217)/3 + \ + 8*sqrt(3081)/27 + 8*sqrt(52809)/27 + assert e.as_sum(4, method="midpoint") == 2*sqrt(730) + \ + 4*sqrt(7) + 4*sqrt(86) + 6*sqrt(14) + assert abs(e.as_sum(4, method="midpoint").n() - e.n()) < 0.5 + + e = Integral(sqrt(x**3 + y**3), (x, 2, 10), (y, 0, 10)) + raises(NotImplementedError, lambda: e.as_sum(4)) + + +def test_as_sum_midpoint2(): + e = Integral((x + y)**2, (x, 0, 1)) + n = Symbol('n', positive=True, integer=True) + assert e.as_sum(1, method="midpoint").expand() == Rational(1, 4) + y + y**2 + assert e.as_sum(2, method="midpoint").expand() == Rational(5, 16) + y + y**2 + assert e.as_sum(3, method="midpoint").expand() == Rational(35, 108) + y + y**2 + assert e.as_sum(4, method="midpoint").expand() == Rational(21, 64) + y + y**2 + assert e.as_sum(n, method="midpoint").expand() == \ + y**2 + y + Rational(1, 3) - 1/(12*n**2) + + +def test_as_sum_left(): + e = Integral((x + y)**2, (x, 0, 1)) + assert e.as_sum(1, method="left").expand() == y**2 + assert e.as_sum(2, method="left").expand() == Rational(1, 8) + y/2 + y**2 + assert e.as_sum(3, method="left").expand() == Rational(5, 27) + y*Rational(2, 3) + y**2 + assert e.as_sum(4, method="left").expand() == Rational(7, 32) + y*Rational(3, 4) + y**2 + assert e.as_sum(n, method="left").expand() == \ + y**2 + y + Rational(1, 3) - y/n - 1/(2*n) + 1/(6*n**2) + assert e.as_sum(10, method="left", evaluate=False).has(Sum) + + +def test_as_sum_right(): + e = Integral((x + y)**2, (x, 0, 1)) + assert e.as_sum(1, method="right").expand() == 1 + 2*y + y**2 + assert e.as_sum(2, method="right").expand() == Rational(5, 8) + y*Rational(3, 2) + y**2 + assert e.as_sum(3, method="right").expand() == Rational(14, 27) + y*Rational(4, 3) + y**2 + assert e.as_sum(4, method="right").expand() == Rational(15, 32) + y*Rational(5, 4) + y**2 + assert e.as_sum(n, method="right").expand() == \ + y**2 + y + Rational(1, 3) + y/n + 1/(2*n) + 1/(6*n**2) + + +def test_as_sum_trapezoid(): + e = Integral((x + y)**2, (x, 0, 1)) + assert e.as_sum(1, method="trapezoid").expand() == y**2 + y + S.Half + assert e.as_sum(2, method="trapezoid").expand() == y**2 + y + Rational(3, 8) + assert e.as_sum(3, method="trapezoid").expand() == y**2 + y + Rational(19, 54) + assert e.as_sum(4, method="trapezoid").expand() == y**2 + y + Rational(11, 32) + assert e.as_sum(n, method="trapezoid").expand() == \ + y**2 + y + Rational(1, 3) + 1/(6*n**2) + assert Integral(sign(x), (x, 0, 1)).as_sum(1, 'trapezoid') == S.Half + + +def test_as_sum_raises(): + e = Integral((x + y)**2, (x, 0, 1)) + raises(ValueError, lambda: e.as_sum(-1)) + raises(ValueError, lambda: e.as_sum(0)) + raises(ValueError, lambda: Integral(x).as_sum(3)) + raises(ValueError, lambda: e.as_sum(oo)) + raises(ValueError, lambda: e.as_sum(3, method='xxxx2')) + + +def test_nested_doit(): + e = Integral(Integral(x, x), x) + f = Integral(x, x, x) + assert e.doit() == f.doit() + + +def test_issue_4665(): + # Allow only upper or lower limit evaluation + e = Integral(x**2, (x, None, 1)) + f = Integral(x**2, (x, 1, None)) + assert e.doit() == Rational(1, 3) + assert f.doit() == Rational(-1, 3) + assert Integral(x*y, (x, None, y)).subs(y, t) == Integral(x*t, (x, None, t)) + assert Integral(x*y, (x, y, None)).subs(y, t) == Integral(x*t, (x, t, None)) + assert integrate(x**2, (x, None, 1)) == Rational(1, 3) + assert integrate(x**2, (x, 1, None)) == Rational(-1, 3) + assert integrate("x**2", ("x", "1", None)) == Rational(-1, 3) + + +def test_integral_reconstruct(): + e = Integral(x**2, (x, -1, 1)) + assert e == Integral(*e.args) + + +def test_doit_integrals(): + e = Integral(Integral(2*x), (x, 0, 1)) + assert e.doit() == Rational(1, 3) + assert e.doit(deep=False) == Rational(1, 3) + f = Function('f') + # doesn't matter if the integral can't be performed + assert Integral(f(x), (x, 1, 1)).doit() == 0 + # doesn't matter if the limits can't be evaluated + assert Integral(0, (x, 1, Integral(f(x), x))).doit() == 0 + assert Integral(x, (a, 0)).doit() == 0 + limits = ((a, 1, exp(x)), (x, 0)) + assert Integral(a, *limits).doit() == Rational(1, 4) + assert Integral(a, *list(reversed(limits))).doit() == 0 + + +def test_issue_4884(): + assert integrate(sqrt(x)*(1 + x)) == \ + Piecewise( + (2*sqrt(x)*(x + 1)**2/5 - 2*sqrt(x)*(x + 1)/15 - 4*sqrt(x)/15, + Abs(x + 1) > 1), + (2*I*sqrt(-x)*(x + 1)**2/5 - 2*I*sqrt(-x)*(x + 1)/15 - + 4*I*sqrt(-x)/15, True)) + assert integrate(x**x*(1 + log(x))) == x**x + +def test_issue_18153(): + assert integrate(x**n*log(x),x) == \ + Piecewise( + (n*x*x**n*log(x)/(n**2 + 2*n + 1) + + x*x**n*log(x)/(n**2 + 2*n + 1) - x*x**n/(n**2 + 2*n + 1) + , Ne(n, -1)), (log(x)**2/2, True) + ) + + +def test_is_number(): + from sympy.abc import x, y, z + assert Integral(x).is_number is False + assert Integral(1, x).is_number is False + assert Integral(1, (x, 1)).is_number is True + assert Integral(1, (x, 1, 2)).is_number is True + assert Integral(1, (x, 1, y)).is_number is False + assert Integral(1, (x, y)).is_number is False + assert Integral(x, y).is_number is False + assert Integral(x, (y, 1, x)).is_number is False + assert Integral(x, (y, 1, 2)).is_number is False + assert Integral(x, (x, 1, 2)).is_number is True + # `foo.is_number` should always be equivalent to `not foo.free_symbols` + # in each of these cases, there are pseudo-free symbols + i = Integral(x, (y, 1, 1)) + assert i.is_number is False and i.n() == 0 + i = Integral(x, (y, z, z)) + assert i.is_number is False and i.n() == 0 + i = Integral(1, (y, z, z + 2)) + assert i.is_number is False and i.n() == 2.0 + + assert Integral(x*y, (x, 1, 2), (y, 1, 3)).is_number is True + assert Integral(x*y, (x, 1, 2), (y, 1, z)).is_number is False + assert Integral(x, (x, 1)).is_number is True + assert Integral(x, (x, 1, Integral(y, (y, 1, 2)))).is_number is True + assert Integral(Sum(z, (z, 1, 2)), (x, 1, 2)).is_number is True + # it is possible to get a false negative if the integrand is + # actually an unsimplified zero, but this is true of is_number in general. + assert Integral(sin(x)**2 + cos(x)**2 - 1, x).is_number is False + assert Integral(f(x), (x, 0, 1)).is_number is True + + +def test_free_symbols(): + from sympy.abc import x, y, z + assert Integral(0, x).free_symbols == {x} + assert Integral(x).free_symbols == {x} + assert Integral(x, (x, None, y)).free_symbols == {y} + assert Integral(x, (x, y, None)).free_symbols == {y} + assert Integral(x, (x, 1, y)).free_symbols == {y} + assert Integral(x, (x, y, 1)).free_symbols == {y} + assert Integral(x, (x, x, y)).free_symbols == {x, y} + assert Integral(x, x, y).free_symbols == {x, y} + assert Integral(x, (x, 1, 2)).free_symbols == set() + assert Integral(x, (y, 1, 2)).free_symbols == {x} + # pseudo-free in this case + assert Integral(x, (y, z, z)).free_symbols == {x, z} + assert Integral(x, (y, 1, 2), (y, None, None) + ).free_symbols == {x, y} + assert Integral(x, (y, 1, 2), (x, 1, y) + ).free_symbols == {y} + assert Integral(2, (y, 1, 2), (y, 1, x), (x, 1, 2) + ).free_symbols == set() + assert Integral(2, (y, x, 2), (y, 1, x), (x, 1, 2) + ).free_symbols == set() + assert Integral(2, (x, 1, 2), (y, x, 2), (y, 1, 2) + ).free_symbols == {x} + assert Integral(f(x), (f(x), 1, y)).free_symbols == {y} + assert Integral(f(x), (f(x), 1, x)).free_symbols == {x} + + +def test_is_zero(): + from sympy.abc import x, m + assert Integral(0, (x, 1, x)).is_zero + assert Integral(1, (x, 1, 1)).is_zero + assert Integral(1, (x, 1, 2), (y, 2)).is_zero is False + assert Integral(x, (m, 0)).is_zero + assert Integral(x + m, (m, 0)).is_zero is None + i = Integral(m, (m, 1, exp(x)), (x, 0)) + assert i.is_zero is None + assert Integral(m, (x, 0), (m, 1, exp(x))).is_zero is True + + assert Integral(x, (x, oo, oo)).is_zero # issue 8171 + assert Integral(x, (x, -oo, -oo)).is_zero + + # this is zero but is beyond the scope of what is_zero + # should be doing + assert Integral(sin(x), (x, 0, 2*pi)).is_zero is None + + +def test_series(): + from sympy.abc import x + i = Integral(cos(x), (x, x)) + e = i.lseries(x) + assert i.nseries(x, n=8).removeO() == Add(*[next(e) for j in range(4)]) + + +def test_trig_nonelementary_integrals(): + x = Symbol('x') + assert integrate((1 + sin(x))/x, x) == log(x) + Si(x) + # next one comes out as log(x) + log(x**2)/2 + Ci(x) + # so not hardcoding this log ugliness + assert integrate((cos(x) + 2)/x, x).has(Ci) + + +def test_issue_4403(): + x = Symbol('x') + y = Symbol('y') + z = Symbol('z', positive=True) + assert integrate(sqrt(x**2 + z**2), x) == \ + z**2*asinh(x/z)/2 + x*sqrt(x**2 + z**2)/2 + assert integrate(sqrt(x**2 - z**2), x) == \ + x*sqrt(x**2 - z**2)/2 - z**2*log(x + sqrt(x**2 - z**2))/2 + + x = Symbol('x', real=True) + y = Symbol('y', positive=True) + assert integrate(1/(x**2 + y**2)**S('3/2'), x) == \ + x/(y**2*sqrt(x**2 + y**2)) + # If y is real and nonzero, we get x*Abs(y)/(y**3*sqrt(x**2 + y**2)), + # which results from sqrt(1 + x**2/y**2) = sqrt(x**2 + y**2)/|y|. + + +def test_issue_4403_2(): + assert integrate(sqrt(-x**2 - 4), x) == \ + -2*atan(x/sqrt(-4 - x**2)) + x*sqrt(-4 - x**2)/2 + + +def test_issue_4100(): + R = Symbol('R', positive=True) + assert integrate(sqrt(R**2 - x**2), (x, 0, R)) == pi*R**2/4 + + +def test_issue_5167(): + from sympy.abc import w, x, y, z + f = Function('f') + assert Integral(Integral(f(x), x), x) == Integral(f(x), x, x) + assert Integral(f(x)).args == (f(x), Tuple(x)) + assert Integral(Integral(f(x))).args == (f(x), Tuple(x), Tuple(x)) + assert Integral(Integral(f(x)), y).args == (f(x), Tuple(x), Tuple(y)) + assert Integral(Integral(f(x), z), y).args == (f(x), Tuple(z), Tuple(y)) + assert Integral(Integral(Integral(f(x), x), y), z).args == \ + (f(x), Tuple(x), Tuple(y), Tuple(z)) + assert integrate(Integral(f(x), x), x) == Integral(f(x), x, x) + assert integrate(Integral(f(x), y), x) == y*Integral(f(x), x) + assert integrate(Integral(f(x), x), y) in [Integral(y*f(x), x), y*Integral(f(x), x)] + assert integrate(Integral(2, x), x) == x**2 + assert integrate(Integral(2, x), y) == 2*x*y + # don't re-order given limits + assert Integral(1, x, y).args != Integral(1, y, x).args + # do as many as possible + assert Integral(f(x), y, x, y, x).doit() == y**2*Integral(f(x), x, x)/2 + assert Integral(f(x), (x, 1, 2), (w, 1, x), (z, 1, y)).doit() == \ + y*(x - 1)*Integral(f(x), (x, 1, 2)) - (x - 1)*Integral(f(x), (x, 1, 2)) + + +def test_issue_4890(): + z = Symbol('z', positive=True) + assert integrate(exp(-log(x)**2), x) == \ + sqrt(pi)*exp(Rational(1, 4))*erf(log(x) - S.Half)/2 + assert integrate(exp(log(x)**2), x) == \ + sqrt(pi)*exp(Rational(-1, 4))*erfi(log(x)+S.Half)/2 + assert integrate(exp(-z*log(x)**2), x) == \ + sqrt(pi)*exp(1/(4*z))*erf(sqrt(z)*log(x) - 1/(2*sqrt(z)))/(2*sqrt(z)) + + +def test_issue_4551(): + assert not integrate(1/(x*sqrt(1 - x**2)), x).has(Integral) + + +def test_issue_4376(): + n = Symbol('n', integer=True, positive=True) + assert simplify(integrate(n*(x**(1/n) - 1), (x, 0, S.Half)) - + (n**2 - 2**(1/n)*n**2 - n*2**(1/n))/(2**(1 + 1/n) + n*2**(1 + 1/n))) == 0 + + +def test_issue_4517(): + assert integrate((sqrt(x) - x**3)/x**Rational(1, 3), x) == \ + 6*x**Rational(7, 6)/7 - 3*x**Rational(11, 3)/11 + + +def test_issue_4527(): + k, m = symbols('k m', integer=True) + assert integrate(sin(k*x)*sin(m*x), (x, 0, pi)).simplify() == \ + Piecewise((0, Eq(k, 0) | Eq(m, 0)), + (-pi/2, Eq(k, -m) | (Eq(k, 0) & Eq(m, 0))), + (pi/2, Eq(k, m) | (Eq(k, 0) & Eq(m, 0))), + (0, True)) + # Should be possible to further simplify to: + # Piecewise( + # (0, Eq(k, 0) | Eq(m, 0)), + # (-pi/2, Eq(k, -m)), + # (pi/2, Eq(k, m)), + # (0, True)) + assert integrate(sin(k*x)*sin(m*x), (x,)) == Piecewise( + (0, And(Eq(k, 0), Eq(m, 0))), + (-x*sin(m*x)**2/2 - x*cos(m*x)**2/2 + sin(m*x)*cos(m*x)/(2*m), Eq(k, -m)), + (x*sin(m*x)**2/2 + x*cos(m*x)**2/2 - sin(m*x)*cos(m*x)/(2*m), Eq(k, m)), + (m*sin(k*x)*cos(m*x)/(k**2 - m**2) - + k*sin(m*x)*cos(k*x)/(k**2 - m**2), True)) + + +def test_issue_4199(): + ypos = Symbol('y', positive=True) + # TODO: Remove conds='none' below, let the assumption take care of it. + assert integrate(exp(-I*2*pi*ypos*x)*x, (x, -oo, oo), conds='none') == \ + Integral(exp(-I*2*pi*ypos*x)*x, (x, -oo, oo)) + + +def test_issue_3940(): + a, b, c, d = symbols('a:d', positive=True) + assert integrate(exp(-x**2 + I*c*x), x) == \ + -sqrt(pi)*exp(-c**2/4)*erf(I*c/2 - x)/2 + assert integrate(exp(a*x**2 + b*x + c), x).equals( + sqrt(pi)*exp(c - b**2/(4*a))*erfi((2*a*x + b)/(2*sqrt(a)))/(2*sqrt(a))) + + from sympy.core.function import expand_mul + from sympy.abc import k + assert expand_mul(integrate(exp(-x**2)*exp(I*k*x), (x, -oo, oo))) == \ + sqrt(pi)*exp(-k**2/4) + a, d = symbols('a d', positive=True) + assert expand_mul(integrate(exp(-a*x**2 + 2*d*x), (x, -oo, oo))) == \ + sqrt(pi)*exp(d**2/a)/sqrt(a) + + +def test_issue_5413(): + # Note that this is not the same as testing ratint() because integrate() + # pulls out the coefficient. + assert integrate(-a/(a**2 + x**2), x) == I*log(-I*a + x)/2 - I*log(I*a + x)/2 + + +def test_issue_4892a(): + A, z = symbols('A z') + c = Symbol('c', nonzero=True) + P1 = -A*exp(-z) + P2 = -A/(c*t)*(sin(x)**2 + cos(y)**2) + + h1 = -sin(x)**2 - cos(y)**2 + h2 = -sin(x)**2 + sin(y)**2 - 1 + + # there is still some non-deterministic behavior in integrate + # or trigsimp which permits one of the following + assert integrate(c*(P2 - P1), t) in [ + c*(-A*(-h1)*log(c*t)/c + A*t*exp(-z)), + c*(-A*(-h2)*log(c*t)/c + A*t*exp(-z)), + c*( A* h1 *log(c*t)/c + A*t*exp(-z)), + c*( A* h2 *log(c*t)/c + A*t*exp(-z)), + (A*c*t - A*(-h1)*log(t)*exp(z))*exp(-z), + (A*c*t - A*(-h2)*log(t)*exp(z))*exp(-z), + ] + + +def test_issue_4892b(): + # Issues relating to issue 4596 are making the actual result of this hard + # to test. The answer should be something like + # + # (-sin(y) + sqrt(-72 + 48*cos(y) - 8*cos(y)**2)/2)*log(x + sqrt(-72 + + # 48*cos(y) - 8*cos(y)**2)/(2*(3 - cos(y)))) + (-sin(y) - sqrt(-72 + + # 48*cos(y) - 8*cos(y)**2)/2)*log(x - sqrt(-72 + 48*cos(y) - + # 8*cos(y)**2)/(2*(3 - cos(y)))) + x**2*sin(y)/2 + 2*x*cos(y) + + expr = (sin(y)*x**3 + 2*cos(y)*x**2 + 12)/(x**2 + 2) + assert trigsimp(factor(integrate(expr, x).diff(x) - expr)) == 0 + + +def test_issue_5178(): + assert integrate(sin(x)*f(y, z), (x, 0, pi), (y, 0, pi), (z, 0, pi)) == \ + 2*Integral(f(y, z), (y, 0, pi), (z, 0, pi)) + + +def test_integrate_series(): + f = sin(x).series(x, 0, 10) + g = x**2/2 - x**4/24 + x**6/720 - x**8/40320 + x**10/3628800 + O(x**11) + + assert integrate(f, x) == g + assert diff(integrate(f, x), x) == f + + assert integrate(O(x**5), x) == O(x**6) + + +def test_atom_bug(): + from sympy.integrals.heurisch import heurisch + assert heurisch(meijerg([], [], [1], [], x), x) is None + + +def test_limit_bug(): + z = Symbol('z', zero=False) + assert integrate(sin(x*y*z), (x, 0, pi), (y, 0, pi)).together() == \ + (log(z) - Ci(pi**2*z) + EulerGamma + 2*log(pi))/z + + +def test_issue_4703(): + g = Function('g') + assert integrate(exp(x)*g(x), x).has(Integral) + + +def test_issue_1888(): + f = Function('f') + assert integrate(f(x).diff(x)**2, x).has(Integral) + +# The following tests work using meijerint. + + +def test_issue_3558(): + assert integrate(cos(x*y), (x, -pi/2, pi/2), (y, 0, pi)) == 2*Si(pi**2/2) + + +def test_issue_4422(): + assert integrate(1/sqrt(16 + 4*x**2), x) == asinh(x/2) / 2 + + +def test_issue_4493(): + assert simplify(integrate(x*sqrt(1 + 2*x), x)) == \ + sqrt(2*x + 1)*(6*x**2 + x - 1)/15 + + +def test_issue_4737(): + assert integrate(sin(x)/x, (x, -oo, oo)) == pi + assert integrate(sin(x)/x, (x, 0, oo)) == pi/2 + assert integrate(sin(x)/x, x) == Si(x) + + +def test_issue_4992(): + # Note: psi in _check_antecedents becomes NaN. + from sympy.core.function import expand_func + a = Symbol('a', positive=True) + assert simplify(expand_func(integrate(exp(-x)*log(x)*x**a, (x, 0, oo)))) == \ + (a*polygamma(0, a) + 1)*gamma(a) + + +def test_issue_4487(): + from sympy.functions.special.gamma_functions import lowergamma + assert simplify(integrate(exp(-x)*x**y, x)) == lowergamma(y + 1, x) + + +def test_issue_4215(): + x = Symbol("x") + assert integrate(1/(x**2), (x, -1, 1)) is oo + + +def test_issue_4400(): + n = Symbol('n', integer=True, positive=True) + assert integrate((x**n)*log(x), x) == \ + n*x*x**n*log(x)/(n**2 + 2*n + 1) + x*x**n*log(x)/(n**2 + 2*n + 1) - \ + x*x**n/(n**2 + 2*n + 1) + + +def test_issue_6253(): + # Note: this used to raise NotImplementedError + # Note: psi in _check_antecedents becomes NaN. + assert integrate((sqrt(1 - x) + sqrt(1 + x))**2/x, x, meijerg=True) == \ + Integral((sqrt(-x + 1) + sqrt(x + 1))**2/x, x) + + +def test_issue_4153(): + assert integrate(1/(1 + x + y + z), (x, 0, 1), (y, 0, 1), (z, 0, 1)) in [ + -12*log(3) - 3*log(6)/2 + 3*log(8)/2 + 5*log(2) + 7*log(4), + 6*log(2) + 8*log(4) - 27*log(3)/2, 22*log(2) - 27*log(3)/2, + -12*log(3) - 3*log(6)/2 + 47*log(2)/2] + + +def test_issue_4326(): + R, b, h = symbols('R b h') + # It doesn't matter if we can do the integral. Just make sure the result + # doesn't contain nan. This is really a test against _eval_interval. + e = integrate(((h*(x - R + b))/b)*sqrt(R**2 - x**2), (x, R - b, R)) + assert not e.has(nan) + # See that it evaluates + assert not e.has(Integral) + + +def test_powers(): + assert integrate(2**x + 3**x, x) == 2**x/log(2) + 3**x/log(3) + + +def test_manual_option(): + raises(ValueError, lambda: integrate(1/x, x, manual=True, meijerg=True)) + # an example of a function that manual integration cannot handle + assert integrate(log(1+x)/x, (x, 0, 1), manual=True).has(Integral) + + +def test_meijerg_option(): + raises(ValueError, lambda: integrate(1/x, x, meijerg=True, risch=True)) + # an example of a function that meijerg integration cannot handle + assert integrate(tan(x), x, meijerg=True) == Integral(tan(x), x) + + +def test_risch_option(): + # risch=True only allowed on indefinite integrals + raises(ValueError, lambda: integrate(1/log(x), (x, 0, oo), risch=True)) + assert integrate(exp(-x**2), x, risch=True) == NonElementaryIntegral(exp(-x**2), x) + assert integrate(log(1/x)*y, x, y, risch=True) == y**2*(x*log(1/x)/2 + x/2) + assert integrate(erf(x), x, risch=True) == Integral(erf(x), x) + # TODO: How to test risch=False? + + +@slow +def test_heurisch_option(): + raises(ValueError, lambda: integrate(1/x, x, risch=True, heurisch=True)) + # an integral that heurisch can handle + assert integrate(exp(x**2), x, heurisch=True) == sqrt(pi)*erfi(x)/2 + # an integral that heurisch currently cannot handle + assert integrate(exp(x)/x, x, heurisch=True) == Integral(exp(x)/x, x) + # an integral where heurisch currently hangs, issue 15471 + assert integrate(log(x)*cos(log(x))/x**Rational(3, 4), x, heurisch=False) == ( + -128*x**Rational(1, 4)*sin(log(x))/289 + 240*x**Rational(1, 4)*cos(log(x))/289 + + (16*x**Rational(1, 4)*sin(log(x))/17 + 4*x**Rational(1, 4)*cos(log(x))/17)*log(x)) + + +def test_issue_6828(): + f = 1/(1.08*x**2 - 4.3) + g = integrate(f, x).diff(x) + assert verify_numerically(f, g, tol=1e-12) + + +def test_issue_4803(): + x_max = Symbol("x_max") + assert integrate(y/pi*exp(-(x_max - x)/cos(a)), x) == \ + y*exp((x - x_max)/cos(a))*cos(a)/pi + + +def test_issue_4234(): + assert integrate(1/sqrt(1 + tan(x)**2)) == tan(x)/sqrt(1 + tan(x)**2) + + +def test_issue_4492(): + assert simplify(integrate(x**2 * sqrt(5 - x**2), x)).factor( + deep=True) == Piecewise( + (I*(2*x**5 - 15*x**3 + 25*x - 25*sqrt(x**2 - 5)*acosh(sqrt(5)*x/5)) / + (8*sqrt(x**2 - 5)), (x > sqrt(5)) | (x < -sqrt(5))), + ((2*x**5 - 15*x**3 + 25*x - 25*sqrt(5 - x**2)*asin(sqrt(5)*x/5)) / + (-8*sqrt(-x**2 + 5)), True)) + + +def test_issue_2708(): + # This test needs to use an integration function that can + # not be evaluated in closed form. Update as needed. + f = 1/(a + z + log(z)) + integral_f = NonElementaryIntegral(f, (z, 2, 3)) + assert Integral(f, (z, 2, 3)).doit() == integral_f + assert integrate(f + exp(z), (z, 2, 3)) == integral_f - exp(2) + exp(3) + assert integrate(2*f + exp(z), (z, 2, 3)) == \ + 2*integral_f - exp(2) + exp(3) + assert integrate(exp(1.2*n*s*z*(-t + z)/t), (z, 0, x)) == \ + NonElementaryIntegral(exp(-1.2*n*s*z)*exp(1.2*n*s*z**2/t), + (z, 0, x)) + + +def test_issue_2884(): + f = (4.000002016020*x + 4.000002016020*y + 4.000006024032)*exp(10.0*x) + e = integrate(f, (x, 0.1, 0.2)) + assert str(e) == '1.86831064982608*y + 2.16387491480008' + + +def test_issue_8368i(): + from sympy.functions.elementary.complexes import arg, Abs + assert integrate(exp(-s*x)*cosh(x), (x, 0, oo)) == \ + Piecewise( + ( pi*Piecewise( + ( -s/(pi*(-s**2 + 1)), + Abs(s**2) < 1), + ( 1/(pi*s*(1 - 1/s**2)), + Abs(s**(-2)) < 1), + ( meijerg( + ((S.Half,), (0, 0)), + ((0, S.Half), (0,)), + polar_lift(s)**2), + True) + ), + s**2 > 1 + ), + ( + Integral(exp(-s*x)*cosh(x), (x, 0, oo)), + True)) + assert integrate(exp(-s*x)*sinh(x), (x, 0, oo)) == \ + Piecewise( + ( -1/(s + 1)/2 - 1/(-s + 1)/2, + And( + Abs(s) > 1, + Abs(arg(s)) < pi/2, + Abs(arg(s)) <= pi/2 + )), + ( Integral(exp(-s*x)*sinh(x), (x, 0, oo)), + True)) + + +def test_issue_8901(): + assert integrate(sinh(1.0*x)) == 1.0*cosh(1.0*x) + assert integrate(tanh(1.0*x)) == 1.0*x - 1.0*log(tanh(1.0*x) + 1) + assert integrate(tanh(x)) == x - log(tanh(x) + 1) + + +@slow +def test_issue_8945(): + assert integrate(sin(x)**3/x, (x, 0, 1)) == -Si(3)/4 + 3*Si(1)/4 + assert integrate(sin(x)**3/x, (x, 0, oo)) == pi/4 + assert integrate(cos(x)**2/x**2, x) == -Si(2*x) - cos(2*x)/(2*x) - 1/(2*x) + + +@slow +def test_issue_7130(): + i, L, a, b = symbols('i L a b') + integrand = (cos(pi*i*x/L)**2 / (a + b*x)).rewrite(exp) + assert x not in integrate(integrand, (x, 0, L)).free_symbols + + +def test_issue_10567(): + a, b, c, t = symbols('a b c t') + vt = Matrix([a*t, b, c]) + assert integrate(vt, t) == Integral(vt, t).doit() + assert integrate(vt, t) == Matrix([[a*t**2/2], [b*t], [c*t]]) + + +def test_issue_11742(): + assert integrate(sqrt(-x**2 + 8*x + 48), (x, 4, 12)) == 16*pi + + +def test_issue_11856(): + t = symbols('t') + assert integrate(sinc(pi*t), t) == Si(pi*t)/pi + + +@slow +def test_issue_11876(): + assert integrate(sqrt(log(1/x)), (x, 0, 1)) == sqrt(pi)/2 + + +def test_issue_4950(): + assert integrate((-60*exp(x) - 19.2*exp(4*x))*exp(4*x), x) ==\ + -2.4*exp(8*x) - 12.0*exp(5*x) + + +def test_issue_4968(): + assert integrate(sin(log(x**2))) == x*sin(log(x**2))/5 - 2*x*cos(log(x**2))/5 + + +def test_singularities(): + assert integrate(1/x**2, (x, -oo, oo)) is oo + assert integrate(1/x**2, (x, -1, 1)) is oo + assert integrate(1/(x - 1)**2, (x, -2, 2)) is oo + + assert integrate(1/x**2, (x, 1, -1)) is -oo + assert integrate(1/(x - 1)**2, (x, 2, -2)) is -oo + + +def test_issue_12645(): + x, y = symbols('x y', real=True) + assert (integrate(sin(x*x*x + y*y), + (x, -sqrt(pi - y*y), sqrt(pi - y*y)), + (y, -sqrt(pi), sqrt(pi))) + == Integral(sin(x**3 + y**2), + (x, -sqrt(-y**2 + pi), sqrt(-y**2 + pi)), + (y, -sqrt(pi), sqrt(pi)))) + + +def test_issue_12677(): + assert integrate(sin(x) / (cos(x)**3), (x, 0, pi/6)) == Rational(1, 6) + + +def test_issue_14078(): + assert integrate((cos(3*x)-cos(x))/x, (x, 0, oo)) == -log(3) + + +def test_issue_14064(): + assert integrate(1/cosh(x), (x, 0, oo)) == pi/2 + + +def test_issue_14027(): + assert integrate(1/(1 + exp(x - S.Half)/(1 + exp(x))), x) == \ + x - exp(S.Half)*log(exp(x) + exp(S.Half)/(1 + exp(S.Half)))/(exp(S.Half) + E) + + +def test_issue_8170(): + assert integrate(tan(x), (x, 0, pi/2)) is S.Infinity + + +def test_issue_8440_14040(): + assert integrate(1/x, (x, -1, 1)) is S.NaN + assert integrate(1/(x + 1), (x, -2, 3)) is S.NaN + + +def test_issue_14096(): + assert integrate(1/(x + y)**2, (x, 0, 1)) == -1/(y + 1) + 1/y + assert integrate(1/(1 + x + y + z)**2, (x, 0, 1), (y, 0, 1), (z, 0, 1)) == \ + -4*log(4) - 6*log(2) + 9*log(3) + + +def test_issue_14144(): + assert Abs(integrate(1/sqrt(1 - x**3), (x, 0, 1)).n() - 1.402182) < 1e-6 + assert Abs(integrate(sqrt(1 - x**3), (x, 0, 1)).n() - 0.841309) < 1e-6 + + +def test_issue_14375(): + # This raised a TypeError. The antiderivative has exp_polar, which + # may be possible to unpolarify, so the exact output is not asserted here. + assert integrate(exp(I*x)*log(x), x).has(Ei) + + +def test_issue_14437(): + f = Function('f')(x, y, z) + assert integrate(f, (x, 0, 1), (y, 0, 2), (z, 0, 3)) == \ + Integral(f, (x, 0, 1), (y, 0, 2), (z, 0, 3)) + + +def test_issue_14470(): + assert integrate(1/sqrt(exp(x) + 1), x) == log(sqrt(exp(x) + 1) - 1) - log(sqrt(exp(x) + 1) + 1) + + +def test_issue_14877(): + f = exp(1 - exp(x**2)*x + 2*x**2)*(2*x**3 + x)/(1 - exp(x**2)*x)**2 + assert integrate(f, x) == \ + -exp(2*x**2 - x*exp(x**2) + 1)/(x*exp(3*x**2) - exp(2*x**2)) + + +def test_issue_14782(): + f = sqrt(-x**2 + 1)*(-x**2 + x) + assert integrate(f, [x, -1, 1]) == - pi / 8 + + +@slow +def test_issue_14782_slow(): + f = sqrt(-x**2 + 1)*(-x**2 + x) + assert integrate(f, [x, 0, 1]) == S.One / 3 - pi / 16 + + +def test_issue_12081(): + f = x**(Rational(-3, 2))*exp(-x) + assert integrate(f, [x, 0, oo]) is oo + + +def test_issue_15285(): + y = 1/x - 1 + f = 4*y*exp(-2*y)/x**2 + assert integrate(f, [x, 0, 1]) == 1 + + +def test_issue_15432(): + assert integrate(x**n * exp(-x) * log(x), (x, 0, oo)).gammasimp() == Piecewise( + (gamma(n + 1)*polygamma(0, n) + gamma(n + 1)/n, re(n) + 1 > 0), + (Integral(x**n*exp(-x)*log(x), (x, 0, oo)), True)) + + +def test_issue_15124(): + omega = IndexedBase('omega') + m, p = symbols('m p', cls=Idx) + assert integrate(exp(x*I*(omega[m] + omega[p])), x, conds='none') == \ + -I*exp(I*x*omega[m])*exp(I*x*omega[p])/(omega[m] + omega[p]) + + +def test_issue_15218(): + with warns_deprecated_sympy(): + Integral(Eq(x, y)) + with warns_deprecated_sympy(): + assert Integral(Eq(x, y), x) == Eq(Integral(x, x), Integral(y, x)) + with warns_deprecated_sympy(): + assert Integral(Eq(x, y), x).doit() == Eq(x**2/2, x*y) + with warns(SymPyDeprecationWarning, test_stacklevel=False): + # The warning is made in the ExprWithLimits superclass. The stacklevel + # is correct for integrate(Eq) but not Eq.integrate + assert Eq(x, y).integrate(x) == Eq(x**2/2, x*y) + + # These are not deprecated because they are definite integrals + assert integrate(Eq(x, y), (x, 0, 1)) == Eq(S.Half, y) + assert Eq(x, y).integrate((x, 0, 1)) == Eq(S.Half, y) + + +def test_issue_15292(): + res = integrate(exp(-x**2*cos(2*t)) * cos(x**2*sin(2*t)), (x, 0, oo)) + assert isinstance(res, Piecewise) + assert gammasimp((res - sqrt(pi)/2 * cos(t)).subs(t, pi/6)) == 0 + + +def test_issue_4514(): + assert integrate(sin(2*x)/sin(x), x) == 2*sin(x) + + +def test_issue_15457(): + x, a, b = symbols('x a b', real=True) + definite = integrate(exp(Abs(x-2)), (x, a, b)) + indefinite = integrate(exp(Abs(x-2)), x) + assert definite.subs({a: 1, b: 3}) == -2 + 2*E + assert indefinite.subs(x, 3) - indefinite.subs(x, 1) == -2 + 2*E + assert definite.subs({a: -3, b: -1}) == -exp(3) + exp(5) + assert indefinite.subs(x, -1) - indefinite.subs(x, -3) == -exp(3) + exp(5) + + +def test_issue_15431(): + assert integrate(x*exp(x)*log(x), x) == \ + (x*exp(x) - exp(x))*log(x) - exp(x) + Ei(x) + + +def test_issue_15640_log_substitutions(): + f = x/log(x) + F = Ei(2*log(x)) + assert integrate(f, x) == F and F.diff(x) == f + f = x**3/log(x)**2 + F = -x**4/log(x) + 4*Ei(4*log(x)) + assert integrate(f, x) == F and F.diff(x) == f + f = sqrt(log(x))/x**2 + F = -sqrt(pi)*erfc(sqrt(log(x)))/2 - sqrt(log(x))/x + assert integrate(f, x) == F and F.diff(x) == f + + +def test_issue_15509(): + from sympy.vector import CoordSys3D + N = CoordSys3D('N') + x = N.x + assert integrate(cos(a*x + b), (x, x_1, x_2), heurisch=True) == Piecewise( + (-sin(a*x_1 + b)/a + sin(a*x_2 + b)/a, (a > -oo) & (a < oo) & Ne(a, 0)), \ + (-x_1*cos(b) + x_2*cos(b), True)) + + +def test_issue_4311_fast(): + x = symbols('x', real=True) + assert integrate(x*abs(9-x**2), x) == Piecewise( + (x**4/4 - 9*x**2/2, x <= -3), + (-x**4/4 + 9*x**2/2 - Rational(81, 2), x <= 3), + (x**4/4 - 9*x**2/2, True)) + + +def test_integrate_with_complex_constants(): + K = Symbol('K', positive=True) + x = Symbol('x', real=True) + m = Symbol('m', real=True) + t = Symbol('t', real=True) + assert integrate(exp(-I*K*x**2+m*x), x) == sqrt(pi)*exp(-I*m**2 + /(4*K))*erfi((-2*I*K*x + m)/(2*sqrt(K)*sqrt(-I)))/(2*sqrt(K)*sqrt(-I)) + assert integrate(1/(1 + I*x**2), x) == (-I*(sqrt(-I)*log(x - I*sqrt(-I))/2 + - sqrt(-I)*log(x + I*sqrt(-I))/2)) + assert integrate(exp(-I*x**2), x) == sqrt(pi)*erf(sqrt(I)*x)/(2*sqrt(I)) + + assert integrate((1/(exp(I*t)-2)), t) == -t/2 - I*log(exp(I*t) - 2)/2 + assert integrate((1/(exp(I*t)-2)), (t, 0, 2*pi)) == -pi + + +def test_issue_14241(): + x = Symbol('x') + n = Symbol('n', positive=True, integer=True) + assert integrate(n * x ** (n - 1) / (x + 1), x) == \ + n**2*x**n*lerchphi(x*exp_polar(I*pi), 1, n)*gamma(n)/gamma(n + 1) + + +def test_issue_13112(): + assert integrate(sin(t)**2 / (5 - 4*cos(t)), [t, 0, 2*pi]) == pi / 4 + + +def test_issue_14709b(): + h = Symbol('h', positive=True) + i = integrate(x*acos(1 - 2*x/h), (x, 0, h)) + assert i == 5*h**2*pi/16 + + +def test_issue_8614(): + x = Symbol('x') + t = Symbol('t') + assert integrate(exp(t)/t, (t, -oo, x)) == Ei(x) + assert integrate((exp(-x) - exp(-2*x))/x, (x, 0, oo)) == log(2) + + +@slow +def test_issue_15494(): + s = symbols('s', positive=True) + + integrand = (exp(s/2) - 2*exp(1.6*s) + exp(s))*exp(s) + solution = integrate(integrand, s) + assert solution != S.NaN + # Not sure how to test this properly as it is a symbolic expression with floats + # assert str(solution) == '0.666666666666667*exp(1.5*s) + 0.5*exp(2.0*s) - 0.769230769230769*exp(2.6*s)' + # Maybe + assert abs(solution.subs(s, 1) - (-3.67440080236188)) <= 1e-8 + + integrand = (exp(s/2) - 2*exp(S(8)/5*s) + exp(s))*exp(s) + assert integrate(integrand, s) == -10*exp(13*s/5)/13 + 2*exp(3*s/2)/3 + exp(2*s)/2 + + +def test_li_integral(): + y = Symbol('y') + assert Integral(li(y*x**2), x).doit() == Piecewise((x*li(x**2*y) - \ + x*Ei(3*log(x**2*y)/2)/sqrt(x**2*y), + Ne(y, 0)), (0, True)) + + +def test_issue_17473(): + x = Symbol('x') + n = Symbol('n') + h = S.Half + ans = x**(n + 1)*gamma(h + h/n)*hyper((h + h/n,), + (3*h, 3*h + h/n), -x**(2*n)/4)/(2*n*gamma(3*h + h/n)) + got = integrate(sin(x**n), x) + assert got == ans + _x = Symbol('x', zero=False) + reps = {x: _x} + assert integrate(sin(_x**n), _x) == ans.xreplace(reps).expand() + + +def test_issue_17671(): + assert integrate(log(log(x)) / x**2, [x, 1, oo]) == -EulerGamma + assert integrate(log(log(x)) / x**3, [x, 1, oo]) == -log(2)/2 - EulerGamma/2 + assert integrate(log(log(x)) / x**10, [x, 1, oo]) == -log(9)/9 - EulerGamma/9 + + +def test_issue_2975(): + w = Symbol('w') + C = Symbol('C') + y = Symbol('y') + assert integrate(1/(y**2+C)**(S(3)/2), (y, -w/2, w/2)) == w/(C**(S(3)/2)*sqrt(1 + w**2/(4*C))) + + +def test_issue_7827(): + x, n, M = symbols('x n M') + N = Symbol('N', integer=True) + assert integrate(summation(x*n, (n, 1, N)), x) == x**2*(N**2/4 + N/4) + assert integrate(summation(x*sin(n), (n,1,N)), x) == \ + Sum(x**2*sin(n)/2, (n, 1, N)) + assert integrate(summation(sin(n*x), (n,1,N)), x) == \ + Sum(Piecewise((-cos(n*x)/n, Ne(n, 0)), (0, True)), (n, 1, N)) + assert integrate(integrate(summation(sin(n*x), (n,1,N)), x), x) == \ + Piecewise((Sum(Piecewise((-sin(n*x)/n**2, Ne(n, 0)), (-x/n, True)), + (n, 1, N)), (n > -oo) & (n < oo) & Ne(n, 0)), (0, True)) + assert integrate(Sum(x, (n, 1, M)), x) == M*x**2/2 + raises(ValueError, lambda: integrate(Sum(x, (x, y, n)), y)) + raises(ValueError, lambda: integrate(Sum(x, (x, 1, n)), n)) + raises(ValueError, lambda: integrate(Sum(x, (x, 1, y)), x)) + + +def test_issue_4231(): + f = (1 + 2*x + sqrt(x + log(x))*(1 + 3*x) + x**2)/(x*(x + sqrt(x + log(x)))*sqrt(x + log(x))) + assert integrate(f, x) == 2*sqrt(x + log(x)) + 2*log(x + sqrt(x + log(x))) + + +def test_issue_17841(): + f = diff(1/(x**2+x+I), x) + assert integrate(f, x) == 1/(x**2 + x + I) + + +def test_issue_21034(): + x = Symbol('x', real=True, nonzero=True) + f1 = x*(-x**4/asin(5)**4 - x*sinh(x + log(asin(5))) + 5) + f2 = (x + cosh(cos(4)))/(x*(x + 1/(12*x))) + + assert integrate(f1, x) == \ + -x**6/(6*asin(5)**4) - x**2*cosh(x + log(asin(5))) + 5*x**2/2 + 2*x*sinh(x + log(asin(5))) - 2*cosh(x + log(asin(5))) + + assert integrate(f2, x) == \ + log(x**2 + S(1)/12)/2 + 2*sqrt(3)*cosh(cos(4))*atan(2*sqrt(3)*x) + + +def test_issue_4187(): + assert integrate(log(x)*exp(-x), x) == Ei(-x) - exp(-x)*log(x) + assert integrate(log(x)*exp(-x), (x, 0, oo)) == -EulerGamma + + +def test_issue_5547(): + L = Symbol('L') + z = Symbol('z') + r0 = Symbol('r0') + R0 = Symbol('R0') + + assert integrate(r0**2*cos(z)**2, (z, -L/2, L/2)) == -r0**2*(-L/4 - + sin(L/2)*cos(L/2)/2) + r0**2*(L/4 + sin(L/2)*cos(L/2)/2) + + assert integrate(r0**2*cos(R0*z)**2, (z, -L/2, L/2)) == Piecewise( + (-r0**2*(-L*R0/4 - sin(L*R0/2)*cos(L*R0/2)/2)/R0 + + r0**2*(L*R0/4 + sin(L*R0/2)*cos(L*R0/2)/2)/R0, (R0 > -oo) & (R0 < oo) & Ne(R0, 0)), + (L*r0**2, True)) + + w = 2*pi*z/L + + sol = sqrt(2)*sqrt(L)*r0**2*fresnelc(sqrt(2)*sqrt(L))*gamma(S.One/4)/(16*gamma(S(5)/4)) + L*r0**2/2 + + assert integrate(r0**2*cos(w*z)**2, (z, -L/2, L/2)) == sol + + +def test_issue_15810(): + assert integrate(1/(2**(2*x/3) + 1), (x, 0, oo)) == Rational(3, 2) + + +def test_issue_21024(): + x = Symbol('x', real=True, nonzero=True) + f = log(x)*log(4*x) + log(3*x + exp(2)) + F = x*log(x)**2 + x*log(3*x + exp(2)) + x*(1 - 2*log(2)) + \ + (-2*x + 2*x*log(2))*log(x) + exp(2)*log(3*x + exp(2))/3 + assert F == integrate(f, x) + + f = (x + exp(3))/x**2 + F = log(x) - exp(3)/x + assert F == integrate(f, x) + + f = (x**2 + exp(5))/x + F = x**2/2 + exp(5)*log(x) + assert F == integrate(f, x) + + f = x/(2*x + tanh(1)) + F = x/2 - log(2*x + tanh(1))*tanh(1)/4 + assert F == integrate(f, x) + + f = x - sinh(4)/x + F = x**2/2 - log(x)*sinh(4) + assert F == integrate(f, x) + + f = log(x + exp(5)/x) + F = x*log(x + exp(5)/x) - x + 2*exp(Rational(5, 2))*atan(x*exp(Rational(-5, 2))) + assert F == integrate(f, x) + + f = x**5/(x + E) + F = x**5/5 - E*x**4/4 + x**3*exp(2)/3 - x**2*exp(3)/2 + x*exp(4) - exp(5)*log(x + E) + assert F == integrate(f, x) + + f = 4*x/(x + sinh(5)) + F = 4*x - 4*log(x + sinh(5))*sinh(5) + assert F == integrate(f, x) + + f = x**2/(2*x + sinh(2)) + F = x**2/4 - x*sinh(2)/4 + log(2*x + sinh(2))*sinh(2)**2/8 + assert F == integrate(f, x) + + f = -x**2/(x + E) + F = -x**2/2 + E*x - exp(2)*log(x + E) + assert F == integrate(f, x) + + f = (2*x + 3)*exp(5)/x + F = 2*x*exp(5) + 3*exp(5)*log(x) + assert F == integrate(f, x) + + f = x + 2 + cosh(3)/x + F = x**2/2 + 2*x + log(x)*cosh(3) + assert F == integrate(f, x) + + f = x - tanh(1)/x**3 + F = x**2/2 + tanh(1)/(2*x**2) + assert F == integrate(f, x) + + f = (3*x - exp(6))/x + F = 3*x - exp(6)*log(x) + assert F == integrate(f, x) + + f = x**4/(x + exp(5))**2 + x + F = x**3/3 + x**2*(Rational(1, 2) - exp(5)) + 3*x*exp(10) - 4*exp(15)*log(x + exp(5)) - exp(20)/(x + exp(5)) + assert F == integrate(f, x) + + f = x*(x + exp(10)/x**2) + x + F = x**3/3 + x**2/2 + exp(10)*log(x) + assert F == integrate(f, x) + + f = x + x/(5*x + sinh(3)) + F = x**2/2 + x/5 - log(5*x + sinh(3))*sinh(3)/25 + assert F == integrate(f, x) + + f = (x + exp(3))/(2*x**2 + 2*x) + F = exp(3)*log(x)/2 - exp(3)*log(x + 1)/2 + log(x + 1)/2 + assert F == integrate(f, x).expand() + + f = log(x + 4*sinh(4)) + F = x*log(x + 4*sinh(4)) - x + 4*log(x + 4*sinh(4))*sinh(4) + assert F == integrate(f, x) + + f = -x + 20*(exp(-5) - atan(4)/x)**3*sin(4)/x + F = (-x**2*exp(15)/2 + 20*log(x)*sin(4) - (-180*x**2*exp(5)*sin(4)*atan(4) + 90*x*exp(10)*sin(4)*atan(4)**2 - \ + 20*exp(15)*sin(4)*atan(4)**3)/(3*x**3))*exp(-15) + assert F == integrate(f, x) + + f = 2*x**2*exp(-4) + 6/x + F_true = (2*x**3/3 + 6*exp(4)*log(x))*exp(-4) + assert F_true == integrate(f, x) + + +def test_issue_21721(): + a = Symbol('a') + assert integrate(1/(pi*(1+(x-a)**2)),(x,-oo,oo)).expand() == \ + -Heaviside(im(a) - 1, 0) + Heaviside(im(a) + 1, 0) + + +def test_issue_21831(): + theta = symbols('theta') + assert integrate(cos(3*theta)/(5-4*cos(theta)), (theta, 0, 2*pi)) == pi/12 + integrand = cos(2*theta)/(5 - 4*cos(theta)) + assert integrate(integrand, (theta, 0, 2*pi)) == pi/6 + + +@slow +def test_issue_22033_integral(): + assert integrate((x**2 - Rational(1, 4))**2 * sqrt(1 - x**2), (x, -1, 1)) == pi/32 + + +@slow +def test_issue_21671(): + assert integrate(1,(z,x**2+y**2,2-x**2-y**2),(y,-sqrt(1-x**2),sqrt(1-x**2)),(x,-1,1)) == pi + assert integrate(-4*(1 - x**2)**(S(3)/2)/3 + 2*sqrt(1 - x**2)*(2 - 2*x**2), (x, -1, 1)) == pi + + +def test_issue_18527(): + # The manual integrator can not currently solve this. Assert that it does + # not give an incorrect result involving Abs when x has real assumptions. + xr = symbols('xr', real=True) + expr = (cos(x)/(4+(sin(x))**2)) + res_real = integrate(expr.subs(x, xr), xr, manual=True).subs(xr, x) + assert integrate(expr, x, manual=True) == res_real == Integral(expr, x) + + +def test_issue_23718(): + f = 1/(b*cos(x) + a*sin(x)) + Fpos = (-log(-a/b + tan(x/2) - sqrt(a**2 + b**2)/b)/sqrt(a**2 + b**2) + +log(-a/b + tan(x/2) + sqrt(a**2 + b**2)/b)/sqrt(a**2 + b**2)) + F = Piecewise( + # XXX: The zoo case here is for a=b=0 so it should just be zoo or maybe + # it doesn't really need to be included at all given that the original + # integrand is really undefined in that case anyway. + (zoo*(-log(tan(x/2) - 1) + log(tan(x/2) + 1)), Eq(a, 0) & Eq(b, 0)), + (log(tan(x/2))/a, Eq(b, 0)), + (-I/(-I*b*sin(x) + b*cos(x)), Eq(a, -I*b)), + (I/(I*b*sin(x) + b*cos(x)), Eq(a, I*b)), + (Fpos, True), + ) + assert integrate(f, x) == F + + ap, bp = symbols('a, b', positive=True) + rep = {a: ap, b: bp} + assert integrate(f.subs(rep), x) == Fpos.subs(rep) + + +def test_issue_23566(): + i = integrate(1/sqrt(x**2-1), (x, -2, -1)) + assert i == -log(2 - sqrt(3)) + assert math.isclose(i.n(), 1.31695789692482) + + +def test_pr_23583(): + # This result from meijerg is wrong. Check whether new result is correct when this test fail. + assert integrate(1/sqrt((x - I)**2-1)) == Piecewise((acosh(x - I), Abs((x - I)**2) > 1), (-I*asin(x - I), True)) + + +def test_issue_7264(): + assert integrate(exp(x)*sqrt(1 + exp(2*x))) == sqrt(exp(2*x) + 1)*exp(x)/2 + asinh(exp(x))/2 + + +def test_issue_11254a(): + assert integrate(sech(x), (x, 0, 1)) == 2*atan(tanh(S.Half)) + + +def test_issue_11254b(): + assert integrate(csch(x), x) == log(tanh(x/2)) + assert integrate(csch(x), (x, 0, 1)) == oo + + +def test_issue_11254d(): + # (sech(x)**2).rewrite(sinh) + assert integrate(-1/sinh(x + I*pi/2, evaluate=False)**2, x) == -2/(exp(2*x) + 1) + assert integrate(cosh(x)**(-2), x) == 2*tanh(x/2)/(tanh(x/2)**2 + 1) + + +def test_issue_22863(): + i = integrate((3*x**3-x**2+2*x-4)/sqrt(x**2-3*x+2), (x, 0, 1)) + assert i == -101*sqrt(2)/8 - 135*log(3 - 2*sqrt(2))/16 + assert math.isclose(i.n(), -2.98126694400554) + + +def test_issue_9723(): + assert integrate(sqrt(x + sqrt(x))) == \ + 2*sqrt(sqrt(x) + x)*(sqrt(x)/12 + x/3 - S(1)/8) + log(2*sqrt(x) + 2*sqrt(sqrt(x) + x) + 1)/8 + assert integrate(sqrt(2*x+3+sqrt(4*x+5))**3) == \ + sqrt(2*x + sqrt(4*x + 5) + 3) * \ + (9*x/10 + 11*(4*x + 5)**(S(3)/2)/40 + sqrt(4*x + 5)/40 + (4*x + 5)**2/10 + S(11)/10)/2 + + +def test_issue_23704(): + # XXX: This is testing that an exception is not raised in risch Ideally + # manualintegrate (manual=True) would be able to compute this but + # manualintegrate is very slow for this example so we don't test that here. + assert (integrate(log(x)/x**2/(c*x**2+b*x+a),x, risch=True) + == NonElementaryIntegral(log(x)/(a*x**2 + b*x**3 + c*x**4), x)) + + +def test_exp_substitution(): + assert integrate(1/sqrt(1-exp(2*x))) == log(sqrt(1 - exp(2*x)) - 1)/2 - log(sqrt(1 - exp(2*x)) + 1)/2 + + +def test_hyperbolic(): + assert integrate(coth(x)) == x - log(tanh(x) + 1) + log(tanh(x)) + assert integrate(sech(x)) == 2*atan(tanh(x/2)) + assert integrate(csch(x)) == log(tanh(x/2)) + + +def test_nested_pow(): + assert integrate(sqrt(x**2)) == x*sqrt(x**2)/2 + assert integrate(sqrt(x**(S(5)/3))) == 6*x*sqrt(x**(S(5)/3))/11 + assert integrate(1/sqrt(x**2)) == x*log(x)/sqrt(x**2) + assert integrate(x*sqrt(x**(-4))) == x**2*sqrt(x**-4)*log(x) + + +def test_sqrt_quadratic(): + assert integrate(1/sqrt(3*x**2+4*x+5)) == sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/3 + assert integrate(1/sqrt(-3*x**2+4*x+5)) == sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/3 + assert integrate(1/sqrt(3*x**2+4*x-5)) == sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/3 + assert integrate(1/sqrt(4*x**2-4*x+1)) == (x - S.Half)*log(x - S.Half)/(2*sqrt((x - S.Half)**2)) + assert integrate(1/sqrt(a+b*x+c*x**2), x) == \ + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(c, 0) & Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), Ne(c, 0)), + (2*sqrt(a + b*x)/b, Ne(b, 0)), (x/sqrt(a), True)) + + assert integrate((7*x+6)/sqrt(3*x**2+4*x+5)) == \ + 7*sqrt(3*x**2 + 4*x + 5)/3 + 4*sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/9 + assert integrate((7*x+6)/sqrt(-3*x**2+4*x+5)) == \ + -7*sqrt(-3*x**2 + 4*x + 5)/3 + 32*sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/9 + assert integrate((7*x+6)/sqrt(3*x**2+4*x-5)) == \ + 7*sqrt(3*x**2 + 4*x - 5)/3 + 4*sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/9 + assert integrate((d+e*x)/sqrt(a+b*x+c*x**2), x) == \ + Piecewise(((-b*e/(2*c) + d) * + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) + + e*sqrt(a + b*x + c*x**2)/c, Ne(c, 0)), + ((2*d*sqrt(a + b*x) + 2*e*(-a*sqrt(a + b*x) + (a + b*x)**(S(3)/2)/3)/b)/b, Ne(b, 0)), + ((d*x + e*x**2/2)/sqrt(a), True)) + + assert integrate((3*x**3-x**2+2*x-4)/sqrt(x**2-3*x+2)) == \ + sqrt(x**2 - 3*x + 2)*(x**2 + 13*x/4 + S(101)/8) + 135*log(2*x + 2*sqrt(x**2 - 3*x + 2) - 3)/16 + + assert integrate(sqrt(53225*x**2-66732*x+23013)) == \ + (x/2 - S(16683)/53225)*sqrt(53225*x**2 - 66732*x + 23013) + \ + 111576969*sqrt(2129)*asinh(53225*x/10563 - S(11122)/3521)/1133160250 + assert integrate(sqrt(a+b*x+c*x**2), x) == \ + Piecewise(((a/2 - b**2/(8*c)) * + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) + + (b/(4*c) + x/2)*sqrt(a + b*x + c*x**2), Ne(c, 0)), + (2*(a + b*x)**(S(3)/2)/(3*b), Ne(b, 0)), + (sqrt(a)*x, True)) + + assert integrate(x*sqrt(x**2+2*x+4)) == \ + (x**2/3 + x/6 + S(5)/6)*sqrt(x**2 + 2*x + 4) - 3*asinh(sqrt(3)*(x + 1)/3)/2 + + +def test_mul_pow_derivative(): + assert integrate(x*sec(x)*tan(x)) == x*sec(x) - log(tan(x) + sec(x)) + assert integrate(x*sec(x)**2, x) == x*tan(x) + log(cos(x)) + assert integrate(x**3*Derivative(f(x), (x, 4))) == \ + x**3*Derivative(f(x), (x, 3)) - 3*x**2*Derivative(f(x), (x, 2)) + 6*x*Derivative(f(x), x) - 6*f(x) + + +def test_issue_20782(): + fun1 = Piecewise((0, x < 0.0), (1, True)) + fun2 = -Piecewise((0, x < 1.0), (1, True)) + fun_sum = fun1 + fun2 + L = (x, -float('Inf'), 1) + + assert integrate(fun1, L) == 1 + assert integrate(fun2, L) == 0 + assert integrate(-fun1, L) == -1 + assert integrate(-fun2, L) == 0 + assert integrate(fun_sum, L) == 1. + assert integrate(-fun_sum, L) == -1. + + +def test_issue_20781(): + P = lambda a: Piecewise((0, x < a), (1, x >= a)) + f = lambda a: P(int(a)) + P(float(a)) + L = (x, -float('Inf'), x) + f1 = integrate(f(1), L) + assert f1 == 2*x - Min(1.0, x) - Min(x, Max(1.0, 1, evaluate=False)) + # XXX is_zero is True for S(0) and Float(0) and this is baked into + # the code more deeply than the issue of Float(0) != S(0) + assert integrate(f(0), (x, -float('Inf'), x) + ) == 2*x - 2*Min(0, x) + + +@slow +def test_issue_19427(): + # + x = Symbol("x") + + # Have always been okay: + assert integrate((x ** 4) * sqrt(1 - x ** 2), (x, -1, 1)) == pi / 16 + assert integrate((-2 * x ** 2) * sqrt(1 - x ** 2), (x, -1, 1)) == -pi / 4 + assert integrate((1) * sqrt(1 - x ** 2), (x, -1, 1)) == pi / 2 + + # Sum of the above, used to incorrectly return 0 for a while: + assert integrate((x ** 4 - 2 * x ** 2 + 1) * sqrt(1 - x ** 2), (x, -1, 1)) == 5 * pi / 16 + + +def test_issue_23942(): + I1 = Integral(1/sqrt(a*(1 + x)**3 + (1 + x)**2), (x, 0, z)) + assert I1.series(a, 1, n=1) == Integral(1/sqrt(x**3 + 4*x**2 + 5*x + 2), (x, 0, z)) + O(a - 1, (a, 1)) + I2 = Integral(1/sqrt(a*(4 - x)**4 + (5 + x)**2), (x, 0, z)) + assert I2.series(a, 2, n=1) == Integral(1/sqrt(2*x**4 - 32*x**3 + 193*x**2 - 502*x + 537), (x, 0, z)) + O(a - 2, (a, 2)) + + +def test_issue_25886(): + # https://github.com/sympy/sympy/issues/25886 + f = (1-x)*exp(0.937098661j*x) + F_exp = (1.0*(-1.0671234968289*I*y + + 1.13875255748434 + + 1.0671234968289*I)*exp(0.937098661*I*y) + - 1.13875255748434*exp(0.937098661*I)) + F = integrate(f, (x, y, 1.0)) + assert F.is_same(F_exp, math.isclose) + + +def test_old_issues(): + # https://github.com/sympy/sympy/issues/5212 + I1 = integrate(cos(log(x**2))/x) + assert I1 == sin(log(x**2))/2 + # https://github.com/sympy/sympy/issues/5462 + I2 = integrate(1/(x**2+y**2)**(Rational(3,2)),x) + assert I2 == x/(y**3*sqrt(x**2/y**2 + 1)) + # https://github.com/sympy/sympy/issues/6278 + I3 = integrate(1/(cos(x)+2),(x,0,2*pi)) + assert I3 == 2*sqrt(3)*pi/3 + + +def test_integral_issue_26566(): + # Define the symbols + x = symbols('x', real=True) + a = symbols('a', real=True, positive=True) + + # Define the integral expression + integral_expr = sin(a * (x + pi))**2 + symbolic_result = integrate(integral_expr, (x, -pi, -pi/2)) + + # Known correct result + correct_result = pi / 4 + + # Substitute a specific value for 'a' to evaluate both results + a_value = 1 + numeric_symbolic_result = symbolic_result.subs(a, a_value).evalf() + numeric_correct_result = correct_result.evalf() + + # Assert that the symbolic result matches the correct value + assert simplify(numeric_symbolic_result - numeric_correct_result) == 0 + + +def test_definite_integral_with_floats_issue_27231(): + # Define the symbol and the integral expression + x = symbols('x', real=True) + integral_expr = sqrt(1 - 0.5625 * (x + 0.333333333333333) ** 2) + + # Perform the definite integral with the known limits + result_symbolic = integrate(integral_expr, (x, -1, 1)) + result_numeric = result_symbolic.evalf() + + # Expected result with higher precision + expected_result = sqrt(3) / 6 + 4 * pi / 9 + + # Verify that the result is approximately equal within a larger tolerance + assert abs(result_numeric - expected_result.evalf()) < 1e-8 + + +def test_issue_27374(): + #https://github.com/sympy/sympy/issues/27374 + r = sqrt(x**2 + z**2) + u = erf(a*r/sqrt(2))/r + Ec = diff(u, z, z).subs([(x, sqrt(b*b-z*z))]) + expected_result = -2*sqrt(2)*b*a**3*exp(-b**2*a**2/2)/(3*sqrt(pi)) + assert simplify(integrate(Ec, (z, -b, b))) == expected_result diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_intpoly.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_intpoly.py new file mode 100644 index 0000000000000000000000000000000000000000..ddbaad1fbdeca53ccab8e8b22758a6ad2d89836e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_intpoly.py @@ -0,0 +1,627 @@ +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import sqrt + +from sympy.core import S, Rational + +from sympy.integrals.intpoly import (decompose, best_origin, distance_to_side, + polytope_integrate, point_sort, + hyperplane_parameters, main_integrate3d, + main_integrate, polygon_integrate, + lineseg_integrate, integration_reduction, + integration_reduction_dynamic, is_vertex) + +from sympy.geometry.line import Segment2D +from sympy.geometry.polygon import Polygon +from sympy.geometry.point import Point, Point2D +from sympy.abc import x, y, z + +from sympy.testing.pytest import slow + + +def test_decompose(): + assert decompose(x) == {1: x} + assert decompose(x**2) == {2: x**2} + assert decompose(x*y) == {2: x*y} + assert decompose(x + y) == {1: x + y} + assert decompose(x**2 + y) == {1: y, 2: x**2} + assert decompose(8*x**2 + 4*y + 7) == {0: 7, 1: 4*y, 2: 8*x**2} + assert decompose(x**2 + 3*y*x) == {2: x**2 + 3*x*y} + assert decompose(9*x**2 + y + 4*x + x**3 + y**2*x + 3) ==\ + {0: 3, 1: 4*x + y, 2: 9*x**2, 3: x**3 + x*y**2} + + assert decompose(x, True) == {x} + assert decompose(x ** 2, True) == {x**2} + assert decompose(x * y, True) == {x * y} + assert decompose(x + y, True) == {x, y} + assert decompose(x ** 2 + y, True) == {y, x ** 2} + assert decompose(8 * x ** 2 + 4 * y + 7, True) == {7, 4*y, 8*x**2} + assert decompose(x ** 2 + 3 * y * x, True) == {x ** 2, 3 * x * y} + assert decompose(9 * x ** 2 + y + 4 * x + x ** 3 + y ** 2 * x + 3, True) == \ + {3, y, 4*x, 9*x**2, x*y**2, x**3} + + +def test_best_origin(): + expr1 = y ** 2 * x ** 5 + y ** 5 * x ** 7 + 7 * x + x ** 12 + y ** 7 * x + + l1 = Segment2D(Point(0, 3), Point(1, 1)) + l2 = Segment2D(Point(S(3) / 2, 0), Point(S(3) / 2, 3)) + l3 = Segment2D(Point(0, S(3) / 2), Point(3, S(3) / 2)) + l4 = Segment2D(Point(0, 2), Point(2, 0)) + l5 = Segment2D(Point(0, 2), Point(1, 1)) + l6 = Segment2D(Point(2, 0), Point(1, 1)) + + assert best_origin((2, 1), 3, l1, expr1) == (0, 3) + # XXX: Should these return exact Rational output? Maybe best_origin should + # sympify its arguments... + assert best_origin((2, 0), 3, l2, x ** 7) == (1.5, 0) + assert best_origin((0, 2), 3, l3, x ** 7) == (0, 1.5) + assert best_origin((1, 1), 2, l4, x ** 7 * y ** 3) == (0, 2) + assert best_origin((1, 1), 2, l4, x ** 3 * y ** 7) == (2, 0) + assert best_origin((1, 1), 2, l5, x ** 2 * y ** 9) == (0, 2) + assert best_origin((1, 1), 2, l6, x ** 9 * y ** 2) == (2, 0) + + +@slow +def test_polytope_integrate(): + # Convex 2-Polytopes + # Vertex representation + assert polytope_integrate(Polygon(Point(0, 0), Point(0, 2), + Point(4, 0)), 1) == 4 + assert polytope_integrate(Polygon(Point(0, 0), Point(0, 1), + Point(1, 1), Point(1, 0)), x * y) ==\ + Rational(1, 4) + assert polytope_integrate(Polygon(Point(0, 3), Point(5, 3), Point(1, 1)), + 6*x**2 - 40*y) == Rational(-935, 3) + + assert polytope_integrate(Polygon(Point(0, 0), Point(0, sqrt(3)), + Point(sqrt(3), sqrt(3)), + Point(sqrt(3), 0)), 1) == 3 + + hexagon = Polygon(Point(0, 0), Point(-sqrt(3) / 2, S.Half), + Point(-sqrt(3) / 2, S(3) / 2), Point(0, 2), + Point(sqrt(3) / 2, S(3) / 2), Point(sqrt(3) / 2, S.Half)) + + assert polytope_integrate(hexagon, 1) == S(3*sqrt(3)) / 2 + + # Hyperplane representation + assert polytope_integrate([((-1, 0), 0), ((1, 2), 4), + ((0, -1), 0)], 1) == 4 + assert polytope_integrate([((-1, 0), 0), ((0, 1), 1), + ((1, 0), 1), ((0, -1), 0)], x * y) == Rational(1, 4) + assert polytope_integrate([((0, 1), 3), ((1, -2), -1), + ((-2, -1), -3)], 6*x**2 - 40*y) == Rational(-935, 3) + assert polytope_integrate([((-1, 0), 0), ((0, sqrt(3)), 3), + ((sqrt(3), 0), 3), ((0, -1), 0)], 1) == 3 + + hexagon = [((Rational(-1, 2), -sqrt(3) / 2), 0), + ((-1, 0), sqrt(3) / 2), + ((Rational(-1, 2), sqrt(3) / 2), sqrt(3)), + ((S.Half, sqrt(3) / 2), sqrt(3)), + ((1, 0), sqrt(3) / 2), + ((S.Half, -sqrt(3) / 2), 0)] + assert polytope_integrate(hexagon, 1) == S(3*sqrt(3)) / 2 + + # Non-convex polytopes + # Vertex representation + assert polytope_integrate(Polygon(Point(-1, -1), Point(-1, 1), + Point(1, 1), Point(0, 0), + Point(1, -1)), 1) == 3 + assert polytope_integrate(Polygon(Point(-1, -1), Point(-1, 1), + Point(0, 0), Point(1, 1), + Point(1, -1), Point(0, 0)), 1) == 2 + # Hyperplane representation + assert polytope_integrate([((-1, 0), 1), ((0, 1), 1), ((1, -1), 0), + ((1, 1), 0), ((0, -1), 1)], 1) == 3 + assert polytope_integrate([((-1, 0), 1), ((1, 1), 0), ((-1, 1), 0), + ((1, 0), 1), ((-1, -1), 0), + ((1, -1), 0)], 1) == 2 + + # Tests for 2D polytopes mentioned in Chin et al(Page 10): + # http://dilbert.engr.ucdavis.edu/~suku/quadrature/cls-integration.pdf + fig1 = Polygon(Point(1.220, -0.827), Point(-1.490, -4.503), + Point(-3.766, -1.622), Point(-4.240, -0.091), + Point(-3.160, 4), Point(-0.981, 4.447), + Point(0.132, 4.027)) + assert polytope_integrate(fig1, x**2 + x*y + y**2) ==\ + S(2031627344735367)/(8*10**12) + + fig2 = Polygon(Point(4.561, 2.317), Point(1.491, -1.315), + Point(-3.310, -3.164), Point(-4.845, -3.110), + Point(-4.569, 1.867)) + assert polytope_integrate(fig2, x**2 + x*y + y**2) ==\ + S(517091313866043)/(16*10**11) + + fig3 = Polygon(Point(-2.740, -1.888), Point(-3.292, 4.233), + Point(-2.723, -0.697), Point(-0.643, -3.151)) + assert polytope_integrate(fig3, x**2 + x*y + y**2) ==\ + S(147449361647041)/(8*10**12) + + fig4 = Polygon(Point(0.211, -4.622), Point(-2.684, 3.851), + Point(0.468, 4.879), Point(4.630, -1.325), + Point(-0.411, -1.044)) + assert polytope_integrate(fig4, x**2 + x*y + y**2) ==\ + S(180742845225803)/(10**12) + + # Tests for many polynomials with maximum degree given(2D case). + tri = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + polys = [] + expr1 = x**9*y + x**7*y**3 + 2*x**2*y**8 + expr2 = x**6*y**4 + x**5*y**5 + 2*y**10 + expr3 = x**10 + x**9*y + x**8*y**2 + x**5*y**5 + polys.extend((expr1, expr2, expr3)) + result_dict = polytope_integrate(tri, polys, max_degree=10) + assert result_dict[expr1] == Rational(615780107, 594) + assert result_dict[expr2] == Rational(13062161, 27) + assert result_dict[expr3] == Rational(1946257153, 924) + + tri = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + expr1 = x**7*y**1 + 2*x**2*y**6 + expr2 = x**6*y**4 + x**5*y**5 + 2*y**10 + expr3 = x**10 + x**9*y + x**8*y**2 + x**5*y**5 + polys.extend((expr1, expr2, expr3)) + assert polytope_integrate(tri, polys, max_degree=9) == \ + {x**7*y + 2*x**2*y**6: Rational(489262, 9)} + + # Tests when all integral of all monomials up to a max_degree is to be + # calculated. + assert polytope_integrate(Polygon(Point(0, 0), Point(0, 1), + Point(1, 1), Point(1, 0)), + max_degree=4) == {0: 0, 1: 1, x: S.Half, + x ** 2 * y ** 2: S.One / 9, + x ** 4: S.One / 5, + y ** 4: S.One / 5, + y: S.Half, + x * y ** 2: S.One / 6, + y ** 2: S.One / 3, + x ** 3: S.One / 4, + x ** 2 * y: S.One / 6, + x ** 3 * y: S.One / 8, + x * y: S.One / 4, + y ** 3: S.One / 4, + x ** 2: S.One / 3, + x * y ** 3: S.One / 8} + + # Tests for 3D polytopes + cube1 = [[(0, 0, 0), (0, 6, 6), (6, 6, 6), (3, 6, 0), + (0, 6, 0), (6, 0, 6), (3, 0, 0), (0, 0, 6)], + [1, 2, 3, 4], [3, 2, 5, 6], [1, 7, 5, 2], [0, 6, 5, 7], + [1, 4, 0, 7], [0, 4, 3, 6]] + assert polytope_integrate(cube1, 1) == S(162) + + # 3D Test cases in Chin et al(2015) + cube2 = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0), + (5, 0, 5), (5, 5, 0), (5, 5, 5)], + [3, 7, 6, 2], [1, 5, 7, 3], [5, 4, 6, 7], [0, 4, 5, 1], + [2, 0, 1, 3], [2, 6, 4, 0]] + + cube3 = [[(0, 0, 0), (5, 0, 0), (5, 4, 0), (3, 2, 0), (3, 5, 0), + (0, 5, 0), (0, 0, 5), (5, 0, 5), (5, 4, 5), (3, 2, 5), + (3, 5, 5), (0, 5, 5)], + [6, 11, 5, 0], [1, 7, 6, 0], [5, 4, 3, 2, 1, 0], [11, 10, 4, 5], + [10, 9, 3, 4], [9, 8, 2, 3], [8, 7, 1, 2], [7, 8, 9, 10, 11, 6]] + + cube4 = [[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), + (S.One / 4, S.One / 4, S.One / 4)], + [0, 2, 1], [1, 3, 0], [4, 2, 3], [4, 3, 1], + [0, 1, 2], [2, 4, 1], [0, 3, 2]] + + assert polytope_integrate(cube2, x ** 2 + y ** 2 + x * y + z ** 2) ==\ + Rational(15625, 4) + assert polytope_integrate(cube3, x ** 2 + y ** 2 + x * y + z ** 2) ==\ + S(33835) / 12 + assert polytope_integrate(cube4, x ** 2 + y ** 2 + x * y + z ** 2) ==\ + S(37) / 960 + + # Test cases from Mathematica's PolyhedronData library + octahedron = [[(S.NegativeOne / sqrt(2), 0, 0), (0, S.One / sqrt(2), 0), + (0, 0, S.NegativeOne / sqrt(2)), (0, 0, S.One / sqrt(2)), + (0, S.NegativeOne / sqrt(2), 0), (S.One / sqrt(2), 0, 0)], + [3, 4, 5], [3, 5, 1], [3, 1, 0], [3, 0, 4], [4, 0, 2], + [4, 2, 5], [2, 0, 1], [5, 2, 1]] + + assert polytope_integrate(octahedron, 1) == sqrt(2) / 3 + + great_stellated_dodecahedron =\ + [[(-0.32491969623290634095, 0, 0.42532540417601993887), + (0.32491969623290634095, 0, -0.42532540417601993887), + (-0.52573111211913359231, 0, 0.10040570794311363956), + (0.52573111211913359231, 0, -0.10040570794311363956), + (-0.10040570794311363956, -0.3090169943749474241, 0.42532540417601993887), + (-0.10040570794311363956, 0.30901699437494742410, 0.42532540417601993887), + (0.10040570794311363956, -0.3090169943749474241, -0.42532540417601993887), + (0.10040570794311363956, 0.30901699437494742410, -0.42532540417601993887), + (-0.16245984811645317047, -0.5, 0.10040570794311363956), + (-0.16245984811645317047, 0.5, 0.10040570794311363956), + (0.16245984811645317047, -0.5, -0.10040570794311363956), + (0.16245984811645317047, 0.5, -0.10040570794311363956), + (-0.42532540417601993887, -0.3090169943749474241, -0.10040570794311363956), + (-0.42532540417601993887, 0.30901699437494742410, -0.10040570794311363956), + (-0.26286555605956679615, 0.1909830056250525759, -0.42532540417601993887), + (-0.26286555605956679615, -0.1909830056250525759, -0.42532540417601993887), + (0.26286555605956679615, 0.1909830056250525759, 0.42532540417601993887), + (0.26286555605956679615, -0.1909830056250525759, 0.42532540417601993887), + (0.42532540417601993887, -0.3090169943749474241, 0.10040570794311363956), + (0.42532540417601993887, 0.30901699437494742410, 0.10040570794311363956)], + [12, 3, 0, 6, 16], [17, 7, 0, 3, 13], + [9, 6, 0, 7, 8], [18, 2, 1, 4, 14], + [15, 5, 1, 2, 19], [11, 4, 1, 5, 10], + [8, 19, 2, 18, 9], [10, 13, 3, 12, 11], + [16, 14, 4, 11, 12], [13, 10, 5, 15, 17], + [14, 16, 6, 9, 18], [19, 8, 7, 17, 15]] + # Actual volume is : 0.163118960624632 + assert Abs(polytope_integrate(great_stellated_dodecahedron, 1) -\ + 0.163118960624632) < 1e-12 + + expr = x **2 + y ** 2 + z ** 2 + octahedron_five_compound = [[(0, -0.7071067811865475244, 0), + (0, 0.70710678118654752440, 0), + (0.1148764602736805918, + -0.35355339059327376220, -0.60150095500754567366), + (0.1148764602736805918, 0.35355339059327376220, + -0.60150095500754567366), + (0.18587401723009224507, + -0.57206140281768429760, 0.37174803446018449013), + (0.18587401723009224507, 0.57206140281768429760, + 0.37174803446018449013), + (0.30075047750377283683, -0.21850801222441053540, + 0.60150095500754567366), + (0.30075047750377283683, 0.21850801222441053540, + 0.60150095500754567366), + (0.48662449473386508189, -0.35355339059327376220, + -0.37174803446018449013), + (0.48662449473386508189, 0.35355339059327376220, + -0.37174803446018449013), + (-0.60150095500754567366, 0, -0.37174803446018449013), + (-0.30075047750377283683, -0.21850801222441053540, + -0.60150095500754567366), + (-0.30075047750377283683, 0.21850801222441053540, + -0.60150095500754567366), + (0.60150095500754567366, 0, 0.37174803446018449013), + (0.4156269377774534286, -0.57206140281768429760, 0), + (0.4156269377774534286, 0.57206140281768429760, 0), + (0.37174803446018449013, 0, -0.60150095500754567366), + (-0.4156269377774534286, -0.57206140281768429760, 0), + (-0.4156269377774534286, 0.57206140281768429760, 0), + (-0.67249851196395732696, -0.21850801222441053540, 0), + (-0.67249851196395732696, 0.21850801222441053540, 0), + (0.67249851196395732696, -0.21850801222441053540, 0), + (0.67249851196395732696, 0.21850801222441053540, 0), + (-0.37174803446018449013, 0, 0.60150095500754567366), + (-0.48662449473386508189, -0.35355339059327376220, + 0.37174803446018449013), + (-0.48662449473386508189, 0.35355339059327376220, + 0.37174803446018449013), + (-0.18587401723009224507, -0.57206140281768429760, + -0.37174803446018449013), + (-0.18587401723009224507, 0.57206140281768429760, + -0.37174803446018449013), + (-0.11487646027368059176, -0.35355339059327376220, + 0.60150095500754567366), + (-0.11487646027368059176, 0.35355339059327376220, + 0.60150095500754567366)], + [0, 10, 16], [23, 10, 0], [16, 13, 0], + [0, 13, 23], [16, 10, 1], [1, 10, 23], + [1, 13, 16], [23, 13, 1], [2, 4, 19], + [22, 4, 2], [2, 19, 27], [27, 22, 2], + [20, 5, 3], [3, 5, 21], [26, 20, 3], + [3, 21, 26], [29, 19, 4], [4, 22, 29], + [5, 20, 28], [28, 21, 5], [6, 8, 15], + [17, 8, 6], [6, 15, 25], [25, 17, 6], + [14, 9, 7], [7, 9, 18], [24, 14, 7], + [7, 18, 24], [8, 12, 15], [17, 12, 8], + [14, 11, 9], [9, 11, 18], [11, 14, 24], + [24, 18, 11], [25, 15, 12], [12, 17, 25], + [29, 27, 19], [20, 26, 28], [28, 26, 21], + [22, 27, 29]] + assert Abs(polytope_integrate(octahedron_five_compound, expr)) - 0.353553\ + < 1e-6 + + cube_five_compound = [[(-0.1624598481164531631, -0.5, -0.6881909602355867691), + (-0.1624598481164531631, 0.5, -0.6881909602355867691), + (0.1624598481164531631, -0.5, 0.68819096023558676910), + (0.1624598481164531631, 0.5, 0.68819096023558676910), + (-0.52573111211913359231, 0, -0.6881909602355867691), + (0.52573111211913359231, 0, 0.68819096023558676910), + (-0.26286555605956679615, -0.8090169943749474241, + -0.1624598481164531631), + (-0.26286555605956679615, 0.8090169943749474241, + -0.1624598481164531631), + (0.26286555605956680301, -0.8090169943749474241, + 0.1624598481164531631), + (0.26286555605956680301, 0.8090169943749474241, + 0.1624598481164531631), + (-0.42532540417601993887, -0.3090169943749474241, + 0.68819096023558676910), + (-0.42532540417601993887, 0.30901699437494742410, + 0.68819096023558676910), + (0.42532540417601996609, -0.3090169943749474241, + -0.6881909602355867691), + (0.42532540417601996609, 0.30901699437494742410, + -0.6881909602355867691), + (-0.6881909602355867691, -0.5, 0.1624598481164531631), + (-0.6881909602355867691, 0.5, 0.1624598481164531631), + (0.68819096023558676910, -0.5, -0.1624598481164531631), + (0.68819096023558676910, 0.5, -0.1624598481164531631), + (-0.85065080835203998877, 0, -0.1624598481164531631), + (0.85065080835203993218, 0, 0.1624598481164531631)], + [18, 10, 3, 7], [13, 19, 8, 0], [18, 0, 8, 10], + [3, 19, 13, 7], [18, 7, 13, 0], [8, 19, 3, 10], + [6, 2, 11, 18], [1, 9, 19, 12], [11, 9, 1, 18], + [6, 12, 19, 2], [1, 12, 6, 18], [11, 2, 19, 9], + [4, 14, 11, 7], [17, 5, 8, 12], [4, 12, 8, 14], + [11, 5, 17, 7], [4, 7, 17, 12], [8, 5, 11, 14], + [6, 10, 15, 4], [13, 9, 5, 16], [15, 9, 13, 4], + [6, 16, 5, 10], [13, 16, 6, 4], [15, 10, 5, 9], + [14, 15, 1, 0], [16, 17, 3, 2], [14, 2, 3, 15], + [1, 17, 16, 0], [14, 0, 16, 2], [3, 17, 1, 15]] + assert Abs(polytope_integrate(cube_five_compound, expr) - 1.25) < 1e-12 + + echidnahedron = [[(0, 0, -2.4898982848827801995), + (0, 0, 2.4898982848827802734), + (0, -4.2360679774997896964, -2.4898982848827801995), + (0, -4.2360679774997896964, 2.4898982848827802734), + (0, 4.2360679774997896964, -2.4898982848827801995), + (0, 4.2360679774997896964, 2.4898982848827802734), + (-4.0287400534704067567, -1.3090169943749474241, -2.4898982848827801995), + (-4.0287400534704067567, -1.3090169943749474241, 2.4898982848827802734), + (-4.0287400534704067567, 1.3090169943749474241, -2.4898982848827801995), + (-4.0287400534704067567, 1.3090169943749474241, 2.4898982848827802734), + (4.0287400534704069747, -1.3090169943749474241, -2.4898982848827801995), + (4.0287400534704069747, -1.3090169943749474241, 2.4898982848827802734), + (4.0287400534704069747, 1.3090169943749474241, -2.4898982848827801995), + (4.0287400534704069747, 1.3090169943749474241, 2.4898982848827802734), + (-2.4898982848827801995, -3.4270509831248422723, -2.4898982848827801995), + (-2.4898982848827801995, -3.4270509831248422723, 2.4898982848827802734), + (-2.4898982848827801995, 3.4270509831248422723, -2.4898982848827801995), + (-2.4898982848827801995, 3.4270509831248422723, 2.4898982848827802734), + (2.4898982848827802734, -3.4270509831248422723, -2.4898982848827801995), + (2.4898982848827802734, -3.4270509831248422723, 2.4898982848827802734), + (2.4898982848827802734, 3.4270509831248422723, -2.4898982848827801995), + (2.4898982848827802734, 3.4270509831248422723, 2.4898982848827802734), + (-4.7169310137059934362, -0.8090169943749474241, -1.1135163644116066184), + (-4.7169310137059934362, 0.8090169943749474241, -1.1135163644116066184), + (4.7169310137059937438, -0.8090169943749474241, 1.11351636441160673519), + (4.7169310137059937438, 0.8090169943749474241, 1.11351636441160673519), + (-4.2916056095299737777, -2.1180339887498948482, 1.11351636441160673519), + (-4.2916056095299737777, 2.1180339887498948482, 1.11351636441160673519), + (4.2916056095299737777, -2.1180339887498948482, -1.1135163644116066184), + (4.2916056095299737777, 2.1180339887498948482, -1.1135163644116066184), + (-3.6034146492943870399, 0, -3.3405490932348205213), + (3.6034146492943870399, 0, 3.3405490932348202056), + (-3.3405490932348205213, -3.4270509831248422723, 1.11351636441160673519), + (-3.3405490932348205213, 3.4270509831248422723, 1.11351636441160673519), + (3.3405490932348202056, -3.4270509831248422723, -1.1135163644116066184), + (3.3405490932348202056, 3.4270509831248422723, -1.1135163644116066184), + (-2.9152236890588002395, -2.1180339887498948482, 3.3405490932348202056), + (-2.9152236890588002395, 2.1180339887498948482, 3.3405490932348202056), + (2.9152236890588002395, -2.1180339887498948482, -3.3405490932348205213), + (2.9152236890588002395, 2.1180339887498948482, -3.3405490932348205213), + (-2.2270327288232132368, 0, -1.1135163644116066184), + (-2.2270327288232132368, -4.2360679774997896964, -1.1135163644116066184), + (-2.2270327288232132368, 4.2360679774997896964, -1.1135163644116066184), + (2.2270327288232134704, 0, 1.11351636441160673519), + (2.2270327288232134704, -4.2360679774997896964, 1.11351636441160673519), + (2.2270327288232134704, 4.2360679774997896964, 1.11351636441160673519), + (-1.8017073246471935200, -1.3090169943749474241, 1.11351636441160673519), + (-1.8017073246471935200, 1.3090169943749474241, 1.11351636441160673519), + (1.8017073246471935043, -1.3090169943749474241, -1.1135163644116066184), + (1.8017073246471935043, 1.3090169943749474241, -1.1135163644116066184), + (-1.3763819204711735382, 0, -4.7169310137059934362), + (-1.3763819204711735382, 0, 0.26286555605956679615), + (1.37638192047117353821, 0, 4.7169310137059937438), + (1.37638192047117353821, 0, -0.26286555605956679615), + (-1.1135163644116066184, -3.4270509831248422723, -3.3405490932348205213), + (-1.1135163644116066184, -0.8090169943749474241, 4.7169310137059937438), + (-1.1135163644116066184, -0.8090169943749474241, -0.26286555605956679615), + (-1.1135163644116066184, 0.8090169943749474241, 4.7169310137059937438), + (-1.1135163644116066184, 0.8090169943749474241, -0.26286555605956679615), + (-1.1135163644116066184, 3.4270509831248422723, -3.3405490932348205213), + (1.11351636441160673519, -3.4270509831248422723, 3.3405490932348202056), + (1.11351636441160673519, -0.8090169943749474241, -4.7169310137059934362), + (1.11351636441160673519, -0.8090169943749474241, 0.26286555605956679615), + (1.11351636441160673519, 0.8090169943749474241, -4.7169310137059934362), + (1.11351636441160673519, 0.8090169943749474241, 0.26286555605956679615), + (1.11351636441160673519, 3.4270509831248422723, 3.3405490932348202056), + (-0.85065080835203998877, 0, 1.11351636441160673519), + (0.85065080835203993218, 0, -1.1135163644116066184), + (-0.6881909602355867691, -0.5, -1.1135163644116066184), + (-0.6881909602355867691, 0.5, -1.1135163644116066184), + (-0.6881909602355867691, -4.7360679774997896964, -1.1135163644116066184), + (-0.6881909602355867691, -2.1180339887498948482, -1.1135163644116066184), + (-0.6881909602355867691, 2.1180339887498948482, -1.1135163644116066184), + (-0.6881909602355867691, 4.7360679774997896964, -1.1135163644116066184), + (0.68819096023558676910, -0.5, 1.11351636441160673519), + (0.68819096023558676910, 0.5, 1.11351636441160673519), + (0.68819096023558676910, -4.7360679774997896964, 1.11351636441160673519), + (0.68819096023558676910, -2.1180339887498948482, 1.11351636441160673519), + (0.68819096023558676910, 2.1180339887498948482, 1.11351636441160673519), + (0.68819096023558676910, 4.7360679774997896964, 1.11351636441160673519), + (-0.42532540417601993887, -1.3090169943749474241, -4.7169310137059934362), + (-0.42532540417601993887, -1.3090169943749474241, 0.26286555605956679615), + (-0.42532540417601993887, 1.3090169943749474241, -4.7169310137059934362), + (-0.42532540417601993887, 1.3090169943749474241, 0.26286555605956679615), + (-0.26286555605956679615, -0.8090169943749474241, 1.11351636441160673519), + (-0.26286555605956679615, 0.8090169943749474241, 1.11351636441160673519), + (0.26286555605956679615, -0.8090169943749474241, -1.1135163644116066184), + (0.26286555605956679615, 0.8090169943749474241, -1.1135163644116066184), + (0.42532540417601996609, -1.3090169943749474241, 4.7169310137059937438), + (0.42532540417601996609, -1.3090169943749474241, -0.26286555605956679615), + (0.42532540417601996609, 1.3090169943749474241, 4.7169310137059937438), + (0.42532540417601996609, 1.3090169943749474241, -0.26286555605956679615)], + [9, 66, 47], [44, 62, 77], [20, 91, 49], [33, 47, 83], + [3, 77, 84], [12, 49, 53], [36, 84, 66], [28, 53, 62], + [73, 83, 91], [15, 84, 46], [25, 64, 43], [16, 58, 72], + [26, 46, 51], [11, 43, 74], [4, 72, 91], [60, 74, 84], + [35, 91, 64], [23, 51, 58], [19, 74, 77], [79, 83, 78], + [6, 56, 40], [76, 77, 81], [21, 78, 75], [8, 40, 58], + [31, 75, 74], [42, 58, 83], [41, 81, 56], [13, 75, 43], + [27, 51, 47], [2, 89, 71], [24, 43, 62], [17, 47, 85], + [14, 71, 56], [65, 85, 75], [22, 56, 51], [34, 62, 89], + [5, 85, 78], [32, 81, 46], [10, 53, 48], [45, 78, 64], + [7, 46, 66], [18, 48, 89], [37, 66, 85], [70, 89, 81], + [29, 64, 53], [88, 74, 1], [38, 67, 48], [42, 83, 72], + [57, 1, 85], [34, 48, 62], [59, 72, 87], [19, 62, 74], + [63, 87, 67], [17, 85, 83], [52, 75, 1], [39, 87, 49], + [22, 51, 40], [55, 1, 66], [29, 49, 64], [30, 40, 69], + [13, 64, 75], [82, 69, 87], [7, 66, 51], [90, 85, 1], + [59, 69, 72], [70, 81, 71], [88, 1, 84], [73, 72, 83], + [54, 71, 68], [5, 83, 85], [50, 68, 69], [3, 84, 81], + [57, 66, 1], [30, 68, 40], [28, 62, 48], [52, 1, 74], + [23, 40, 51], [38, 48, 86], [9, 51, 66], [80, 86, 68], + [11, 74, 62], [55, 84, 1], [54, 86, 71], [35, 64, 49], + [90, 1, 75], [41, 71, 81], [39, 49, 67], [15, 81, 84], + [61, 67, 86], [21, 75, 64], [24, 53, 43], [50, 69, 0], + [37, 85, 47], [31, 43, 75], [61, 0, 67], [27, 47, 58], + [10, 67, 53], [8, 58, 69], [90, 75, 85], [45, 91, 78], + [80, 68, 0], [36, 66, 46], [65, 78, 85], [63, 0, 87], + [32, 46, 56], [20, 87, 91], [14, 56, 68], [57, 85, 66], + [33, 58, 47], [61, 86, 0], [60, 84, 77], [37, 47, 66], + [82, 0, 69], [44, 77, 89], [16, 69, 58], [18, 89, 86], + [55, 66, 84], [26, 56, 46], [63, 67, 0], [31, 74, 43], + [36, 46, 84], [50, 0, 68], [25, 43, 53], [6, 68, 56], + [12, 53, 67], [88, 84, 74], [76, 89, 77], [82, 87, 0], + [65, 75, 78], [60, 77, 74], [80, 0, 86], [79, 78, 91], + [2, 86, 89], [4, 91, 87], [52, 74, 75], [21, 64, 78], + [18, 86, 48], [23, 58, 40], [5, 78, 83], [28, 48, 53], + [6, 40, 68], [25, 53, 64], [54, 68, 86], [33, 83, 58], + [17, 83, 47], [12, 67, 49], [41, 56, 71], [9, 47, 51], + [35, 49, 91], [2, 71, 86], [79, 91, 83], [38, 86, 67], + [26, 51, 56], [7, 51, 46], [4, 87, 72], [34, 89, 48], + [15, 46, 81], [42, 72, 58], [10, 48, 67], [27, 58, 51], + [39, 67, 87], [76, 81, 89], [3, 81, 77], [8, 69, 40], + [29, 53, 49], [19, 77, 62], [22, 40, 56], [20, 49, 87], + [32, 56, 81], [59, 87, 69], [24, 62, 53], [11, 62, 43], + [14, 68, 71], [73, 91, 72], [13, 43, 64], [70, 71, 89], + [16, 72, 69], [44, 89, 62], [30, 69, 68], [45, 64, 91]] + # Actual volume is : 51.405764746872634 + assert Abs(polytope_integrate(echidnahedron, 1) - 51.4057647468726) < 1e-12 + assert Abs(polytope_integrate(echidnahedron, expr) - 253.569603474519) <\ + 1e-12 + + # Tests for many polynomials with maximum degree given(2D case). + assert polytope_integrate(cube2, [x**2, y*z], max_degree=2) == \ + {y * z: 3125 / S(4), x ** 2: 3125 / S(3)} + + assert polytope_integrate(cube2, max_degree=2) == \ + {1: 125, x: 625 / S(2), x * z: 3125 / S(4), y: 625 / S(2), + y * z: 3125 / S(4), z ** 2: 3125 / S(3), y ** 2: 3125 / S(3), + z: 625 / S(2), x * y: 3125 / S(4), x ** 2: 3125 / S(3)} + +def test_point_sort(): + assert point_sort([Point(0, 0), Point(1, 0), Point(1, 1)]) == \ + [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)] + + fig6 = Polygon((0, 0), (1, 0), (1, 1)) + assert polytope_integrate(fig6, x*y) == Rational(-1, 8) + assert polytope_integrate(fig6, x*y, clockwise = True) == Rational(1, 8) + + +def test_polytopes_intersecting_sides(): + fig5 = Polygon(Point(-4.165, -0.832), Point(-3.668, 1.568), + Point(-3.266, 1.279), Point(-1.090, -2.080), + Point(3.313, -0.683), Point(3.033, -4.845), + Point(-4.395, 4.840), Point(-1.007, -3.328)) + assert polytope_integrate(fig5, x**2 + x*y + y**2) ==\ + S(1633405224899363)/(24*10**12) + + fig6 = Polygon(Point(-3.018, -4.473), Point(-0.103, 2.378), + Point(-1.605, -2.308), Point(4.516, -0.771), + Point(4.203, 0.478)) + assert polytope_integrate(fig6, x**2 + x*y + y**2) ==\ + S(88161333955921)/(3*10**12) + + +def test_max_degree(): + polygon = Polygon((0, 0), (0, 1), (1, 1), (1, 0)) + polys = [1, x, y, x*y, x**2*y, x*y**2] + assert polytope_integrate(polygon, polys, max_degree=3) == \ + {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4), x**2*y: Rational(1, 6), x*y**2: Rational(1, 6)} + assert polytope_integrate(polygon, polys, max_degree=2) == \ + {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4)} + assert polytope_integrate(polygon, polys, max_degree=1) == \ + {1: 1, x: S.Half, y: S.Half} + + +def test_main_integrate3d(): + cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + vertices = cube[0] + faces = cube[1:] + hp_params = hyperplane_parameters(faces, vertices) + assert main_integrate3d(1, faces, vertices, hp_params) == -125 + assert main_integrate3d(1, faces, vertices, hp_params, max_degree=1) == \ + {1: -125, y: Rational(-625, 2), z: Rational(-625, 2), x: Rational(-625, 2)} + + +def test_main_integrate(): + triangle = Polygon((0, 3), (5, 3), (1, 1)) + facets = triangle.sides + hp_params = hyperplane_parameters(triangle) + assert main_integrate(x**2 + y**2, facets, hp_params) == Rational(325, 6) + assert main_integrate(x**2 + y**2, facets, hp_params, max_degree=1) == \ + {0: 0, 1: 5, y: Rational(35, 3), x: 10} + + +def test_polygon_integrate(): + cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + facet = cube[1] + facets = cube[1:] + vertices = cube[0] + assert polygon_integrate(facet, [(0, 1, 0), 5], 0, facets, vertices, 1, 0) == -25 + + +def test_distance_to_side(): + point = (0, 0, 0) + assert distance_to_side(point, [(0, 0, 1), (0, 1, 0)], (1, 0, 0)) == -sqrt(2)/2 + + +def test_lineseg_integrate(): + polygon = [(0, 5, 0), (5, 5, 0), (5, 5, 5), (0, 5, 5)] + line_seg = [(0, 5, 0), (5, 5, 0)] + assert lineseg_integrate(polygon, 0, line_seg, 1, 0) == 5 + assert lineseg_integrate(polygon, 0, line_seg, 0, 0) == 0 + + +def test_integration_reduction(): + triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + facets = triangle.sides + a, b = hyperplane_parameters(triangle)[0] + assert integration_reduction(facets, 0, a, b, 1, (x, y), 0) == 5 + assert integration_reduction(facets, 0, a, b, 0, (x, y), 0) == 0 + + +def test_integration_reduction_dynamic(): + triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + facets = triangle.sides + a, b = hyperplane_parameters(triangle)[0] + x0 = facets[0].points[0] + monomial_values = [[0, 0, 0, 0], [1, 0, 0, 5],\ + [y, 0, 1, 15], [x, 1, 0, None]] + + assert integration_reduction_dynamic(facets, 0, a, b, x, 1, (x, y), 1,\ + 0, 1, x0, monomial_values, 3) == Rational(25, 2) + assert integration_reduction_dynamic(facets, 0, a, b, 0, 1, (x, y), 1,\ + 0, 1, x0, monomial_values, 3) == 0 + + +def test_is_vertex(): + assert is_vertex(2) is False + assert is_vertex((2, 3)) is True + assert is_vertex(Point(2, 3)) is True + assert is_vertex((2, 3, 4)) is True + assert is_vertex((2, 3, 4, 5)) is False + + +def test_issue_19234(): + polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) + polys = [ 1, x, y, x*y, x**2*y, x*y**2] + assert polytope_integrate(polygon, polys) == \ + {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4), x**2*y: Rational(1, 6), x*y**2: Rational(1, 6)} + polys = [ 1, x, y, x*y, 3 + x**2*y, x + x*y**2] + assert polytope_integrate(polygon, polys) == \ + {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4), x**2*y + 3: Rational(19, 6), x*y**2 + x: Rational(2, 3)} diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_laplace.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_laplace.py new file mode 100644 index 0000000000000000000000000000000000000000..cb7222d01e3dcb3e14e8d0564610ab553e637155 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_laplace.py @@ -0,0 +1,774 @@ +from sympy.integrals.laplace import ( + laplace_transform, inverse_laplace_transform, + LaplaceTransform, InverseLaplaceTransform, + _laplace_deep_collect, laplace_correspondence, + laplace_initial_conds) +from sympy.core.function import Function, expand_mul +from sympy.core import EulerGamma, Subs, Derivative, diff +from sympy.core.exprtools import factor_terms +from sympy.core.numbers import I, oo, pi +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, symbols +from sympy.simplify.simplify import simplify +from sympy.functions.elementary.complexes import Abs, re +from sympy.functions.elementary.exponential import exp, log, exp_polar +from sympy.functions.elementary.hyperbolic import cosh, sinh, coth, asinh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import atan, cos, sin +from sympy.logic.boolalg import And +from sympy.functions.special.gamma_functions import ( + lowergamma, gamma, uppergamma) +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.functions.special.zeta_functions import lerchphi +from sympy.functions.special.error_functions import ( + fresnelc, fresnels, erf, erfc, Ei, Ci, expint, E1) +from sympy.functions.special.bessel import besseli, besselj, besselk, bessely +from sympy.testing.pytest import slow, warns_deprecated_sympy +from sympy.matrices import Matrix, eye +from sympy.abc import s + + +@slow +def test_laplace_transform(): + LT = laplace_transform + ILT = inverse_laplace_transform + a, b, c = symbols('a, b, c', positive=True) + np = symbols('np', integer=True, positive=True) + t, w, x = symbols('t, w, x') + f = Function('f') + F = Function('F') + g = Function('g') + y = Function('y') + Y = Function('Y') + + # Test helper functions + assert ( + _laplace_deep_collect(exp((t+a)*(t+b)) + + besselj(2, exp((t+a)*(t+b)-t**2)), t) == + exp(a*b + t**2 + t*(a + b)) + besselj(2, exp(a*b + t*(a + b)))) + L = laplace_transform(diff(y(t), t, 3), t, s, noconds=True) + L = laplace_correspondence(L, {y: Y}) + L = laplace_initial_conds(L, t, {y: [2, 4, 8, 16, 32]}) + assert L == s**3*Y(s) - 2*s**2 - 4*s - 8 + # Test whether `noconds=True` in `doit`: + assert (2*LaplaceTransform(exp(t), t, s) - 1).doit() == -1 + 2/(s - 1) + assert (LT(a*t+t**2+t**(S(5)/2), t, s) == + (a/s**2 + 2/s**3 + 15*sqrt(pi)/(8*s**(S(7)/2)), 0, True)) + assert LT(b/(t+a), t, s) == (-b*exp(-a*s)*Ei(-a*s), 0, True) + assert (LT(1/sqrt(t+a), t, s) == + (sqrt(pi)*sqrt(1/s)*exp(a*s)*erfc(sqrt(a)*sqrt(s)), 0, True)) + assert (LT(sqrt(t)/(t+a), t, s) == + (-pi*sqrt(a)*exp(a*s)*erfc(sqrt(a)*sqrt(s)) + sqrt(pi)*sqrt(1/s), + 0, True)) + assert (LT((t+a)**(-S(3)/2), t, s) == + (-2*sqrt(pi)*sqrt(s)*exp(a*s)*erfc(sqrt(a)*sqrt(s)) + 2/sqrt(a), + 0, True)) + assert (LT(t**(S(1)/2)*(t+a)**(-1), t, s) == + (-pi*sqrt(a)*exp(a*s)*erfc(sqrt(a)*sqrt(s)) + sqrt(pi)*sqrt(1/s), + 0, True)) + assert (LT(1/(a*sqrt(t) + t**(3/2)), t, s) == + (pi*sqrt(a)*exp(a*s)*erfc(sqrt(a)*sqrt(s)), 0, True)) + assert (LT((t+a)**b, t, s) == + (s**(-b - 1)*exp(-a*s)*uppergamma(b + 1, a*s), 0, True)) + assert LT(t**5/(t+a), t, s) == (120*a**5*uppergamma(-5, a*s), 0, True) + assert LT(exp(t), t, s) == (1/(s - 1), 1, True) + assert LT(exp(2*t), t, s) == (1/(s - 2), 2, True) + assert LT(exp(a*t), t, s) == (1/(s - a), a, True) + assert LT(exp(a*(t-b)), t, s) == (exp(-a*b)/(-a + s), a, True) + assert LT(t*exp(-a*(t)), t, s) == ((a + s)**(-2), -a, True) + assert LT(t*exp(-a*(t-b)), t, s) == (exp(a*b)/(a + s)**2, -a, True) + assert LT(b*t*exp(-a*t), t, s) == (b/(a + s)**2, -a, True) + assert LT(exp(-a*exp(-t)), t, s) == (lowergamma(s, a)/a**s, 0, True) + assert LT(exp(-a*exp(t)), t, s) == (a**s*uppergamma(-s, a), 0, True) + assert (LT(t**(S(7)/4)*exp(-8*t)/gamma(S(11)/4), t, s) == + ((s + 8)**(-S(11)/4), -8, True)) + assert (LT(t**(S(3)/2)*exp(-8*t), t, s) == + (3*sqrt(pi)/(4*(s + 8)**(S(5)/2)), -8, True)) + assert LT(t**a*exp(-a*t), t, s) == ((a+s)**(-a-1)*gamma(a+1), -a, True) + assert (LT(b*exp(-a*t**2), t, s) == + (sqrt(pi)*b*exp(s**2/(4*a))*erfc(s/(2*sqrt(a)))/(2*sqrt(a)), + 0, True)) + assert (LT(exp(-2*t**2), t, s) == + (sqrt(2)*sqrt(pi)*exp(s**2/8)*erfc(sqrt(2)*s/4)/4, 0, True)) + assert (LT(b*exp(2*t**2), t, s) == + (b*LaplaceTransform(exp(2*t**2), t, s), -oo, True)) + assert (LT(t*exp(-a*t**2), t, s) == + (1/(2*a) - s*erfc(s/(2*sqrt(a)))/(4*sqrt(pi)*a**(S(3)/2)), + 0, True)) + assert (LT(exp(-a/t), t, s) == + (2*sqrt(a)*sqrt(1/s)*besselk(1, 2*sqrt(a)*sqrt(s)), 0, True)) + assert LT(sqrt(t)*exp(-a/t), t, s, simplify=True) == ( + sqrt(pi)*(sqrt(a)*sqrt(s) + 1/S(2))*sqrt(s**(-3)) * + exp(-2*sqrt(a)*sqrt(s)), 0, True) + assert (LT(exp(-a/t)/sqrt(t), t, s) == + (sqrt(pi)*sqrt(1/s)*exp(-2*sqrt(a)*sqrt(s)), 0, True)) + assert (LT(exp(-a/t)/(t*sqrt(t)), t, s) == + (sqrt(pi)*sqrt(1/a)*exp(-2*sqrt(a)*sqrt(s)), 0, True)) + # TODO: rules with sqrt(a*t) and sqrt(a/t) have stopped working after + # changes to as_base_exp + # assert ( + # LT(exp(-2*sqrt(a*t)), t, s) == + # (1/s - sqrt(pi)*sqrt(a) * exp(a/s)*erfc(sqrt(a)*sqrt(1/s)) / + # s**(S(3)/2), 0, True)) + # assert LT(exp(-2*sqrt(a*t))/sqrt(t), t, s) == ( + # exp(a/s)*erfc(sqrt(a) * sqrt(1/s))*(sqrt(pi)*sqrt(1/s)), 0, True) + assert (LT(t**4*exp(-2/t), t, s) == + (8*sqrt(2)*(1/s)**(S(5)/2)*besselk(5, 2*sqrt(2)*sqrt(s)), + 0, True)) + assert LT(sinh(a*t), t, s) == (a/(-a**2 + s**2), a, True) + assert (LT(b*sinh(a*t)**2, t, s) == + (2*a**2*b/(-4*a**2*s + s**3), 2*a, True)) + assert (LT(b*sinh(a*t)**2, t, s, simplify=True) == + (2*a**2*b/(s*(-4*a**2 + s**2)), 2*a, True)) + # The following line confirms that issue #21202 is solved + assert LT(cosh(2*t), t, s) == (s/(-4 + s**2), 2, True) + assert LT(cosh(a*t), t, s) == (s/(-a**2 + s**2), a, True) + assert (LT(cosh(a*t)**2, t, s, simplify=True) == + ((2*a**2 - s**2)/(s*(4*a**2 - s**2)), 2*a, True)) + assert (LT(sinh(x+3), x, s, simplify=True) == + ((s*sinh(3) + cosh(3))/(s**2 - 1), 1, True)) + L, _, _ = LT(42*sin(w*t+x)**2, t, s) + assert ( + L - + 21*(s**2 + s*(-s*cos(2*x) + 2*w*sin(2*x)) + + 4*w**2)/(s*(s**2 + 4*w**2))).simplify() == 0 + # The following line replaces the old test test_issue_7173() + assert LT(sinh(a*t)*cosh(a*t), t, s, simplify=True) == (a/(-4*a**2 + s**2), + 2*a, True) + assert LT(sinh(a*t)/t, t, s) == (log((a + s)/(-a + s))/2, a, True) + assert (LT(t**(-S(3)/2)*sinh(a*t), t, s) == + (-sqrt(pi)*(sqrt(-a + s) - sqrt(a + s)), a, True)) + # assert (LT(sinh(2*sqrt(a*t)), t, s) == + # (sqrt(pi)*sqrt(a)*exp(a/s)/s**(S(3)/2), 0, True)) + # assert (LT(sqrt(t)*sinh(2*sqrt(a*t)), t, s, simplify=True) == + # ((-sqrt(a)*s**(S(5)/2) + sqrt(pi)*s**2*(2*a + s)*exp(a/s) * + # erf(sqrt(a)*sqrt(1/s))/2)/s**(S(9)/2), 0, True)) + # assert (LT(sinh(2*sqrt(a*t))/sqrt(t), t, s) == + # (sqrt(pi)*exp(a/s)*erf(sqrt(a)*sqrt(1/s))/sqrt(s), 0, True)) + # assert (LT(sinh(sqrt(a*t))**2/sqrt(t), t, s) == + # (sqrt(pi)*(exp(a/s) - 1)/(2*sqrt(s)), 0, True)) + assert (LT(t**(S(3)/7)*cosh(a*t), t, s) == + (((a + s)**(-S(10)/7) + (-a+s)**(-S(10)/7))*gamma(S(10)/7)/2, + a, True)) + # assert (LT(cosh(2*sqrt(a*t)), t, s) == + # (sqrt(pi)*sqrt(a)*exp(a/s)*erf(sqrt(a)*sqrt(1/s))/s**(S(3)/2) + + # 1/s, 0, True)) + # assert (LT(sqrt(t)*cosh(2*sqrt(a*t)), t, s) == + # (sqrt(pi)*(a + s/2)*exp(a/s)/s**(S(5)/2), 0, True)) + # assert (LT(cosh(2*sqrt(a*t))/sqrt(t), t, s) == + # (sqrt(pi)*exp(a/s)/sqrt(s), 0, True)) + # assert (LT(cosh(sqrt(a*t))**2/sqrt(t), t, s) == + # (sqrt(pi)*(exp(a/s) + 1)/(2*sqrt(s)), 0, True)) + assert LT(log(t), t, s, simplify=True) == ( + (-log(s) - EulerGamma)/s, 0, True) + assert (LT(-log(t/a), t, s, simplify=True) == + ((log(a) + log(s) + EulerGamma)/s, 0, True)) + assert LT(log(1+a*t), t, s) == (-exp(s/a)*Ei(-s/a)/s, 0, True) + assert (LT(log(t+a), t, s, simplify=True) == + ((s*log(a) - exp(s/a)*Ei(-s/a))/s**2, 0, True)) + assert (LT(log(t)/sqrt(t), t, s, simplify=True) == + (sqrt(pi)*(-log(s) - log(4) - EulerGamma)/sqrt(s), 0, True)) + assert (LT(t**(S(5)/2)*log(t), t, s, simplify=True) == + (sqrt(pi)*(-15*log(s) - log(1073741824) - 15*EulerGamma + 46) / + (8*s**(S(7)/2)), 0, True)) + assert (LT(t**3*log(t), t, s, noconds=True, simplify=True) - + 6*(-log(s) - S.EulerGamma + S(11)/6)/s**4).simplify() == S.Zero + assert (LT(log(t)**2, t, s, simplify=True) == + (((log(s) + EulerGamma)**2 + pi**2/6)/s, 0, True)) + assert (LT(exp(-a*t)*log(t), t, s, simplify=True) == + ((-log(a + s) - EulerGamma)/(a + s), -a, True)) + assert LT(sin(a*t), t, s) == (a/(a**2 + s**2), 0, True) + assert (LT(Abs(sin(a*t)), t, s) == + (a*coth(pi*s/(2*a))/(a**2 + s**2), 0, True)) + assert LT(sin(a*t)/t, t, s) == (atan(a/s), 0, True) + assert LT(sin(a*t)**2/t, t, s) == (log(4*a**2/s**2 + 1)/4, 0, True) + assert (LT(sin(a*t)**2/t**2, t, s) == + (a*atan(2*a/s) - s*log(4*a**2/s**2 + 1)/4, 0, True)) + # assert (LT(sin(2*sqrt(a*t)), t, s) == + # (sqrt(pi)*sqrt(a)*exp(-a/s)/s**(S(3)/2), 0, True)) + # assert LT(sin(2*sqrt(a*t))/t, t, s) == (pi*erf(sqrt(a)*sqrt(1/s)), 0, True) + assert LT(cos(a*t), t, s) == (s/(a**2 + s**2), 0, True) + assert (LT(cos(a*t)**2, t, s) == + ((2*a**2 + s**2)/(s*(4*a**2 + s**2)), 0, True)) + # assert (LT(sqrt(t)*cos(2*sqrt(a*t)), t, s, simplify=True) == + # (sqrt(pi)*(-a + s/2)*exp(-a/s)/s**(S(5)/2), 0, True)) + # assert (LT(cos(2*sqrt(a*t))/sqrt(t), t, s) == + # (sqrt(pi)*sqrt(1/s)*exp(-a/s), 0, True)) + assert (LT(sin(a*t)*sin(b*t), t, s) == + (2*a*b*s/((s**2 + (a - b)**2)*(s**2 + (a + b)**2)), 0, True)) + assert (LT(cos(a*t)*sin(b*t), t, s) == + (b*(-a**2 + b**2 + s**2)/((s**2 + (a - b)**2)*(s**2 + (a + b)**2)), + 0, True)) + assert (LT(cos(a*t)*cos(b*t), t, s) == + (s*(a**2 + b**2 + s**2)/((s**2 + (a - b)**2)*(s**2 + (a + b)**2)), + 0, True)) + assert (LT(-a*t*cos(a*t) + sin(a*t), t, s, simplify=True) == + (2*a**3/(a**4 + 2*a**2*s**2 + s**4), 0, True)) + assert LT(c*exp(-b*t)*sin(a*t), t, s) == (a * + c/(a**2 + (b + s)**2), -b, True) + assert LT(c*exp(-b*t)*cos(a*t), t, s) == (c*(b + s)/(a**2 + (b + s)**2), + -b, True) + L, plane, cond = LT(cos(x + 3), x, s, simplify=True) + assert plane == 0 + assert L - (s*cos(3) - sin(3))/(s**2 + 1) == 0 + # Error functions (laplace7.pdf) + assert LT(erf(a*t), t, s) == (exp(s**2/(4*a**2))*erfc(s/(2*a))/s, 0, True) + # assert LT(erf(sqrt(a*t)), t, s) == (sqrt(a)/(s*sqrt(a + s)), 0, True) + # assert (LT(exp(a*t)*erf(sqrt(a*t)), t, s, simplify=True) == + # (-sqrt(a)/(sqrt(s)*(a - s)), a, True)) + # assert (LT(erf(sqrt(a/t)/2), t, s, simplify=True) == + # (1/s - exp(-sqrt(a)*sqrt(s))/s, 0, True)) + # assert (LT(erfc(sqrt(a*t)), t, s, simplify=True) == + # (-sqrt(a)/(s*sqrt(a + s)) + 1/s, -a, True)) + # assert (LT(exp(a*t)*erfc(sqrt(a*t)), t, s) == + # (1/(sqrt(a)*sqrt(s) + s), 0, True)) + # assert LT(erfc(sqrt(a/t)/2), t, s) == (exp(-sqrt(a)*sqrt(s))/s, 0, True) + # Bessel functions (laplace8.pdf) + assert LT(besselj(0, a*t), t, s) == (1/sqrt(a**2 + s**2), 0, True) + assert (LT(besselj(1, a*t), t, s, simplify=True) == + (a/(a**2 + s**2 + s*sqrt(a**2 + s**2)), 0, True)) + assert (LT(besselj(2, a*t), t, s, simplify=True) == + (a**2/(sqrt(a**2 + s**2)*(s + sqrt(a**2 + s**2))**2), 0, True)) + assert (LT(t*besselj(0, a*t), t, s) == + (s/(a**2 + s**2)**(S(3)/2), 0, True)) + assert (LT(t*besselj(1, a*t), t, s) == + (a/(a**2 + s**2)**(S(3)/2), 0, True)) + assert (LT(t**2*besselj(2, a*t), t, s) == + (3*a**2/(a**2 + s**2)**(S(5)/2), 0, True)) + # assert LT(besselj(0, 2*sqrt(a*t)), t, s) == (exp(-a/s)/s, 0, True) + # assert (LT(t**(S(3)/2)*besselj(3, 2*sqrt(a*t)), t, s) == + # (a**(S(3)/2)*exp(-a/s)/s**4, 0, True)) + assert (LT(besselj(0, a*sqrt(t**2+b*t)), t, s, simplify=True) == + (exp(b*(s - sqrt(a**2 + s**2)))/sqrt(a**2 + s**2), 0, True)) + assert LT(besseli(0, a*t), t, s) == (1/sqrt(-a**2 + s**2), a, True) + assert (LT(besseli(1, a*t), t, s, simplify=True) == + (a/(-a**2 + s**2 + s*sqrt(-a**2 + s**2)), a, True)) + assert (LT(besseli(2, a*t), t, s, simplify=True) == + (a**2/(sqrt(-a**2 + s**2)*(s + sqrt(-a**2 + s**2))**2), a, True)) + assert LT(t*besseli(0, a*t), t, s) == (s/(-a**2 + s**2)**(S(3)/2), a, True) + assert LT(t*besseli(1, a*t), t, s) == (a/(-a**2 + s**2)**(S(3)/2), a, True) + assert (LT(t**2*besseli(2, a*t), t, s) == + (3*a**2/(-a**2 + s**2)**(S(5)/2), a, True)) + # assert (LT(t**(S(3)/2)*besseli(3, 2*sqrt(a*t)), t, s) == + # (a**(S(3)/2)*exp(a/s)/s**4, 0, True)) + assert (LT(bessely(0, a*t), t, s) == + (-2*asinh(s/a)/(pi*sqrt(a**2 + s**2)), 0, True)) + assert (LT(besselk(0, a*t), t, s) == + (log((s + sqrt(-a**2 + s**2))/a)/sqrt(-a**2 + s**2), -a, True)) + assert (LT(sin(a*t)**4, t, s, simplify=True) == + (24*a**4/(s*(64*a**4 + 20*a**2*s**2 + s**4)), 0, True)) + # Test general rules and unevaluated forms + # These all also test whether issue #7219 is solved. + assert LT(Heaviside(t-1)*cos(t-1), t, s) == (s*exp(-s)/(s**2 + 1), 0, True) + assert LT(a*f(t), t, w) == (a*LaplaceTransform(f(t), t, w), -oo, True) + assert (LT(a*Heaviside(t+1)*f(t+1), t, s) == + (a*LaplaceTransform(f(t + 1), t, s), -oo, True)) + assert (LT(a*Heaviside(t-1)*f(t-1), t, s) == + (a*LaplaceTransform(f(t), t, s)*exp(-s), -oo, True)) + assert (LT(b*f(t/a), t, s) == + (a*b*LaplaceTransform(f(t), t, a*s), -oo, True)) + assert LT(exp(-f(x)*t), t, s) == (1/(s + f(x)), -re(f(x)), True) + assert (LT(exp(-a*t)*f(t), t, s) == + (LaplaceTransform(f(t), t, a + s), -oo, True)) + # assert (LT(exp(-a*t)*erfc(sqrt(b/t)/2), t, s) == + # (exp(-sqrt(b)*sqrt(a + s))/(a + s), -a, True)) + assert (LT(sinh(a*t)*f(t), t, s) == + (LaplaceTransform(f(t), t, -a + s)/2 - + LaplaceTransform(f(t), t, a + s)/2, -oo, True)) + assert (LT(sinh(a*t)*t, t, s, simplify=True) == + (2*a*s/(a**4 - 2*a**2*s**2 + s**4), a, True)) + assert (LT(cosh(a*t)*f(t), t, s) == + (LaplaceTransform(f(t), t, -a + s)/2 + + LaplaceTransform(f(t), t, a + s)/2, -oo, True)) + assert (LT(cosh(a*t)*t, t, s, simplify=True) == + (1/(2*(a + s)**2) + 1/(2*(a - s)**2), a, True)) + assert (LT(sin(a*t)*f(t), t, s, simplify=True) == + (I*(-LaplaceTransform(f(t), t, -I*a + s) + + LaplaceTransform(f(t), t, I*a + s))/2, -oo, True)) + assert (LT(sin(f(t)), t, s) == + (LaplaceTransform(sin(f(t)), t, s), -oo, True)) + assert (LT(sin(a*t)*t, t, s, simplify=True) == + (2*a*s/(a**4 + 2*a**2*s**2 + s**4), 0, True)) + assert (LT(cos(a*t)*f(t), t, s) == + (LaplaceTransform(f(t), t, -I*a + s)/2 + + LaplaceTransform(f(t), t, I*a + s)/2, -oo, True)) + assert (LT(cos(a*t)*t, t, s, simplify=True) == + ((-a**2 + s**2)/(a**4 + 2*a**2*s**2 + s**4), 0, True)) + L, plane, _ = LT(sin(a*t+b)**2*f(t), t, s) + assert plane == -oo + assert ( + -L + ( + LaplaceTransform(f(t), t, s)/2 - + LaplaceTransform(f(t), t, -2*I*a + s)*exp(2*I*b)/4 - + LaplaceTransform(f(t), t, 2*I*a + s)*exp(-2*I*b)/4)) == 0 + L = LT(sin(a*t+b)**2*f(t), t, s, noconds=True) + assert ( + laplace_correspondence(L, {f: F}) == + F(s)/2 - F(-2*I*a + s)*exp(2*I*b)/4 - + F(2*I*a + s)*exp(-2*I*b)/4) + L, plane, _ = LT(sin(a*t)**3*cosh(b*t), t, s) + assert plane == b + assert ( + -L - 3*a/(8*(9*a**2 + b**2 + 2*b*s + s**2)) - + 3*a/(8*(9*a**2 + b**2 - 2*b*s + s**2)) + + 3*a/(8*(a**2 + b**2 + 2*b*s + s**2)) + + 3*a/(8*(a**2 + b**2 - 2*b*s + s**2))).simplify() == 0 + assert (LT(t**2*exp(-t**2), t, s) == + (sqrt(pi)*s**2*exp(s**2/4)*erfc(s/2)/8 - s/4 + + sqrt(pi)*exp(s**2/4)*erfc(s/2)/4, 0, True)) + assert (LT((a*t**2 + b*t + c)*f(t), t, s) == + (a*Derivative(LaplaceTransform(f(t), t, s), (s, 2)) - + b*Derivative(LaplaceTransform(f(t), t, s), s) + + c*LaplaceTransform(f(t), t, s), -oo, True)) + assert (LT(t**np*g(t), t, s) == + ((-1)**np*Derivative(LaplaceTransform(g(t), t, s), (s, np)), + -oo, True)) + # The following tests check whether _piecewise_to_heaviside works: + x1 = Piecewise((0, t <= 0), (1, t <= 1), (0, True)) + X1 = LT(x1, t, s)[0] + assert X1 == 1/s - exp(-s)/s + y1 = ILT(X1, s, t) + assert y1 == Heaviside(t) - Heaviside(t - 1) + x1 = Piecewise((0, t <= 0), (t, t <= 1), (2-t, t <= 2), (0, True)) + X1 = LT(x1, t, s)[0].simplify() + assert X1 == (exp(2*s) - 2*exp(s) + 1)*exp(-2*s)/s**2 + y1 = ILT(X1, s, t) + assert ( + -y1 + t*Heaviside(t) + (t - 2)*Heaviside(t - 2) - + 2*(t - 1)*Heaviside(t - 1)).simplify() == 0 + x1 = Piecewise((exp(t), t <= 0), (1, t <= 1), (exp(-(t)), True)) + X1 = LT(x1, t, s)[0] + assert X1 == exp(-1)*exp(-s)/(s + 1) + 1/s - exp(-s)/s + y1 = ILT(X1, s, t) + assert y1 == ( + exp(-1)*exp(1 - t)*Heaviside(t - 1) + Heaviside(t) - Heaviside(t - 1)) + x1 = Piecewise((0, x <= 0), (1, x <= 1), (0, True)) + X1 = LT(x1, t, s)[0] + assert X1 == Piecewise((0, x <= 0), (1, x <= 1), (0, True))/s + x1 = [ + a*Piecewise((1, And(t > 1, t <= 3)), (2, True)), + a*Piecewise((1, And(t >= 1, t <= 3)), (2, True)), + a*Piecewise((1, And(t >= 1, t < 3)), (2, True)), + a*Piecewise((1, And(t > 1, t < 3)), (2, True))] + for x2 in x1: + assert LT(x2, t, s)[0].expand() == 2*a/s - a*exp(-s)/s + a*exp(-3*s)/s + assert ( + LT(Piecewise((1, Eq(t, 1)), (2, True)), t, s)[0] == + LaplaceTransform(Piecewise((1, Eq(t, 1)), (2, True)), t, s)) + # The following lines test whether _laplace_transform successfully + # removes Heaviside(1) before processing espressions. It fails if + # Heaviside(t) remains because then meijerg functions will appear. + X1 = 1/sqrt(a*s**2-b) + x1 = ILT(X1, s, t) + Y1 = LT(x1, t, s)[0] + Z1 = (Y1**2/X1**2).simplify() + assert Z1 == 1 + # The following two lines test whether issues #5813 and #7176 are solved. + assert (LT(diff(f(t), (t, 1)), t, s, noconds=True) == + s*LaplaceTransform(f(t), t, s) - f(0)) + assert (LT(diff(f(t), (t, 3)), t, s, noconds=True) == + s**3*LaplaceTransform(f(t), t, s) - s**2*f(0) - + s*Subs(Derivative(f(t), t), t, 0) - + Subs(Derivative(f(t), (t, 2)), t, 0)) + # Issue #7219 + assert (LT(diff(f(x, t, w), t, 2), t, s) == + (s**2*LaplaceTransform(f(x, t, w), t, s) - s*f(x, 0, w) - + Subs(Derivative(f(x, t, w), t), t, 0), -oo, True)) + # Issue #23307 + assert (LT(10*diff(f(t), (t, 1)), t, s, noconds=True) == + 10*s*LaplaceTransform(f(t), t, s) - 10*f(0)) + assert (LT(a*f(b*t)+g(c*t), t, s, noconds=True) == + a*LaplaceTransform(f(t), t, s/b)/b + + LaplaceTransform(g(t), t, s/c)/c) + assert inverse_laplace_transform( + f(w), w, t, plane=0) == InverseLaplaceTransform(f(w), w, t, 0) + assert (LT(f(t)*g(t), t, s, noconds=True) == + LaplaceTransform(f(t)*g(t), t, s)) + # Issue #24294 + assert (LT(b*f(a*t), t, s, noconds=True) == + b*LaplaceTransform(f(t), t, s/a)/a) + assert LT(3*exp(t)*Heaviside(t), t, s) == (3/(s - 1), 1, True) + assert (LT(2*sin(t)*Heaviside(t), t, s, simplify=True) == + (2/(s**2 + 1), 0, True)) + # Issue #25293 + assert ( + LT((1/(t-1))*sin(4*pi*(t-1))*DiracDelta(t-1) * + (Heaviside(t-1/4) - Heaviside(t-2)), t, s)[0] == 4*pi*exp(-s)) + # additional basic tests from wikipedia + assert (LT((t - a)**b*exp(-c*(t - a))*Heaviside(t - a), t, s) == + ((c + s)**(-b - 1)*exp(-a*s)*gamma(b + 1), -c, True)) + assert ( + LT((exp(2*t)-1)*exp(-b-t)*Heaviside(t)/2, t, s, noconds=True, + simplify=True) == + exp(-b)/(s**2 - 1)) + # DiracDelta function: standard cases + assert LT(DiracDelta(t), t, s) == (1, -oo, True) + assert LT(DiracDelta(a*t), t, s) == (1/a, -oo, True) + assert LT(DiracDelta(t/42), t, s) == (42, -oo, True) + assert LT(DiracDelta(t+42), t, s) == (0, -oo, True) + assert (LT(DiracDelta(t)+DiracDelta(t-42), t, s) == + (1 + exp(-42*s), -oo, True)) + assert (LT(DiracDelta(t)-a*exp(-a*t), t, s, simplify=True) == + (s/(a + s), -a, True)) + assert ( + LT(exp(-t)*(DiracDelta(t)+DiracDelta(t-42)), t, s, simplify=True) == + (exp(-42*s - 42) + 1, -oo, True)) + assert LT(f(t)*DiracDelta(t-42), t, s) == (f(42)*exp(-42*s), -oo, True) + assert LT(f(t)*DiracDelta(b*t-a), t, s) == (f(a/b)*exp(-a*s/b)/b, + -oo, True) + assert LT(f(t)*DiracDelta(b*t+a), t, s) == (0, -oo, True) + # SingularityFunction + assert LT(SingularityFunction(t, a, -1), t, s)[0] == exp(-a*s) + assert LT(SingularityFunction(t, a, 1), t, s)[0] == exp(-a*s)/s**2 + assert LT(SingularityFunction(t, a, x), t, s)[0] == ( + LaplaceTransform(SingularityFunction(t, a, x), t, s)) + # Collection of cases that cannot be fully evaluated and/or would catch + # some common implementation errors + assert (LT(DiracDelta(t**2), t, s, noconds=True) == + LaplaceTransform(DiracDelta(t**2), t, s)) + assert LT(DiracDelta(t**2 - 1), t, s) == (exp(-s)/2, -oo, True) + assert LT(DiracDelta(t*(1 - t)), t, s) == (1 - exp(-s), -oo, True) + assert (LT((DiracDelta(t) + 1)*(DiracDelta(t - 1) + 1), t, s) == + (LaplaceTransform(DiracDelta(t)*DiracDelta(t - 1), t, s) + + 1 + exp(-s) + 1/s, 0, True)) + assert LT(DiracDelta(2*t-2*exp(a)), t, s) == (exp(-s*exp(a))/2, -oo, True) + assert LT(DiracDelta(-2*t+2*exp(a)), t, s) == (exp(-s*exp(a))/2, -oo, True) + # Heaviside tests + assert LT(Heaviside(t), t, s) == (1/s, 0, True) + assert LT(Heaviside(t - a), t, s) == (exp(-a*s)/s, 0, True) + assert LT(Heaviside(t-1), t, s) == (exp(-s)/s, 0, True) + assert LT(Heaviside(2*t-4), t, s) == (exp(-2*s)/s, 0, True) + assert LT(Heaviside(2*t+4), t, s) == (1/s, 0, True) + assert (LT(Heaviside(-2*t+4), t, s, simplify=True) == + (1/s - exp(-2*s)/s, 0, True)) + assert (LT(g(t)*Heaviside(t - w), t, s) == + (LaplaceTransform(g(t)*Heaviside(t - w), t, s), -oo, True)) + assert ( + LT(Heaviside(t-a)*g(t), t, s) == + (LaplaceTransform(g(a + t), t, s)*exp(-a*s), -oo, True)) + assert ( + LT(Heaviside(t+a)*g(t), t, s) == + (LaplaceTransform(g(t), t, s), -oo, True)) + assert ( + LT(Heaviside(-t+a)*g(t), t, s) == + (LaplaceTransform(g(t), t, s) - + LaplaceTransform(g(a + t), t, s)*exp(-a*s), -oo, True)) + assert ( + LT(Heaviside(-t-a)*g(t), t, s) == (0, 0, True)) + # Fresnel functions + assert (laplace_transform(fresnels(t), t, s, simplify=True) == + ((-sin(s**2/(2*pi))*fresnels(s/pi) + + sqrt(2)*sin(s**2/(2*pi) + pi/4)/2 - + cos(s**2/(2*pi))*fresnelc(s/pi))/s, 0, True)) + assert (laplace_transform(fresnelc(t), t, s, simplify=True) == + ((sin(s**2/(2*pi))*fresnelc(s/pi) - + cos(s**2/(2*pi))*fresnels(s/pi) + + sqrt(2)*cos(s**2/(2*pi) + pi/4)/2)/s, 0, True)) + # Matrix tests + Mt = Matrix([[exp(t), t*exp(-t)], [t*exp(-t), exp(t)]]) + Ms = Matrix([[1/(s - 1), (s + 1)**(-2)], + [(s + 1)**(-2), 1/(s - 1)]]) + # The default behaviour for Laplace transform of a Matrix returns a Matrix + # of Tuples and is deprecated: + with warns_deprecated_sympy(): + Ms_conds = Matrix( + [[(1/(s - 1), 1, True), ((s + 1)**(-2), -1, True)], + [((s + 1)**(-2), -1, True), (1/(s - 1), 1, True)]]) + with warns_deprecated_sympy(): + assert LT(Mt, t, s) == Ms_conds + # The new behavior is to return a tuple of a Matrix and the convergence + # conditions for the matrix as a whole: + assert LT(Mt, t, s, legacy_matrix=False) == (Ms, 1, True) + # With noconds=True the transformed matrix is returned without conditions + # either way: + assert LT(Mt, t, s, noconds=True) == Ms + assert LT(Mt, t, s, legacy_matrix=False, noconds=True) == Ms + + +@slow +def test_inverse_laplace_transform(): + s = symbols('s') + k, n, t = symbols('k, n, t', real=True) + a, b, c, d = symbols('a, b, c, d', positive=True) + f = Function('f') + F = Function('F') + + def ILT(g): + return inverse_laplace_transform(g, s, t) + + def ILTS(g): + return inverse_laplace_transform(g, s, t, simplify=True) + + def ILTF(g): + return laplace_correspondence( + inverse_laplace_transform(g, s, t), {f: F}) + + # Tests for the rules in Bateman54. + + # Section 4.1: Some of the Laplace transform rules can also be used well + # in the inverse transform. + assert ILTF(exp(-a*s)*F(s)) == f(-a + t) + assert ILTF(k*F(s-a)) == k*f(t)*exp(-a*t) + assert ILTF(diff(F(s), s, 3)) == -t**3*f(t) + assert ILTF(diff(F(s), s, 4)) == t**4*f(t) + + # Section 5.1: Most rules are impractical for a computer algebra system. + + # Section 5.2: Rational functions + assert ILT(2) == 2*DiracDelta(t) + assert ILT(1/s) == Heaviside(t) + assert ILT(1/s**2) == t*Heaviside(t) + assert ILT(1/s**5) == t**4*Heaviside(t)/24 + assert ILT(1/s**n) == t**(n - 1)*Heaviside(t)/gamma(n) + assert ILT(a/(a + s)) == a*exp(-a*t)*Heaviside(t) + assert ILT(s/(a + s)) == -a*exp(-a*t)*Heaviside(t) + DiracDelta(t) + assert (ILT(b*s/(s+a)**2) == + b*(-a*t*exp(-a*t)*Heaviside(t) + exp(-a*t)*Heaviside(t))) + assert (ILTS(c/((s+a)*(s+b))) == + c*(exp(a*t) - exp(b*t))*exp(-t*(a + b))*Heaviside(t)/(a - b)) + assert (ILTS(c*s/((s+a)*(s+b))) == + c*(a*exp(b*t) - b*exp(a*t))*exp(-t*(a + b))*Heaviside(t)/(a - b)) + assert ILTS(s/(a + s)**3) == t*(-a*t + 2)*exp(-a*t)*Heaviside(t)/2 + assert ILTS(1/(s*(a + s)**3)) == ( + -a**2*t**2 - 2*a*t + 2*exp(a*t) - 2)*exp(-a*t)*Heaviside(t)/(2*a**3) + assert ILT(1/(s*(a + s)**n)) == ( + Heaviside(t)*lowergamma(n, a*t)/(a**n*gamma(n))) + assert ILT((s-a)**(-b)) == t**(b - 1)*exp(a*t)*Heaviside(t)/gamma(b) + assert ILT((a + s)**(-2)) == t*exp(-a*t)*Heaviside(t) + assert ILT((a + s)**(-5)) == t**4*exp(-a*t)*Heaviside(t)/24 + assert ILT(s**2/(s**2 + 1)) == -sin(t)*Heaviside(t) + DiracDelta(t) + assert ILT(1 - 1/(s**2 + 1)) == -sin(t)*Heaviside(t) + DiracDelta(t) + assert ILT(a/(a**2 + s**2)) == sin(a*t)*Heaviside(t) + assert ILT(s/(s**2 + a**2)) == cos(a*t)*Heaviside(t) + assert ILT(b/(b**2 + (a + s)**2)) == exp(-a*t)*sin(b*t)*Heaviside(t) + assert (ILT(b*s/(b**2 + (a + s)**2)) == + b*(-a*exp(-a*t)*sin(b*t)/b + exp(-a*t)*cos(b*t))*Heaviside(t)) + assert ILT(1/(s**2*(s**2 + 1))) == t*Heaviside(t) - sin(t)*Heaviside(t) + assert (ILTS(c*s/(d**2*(s+a)**2+b**2)) == + c*(-a*d*sin(b*t/d) + b*cos(b*t/d))*exp(-a*t)*Heaviside(t)/(b*d**2)) + assert ILTS((b*s**2 + d)/(a**2 + s**2)**2) == ( + 2*a**2*b*sin(a*t) + (a**2*b - d)*(a*t*cos(a*t) - + sin(a*t)))*Heaviside(t)/(2*a**3) + assert ILTS(b/(s**2-a**2)) == b*sinh(a*t)*Heaviside(t)/a + assert (ILT(b/(s**2-a**2)) == + b*(exp(a*t)*Heaviside(t)/(2*a) - exp(-a*t)*Heaviside(t)/(2*a))) + assert ILTS(b*s/(s**2-a**2)) == b*cosh(a*t)*Heaviside(t) + assert (ILT(b/(s*(s+a))) == + b*(Heaviside(t)/a - exp(-a*t)*Heaviside(t)/a)) + # Issue #24424 + assert (ILTS((s + 8)/((s + 2)*(s**2 + 2*s + 10))) == + ((8*sin(3*t) - 9*cos(3*t))*exp(t) + 9)*exp(-2*t)*Heaviside(t)/15) + # Issue #8514; this is not important anymore, since this function + # is not solved by integration anymore + assert (ILT(1/(a*s**2+b*s+c)) == + 2*exp(-b*t/(2*a))*sin(t*sqrt(4*a*c - b**2)/(2*a)) * + Heaviside(t)/sqrt(4*a*c - b**2)) + + # Section 5.3: Irrational algebraic functions + assert ( # (1) + ILT(1/sqrt(s)/(b*s-a)) == + exp(a*t/b)*Heaviside(t)*erf(sqrt(a)*sqrt(t)/sqrt(b))/(sqrt(a)*sqrt(b))) + assert ( # (2) + ILT(1/sqrt(k*s)/(c*s-a)/s) == + (-2*c*sqrt(t)/(sqrt(pi)*a) + + c**(S(3)/2)*exp(a*t/c)*erf(sqrt(a)*sqrt(t)/sqrt(c))/a**(S(3)/2)) * + Heaviside(t)/(c*sqrt(k))) + assert ( # (4) + ILT(1/(sqrt(c*s)+a)) == (-a*exp(a**2*t/c)*erfc(a*sqrt(t)/sqrt(c))/c + + 1/(sqrt(pi)*sqrt(c)*sqrt(t)))*Heaviside(t)) + assert ( # (5) + ILT(a/s/(b*sqrt(s)+a)) == + (-exp(a**2*t/b**2)*erfc(a*sqrt(t)/b) + 1)*Heaviside(t)) + assert ( # (6) + ILT((a-b)*sqrt(s)/(sqrt(s)+sqrt(a))/(s-b)) == + (sqrt(a)*sqrt(b)*exp(b*t)*erfc(sqrt(b)*sqrt(t)) + + a*exp(a*t)*erfc(sqrt(a)*sqrt(t)) - b*exp(b*t))*Heaviside(t)) + assert ( # (7) + ILT(1/sqrt(s)/(sqrt(b*s)+a)) == + exp(a**2*t/b)*Heaviside(t)*erfc(a*sqrt(t)/sqrt(b))/sqrt(b)) + assert ( # (8) + ILT(a**2/(sqrt(s)+a)/s**(S(3)/2)) == + (2*a*sqrt(t)/sqrt(pi) + exp(a**2*t)*erfc(a*sqrt(t)) - 1) * + Heaviside(t)) + assert ( # (9) + ILT((a-b)*sqrt(b)/(s-b)/sqrt(s)/(sqrt(s)+sqrt(a))) == + (sqrt(a)*exp(b*t)*erf(sqrt(b)*sqrt(t)) + + sqrt(b)*exp(a*t)*erfc(sqrt(a)*sqrt(t)) - + sqrt(b)*exp(b*t))*Heaviside(t)) + assert ( # (10) + ILT(1/(sqrt(s)+sqrt(a))**2) == + (-2*sqrt(a)*sqrt(t)/sqrt(pi) + + (-2*a*t + 1)*(erf(sqrt(a)*sqrt(t)) - + 1)*exp(a*t) + 1)*Heaviside(t)) + assert ( # (11) + ILT(1/(sqrt(s)+sqrt(a))**2/s) == + ((2*t - 1/a)*exp(a*t)*erfc(sqrt(a)*sqrt(t)) + 1/a - + 2*sqrt(t)/(sqrt(pi)*sqrt(a)))*Heaviside(t)) + assert ( # (12) + ILT(1/(sqrt(s)+a)**2/sqrt(s)) == + (-2*a*t*exp(a**2*t)*erfc(a*sqrt(t)) + + 2*sqrt(t)/sqrt(pi))*Heaviside(t)) + assert ( # (13) + ILT(1/(sqrt(s)+a)**3) == + (-a*t*(2*a**2*t + 3)*exp(a**2*t)*erfc(a*sqrt(t)) + + 2*sqrt(t)*(a**2*t + 1)/sqrt(pi))*Heaviside(t)) + x = ( + - ILT(sqrt(s)/(sqrt(s)+a)**3) + + 2*(sqrt(pi)*a**2*t*(-2*sqrt(pi)*erfc(a*sqrt(t)) + + 2*exp(-a**2*t)/(a*sqrt(t))) * + (-a**4*t**2 - 5*a**2*t/2 - S.Half) * exp(a**2*t)/2 + + sqrt(pi)*a*sqrt(t)*(a**2*t + 1)/2) * + Heaviside(t)/(pi*a**2*t)).simplify() + assert ( # (14) + x == 0) + x = ( + - ILT(1/sqrt(s)/(sqrt(s)+a)**3) + + Heaviside(t)*(sqrt(t)*((2*a**2*t + 1) * + (sqrt(pi)*a*sqrt(t)*exp(a**2*t) * + erfc(a*sqrt(t)) - 1) + 1) / + (sqrt(pi)*a))).simplify() + assert ( # (15) + x == 0) + assert ( # (16) + factor_terms(ILT(3/(sqrt(s)+a)**4)) == + 3*(-2*a**3*t**(S(5)/2)*(2*a**2*t + 5)/(3*sqrt(pi)) + + t*(4*a**4*t**2 + 12*a**2*t + 3)*exp(a**2*t) * + erfc(a*sqrt(t))/3)*Heaviside(t)) + assert ( # (17) + ILT((sqrt(s)-a)/(s*(sqrt(s)+a))) == + (2*exp(a**2*t)*erfc(a*sqrt(t))-1)*Heaviside(t)) + assert ( # (18) + ILT((sqrt(s)-a)**2/(s*(sqrt(s)+a)**2)) == ( + 1 + 8*a**2*t*exp(a**2*t)*erfc(a*sqrt(t)) - + 8/sqrt(pi)*a*sqrt(t))*Heaviside(t)) + assert ( # (19) + ILT((sqrt(s)-a)**3/(s*(sqrt(s)+a)**3)) == Heaviside(t)*( + 2*(8*a**4*t**2+8*a**2*t+1)*exp(a**2*t) * + erfc(a*sqrt(t))-8/sqrt(pi)*a*sqrt(t)*(2*a**2*t+1)-1)) + assert ( # (22) + ILT(sqrt(s+a)/(s+b)) == Heaviside(t)*( + exp(-a*t)/sqrt(t)/sqrt(pi) + + sqrt(a-b)*exp(-b*t)*erf(sqrt(a-b)*sqrt(t)))) + assert ( # (23) + ILT(1/sqrt(s+b)/(s+a)) == Heaviside(t)*( + 1/sqrt(b-a)*exp(-a*t)*erf(sqrt(b-a)*sqrt(t)))) + assert ( # (35) + ILT(1/sqrt(s**2+a**2)) == Heaviside(t)*( + besselj(0, a*t))) + assert ( # (44) + ILT(1/sqrt(s**2-a**2)) == Heaviside(t)*( + besseli(0, a*t))) + + # Miscellaneous tests + # Can _inverse_laplace_time_shift deal with positive exponents? + assert ( + - ILT((s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1))) + + cos(t)*Heaviside(t) + 4*cos(t - 2)*Heaviside(t - 2) - + 4*cos(t - 1)*Heaviside(t - 1) - 4*Heaviside(t - 2) + + 4*Heaviside(t - 1)).simplify() == 0 + + +@slow +def test_inverse_laplace_transform_old(): + from sympy.functions.special.delta_functions import DiracDelta + ILT = inverse_laplace_transform + a, b, c, d = symbols('a b c d', positive=True) + n, r = symbols('n, r', real=True) + t, z = symbols('t z') + f = Function('f') + F = Function('F') + + def simp_hyp(expr): + return factor_terms(expand_mul(expr)).rewrite(sin) + + L = ILT(F(s), s, t) + assert laplace_correspondence(L, {f: F}) == f(t) + assert ILT(exp(-a*s)/s, s, t) == Heaviside(-a + t) + assert ILT(exp(-a*s)/(b + s), s, t) == exp(-b*(-a + t))*Heaviside(-a + t) + assert (ILT((b + s)/(a**2 + (b + s)**2), s, t) == + exp(-b*t)*cos(a*t)*Heaviside(t)) + assert (ILT(exp(-a*s)/s**b, s, t) == + (-a + t)**(b - 1)*Heaviside(-a + t)/gamma(b)) + assert (ILT(exp(-a*s)/sqrt(s**2 + 1), s, t) == + Heaviside(-a + t)*besselj(0, a - t)) + assert ILT(1/(s*sqrt(s + 1)), s, t) == Heaviside(t)*erf(sqrt(t)) + # TODO sinh/cosh shifted come out a mess. also delayed trig is a mess + # TODO should this simplify further? + assert (ILT(exp(-a*s)/s**b, s, t) == + (t - a)**(b - 1)*Heaviside(t - a)/gamma(b)) + assert (ILT(exp(-a*s)/sqrt(1 + s**2), s, t) == + Heaviside(t - a)*besselj(0, a - t)) # note: besselj(0, x) is even + # XXX ILT turns these branch factor into trig functions ... + assert ( + simplify(ILT(a**b*(s + sqrt(s**2 - a**2))**(-b)/sqrt(s**2 - a**2), + s, t).rewrite(exp)) == + Heaviside(t)*besseli(b, a*t)) + assert ( + ILT(a**b*(s + sqrt(s**2 + a**2))**(-b)/sqrt(s**2 + a**2), + s, t, simplify=True).rewrite(exp) == + Heaviside(t)*besselj(b, a*t)) + assert ILT(1/(s*sqrt(s + 1)), s, t) == Heaviside(t)*erf(sqrt(t)) + # TODO can we make erf(t) work? + assert (ILT((s * eye(2) - Matrix([[1, 0], [0, 2]])).inv(), s, t) == + Matrix([[exp(t)*Heaviside(t), 0], [0, exp(2*t)*Heaviside(t)]])) + # Test time_diff rule + assert (ILT(s**42*f(s), s, t) == + Derivative(InverseLaplaceTransform(f(s), s, t, None), (t, 42))) + assert ILT(cos(s), s, t) == InverseLaplaceTransform(cos(s), s, t, None) + # Rules for testing different DiracDelta cases + assert ( + ILT(1 + 2*s + 3*s**2 + 5*s**3, s, t) == DiracDelta(t) + + 2*DiracDelta(t, 1) + 3*DiracDelta(t, 2) + 5*DiracDelta(t, 3)) + assert (ILT(2*exp(3*s) - 5*exp(-7*s), s, t) == + 2*InverseLaplaceTransform(exp(3*s), s, t, None) - + 5*DiracDelta(t - 7)) + a = cos(sin(7)/2) + assert ILT(a*exp(-3*s), s, t) == a*DiracDelta(t - 3) + assert ILT(exp(2*s), s, t) == InverseLaplaceTransform(exp(2*s), s, t, None) + r = Symbol('r', real=True) + assert ILT(exp(r*s), s, t) == InverseLaplaceTransform(exp(r*s), s, t, None) + # Rules for testing whether Heaviside(t) is treated properly in diff rule + assert ILT(s**2/(a**2 + s**2), s, t) == ( + -a*sin(a*t)*Heaviside(t) + DiracDelta(t)) + assert ILT(s**2*(f(s) + 1/(a**2 + s**2)), s, t) == ( + -a*sin(a*t)*Heaviside(t) + DiracDelta(t) + + Derivative(InverseLaplaceTransform(f(s), s, t, None), (t, 2))) + # Rules from the previous test_inverse_laplace_transform_delta_cond(): + assert (ILT(exp(r*s), s, t, noconds=False) == + (InverseLaplaceTransform(exp(r*s), s, t, None), True)) + # inversion does not exist: verify it doesn't evaluate to DiracDelta + for z in (Symbol('z', extended_real=False), + Symbol('z', imaginary=True, zero=False)): + f = ILT(exp(z*s), s, t, noconds=False) + f = f[0] if isinstance(f, tuple) else f + assert f.func != DiracDelta + + +@slow +def test_expint(): + x = Symbol('x') + a = Symbol('a') + u = Symbol('u', polar=True) + + # TODO LT of Si, Shi, Chi is a mess ... + assert laplace_transform(Ci(x), x, s) == (-log(1 + s**2)/2/s, 0, True) + assert (laplace_transform(expint(a, x), x, s, simplify=True) == + (lerchphi(s*exp_polar(I*pi), 1, a), 0, re(a) > S.Zero)) + assert (laplace_transform(expint(1, x), x, s, simplify=True) == + (log(s + 1)/s, 0, True)) + assert (laplace_transform(expint(2, x), x, s, simplify=True) == + ((s - log(s + 1))/s**2, 0, True)) + assert (inverse_laplace_transform(-log(1 + s**2)/2/s, s, u).expand() == + Heaviside(u)*Ci(u)) + assert ( + inverse_laplace_transform(log(s + 1)/s, s, x, + simplify=True).rewrite(expint) == + Heaviside(x)*E1(x)) + assert ( + inverse_laplace_transform( + (s - log(s + 1))/s**2, s, x, + simplify=True).rewrite(expint).expand() == + (expint(2, x)*Heaviside(x)).rewrite(Ei).rewrite(expint).expand()) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_lineintegrals.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_lineintegrals.py new file mode 100644 index 0000000000000000000000000000000000000000..d0af146b52406a153d033286f3fcfa79334d2a73 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_lineintegrals.py @@ -0,0 +1,13 @@ +from sympy.core.numbers import E +from sympy.core.symbol import symbols +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.geometry.curve import Curve +from sympy.integrals.integrals import line_integrate + +s, t, x, y, z = symbols('s,t,x,y,z') + + +def test_lineintegral(): + c = Curve([E**t + 1, E**t - 1], (t, 0, log(2))) + assert line_integrate(x + y, c, [x, y]) == 3*sqrt(2) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_manual.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_manual.py new file mode 100644 index 0000000000000000000000000000000000000000..74cae4521ec97608a21553e0203be60c210387b3 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_manual.py @@ -0,0 +1,714 @@ +from sympy.core.expr import Expr +from sympy.core.mul import Mul +from sympy.core.function import (Derivative, Function, diff, expand) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, csch, cosh, coth, sech, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold +from sympy.functions.elementary.trigonometric import (acos, acot, acsc, asec, asin, atan, cos, cot, csc, sec, sin, tan) +from sympy.functions.special.delta_functions import Heaviside, DiracDelta +from sympy.functions.special.elliptic_integrals import (elliptic_e, elliptic_f) +from sympy.functions.special.error_functions import (Chi, Ci, Ei, Shi, Si, erf, erfi, fresnelc, fresnels, li) +from sympy.functions.special.gamma_functions import uppergamma +from sympy.functions.special.polynomials import (assoc_laguerre, chebyshevt, chebyshevu, gegenbauer, hermite, jacobi, laguerre, legendre) +from sympy.functions.special.zeta_functions import polylog +from sympy.integrals.integrals import (Integral, integrate) +from sympy.logic.boolalg import And +from sympy.integrals.manualintegrate import (manualintegrate, find_substitutions, + _parts_rule, integral_steps, manual_subs) +from sympy.testing.pytest import raises, slow + +x, y, z, u, n, a, b, c, d, e = symbols('x y z u n a b c d e') +f = Function('f') + + +def assert_is_integral_of(f: Expr, F: Expr): + assert manualintegrate(f, x) == F + assert F.diff(x).equals(f) + + +def test_find_substitutions(): + assert find_substitutions((cot(x)**2 + 1)**2*csc(x)**2*cot(x)**2, x, u) == \ + [(cot(x), 1, -u**6 - 2*u**4 - u**2)] + assert find_substitutions((sec(x)**2 + tan(x) * sec(x)) / (sec(x) + tan(x)), + x, u) == [(sec(x) + tan(x), 1, 1/u)] + assert (-x**2, Rational(-1, 2), exp(u)) in find_substitutions(x * exp(-x**2), x, u) + assert not find_substitutions(Derivative(f(x), x)**2, x, u) + + +def test_manualintegrate_polynomials(): + assert manualintegrate(y, x) == x*y + assert manualintegrate(exp(2), x) == x * exp(2) + assert manualintegrate(x**2, x) == x**3 / 3 + assert manualintegrate(3 * x**2 + 4 * x**3, x) == x**3 + x**4 + + assert manualintegrate((x + 2)**3, x) == (x + 2)**4 / 4 + assert manualintegrate((3*x + 4)**2, x) == (3*x + 4)**3 / 9 + + assert manualintegrate((u + 2)**3, u) == (u + 2)**4 / 4 + assert manualintegrate((3*u + 4)**2, u) == (3*u + 4)**3 / 9 + + +def test_manualintegrate_exponentials(): + assert manualintegrate(exp(2*x), x) == exp(2*x) / 2 + assert manualintegrate(2**x, x) == (2 ** x) / log(2) + assert_is_integral_of(1/sqrt(1-exp(2*x)), + log(sqrt(1 - exp(2*x)) - 1)/2 - log(sqrt(1 - exp(2*x)) + 1)/2) + + assert manualintegrate(1 / x, x) == log(x) + assert manualintegrate(1 / (2*x + 3), x) == log(2*x + 3) / 2 + assert manualintegrate(log(x)**2 / x, x) == log(x)**3 / 3 + + assert_is_integral_of(x**x*(log(x)+1), x**x) + + +def test_manualintegrate_parts(): + assert manualintegrate(exp(x) * sin(x), x) == \ + (exp(x) * sin(x)) / 2 - (exp(x) * cos(x)) / 2 + assert manualintegrate(2*x*cos(x), x) == 2*x*sin(x) + 2*cos(x) + assert manualintegrate(x * log(x), x) == x**2*log(x)/2 - x**2/4 + assert manualintegrate(log(x), x) == x * log(x) - x + assert manualintegrate((3*x**2 + 5) * exp(x), x) == \ + 3*x**2*exp(x) - 6*x*exp(x) + 11*exp(x) + assert manualintegrate(atan(x), x) == x*atan(x) - log(x**2 + 1)/2 + + # Make sure _parts_rule doesn't pick u = constant but can pick dv = + # constant if necessary, e.g. for integrate(atan(x)) + assert _parts_rule(cos(x), x) == None + assert _parts_rule(exp(x), x) == None + assert _parts_rule(x**2, x) == None + result = _parts_rule(atan(x), x) + assert result[0] == atan(x) and result[1] == 1 + + +def test_manualintegrate_trigonometry(): + assert manualintegrate(sin(x), x) == -cos(x) + assert manualintegrate(tan(x), x) == -log(cos(x)) + + assert manualintegrate(sec(x), x) == log(sec(x) + tan(x)) + assert manualintegrate(csc(x), x) == -log(csc(x) + cot(x)) + + assert manualintegrate(sin(x) * cos(x), x) in [sin(x) ** 2 / 2, -cos(x)**2 / 2] + assert manualintegrate(-sec(x) * tan(x), x) == -sec(x) + assert manualintegrate(csc(x) * cot(x), x) == -csc(x) + assert manualintegrate(sec(x)**2, x) == tan(x) + assert manualintegrate(csc(x)**2, x) == -cot(x) + + assert manualintegrate(x * sec(x**2), x) == log(tan(x**2) + sec(x**2))/2 + assert manualintegrate(cos(x)*csc(sin(x)), x) == -log(cot(sin(x)) + csc(sin(x))) + assert manualintegrate(cos(3*x)*sec(x), x) == -x + sin(2*x) + assert manualintegrate(sin(3*x)*sec(x), x) == \ + -3*log(cos(x)) + 2*log(cos(x)**2) - 2*cos(x)**2 + + assert_is_integral_of(sinh(2*x), cosh(2*x)/2) + assert_is_integral_of(x*cosh(x**2), sinh(x**2)/2) + assert_is_integral_of(tanh(x), log(cosh(x))) + assert_is_integral_of(coth(x), log(sinh(x))) + f, F = sech(x), 2*atan(tanh(x/2)) + assert manualintegrate(f, x) == F + assert (F.diff(x) - f).rewrite(exp).simplify() == 0 # todo: equals returns None + f, F = csch(x), log(tanh(x/2)) + assert manualintegrate(f, x) == F + assert (F.diff(x) - f).rewrite(exp).simplify() == 0 + + +@slow +def test_manualintegrate_trigpowers(): + assert manualintegrate(sin(x)**2 * cos(x), x) == sin(x)**3 / 3 + assert manualintegrate(sin(x)**2 * cos(x) **2, x) == \ + x / 8 - sin(4*x) / 32 + assert manualintegrate(sin(x) * cos(x)**3, x) == -cos(x)**4 / 4 + assert manualintegrate(sin(x)**3 * cos(x)**2, x) == \ + cos(x)**5 / 5 - cos(x)**3 / 3 + + assert manualintegrate(tan(x)**3 * sec(x), x) == sec(x)**3/3 - sec(x) + assert manualintegrate(tan(x) * sec(x) **2, x) == sec(x)**2/2 + + assert manualintegrate(cot(x)**5 * csc(x), x) == \ + -csc(x)**5/5 + 2*csc(x)**3/3 - csc(x) + assert manualintegrate(cot(x)**2 * csc(x)**6, x) == \ + -cot(x)**7/7 - 2*cot(x)**5/5 - cot(x)**3/3 + + +@slow +def test_manualintegrate_inversetrig(): + # atan + assert manualintegrate(exp(x) / (1 + exp(2*x)), x) == atan(exp(x)) + assert manualintegrate(1 / (4 + 9 * x**2), x) == atan(3 * x/2) / 6 + assert manualintegrate(1 / (16 + 16 * x**2), x) == atan(x) / 16 + assert manualintegrate(1 / (4 + x**2), x) == atan(x / 2) / 2 + assert manualintegrate(1 / (1 + 4 * x**2), x) == atan(2*x) / 2 + ra = Symbol('a', real=True) + rb = Symbol('b', real=True) + assert manualintegrate(1/(ra + rb*x**2), x) == \ + Piecewise((atan(x/sqrt(ra/rb))/(rb*sqrt(ra/rb)), ra/rb > 0), + ((log(x - sqrt(-ra/rb)) - log(x + sqrt(-ra/rb)))/(2*sqrt(rb)*sqrt(-ra)), True)) + assert manualintegrate(1/(4 + rb*x**2), x) == \ + Piecewise((atan(x/(2*sqrt(1/rb)))/(2*rb*sqrt(1/rb)), 1/rb > 0), + (-I*(log(x - 2*sqrt(-1/rb)) - log(x + 2*sqrt(-1/rb)))/(4*sqrt(rb)), True)) + assert manualintegrate(1/(ra + 4*x**2), x) == \ + Piecewise((atan(2*x/sqrt(ra))/(2*sqrt(ra)), ra > 0), + ((log(x - sqrt(-ra)/2) - log(x + sqrt(-ra)/2))/(4*sqrt(-ra)), True)) + assert manualintegrate(1/(4 + 4*x**2), x) == atan(x) / 4 + + assert manualintegrate(1/(a + b*x**2), x) == Piecewise((atan(x/sqrt(a/b))/(b*sqrt(a/b)), Ne(a, 0)), + (-1/(b*x), True)) + + # asin + assert manualintegrate(1/sqrt(1-x**2), x) == asin(x) + assert manualintegrate(1/sqrt(4-4*x**2), x) == asin(x)/2 + assert manualintegrate(3/sqrt(1-9*x**2), x) == asin(3*x) + assert manualintegrate(1/sqrt(4-9*x**2), x) == asin(x*Rational(3, 2))/3 + + # asinh + assert manualintegrate(1/sqrt(x**2 + 1), x) == \ + asinh(x) + assert manualintegrate(1/sqrt(x**2 + 4), x) == \ + asinh(x/2) + assert manualintegrate(1/sqrt(4*x**2 + 4), x) == \ + asinh(x)/2 + assert manualintegrate(1/sqrt(4*x**2 + 1), x) == \ + asinh(2*x)/2 + assert manualintegrate(1/sqrt(ra*x**2 + 1), x) == \ + Piecewise((asin(x*sqrt(-ra))/sqrt(-ra), ra < 0), (asinh(sqrt(ra)*x)/sqrt(ra), ra > 0), (x, True)) + assert manualintegrate(1/sqrt(ra + x**2), x) == \ + Piecewise((asinh(x*sqrt(1/ra)), ra > 0), (log(2*x + 2*sqrt(ra + x**2)), True)) + + # log + assert manualintegrate(1/sqrt(x**2 - 1), x) == log(2*x + 2*sqrt(x**2 - 1)) + assert manualintegrate(1/sqrt(x**2 - 4), x) == log(2*x + 2*sqrt(x**2 - 4)) + assert manualintegrate(1/sqrt(4*x**2 - 4), x) == log(8*x + 4*sqrt(4*x**2 - 4))/2 + assert manualintegrate(1/sqrt(9*x**2 - 1), x) == log(18*x + 6*sqrt(9*x**2 - 1))/3 + assert manualintegrate(1/sqrt(ra*x**2 - 4), x) == \ + Piecewise((log(2*sqrt(ra)*sqrt(ra*x**2 - 4) + 2*ra*x)/sqrt(ra), Ne(ra, 0)), (-I*x/2, True)) + assert manualintegrate(1/sqrt(-ra + 4*x**2), x) == \ + Piecewise((asinh(2*x*sqrt(-1/ra))/2, ra < 0), (log(8*x + 4*sqrt(-ra + 4*x**2))/2, True)) + + # From https://www.wikiwand.com/en/List_of_integrals_of_inverse_trigonometric_functions + # asin + assert manualintegrate(asin(x), x) == x*asin(x) + sqrt(1 - x**2) + assert manualintegrate(asin(a*x), x) == Piecewise(((a*x*asin(a*x) + sqrt(-a**2*x**2 + 1))/a, Ne(a, 0)), (0, True)) + assert manualintegrate(x*asin(a*x), x) == \ + -a*Piecewise((-x*sqrt(-a**2*x**2 + 1)/(2*a**2) + + log(-2*a**2*x + 2*sqrt(-a**2)*sqrt(-a**2*x**2 + 1))/(2*a**2*sqrt(-a**2)), Ne(a**2, 0)), + (x**3/3, True))/2 + x**2*asin(a*x)/2 + # acos + assert manualintegrate(acos(x), x) == x*acos(x) - sqrt(1 - x**2) + assert manualintegrate(acos(a*x), x) == Piecewise(((a*x*acos(a*x) - sqrt(-a**2*x**2 + 1))/a, Ne(a, 0)), (pi*x/2, True)) + assert manualintegrate(x*acos(a*x), x) == \ + a*Piecewise((-x*sqrt(-a**2*x**2 + 1)/(2*a**2) + + log(-2*a**2*x + 2*sqrt(-a**2)*sqrt(-a**2*x**2 + 1))/(2*a**2*sqrt(-a**2)), Ne(a**2, 0)), + (x**3/3, True))/2 + x**2*acos(a*x)/2 + # atan + assert manualintegrate(atan(x), x) == x*atan(x) - log(x**2 + 1)/2 + assert manualintegrate(atan(a*x), x) == Piecewise(((a*x*atan(a*x) - log(a**2*x**2 + 1)/2)/a, Ne(a, 0)), (0, True)) + assert manualintegrate(x*atan(a*x), x) == -a*(x/a**2 - atan(x/sqrt(a**(-2)))/(a**4*sqrt(a**(-2))))/2 + x**2*atan(a*x)/2 + # acsc + assert manualintegrate(acsc(x), x) == x*acsc(x) + Integral(1/(x*sqrt(1 - 1/x**2)), x) + assert manualintegrate(acsc(a*x), x) == x*acsc(a*x) + Integral(1/(x*sqrt(1 - 1/(a**2*x**2))), x)/a + assert manualintegrate(x*acsc(a*x), x) == x**2*acsc(a*x)/2 + Integral(1/sqrt(1 - 1/(a**2*x**2)), x)/(2*a) + # asec + assert manualintegrate(asec(x), x) == x*asec(x) - Integral(1/(x*sqrt(1 - 1/x**2)), x) + assert manualintegrate(asec(a*x), x) == x*asec(a*x) - Integral(1/(x*sqrt(1 - 1/(a**2*x**2))), x)/a + assert manualintegrate(x*asec(a*x), x) == x**2*asec(a*x)/2 - Integral(1/sqrt(1 - 1/(a**2*x**2)), x)/(2*a) + # acot + assert manualintegrate(acot(x), x) == x*acot(x) + log(x**2 + 1)/2 + assert manualintegrate(acot(a*x), x) == Piecewise(((a*x*acot(a*x) + log(a**2*x**2 + 1)/2)/a, Ne(a, 0)), (pi*x/2, True)) + assert manualintegrate(x*acot(a*x), x) == a*(x/a**2 - atan(x/sqrt(a**(-2)))/(a**4*sqrt(a**(-2))))/2 + x**2*acot(a*x)/2 + + # piecewise + assert manualintegrate(1/sqrt(ra-rb*x**2), x) == \ + Piecewise((asin(x*sqrt(rb/ra))/sqrt(rb), And(-rb < 0, ra > 0)), + (asinh(x*sqrt(-rb/ra))/sqrt(-rb), And(-rb > 0, ra > 0)), + (log(-2*rb*x + 2*sqrt(-rb)*sqrt(ra - rb*x**2))/sqrt(-rb), Ne(rb, 0)), + (x/sqrt(ra), True)) + assert manualintegrate(1/sqrt(ra + rb*x**2), x) == \ + Piecewise((asin(x*sqrt(-rb/ra))/sqrt(-rb), And(ra > 0, rb < 0)), + (asinh(x*sqrt(rb/ra))/sqrt(rb), And(ra > 0, rb > 0)), + (log(2*sqrt(rb)*sqrt(ra + rb*x**2) + 2*rb*x)/sqrt(rb), Ne(rb, 0)), + (x/sqrt(ra), True)) + + +def test_manualintegrate_trig_substitution(): + assert manualintegrate(sqrt(16*x**2 - 9)/x, x) == \ + Piecewise((sqrt(16*x**2 - 9) - 3*acos(3/(4*x)), + And(x < Rational(3, 4), x > Rational(-3, 4)))) + assert manualintegrate(1/(x**4 * sqrt(25-x**2)), x) == \ + Piecewise((-sqrt(-x**2/25 + 1)/(125*x) - + (-x**2/25 + 1)**(3*S.Half)/(15*x**3), And(x < 5, x > -5))) + assert manualintegrate(x**7/(49*x**2 + 1)**(3 * S.Half), x) == \ + ((49*x**2 + 1)**(5*S.Half)/28824005 - + (49*x**2 + 1)**(3*S.Half)/5764801 + + 3*sqrt(49*x**2 + 1)/5764801 + 1/(5764801*sqrt(49*x**2 + 1))) + +def test_manualintegrate_trivial_substitution(): + assert manualintegrate((exp(x) - exp(-x))/x, x) == -Ei(-x) + Ei(x) + f = Function('f') + assert manualintegrate((f(x) - f(-x))/x, x) == \ + -Integral(f(-x)/x, x) + Integral(f(x)/x, x) + + +def test_manualintegrate_rational(): + assert manualintegrate(1/(4 - x**2), x) == -log(x - 2)/4 + log(x + 2)/4 + assert manualintegrate(1/(-1 + x**2), x) == log(x - 1)/2 - log(x + 1)/2 + + +def test_manualintegrate_special(): + f, F = 4*exp(-x**2/3), 2*sqrt(3)*sqrt(pi)*erf(sqrt(3)*x/3) + assert_is_integral_of(f, F) + f, F = 3*exp(4*x**2), 3*sqrt(pi)*erfi(2*x)/4 + assert_is_integral_of(f, F) + f, F = x**Rational(1, 3)*exp(-x/8), -16*uppergamma(Rational(4, 3), x/8) + assert_is_integral_of(f, F) + f, F = exp(2*x)/x, Ei(2*x) + assert_is_integral_of(f, F) + f, F = exp(1 + 2*x - x**2), sqrt(pi)*exp(2)*erf(x - 1)/2 + assert_is_integral_of(f, F) + f = sin(x**2 + 4*x + 1) + F = (sqrt(2)*sqrt(pi)*(-sin(3)*fresnelc(sqrt(2)*(2*x + 4)/(2*sqrt(pi))) + + cos(3)*fresnels(sqrt(2)*(2*x + 4)/(2*sqrt(pi))))/2) + assert_is_integral_of(f, F) + f, F = cos(4*x**2), sqrt(2)*sqrt(pi)*fresnelc(2*sqrt(2)*x/sqrt(pi))/4 + assert_is_integral_of(f, F) + f, F = sin(3*x + 2)/x, sin(2)*Ci(3*x) + cos(2)*Si(3*x) + assert_is_integral_of(f, F) + f, F = sinh(3*x - 2)/x, -sinh(2)*Chi(3*x) + cosh(2)*Shi(3*x) + assert_is_integral_of(f, F) + f, F = 5*cos(2*x - 3)/x, 5*cos(3)*Ci(2*x) + 5*sin(3)*Si(2*x) + assert_is_integral_of(f, F) + f, F = cosh(x/2)/x, Chi(x/2) + assert_is_integral_of(f, F) + f, F = cos(x**2)/x, Ci(x**2)/2 + assert_is_integral_of(f, F) + f, F = 1/log(2*x + 1), li(2*x + 1)/2 + assert_is_integral_of(f, F) + f, F = polylog(2, 5*x)/x, polylog(3, 5*x) + assert_is_integral_of(f, F) + f, F = 5/sqrt(3 - 2*sin(x)**2), 5*sqrt(3)*elliptic_f(x, Rational(2, 3))/3 + assert_is_integral_of(f, F) + f, F = sqrt(4 + 9*sin(x)**2), 2*elliptic_e(x, Rational(-9, 4)) + assert_is_integral_of(f, F) + + +def test_manualintegrate_derivative(): + assert manualintegrate(pi * Derivative(x**2 + 2*x + 3), x) == \ + pi * (x**2 + 2*x + 3) + assert manualintegrate(Derivative(x**2 + 2*x + 3, y), x) == \ + Integral(Derivative(x**2 + 2*x + 3, y)) + assert manualintegrate(Derivative(sin(x), x, x, x, y), x) == \ + Derivative(sin(x), x, x, y) + + +def test_manualintegrate_Heaviside(): + assert_is_integral_of(DiracDelta(3*x+2), Heaviside(3*x+2)/3) + assert_is_integral_of(DiracDelta(3*x, 0), Heaviside(3*x)/3) + assert manualintegrate(DiracDelta(a+b*x, 1), x) == \ + Piecewise((DiracDelta(a + b*x)/b, Ne(b, 0)), (x*DiracDelta(a, 1), True)) + assert_is_integral_of(DiracDelta(x/3-1, 2), 3*DiracDelta(x/3-1, 1)) + assert manualintegrate(Heaviside(x), x) == x*Heaviside(x) + assert manualintegrate(x*Heaviside(2), x) == x**2/2 + assert manualintegrate(x*Heaviside(-2), x) == 0 + assert manualintegrate(x*Heaviside( x), x) == x**2*Heaviside( x)/2 + assert manualintegrate(x*Heaviside(-x), x) == x**2*Heaviside(-x)/2 + assert manualintegrate(Heaviside(2*x + 4), x) == (x+2)*Heaviside(2*x + 4) + assert manualintegrate(x*Heaviside(x), x) == x**2*Heaviside(x)/2 + assert manualintegrate(Heaviside(x + 1)*Heaviside(1 - x)*x**2, x) == \ + ((x**3/3 + Rational(1, 3))*Heaviside(x + 1) - Rational(2, 3))*Heaviside(-x + 1) + + y = Symbol('y') + assert manualintegrate(sin(7 + x)*Heaviside(3*x - 7), x) == \ + (- cos(x + 7) + cos(Rational(28, 3)))*Heaviside(3*x - S(7)) + + assert manualintegrate(sin(y + x)*Heaviside(3*x - y), x) == \ + (cos(y*Rational(4, 3)) - cos(x + y))*Heaviside(3*x - y) + + +def test_manualintegrate_orthogonal_poly(): + n = symbols('n') + a, b = 7, Rational(5, 3) + polys = [jacobi(n, a, b, x), gegenbauer(n, a, x), chebyshevt(n, x), + chebyshevu(n, x), legendre(n, x), hermite(n, x), laguerre(n, x), + assoc_laguerre(n, a, x)] + for p in polys: + integral = manualintegrate(p, x) + for deg in [-2, -1, 0, 1, 3, 5, 8]: + # some accept negative "degree", some do not + try: + p_subbed = p.subs(n, deg) + except ValueError: + continue + assert (integral.subs(n, deg).diff(x) - p_subbed).expand() == 0 + + # can also integrate simple expressions with these polynomials + q = x*p.subs(x, 2*x + 1) + integral = manualintegrate(q, x) + for deg in [2, 4, 7]: + assert (integral.subs(n, deg).diff(x) - q.subs(n, deg)).expand() == 0 + + # cannot integrate with respect to any other parameter + t = symbols('t') + for i in range(len(p.args) - 1): + new_args = list(p.args) + new_args[i] = t + assert isinstance(manualintegrate(p.func(*new_args), t), Integral) + + +@slow +def test_issue_6799(): + r, x, phi = map(Symbol, 'r x phi'.split()) + n = Symbol('n', integer=True, positive=True) + + integrand = (cos(n*(x-phi))*cos(n*x)) + limits = (x, -pi, pi) + assert manualintegrate(integrand, x) == \ + ((n*x/2 + sin(2*n*x)/4)*cos(n*phi) - sin(n*phi)*cos(n*x)**2/2)/n + assert r * integrate(integrand, limits).trigsimp() / pi == r * cos(n * phi) + assert not integrate(integrand, limits).has(Dummy) + + +def test_issue_12251(): + assert manualintegrate(x**y, x) == Piecewise( + (x**(y + 1)/(y + 1), Ne(y, -1)), (log(x), True)) + + +def test_issue_3796(): + assert manualintegrate(diff(exp(x + x**2)), x) == exp(x + x**2) + assert integrate(x * exp(x**4), x, risch=False) == -I*sqrt(pi)*erf(I*x**2)/4 + + +def test_manual_true(): + assert integrate(exp(x) * sin(x), x, manual=True) == \ + (exp(x) * sin(x)) / 2 - (exp(x) * cos(x)) / 2 + assert integrate(sin(x) * cos(x), x, manual=True) in \ + [sin(x) ** 2 / 2, -cos(x)**2 / 2] + + +def test_issue_6746(): + y = Symbol('y') + n = Symbol('n') + assert manualintegrate(y**x, x) == Piecewise( + (y**x/log(y), Ne(log(y), 0)), (x, True)) + assert manualintegrate(y**(n*x), x) == Piecewise( + (Piecewise( + (y**(n*x)/log(y), Ne(log(y), 0)), + (n*x, True) + )/n, Ne(n, 0)), + (x, True)) + assert manualintegrate(exp(n*x), x) == Piecewise( + (exp(n*x)/n, Ne(n, 0)), (x, True)) + + y = Symbol('y', positive=True) + assert manualintegrate((y + 1)**x, x) == (y + 1)**x/log(y + 1) + y = Symbol('y', zero=True) + assert manualintegrate((y + 1)**x, x) == x + y = Symbol('y') + n = Symbol('n', nonzero=True) + assert manualintegrate(y**(n*x), x) == Piecewise( + (y**(n*x)/log(y), Ne(log(y), 0)), (n*x, True))/n + y = Symbol('y', positive=True) + assert manualintegrate((y + 1)**(n*x), x) == \ + (y + 1)**(n*x)/(n*log(y + 1)) + a = Symbol('a', negative=True) + b = Symbol('b') + assert manualintegrate(1/(a + b*x**2), x) == atan(x/sqrt(a/b))/(b*sqrt(a/b)) + b = Symbol('b', negative=True) + assert manualintegrate(1/(a + b*x**2), x) == \ + atan(x/(sqrt(-a)*sqrt(-1/b)))/(b*sqrt(-a)*sqrt(-1/b)) + assert manualintegrate(1/((x**a + y**b + 4)*sqrt(a*x**2 + 1)), x) == \ + y**(-b)*Integral(x**(-a)/(y**(-b)*sqrt(a*x**2 + 1) + + x**(-a)*sqrt(a*x**2 + 1) + 4*x**(-a)*y**(-b)*sqrt(a*x**2 + 1)), x) + assert manualintegrate(1/((x**2 + 4)*sqrt(4*x**2 + 1)), x) == \ + Integral(1/((x**2 + 4)*sqrt(4*x**2 + 1)), x) + assert manualintegrate(1/(x - a**x + x*b**2), x) == \ + Integral(1/(-a**x + b**2*x + x), x) + + +@slow +def test_issue_2850(): + assert manualintegrate(asin(x)*log(x), x) == -x*asin(x) - sqrt(-x**2 + 1) \ + + (x*asin(x) + sqrt(-x**2 + 1))*log(x) - Integral(sqrt(-x**2 + 1)/x, x) + assert manualintegrate(acos(x)*log(x), x) == -x*acos(x) + sqrt(-x**2 + 1) + \ + (x*acos(x) - sqrt(-x**2 + 1))*log(x) + Integral(sqrt(-x**2 + 1)/x, x) + assert manualintegrate(atan(x)*log(x), x) == -x*atan(x) + (x*atan(x) - \ + log(x**2 + 1)/2)*log(x) + log(x**2 + 1)/2 + Integral(log(x**2 + 1)/x, x)/2 + + +def test_issue_9462(): + assert manualintegrate(sin(2*x)*exp(x), x) == exp(x)*sin(2*x)/5 - 2*exp(x)*cos(2*x)/5 + assert not integral_steps(sin(2*x)*exp(x), x).contains_dont_know() + assert manualintegrate((x - 3) / (x**2 - 2*x + 2)**2, x) == \ + Integral(x/(x**4 - 4*x**3 + 8*x**2 - 8*x + 4), x) \ + - 3*Integral(1/(x**4 - 4*x**3 + 8*x**2 - 8*x + 4), x) + + +def test_cyclic_parts(): + f = cos(x)*exp(x/4) + F = 16*exp(x/4)*sin(x)/17 + 4*exp(x/4)*cos(x)/17 + assert manualintegrate(f, x) == F and F.diff(x) == f + f = x*cos(x)*exp(x/4) + F = (x*(16*exp(x/4)*sin(x)/17 + 4*exp(x/4)*cos(x)/17) - + 128*exp(x/4)*sin(x)/289 + 240*exp(x/4)*cos(x)/289) + assert manualintegrate(f, x) == F and F.diff(x) == f + + +@slow +def test_issue_10847_slow(): + assert manualintegrate((4*x**4 + 4*x**3 + 16*x**2 + 12*x + 8) + / (x**6 + 2*x**5 + 3*x**4 + 4*x**3 + 3*x**2 + 2*x + 1), x) == \ + 2*x/(x**2 + 1) + 3*atan(x) - 1/(x**2 + 1) - 3/(x + 1) + + +@slow +def test_issue_10847(): + + assert manualintegrate(x**2 / (x**2 - c), x) == \ + c*Piecewise((atan(x/sqrt(-c))/sqrt(-c), Ne(c, 0)), (-1/x, True)) + x + + rc = Symbol('c', real=True) + assert manualintegrate(x**2 / (x**2 - rc), x) == \ + rc*Piecewise((atan(x/sqrt(-rc))/sqrt(-rc), rc < 0), + ((log(-sqrt(rc) + x) - log(sqrt(rc) + x))/(2*sqrt(rc)), True)) + x + + assert manualintegrate(sqrt(x - y) * log(z / x), x) == \ + 4*y**2*Piecewise((atan(sqrt(x - y)/sqrt(y))/sqrt(y), Ne(y, 0)), + (-1/sqrt(x - y), True))/3 - 4*y*sqrt(x - y)/3 + \ + 2*(x - y)**Rational(3, 2)*log(z/x)/3 + 4*(x - y)**Rational(3, 2)/9 + ry = Symbol('y', real=True) + rz = Symbol('z', real=True) + assert manualintegrate(sqrt(x - ry) * log(rz / x), x) == \ + 4*ry**2*Piecewise((atan(sqrt(x - ry)/sqrt(ry))/sqrt(ry), ry > 0), + ((log(-sqrt(-ry) + sqrt(x - ry)) - log(sqrt(-ry) + sqrt(x - ry)))/(2*sqrt(-ry)), True))/3 \ + - 4*ry*sqrt(x - ry)/3 + 2*(x - ry)**Rational(3, 2)*log(rz/x)/3 \ + + 4*(x - ry)**Rational(3, 2)/9 + + assert manualintegrate(sqrt(x) * log(x), x) == 2*x**Rational(3, 2)*log(x)/3 - 4*x**Rational(3, 2)/9 + + result = manualintegrate(sqrt(a*x + b) / x, x) + assert result == Piecewise((-2*b*Piecewise( + (-atan(sqrt(a*x + b)/sqrt(-b))/sqrt(-b), Ne(b, 0)), + (1/sqrt(a*x + b), True)) + 2*sqrt(a*x + b), Ne(a, 0)), + (sqrt(b)*log(x), True)) + assert piecewise_fold(result) == Piecewise( + (2*b*atan(sqrt(a*x + b)/sqrt(-b))/sqrt(-b) + 2*sqrt(a*x + b), Ne(a, 0) & Ne(b, 0)), + (-2*b/sqrt(a*x + b) + 2*sqrt(a*x + b), Ne(a, 0)), + (sqrt(b)*log(x), True)) + + ra = Symbol('a', real=True) + rb = Symbol('b', real=True) + assert manualintegrate(sqrt(ra*x + rb) / x, x) == \ + Piecewise( + (-2*rb*Piecewise( + (-atan(sqrt(ra*x + rb)/sqrt(-rb))/sqrt(-rb), rb < 0), + (-I*(log(-sqrt(rb) + sqrt(ra*x + rb)) - log(sqrt(rb) + sqrt(ra*x + rb)))/(2*sqrt(-rb)), True)) + + 2*sqrt(ra*x + rb), Ne(ra, 0)), + (sqrt(rb)*log(x), True)) + + assert expand(manualintegrate(sqrt(ra*x + rb) / (x + rc), x)) == \ + Piecewise((-2*ra*rc*Piecewise((atan(sqrt(ra*x + rb)/sqrt(ra*rc - rb))/sqrt(ra*rc - rb), ra*rc - rb > 0), + (log(-sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)) - + log(sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)), True)) + + 2*rb*Piecewise((atan(sqrt(ra*x + rb)/sqrt(ra*rc - rb))/sqrt(ra*rc - rb), ra*rc - rb > 0), + (log(-sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)) - + log(sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)), True)) + + 2*sqrt(ra*x + rb), Ne(ra, 0)), (sqrt(rb)*log(rc + x), True)) + + assert manualintegrate(sqrt(2*x + 3) / (x + 1), x) == 2*sqrt(2*x + 3) - log(sqrt(2*x + 3) + 1) + log(sqrt(2*x + 3) - 1) + assert manualintegrate(sqrt(2*x + 3) / 2 * x, x) == (2*x + 3)**Rational(5, 2)/20 - (2*x + 3)**Rational(3, 2)/4 + assert manualintegrate(x**Rational(3,2) * log(x), x) == 2*x**Rational(5,2)*log(x)/5 - 4*x**Rational(5,2)/25 + assert manualintegrate(x**(-3) * log(x), x) == -log(x)/(2*x**2) - 1/(4*x**2) + assert manualintegrate(log(y)/(y**2*(1 - 1/y)), y) == \ + log(y)*log(-1 + 1/y) - Integral(log(-1 + 1/y)/y, y) + + +def test_issue_12899(): + assert manualintegrate(f(x,y).diff(x),y) == Integral(Derivative(f(x,y),x),y) + assert manualintegrate(f(x,y).diff(y).diff(x),y) == Derivative(f(x,y),x) + + +def test_constant_independent_of_symbol(): + assert manualintegrate(Integral(y, (x, 1, 2)), x) == \ + x*Integral(y, (x, 1, 2)) + + +def test_issue_12641(): + assert manualintegrate(sin(2*x), x) == -cos(2*x)/2 + assert manualintegrate(cos(x)*sin(2*x), x) == -2*cos(x)**3/3 + assert manualintegrate((sin(2*x)*cos(x))/(1 + cos(x)), x) == \ + -2*log(cos(x) + 1) - cos(x)**2 + 2*cos(x) + + +@slow +def test_issue_13297(): + assert manualintegrate(sin(x) * cos(x)**5, x) == -cos(x)**6 / 6 + + +def test_issue_14470(): + assert_is_integral_of(1/(x*sqrt(x + 1)), log(sqrt(x + 1) - 1) - log(sqrt(x + 1) + 1)) + + +@slow +def test_issue_9858(): + assert manualintegrate(exp(x)*cos(exp(x)), x) == sin(exp(x)) + assert manualintegrate(exp(2*x)*cos(exp(x)), x) == \ + exp(x)*sin(exp(x)) + cos(exp(x)) + res = manualintegrate(exp(10*x)*sin(exp(x)), x) + assert not res.has(Integral) + assert res.diff(x) == exp(10*x)*sin(exp(x)) + # an example with many similar integrations by parts + assert manualintegrate(sum(x*exp(k*x) for k in range(1, 8)), x) == ( + x*exp(7*x)/7 + x*exp(6*x)/6 + x*exp(5*x)/5 + x*exp(4*x)/4 + + x*exp(3*x)/3 + x*exp(2*x)/2 + x*exp(x) - exp(7*x)/49 -exp(6*x)/36 - + exp(5*x)/25 - exp(4*x)/16 - exp(3*x)/9 - exp(2*x)/4 - exp(x)) + + +def test_issue_8520(): + assert manualintegrate(x/(x**4 + 1), x) == atan(x**2)/2 + assert manualintegrate(x**2/(x**6 + 25), x) == atan(x**3/5)/15 + f = x/(9*x**4 + 4)**2 + assert manualintegrate(f, x).diff(x).factor() == f + + +def test_manual_subs(): + x, y = symbols('x y') + expr = log(x) + exp(x) + # if log(x) is y, then exp(y) is x + assert manual_subs(expr, log(x), y) == y + exp(exp(y)) + # if exp(x) is y, then log(y) need not be x + assert manual_subs(expr, exp(x), y) == log(x) + y + + raises(ValueError, lambda: manual_subs(expr, x)) + raises(ValueError, lambda: manual_subs(expr, exp(x), x, y)) + + +@slow +def test_issue_15471(): + f = log(x)*cos(log(x))/x**Rational(3, 4) + F = -128*x**Rational(1, 4)*sin(log(x))/289 + 240*x**Rational(1, 4)*cos(log(x))/289 + (16*x**Rational(1, 4)*sin(log(x))/17 + 4*x**Rational(1, 4)*cos(log(x))/17)*log(x) + assert_is_integral_of(f, F) + + +def test_quadratic_denom(): + f = (5*x + 2)/(3*x**2 - 2*x + 8) + assert manualintegrate(f, x) == 5*log(3*x**2 - 2*x + 8)/6 + 11*sqrt(23)*atan(3*sqrt(23)*(x - Rational(1, 3))/23)/69 + g = 3/(2*x**2 + 3*x + 1) + assert manualintegrate(g, x) == 3*log(4*x + 2) - 3*log(4*x + 4) + +def test_issue_22757(): + assert manualintegrate(sin(x), y) == y * sin(x) + + +def test_issue_23348(): + steps = integral_steps(tan(x), x) + constant_times_step = steps.substep.substep + assert constant_times_step.integrand == constant_times_step.constant * constant_times_step.other + + +def test_issue_23566(): + i = Integral(1/sqrt(x**2 - 1), (x, -2, -1)).doit(manual=True) + assert i == -log(4 - 2*sqrt(3)) + log(2) + assert str(i.n()) == '1.31695789692482' + + +def test_issue_25093(): + ap = Symbol('ap', positive=True) + an = Symbol('an', negative=True) + assert manualintegrate(exp(a*x**2 + b), x) == sqrt(pi)*exp(b)*erfi(sqrt(a)*x)/(2*sqrt(a)) + assert manualintegrate(exp(ap*x**2 + b), x) == sqrt(pi)*exp(b)*erfi(sqrt(ap)*x)/(2*sqrt(ap)) + assert manualintegrate(exp(an*x**2 + b), x) == -sqrt(pi)*exp(b)*erf(an*x/sqrt(-an))/(2*sqrt(-an)) + assert manualintegrate(sin(a*x**2 + b), x) == ( + sqrt(2)*sqrt(pi)*(sin(b)*fresnelc(sqrt(2)*sqrt(a)*x/sqrt(pi)) + + cos(b)*fresnels(sqrt(2)*sqrt(a)*x/sqrt(pi)))/(2*sqrt(a))) + assert manualintegrate(cos(a*x**2 + b), x) == ( + sqrt(2)*sqrt(pi)*(-sin(b)*fresnels(sqrt(2)*sqrt(a)*x/sqrt(pi)) + + cos(b)*fresnelc(sqrt(2)*sqrt(a)*x/sqrt(pi)))/(2*sqrt(a))) + + +def test_nested_pow(): + assert_is_integral_of(sqrt(x**2), x*sqrt(x**2)/2) + assert_is_integral_of(sqrt(x**(S(5)/3)), 6*x*sqrt(x**(S(5)/3))/11) + assert_is_integral_of(1/sqrt(x**2), x*log(x)/sqrt(x**2)) + assert_is_integral_of(x*sqrt(x**(-4)), x**2*sqrt(x**-4)*log(x)) + f = (c*(a+b*x)**d)**e + F1 = (c*(a + b*x)**d)**e*(a/b + x)/(d*e + 1) + F2 = (c*(a + b*x)**d)**e*(a/b + x)*log(a/b + x) + assert manualintegrate(f, x) == \ + Piecewise((Piecewise((F1, Ne(d*e, -1)), (F2, True)), Ne(b, 0)), (x*(a**d*c)**e, True)) + assert F1.diff(x).equals(f) + assert F2.diff(x).subs(d*e, -1).equals(f) + + +def test_manualintegrate_sqrt_linear(): + assert_is_integral_of((5*x**3+4)/sqrt(2+3*x), + 10*(3*x + 2)**(S(7)/2)/567 - 4*(3*x + 2)**(S(5)/2)/27 + + 40*(3*x + 2)**(S(3)/2)/81 + 136*sqrt(3*x + 2)/81) + assert manualintegrate(x/sqrt(a+b*x)**3, x) == \ + Piecewise((Mul(2, b**-2, a/sqrt(a + b*x) + sqrt(a + b*x)), Ne(b, 0)), (x**2/(2*a**(S(3)/2)), True)) + assert_is_integral_of((sqrt(3*x+3)+1)/((2*x+2)**(1/S(3))+1), + 3*sqrt(6)*(2*x + 2)**(S(7)/6)/14 - 3*sqrt(6)*(2*x + 2)**(S(5)/6)/10 - + 3*sqrt(6)*(2*x + 2)**(S.One/6)/2 + 3*(2*x + 2)**(S(2)/3)/4 - 3*(2*x + 2)**(S.One/3)/2 + + sqrt(6)*sqrt(2*x + 2)/2 + 3*log((2*x + 2)**(S.One/3) + 1)/2 + + 3*sqrt(6)*atan((2*x + 2)**(S.One/6))/2) + assert_is_integral_of(sqrt(x+sqrt(x)), + 2*sqrt(sqrt(x) + x)*(sqrt(x)/12 + x/3 - S(1)/8) + log(2*sqrt(x) + 2*sqrt(sqrt(x) + x) + 1)/8) + assert_is_integral_of(sqrt(2*x+3+sqrt(4*x+5))**3, + sqrt(2*x + sqrt(4*x + 5) + 3) * + (9*x/10 + 11*(4*x + 5)**(S(3)/2)/40 + sqrt(4*x + 5)/40 + (4*x + 5)**2/10 + S(11)/10)/2) + + +def test_manualintegrate_sqrt_quadratic(): + assert_is_integral_of(1/sqrt((x - I)**2-1), log(2*x + 2*sqrt(x**2 - 2*I*x - 2) - 2*I)) + assert_is_integral_of(1/sqrt(3*x**2+4*x+5), sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/3) + assert_is_integral_of(1/sqrt(-3*x**2+4*x+5), sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/3) + assert_is_integral_of(1/sqrt(3*x**2+4*x-5), sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/3) + assert_is_integral_of(1/sqrt(4*x**2-4*x+1), (x - S.Half)*log(x - S.Half)/(2*sqrt((x - S.Half)**2))) + assert manualintegrate(1/sqrt(a+b*x+c*x**2), x) == \ + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(c, 0) & Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), Ne(c, 0)), + (2*sqrt(a + b*x)/b, Ne(b, 0)), (x/sqrt(a), True)) + + assert_is_integral_of((7*x+6)/sqrt(3*x**2+4*x+5), + 7*sqrt(3*x**2 + 4*x + 5)/3 + 4*sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/9) + assert_is_integral_of((7*x+6)/sqrt(-3*x**2+4*x+5), + -7*sqrt(-3*x**2 + 4*x + 5)/3 + 32*sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/9) + assert_is_integral_of((7*x+6)/sqrt(3*x**2+4*x-5), + 7*sqrt(3*x**2 + 4*x - 5)/3 + 4*sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/9) + assert manualintegrate((d+e*x)/sqrt(a+b*x+c*x**2), x) == \ + Piecewise(((-b*e/(2*c) + d) * + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) + + e*sqrt(a + b*x + c*x**2)/c, Ne(c, 0)), + ((2*d*sqrt(a + b*x) + 2*e*(-a*sqrt(a + b*x) + (a + b*x)**(S(3)/2)/3)/b)/b, Ne(b, 0)), + ((d*x + e*x**2/2)/sqrt(a), True)) + + assert manualintegrate((3*x**3-x**2+2*x-4)/sqrt(x**2-3*x+2), x) == \ + sqrt(x**2 - 3*x + 2)*(x**2 + 13*x/4 + S(101)/8) + 135*log(2*x + 2*sqrt(x**2 - 3*x + 2) - 3)/16 + + assert_is_integral_of(sqrt(53225*x**2-66732*x+23013), + (x/2 - S(16683)/53225)*sqrt(53225*x**2 - 66732*x + 23013) + + 111576969*sqrt(2129)*asinh(53225*x/10563 - S(11122)/3521)/1133160250) + assert manualintegrate(sqrt(a+c*x**2), x) == \ + Piecewise((a*Piecewise((log(2*sqrt(c)*sqrt(a + c*x**2) + 2*c*x)/sqrt(c), Ne(a, 0)), + (x*log(x)/sqrt(c*x**2), True))/2 + x*sqrt(a + c*x**2)/2, Ne(c, 0)), + (sqrt(a)*x, True)) + assert manualintegrate(sqrt(a+b*x+c*x**2), x) == \ + Piecewise(((a/2 - b**2/(8*c)) * + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) + + (b/(4*c) + x/2)*sqrt(a + b*x + c*x**2), Ne(c, 0)), + (2*(a + b*x)**(S(3)/2)/(3*b), Ne(b, 0)), + (sqrt(a)*x, True)) + + assert_is_integral_of(x*sqrt(x**2+2*x+4), + (x**2/3 + x/6 + S(5)/6)*sqrt(x**2 + 2*x + 4) - 3*asinh(sqrt(3)*(x + 1)/3)/2) + + +def test_mul_pow_derivative(): + assert_is_integral_of(x*sec(x)*tan(x), x*sec(x) - log(tan(x) + sec(x))) + assert_is_integral_of(x*sec(x)**2, x*tan(x) + log(cos(x))) + assert_is_integral_of(x**3*Derivative(f(x), (x, 4)), + x**3*Derivative(f(x), (x, 3)) - 3*x**2*Derivative(f(x), (x, 2)) + + 6*x*Derivative(f(x), x) - 6*f(x)) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_meijerint.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_meijerint.py new file mode 100644 index 0000000000000000000000000000000000000000..899bc96e63d6dd5bccd52856f15d3085e87d5807 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_meijerint.py @@ -0,0 +1,774 @@ +from sympy.core.function import expand_func +from sympy.core.numbers import (I, Rational, oo, pi) +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.functions.elementary.complexes import Abs, arg, re, unpolarify +from sympy.functions.elementary.exponential import (exp, exp_polar, log) +from sympy.functions.elementary.hyperbolic import cosh, acosh, sinh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold +from sympy.functions.elementary.trigonometric import (cos, sin, sinc, asin) +from sympy.functions.special.error_functions import (erf, erfc) +from sympy.functions.special.gamma_functions import (gamma, polygamma) +from sympy.functions.special.hyper import (hyper, meijerg) +from sympy.integrals.integrals import (Integral, integrate) +from sympy.simplify.hyperexpand import hyperexpand +from sympy.simplify.simplify import simplify +from sympy.integrals.meijerint import (_rewrite_single, _rewrite1, + meijerint_indefinite, _inflate_g, _create_lookup_table, + meijerint_definite, meijerint_inversion) +from sympy.testing.pytest import slow +from sympy.core.random import (verify_numerically, + random_complex_number as randcplx) +from sympy.abc import x, y, a, b, c, d, s, t, z + + +def test_rewrite_single(): + def t(expr, c, m): + e = _rewrite_single(meijerg([a], [b], [c], [d], expr), x) + assert e is not None + assert isinstance(e[0][0][2], meijerg) + assert e[0][0][2].argument.as_coeff_mul(x) == (c, (m,)) + + def tn(expr): + assert _rewrite_single(meijerg([a], [b], [c], [d], expr), x) is None + + t(x, 1, x) + t(x**2, 1, x**2) + t(x**2 + y*x**2, y + 1, x**2) + tn(x**2 + x) + tn(x**y) + + def u(expr, x): + from sympy.core.add import Add + r = _rewrite_single(expr, x) + e = Add(*[res[0]*res[2] for res in r[0]]).replace( + exp_polar, exp) # XXX Hack? + assert verify_numerically(e, expr, x) + + u(exp(-x)*sin(x), x) + + # The following has stopped working because hyperexpand changed slightly. + # It is probably not worth fixing + #u(exp(-x)*sin(x)*cos(x), x) + + # This one cannot be done numerically, since it comes out as a g-function + # of argument 4*pi + # NOTE This also tests a bug in inverse mellin transform (which used to + # turn exp(4*pi*I*t) into a factor of exp(4*pi*I)**t instead of + # exp_polar). + #u(exp(x)*sin(x), x) + assert _rewrite_single(exp(x)*sin(x), x) == \ + ([(-sqrt(2)/(2*sqrt(pi)), 0, + meijerg(((Rational(-1, 2), 0, Rational(1, 4), S.Half, Rational(3, 4)), (1,)), + ((), (Rational(-1, 2), 0)), 64*exp_polar(-4*I*pi)/x**4))], True) + + +def test_rewrite1(): + assert _rewrite1(x**3*meijerg([a], [b], [c], [d], x**2 + y*x**2)*5, x) == \ + (5, x**3, [(1, 0, meijerg([a], [b], [c], [d], x**2*(y + 1)))], True) + + +def test_meijerint_indefinite_numerically(): + def t(fac, arg): + g = meijerg([a], [b], [c], [d], arg)*fac + subs = {a: randcplx()/10, b: randcplx()/10 + I, + c: randcplx(), d: randcplx()} + integral = meijerint_indefinite(g, x) + assert integral is not None + assert verify_numerically(g.subs(subs), integral.diff(x).subs(subs), x) + t(1, x) + t(2, x) + t(1, 2*x) + t(1, x**2) + t(5, x**S('3/2')) + t(x**3, x) + t(3*x**S('3/2'), 4*x**S('7/3')) + + +def test_meijerint_definite(): + v, b = meijerint_definite(x, x, 0, 0) + assert v.is_zero and b is True + v, b = meijerint_definite(x, x, oo, oo) + assert v.is_zero and b is True + + +def test_inflate(): + subs = {a: randcplx()/10, b: randcplx()/10 + I, c: randcplx(), + d: randcplx(), y: randcplx()/10} + + def t(a, b, arg, n): + from sympy.core.mul import Mul + m1 = meijerg(a, b, arg) + m2 = Mul(*_inflate_g(m1, n)) + # NOTE: (the random number)**9 must still be on the principal sheet. + # Thus make b&d small to create random numbers of small imaginary part. + return verify_numerically(m1.subs(subs), m2.subs(subs), x, b=0.1, d=-0.1) + assert t([[a], [b]], [[c], [d]], x, 3) + assert t([[a, y], [b]], [[c], [d]], x, 3) + assert t([[a], [b]], [[c, y], [d]], 2*x**3, 3) + + +def test_recursive(): + from sympy.core.symbol import symbols + a, b, c = symbols('a b c', positive=True) + r = exp(-(x - a)**2)*exp(-(x - b)**2) + e = integrate(r, (x, 0, oo), meijerg=True) + assert simplify(e.expand()) == ( + sqrt(2)*sqrt(pi)*( + (erf(sqrt(2)*(a + b)/2) + 1)*exp(-a**2/2 + a*b - b**2/2))/4) + e = integrate(exp(-(x - a)**2)*exp(-(x - b)**2)*exp(c*x), (x, 0, oo), meijerg=True) + assert simplify(e) == ( + sqrt(2)*sqrt(pi)*(erf(sqrt(2)*(2*a + 2*b + c)/4) + 1)*exp(-a**2 - b**2 + + (2*a + 2*b + c)**2/8)/4) + assert simplify(integrate(exp(-(x - a - b - c)**2), (x, 0, oo), meijerg=True)) == \ + sqrt(pi)/2*(1 + erf(a + b + c)) + assert simplify(integrate(exp(-(x + a + b + c)**2), (x, 0, oo), meijerg=True)) == \ + sqrt(pi)/2*(1 - erf(a + b + c)) + + +@slow +def test_meijerint(): + from sympy.core.function import expand + from sympy.core.symbol import symbols + s, t, mu = symbols('s t mu', real=True) + assert integrate(meijerg([], [], [0], [], s*t) + *meijerg([], [], [mu/2], [-mu/2], t**2/4), + (t, 0, oo)).is_Piecewise + s = symbols('s', positive=True) + assert integrate(x**s*meijerg([[], []], [[0], []], x), (x, 0, oo)) == \ + gamma(s + 1) + assert integrate(x**s*meijerg([[], []], [[0], []], x), (x, 0, oo), + meijerg=True) == gamma(s + 1) + assert isinstance(integrate(x**s*meijerg([[], []], [[0], []], x), + (x, 0, oo), meijerg=False), + Integral) + + assert meijerint_indefinite(exp(x), x) == exp(x) + + # TODO what simplifications should be done automatically? + # This tests "extra case" for antecedents_1. + a, b = symbols('a b', positive=True) + assert simplify(meijerint_definite(x**a, x, 0, b)[0]) == \ + b**(a + 1)/(a + 1) + + # This tests various conditions and expansions: + assert meijerint_definite((x + 1)**3*exp(-x), x, 0, oo) == (16, True) + + # Again, how about simplifications? + sigma, mu = symbols('sigma mu', positive=True) + i, c = meijerint_definite(exp(-((x - mu)/(2*sigma))**2), x, 0, oo) + assert simplify(i) == sqrt(pi)*sigma*(2 - erfc(mu/(2*sigma))) + assert c == True + + i, _ = meijerint_definite(exp(-mu*x)*exp(sigma*x), x, 0, oo) + # TODO it would be nice to test the condition + assert simplify(i) == 1/(mu - sigma) + + # Test substitutions to change limits + assert meijerint_definite(exp(x), x, -oo, 2) == (exp(2), True) + # Note: causes a NaN in _check_antecedents + assert expand(meijerint_definite(exp(x), x, 0, I)[0]) == exp(I) - 1 + assert expand(meijerint_definite(exp(-x), x, 0, x)[0]) == \ + 1 - exp(-exp(I*arg(x))*abs(x)) + + # Test -oo to oo + assert meijerint_definite(exp(-x**2), x, -oo, oo) == (sqrt(pi), True) + assert meijerint_definite(exp(-abs(x)), x, -oo, oo) == (2, True) + assert meijerint_definite(exp(-(2*x - 3)**2), x, -oo, oo) == \ + (sqrt(pi)/2, True) + assert meijerint_definite(exp(-abs(2*x - 3)), x, -oo, oo) == (1, True) + assert meijerint_definite(exp(-((x - mu)/sigma)**2/2)/sqrt(2*pi*sigma**2), + x, -oo, oo) == (1, True) + assert meijerint_definite(sinc(x)**2, x, -oo, oo) == (pi, True) + + # Test one of the extra conditions for 2 g-functinos + assert meijerint_definite(exp(-x)*sin(x), x, 0, oo) == (S.Half, True) + + # Test a bug + def res(n): + return (1/(1 + x**2)).diff(x, n).subs(x, 1)*(-1)**n + for n in range(6): + assert integrate(exp(-x)*sin(x)*x**n, (x, 0, oo), meijerg=True) == \ + res(n) + + # This used to test trigexpand... now it is done by linear substitution + assert simplify(integrate(exp(-x)*sin(x + a), (x, 0, oo), meijerg=True) + ) == sqrt(2)*sin(a + pi/4)/2 + + # Test the condition 14 from prudnikov. + # (This is besselj*besselj in disguise, to stop the product from being + # recognised in the tables.) + a, b, s = symbols('a b s') + assert meijerint_definite(meijerg([], [], [a/2], [-a/2], x/4) + *meijerg([], [], [b/2], [-b/2], x/4)*x**(s - 1), x, 0, oo + ) == ( + (4*2**(2*s - 2)*gamma(-2*s + 1)*gamma(a/2 + b/2 + s) + /(gamma(-a/2 + b/2 - s + 1)*gamma(a/2 - b/2 - s + 1) + *gamma(a/2 + b/2 - s + 1)), + (re(s) < 1) & (re(s) < S(1)/2) & (re(a)/2 + re(b)/2 + re(s) > 0))) + + # test a bug + assert integrate(sin(x**a)*sin(x**b), (x, 0, oo), meijerg=True) == \ + Integral(sin(x**a)*sin(x**b), (x, 0, oo)) + + # test better hyperexpand + assert integrate(exp(-x**2)*log(x), (x, 0, oo), meijerg=True) == \ + (sqrt(pi)*polygamma(0, S.Half)/4).expand() + + # Test hyperexpand bug. + from sympy.functions.special.gamma_functions import lowergamma + n = symbols('n', integer=True) + assert simplify(integrate(exp(-x)*x**n, x, meijerg=True)) == \ + lowergamma(n + 1, x) + + # Test a bug with argument 1/x + alpha = symbols('alpha', positive=True) + assert meijerint_definite((2 - x)**alpha*sin(alpha/x), x, 0, 2) == \ + (sqrt(pi)*alpha*gamma(alpha + 1)*meijerg(((), (alpha/2 + S.Half, + alpha/2 + 1)), ((0, 0, S.Half), (Rational(-1, 2),)), alpha**2/16)/4, True) + + # test a bug related to 3016 + a, s = symbols('a s', positive=True) + assert simplify(integrate(x**s*exp(-a*x**2), (x, -oo, oo))) == \ + a**(-s/2 - S.Half)*((-1)**s + 1)*gamma(s/2 + S.Half)/2 + + +def test_bessel(): + from sympy.functions.special.bessel import (besseli, besselj) + assert simplify(integrate(besselj(a, z)*besselj(b, z)/z, (z, 0, oo), + meijerg=True, conds='none')) == \ + 2*sin(pi*(a/2 - b/2))/(pi*(a - b)*(a + b)) + assert simplify(integrate(besselj(a, z)*besselj(a, z)/z, (z, 0, oo), + meijerg=True, conds='none')) == 1/(2*a) + + # TODO more orthogonality integrals + + assert simplify(integrate(sin(z*x)*(x**2 - 1)**(-(y + S.Half)), + (x, 1, oo), meijerg=True, conds='none') + *2/((z/2)**y*sqrt(pi)*gamma(S.Half - y))) == \ + besselj(y, z) + + # Werner Rosenheinrich + # SOME INDEFINITE INTEGRALS OF BESSEL FUNCTIONS + + assert integrate(x*besselj(0, x), x, meijerg=True) == x*besselj(1, x) + assert integrate(x*besseli(0, x), x, meijerg=True) == x*besseli(1, x) + # TODO can do higher powers, but come out as high order ... should they be + # reduced to order 0, 1? + assert integrate(besselj(1, x), x, meijerg=True) == -besselj(0, x) + assert integrate(besselj(1, x)**2/x, x, meijerg=True) == \ + -(besselj(0, x)**2 + besselj(1, x)**2)/2 + # TODO more besseli when tables are extended or recursive mellin works + assert integrate(besselj(0, x)**2/x**2, x, meijerg=True) == \ + -2*x*besselj(0, x)**2 - 2*x*besselj(1, x)**2 \ + + 2*besselj(0, x)*besselj(1, x) - besselj(0, x)**2/x + assert integrate(besselj(0, x)*besselj(1, x), x, meijerg=True) == \ + -besselj(0, x)**2/2 + assert integrate(x**2*besselj(0, x)*besselj(1, x), x, meijerg=True) == \ + x**2*besselj(1, x)**2/2 + assert integrate(besselj(0, x)*besselj(1, x)/x, x, meijerg=True) == \ + (x*besselj(0, x)**2 + x*besselj(1, x)**2 - + besselj(0, x)*besselj(1, x)) + # TODO how does besselj(0, a*x)*besselj(0, b*x) work? + # TODO how does besselj(0, x)**2*besselj(1, x)**2 work? + # TODO sin(x)*besselj(0, x) etc come out a mess + # TODO can x*log(x)*besselj(0, x) be done? + # TODO how does besselj(1, x)*besselj(0, x+a) work? + # TODO more indefinite integrals when struve functions etc are implemented + + # test a substitution + assert integrate(besselj(1, x**2)*x, x, meijerg=True) == \ + -besselj(0, x**2)/2 + + +def test_inversion(): + from sympy.functions.special.bessel import besselj + from sympy.functions.special.delta_functions import Heaviside + + def inv(f): + return piecewise_fold(meijerint_inversion(f, s, t)) + assert inv(1/(s**2 + 1)) == sin(t)*Heaviside(t) + assert inv(s/(s**2 + 1)) == cos(t)*Heaviside(t) + assert inv(exp(-s)/s) == Heaviside(t - 1) + assert inv(1/sqrt(1 + s**2)) == besselj(0, t)*Heaviside(t) + + # Test some antcedents checking. + assert meijerint_inversion(sqrt(s)/sqrt(1 + s**2), s, t) is None + assert inv(exp(s**2)) is None + assert meijerint_inversion(exp(-s**2), s, t) is None + + +def test_inversion_conditional_output(): + from sympy.core.symbol import Symbol + from sympy.integrals.transforms import InverseLaplaceTransform + + a = Symbol('a', positive=True) + F = sqrt(pi/a)*exp(-2*sqrt(a)*sqrt(s)) + f = meijerint_inversion(F, s, t) + assert not f.is_Piecewise + + b = Symbol('b', real=True) + F = F.subs(a, b) + f2 = meijerint_inversion(F, s, t) + assert f2.is_Piecewise + # first piece is same as f + assert f2.args[0][0] == f.subs(a, b) + # last piece is an unevaluated transform + assert f2.args[-1][1] + ILT = InverseLaplaceTransform(F, s, t, None) + assert f2.args[-1][0] == ILT or f2.args[-1][0] == ILT.as_integral + + +def test_inversion_exp_real_nonreal_shift(): + from sympy.core.symbol import Symbol + from sympy.functions.special.delta_functions import DiracDelta + r = Symbol('r', real=True) + c = Symbol('c', extended_real=False) + a = 1 + 2*I + z = Symbol('z') + assert not meijerint_inversion(exp(r*s), s, t).is_Piecewise + assert meijerint_inversion(exp(a*s), s, t) is None + assert meijerint_inversion(exp(c*s), s, t) is None + f = meijerint_inversion(exp(z*s), s, t) + assert f.is_Piecewise + assert isinstance(f.args[0][0], DiracDelta) + + +@slow +def test_lookup_table(): + from sympy.core.random import uniform, randrange + from sympy.core.add import Add + from sympy.integrals.meijerint import z as z_dummy + table = {} + _create_lookup_table(table) + for l in table.values(): + for formula, terms, cond, hint in sorted(l, key=default_sort_key): + subs = {} + for ai in list(formula.free_symbols) + [z_dummy]: + if hasattr(ai, 'properties') and ai.properties: + # these Wilds match positive integers + subs[ai] = randrange(1, 10) + else: + subs[ai] = uniform(1.5, 2.0) + if not isinstance(terms, list): + terms = terms(subs) + + # First test that hyperexpand can do this. + expanded = [hyperexpand(g) for (_, g) in terms] + assert all(x.is_Piecewise or not x.has(meijerg) for x in expanded) + + # Now test that the meijer g-function is indeed as advertised. + expanded = Add(*[f*x for (f, x) in terms]) + a, b = formula.n(subs=subs), expanded.n(subs=subs) + r = min(abs(a), abs(b)) + if r < 1: + assert abs(a - b).n() <= 1e-10 + else: + assert (abs(a - b)/r).n() <= 1e-10 + + +def test_branch_bug(): + from sympy.functions.special.gamma_functions import lowergamma + from sympy.simplify.powsimp import powdenest + # TODO gammasimp cannot prove that the factor is unity + assert powdenest(integrate(erf(x**3), x, meijerg=True).diff(x), + polar=True) == 2*erf(x**3)*gamma(Rational(2, 3))/3/gamma(Rational(5, 3)) + assert integrate(erf(x**3), x, meijerg=True) == \ + 2*x*erf(x**3)*gamma(Rational(2, 3))/(3*gamma(Rational(5, 3))) \ + - 2*gamma(Rational(2, 3))*lowergamma(Rational(2, 3), x**6)/(3*sqrt(pi)*gamma(Rational(5, 3))) + + +def test_linear_subs(): + from sympy.functions.special.bessel import besselj + assert integrate(sin(x - 1), x, meijerg=True) == -cos(1 - x) + assert integrate(besselj(1, x - 1), x, meijerg=True) == -besselj(0, 1 - x) + + +@slow +def test_probability(): + # various integrals from probability theory + from sympy.core.function import expand_mul + from sympy.core.symbol import (Symbol, symbols) + from sympy.simplify.gammasimp import gammasimp + from sympy.simplify.powsimp import powsimp + mu1, mu2 = symbols('mu1 mu2', nonzero=True) + sigma1, sigma2 = symbols('sigma1 sigma2', positive=True) + rate = Symbol('lambda', positive=True) + + def normal(x, mu, sigma): + return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2) + + def exponential(x, rate): + return rate*exp(-rate*x) + + assert integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) == 1 + assert integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) == \ + mu1 + assert integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) \ + == mu1**2 + sigma1**2 + assert integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) \ + == mu1**3 + 3*mu1*sigma1**2 + assert integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == 1 + assert integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == mu1 + assert integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == mu2 + assert integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == mu1*mu2 + assert integrate((x + y + 1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == 1 + mu1 + mu2 + assert integrate((x + y - 1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == \ + -1 + mu1 + mu2 + + i = integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) + assert not i.has(Abs) + assert simplify(i) == mu1**2 + sigma1**2 + assert integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2), + (x, -oo, oo), (y, -oo, oo), meijerg=True) == \ + sigma2**2 + mu2**2 + + assert integrate(exponential(x, rate), (x, 0, oo), meijerg=True) == 1 + assert integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True) == \ + 1/rate + assert integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True) == \ + 2/rate**2 + + def E(expr): + res1 = integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1), + (x, 0, oo), (y, -oo, oo), meijerg=True) + res2 = integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1), + (y, -oo, oo), (x, 0, oo), meijerg=True) + assert expand_mul(res1) == expand_mul(res2) + return res1 + + assert E(1) == 1 + assert E(x*y) == mu1/rate + assert E(x*y**2) == mu1**2/rate + sigma1**2/rate + ans = sigma1**2 + 1/rate**2 + assert simplify(E((x + y + 1)**2) - E(x + y + 1)**2) == ans + assert simplify(E((x + y - 1)**2) - E(x + y - 1)**2) == ans + assert simplify(E((x + y)**2) - E(x + y)**2) == ans + + # Beta' distribution + alpha, beta = symbols('alpha beta', positive=True) + betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \ + /gamma(alpha)/gamma(beta) + assert integrate(betadist, (x, 0, oo), meijerg=True) == 1 + i = integrate(x*betadist, (x, 0, oo), meijerg=True, conds='separate') + assert (gammasimp(i[0]), i[1]) == (alpha/(beta - 1), 1 < beta) + j = integrate(x**2*betadist, (x, 0, oo), meijerg=True, conds='separate') + assert j[1] == (beta > 2) + assert gammasimp(j[0] - i[0]**2) == (alpha + beta - 1)*alpha \ + /(beta - 2)/(beta - 1)**2 + + # Beta distribution + # NOTE: this is evaluated using antiderivatives. It also tests that + # meijerint_indefinite returns the simplest possible answer. + a, b = symbols('a b', positive=True) + betadist = x**(a - 1)*(-x + 1)**(b - 1)*gamma(a + b)/(gamma(a)*gamma(b)) + assert simplify(integrate(betadist, (x, 0, 1), meijerg=True)) == 1 + assert simplify(integrate(x*betadist, (x, 0, 1), meijerg=True)) == \ + a/(a + b) + assert simplify(integrate(x**2*betadist, (x, 0, 1), meijerg=True)) == \ + a*(a + 1)/(a + b)/(a + b + 1) + assert simplify(integrate(x**y*betadist, (x, 0, 1), meijerg=True)) == \ + gamma(a + b)*gamma(a + y)/gamma(a)/gamma(a + b + y) + + # Chi distribution + k = Symbol('k', integer=True, positive=True) + chi = 2**(1 - k/2)*x**(k - 1)*exp(-x**2/2)/gamma(k/2) + assert powsimp(integrate(chi, (x, 0, oo), meijerg=True)) == 1 + assert simplify(integrate(x*chi, (x, 0, oo), meijerg=True)) == \ + sqrt(2)*gamma((k + 1)/2)/gamma(k/2) + assert simplify(integrate(x**2*chi, (x, 0, oo), meijerg=True)) == k + + # Chi^2 distribution + chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2) + assert powsimp(integrate(chisquared, (x, 0, oo), meijerg=True)) == 1 + assert simplify(integrate(x*chisquared, (x, 0, oo), meijerg=True)) == k + assert simplify(integrate(x**2*chisquared, (x, 0, oo), meijerg=True)) == \ + k*(k + 2) + assert gammasimp(integrate(((x - k)/sqrt(2*k))**3*chisquared, (x, 0, oo), + meijerg=True)) == 2*sqrt(2)/sqrt(k) + + # Dagum distribution + a, b, p = symbols('a b p', positive=True) + # XXX (x/b)**a does not work + dagum = a*p/x*(x/b)**(a*p)/(1 + x**a/b**a)**(p + 1) + assert simplify(integrate(dagum, (x, 0, oo), meijerg=True)) == 1 + # XXX conditions are a mess + arg = x*dagum + assert simplify(integrate(arg, (x, 0, oo), meijerg=True, conds='none') + ) == a*b*gamma(1 - 1/a)*gamma(p + 1 + 1/a)/( + (a*p + 1)*gamma(p)) + assert simplify(integrate(x*arg, (x, 0, oo), meijerg=True, conds='none') + ) == a*b**2*gamma(1 - 2/a)*gamma(p + 1 + 2/a)/( + (a*p + 2)*gamma(p)) + + # F-distribution + d1, d2 = symbols('d1 d2', positive=True) + f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \ + /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2) + assert simplify(integrate(f, (x, 0, oo), meijerg=True)) == 1 + # TODO conditions are a mess + assert simplify(integrate(x*f, (x, 0, oo), meijerg=True, conds='none') + ) == d2/(d2 - 2) + assert simplify(integrate(x**2*f, (x, 0, oo), meijerg=True, conds='none') + ) == d2**2*(d1 + 2)/d1/(d2 - 4)/(d2 - 2) + + # TODO gamma, rayleigh + + # inverse gaussian + lamda, mu = symbols('lamda mu', positive=True) + dist = sqrt(lamda/2/pi)*x**(Rational(-3, 2))*exp(-lamda*(x - mu)**2/x/2/mu**2) + mysimp = lambda expr: simplify(expr.rewrite(exp)) + assert mysimp(integrate(dist, (x, 0, oo))) == 1 + assert mysimp(integrate(x*dist, (x, 0, oo))) == mu + assert mysimp(integrate((x - mu)**2*dist, (x, 0, oo))) == mu**3/lamda + assert mysimp(integrate((x - mu)**3*dist, (x, 0, oo))) == 3*mu**5/lamda**2 + + # Levi + c = Symbol('c', positive=True) + assert integrate(sqrt(c/2/pi)*exp(-c/2/(x - mu))/(x - mu)**S('3/2'), + (x, mu, oo)) == 1 + # higher moments oo + + # log-logistic + alpha, beta = symbols('alpha beta', positive=True) + distn = (beta/alpha)*x**(beta - 1)/alpha**(beta - 1)/ \ + (1 + x**beta/alpha**beta)**2 + # FIXME: If alpha, beta are not declared as finite the line below hangs + # after the changes in: + # https://github.com/sympy/sympy/pull/16603 + assert simplify(integrate(distn, (x, 0, oo))) == 1 + # NOTE the conditions are a mess, but correctly state beta > 1 + assert simplify(integrate(x*distn, (x, 0, oo), conds='none')) == \ + pi*alpha/beta/sin(pi/beta) + # (similar comment for conditions applies) + assert simplify(integrate(x**y*distn, (x, 0, oo), conds='none')) == \ + pi*alpha**y*y/beta/sin(pi*y/beta) + + # weibull + k = Symbol('k', positive=True) + n = Symbol('n', positive=True) + distn = k/lamda*(x/lamda)**(k - 1)*exp(-(x/lamda)**k) + assert simplify(integrate(distn, (x, 0, oo))) == 1 + assert simplify(integrate(x**n*distn, (x, 0, oo))) == \ + lamda**n*gamma(1 + n/k) + + # rice distribution + from sympy.functions.special.bessel import besseli + nu, sigma = symbols('nu sigma', positive=True) + rice = x/sigma**2*exp(-(x**2 + nu**2)/2/sigma**2)*besseli(0, x*nu/sigma**2) + assert integrate(rice, (x, 0, oo), meijerg=True) == 1 + # can someone verify higher moments? + + # Laplace distribution + mu = Symbol('mu', real=True) + b = Symbol('b', positive=True) + laplace = exp(-abs(x - mu)/b)/2/b + assert integrate(laplace, (x, -oo, oo), meijerg=True) == 1 + assert integrate(x*laplace, (x, -oo, oo), meijerg=True) == mu + assert integrate(x**2*laplace, (x, -oo, oo), meijerg=True) == \ + 2*b**2 + mu**2 + + # TODO are there other distributions supported on (-oo, oo) that we can do? + + # misc tests + k = Symbol('k', positive=True) + assert gammasimp(expand_mul(integrate(log(x)*x**(k - 1)*exp(-x)/gamma(k), + (x, 0, oo)))) == polygamma(0, k) + + +@slow +def test_expint(): + """ Test various exponential integrals. """ + from sympy.core.symbol import Symbol + from sympy.functions.elementary.hyperbolic import sinh + from sympy.functions.special.error_functions import (Chi, Ci, Ei, Shi, Si, expint) + assert simplify(unpolarify(integrate(exp(-z*x)/x**y, (x, 1, oo), + meijerg=True, conds='none' + ).rewrite(expint).expand(func=True))) == expint(y, z) + + assert integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, + conds='none').rewrite(expint).expand() == \ + expint(1, z) + assert integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, + conds='none').rewrite(expint).expand() == \ + expint(2, z).rewrite(Ei).rewrite(expint) + assert integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True, + conds='none').rewrite(expint).expand() == \ + expint(3, z).rewrite(Ei).rewrite(expint).expand() + + t = Symbol('t', positive=True) + assert integrate(-cos(x)/x, (x, t, oo), meijerg=True).expand() == Ci(t) + assert integrate(-sin(x)/x, (x, t, oo), meijerg=True).expand() == \ + Si(t) - pi/2 + assert integrate(sin(x)/x, (x, 0, z), meijerg=True) == Si(z) + assert integrate(sinh(x)/x, (x, 0, z), meijerg=True) == Shi(z) + assert integrate(exp(-x)/x, x, meijerg=True).expand().rewrite(expint) == \ + I*pi - expint(1, x) + assert integrate(exp(-x)/x**2, x, meijerg=True).rewrite(expint).expand() \ + == expint(1, x) - exp(-x)/x - I*pi + + u = Symbol('u', polar=True) + assert integrate(cos(u)/u, u, meijerg=True).expand().as_independent(u)[1] \ + == Ci(u) + assert integrate(cosh(u)/u, u, meijerg=True).expand().as_independent(u)[1] \ + == Chi(u) + + assert integrate(expint(1, x), x, meijerg=True + ).rewrite(expint).expand() == x*expint(1, x) - exp(-x) + assert integrate(expint(2, x), x, meijerg=True + ).rewrite(expint).expand() == \ + -x**2*expint(1, x)/2 + x*exp(-x)/2 - exp(-x)/2 + assert simplify(unpolarify(integrate(expint(y, x), x, + meijerg=True).rewrite(expint).expand(func=True))) == \ + -expint(y + 1, x) + + assert integrate(Si(x), x, meijerg=True) == x*Si(x) + cos(x) + assert integrate(Ci(u), u, meijerg=True).expand() == u*Ci(u) - sin(u) + assert integrate(Shi(x), x, meijerg=True) == x*Shi(x) - cosh(x) + assert integrate(Chi(u), u, meijerg=True).expand() == u*Chi(u) - sinh(u) + + assert integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True) == pi/4 + assert integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True) == log(2)/2 + + +def test_messy(): + from sympy.functions.elementary.hyperbolic import (acosh, acoth) + from sympy.functions.elementary.trigonometric import (asin, atan) + from sympy.functions.special.bessel import besselj + from sympy.functions.special.error_functions import (Chi, E1, Shi, Si) + from sympy.integrals.transforms import (fourier_transform, laplace_transform) + assert (laplace_transform(Si(x), x, s, simplify=True) == + ((-atan(s) + pi/2)/s, 0, True)) + + assert laplace_transform(Shi(x), x, s, simplify=True) == ( + acoth(s)/s, -oo, s**2 > 1) + + # where should the logs be simplified? + assert laplace_transform(Chi(x), x, s, simplify=True) == ( + (log(s**(-2)) - log(1 - 1/s**2))/(2*s), -oo, s**2 > 1) + + # TODO maybe simplify the inequalities? when the simplification + # allows for generators instead of symbols this will work + assert laplace_transform(besselj(a, x), x, s)[1:] == \ + (0, (re(a) > -2) & (re(a) > -1)) + + # NOTE s < 0 can be done, but argument reduction is not good enough yet + ans = fourier_transform(besselj(1, x)/x, x, s, noconds=False) + assert (ans[0].factor(deep=True).expand(), ans[1]) == \ + (Piecewise((0, (s > 1/(2*pi)) | (s < -1/(2*pi))), + (2*sqrt(-4*pi**2*s**2 + 1), True)), s > 0) + # TODO FT(besselj(0,x)) - conditions are messy (but for acceptable reasons) + # - folding could be better + + assert integrate(E1(x)*besselj(0, x), (x, 0, oo), meijerg=True) == \ + log(1 + sqrt(2)) + assert integrate(E1(x)*besselj(1, x), (x, 0, oo), meijerg=True) == \ + log(S.Half + sqrt(2)/2) + + assert integrate(1/x/sqrt(1 - x**2), x, meijerg=True) == \ + Piecewise((-acosh(1/x), abs(x**(-2)) > 1), (I*asin(1/x), True)) + + +def test_issue_6122(): + assert integrate(exp(-I*x**2), (x, -oo, oo), meijerg=True) == \ + -I*sqrt(pi)*exp(I*pi/4) + + +def test_issue_6252(): + expr = 1/x/(a + b*x)**Rational(1, 3) + anti = integrate(expr, x, meijerg=True) + assert not anti.has(hyper) + # XXX the expression is a mess, but actually upon differentiation and + # putting in numerical values seems to work... + + +def test_issue_6348(): + assert integrate(exp(I*x)/(1 + x**2), (x, -oo, oo)).simplify().rewrite(exp) \ + == pi*exp(-1) + + +def test_fresnel(): + from sympy.functions.special.error_functions import (fresnelc, fresnels) + + assert expand_func(integrate(sin(pi*x**2/2), x)) == fresnels(x) + assert expand_func(integrate(cos(pi*x**2/2), x)) == fresnelc(x) + + +def test_issue_6860(): + assert meijerint_indefinite(x**x**x, x) is None + + +def test_issue_7337(): + f = meijerint_indefinite(x*sqrt(2*x + 3), x).together() + assert f == sqrt(2*x + 3)*(2*x**2 + x - 3)/5 + assert f._eval_interval(x, S.NegativeOne, S.One) == Rational(2, 5) + + +def test_issue_8368(): + assert meijerint_indefinite(cosh(x)*exp(-x*t), x) == ( + (-t - 1)*exp(x) + (-t + 1)*exp(-x))*exp(-t*x)/2/(t**2 - 1) + + +def test_issue_10211(): + from sympy.abc import h, w + assert integrate((1/sqrt((y-x)**2 + h**2)**3), (x,0,w), (y,0,w)) == \ + 2*sqrt(1 + w**2/h**2)/h - 2/h + + +def test_issue_11806(): + from sympy.core.symbol import symbols + y, L = symbols('y L', positive=True) + assert integrate(1/sqrt(x**2 + y**2)**3, (x, -L, L)) == \ + 2*L/(y**2*sqrt(L**2 + y**2)) + +def test_issue_10681(): + from sympy.polys.domains.realfield import RR + from sympy.abc import R, r + f = integrate(r**2*(R**2-r**2)**0.5, r, meijerg=True) + g = (1.0/3)*R**1.0*r**3*hyper((-0.5, Rational(3, 2)), (Rational(5, 2),), + r**2*exp_polar(2*I*pi)/R**2) + assert RR.almosteq((f/g).n(), 1.0, 1e-12) + +def test_issue_13536(): + from sympy.core.symbol import Symbol + a = Symbol('a', positive=True) + assert integrate(1/x**2, (x, oo, a)) == -1/a + + +def test_issue_6462(): + from sympy.core.symbol import Symbol + x = Symbol('x') + n = Symbol('n') + # Not the actual issue, still wrong answer for n = 1, but that there is no + # exception + assert integrate(cos(x**n)/x**n, x, meijerg=True).subs(n, 2).equals( + integrate(cos(x**2)/x**2, x, meijerg=True)) + + +def test_indefinite_1_bug(): + assert integrate((b + t)**(-a), t, meijerg=True) == -b*(1 + t/b)**(1 - a)/(a*b**a - b**a) + + +def test_pr_23583(): + # This result is wrong. Check whether new result is correct when this test fail. + assert integrate(1/sqrt((x - I)**2-1), meijerg=True) == \ + Piecewise((acosh(x - I), Abs((x - I)**2) > 1), (-I*asin(x - I), True)) + + +# 25786 +def test_integrate_function_of_square_over_negatives(): + assert integrate(exp(-x**2), (x,-5,0), meijerg=True) == sqrt(pi)/2 * erf(5) + + +def test_issue_25949(): + from sympy.core.symbol import symbols + y = symbols("y", nonzero=True) + assert integrate(cosh(y*(x + 1)), (x, -1, -0.25), meijerg=True) == sinh(0.75*y)/y diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_prde.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_prde.py new file mode 100644 index 0000000000000000000000000000000000000000..a7429ea8634c742eb77cdb26f99b2cb15853cd42 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_prde.py @@ -0,0 +1,322 @@ +"""Most of these tests come from the examples in Bronstein's book.""" +from sympy.integrals.risch import DifferentialExtension, derivation +from sympy.integrals.prde import (prde_normal_denom, prde_special_denom, + prde_linear_constraints, constant_system, prde_spde, prde_no_cancel_b_large, + prde_no_cancel_b_small, limited_integrate_reduce, limited_integrate, + is_deriv_k, is_log_deriv_k_t_radical, parametric_log_deriv_heu, + is_log_deriv_k_t_radical_in_field, param_poly_rischDE, param_rischDE, + prde_cancel_liouvillian) + +from sympy.polys.polymatrix import PolyMatrix as Matrix + +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.polytools import Poly +from sympy.abc import x, t, n + +t0, t1, t2, t3, k = symbols('t:4 k') + + +def test_prde_normal_denom(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + fa = Poly(1, t) + fd = Poly(x, t) + G = [(Poly(t, t), Poly(1 + t**2, t)), (Poly(1, t), Poly(x + x*t**2, t))] + assert prde_normal_denom(fa, fd, G, DE) == \ + (Poly(x, t, domain='ZZ(x)'), (Poly(1, t, domain='ZZ(x)'), Poly(1, t, + domain='ZZ(x)')), [(Poly(x*t, t, domain='ZZ(x)'), + Poly(t**2 + 1, t, domain='ZZ(x)')), (Poly(1, t, domain='ZZ(x)'), + Poly(t**2 + 1, t, domain='ZZ(x)'))], Poly(1, t, domain='ZZ(x)')) + G = [(Poly(t, t), Poly(t**2 + 2*t + 1, t)), (Poly(x*t, t), + Poly(t**2 + 2*t + 1, t)), (Poly(x*t**2, t), Poly(t**2 + 2*t + 1, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert prde_normal_denom(Poly(x, t), Poly(1, t), G, DE) == \ + (Poly(t + 1, t), (Poly((-1 + x)*t + x, t), Poly(1, t, domain='ZZ[x]')), [(Poly(t, t), + Poly(1, t)), (Poly(x*t, t), Poly(1, t, domain='ZZ[x]')), (Poly(x*t**2, t), + Poly(1, t, domain='ZZ[x]'))], Poly(t + 1, t)) + + +def test_prde_special_denom(): + a = Poly(t + 1, t) + ba = Poly(t**2, t) + bd = Poly(1, t) + G = [(Poly(t, t), Poly(1, t)), (Poly(t**2, t), Poly(1, t)), (Poly(t**3, t), Poly(1, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert prde_special_denom(a, ba, bd, G, DE) == \ + (Poly(t + 1, t), Poly(t**2, t), [(Poly(t, t), Poly(1, t)), + (Poly(t**2, t), Poly(1, t)), (Poly(t**3, t), Poly(1, t))], Poly(1, t)) + G = [(Poly(t, t), Poly(1, t)), (Poly(1, t), Poly(t, t))] + assert prde_special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), G, DE) == \ + (Poly(1, t), Poly(t**2 - 1, t), [(Poly(t**2, t), Poly(1, t)), + (Poly(1, t), Poly(1, t))], Poly(t, t)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-2*x*t0, t0)]}) + DE.decrement_level() + G = [(Poly(t, t), Poly(t**2, t)), (Poly(2*t, t), Poly(t, t))] + assert prde_special_denom(Poly(5*x*t + 1, t), Poly(t**2 + 2*x**3*t, t), Poly(t**3 + 2, t), G, DE) == \ + (Poly(5*x*t + 1, t), Poly(0, t, domain='ZZ[x]'), [(Poly(t, t), Poly(t**2, t)), + (Poly(2*t, t), Poly(t, t))], Poly(1, x)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly((t**2 + 1)*2*x, t)]}) + G = [(Poly(t + x, t), Poly(t*x, t)), (Poly(2*t, t), Poly(x**2, x))] + assert prde_special_denom(Poly(5*x*t + 1, t), Poly(t**2 + 2*x**3*t, t), Poly(t**3, t), G, DE) == \ + (Poly(5*x*t + 1, t), Poly(0, t, domain='ZZ[x]'), [(Poly(t + x, t), Poly(x*t, t)), + (Poly(2*t, t, x), Poly(x**2, t, x))], Poly(1, t)) + assert prde_special_denom(Poly(t + 1, t), Poly(t**2, t), Poly(t**3, t), G, DE) == \ + (Poly(t + 1, t), Poly(0, t, domain='ZZ[x]'), [(Poly(t + x, t), Poly(x*t, t)), (Poly(2*t, t, x), + Poly(x**2, t, x))], Poly(1, t)) + + +def test_prde_linear_constraints(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + G = [(Poly(2*x**3 + 3*x + 1, x), Poly(x**2 - 1, x)), (Poly(1, x), Poly(x - 1, x)), + (Poly(1, x), Poly(x + 1, x))] + assert prde_linear_constraints(Poly(1, x), Poly(0, x), G, DE) == \ + ((Poly(2*x, x, domain='QQ'), Poly(0, x, domain='QQ'), Poly(0, x, domain='QQ')), + Matrix([[1, 1, -1], [5, 1, 1]], x)) + G = [(Poly(t, t), Poly(1, t)), (Poly(t**2, t), Poly(1, t)), (Poly(t**3, t), Poly(1, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert prde_linear_constraints(Poly(t + 1, t), Poly(t**2, t), G, DE) == \ + ((Poly(t, t, domain='QQ'), Poly(t**2, t, domain='QQ'), Poly(t**3, t, domain='QQ')), + Matrix(0, 3, [], t)) + G = [(Poly(2*x, t), Poly(t, t)), (Poly(-x, t), Poly(t, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert prde_linear_constraints(Poly(1, t), Poly(0, t), G, DE) == \ + ((Poly(0, t, domain='QQ[x]'), Poly(0, t, domain='QQ[x]')), Matrix([[2*x, -x]], t)) + + +def test_constant_system(): + A = Matrix([[-(x + 3)/(x - 1), (x + 1)/(x - 1), 1], + [-x - 3, x + 1, x - 1], + [2*(x + 3)/(x - 1), 0, 0]], t) + u = Matrix([[(x + 1)/(x - 1)], [x + 1], [0]], t) + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + R = QQ.frac_field(x)[t] + assert constant_system(A, u, DE) == \ + (Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 0], + [0, 0, 1]], ring=R), Matrix([0, 1, 0, 0], ring=R)) + + +def test_prde_spde(): + D = [Poly(x, t), Poly(-x*t, t)] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + # TODO: when bound_degree() can handle this, test degree bound from that too + assert prde_spde(Poly(t, t), Poly(-1/x, t), D, n, DE) == \ + (Poly(t, t), Poly(0, t, domain='ZZ(x)'), + [Poly(2*x, t, domain='ZZ(x)'), Poly(-x, t, domain='ZZ(x)')], + [Poly(-x**2, t, domain='ZZ(x)'), Poly(0, t, domain='ZZ(x)')], n - 1) + + +def test_prde_no_cancel(): + # b large + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert prde_no_cancel_b_large(Poly(1, x), [Poly(x**2, x), Poly(1, x)], 2, DE) == \ + ([Poly(x**2 - 2*x + 2, x), Poly(1, x)], Matrix([[1, 0, -1, 0], + [0, 1, 0, -1]], x)) + assert prde_no_cancel_b_large(Poly(1, x), [Poly(x**3, x), Poly(1, x)], 3, DE) == \ + ([Poly(x**3 - 3*x**2 + 6*x - 6, x), Poly(1, x)], Matrix([[1, 0, -1, 0], + [0, 1, 0, -1]], x)) + assert prde_no_cancel_b_large(Poly(x, x), [Poly(x**2, x), Poly(1, x)], 1, DE) == \ + ([Poly(x, x, domain='ZZ'), Poly(0, x, domain='ZZ')], Matrix([[1, -1, 0, 0], + [1, 0, -1, 0], + [0, 1, 0, -1]], x)) + # b small + # XXX: Is there a better example of a monomial with D.degree() > 2? + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**3 + 1, t)]}) + + # My original q was t**4 + t + 1, but this solution implies q == t**4 + # (c1 = 4), with some of the ci for the original q equal to 0. + G = [Poly(t**6, t), Poly(x*t**5, t), Poly(t**3, t), Poly(x*t**2, t), Poly(1 + x, t)] + R = QQ.frac_field(x)[t] + assert prde_no_cancel_b_small(Poly(x*t, t), G, 4, DE) == \ + ([Poly(t**4/4 - x/12*t**3 + x**2/24*t**2 + (Rational(-11, 12) - x**3/24)*t + x/24, t), + Poly(x/3*t**3 - x**2/6*t**2 + (Rational(-1, 3) + x**3/6)*t - x/6, t), Poly(t, t), + Poly(0, t), Poly(0, t)], Matrix([[1, 0, -1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, Rational(-1, 4), 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, -1, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, -1, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, -1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, -1, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, -1]], ring=R)) + + # TODO: Add test for deg(b) <= 0 with b small + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + b = Poly(-1/x**2, t, field=True) # deg(b) == 0 + q = [Poly(x**i*t**j, t, field=True) for i in range(2) for j in range(3)] + h, A = prde_no_cancel_b_small(b, q, 3, DE) + V = A.nullspace() + R = QQ.frac_field(x)[t] + assert len(V) == 1 + assert V[0] == Matrix([Rational(-1, 2), 0, 0, 1, 0, 0]*3, ring=R) + assert (Matrix([h])*V[0][6:, :])[0] == Poly(x**2/2, t, domain='QQ(x)') + assert (Matrix([q])*V[0][:6, :])[0] == Poly(x - S.Half, t, domain='QQ(x)') + + +def test_prde_cancel_liouvillian(): + ### 1. case == 'primitive' + # used when integrating f = log(x) - log(x - 1) + # Not taken from 'the' book + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + p0 = Poly(0, t, field=True) + p1 = Poly((x - 1)*t, t, domain='ZZ(x)') + p2 = Poly(x - 1, t, domain='ZZ(x)') + p3 = Poly(-x**2 + x, t, domain='ZZ(x)') + h, A = prde_cancel_liouvillian(Poly(-1/(x - 1), t), [Poly(-x + 1, t), Poly(1, t)], 1, DE) + V = A.nullspace() + assert h == [p0, p0, p1, p0, p0, p0, p0, p0, p0, p0, p2, p3, p0, p0, p0, p0] + assert A.rank() == 16 + assert (Matrix([h])*V[0][:16, :]) == Matrix([[Poly(0, t, domain='QQ(x)')]]) + + ### 2. case == 'exp' + # used when integrating log(x/exp(x) + 1) + # Not taken from book + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t, t)]}) + assert prde_cancel_liouvillian(Poly(0, t, domain='QQ[x]'), [Poly(1, t, domain='QQ(x)')], 0, DE) == \ + ([Poly(1, t, domain='QQ'), Poly(x, t, domain='ZZ(x)')], Matrix([[-1, 0, 1]], DE.t)) + + +def test_param_poly_rischDE(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + a = Poly(x**2 - x, x, field=True) + b = Poly(1, x, field=True) + q = [Poly(x, x, field=True), Poly(x**2, x, field=True)] + h, A = param_poly_rischDE(a, b, q, 3, DE) + + assert A.nullspace() == [Matrix([0, 1, 1, 1], DE.t)] # c1, c2, d1, d2 + # Solution of a*Dp + b*p = c1*q1 + c2*q2 = q2 = x**2 + # is d1*h1 + d2*h2 = h1 + h2 = x. + assert h[0] + h[1] == Poly(x, x, domain='QQ') + # a*Dp + b*p = q1 = x has no solution. + + a = Poly(x**2 - x, x, field=True) + b = Poly(x**2 - 5*x + 3, x, field=True) + q = [Poly(1, x, field=True), Poly(x, x, field=True), + Poly(x**2, x, field=True)] + h, A = param_poly_rischDE(a, b, q, 3, DE) + + assert A.nullspace() == [Matrix([3, -5, 1, -5, 1, 1], DE.t)] + p = -Poly(5, DE.t)*h[0] + h[1] + h[2] # Poly(1, x) + assert a*derivation(p, DE) + b*p == Poly(x**2 - 5*x + 3, x, domain='QQ') + + +def test_param_rischDE(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + p1, px = Poly(1, x, field=True), Poly(x, x, field=True) + G = [(p1, px), (p1, p1), (px, p1)] # [1/x, 1, x] + h, A = param_rischDE(-p1, Poly(x**2, x, field=True), G, DE) + assert len(h) == 3 + p = [hi[0].as_expr()/hi[1].as_expr() for hi in h] + V = A.nullspace() + assert len(V) == 2 + assert V[0] == Matrix([-1, 1, 0, -1, 1, 0], DE.t) + y = -p[0] + p[1] + 0*p[2] # x + assert y.diff(x) - y/x**2 == 1 - 1/x # Dy + f*y == -G0 + G1 + 0*G2 + + # the below test computation takes place while computing the integral + # of 'f = log(log(x + exp(x)))' + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + G = [(Poly(t + x, t, domain='ZZ(x)'), Poly(1, t, domain='QQ')), (Poly(0, t, domain='QQ'), Poly(1, t, domain='QQ'))] + h, A = param_rischDE(Poly(-t - 1, t, field=True), Poly(t + x, t, field=True), G, DE) + assert len(h) == 5 + p = [hi[0].as_expr()/hi[1].as_expr() for hi in h] + V = A.nullspace() + assert len(V) == 3 + assert V[0] == Matrix([0, 0, 0, 0, 1, 0, 0], DE.t) + y = 0*p[0] + 0*p[1] + 1*p[2] + 0*p[3] + 0*p[4] + assert y.diff(t) - y/(t + x) == 0 # Dy + f*y = 0*G0 + 0*G1 + + +def test_limited_integrate_reduce(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert limited_integrate_reduce(Poly(x, t), Poly(t**2, t), [(Poly(x, t), + Poly(t, t))], DE) == \ + (Poly(t, t), Poly(-1/x, t), Poly(t, t), 1, (Poly(x, t), Poly(1, t, domain='ZZ[x]')), + [(Poly(-x*t, t), Poly(1, t, domain='ZZ[x]'))]) + + +def test_limited_integrate(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + G = [(Poly(x, x), Poly(x + 1, x))] + assert limited_integrate(Poly(-(1 + x + 5*x**2 - 3*x**3), x), + Poly(1 - x - x**2 + x**3, x), G, DE) == \ + ((Poly(x**2 - x + 2, x), Poly(x - 1, x, domain='QQ')), [2]) + G = [(Poly(1, x), Poly(x, x))] + assert limited_integrate(Poly(5*x**2, x), Poly(3, x), G, DE) == \ + ((Poly(5*x**3/9, x), Poly(1, x, domain='QQ')), [0]) + + +def test_is_log_deriv_k_t_radical(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)], 'exts': [None], + 'extargs': [None]}) + assert is_log_deriv_k_t_radical(Poly(2*x, x), Poly(1, x), DE) is None + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2*t1, t1), Poly(1/x, t2)], + 'exts': [None, 'exp', 'log'], 'extargs': [None, 2*x, x]}) + assert is_log_deriv_k_t_radical(Poly(x + t2/2, t2), Poly(1, t2), DE) == \ + ([(t1, 1), (x, 1)], t1*x, 2, 0) + # TODO: Add more tests + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0, t0), Poly(1/x, t)], + 'exts': [None, 'exp', 'log'], 'extargs': [None, x, x]}) + assert is_log_deriv_k_t_radical(Poly(x + t/2 + 3, t), Poly(1, t), DE) == \ + ([(t0, 2), (x, 1)], x*t0**2, 2, 3) + + +def test_is_deriv_k(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(1/(x + 1), t2)], + 'exts': [None, 'log', 'log'], 'extargs': [None, x, x + 1]}) + assert is_deriv_k(Poly(2*x**2 + 2*x, t2), Poly(1, t2), DE) == \ + ([(t1, 1), (t2, 1)], t1 + t2, 2) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(t2, t2)], + 'exts': [None, 'log', 'exp'], 'extargs': [None, x, x]}) + assert is_deriv_k(Poly(x**2*t2**3, t2), Poly(1, t2), DE) == \ + ([(x, 3), (t1, 2)], 2*t1 + 3*x, 1) + # TODO: Add more tests, including ones with exponentials + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2/x, t1)], + 'exts': [None, 'log'], 'extargs': [None, x**2]}) + assert is_deriv_k(Poly(x, t1), Poly(1, t1), DE) == \ + ([(t1, S.Half)], t1/2, 1) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2/(1 + x), t0)], + 'exts': [None, 'log'], 'extargs': [None, x**2 + 2*x + 1]}) + assert is_deriv_k(Poly(1 + x, t0), Poly(1, t0), DE) == \ + ([(t0, S.Half)], t0/2, 1) + + # Issue 10798 + # DE = DifferentialExtension(log(1/x), x) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-1/x, t)], + 'exts': [None, 'log'], 'extargs': [None, 1/x]}) + assert is_deriv_k(Poly(1, t), Poly(x, t), DE) == ([(t, 1)], t, 1) + + +def test_is_log_deriv_k_t_radical_in_field(): + # NOTE: any potential constant factor in the second element of the result + # doesn't matter, because it cancels in Da/a. + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert is_log_deriv_k_t_radical_in_field(Poly(5*t + 1, t), Poly(2*t*x, t), DE) == \ + (2, t*x**5) + assert is_log_deriv_k_t_radical_in_field(Poly(2 + 3*t, t), Poly(5*x*t, t), DE) == \ + (5, x**3*t**2) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t/x**2, t)]}) + assert is_log_deriv_k_t_radical_in_field(Poly(-(1 + 2*t), t), + Poly(2*x**2 + 2*x**2*t, t), DE) == \ + (2, t + t**2) + assert is_log_deriv_k_t_radical_in_field(Poly(-1, t), Poly(x**2, t), DE) == \ + (1, t) + assert is_log_deriv_k_t_radical_in_field(Poly(1, t), Poly(2*x**2, t), DE) == \ + (2, 1/t) + + +def test_parametric_log_deriv(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert parametric_log_deriv_heu(Poly(5*t**2 + t - 6, t), Poly(2*x*t**2, t), + Poly(-1, t), Poly(x*t**2, t), DE) == \ + (2, 6, t*x**5) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_quadrature.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_quadrature.py new file mode 100644 index 0000000000000000000000000000000000000000..97471dbdbc13fda0bce7a8823ff2cefac4ab8802 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_quadrature.py @@ -0,0 +1,601 @@ +from sympy.core import S, Rational +from sympy.integrals.quadrature import (gauss_legendre, gauss_laguerre, + gauss_hermite, gauss_gen_laguerre, + gauss_chebyshev_t, gauss_chebyshev_u, + gauss_jacobi, gauss_lobatto) + + +def test_legendre(): + x, w = gauss_legendre(1, 17) + assert [str(r) for r in x] == ['0'] + assert [str(r) for r in w] == ['2.0000000000000000'] + + x, w = gauss_legendre(2, 17) + assert [str(r) for r in x] == [ + '-0.57735026918962576', + '0.57735026918962576'] + assert [str(r) for r in w] == [ + '1.0000000000000000', + '1.0000000000000000'] + + x, w = gauss_legendre(3, 17) + assert [str(r) for r in x] == [ + '-0.77459666924148338', + '0', + '0.77459666924148338'] + assert [str(r) for r in w] == [ + '0.55555555555555556', + '0.88888888888888889', + '0.55555555555555556'] + + x, w = gauss_legendre(4, 17) + assert [str(r) for r in x] == [ + '-0.86113631159405258', + '-0.33998104358485626', + '0.33998104358485626', + '0.86113631159405258'] + assert [str(r) for r in w] == [ + '0.34785484513745386', + '0.65214515486254614', + '0.65214515486254614', + '0.34785484513745386'] + + +def test_legendre_precise(): + x, w = gauss_legendre(3, 40) + assert [str(r) for r in x] == [ + '-0.7745966692414833770358530799564799221666', + '0', + '0.7745966692414833770358530799564799221666'] + assert [str(r) for r in w] == [ + '0.5555555555555555555555555555555555555556', + '0.8888888888888888888888888888888888888889', + '0.5555555555555555555555555555555555555556'] + + +def test_laguerre(): + x, w = gauss_laguerre(1, 17) + assert [str(r) for r in x] == ['1.0000000000000000'] + assert [str(r) for r in w] == ['1.0000000000000000'] + + x, w = gauss_laguerre(2, 17) + assert [str(r) for r in x] == [ + '0.58578643762690495', + '3.4142135623730950'] + assert [str(r) for r in w] == [ + '0.85355339059327376', + '0.14644660940672624'] + + x, w = gauss_laguerre(3, 17) + assert [str(r) for r in x] == [ + '0.41577455678347908', + '2.2942803602790417', + '6.2899450829374792', + ] + assert [str(r) for r in w] == [ + '0.71109300992917302', + '0.27851773356924085', + '0.010389256501586136', + ] + + x, w = gauss_laguerre(4, 17) + assert [str(r) for r in x] == [ + '0.32254768961939231', + '1.7457611011583466', + '4.5366202969211280', + '9.3950709123011331'] + assert [str(r) for r in w] == [ + '0.60315410434163360', + '0.35741869243779969', + '0.038887908515005384', + '0.00053929470556132745'] + + x, w = gauss_laguerre(5, 17) + assert [str(r) for r in x] == [ + '0.26356031971814091', + '1.4134030591065168', + '3.5964257710407221', + '7.0858100058588376', + '12.640800844275783'] + assert [str(r) for r in w] == [ + '0.52175561058280865', + '0.39866681108317593', + '0.075942449681707595', + '0.0036117586799220485', + '2.3369972385776228e-5'] + + +def test_laguerre_precise(): + x, w = gauss_laguerre(3, 40) + assert [str(r) for r in x] == [ + '0.4157745567834790833115338731282744735466', + '2.294280360279041719822050361359593868960', + '6.289945082937479196866415765512131657493'] + assert [str(r) for r in w] == [ + '0.7110930099291730154495901911425944313094', + '0.2785177335692408488014448884567264810349', + '0.01038925650158613574896492040067908765572'] + + +def test_hermite(): + x, w = gauss_hermite(1, 17) + assert [str(r) for r in x] == ['0'] + assert [str(r) for r in w] == ['1.7724538509055160'] + + x, w = gauss_hermite(2, 17) + assert [str(r) for r in x] == [ + '-0.70710678118654752', + '0.70710678118654752'] + assert [str(r) for r in w] == [ + '0.88622692545275801', + '0.88622692545275801'] + + x, w = gauss_hermite(3, 17) + assert [str(r) for r in x] == [ + '-1.2247448713915890', + '0', + '1.2247448713915890'] + assert [str(r) for r in w] == [ + '0.29540897515091934', + '1.1816359006036774', + '0.29540897515091934'] + + x, w = gauss_hermite(4, 17) + assert [str(r) for r in x] == [ + '-1.6506801238857846', + '-0.52464762327529032', + '0.52464762327529032', + '1.6506801238857846'] + assert [str(r) for r in w] == [ + '0.081312835447245177', + '0.80491409000551284', + '0.80491409000551284', + '0.081312835447245177'] + + x, w = gauss_hermite(5, 17) + assert [str(r) for r in x] == [ + '-2.0201828704560856', + '-0.95857246461381851', + '0', + '0.95857246461381851', + '2.0201828704560856'] + assert [str(r) for r in w] == [ + '0.019953242059045913', + '0.39361932315224116', + '0.94530872048294188', + '0.39361932315224116', + '0.019953242059045913'] + + +def test_hermite_precise(): + x, w = gauss_hermite(3, 40) + assert [str(r) for r in x] == [ + '-1.224744871391589049098642037352945695983', + '0', + '1.224744871391589049098642037352945695983'] + assert [str(r) for r in w] == [ + '0.2954089751509193378830279138901908637996', + '1.181635900603677351532111655560763455198', + '0.2954089751509193378830279138901908637996'] + + +def test_gen_laguerre(): + x, w = gauss_gen_laguerre(1, Rational(-1, 2), 17) + assert [str(r) for r in x] == ['0.50000000000000000'] + assert [str(r) for r in w] == ['1.7724538509055160'] + + x, w = gauss_gen_laguerre(2, Rational(-1, 2), 17) + assert [str(r) for r in x] == [ + '0.27525512860841095', + '2.7247448713915890'] + assert [str(r) for r in w] == [ + '1.6098281800110257', + '0.16262567089449035'] + + x, w = gauss_gen_laguerre(3, Rational(-1, 2), 17) + assert [str(r) for r in x] == [ + '0.19016350919348813', + '1.7844927485432516', + '5.5253437422632603'] + assert [str(r) for r in w] == [ + '1.4492591904487850', + '0.31413464064571329', + '0.0090600198110176913'] + + x, w = gauss_gen_laguerre(4, Rational(-1, 2), 17) + assert [str(r) for r in x] == [ + '0.14530352150331709', + '1.3390972881263614', + '3.9269635013582872', + '8.5886356890120343'] + assert [str(r) for r in w] == [ + '1.3222940251164826', + '0.41560465162978376', + '0.034155966014826951', + '0.00039920814442273524'] + + x, w = gauss_gen_laguerre(5, Rational(-1, 2), 17) + assert [str(r) for r in x] == [ + '0.11758132021177814', + '1.0745620124369040', + '3.0859374437175500', + '6.4147297336620305', + '11.807189489971737'] + assert [str(r) for r in w] == [ + '1.2217252674706516', + '0.48027722216462937', + '0.067748788910962126', + '0.0026872914935624654', + '1.5280865710465241e-5'] + + x, w = gauss_gen_laguerre(1, 2, 17) + assert [str(r) for r in x] == ['3.0000000000000000'] + assert [str(r) for r in w] == ['2.0000000000000000'] + + x, w = gauss_gen_laguerre(2, 2, 17) + assert [str(r) for r in x] == [ + '2.0000000000000000', + '6.0000000000000000'] + assert [str(r) for r in w] == [ + '1.5000000000000000', + '0.50000000000000000'] + + x, w = gauss_gen_laguerre(3, 2, 17) + assert [str(r) for r in x] == [ + '1.5173870806774125', + '4.3115831337195203', + '9.1710297856030672'] + assert [str(r) for r in w] == [ + '1.0374949614904253', + '0.90575000470306537', + '0.056755033806509347'] + + x, w = gauss_gen_laguerre(4, 2, 17) + assert [str(r) for r in x] == [ + '1.2267632635003021', + '3.4125073586969460', + '6.9026926058516134', + '12.458036771951139'] + assert [str(r) for r in w] == [ + '0.72552499769865438', + '1.0634242919791946', + '0.20669613102835355', + '0.0043545792937974889'] + + x, w = gauss_gen_laguerre(5, 2, 17) + assert [str(r) for r in x] == [ + '1.0311091440933816', + '2.8372128239538217', + '5.6202942725987079', + '9.6829098376640271', + '15.828473921690062'] + assert [str(r) for r in w] == [ + '0.52091739683509184', + '1.0667059331592211', + '0.38354972366693113', + '0.028564233532974658', + '0.00026271280578124935'] + + +def test_gen_laguerre_precise(): + x, w = gauss_gen_laguerre(3, Rational(-1, 2), 40) + assert [str(r) for r in x] == [ + '0.1901635091934881328718554276203028970878', + '1.784492748543251591186722461957367638500', + '5.525343742263260275941422110422329464413'] + assert [str(r) for r in w] == [ + '1.449259190448785048183829411195134343108', + '0.3141346406457132878326231270167565378246', + '0.009060019811017691281714945129254301865020'] + + x, w = gauss_gen_laguerre(3, 2, 40) + assert [str(r) for r in x] == [ + '1.517387080677412495020323111016672547482', + '4.311583133719520302881184669723530562299', + '9.171029785603067202098492219259796890218'] + assert [str(r) for r in w] == [ + '1.037494961490425285817554606541269153041', + '0.9057500047030653669269785048806009945254', + '0.05675503380650934725546688857812985243312'] + + +def test_chebyshev_t(): + x, w = gauss_chebyshev_t(1, 17) + assert [str(r) for r in x] == ['0'] + assert [str(r) for r in w] == ['3.1415926535897932'] + + x, w = gauss_chebyshev_t(2, 17) + assert [str(r) for r in x] == [ + '0.70710678118654752', + '-0.70710678118654752'] + assert [str(r) for r in w] == [ + '1.5707963267948966', + '1.5707963267948966'] + + x, w = gauss_chebyshev_t(3, 17) + assert [str(r) for r in x] == [ + '0.86602540378443865', + '0', + '-0.86602540378443865'] + assert [str(r) for r in w] == [ + '1.0471975511965977', + '1.0471975511965977', + '1.0471975511965977'] + + x, w = gauss_chebyshev_t(4, 17) + assert [str(r) for r in x] == [ + '0.92387953251128676', + '0.38268343236508977', + '-0.38268343236508977', + '-0.92387953251128676'] + assert [str(r) for r in w] == [ + '0.78539816339744831', + '0.78539816339744831', + '0.78539816339744831', + '0.78539816339744831'] + + x, w = gauss_chebyshev_t(5, 17) + assert [str(r) for r in x] == [ + '0.95105651629515357', + '0.58778525229247313', + '0', + '-0.58778525229247313', + '-0.95105651629515357'] + assert [str(r) for r in w] == [ + '0.62831853071795865', + '0.62831853071795865', + '0.62831853071795865', + '0.62831853071795865', + '0.62831853071795865'] + + +def test_chebyshev_t_precise(): + x, w = gauss_chebyshev_t(3, 40) + assert [str(r) for r in x] == [ + '0.8660254037844386467637231707529361834714', + '0', + '-0.8660254037844386467637231707529361834714'] + assert [str(r) for r in w] == [ + '1.047197551196597746154214461093167628066', + '1.047197551196597746154214461093167628066', + '1.047197551196597746154214461093167628066'] + + +def test_chebyshev_u(): + x, w = gauss_chebyshev_u(1, 17) + assert [str(r) for r in x] == ['0'] + assert [str(r) for r in w] == ['1.5707963267948966'] + + x, w = gauss_chebyshev_u(2, 17) + assert [str(r) for r in x] == [ + '0.50000000000000000', + '-0.50000000000000000'] + assert [str(r) for r in w] == [ + '0.78539816339744831', + '0.78539816339744831'] + + x, w = gauss_chebyshev_u(3, 17) + assert [str(r) for r in x] == [ + '0.70710678118654752', + '0', + '-0.70710678118654752'] + assert [str(r) for r in w] == [ + '0.39269908169872415', + '0.78539816339744831', + '0.39269908169872415'] + + x, w = gauss_chebyshev_u(4, 17) + assert [str(r) for r in x] == [ + '0.80901699437494742', + '0.30901699437494742', + '-0.30901699437494742', + '-0.80901699437494742'] + assert [str(r) for r in w] == [ + '0.21707871342270599', + '0.56831944997474231', + '0.56831944997474231', + '0.21707871342270599'] + + x, w = gauss_chebyshev_u(5, 17) + assert [str(r) for r in x] == [ + '0.86602540378443865', + '0.50000000000000000', + '0', + '-0.50000000000000000', + '-0.86602540378443865'] + assert [str(r) for r in w] == [ + '0.13089969389957472', + '0.39269908169872415', + '0.52359877559829887', + '0.39269908169872415', + '0.13089969389957472'] + + +def test_chebyshev_u_precise(): + x, w = gauss_chebyshev_u(3, 40) + assert [str(r) for r in x] == [ + '0.7071067811865475244008443621048490392848', + '0', + '-0.7071067811865475244008443621048490392848'] + assert [str(r) for r in w] == [ + '0.3926990816987241548078304229099378605246', + '0.7853981633974483096156608458198757210493', + '0.3926990816987241548078304229099378605246'] + + +def test_jacobi(): + x, w = gauss_jacobi(1, Rational(-1, 2), S.Half, 17) + assert [str(r) for r in x] == ['0.50000000000000000'] + assert [str(r) for r in w] == ['3.1415926535897932'] + + x, w = gauss_jacobi(2, Rational(-1, 2), S.Half, 17) + assert [str(r) for r in x] == [ + '-0.30901699437494742', + '0.80901699437494742'] + assert [str(r) for r in w] == [ + '0.86831485369082398', + '2.2732777998989693'] + + x, w = gauss_jacobi(3, Rational(-1, 2), S.Half, 17) + assert [str(r) for r in x] == [ + '-0.62348980185873353', + '0.22252093395631440', + '0.90096886790241913'] + assert [str(r) for r in w] == [ + '0.33795476356635433', + '1.0973322242791115', + '1.7063056657443274'] + + x, w = gauss_jacobi(4, Rational(-1, 2), S.Half, 17) + assert [str(r) for r in x] == [ + '-0.76604444311897804', + '-0.17364817766693035', + '0.50000000000000000', + '0.93969262078590838'] + assert [str(r) for r in w] == [ + '0.16333179083642836', + '0.57690240318269103', + '1.0471975511965977', + '1.3541609083740761'] + + x, w = gauss_jacobi(5, Rational(-1, 2), S.Half, 17) + assert [str(r) for r in x] == [ + '-0.84125353283118117', + '-0.41541501300188643', + '0.14231483827328514', + '0.65486073394528506', + '0.95949297361449739'] + assert [str(r) for r in w] == [ + '0.090675770007435372', + '0.33391416373675607', + '0.65248870981926643', + '0.94525424081394926', + '1.1192597692123861'] + + x, w = gauss_jacobi(1, 2, 3, 17) + assert [str(r) for r in x] == ['0.14285714285714286'] + assert [str(r) for r in w] == ['1.0666666666666667'] + + x, w = gauss_jacobi(2, 2, 3, 17) + assert [str(r) for r in x] == [ + '-0.24025307335204215', + '0.46247529557426437'] + assert [str(r) for r in w] == [ + '0.48514624517838660', + '0.58152042148828007'] + + x, w = gauss_jacobi(3, 2, 3, 17) + assert [str(r) for r in x] == [ + '-0.46115870378089762', + '0.10438533038323902', + '0.62950064612493132'] + assert [str(r) for r in w] == [ + '0.17937613502213266', + '0.61595640991147154', + '0.27133412173306246'] + + x, w = gauss_jacobi(4, 2, 3, 17) + assert [str(r) for r in x] == [ + '-0.59903470850824782', + '-0.14761105199952565', + '0.32554377081188859', + '0.72879429738819258'] + assert [str(r) for r in w] == [ + '0.067809641836772187', + '0.38956404952032481', + '0.47995970868024150', + '0.12933326662932816'] + + x, w = gauss_jacobi(5, 2, 3, 17) + assert [str(r) for r in x] == [ + '-0.69045775012676106', + '-0.32651993134900065', + '0.082337849552034905', + '0.47517887061283164', + '0.79279429464422850'] + assert [str(r) for r in w] == [ + '0.027410178066337099', + '0.21291786060364828', + '0.43908437944395081', + '0.32220656547221822', + '0.065047683080512268'] + + +def test_jacobi_precise(): + x, w = gauss_jacobi(3, Rational(-1, 2), S.Half, 40) + assert [str(r) for r in x] == [ + '-0.6234898018587335305250048840042398106323', + '0.2225209339563144042889025644967947594664', + '0.9009688679024191262361023195074450511659'] + assert [str(r) for r in w] == [ + '0.3379547635663543330553835737094171534907', + '1.097332224279111467485302294320899710461', + '1.706305665744327437921957515249186020246'] + + x, w = gauss_jacobi(3, 2, 3, 40) + assert [str(r) for r in x] == [ + '-0.4611587037808976179121958105554375981274', + '0.1043853303832390210914918407615869143233', + '0.6295006461249313240934312425211234110769'] + assert [str(r) for r in w] == [ + '0.1793761350221326596137764371503859752628', + '0.6159564099114715430909548532229749439714', + '0.2713341217330624639619353762933057474325'] + + +def test_lobatto(): + x, w = gauss_lobatto(2, 17) + assert [str(r) for r in x] == [ + '-1', + '1'] + assert [str(r) for r in w] == [ + '1.0000000000000000', + '1.0000000000000000'] + + x, w = gauss_lobatto(3, 17) + assert [str(r) for r in x] == [ + '-1', + '0', + '1'] + assert [str(r) for r in w] == [ + '0.33333333333333333', + '1.3333333333333333', + '0.33333333333333333'] + + x, w = gauss_lobatto(4, 17) + assert [str(r) for r in x] == [ + '-1', + '-0.44721359549995794', + '0.44721359549995794', + '1'] + assert [str(r) for r in w] == [ + '0.16666666666666667', + '0.83333333333333333', + '0.83333333333333333', + '0.16666666666666667'] + + x, w = gauss_lobatto(5, 17) + assert [str(r) for r in x] == [ + '-1', + '-0.65465367070797714', + '0', + '0.65465367070797714', + '1'] + assert [str(r) for r in w] == [ + '0.10000000000000000', + '0.54444444444444444', + '0.71111111111111111', + '0.54444444444444444', + '0.10000000000000000'] + + +def test_lobatto_precise(): + x, w = gauss_lobatto(3, 40) + assert [str(r) for r in x] == [ + '-1', + '0', + '1'] + assert [str(r) for r in w] == [ + '0.3333333333333333333333333333333333333333', + '1.333333333333333333333333333333333333333', + '0.3333333333333333333333333333333333333333'] diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_rationaltools.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_rationaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..809bf30c1c35f80e2a0b15c1e639603eab28a250 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_rationaltools.py @@ -0,0 +1,183 @@ +from sympy.core.numbers import (I, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import atan +from sympy.integrals.integrals import integrate +from sympy.polys.polytools import Poly +from sympy.simplify.simplify import simplify + +from sympy.integrals.rationaltools import ratint, ratint_logpart, log_to_atan + +from sympy.abc import a, b, x, t + +half = S.Half + + +def test_ratint(): + assert ratint(S.Zero, x) == 0 + assert ratint(S(7), x) == 7*x + + assert ratint(x, x) == x**2/2 + assert ratint(2*x, x) == x**2 + assert ratint(-2*x, x) == -x**2 + + assert ratint(8*x**7 + 2*x + 1, x) == x**8 + x**2 + x + + f = S.One + g = x + 1 + + assert ratint(f / g, x) == log(x + 1) + assert ratint((f, g), x) == log(x + 1) + + f = x**3 - x + g = x - 1 + + assert ratint(f/g, x) == x**3/3 + x**2/2 + + f = x + g = (x - a)*(x + a) + + assert ratint(f/g, x) == log(x**2 - a**2)/2 + + f = S.One + g = x**2 + 1 + + assert ratint(f/g, x, real=None) == atan(x) + assert ratint(f/g, x, real=True) == atan(x) + + assert ratint(f/g, x, real=False) == I*log(x + I)/2 - I*log(x - I)/2 + + f = S(36) + g = x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2 + + assert ratint(f/g, x) == \ + -4*log(x + 1) + 4*log(x - 2) + (12*x + 6)/(x**2 - 1) + + f = x**4 - 3*x**2 + 6 + g = x**6 - 5*x**4 + 5*x**2 + 4 + + assert ratint(f/g, x) == \ + atan(x) + atan(x**3) + atan(x/2 - Rational(3, 2)*x**3 + S.Half*x**5) + + f = x**7 - 24*x**4 - 4*x**2 + 8*x - 8 + g = x**8 + 6*x**6 + 12*x**4 + 8*x**2 + + assert ratint(f/g, x) == \ + (4 + 6*x + 8*x**2 + 3*x**3)/(4*x + 4*x**3 + x**5) + log(x) + + assert ratint((x**3*f)/(x*g), x) == \ + -(12 - 16*x + 6*x**2 - 14*x**3)/(4 + 4*x**2 + x**4) - \ + 5*sqrt(2)*atan(x*sqrt(2)/2) + S.Half*x**2 - 3*log(2 + x**2) + + f = x**5 - x**4 + 4*x**3 + x**2 - x + 5 + g = x**4 - 2*x**3 + 5*x**2 - 4*x + 4 + + assert ratint(f/g, x) == \ + x + S.Half*x**2 + S.Half*log(2 - x + x**2) + (9 - 4*x)/(7*x**2 - 7*x + 14) + \ + 13*sqrt(7)*atan(Rational(-1, 7)*sqrt(7) + 2*x*sqrt(7)/7)/49 + + assert ratint(1/(x**2 + x + 1), x) == \ + 2*sqrt(3)*atan(sqrt(3)/3 + 2*x*sqrt(3)/3)/3 + + assert ratint(1/(x**3 + 1), x) == \ + -log(1 - x + x**2)/6 + log(1 + x)/3 + sqrt(3)*atan(-sqrt(3) + /3 + 2*x*sqrt(3)/3)/3 + + assert ratint(1/(x**2 + x + 1), x, real=False) == \ + -I*3**half*log(half + x - half*I*3**half)/3 + \ + I*3**half*log(half + x + half*I*3**half)/3 + + assert ratint(1/(x**3 + 1), x, real=False) == log(1 + x)/3 + \ + (Rational(-1, 6) + I*3**half/6)*log(-half + x + I*3**half/2) + \ + (Rational(-1, 6) - I*3**half/6)*log(-half + x - I*3**half/2) + + # issue 4991 + assert ratint(1/(x*(a + b*x)**3), x) == \ + (3*a + 2*b*x)/(2*a**4 + 4*a**3*b*x + 2*a**2*b**2*x**2) + ( + log(x) - log(a/b + x))/a**3 + + assert ratint(x/(1 - x**2), x) == -log(x**2 - 1)/2 + assert ratint(-x/(1 - x**2), x) == log(x**2 - 1)/2 + + assert ratint((x/4 - 4/(1 - x)).diff(x), x) == x/4 + 4/(x - 1) + + ans = atan(x) + assert ratint(1/(x**2 + 1), x, symbol=x) == ans + assert ratint(1/(x**2 + 1), x, symbol='x') == ans + assert ratint(1/(x**2 + 1), x, symbol=a) == ans + # this asserts that as_dummy must return a unique symbol + # even if the symbol is already a Dummy + d = Dummy() + assert ratint(1/(d**2 + 1), d, symbol=d) == atan(d) + + +def test_ratint_logpart(): + assert ratint_logpart(x, x**2 - 9, x, t) == \ + [(Poly(x**2 - 9, x), Poly(-2*t + 1, t))] + assert ratint_logpart(x**2, x**3 - 5, x, t) == \ + [(Poly(x**3 - 5, x), Poly(-3*t + 1, t))] + + +def test_issue_5414(): + assert ratint(1/(x**2 + 16), x) == atan(x/4)/4 + + +def test_issue_5249(): + assert ratint( + 1/(x**2 + a**2), x) == (-I*log(-I*a + x)/2 + I*log(I*a + x)/2)/a + + +def test_issue_5817(): + a, b, c = symbols('a,b,c', positive=True) + + assert simplify(ratint(a/(b*c*x**2 + a**2 + b*a), x)) == \ + sqrt(a)*atan(sqrt( + b)*sqrt(c)*x/(sqrt(a)*sqrt(a + b)))/(sqrt(b)*sqrt(c)*sqrt(a + b)) + + +def test_issue_5981(): + u = symbols('u') + assert integrate(1/(u**2 + 1)) == atan(u) + +def test_issue_10488(): + a,b,c,x = symbols('a b c x', positive=True) + assert integrate(x/(a*x+b),x) == x/a - b*log(a*x + b)/a**2 + + +def test_issues_8246_12050_13501_14080(): + a = symbols('a', nonzero=True) + assert integrate(a/(x**2 + a**2), x) == atan(x/a) + assert integrate(1/(x**2 + a**2), x) == atan(x/a)/a + assert integrate(1/(1 + a**2*x**2), x) == atan(a*x)/a + + +def test_issue_6308(): + k, a0 = symbols('k a0', real=True) + assert integrate((x**2 + 1 - k**2)/(x**2 + 1 + a0**2), x) == \ + x - (a0**2 + k**2)*atan(x/sqrt(a0**2 + 1))/sqrt(a0**2 + 1) + + +def test_issue_5907(): + a = symbols('a', nonzero=True) + assert integrate(1/(x**2 + a**2)**2, x) == \ + x/(2*a**4 + 2*a**2*x**2) + atan(x/a)/(2*a**3) + + +def test_log_to_atan(): + f, g = (Poly(x + S.Half, x, domain='QQ'), Poly(sqrt(3)/2, x, domain='EX')) + fg_ans = 2*atan(2*sqrt(3)*x/3 + sqrt(3)/3) + assert log_to_atan(f, g) == fg_ans + assert log_to_atan(g, f) == -fg_ans + + +def test_issue_25896(): + # for both tests, C = 0 in log_to_real + # but this only has a log result + e = (2*x + 1)/(x**2 + x + 1) + 1/x + assert ratint(e, x) == log(x**3 + x**2 + x) + # while this has more + assert ratint((4*x + 7)/(x**2 + 4*x + 6) + 2/x, x) == ( + 2*log(x) + 2*log(x**2 + 4*x + 6) - sqrt(2)*atan( + sqrt(2)*x/2 + sqrt(2))/2) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_rde.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_rde.py new file mode 100644 index 0000000000000000000000000000000000000000..3c7df5ce05846dc270756cd878870bbff78ff976 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_rde.py @@ -0,0 +1,202 @@ +"""Most of these tests come from the examples in Bronstein's book.""" +from sympy.core.numbers import (I, Rational, oo) +from sympy.core.symbol import symbols +from sympy.polys.polytools import Poly +from sympy.integrals.risch import (DifferentialExtension, + NonElementaryIntegralException) +from sympy.integrals.rde import (order_at, order_at_oo, weak_normalizer, + normal_denom, special_denom, bound_degree, spde, solve_poly_rde, + no_cancel_equal, cancel_primitive, cancel_exp, rischDE) + +from sympy.testing.pytest import raises +from sympy.abc import x, t, z, n + +t0, t1, t2, k = symbols('t:3 k') + + +def test_order_at(): + a = Poly(t**4, t) + b = Poly((t**2 + 1)**3*t, t) + c = Poly((t**2 + 1)**6*t, t) + d = Poly((t**2 + 1)**10*t**10, t) + e = Poly((t**2 + 1)**100*t**37, t) + p1 = Poly(t, t) + p2 = Poly(1 + t**2, t) + assert order_at(a, p1, t) == 4 + assert order_at(b, p1, t) == 1 + assert order_at(c, p1, t) == 1 + assert order_at(d, p1, t) == 10 + assert order_at(e, p1, t) == 37 + assert order_at(a, p2, t) == 0 + assert order_at(b, p2, t) == 3 + assert order_at(c, p2, t) == 6 + assert order_at(d, p1, t) == 10 + assert order_at(e, p2, t) == 100 + assert order_at(Poly(0, t), Poly(t, t), t) is oo + assert order_at_oo(Poly(t**2 - 1, t), Poly(t + 1), t) == \ + order_at_oo(Poly(t - 1, t), Poly(1, t), t) == -1 + assert order_at_oo(Poly(0, t), Poly(1, t), t) is oo + +def test_weak_normalizer(): + a = Poly((1 + x)*t**5 + 4*t**4 + (-1 - 3*x)*t**3 - 4*t**2 + (-2 + 2*x)*t, t) + d = Poly(t**4 - 3*t**2 + 2, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + r = weak_normalizer(a, d, DE, z) + assert r == (Poly(t**5 - t**4 - 4*t**3 + 4*t**2 + 4*t - 4, t, domain='ZZ[x]'), + (Poly((1 + x)*t**2 + x*t, t, domain='ZZ[x]'), + Poly(t + 1, t, domain='ZZ[x]'))) + assert weak_normalizer(r[1][0], r[1][1], DE) == (Poly(1, t), r[1]) + r = weak_normalizer(Poly(1 + t**2), Poly(t**2 - 1, t), DE, z) + assert r == (Poly(t**4 - 2*t**2 + 1, t), (Poly(-3*t**2 + 1, t), Poly(t**2 - 1, t))) + assert weak_normalizer(r[1][0], r[1][1], DE, z) == (Poly(1, t), r[1]) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2)]}) + r = weak_normalizer(Poly(1 + t**2), Poly(t, t), DE, z) + assert r == (Poly(t, t), (Poly(0, t), Poly(1, t))) + assert weak_normalizer(r[1][0], r[1][1], DE, z) == (Poly(1, t), r[1]) + + +def test_normal_denom(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + raises(NonElementaryIntegralException, lambda: normal_denom(Poly(1, x), Poly(1, x), + Poly(1, x), Poly(x, x), DE)) + fa, fd = Poly(t**2 + 1, t), Poly(1, t) + ga, gd = Poly(1, t), Poly(t**2, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert normal_denom(fa, fd, ga, gd, DE) == \ + (Poly(t, t), (Poly(t**3 - t**2 + t - 1, t), Poly(1, t)), (Poly(1, t), + Poly(1, t)), Poly(t, t)) + + +def test_special_denom(): + # TODO: add more tests here + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), Poly(t**2 - 1, t), + Poly(t, t), DE) == \ + (Poly(1, t), Poly(t**2 - 1, t), Poly(t**2 - 1, t), Poly(t, t)) +# assert special_denom(Poly(1, t), Poly(2*x, t), Poly((1 + 2*x)*t, t), DE) == 1 + + # issue 3940 + # Note, this isn't a very good test, because the denominator is just 1, + # but at least it tests the exp cancellation case + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-2*x*t0, t0), + Poly(I*k*t1, t1)]}) + DE.decrement_level() + assert special_denom(Poly(1, t0), Poly(I*k, t0), Poly(1, t0), Poly(t0, t0), + Poly(1, t0), DE) == \ + (Poly(1, t0, domain='ZZ'), Poly(I*k, t0, domain='ZZ_I[k,x]'), + Poly(t0, t0, domain='ZZ'), Poly(1, t0, domain='ZZ')) + + + assert special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), Poly(t**2 - 1, t), + Poly(t, t), DE, case='tan') == \ + (Poly(1, t, t0, domain='ZZ'), Poly(t**2, t0, t, domain='ZZ[x]'), + Poly(t, t, t0, domain='ZZ'), Poly(1, t0, domain='ZZ')) + + raises(ValueError, lambda: special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), Poly(t**2 - 1, t), + Poly(t, t), DE, case='unrecognized_case')) + + +def test_bound_degree_fail(): + # Primitive + DE = DifferentialExtension(extension={'D': [Poly(1, x), + Poly(t0/x**2, t0), Poly(1/x, t)]}) + assert bound_degree(Poly(t**2, t), Poly(-(1/x**2*t**2 + 1/x), t), + Poly((2*x - 1)*t**4 + (t0 + x)/x*t**3 - (t0 + 4*x**2)/2*x*t**2 + x*t, + t), DE) == 3 + + +def test_bound_degree(): + # Base + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert bound_degree(Poly(1, x), Poly(-2*x, x), Poly(1, x), DE) == 0 + + # Primitive (see above test_bound_degree_fail) + # TODO: Add test for when the degree bound becomes larger after limited_integrate + # TODO: Add test for db == da - 1 case + + # Exp + # TODO: Add tests + # TODO: Add test for when the degree becomes larger after parametric_log_deriv() + + # Nonlinear + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert bound_degree(Poly(t, t), Poly((t - 1)*(t**2 + 1), t), Poly(1, t), DE) == 0 + + +def test_spde(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + raises(NonElementaryIntegralException, lambda: spde(Poly(t, t), Poly((t - 1)*(t**2 + 1), t), Poly(1, t), 0, DE)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert spde(Poly(t**2 + x*t*2 + x**2, t), Poly(t**2/x**2 + (2/x - 1)*t, t), + Poly(t**2/x**2 + (2/x - 1)*t, t), 0, DE) == \ + (Poly(0, t), Poly(0, t), 0, Poly(0, t), Poly(1, t, domain='ZZ(x)')) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0/x**2, t0), Poly(1/x, t)]}) + assert spde(Poly(t**2, t), Poly(-t**2/x**2 - 1/x, t), + Poly((2*x - 1)*t**4 + (t0 + x)/x*t**3 - (t0 + 4*x**2)/(2*x)*t**2 + x*t, t), 3, DE) == \ + (Poly(0, t), Poly(0, t), 0, Poly(0, t), + Poly(t0*t**2/2 + x**2*t**2 - x**2*t, t, domain='ZZ(x,t0)')) + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert spde(Poly(x**2 + x + 1, x), Poly(-2*x - 1, x), Poly(x**5/2 + + 3*x**4/4 + x**3 - x**2 + 1, x), 4, DE) == \ + (Poly(0, x, domain='QQ'), Poly(x/2 - Rational(1, 4), x), 2, Poly(x**2 + x + 1, x), Poly(x*Rational(5, 4), x)) + assert spde(Poly(x**2 + x + 1, x), Poly(-2*x - 1, x), Poly(x**5/2 + + 3*x**4/4 + x**3 - x**2 + 1, x), n, DE) == \ + (Poly(0, x, domain='QQ'), Poly(x/2 - Rational(1, 4), x), -2 + n, Poly(x**2 + x + 1, x), Poly(x*Rational(5, 4), x)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1, t)]}) + raises(NonElementaryIntegralException, lambda: spde(Poly((t - 1)*(t**2 + 1)**2, t), Poly((t - 1)*(t**2 + 1), t), Poly(1, t), 0, DE)) + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert spde(Poly(x**2 - x, x), Poly(1, x), Poly(9*x**4 - 10*x**3 + 2*x**2, x), 4, DE) == \ + (Poly(0, x, domain='ZZ'), Poly(0, x), 0, Poly(0, x), Poly(3*x**3 - 2*x**2, x, domain='QQ')) + assert spde(Poly(x**2 - x, x), Poly(x**2 - 5*x + 3, x), Poly(x**7 - x**6 - 2*x**4 + 3*x**3 - x**2, x), 5, DE) == \ + (Poly(1, x, domain='QQ'), Poly(x + 1, x, domain='QQ'), 1, Poly(x**4 - x**3, x), Poly(x**3 - x**2, x, domain='QQ')) + +def test_solve_poly_rde_no_cancel(): + # deg(b) large + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + assert solve_poly_rde(Poly(t**2 + 1, t), Poly(t**3 + (x + 1)*t**2 + t + x + 2, t), + oo, DE) == Poly(t + x, t) + # deg(b) small + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert solve_poly_rde(Poly(0, x), Poly(x/2 - Rational(1, 4), x), oo, DE) == \ + Poly(x**2/4 - x/4, x) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert solve_poly_rde(Poly(2, t), Poly(t**2 + 2*t + 3, t), 1, DE) == \ + Poly(t + 1, t, x) + # deg(b) == deg(D) - 1 + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert no_cancel_equal(Poly(1 - t, t), + Poly(t**3 + t**2 - 2*x*t - 2*x, t), oo, DE) == \ + (Poly(t**2, t), 1, Poly((-2 - 2*x)*t - 2*x, t)) + + +def test_solve_poly_rde_cancel(): + # exp + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert cancel_exp(Poly(2*x, t), Poly(2*x, t), 0, DE) == \ + Poly(1, t) + assert cancel_exp(Poly(2*x, t), Poly((1 + 2*x)*t, t), 1, DE) == \ + Poly(t, t) + # TODO: Add more exp tests, including tests that require is_deriv_in_field() + + # primitive + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + + # If the DecrementLevel context manager is working correctly, this shouldn't + # cause any problems with the further tests. + raises(NonElementaryIntegralException, lambda: cancel_primitive(Poly(1, t), Poly(t, t), oo, DE)) + + assert cancel_primitive(Poly(1, t), Poly(t + 1/x, t), 2, DE) == \ + Poly(t, t) + assert cancel_primitive(Poly(4*x, t), Poly(4*x*t**2 + 2*t/x, t), 3, DE) == \ + Poly(t**2, t) + + # TODO: Add more primitive tests, including tests that require is_deriv_in_field() + + +def test_rischDE(): + # TODO: Add more tests for rischDE, including ones from the text + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + DE.decrement_level() + assert rischDE(Poly(-2*x, x), Poly(1, x), Poly(1 - 2*x - 2*x**2, x), + Poly(1, x), DE) == \ + (Poly(x + 1, x), Poly(1, x)) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_risch.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_risch.py new file mode 100644 index 0000000000000000000000000000000000000000..68be260e1f3d42fb14c790afe6bda1768e777666 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_risch.py @@ -0,0 +1,763 @@ +"""Most of these tests come from the examples in Bronstein's book.""" +from sympy.core.function import (Function, Lambda, diff, expand_log) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (atan, sin, tan) +from sympy.polys.polytools import (Poly, cancel, factor) +from sympy.integrals.risch import (gcdex_diophantine, frac_in, as_poly_1t, + derivation, splitfactor, splitfactor_sqf, canonical_representation, + hermite_reduce, polynomial_reduce, residue_reduce, residue_reduce_to_basic, + integrate_primitive, integrate_hyperexponential_polynomial, + integrate_hyperexponential, integrate_hypertangent_polynomial, + integrate_nonlinear_no_specials, integer_powers, DifferentialExtension, + risch_integrate, DecrementLevel, NonElementaryIntegral, recognize_log_derivative, + recognize_derivative, laurent_series) +from sympy.testing.pytest import raises + +from sympy.abc import x, t, nu, z, a, y +t0, t1, t2 = symbols('t:3') +i = Symbol('i') + +def test_gcdex_diophantine(): + assert gcdex_diophantine(Poly(x**4 - 2*x**3 - 6*x**2 + 12*x + 15), + Poly(x**3 + x**2 - 4*x - 4), Poly(x**2 - 1)) == \ + (Poly((-x**2 + 4*x - 3)/5), Poly((x**3 - 7*x**2 + 16*x - 10)/5)) + assert gcdex_diophantine(Poly(x**3 + 6*x + 7), Poly(x**2 + 3*x + 2), Poly(x + 1)) == \ + (Poly(1/13, x, domain='QQ'), Poly(-1/13*x + 3/13, x, domain='QQ')) + + +def test_frac_in(): + assert frac_in(Poly((x + 1)/x*t, t), x) == \ + (Poly(t*x + t, x), Poly(x, x)) + assert frac_in((x + 1)/x*t, x) == \ + (Poly(t*x + t, x), Poly(x, x)) + assert frac_in((Poly((x + 1)/x*t, t), Poly(t + 1, t)), x) == \ + (Poly(t*x + t, x), Poly((1 + t)*x, x)) + raises(ValueError, lambda: frac_in((x + 1)/log(x)*t, x)) + assert frac_in(Poly((2 + 2*x + x*(1 + x))/(1 + x)**2, t), x, cancel=True) == \ + (Poly(x + 2, x), Poly(x + 1, x)) + + +def test_as_poly_1t(): + assert as_poly_1t(2/t + t, t, z) in [ + Poly(t + 2*z, t, z), Poly(t + 2*z, z, t)] + assert as_poly_1t(2/t + 3/t**2, t, z) in [ + Poly(2*z + 3*z**2, t, z), Poly(2*z + 3*z**2, z, t)] + assert as_poly_1t(2/((exp(2) + 1)*t), t, z) in [ + Poly(2/(exp(2) + 1)*z, t, z), Poly(2/(exp(2) + 1)*z, z, t)] + assert as_poly_1t(2/((exp(2) + 1)*t) + t, t, z) in [ + Poly(t + 2/(exp(2) + 1)*z, t, z), Poly(t + 2/(exp(2) + 1)*z, z, t)] + assert as_poly_1t(S.Zero, t, z) == Poly(0, t, z) + + +def test_derivation(): + p = Poly(4*x**4*t**5 + (-4*x**3 - 4*x**4)*t**4 + (-3*x**2 + 2*x**3)*t**3 + + (2*x + 7*x**2 + 2*x**3)*t**2 + (1 - 4*x - 4*x**2)*t - 1 + 2*x, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - 3/(2*x)*t + 1/(2*x), t)]}) + assert derivation(p, DE) == Poly(-20*x**4*t**6 + (2*x**3 + 16*x**4)*t**5 + + (21*x**2 + 12*x**3)*t**4 + (x*Rational(7, 2) - 25*x**2 - 12*x**3)*t**3 + + (-5 - x*Rational(15, 2) + 7*x**2)*t**2 - (3 - 8*x - 10*x**2 - 4*x**3)/(2*x)*t + + (1 - 4*x**2)/(2*x), t) + assert derivation(Poly(1, t), DE) == Poly(0, t) + assert derivation(Poly(t, t), DE) == DE.d + assert derivation(Poly(t**2 + 1/x*t + (1 - 2*x)/(4*x**2), t), DE) == \ + Poly(-2*t**3 - 4/x*t**2 - (5 - 2*x)/(2*x**2)*t - (1 - 2*x)/(2*x**3), t, domain='ZZ(x)') + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(t, t)]}) + assert derivation(Poly(x*t*t1, t), DE) == Poly(t*t1 + x*t*t1 + t, t) + assert derivation(Poly(x*t*t1, t), DE, coefficientD=True) == \ + Poly((1 + t1)*t, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert derivation(Poly(x, x), DE) == Poly(1, x) + # Test basic option + assert derivation((x + 1)/(x - 1), DE, basic=True) == -2/(1 - 2*x + x**2) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert derivation((t + 1)/(t - 1), DE, basic=True) == -2*t/(1 - 2*t + t**2) + assert derivation(t + 1, DE, basic=True) == t + + +def test_splitfactor(): + p = Poly(4*x**4*t**5 + (-4*x**3 - 4*x**4)*t**4 + (-3*x**2 + 2*x**3)*t**3 + + (2*x + 7*x**2 + 2*x**3)*t**2 + (1 - 4*x - 4*x**2)*t - 1 + 2*x, t, field=True) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - 3/(2*x)*t + 1/(2*x), t)]}) + assert splitfactor(p, DE) == (Poly(4*x**4*t**3 + (-8*x**3 - 4*x**4)*t**2 + + (4*x**2 + 8*x**3)*t - 4*x**2, t, domain='ZZ(x)'), + Poly(t**2 + 1/x*t + (1 - 2*x)/(4*x**2), t, domain='ZZ(x)')) + assert splitfactor(Poly(x, t), DE) == (Poly(x, t), Poly(1, t)) + r = Poly(-4*x**4*z**2 + 4*x**6*z**2 - z*x**3 - 4*x**5*z**3 + 4*x**3*z**3 + x**4 + z*x**5 - x**6, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert splitfactor(r, DE, coefficientD=True) == \ + (Poly(x*z - x**2 - z*x**3 + x**4, t), Poly(-x**2 + 4*x**2*z**2, t)) + assert splitfactor_sqf(r, DE, coefficientD=True) == \ + (((Poly(x*z - x**2 - z*x**3 + x**4, t), 1),), ((Poly(-x**2 + 4*x**2*z**2, t), 1),)) + assert splitfactor(Poly(0, t), DE) == (Poly(0, t), Poly(1, t)) + assert splitfactor_sqf(Poly(0, t), DE) == (((Poly(0, t), 1),), ()) + + +def test_canonical_representation(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + assert canonical_representation(Poly(x - t, t), Poly(t**2, t), DE) == \ + (Poly(0, t, domain='ZZ[x]'), (Poly(0, t, domain='QQ[x]'), + Poly(1, t, domain='ZZ')), (Poly(-t + x, t, domain='QQ[x]'), + Poly(t**2, t))) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert canonical_representation(Poly(t**5 + t**3 + x**2*t + 1, t), + Poly((t**2 + 1)**3, t), DE) == \ + (Poly(0, t, domain='ZZ[x]'), (Poly(t**5 + t**3 + x**2*t + 1, t, domain='QQ[x]'), + Poly(t**6 + 3*t**4 + 3*t**2 + 1, t, domain='QQ')), + (Poly(0, t, domain='QQ[x]'), Poly(1, t, domain='QQ'))) + + +def test_hermite_reduce(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + + assert hermite_reduce(Poly(x - t, t), Poly(t**2, t), DE) == \ + ((Poly(-x, t, domain='QQ[x]'), Poly(t, t, domain='QQ[x]')), + (Poly(0, t, domain='QQ[x]'), Poly(1, t, domain='QQ[x]')), + (Poly(-x, t, domain='QQ[x]'), Poly(1, t, domain='QQ[x]'))) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - t/x - (1 - nu**2/x**2), t)]}) + + assert hermite_reduce( + Poly(x**2*t**5 + x*t**4 - nu**2*t**3 - x*(x**2 + 1)*t**2 - (x**2 - nu**2)*t - x**5/4, t), + Poly(x**2*t**4 + x**2*(x**2 + 2)*t**2 + x**2 + x**4 + x**6/4, t), DE) == \ + ((Poly(-x**2 - 4, t, domain='ZZ(x,nu)'), Poly(4*t**2 + 2*x**2 + 4, t, domain='ZZ(x,nu)')), + (Poly((-2*nu**2 - x**4)*t - (2*x**3 + 2*x), t, domain='ZZ(x,nu)'), + Poly(2*x**2*t**2 + x**4 + 2*x**2, t, domain='ZZ(x,nu)')), + (Poly(x*t + 1, t, domain='ZZ(x,nu)'), Poly(x, t, domain='ZZ(x,nu)'))) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + + a = Poly((-2 + 3*x)*t**3 + (-1 + x)*t**2 + (-4*x + 2*x**2)*t + x**2, t) + d = Poly(x*t**6 - 4*x**2*t**5 + 6*x**3*t**4 - 4*x**4*t**3 + x**5*t**2, t) + + assert hermite_reduce(a, d, DE) == \ + ((Poly(3*t**2 + t + 3*x, t, domain='ZZ(x)'), + Poly(3*t**4 - 9*x*t**3 + 9*x**2*t**2 - 3*x**3*t, t, domain='ZZ(x)')), + (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')), + (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)'))) + + assert hermite_reduce( + Poly(-t**2 + 2*t + 2, t, domain='ZZ(x)'), + Poly(-x*t**2 + 2*x*t - x, t, domain='ZZ(x)'), DE) == \ + ((Poly(3, t, domain='ZZ(x)'), Poly(t - 1, t, domain='ZZ(x)')), + (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')), + (Poly(1, t, domain='ZZ(x)'), Poly(x, t, domain='ZZ(x)'))) + + assert hermite_reduce( + Poly(-x**2*t**6 + (-1 - 2*x**3 + x**4)*t**3 + (-3 - 3*x**4)*t**2 - + 2*x*t - x - 3*x**2, t, domain='ZZ(x)'), + Poly(x**4*t**6 - 2*x**2*t**3 + 1, t, domain='ZZ(x)'), DE) == \ + ((Poly(x**3*t + x**4 + 1, t, domain='ZZ(x)'), Poly(x**3*t**3 - x, t, domain='ZZ(x)')), + (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')), + (Poly(-1, t, domain='ZZ(x)'), Poly(x**2, t, domain='ZZ(x)'))) + + assert hermite_reduce( + Poly((-2 + 3*x)*t**3 + (-1 + x)*t**2 + (-4*x + 2*x**2)*t + x**2, t), + Poly(x*t**6 - 4*x**2*t**5 + 6*x**3*t**4 - 4*x**4*t**3 + x**5*t**2, t), DE) == \ + ((Poly(3*t**2 + t + 3*x, t, domain='ZZ(x)'), + Poly(3*t**4 - 9*x*t**3 + 9*x**2*t**2 - 3*x**3*t, t, domain='ZZ(x)')), + (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')), + (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)'))) + + +def test_polynomial_reduce(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + assert polynomial_reduce(Poly(1 + x*t + t**2, t), DE) == \ + (Poly(t, t), Poly(x*t, t)) + assert polynomial_reduce(Poly(0, t), DE) == \ + (Poly(0, t), Poly(0, t)) + + +def test_laurent_series(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1, t)]}) + a = Poly(36, t) + d = Poly((t - 2)*(t**2 - 1)**2, t) + F = Poly(t**2 - 1, t) + n = 2 + assert laurent_series(a, d, F, n, DE) == \ + (Poly(-3*t**3 + 3*t**2 - 6*t - 8, t), Poly(t**5 + t**4 - 2*t**3 - 2*t**2 + t + 1, t), + [Poly(-3*t**3 - 6*t**2, t, domain='QQ'), Poly(2*t**6 + 6*t**5 - 8*t**3, t, domain='QQ')]) + + +def test_recognize_derivative(): + DE = DifferentialExtension(extension={'D': [Poly(1, t)]}) + a = Poly(36, t) + d = Poly((t - 2)*(t**2 - 1)**2, t) + assert recognize_derivative(a, d, DE) == False + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + a = Poly(2, t) + d = Poly(t**2 - 1, t) + assert recognize_derivative(a, d, DE) == False + assert recognize_derivative(Poly(x*t, t), Poly(1, t), DE) == True + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert recognize_derivative(Poly(t, t), Poly(1, t), DE) == True + + +def test_recognize_log_derivative(): + + a = Poly(2*x**2 + 4*x*t - 2*t - x**2*t, t) + d = Poly((2*x + t)*(t + x**2), t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert recognize_log_derivative(a, d, DE, z) == True + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert recognize_log_derivative(Poly(t + 1, t), Poly(t + x, t), DE) == True + assert recognize_log_derivative(Poly(2, t), Poly(t**2 - 1, t), DE) == True + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert recognize_log_derivative(Poly(1, x), Poly(x**2 - 2, x), DE) == False + assert recognize_log_derivative(Poly(1, x), Poly(x**2 + x, x), DE) == True + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert recognize_log_derivative(Poly(1, t), Poly(t**2 - 2, t), DE) == False + assert recognize_log_derivative(Poly(1, t), Poly(t**2 + t, t), DE) == False + + +def test_residue_reduce(): + a = Poly(2*t**2 - t - x**2, t) + d = Poly(t**3 - x**2*t, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)], 'Tfuncs': [log]}) + assert residue_reduce(a, d, DE, z, invert=False) == \ + ([(Poly(z**2 - Rational(1, 4), z, domain='ZZ(x)'), + Poly((1 + 3*x*z - 6*z**2 - 2*x**2 + 4*x**2*z**2)*t - x*z + x**2 + + 2*x**2*z**2 - 2*z*x**3, t, domain='ZZ(z, x)'))], False) + assert residue_reduce(a, d, DE, z, invert=True) == \ + ([(Poly(z**2 - Rational(1, 4), z, domain='ZZ(x)'), Poly(t + 2*x*z, t))], False) + assert residue_reduce(Poly(-2/x, t), Poly(t**2 - 1, t,), DE, z, invert=False) == \ + ([(Poly(z**2 - 1, z, domain='QQ'), Poly(-2*z*t/x - 2/x, t, domain='ZZ(z,x)'))], True) + ans = residue_reduce(Poly(-2/x, t), Poly(t**2 - 1, t), DE, z, invert=True) + assert ans == ([(Poly(z**2 - 1, z, domain='QQ'), Poly(t + z, t))], True) + assert residue_reduce_to_basic(ans[0], DE, z) == -log(-1 + log(x)) + log(1 + log(x)) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - t/x - (1 - nu**2/x**2), t)]}) + # TODO: Skip or make faster + assert residue_reduce(Poly((-2*nu**2 - x**4)/(2*x**2)*t - (1 + x**2)/x, t), + Poly(t**2 + 1 + x**2/2, t), DE, z) == \ + ([(Poly(z + S.Half, z, domain='QQ'), Poly(t**2 + 1 + x**2/2, t, + domain='ZZ(x,nu)'))], True) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + assert residue_reduce(Poly(-2*x*t + 1 - x**2, t), + Poly(t**2 + 2*x*t + 1 + x**2, t), DE, z) == \ + ([(Poly(z**2 + Rational(1, 4), z), Poly(t + x + 2*z, t))], True) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert residue_reduce(Poly(t, t), Poly(t + sqrt(2), t), DE, z) == \ + ([(Poly(z - 1, z, domain='QQ'), Poly(t + sqrt(2), t))], True) + + +def test_integrate_hyperexponential(): + # TODO: Add tests for integrate_hyperexponential() from the book + a = Poly((1 + 2*t1 + t1**2 + 2*t1**3)*t**2 + (1 + t1**2)*t + 1 + t1**2, t) + d = Poly(1, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t1**2, t1), + Poly(t*(1 + t1**2), t)], 'Tfuncs': [tan, Lambda(i, exp(tan(i)))]}) + assert integrate_hyperexponential(a, d, DE) == \ + (exp(2*tan(x))*tan(x) + exp(tan(x)), 1 + t1**2, True) + a = Poly((t1**3 + (x + 1)*t1**2 + t1 + x + 2)*t, t) + assert integrate_hyperexponential(a, d, DE) == \ + ((x + tan(x))*exp(tan(x)), 0, True) + + a = Poly(t, t) + d = Poly(1, t) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2*x*t, t)], + 'Tfuncs': [Lambda(i, exp(x**2))]}) + + assert integrate_hyperexponential(a, d, DE) == \ + (0, NonElementaryIntegral(exp(x**2), x), False) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)], 'Tfuncs': [exp]}) + assert integrate_hyperexponential(a, d, DE) == (exp(x), 0, True) + + a = Poly(25*t**6 - 10*t**5 + 7*t**4 - 8*t**3 + 13*t**2 + 2*t - 1, t) + d = Poly(25*t**6 + 35*t**4 + 11*t**2 + 1, t) + assert integrate_hyperexponential(a, d, DE) == \ + (-(11 - 10*exp(x))/(5 + 25*exp(2*x)) + log(1 + exp(2*x)), -1, True) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0, t0), Poly(t0*t, t)], + 'Tfuncs': [exp, Lambda(i, exp(exp(i)))]}) + assert integrate_hyperexponential(Poly(2*t0*t**2, t), Poly(1, t), DE) == (exp(2*exp(x)), 0, True) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0, t0), Poly(-t0*t, t)], + 'Tfuncs': [exp, Lambda(i, exp(-exp(i)))]}) + assert integrate_hyperexponential(Poly(-27*exp(9) - 162*t0*exp(9) + + 27*x*t0*exp(9), t), Poly((36*exp(18) + x**2*exp(18) - 12*x*exp(18))*t, t), DE) == \ + (27*exp(exp(x))/(-6*exp(9) + x*exp(9)), 0, True) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)], 'Tfuncs': [exp]}) + assert integrate_hyperexponential(Poly(x**2/2*t, t), Poly(1, t), DE) == \ + ((2 - 2*x + x**2)*exp(x)/2, 0, True) + assert integrate_hyperexponential(Poly(1 + t, t), Poly(t, t), DE) == \ + (-exp(-x), 1, True) # x - exp(-x) + assert integrate_hyperexponential(Poly(x, t), Poly(t + 1, t), DE) == \ + (0, NonElementaryIntegral(x/(1 + exp(x)), x), False) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t0), Poly(2*x*t1, t1)], + 'Tfuncs': [log, Lambda(i, exp(i**2))]}) + + elem, nonelem, b = integrate_hyperexponential(Poly((8*x**7 - 12*x**5 + 6*x**3 - x)*t1**4 + + (8*t0*x**7 - 8*t0*x**6 - 4*t0*x**5 + 2*t0*x**3 + 2*t0*x**2 - t0*x + + 24*x**8 - 36*x**6 - 4*x**5 + 22*x**4 + 4*x**3 - 7*x**2 - x + 1)*t1**3 + + (8*t0*x**8 - 4*t0*x**6 - 16*t0*x**5 - 2*t0*x**4 + 12*t0*x**3 + + t0*x**2 - 2*t0*x + 24*x**9 - 36*x**7 - 8*x**6 + 22*x**5 + 12*x**4 - + 7*x**3 - 6*x**2 + x + 1)*t1**2 + (8*t0*x**8 - 8*t0*x**6 - 16*t0*x**5 + + 6*t0*x**4 + 10*t0*x**3 - 2*t0*x**2 - t0*x + 8*x**10 - 12*x**8 - 4*x**7 + + 2*x**6 + 12*x**5 + 3*x**4 - 9*x**3 - x**2 + 2*x)*t1 + 8*t0*x**7 - + 12*t0*x**6 - 4*t0*x**5 + 8*t0*x**4 - t0*x**2 - 4*x**7 + 4*x**6 + + 4*x**5 - 4*x**4 - x**3 + x**2, t1), Poly((8*x**7 - 12*x**5 + 6*x**3 - + x)*t1**4 + (24*x**8 + 8*x**7 - 36*x**6 - 12*x**5 + 18*x**4 + 6*x**3 - + 3*x**2 - x)*t1**3 + (24*x**9 + 24*x**8 - 36*x**7 - 36*x**6 + 18*x**5 + + 18*x**4 - 3*x**3 - 3*x**2)*t1**2 + (8*x**10 + 24*x**9 - 12*x**8 - + 36*x**7 + 6*x**6 + 18*x**5 - x**4 - 3*x**3)*t1 + 8*x**10 - 12*x**8 + + 6*x**6 - x**4, t1), DE) + + assert factor(elem) == -((x - 1)*log(x)/((x + exp(x**2))*(2*x**2 - 1))) + assert (nonelem, b) == (NonElementaryIntegral(exp(x**2)/(exp(x**2) + 1), x), False) + +def test_integrate_hyperexponential_polynomial(): + # Without proper cancellation within integrate_hyperexponential_polynomial(), + # this will take a long time to complete, and will return a complicated + # expression + p = Poly((-28*x**11*t0 - 6*x**8*t0 + 6*x**9*t0 - 15*x**8*t0**2 + + 15*x**7*t0**2 + 84*x**10*t0**2 - 140*x**9*t0**3 - 20*x**6*t0**3 + + 20*x**7*t0**3 - 15*x**6*t0**4 + 15*x**5*t0**4 + 140*x**8*t0**4 - + 84*x**7*t0**5 - 6*x**4*t0**5 + 6*x**5*t0**5 + x**3*t0**6 - x**4*t0**6 + + 28*x**6*t0**6 - 4*x**5*t0**7 + x**9 - x**10 + 4*x**12)/(-8*x**11*t0 + + 28*x**10*t0**2 - 56*x**9*t0**3 + 70*x**8*t0**4 - 56*x**7*t0**5 + + 28*x**6*t0**6 - 8*x**5*t0**7 + x**4*t0**8 + x**12)*t1**2 + + (-28*x**11*t0 - 12*x**8*t0 + 12*x**9*t0 - 30*x**8*t0**2 + + 30*x**7*t0**2 + 84*x**10*t0**2 - 140*x**9*t0**3 - 40*x**6*t0**3 + + 40*x**7*t0**3 - 30*x**6*t0**4 + 30*x**5*t0**4 + 140*x**8*t0**4 - + 84*x**7*t0**5 - 12*x**4*t0**5 + 12*x**5*t0**5 - 2*x**4*t0**6 + + 2*x**3*t0**6 + 28*x**6*t0**6 - 4*x**5*t0**7 + 2*x**9 - 2*x**10 + + 4*x**12)/(-8*x**11*t0 + 28*x**10*t0**2 - 56*x**9*t0**3 + + 70*x**8*t0**4 - 56*x**7*t0**5 + 28*x**6*t0**6 - 8*x**5*t0**7 + + x**4*t0**8 + x**12)*t1 + (-2*x**2*t0 + 2*x**3*t0 + x*t0**2 - + x**2*t0**2 + x**3 - x**4)/(-4*x**5*t0 + 6*x**4*t0**2 - 4*x**3*t0**3 + + x**2*t0**4 + x**6), t1, z, expand=False) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t0), Poly(2*x*t1, t1)]}) + assert integrate_hyperexponential_polynomial(p, DE, z) == ( + Poly((x - t0)*t1**2 + (-2*t0 + 2*x)*t1, t1), Poly(-2*x*t0 + x**2 + + t0**2, t1), True) + + DE = DifferentialExtension(extension={'D':[Poly(1, x), Poly(t0, t0)]}) + assert integrate_hyperexponential_polynomial(Poly(0, t0), DE, z) == ( + Poly(0, t0), Poly(1, t0), True) + + +def test_integrate_hyperexponential_returns_piecewise(): + a, b = symbols('a b') + DE = DifferentialExtension(a**x, x) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + (exp(x*log(a))/log(a), Ne(log(a), 0)), (x, True)), 0, True) + DE = DifferentialExtension(a**(b*x), x) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + (exp(b*x*log(a))/(b*log(a)), Ne(b*log(a), 0)), (x, True)), 0, True) + DE = DifferentialExtension(exp(a*x), x) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + (exp(a*x)/a, Ne(a, 0)), (x, True)), 0, True) + DE = DifferentialExtension(x*exp(a*x), x) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + ((a*x - 1)*exp(a*x)/a**2, Ne(a**2, 0)), (x**2/2, True)), 0, True) + DE = DifferentialExtension(x**2*exp(a*x), x) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + ((x**2*a**2 - 2*a*x + 2)*exp(a*x)/a**3, Ne(a**3, 0)), + (x**3/3, True)), 0, True) + DE = DifferentialExtension(x**y + z, y) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + (exp(log(x)*y)/log(x), Ne(log(x), 0)), (y, True)), z, True) + DE = DifferentialExtension(x**y + z + x**(2*y), y) + assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise( + ((exp(2*log(x)*y)*log(x) + + 2*exp(log(x)*y)*log(x))/(2*log(x)**2), Ne(2*log(x)**2, 0)), + (2*y, True), + ), z, True) + # TODO: Add a test where two different parts of the extension use a + # Piecewise, like y**x + z**x. + + +def test_issue_13947(): + a, t, s = symbols('a t s') + assert risch_integrate(2**(-pi)/(2**t + 1), t) == \ + 2**(-pi)*t - 2**(-pi)*log(2**t + 1)/log(2) + assert risch_integrate(a**(t - s)/(a**t + 1), t) == \ + exp(-s*log(a))*log(a**t + 1)/log(a) + + +def test_integrate_primitive(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)], + 'Tfuncs': [log]}) + assert integrate_primitive(Poly(t, t), Poly(1, t), DE) == (x*log(x), -1, True) + assert integrate_primitive(Poly(x, t), Poly(t, t), DE) == (0, NonElementaryIntegral(x/log(x), x), False) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(1/(x + 1), t2)], + 'Tfuncs': [log, Lambda(i, log(i + 1))]}) + assert integrate_primitive(Poly(t1, t2), Poly(t2, t2), DE) == \ + (0, NonElementaryIntegral(log(x)/log(1 + x), x), False) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(1/(x*t1), t2)], + 'Tfuncs': [log, Lambda(i, log(log(i)))]}) + assert integrate_primitive(Poly(t2, t2), Poly(t1, t2), DE) == \ + (0, NonElementaryIntegral(log(log(x))/log(x), x), False) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t0)], + 'Tfuncs': [log]}) + assert integrate_primitive(Poly(x**2*t0**3 + (3*x**2 + x)*t0**2 + (3*x**2 + + 2*x)*t0 + x**2 + x, t0), Poly(x**2*t0**4 + 4*x**2*t0**3 + 6*x**2*t0**2 + + 4*x**2*t0 + x**2, t0), DE) == \ + (-1/(log(x) + 1), NonElementaryIntegral(1/(log(x) + 1), x), False) + +def test_integrate_hypertangent_polynomial(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]}) + assert integrate_hypertangent_polynomial(Poly(t**2 + x*t + 1, t), DE) == \ + (Poly(t, t), Poly(x/2, t)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(a*(t**2 + 1), t)]}) + assert integrate_hypertangent_polynomial(Poly(t**5, t), DE) == \ + (Poly(1/(4*a)*t**4 - 1/(2*a)*t**2, t), Poly(1/(2*a), t)) + + +def test_integrate_nonlinear_no_specials(): + a, d, = Poly(x**2*t**5 + x*t**4 - nu**2*t**3 - x*(x**2 + 1)*t**2 - (x**2 - + nu**2)*t - x**5/4, t), Poly(x**2*t**4 + x**2*(x**2 + 2)*t**2 + x**2 + x**4 + x**6/4, t) + # f(x) == phi_nu(x), the logarithmic derivative of J_v, the Bessel function, + # which has no specials (see Chapter 5, note 4 of Bronstein's book). + f = Function('phi_nu') + DE = DifferentialExtension(extension={'D': [Poly(1, x), + Poly(-t**2 - t/x - (1 - nu**2/x**2), t)], 'Tfuncs': [f]}) + assert integrate_nonlinear_no_specials(a, d, DE) == \ + (-log(1 + f(x)**2 + x**2/2)/2 + (- 4 - x**2)/(4 + 2*x**2 + 4*f(x)**2), True) + assert integrate_nonlinear_no_specials(Poly(t, t), Poly(1, t), DE) == \ + (0, False) + + +def test_integer_powers(): + assert integer_powers([x, x/2, x**2 + 1, x*Rational(2, 3)]) == [ + (x/6, [(x, 6), (x/2, 3), (x*Rational(2, 3), 4)]), + (1 + x**2, [(1 + x**2, 1)])] + + +def test_DifferentialExtension_exp(): + assert DifferentialExtension(exp(x) + exp(x**2), x)._important_attrs == \ + (Poly(t1 + t0, t1), Poly(1, t1), [Poly(1, x,), Poly(t0, t0), + Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i)), + Lambda(i, exp(i**2))], [], [None, 'exp', 'exp'], [None, x, x**2]) + assert DifferentialExtension(exp(x) + exp(2*x), x)._important_attrs == \ + (Poly(t0**2 + t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0, t0)], [x, t0], + [Lambda(i, exp(i))], [], [None, 'exp'], [None, x]) + assert DifferentialExtension(exp(x) + exp(x/2), x)._important_attrs == \ + (Poly(t0**2 + t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0/2, t0)], + [x, t0], [Lambda(i, exp(i/2))], [], [None, 'exp'], [None, x/2]) + assert DifferentialExtension(exp(x) + exp(x**2) + exp(x + x**2), x)._important_attrs == \ + (Poly((1 + t0)*t1 + t0, t1), Poly(1, t1), [Poly(1, x), Poly(t0, t0), + Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i)), + Lambda(i, exp(i**2))], [], [None, 'exp', 'exp'], [None, x, x**2]) + assert DifferentialExtension(exp(x) + exp(x**2) + exp(x + x**2 + 1), x)._important_attrs == \ + (Poly((1 + S.Exp1*t0)*t1 + t0, t1), Poly(1, t1), [Poly(1, x), + Poly(t0, t0), Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i)), + Lambda(i, exp(i**2))], [], [None, 'exp', 'exp'], [None, x, x**2]) + assert DifferentialExtension(exp(x) + exp(x**2) + exp(x/2 + x**2), x)._important_attrs == \ + (Poly((t0 + 1)*t1 + t0**2, t1), Poly(1, t1), [Poly(1, x), + Poly(t0/2, t0), Poly(2*x*t1, t1)], [x, t0, t1], + [Lambda(i, exp(i/2)), Lambda(i, exp(i**2))], + [(exp(x/2), sqrt(exp(x)))], [None, 'exp', 'exp'], [None, x/2, x**2]) + assert DifferentialExtension(exp(x) + exp(x**2) + exp(x/2 + x**2 + 3), x)._important_attrs == \ + (Poly((t0*exp(3) + 1)*t1 + t0**2, t1), Poly(1, t1), [Poly(1, x), + Poly(t0/2, t0), Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i/2)), + Lambda(i, exp(i**2))], [(exp(x/2), sqrt(exp(x)))], [None, 'exp', 'exp'], + [None, x/2, x**2]) + assert DifferentialExtension(sqrt(exp(x)), x)._important_attrs == \ + (Poly(t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0/2, t0)], [x, t0], + [Lambda(i, exp(i/2))], [(exp(x/2), sqrt(exp(x)))], [None, 'exp'], [None, x/2]) + + assert DifferentialExtension(exp(x/2), x)._important_attrs == \ + (Poly(t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0/2, t0)], [x, t0], + [Lambda(i, exp(i/2))], [], [None, 'exp'], [None, x/2]) + + +def test_DifferentialExtension_log(): + assert DifferentialExtension(log(x)*log(x + 1)*log(2*x**2 + 2*x), x)._important_attrs == \ + (Poly(t0*t1**2 + (t0*log(2) + t0**2)*t1, t1), Poly(1, t1), + [Poly(1, x), Poly(1/x, t0), + Poly(1/(x + 1), t1, expand=False)], [x, t0, t1], + [Lambda(i, log(i)), Lambda(i, log(i + 1))], [], [None, 'log', 'log'], + [None, x, x + 1]) + assert DifferentialExtension(x**x*log(x), x)._important_attrs == \ + (Poly(t0*t1, t1), Poly(1, t1), [Poly(1, x), Poly(1/x, t0), + Poly((1 + t0)*t1, t1)], [x, t0, t1], [Lambda(i, log(i)), + Lambda(i, exp(t0*i))], [(exp(x*log(x)), x**x)], [None, 'log', 'exp'], + [None, x, t0*x]) + + +def test_DifferentialExtension_symlog(): + # See comment on test_risch_integrate below + assert DifferentialExtension(log(x**x), x)._important_attrs == \ + (Poly(t0*x, t1), Poly(1, t1), [Poly(1, x), Poly(1/x, t0), Poly((t0 + + 1)*t1, t1)], [x, t0, t1], [Lambda(i, log(i)), Lambda(i, exp(i*t0))], + [(exp(x*log(x)), x**x)], [None, 'log', 'exp'], [None, x, t0*x]) + assert DifferentialExtension(log(x**y), x)._important_attrs == \ + (Poly(y*t0, t0), Poly(1, t0), [Poly(1, x), Poly(1/x, t0)], [x, t0], + [Lambda(i, log(i))], [(y*log(x), log(x**y))], [None, 'log'], + [None, x]) + assert DifferentialExtension(log(sqrt(x)), x)._important_attrs == \ + (Poly(t0, t0), Poly(2, t0), [Poly(1, x), Poly(1/x, t0)], [x, t0], + [Lambda(i, log(i))], [(log(x)/2, log(sqrt(x)))], [None, 'log'], + [None, x]) + + +def test_DifferentialExtension_handle_first(): + assert DifferentialExtension(exp(x)*log(x), x, handle_first='log')._important_attrs == \ + (Poly(t0*t1, t1), Poly(1, t1), [Poly(1, x), Poly(1/x, t0), + Poly(t1, t1)], [x, t0, t1], [Lambda(i, log(i)), Lambda(i, exp(i))], + [], [None, 'log', 'exp'], [None, x, x]) + assert DifferentialExtension(exp(x)*log(x), x, handle_first='exp')._important_attrs == \ + (Poly(t0*t1, t1), Poly(1, t1), [Poly(1, x), Poly(t0, t0), + Poly(1/x, t1)], [x, t0, t1], [Lambda(i, exp(i)), Lambda(i, log(i))], + [], [None, 'exp', 'log'], [None, x, x]) + + # This one must have the log first, regardless of what we set it to + # (because the log is inside of the exponential: x**x == exp(x*log(x))) + assert DifferentialExtension(-x**x*log(x)**2 + x**x - x**x/x, x, + handle_first='exp')._important_attrs == \ + DifferentialExtension(-x**x*log(x)**2 + x**x - x**x/x, x, + handle_first='log')._important_attrs == \ + (Poly((-1 + x - x*t0**2)*t1, t1), Poly(x, t1), + [Poly(1, x), Poly(1/x, t0), Poly((1 + t0)*t1, t1)], [x, t0, t1], + [Lambda(i, log(i)), Lambda(i, exp(t0*i))], [(exp(x*log(x)), x**x)], + [None, 'log', 'exp'], [None, x, t0*x]) + + +def test_DifferentialExtension_all_attrs(): + # Test 'unimportant' attributes + DE = DifferentialExtension(exp(x)*log(x), x, handle_first='exp') + assert DE.f == exp(x)*log(x) + assert DE.newf == t0*t1 + assert DE.x == x + assert DE.cases == ['base', 'exp', 'primitive'] + assert DE.case == 'primitive' + + assert DE.level == -1 + assert DE.t == t1 == DE.T[DE.level] + assert DE.d == Poly(1/x, t1) == DE.D[DE.level] + raises(ValueError, lambda: DE.increment_level()) + DE.decrement_level() + assert DE.level == -2 + assert DE.t == t0 == DE.T[DE.level] + assert DE.d == Poly(t0, t0) == DE.D[DE.level] + assert DE.case == 'exp' + DE.decrement_level() + assert DE.level == -3 + assert DE.t == x == DE.T[DE.level] == DE.x + assert DE.d == Poly(1, x) == DE.D[DE.level] + assert DE.case == 'base' + raises(ValueError, lambda: DE.decrement_level()) + DE.increment_level() + DE.increment_level() + assert DE.level == -1 + assert DE.t == t1 == DE.T[DE.level] + assert DE.d == Poly(1/x, t1) == DE.D[DE.level] + assert DE.case == 'primitive' + + # Test methods + assert DE.indices('log') == [2] + assert DE.indices('exp') == [1] + + +def test_DifferentialExtension_extension_flag(): + raises(ValueError, lambda: DifferentialExtension(extension={'T': [x, t]})) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert DE._important_attrs == (None, None, [Poly(1, x), Poly(t, t)], [x, t], + None, None, None, None) + assert DE.d == Poly(t, t) + assert DE.t == t + assert DE.level == -1 + assert DE.cases == ['base', 'exp'] + assert DE.x == x + assert DE.case == 'exp' + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)], + 'exts': [None, 'exp'], 'extargs': [None, x]}) + assert DE._important_attrs == (None, None, [Poly(1, x), Poly(t, t)], [x, t], + None, None, [None, 'exp'], [None, x]) + raises(ValueError, lambda: DifferentialExtension()) + + +def test_DifferentialExtension_misc(): + # Odd ends + assert DifferentialExtension(sin(y)*exp(x), x)._important_attrs == \ + (Poly(sin(y)*t0, t0, domain='ZZ[sin(y)]'), Poly(1, t0, domain='ZZ'), + [Poly(1, x, domain='ZZ'), Poly(t0, t0, domain='ZZ')], [x, t0], + [Lambda(i, exp(i))], [], [None, 'exp'], [None, x]) + raises(NotImplementedError, lambda: DifferentialExtension(sin(x), x)) + assert DifferentialExtension(10**x, x)._important_attrs == \ + (Poly(t0, t0), Poly(1, t0), [Poly(1, x), Poly(log(10)*t0, t0)], [x, t0], + [Lambda(i, exp(i*log(10)))], [(exp(x*log(10)), 10**x)], [None, 'exp'], + [None, x*log(10)]) + assert DifferentialExtension(log(x) + log(x**2), x)._important_attrs in [ + (Poly(3*t0, t0), Poly(2, t0), [Poly(1, x), Poly(2/x, t0)], [x, t0], + [Lambda(i, log(i**2))], [], [None, ], [], [1], [x**2]), + (Poly(3*t0, t0), Poly(1, t0), [Poly(1, x), Poly(1/x, t0)], [x, t0], + [Lambda(i, log(i))], [], [None, 'log'], [None, x])] + assert DifferentialExtension(S.Zero, x)._important_attrs == \ + (Poly(0, x), Poly(1, x), [Poly(1, x)], [x], [], [], [None], [None]) + assert DifferentialExtension(tan(atan(x).rewrite(log)), x)._important_attrs == \ + (Poly(x, x), Poly(1, x), [Poly(1, x)], [x], [], [], [None], [None]) + + +def test_DifferentialExtension_Rothstein(): + # Rothstein's integral + f = (2581284541*exp(x) + 1757211400)/(39916800*exp(3*x) + + 119750400*exp(x)**2 + 119750400*exp(x) + 39916800)*exp(1/(exp(x) + 1) - 10*x) + assert DifferentialExtension(f, x)._important_attrs == \ + (Poly((1757211400 + 2581284541*t0)*t1, t1), Poly(39916800 + + 119750400*t0 + 119750400*t0**2 + 39916800*t0**3, t1), + [Poly(1, x), Poly(t0, t0), Poly(-(10 + 21*t0 + 10*t0**2)/(1 + 2*t0 + + t0**2)*t1, t1, domain='ZZ(t0)')], [x, t0, t1], + [Lambda(i, exp(i)), Lambda(i, exp(1/(t0 + 1) - 10*i))], [], + [None, 'exp', 'exp'], [None, x, 1/(t0 + 1) - 10*x]) + + +class _TestingException(Exception): + """Dummy Exception class for testing.""" + pass + + +def test_DecrementLevel(): + DE = DifferentialExtension(x*log(exp(x) + 1), x) + assert DE.level == -1 + assert DE.t == t1 + assert DE.d == Poly(t0/(t0 + 1), t1) + assert DE.case == 'primitive' + + with DecrementLevel(DE): + assert DE.level == -2 + assert DE.t == t0 + assert DE.d == Poly(t0, t0) + assert DE.case == 'exp' + + with DecrementLevel(DE): + assert DE.level == -3 + assert DE.t == x + assert DE.d == Poly(1, x) + assert DE.case == 'base' + + assert DE.level == -2 + assert DE.t == t0 + assert DE.d == Poly(t0, t0) + assert DE.case == 'exp' + + assert DE.level == -1 + assert DE.t == t1 + assert DE.d == Poly(t0/(t0 + 1), t1) + assert DE.case == 'primitive' + + # Test that __exit__ is called after an exception correctly + try: + with DecrementLevel(DE): + raise _TestingException + except _TestingException: + pass + else: + raise AssertionError("Did not raise.") + + assert DE.level == -1 + assert DE.t == t1 + assert DE.d == Poly(t0/(t0 + 1), t1) + assert DE.case == 'primitive' + + +def test_risch_integrate(): + assert risch_integrate(t0*exp(x), x) == t0*exp(x) + assert risch_integrate(sin(x), x, rewrite_complex=True) == -exp(I*x)/2 - exp(-I*x)/2 + + # From my GSoC writeup + assert risch_integrate((1 + 2*x**2 + x**4 + 2*x**3*exp(2*x**2))/ + (x**4*exp(x**2) + 2*x**2*exp(x**2) + exp(x**2)), x) == \ + NonElementaryIntegral(exp(-x**2), x) + exp(x**2)/(1 + x**2) + + + assert risch_integrate(0, x) == 0 + + # also tests prde_cancel() + e1 = log(x/exp(x) + 1) + ans1 = risch_integrate(e1, x) + assert ans1 == (x*log(x*exp(-x) + 1) + NonElementaryIntegral((x**2 - x)/(x + exp(x)), x)) + assert cancel(diff(ans1, x) - e1) == 0 + + # also tests issue #10798 + e2 = (log(-1/y)/2 - log(1/y)/2)/y - (log(1 - 1/y)/2 - log(1 + 1/y)/2)/y + ans2 = risch_integrate(e2, y) + assert ans2 == log(1/y)*log(1 - 1/y)/2 - log(1/y)*log(1 + 1/y)/2 + \ + NonElementaryIntegral((I*pi*y**2 - 2*y*log(1/y) - I*pi)/(2*y**3 - 2*y), y) + assert expand_log(cancel(diff(ans2, y) - e2), force=True) == 0 + + # These are tested here in addition to in test_DifferentialExtension above + # (symlogs) to test that backsubs works correctly. The integrals should be + # written in terms of the original logarithms in the integrands. + + # XXX: Unfortunately, making backsubs work on this one is a little + # trickier, because x**x is converted to exp(x*log(x)), and so log(x**x) + # is converted to x*log(x). (x**2*log(x)).subs(x*log(x), log(x**x)) is + # smart enough, the issue is that these splits happen at different places + # in the algorithm. Maybe a heuristic is in order + assert risch_integrate(log(x**x), x) == x**2*log(x)/2 - x**2/4 + + assert risch_integrate(log(x**y), x) == x*log(x**y) - x*y + assert risch_integrate(log(sqrt(x)), x) == x*log(sqrt(x)) - x/2 + + +def test_risch_integrate_float(): + assert risch_integrate((-60*exp(x) - 19.2*exp(4*x))*exp(4*x), x) == -2.4*exp(8*x) - 12.0*exp(5*x) + + +def test_NonElementaryIntegral(): + assert isinstance(risch_integrate(exp(x**2), x), NonElementaryIntegral) + assert isinstance(risch_integrate(x**x*log(x), x), NonElementaryIntegral) + # Make sure methods of Integral still give back a NonElementaryIntegral + assert isinstance(NonElementaryIntegral(x**x*t0, x).subs(t0, log(x)), NonElementaryIntegral) + + +def test_xtothex(): + a = risch_integrate(x**x, x) + assert a == NonElementaryIntegral(x**x, x) + assert isinstance(a, NonElementaryIntegral) + + +def test_DifferentialExtension_equality(): + DE1 = DE2 = DifferentialExtension(log(x), x) + assert DE1 == DE2 + + +def test_DifferentialExtension_printing(): + DE = DifferentialExtension(exp(2*x**2) + log(exp(x**2) + 1), x) + assert repr(DE) == ("DifferentialExtension(dict([('f', exp(2*x**2) + log(exp(x**2) + 1)), " + "('x', x), ('T', [x, t0, t1]), ('D', [Poly(1, x, domain='ZZ'), Poly(2*x*t0, t0, domain='ZZ[x]'), " + "Poly(2*t0*x/(t0 + 1), t1, domain='ZZ(x,t0)')]), ('fa', Poly(t1 + t0**2, t1, domain='ZZ[t0]')), " + "('fd', Poly(1, t1, domain='ZZ')), ('Tfuncs', [Lambda(i, exp(i**2)), Lambda(i, log(t0 + 1))]), " + "('backsubs', []), ('exts', [None, 'exp', 'log']), ('extargs', [None, x**2, t0 + 1]), " + "('cases', ['base', 'exp', 'primitive']), ('case', 'primitive'), ('t', t1), " + "('d', Poly(2*t0*x/(t0 + 1), t1, domain='ZZ(x,t0)')), ('newf', t0**2 + t1), ('level', -1), " + "('dummy', False)]))") + + assert str(DE) == ("DifferentialExtension({fa=Poly(t1 + t0**2, t1, domain='ZZ[t0]'), " + "fd=Poly(1, t1, domain='ZZ'), D=[Poly(1, x, domain='ZZ'), Poly(2*x*t0, t0, domain='ZZ[x]'), " + "Poly(2*t0*x/(t0 + 1), t1, domain='ZZ(x,t0)')]})") + + +def test_issue_23948(): + f = ( + ( (-2*x**5 + 28*x**4 - 144*x**3 + 324*x**2 - 270*x)*log(x)**2 + +(-4*x**6 + 56*x**5 - 288*x**4 + 648*x**3 - 540*x**2)*log(x) + +(2*x**5 - 28*x**4 + 144*x**3 - 324*x**2 + 270*x)*exp(x) + +(2*x**5 - 28*x**4 + 144*x**3 - 324*x**2 + 270*x)*log(5) + -2*x**7 + 26*x**6 - 116*x**5 + 180*x**4 + 54*x**3 - 270*x**2 + )*log(-log(x)**2 - 2*x*log(x) + exp(x) + log(5) - x**2 - x)**2 + +( (4*x**5 - 44*x**4 + 168*x**3 - 216*x**2 - 108*x + 324)*log(x) + +(-2*x**5 + 24*x**4 - 108*x**3 + 216*x**2 - 162*x)*exp(x) + +4*x**6 - 42*x**5 + 144*x**4 - 108*x**3 - 324*x**2 + 486*x + )*log(-log(x)**2 - 2*x*log(x) + exp(x) + log(5) - x**2 - x) + )/(x*exp(x)**2*log(x)**2 + 2*x**2*exp(x)**2*log(x) - x*exp(x)**3 + +(-x*log(5) + x**3 + x**2)*exp(x)**2) + + F = ((x**4 - 12*x**3 + 54*x**2 - 108*x + 81)*exp(-2*x) + *log(-x**2 - 2*x*log(x) - x + exp(x) - log(x)**2 + log(5))**2) + + assert risch_integrate(f, x) == F diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_singularityfunctions.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_singularityfunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..587e5f104cbf095f851ec538601ca146377b51ae --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_singularityfunctions.py @@ -0,0 +1,22 @@ +from sympy.integrals.singularityfunctions import singularityintegrate +from sympy.core.function import Function +from sympy.core.symbol import symbols +from sympy.functions.special.singularity_functions import SingularityFunction + +x, a, n, y = symbols('x a n y') +f = Function('f') + + +def test_singularityintegrate(): + assert singularityintegrate(x, x) is None + assert singularityintegrate(x + SingularityFunction(x, 9, 1), x) is None + + assert 4*singularityintegrate(SingularityFunction(x, a, 3), x) == 4*SingularityFunction(x, a, 4)/4 + assert singularityintegrate(5*SingularityFunction(x, 5, -2), x) == 5*SingularityFunction(x, 5, -1) + assert singularityintegrate(6*SingularityFunction(x, 5, -1), x) == 6*SingularityFunction(x, 5, 0) + assert singularityintegrate(x*SingularityFunction(x, 0, -1), x) == 0 + assert singularityintegrate((x - 5)*SingularityFunction(x, 5, -1), x) == 0 + assert singularityintegrate(SingularityFunction(x, 0, -1) * f(x), x) == f(0) * SingularityFunction(x, 0, 0) + assert singularityintegrate(SingularityFunction(x, 1, -1) * f(x), x) == f(1) * SingularityFunction(x, 1, 0) + assert singularityintegrate(y*SingularityFunction(x, 0, -1)**2, x) == \ + y*SingularityFunction(0, 0, -1)*SingularityFunction(x, 0, 0) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_transforms.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..fdf7192594bad532c93ffb31e5b2f05cfd8970f1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_transforms.py @@ -0,0 +1,637 @@ +from sympy.integrals.transforms import ( + mellin_transform, inverse_mellin_transform, + fourier_transform, inverse_fourier_transform, + sine_transform, inverse_sine_transform, + cosine_transform, inverse_cosine_transform, + hankel_transform, inverse_hankel_transform, + FourierTransform, SineTransform, CosineTransform, InverseFourierTransform, + InverseSineTransform, InverseCosineTransform, IntegralTransformError) +from sympy.integrals.laplace import ( + laplace_transform, inverse_laplace_transform) +from sympy.core.function import Function, expand_mul +from sympy.core import EulerGamma +from sympy.core.numbers import I, Rational, oo, pi +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, symbols +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import re, unpolarify +from sympy.functions.elementary.exponential import exp, exp_polar, log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import atan, cos, sin, tan +from sympy.functions.special.bessel import besseli, besselj, besselk, bessely +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.error_functions import erf, expint +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import meijerg +from sympy.simplify.gammasimp import gammasimp +from sympy.simplify.hyperexpand import hyperexpand +from sympy.simplify.trigsimp import trigsimp +from sympy.testing.pytest import XFAIL, slow, skip, raises +from sympy.abc import x, s, a, b, c, d + + +nu, beta, rho = symbols('nu beta rho') + + +def test_undefined_function(): + from sympy.integrals.transforms import MellinTransform + f = Function('f') + assert mellin_transform(f(x), x, s) == MellinTransform(f(x), x, s) + assert mellin_transform(f(x) + exp(-x), x, s) == \ + (MellinTransform(f(x), x, s) + gamma(s + 1)/s, (0, oo), True) + + +def test_free_symbols(): + f = Function('f') + assert mellin_transform(f(x), x, s).free_symbols == {s} + assert mellin_transform(f(x)*a, x, s).free_symbols == {s, a} + + +def test_as_integral(): + from sympy.integrals.integrals import Integral + f = Function('f') + assert mellin_transform(f(x), x, s).rewrite('Integral') == \ + Integral(x**(s - 1)*f(x), (x, 0, oo)) + assert fourier_transform(f(x), x, s).rewrite('Integral') == \ + Integral(f(x)*exp(-2*I*pi*s*x), (x, -oo, oo)) + assert laplace_transform(f(x), x, s, noconds=True).rewrite('Integral') == \ + Integral(f(x)*exp(-s*x), (x, 0, oo)) + assert str(2*pi*I*inverse_mellin_transform(f(s), s, x, (a, b)).rewrite('Integral')) \ + == "Integral(f(s)/x**s, (s, _c - oo*I, _c + oo*I))" + assert str(2*pi*I*inverse_laplace_transform(f(s), s, x).rewrite('Integral')) == \ + "Integral(f(s)*exp(s*x), (s, _c - oo*I, _c + oo*I))" + assert inverse_fourier_transform(f(s), s, x).rewrite('Integral') == \ + Integral(f(s)*exp(2*I*pi*s*x), (s, -oo, oo)) + +# NOTE this is stuck in risch because meijerint cannot handle it + + +@slow +@XFAIL +def test_mellin_transform_fail(): + skip("Risch takes forever.") + + MT = mellin_transform + + bpos = symbols('b', positive=True) + # bneg = symbols('b', negative=True) + + expr = (sqrt(x + b**2) + b)**a/sqrt(x + b**2) + # TODO does not work with bneg, argument wrong. Needs changes to matching. + assert MT(expr.subs(b, -bpos), x, s) == \ + ((-1)**(a + 1)*2**(a + 2*s)*bpos**(a + 2*s - 1)*gamma(a + s) + *gamma(1 - a - 2*s)/gamma(1 - s), + (-re(a), -re(a)/2 + S.Half), True) + + expr = (sqrt(x + b**2) + b)**a + assert MT(expr.subs(b, -bpos), x, s) == \ + ( + 2**(a + 2*s)*a*bpos**(a + 2*s)*gamma(-a - 2* + s)*gamma(a + s)/gamma(-s + 1), + (-re(a), -re(a)/2), True) + + # Test exponent 1: + assert MT(expr.subs({b: -bpos, a: 1}), x, s) == \ + (-bpos**(2*s + 1)*gamma(s)*gamma(-s - S.Half)/(2*sqrt(pi)), + (-1, Rational(-1, 2)), True) + + +def test_mellin_transform(): + from sympy.functions.elementary.miscellaneous import (Max, Min) + MT = mellin_transform + + bpos = symbols('b', positive=True) + + # 8.4.2 + assert MT(x**nu*Heaviside(x - 1), x, s) == \ + (-1/(nu + s), (-oo, -re(nu)), True) + assert MT(x**nu*Heaviside(1 - x), x, s) == \ + (1/(nu + s), (-re(nu), oo), True) + + assert MT((1 - x)**(beta - 1)*Heaviside(1 - x), x, s) == \ + (gamma(beta)*gamma(s)/gamma(beta + s), (0, oo), re(beta) > 0) + assert MT((x - 1)**(beta - 1)*Heaviside(x - 1), x, s) == \ + (gamma(beta)*gamma(1 - beta - s)/gamma(1 - s), + (-oo, 1 - re(beta)), re(beta) > 0) + + assert MT((1 + x)**(-rho), x, s) == \ + (gamma(s)*gamma(rho - s)/gamma(rho), (0, re(rho)), True) + + assert MT(abs(1 - x)**(-rho), x, s) == ( + 2*sin(pi*rho/2)*gamma(1 - rho)* + cos(pi*(s - rho/2))*gamma(s)*gamma(rho-s)/pi, + (0, re(rho)), re(rho) < 1) + mt = MT((1 - x)**(beta - 1)*Heaviside(1 - x) + + a*(x - 1)**(beta - 1)*Heaviside(x - 1), x, s) + assert mt[1], mt[2] == ((0, -re(beta) + 1), re(beta) > 0) + + assert MT((x**a - b**a)/(x - b), x, s)[0] == \ + pi*b**(a + s - 1)*sin(pi*a)/(sin(pi*s)*sin(pi*(a + s))) + assert MT((x**a - bpos**a)/(x - bpos), x, s) == \ + (pi*bpos**(a + s - 1)*sin(pi*a)/(sin(pi*s)*sin(pi*(a + s))), + (Max(0, -re(a)), Min(1, 1 - re(a))), True) + + expr = (sqrt(x + b**2) + b)**a + assert MT(expr.subs(b, bpos), x, s) == \ + (-a*(2*bpos)**(a + 2*s)*gamma(s)*gamma(-a - 2*s)/gamma(-a - s + 1), + (0, -re(a)/2), True) + + expr = (sqrt(x + b**2) + b)**a/sqrt(x + b**2) + assert MT(expr.subs(b, bpos), x, s) == \ + (2**(a + 2*s)*bpos**(a + 2*s - 1)*gamma(s) + *gamma(1 - a - 2*s)/gamma(1 - a - s), + (0, -re(a)/2 + S.Half), True) + + # 8.4.2 + assert MT(exp(-x), x, s) == (gamma(s), (0, oo), True) + assert MT(exp(-1/x), x, s) == (gamma(-s), (-oo, 0), True) + + # 8.4.5 + assert MT(log(x)**4*Heaviside(1 - x), x, s) == (24/s**5, (0, oo), True) + assert MT(log(x)**3*Heaviside(x - 1), x, s) == (6/s**4, (-oo, 0), True) + assert MT(log(x + 1), x, s) == (pi/(s*sin(pi*s)), (-1, 0), True) + assert MT(log(1/x + 1), x, s) == (pi/(s*sin(pi*s)), (0, 1), True) + assert MT(log(abs(1 - x)), x, s) == (pi/(s*tan(pi*s)), (-1, 0), True) + assert MT(log(abs(1 - 1/x)), x, s) == (pi/(s*tan(pi*s)), (0, 1), True) + + # 8.4.14 + assert MT(erf(sqrt(x)), x, s) == \ + (-gamma(s + S.Half)/(sqrt(pi)*s), (Rational(-1, 2), 0), True) + + +def test_mellin_transform2(): + MT = mellin_transform + # TODO we cannot currently do these (needs summation of 3F2(-1)) + # this also implies that they cannot be written as a single g-function + # (although this is possible) + mt = MT(log(x)/(x + 1), x, s) + assert mt[1:] == ((0, 1), True) + assert not hyperexpand(mt[0], allow_hyper=True).has(meijerg) + mt = MT(log(x)**2/(x + 1), x, s) + assert mt[1:] == ((0, 1), True) + assert not hyperexpand(mt[0], allow_hyper=True).has(meijerg) + mt = MT(log(x)/(x + 1)**2, x, s) + assert mt[1:] == ((0, 2), True) + assert not hyperexpand(mt[0], allow_hyper=True).has(meijerg) + + +@slow +def test_mellin_transform_bessel(): + from sympy.functions.elementary.miscellaneous import Max + MT = mellin_transform + + # 8.4.19 + assert MT(besselj(a, 2*sqrt(x)), x, s) == \ + (gamma(a/2 + s)/gamma(a/2 - s + 1), (-re(a)/2, Rational(3, 4)), True) + assert MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s) == \ + (2**a*gamma(-2*s + S.Half)*gamma(a/2 + s + S.Half)/( + gamma(-a/2 - s + 1)*gamma(a - 2*s + 1)), ( + -re(a)/2 - S.Half, Rational(1, 4)), True) + assert MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s) == \ + (2**a*gamma(a/2 + s)*gamma(-2*s + S.Half)/( + gamma(-a/2 - s + S.Half)*gamma(a - 2*s + 1)), ( + -re(a)/2, Rational(1, 4)), True) + assert MT(besselj(a, sqrt(x))**2, x, s) == \ + (gamma(a + s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - s)*gamma(1 + a - s)), + (-re(a), S.Half), True) + assert MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s) == \ + (gamma(s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - a - s)*gamma(1 + a - s)), + (0, S.Half), True) + # NOTE: prudnikov gives the strip below as (1/2 - re(a), 1). As far as + # I can see this is wrong (since besselj(z) ~ 1/sqrt(z) for z large) + assert MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s) == \ + (gamma(1 - s)*gamma(a + s - S.Half) + / (sqrt(pi)*gamma(Rational(3, 2) - s)*gamma(a - s + S.Half)), + (S.Half - re(a), S.Half), True) + assert MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s) == \ + (4**s*gamma(1 - 2*s)*gamma((a + b)/2 + s) + / (gamma(1 - s + (b - a)/2)*gamma(1 - s + (a - b)/2) + *gamma( 1 - s + (a + b)/2)), + (-(re(a) + re(b))/2, S.Half), True) + assert MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)[1:] == \ + ((Max(re(a), -re(a)), S.Half), True) + + # Section 8.4.20 + assert MT(bessely(a, 2*sqrt(x)), x, s) == \ + (-cos(pi*(a/2 - s))*gamma(s - a/2)*gamma(s + a/2)/pi, + (Max(-re(a)/2, re(a)/2), Rational(3, 4)), True) + assert MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s) == \ + (-4**s*sin(pi*(a/2 - s))*gamma(S.Half - 2*s) + * gamma((1 - a)/2 + s)*gamma((1 + a)/2 + s) + / (sqrt(pi)*gamma(1 - s - a/2)*gamma(1 - s + a/2)), + (Max(-(re(a) + 1)/2, (re(a) - 1)/2), Rational(1, 4)), True) + assert MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s) == \ + (-4**s*cos(pi*(a/2 - s))*gamma(s - a/2)*gamma(s + a/2)*gamma(S.Half - 2*s) + / (sqrt(pi)*gamma(S.Half - s - a/2)*gamma(S.Half - s + a/2)), + (Max(-re(a)/2, re(a)/2), Rational(1, 4)), True) + assert MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s) == \ + (-cos(pi*s)*gamma(s)*gamma(a + s)*gamma(S.Half - s) + / (pi**S('3/2')*gamma(1 + a - s)), + (Max(-re(a), 0), S.Half), True) + assert MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s) == \ + (-4**s*cos(pi*(a/2 - b/2 + s))*gamma(1 - 2*s) + * gamma(a/2 - b/2 + s)*gamma(a/2 + b/2 + s) + / (pi*gamma(a/2 - b/2 - s + 1)*gamma(a/2 + b/2 - s + 1)), + (Max((-re(a) + re(b))/2, (-re(a) - re(b))/2), S.Half), True) + # NOTE bessely(a, sqrt(x))**2 and bessely(a, sqrt(x))*bessely(b, sqrt(x)) + # are a mess (no matter what way you look at it ...) + assert MT(bessely(a, sqrt(x))**2, x, s)[1:] == \ + ((Max(-re(a), 0, re(a)), S.Half), True) + + # Section 8.4.22 + # TODO we can't do any of these (delicate cancellation) + + # Section 8.4.23 + assert MT(besselk(a, 2*sqrt(x)), x, s) == \ + (gamma( + s - a/2)*gamma(s + a/2)/2, (Max(-re(a)/2, re(a)/2), oo), True) + assert MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk( + a, 2*sqrt(2*sqrt(x))), x, s) == (4**(-s)*gamma(2*s)* + gamma(a/2 + s)/(2*gamma(a/2 - s + 1)), (Max(0, -re(a)/2), oo), True) + # TODO bessely(a, x)*besselk(a, x) is a mess + assert MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s) == \ + (gamma(s)*gamma( + a + s)*gamma(-s + S.Half)/(2*sqrt(pi)*gamma(a - s + 1)), + (Max(-re(a), 0), S.Half), True) + assert MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s) == \ + (2**(2*s - 1)*gamma(-2*s + 1)*gamma(-a/2 + b/2 + s)* \ + gamma(a/2 + b/2 + s)/(gamma(-a/2 + b/2 - s + 1)* \ + gamma(a/2 + b/2 - s + 1)), (Max(-re(a)/2 - re(b)/2, \ + re(a)/2 - re(b)/2), S.Half), True) + + # TODO products of besselk are a mess + + mt = MT(exp(-x/2)*besselk(a, x/2), x, s) + mt0 = gammasimp(trigsimp(gammasimp(mt[0].expand(func=True)))) + assert mt0 == 2*pi**Rational(3, 2)*cos(pi*s)*gamma(S.Half - s)/( + (cos(2*pi*a) - cos(2*pi*s))*gamma(-a - s + 1)*gamma(a - s + 1)) + assert mt[1:] == ((Max(-re(a), re(a)), oo), True) + # TODO exp(x/2)*besselk(a, x/2) [etc] cannot currently be done + # TODO various strange products of special orders + + +@slow +def test_expint(): + from sympy.functions.elementary.miscellaneous import Max + from sympy.functions.special.error_functions import Ci, E1, Si + from sympy.simplify.simplify import simplify + + aneg = Symbol('a', negative=True) + u = Symbol('u', polar=True) + + assert mellin_transform(E1(x), x, s) == (gamma(s)/s, (0, oo), True) + assert inverse_mellin_transform(gamma(s)/s, s, x, + (0, oo)).rewrite(expint).expand() == E1(x) + assert mellin_transform(expint(a, x), x, s) == \ + (gamma(s)/(a + s - 1), (Max(1 - re(a), 0), oo), True) + # XXX IMT has hickups with complicated strips ... + assert simplify(unpolarify( + inverse_mellin_transform(gamma(s)/(aneg + s - 1), s, x, + (1 - aneg, oo)).rewrite(expint).expand(func=True))) == \ + expint(aneg, x) + + assert mellin_transform(Si(x), x, s) == \ + (-2**s*sqrt(pi)*gamma(s/2 + S.Half)/( + 2*s*gamma(-s/2 + 1)), (-1, 0), True) + assert inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2) + /(2*s*gamma(-s/2 + 1)), s, x, (-1, 0)) \ + == Si(x) + + assert mellin_transform(Ci(sqrt(x)), x, s) == \ + (-2**(2*s - 1)*sqrt(pi)*gamma(s)/(s*gamma(-s + S.Half)), (0, 1), True) + assert inverse_mellin_transform( + -4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S.Half)), + s, u, (0, 1)).expand() == Ci(sqrt(u)) + + +@slow +def test_inverse_mellin_transform(): + from sympy.core.function import expand + from sympy.functions.elementary.miscellaneous import (Max, Min) + from sympy.functions.elementary.trigonometric import cot + from sympy.simplify.powsimp import powsimp + from sympy.simplify.simplify import simplify + IMT = inverse_mellin_transform + + assert IMT(gamma(s), s, x, (0, oo)) == exp(-x) + assert IMT(gamma(-s), s, x, (-oo, 0)) == exp(-1/x) + assert simplify(IMT(s/(2*s**2 - 2), s, x, (2, oo))) == \ + (x**2 + 1)*Heaviside(1 - x)/(4*x) + + # test passing "None" + assert IMT(1/(s**2 - 1), s, x, (-1, None)) == \ + -x*Heaviside(-x + 1)/2 - Heaviside(x - 1)/(2*x) + assert IMT(1/(s**2 - 1), s, x, (None, 1)) == \ + -x*Heaviside(-x + 1)/2 - Heaviside(x - 1)/(2*x) + + # test expansion of sums + assert IMT(gamma(s) + gamma(s - 1), s, x, (1, oo)) == (x + 1)*exp(-x)/x + + # test factorisation of polys + r = symbols('r', real=True) + assert IMT(1/(s**2 + 1), s, exp(-x), (None, oo) + ).subs(x, r).rewrite(sin).simplify() \ + == sin(r)*Heaviside(1 - exp(-r)) + + # test multiplicative substitution + _a, _b = symbols('a b', positive=True) + assert IMT(_b**(-s/_a)*factorial(s/_a)/s, s, x, (0, oo)) == exp(-_b*x**_a) + assert IMT(factorial(_a/_b + s/_b)/(_a + s), s, x, (-_a, oo)) == x**_a*exp(-x**_b) + + def simp_pows(expr): + return simplify(powsimp(expand_mul(expr, deep=False), force=True)).replace(exp_polar, exp) + + # Now test the inverses of all direct transforms tested above + + # Section 8.4.2 + nu = symbols('nu', real=True) + assert IMT(-1/(nu + s), s, x, (-oo, None)) == x**nu*Heaviside(x - 1) + assert IMT(1/(nu + s), s, x, (None, oo)) == x**nu*Heaviside(1 - x) + assert simp_pows(IMT(gamma(beta)*gamma(s)/gamma(s + beta), s, x, (0, oo))) \ + == (1 - x)**(beta - 1)*Heaviside(1 - x) + assert simp_pows(IMT(gamma(beta)*gamma(1 - beta - s)/gamma(1 - s), + s, x, (-oo, None))) \ + == (x - 1)**(beta - 1)*Heaviside(x - 1) + assert simp_pows(IMT(gamma(s)*gamma(rho - s)/gamma(rho), s, x, (0, None))) \ + == (1/(x + 1))**rho + expr = IMT(d**c*d**(s - 1)*sin(pi*c) + *gamma(s)*gamma(s + c)*gamma(1 - s)*gamma(1 - s - c)/pi, + s, x, (Max(-re(c), 0), Min(1 - re(c), 1))) + assert powsimp(expand_mul(expr, deep=False)).replace(exp_polar, exp).simplify() \ + == (-d**c + x**c)/(-d + x) + + assert simplify(IMT(1/sqrt(pi)*(-c/2)*gamma(s)*gamma((1 - c)/2 - s) + *gamma(-c/2 - s)/gamma(1 - c - s), + s, x, (0, -re(c)/2))) == \ + (1 + sqrt(x + 1))**c + assert simplify(IMT(2**(a + 2*s)*b**(a + 2*s - 1)*gamma(s)*gamma(1 - a - 2*s) + /gamma(1 - a - s), s, x, (0, (-re(a) + 1)/2))) == \ + b**(a - 1)*(b**2*(sqrt(1 + x/b**2) + 1)**a + x*(sqrt(1 + x/b**2) + 1 + )**(a - 1))/(b**2 + x) + assert simplify(IMT(-2**(c + 2*s)*c*b**(c + 2*s)*gamma(s)*gamma(-c - 2*s) + / gamma(-c - s + 1), s, x, (0, -re(c)/2))) == \ + b**c*(sqrt(1 + x/b**2) + 1)**c + + # Section 8.4.5 + assert IMT(24/s**5, s, x, (0, oo)) == log(x)**4*Heaviside(1 - x) + assert expand(IMT(6/s**4, s, x, (-oo, 0)), force=True) == \ + log(x)**3*Heaviside(x - 1) + assert IMT(pi/(s*sin(pi*s)), s, x, (-1, 0)) == log(x + 1) + assert IMT(pi/(s*sin(pi*s/2)), s, x, (-2, 0)) == log(x**2 + 1) + assert IMT(pi/(s*sin(2*pi*s)), s, x, (Rational(-1, 2), 0)) == log(sqrt(x) + 1) + assert IMT(pi/(s*sin(pi*s)), s, x, (0, 1)) == log(1 + 1/x) + + # TODO + def mysimp(expr): + from sympy.core.function import expand + from sympy.simplify.powsimp import powsimp + from sympy.simplify.simplify import logcombine + return expand( + powsimp(logcombine(expr, force=True), force=True, deep=True), + force=True).replace(exp_polar, exp) + + assert mysimp(mysimp(IMT(pi/(s*tan(pi*s)), s, x, (-1, 0)))) in [ + log(1 - x)*Heaviside(1 - x) + log(x - 1)*Heaviside(x - 1), + log(x)*Heaviside(x - 1) + log(1 - 1/x)*Heaviside(x - 1) + log(-x + + 1)*Heaviside(-x + 1)] + # test passing cot + assert mysimp(IMT(pi*cot(pi*s)/s, s, x, (0, 1))) in [ + log(1/x - 1)*Heaviside(1 - x) + log(1 - 1/x)*Heaviside(x - 1), + -log(x)*Heaviside(-x + 1) + log(1 - 1/x)*Heaviside(x - 1) + log(-x + + 1)*Heaviside(-x + 1), ] + + # 8.4.14 + assert IMT(-gamma(s + S.Half)/(sqrt(pi)*s), s, x, (Rational(-1, 2), 0)) == \ + erf(sqrt(x)) + + # 8.4.19 + assert simplify(IMT(gamma(a/2 + s)/gamma(a/2 - s + 1), s, x, (-re(a)/2, Rational(3, 4)))) \ + == besselj(a, 2*sqrt(x)) + assert simplify(IMT(2**a*gamma(S.Half - 2*s)*gamma(s + (a + 1)/2) + / (gamma(1 - s - a/2)*gamma(1 - 2*s + a)), + s, x, (-(re(a) + 1)/2, Rational(1, 4)))) == \ + sin(sqrt(x))*besselj(a, sqrt(x)) + assert simplify(IMT(2**a*gamma(a/2 + s)*gamma(S.Half - 2*s) + / (gamma(S.Half - s - a/2)*gamma(1 - 2*s + a)), + s, x, (-re(a)/2, Rational(1, 4)))) == \ + cos(sqrt(x))*besselj(a, sqrt(x)) + # TODO this comes out as an amazing mess, but simplifies nicely + assert simplify(IMT(gamma(a + s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - s)*gamma(1 + a - s)), + s, x, (-re(a), S.Half))) == \ + besselj(a, sqrt(x))**2 + assert simplify(IMT(gamma(s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - s - a)*gamma(1 + a - s)), + s, x, (0, S.Half))) == \ + besselj(-a, sqrt(x))*besselj(a, sqrt(x)) + assert simplify(IMT(4**s*gamma(-2*s + 1)*gamma(a/2 + b/2 + s) + / (gamma(-a/2 + b/2 - s + 1)*gamma(a/2 - b/2 - s + 1) + *gamma(a/2 + b/2 - s + 1)), + s, x, (-(re(a) + re(b))/2, S.Half))) == \ + besselj(a, sqrt(x))*besselj(b, sqrt(x)) + + # Section 8.4.20 + # TODO this can be further simplified! + assert simplify(IMT(-2**(2*s)*cos(pi*a/2 - pi*b/2 + pi*s)*gamma(-2*s + 1) * + gamma(a/2 - b/2 + s)*gamma(a/2 + b/2 + s) / + (pi*gamma(a/2 - b/2 - s + 1)*gamma(a/2 + b/2 - s + 1)), + s, x, + (Max(-re(a)/2 - re(b)/2, -re(a)/2 + re(b)/2), S.Half))) == \ + besselj(a, sqrt(x))*-(besselj(-b, sqrt(x)) - + besselj(b, sqrt(x))*cos(pi*b))/sin(pi*b) + # TODO more + + # for coverage + + assert IMT(pi/cos(pi*s), s, x, (0, S.Half)) == sqrt(x)/(x + 1) + + +def test_fourier_transform(): + from sympy.core.function import (expand, expand_complex, expand_trig) + from sympy.polys.polytools import factor + from sympy.simplify.simplify import simplify + FT = fourier_transform + IFT = inverse_fourier_transform + + def simp(x): + return simplify(expand_trig(expand_complex(expand(x)))) + + def sinc(x): + return sin(pi*x)/(pi*x) + k = symbols('k', real=True) + f = Function("f") + + # TODO for this to work with real a, need to expand abs(a*x) to abs(a)*abs(x) + a = symbols('a', positive=True) + b = symbols('b', positive=True) + + posk = symbols('posk', positive=True) + + # Test unevaluated form + assert fourier_transform(f(x), x, k) == FourierTransform(f(x), x, k) + assert inverse_fourier_transform( + f(k), k, x) == InverseFourierTransform(f(k), k, x) + + # basic examples from wikipedia + assert simp(FT(Heaviside(1 - abs(2*a*x)), x, k)) == sinc(k/a)/a + # TODO IFT is a *mess* + assert simp(FT(Heaviside(1 - abs(a*x))*(1 - abs(a*x)), x, k)) == sinc(k/a)**2/a + # TODO IFT + + assert factor(FT(exp(-a*x)*Heaviside(x), x, k), extension=I) == \ + 1/(a + 2*pi*I*k) + # NOTE: the ift comes out in pieces + assert IFT(1/(a + 2*pi*I*x), x, posk, + noconds=False) == (exp(-a*posk), True) + assert IFT(1/(a + 2*pi*I*x), x, -posk, + noconds=False) == (0, True) + assert IFT(1/(a + 2*pi*I*x), x, symbols('k', negative=True), + noconds=False) == (0, True) + # TODO IFT without factoring comes out as meijer g + + assert factor(FT(x*exp(-a*x)*Heaviside(x), x, k), extension=I) == \ + 1/(a + 2*pi*I*k)**2 + assert FT(exp(-a*x)*sin(b*x)*Heaviside(x), x, k) == \ + b/(b**2 + (a + 2*I*pi*k)**2) + + assert FT(exp(-a*x**2), x, k) == sqrt(pi)*exp(-pi**2*k**2/a)/sqrt(a) + assert IFT(sqrt(pi/a)*exp(-(pi*k)**2/a), k, x) == exp(-a*x**2) + assert FT(exp(-a*abs(x)), x, k) == 2*a/(a**2 + 4*pi**2*k**2) + # TODO IFT (comes out as meijer G) + + # TODO besselj(n, x), n an integer > 0 actually can be done... + + # TODO are there other common transforms (no distributions!)? + + +def test_sine_transform(): + t = symbols("t") + w = symbols("w") + a = symbols("a") + f = Function("f") + + # Test unevaluated form + assert sine_transform(f(t), t, w) == SineTransform(f(t), t, w) + assert inverse_sine_transform( + f(w), w, t) == InverseSineTransform(f(w), w, t) + + assert sine_transform(1/sqrt(t), t, w) == 1/sqrt(w) + assert inverse_sine_transform(1/sqrt(w), w, t) == 1/sqrt(t) + + assert sine_transform((1/sqrt(t))**3, t, w) == 2*sqrt(w) + + assert sine_transform(t**(-a), t, w) == 2**( + -a + S.Half)*w**(a - 1)*gamma(-a/2 + 1)/gamma((a + 1)/2) + assert inverse_sine_transform(2**(-a + S( + 1)/2)*w**(a - 1)*gamma(-a/2 + 1)/gamma(a/2 + S.Half), w, t) == t**(-a) + + assert sine_transform( + exp(-a*t), t, w) == sqrt(2)*w/(sqrt(pi)*(a**2 + w**2)) + assert inverse_sine_transform( + sqrt(2)*w/(sqrt(pi)*(a**2 + w**2)), w, t) == exp(-a*t) + + assert sine_transform( + log(t)/t, t, w) == sqrt(2)*sqrt(pi)*-(log(w**2) + 2*EulerGamma)/4 + + assert sine_transform( + t*exp(-a*t**2), t, w) == sqrt(2)*w*exp(-w**2/(4*a))/(4*a**Rational(3, 2)) + assert inverse_sine_transform( + sqrt(2)*w*exp(-w**2/(4*a))/(4*a**Rational(3, 2)), w, t) == t*exp(-a*t**2) + + +def test_cosine_transform(): + from sympy.functions.special.error_functions import (Ci, Si) + + t = symbols("t") + w = symbols("w") + a = symbols("a") + f = Function("f") + + # Test unevaluated form + assert cosine_transform(f(t), t, w) == CosineTransform(f(t), t, w) + assert inverse_cosine_transform( + f(w), w, t) == InverseCosineTransform(f(w), w, t) + + assert cosine_transform(1/sqrt(t), t, w) == 1/sqrt(w) + assert inverse_cosine_transform(1/sqrt(w), w, t) == 1/sqrt(t) + + assert cosine_transform(1/( + a**2 + t**2), t, w) == sqrt(2)*sqrt(pi)*exp(-a*w)/(2*a) + + assert cosine_transform(t**( + -a), t, w) == 2**(-a + S.Half)*w**(a - 1)*gamma((-a + 1)/2)/gamma(a/2) + assert inverse_cosine_transform(2**(-a + S( + 1)/2)*w**(a - 1)*gamma(-a/2 + S.Half)/gamma(a/2), w, t) == t**(-a) + + assert cosine_transform( + exp(-a*t), t, w) == sqrt(2)*a/(sqrt(pi)*(a**2 + w**2)) + assert inverse_cosine_transform( + sqrt(2)*a/(sqrt(pi)*(a**2 + w**2)), w, t) == exp(-a*t) + + assert cosine_transform(exp(-a*sqrt(t))*cos(a*sqrt( + t)), t, w) == a*exp(-a**2/(2*w))/(2*w**Rational(3, 2)) + + assert cosine_transform(1/(a + t), t, w) == sqrt(2)*( + (-2*Si(a*w) + pi)*sin(a*w)/2 - cos(a*w)*Ci(a*w))/sqrt(pi) + assert inverse_cosine_transform(sqrt(2)*meijerg(((S.Half, 0), ()), ( + (S.Half, 0, 0), (S.Half,)), a**2*w**2/4)/(2*pi), w, t) == 1/(a + t) + + assert cosine_transform(1/sqrt(a**2 + t**2), t, w) == sqrt(2)*meijerg( + ((S.Half,), ()), ((0, 0), (S.Half,)), a**2*w**2/4)/(2*sqrt(pi)) + assert inverse_cosine_transform(sqrt(2)*meijerg(((S.Half,), ()), ((0, 0), (S.Half,)), a**2*w**2/4)/(2*sqrt(pi)), w, t) == 1/(t*sqrt(a**2/t**2 + 1)) + + +def test_hankel_transform(): + r = Symbol("r") + k = Symbol("k") + nu = Symbol("nu") + m = Symbol("m") + a = symbols("a") + + assert hankel_transform(1/r, r, k, 0) == 1/k + assert inverse_hankel_transform(1/k, k, r, 0) == 1/r + + assert hankel_transform( + 1/r**m, r, k, 0) == 2**(-m + 1)*k**(m - 2)*gamma(-m/2 + 1)/gamma(m/2) + assert inverse_hankel_transform( + 2**(-m + 1)*k**(m - 2)*gamma(-m/2 + 1)/gamma(m/2), k, r, 0) == r**(-m) + + assert hankel_transform(1/r**m, r, k, nu) == ( + 2*2**(-m)*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/gamma(m/2 + nu/2)) + assert inverse_hankel_transform(2**(-m + 1)*k**( + m - 2)*gamma(-m/2 + nu/2 + 1)/gamma(m/2 + nu/2), k, r, nu) == r**(-m) + + assert hankel_transform(r**nu*exp(-a*r), r, k, nu) == \ + 2**(nu + 1)*a*k**(-nu - 3)*(a**2/k**2 + 1)**(-nu - S( + 3)/2)*gamma(nu + Rational(3, 2))/sqrt(pi) + assert inverse_hankel_transform( + 2**(nu + 1)*a*k**(-nu - 3)*(a**2/k**2 + 1)**(-nu - Rational(3, 2))*gamma( + nu + Rational(3, 2))/sqrt(pi), k, r, nu) == r**nu*exp(-a*r) + + +def test_issue_7181(): + assert mellin_transform(1/(1 - x), x, s) != None + + +def test_issue_8882(): + # This is the original test. + # from sympy import diff, Integral, integrate + # r = Symbol('r') + # psi = 1/r*sin(r)*exp(-(a0*r)) + # h = -1/2*diff(psi, r, r) - 1/r*psi + # f = 4*pi*psi*h*r**2 + # assert integrate(f, (r, -oo, 3), meijerg=True).has(Integral) == True + + # To save time, only the critical part is included. + F = -a**(-s + 1)*(4 + 1/a**2)**(-s/2)*sqrt(1/a**2)*exp(-s*I*pi)* \ + sin(s*atan(sqrt(1/a**2)/2))*gamma(s) + raises(IntegralTransformError, lambda: + inverse_mellin_transform(F, s, x, (-1, oo), + **{'as_meijerg': True, 'needeval': True})) + + +def test_issue_12591(): + x, y = symbols("x y", real=True) + assert fourier_transform(exp(x), x, y) == FourierTransform(exp(x), x, y) diff --git a/phivenv/Lib/site-packages/sympy/integrals/tests/test_trigonometry.py b/phivenv/Lib/site-packages/sympy/integrals/tests/test_trigonometry.py new file mode 100644 index 0000000000000000000000000000000000000000..857c8503c5aa690d66e9cdab49730b4ea655a52c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/tests/test_trigonometry.py @@ -0,0 +1,98 @@ +from sympy.core import Ne, Rational, Symbol +from sympy.functions import sin, cos, tan, csc, sec, cot, log, Piecewise +from sympy.integrals.trigonometry import trigintegrate + +x = Symbol('x') + + +def test_trigintegrate_odd(): + assert trigintegrate(Rational(1), x) == x + assert trigintegrate(x, x) is None + assert trigintegrate(x**2, x) is None + + assert trigintegrate(sin(x), x) == -cos(x) + assert trigintegrate(cos(x), x) == sin(x) + + assert trigintegrate(sin(3*x), x) == -cos(3*x)/3 + assert trigintegrate(cos(3*x), x) == sin(3*x)/3 + + y = Symbol('y') + assert trigintegrate(sin(y*x), x) == Piecewise( + (-cos(y*x)/y, Ne(y, 0)), (0, True)) + assert trigintegrate(cos(y*x), x) == Piecewise( + (sin(y*x)/y, Ne(y, 0)), (x, True)) + assert trigintegrate(sin(y*x)**2, x) == Piecewise( + ((x*y/2 - sin(x*y)*cos(x*y)/2)/y, Ne(y, 0)), (0, True)) + assert trigintegrate(sin(y*x)*cos(y*x), x) == Piecewise( + (sin(x*y)**2/(2*y), Ne(y, 0)), (0, True)) + assert trigintegrate(cos(y*x)**2, x) == Piecewise( + ((x*y/2 + sin(x*y)*cos(x*y)/2)/y, Ne(y, 0)), (x, True)) + + y = Symbol('y', positive=True) + # TODO: remove conds='none' below. For this to work we would have to rule + # out (e.g. by trying solve) the condition y = 0, incompatible with + # y.is_positive being True. + assert trigintegrate(sin(y*x), x, conds='none') == -cos(y*x)/y + assert trigintegrate(cos(y*x), x, conds='none') == sin(y*x)/y + + assert trigintegrate(sin(x)*cos(x), x) == sin(x)**2/2 + assert trigintegrate(sin(x)*cos(x)**2, x) == -cos(x)**3/3 + assert trigintegrate(sin(x)**2*cos(x), x) == sin(x)**3/3 + + # check if it selects right function to substitute, + # so the result is kept simple + assert trigintegrate(sin(x)**7 * cos(x), x) == sin(x)**8/8 + assert trigintegrate(sin(x) * cos(x)**7, x) == -cos(x)**8/8 + + assert trigintegrate(sin(x)**7 * cos(x)**3, x) == \ + -sin(x)**10/10 + sin(x)**8/8 + assert trigintegrate(sin(x)**3 * cos(x)**7, x) == \ + cos(x)**10/10 - cos(x)**8/8 + + # both n, m are odd and -ve, and not necessarily equal + assert trigintegrate(sin(x)**-1*cos(x)**-1, x) == \ + -log(sin(x)**2 - 1)/2 + log(sin(x)) + + +def test_trigintegrate_even(): + assert trigintegrate(sin(x)**2, x) == x/2 - cos(x)*sin(x)/2 + assert trigintegrate(cos(x)**2, x) == x/2 + cos(x)*sin(x)/2 + + assert trigintegrate(sin(3*x)**2, x) == x/2 - cos(3*x)*sin(3*x)/6 + assert trigintegrate(cos(3*x)**2, x) == x/2 + cos(3*x)*sin(3*x)/6 + assert trigintegrate(sin(x)**2 * cos(x)**2, x) == \ + x/8 - sin(2*x)*cos(2*x)/16 + + assert trigintegrate(sin(x)**4 * cos(x)**2, x) == \ + x/16 - sin(x) *cos(x)/16 - sin(x)**3*cos(x)/24 + \ + sin(x)**5*cos(x)/6 + + assert trigintegrate(sin(x)**2 * cos(x)**4, x) == \ + x/16 + cos(x) *sin(x)/16 + cos(x)**3*sin(x)/24 - \ + cos(x)**5*sin(x)/6 + + assert trigintegrate(sin(x)**(-4), x) == -2*cos(x)/(3*sin(x)) \ + - cos(x)/(3*sin(x)**3) + + assert trigintegrate(cos(x)**(-6), x) == sin(x)/(5*cos(x)**5) \ + + 4*sin(x)/(15*cos(x)**3) + 8*sin(x)/(15*cos(x)) + + +def test_trigintegrate_mixed(): + assert trigintegrate(sin(x)*sec(x), x) == -log(cos(x)) + assert trigintegrate(sin(x)*csc(x), x) == x + assert trigintegrate(sin(x)*cot(x), x) == sin(x) + + assert trigintegrate(cos(x)*sec(x), x) == x + assert trigintegrate(cos(x)*csc(x), x) == log(sin(x)) + assert trigintegrate(cos(x)*tan(x), x) == -cos(x) + assert trigintegrate(cos(x)*cot(x), x) == log(cos(x) - 1)/2 \ + - log(cos(x) + 1)/2 + cos(x) + assert trigintegrate(cot(x)*cos(x)**2, x) == log(sin(x)) - sin(x)**2/2 + + +def test_trigintegrate_symbolic(): + n = Symbol('n', integer=True) + assert trigintegrate(cos(x)**n, x) is None + assert trigintegrate(sin(x)**n, x) is None + assert trigintegrate(cot(x)**n, x) is None diff --git a/phivenv/Lib/site-packages/sympy/integrals/transforms.py b/phivenv/Lib/site-packages/sympy/integrals/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..19dbd990e4f0320e76707a1a2a8b1601fcd8a3df --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/transforms.py @@ -0,0 +1,1590 @@ +""" Integral Transforms """ +from functools import reduce, wraps +from itertools import repeat +from sympy.core import S, pi +from sympy.core.add import Add +from sympy.core.function import ( + AppliedUndef, count_ops, expand, expand_mul, Function) +from sympy.core.mul import Mul +from sympy.core.intfunc import igcd, ilcm +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Dummy +from sympy.core.traversal import postorder_traversal +from sympy.functions.combinatorial.factorials import factorial, rf +from sympy.functions.elementary.complexes import re, arg, Abs +from sympy.functions.elementary.exponential import exp, exp_polar +from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, tanh +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.miscellaneous import Max, Min, sqrt +from sympy.functions.elementary.piecewise import piecewise_fold +from sympy.functions.elementary.trigonometric import cos, cot, sin, tan +from sympy.functions.special.bessel import besselj +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import meijerg +from sympy.integrals import integrate, Integral +from sympy.integrals.meijerint import _dummy +from sympy.logic.boolalg import to_cnf, conjuncts, disjuncts, Or, And +from sympy.polys.polyroots import roots +from sympy.polys.polytools import factor, Poly +from sympy.polys.rootoftools import CRootOf +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import debug + + +########################################################################## +# Helpers / Utilities +########################################################################## + + +class IntegralTransformError(NotImplementedError): + """ + Exception raised in relation to problems computing transforms. + + Explanation + =========== + + This class is mostly used internally; if integrals cannot be computed + objects representing unevaluated transforms are usually returned. + + The hint ``needeval=True`` can be used to disable returning transform + objects, and instead raise this exception if an integral cannot be + computed. + """ + def __init__(self, transform, function, msg): + super().__init__( + "%s Transform could not be computed: %s." % (transform, msg)) + self.function = function + + +class IntegralTransform(Function): + """ + Base class for integral transforms. + + Explanation + =========== + + This class represents unevaluated transforms. + + To implement a concrete transform, derive from this class and implement + the ``_compute_transform(f, x, s, **hints)`` and ``_as_integral(f, x, s)`` + functions. If the transform cannot be computed, raise :obj:`IntegralTransformError`. + + Also set ``cls._name``. For instance, + + >>> from sympy import LaplaceTransform + >>> LaplaceTransform._name + 'Laplace' + + Implement ``self._collapse_extra`` if your function returns more than just a + number and possibly a convergence condition. + """ + + @property + def function(self): + """ The function to be transformed. """ + return self.args[0] + + @property + def function_variable(self): + """ The dependent variable of the function to be transformed. """ + return self.args[1] + + @property + def transform_variable(self): + """ The independent transform variable. """ + return self.args[2] + + @property + def free_symbols(self): + """ + This method returns the symbols that will exist when the transform + is evaluated. + """ + return self.function.free_symbols.union({self.transform_variable}) \ + - {self.function_variable} + + def _compute_transform(self, f, x, s, **hints): + raise NotImplementedError + + def _as_integral(self, f, x, s): + raise NotImplementedError + + def _collapse_extra(self, extra): + cond = And(*extra) + if cond == False: + raise IntegralTransformError(self.__class__.name, None, '') + return cond + + def _try_directly(self, **hints): + T = None + try_directly = not any(func.has(self.function_variable) + for func in self.function.atoms(AppliedUndef)) + if try_directly: + try: + T = self._compute_transform(self.function, + self.function_variable, self.transform_variable, **hints) + except IntegralTransformError: + debug('[IT _try ] Caught IntegralTransformError, returns None') + T = None + + fn = self.function + if not fn.is_Add: + fn = expand_mul(fn) + return fn, T + + def doit(self, **hints): + """ + Try to evaluate the transform in closed form. + + Explanation + =========== + + This general function handles linearity, but apart from that leaves + pretty much everything to _compute_transform. + + Standard hints are the following: + + - ``simplify``: whether or not to simplify the result + - ``noconds``: if True, do not return convergence conditions + - ``needeval``: if True, raise IntegralTransformError instead of + returning IntegralTransform objects + + The default values of these hints depend on the concrete transform, + usually the default is + ``(simplify, noconds, needeval) = (True, False, False)``. + """ + needeval = hints.pop('needeval', False) + simplify = hints.pop('simplify', True) + hints['simplify'] = simplify + + fn, T = self._try_directly(**hints) + + if T is not None: + return T + + if fn.is_Add: + hints['needeval'] = needeval + res = [self.__class__(*([x] + list(self.args[1:]))).doit(**hints) + for x in fn.args] + extra = [] + ress = [] + for x in res: + if not isinstance(x, tuple): + x = [x] + ress.append(x[0]) + if len(x) == 2: + # only a condition + extra.append(x[1]) + elif len(x) > 2: + # some region parameters and a condition (Mellin, Laplace) + extra += [x[1:]] + if simplify==True: + res = Add(*ress).simplify() + else: + res = Add(*ress) + if not extra: + return res + try: + extra = self._collapse_extra(extra) + if iterable(extra): + return (res,) + tuple(extra) + else: + return (res, extra) + except IntegralTransformError: + pass + + if needeval: + raise IntegralTransformError( + self.__class__._name, self.function, 'needeval') + + # TODO handle derivatives etc + + # pull out constant coefficients + coeff, rest = fn.as_coeff_mul(self.function_variable) + return coeff*self.__class__(*([Mul(*rest)] + list(self.args[1:]))) + + @property + def as_integral(self): + return self._as_integral(self.function, self.function_variable, + self.transform_variable) + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + return self.as_integral + + +def _simplify(expr, doit): + if doit: + from sympy.simplify import simplify + from sympy.simplify.powsimp import powdenest + return simplify(powdenest(piecewise_fold(expr), polar=True)) + return expr + + +def _noconds_(default): + """ + This is a decorator generator for dropping convergence conditions. + + Explanation + =========== + + Suppose you define a function ``transform(*args)`` which returns a tuple of + the form ``(result, cond1, cond2, ...)``. + + Decorating it ``@_noconds_(default)`` will add a new keyword argument + ``noconds`` to it. If ``noconds=True``, the return value will be altered to + be only ``result``, whereas if ``noconds=False`` the return value will not + be altered. + + The default value of the ``noconds`` keyword will be ``default`` (i.e. the + argument of this function). + """ + def make_wrapper(func): + @wraps(func) + def wrapper(*args, noconds=default, **kwargs): + res = func(*args, **kwargs) + if noconds: + return res[0] + return res + return wrapper + return make_wrapper +_noconds = _noconds_(False) + + +########################################################################## +# Mellin Transform +########################################################################## + +def _default_integrator(f, x): + return integrate(f, (x, S.Zero, S.Infinity)) + + +@_noconds +def _mellin_transform(f, x, s_, integrator=_default_integrator, simplify=True): + """ Backend function to compute Mellin transforms. """ + # We use a fresh dummy, because assumptions on s might drop conditions on + # convergence of the integral. + s = _dummy('s', 'mellin-transform', f) + F = integrator(x**(s - 1) * f, x) + + if not F.has(Integral): + return _simplify(F.subs(s, s_), simplify), (S.NegativeInfinity, S.Infinity), S.true + + if not F.is_Piecewise: # XXX can this work if integration gives continuous result now? + raise IntegralTransformError('Mellin', f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError( + 'Mellin', f, 'integral in unexpected form') + + def process_conds(cond): + """ + Turn ``cond`` into a strip (a, b), and auxiliary conditions. + """ + from sympy.solvers.inequalities import _solve_inequality + a = S.NegativeInfinity + b = S.Infinity + aux = S.true + conds = conjuncts(to_cnf(cond)) + t = Dummy('t', real=True) + for c in conds: + a_ = S.Infinity + b_ = S.NegativeInfinity + aux_ = [] + for d in disjuncts(c): + d_ = d.replace( + re, lambda x: x.as_real_imag()[0]).subs(re(s), t) + if not d.is_Relational or \ + d.rel_op in ('==', '!=') \ + or d_.has(s) or not d_.has(t): + aux_ += [d] + continue + soln = _solve_inequality(d_, t) + if not soln.is_Relational or \ + soln.rel_op in ('==', '!='): + aux_ += [d] + continue + if soln.lts == t: + b_ = Max(soln.gts, b_) + else: + a_ = Min(soln.lts, a_) + if a_ is not S.Infinity and a_ != b: + a = Max(a_, a) + elif b_ is not S.NegativeInfinity and b_ != a: + b = Min(b_, b) + else: + aux = And(aux, Or(*aux_)) + return a, b, aux + + conds = [process_conds(c) for c in disjuncts(cond)] + conds = [x for x in conds if x[2] != False] + conds.sort(key=lambda x: (x[0] - x[1], count_ops(x[2]))) + + if not conds: + raise IntegralTransformError('Mellin', f, 'no convergence found') + + a, b, aux = conds[0] + return _simplify(F.subs(s, s_), simplify), (a, b), aux + + +class MellinTransform(IntegralTransform): + """ + Class representing unevaluated Mellin transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Mellin transforms, see the :func:`mellin_transform` + docstring. + """ + + _name = 'Mellin' + + def _compute_transform(self, f, x, s, **hints): + return _mellin_transform(f, x, s, **hints) + + def _as_integral(self, f, x, s): + return Integral(f*x**(s - 1), (x, S.Zero, S.Infinity)) + + def _collapse_extra(self, extra): + a = [] + b = [] + cond = [] + for (sa, sb), c in extra: + a += [sa] + b += [sb] + cond += [c] + res = (Max(*a), Min(*b)), And(*cond) + if (res[0][0] >= res[0][1]) == True or res[1] == False: + raise IntegralTransformError( + 'Mellin', None, 'no combined convergence.') + return res + + +def mellin_transform(f, x, s, **hints): + r""" + Compute the Mellin transform `F(s)` of `f(x)`, + + .. math :: F(s) = \int_0^\infty x^{s-1} f(x) \mathrm{d}x. + + For all "sensible" functions, this converges absolutely in a strip + `a < \operatorname{Re}(s) < b`. + + Explanation + =========== + + The Mellin transform is related via change of variables to the Fourier + transform, and also to the (bilateral) Laplace transform. + + This function returns ``(F, (a, b), cond)`` + where ``F`` is the Mellin transform of ``f``, ``(a, b)`` is the fundamental strip + (as above), and ``cond`` are auxiliary convergence conditions. + + If the integral cannot be computed in closed form, this function returns + an unevaluated :class:`MellinTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. If ``noconds=False``, + then only `F` will be returned (i.e. not ``cond``, and also not the strip + ``(a, b)``). + + Examples + ======== + + >>> from sympy import mellin_transform, exp + >>> from sympy.abc import x, s + >>> mellin_transform(exp(-x), x, s) + (gamma(s), (0, oo), True) + + See Also + ======== + + inverse_mellin_transform, laplace_transform, fourier_transform + hankel_transform, inverse_hankel_transform + """ + return MellinTransform(f, x, s).doit(**hints) + + +def _rewrite_sin(m_n, s, a, b): + """ + Re-write the sine function ``sin(m*s + n)`` as gamma functions, compatible + with the strip (a, b). + + Return ``(gamma1, gamma2, fac)`` so that ``f == fac/(gamma1 * gamma2)``. + + Examples + ======== + + >>> from sympy.integrals.transforms import _rewrite_sin + >>> from sympy import pi, S + >>> from sympy.abc import s + >>> _rewrite_sin((pi, 0), s, 0, 1) + (gamma(s), gamma(1 - s), pi) + >>> _rewrite_sin((pi, 0), s, 1, 0) + (gamma(s - 1), gamma(2 - s), -pi) + >>> _rewrite_sin((pi, 0), s, -1, 0) + (gamma(s + 1), gamma(-s), -pi) + >>> _rewrite_sin((pi, pi/2), s, S(1)/2, S(3)/2) + (gamma(s - 1/2), gamma(3/2 - s), -pi) + >>> _rewrite_sin((pi, pi), s, 0, 1) + (gamma(s), gamma(1 - s), -pi) + >>> _rewrite_sin((2*pi, 0), s, 0, S(1)/2) + (gamma(2*s), gamma(1 - 2*s), pi) + >>> _rewrite_sin((2*pi, 0), s, S(1)/2, 1) + (gamma(2*s - 1), gamma(2 - 2*s), -pi) + """ + # (This is a separate function because it is moderately complicated, + # and I want to doctest it.) + # We want to use pi/sin(pi*x) = gamma(x)*gamma(1-x). + # But there is one complication: the gamma functions determine the + # integration contour in the definition of the G-function. Usually + # it would not matter if this is slightly shifted, unless this way + # we create an undefined function! + # So we try to write this in such a way that the gammas are + # eminently on the right side of the strip. + m, n = m_n + + m = expand_mul(m/pi) + n = expand_mul(n/pi) + r = ceiling(-m*a - n.as_real_imag()[0]) # Don't use re(n), does not expand + return gamma(m*s + n + r), gamma(1 - n - r - m*s), (-1)**r*pi + + +class MellinTransformStripError(ValueError): + """ + Exception raised by _rewrite_gamma. Mainly for internal use. + """ + pass + + +def _rewrite_gamma(f, s, a, b): + """ + Try to rewrite the product f(s) as a product of gamma functions, + so that the inverse Mellin transform of f can be expressed as a meijer + G function. + + Explanation + =========== + + Return (an, ap), (bm, bq), arg, exp, fac such that + G((an, ap), (bm, bq), arg/z**exp)*fac is the inverse Mellin transform of f(s). + + Raises IntegralTransformError or MellinTransformStripError on failure. + + It is asserted that f has no poles in the fundamental strip designated by + (a, b). One of a and b is allowed to be None. The fundamental strip is + important, because it determines the inversion contour. + + This function can handle exponentials, linear factors, trigonometric + functions. + + This is a helper function for inverse_mellin_transform that will not + attempt any transformations on f. + + Examples + ======== + + >>> from sympy.integrals.transforms import _rewrite_gamma + >>> from sympy.abc import s + >>> from sympy import oo + >>> _rewrite_gamma(s*(s+3)*(s-1), s, -oo, oo) + (([], [-3, 0, 1]), ([-2, 1, 2], []), 1, 1, -1) + >>> _rewrite_gamma((s-1)**2, s, -oo, oo) + (([], [1, 1]), ([2, 2], []), 1, 1, 1) + + Importance of the fundamental strip: + + >>> _rewrite_gamma(1/s, s, 0, oo) + (([1], []), ([], [0]), 1, 1, 1) + >>> _rewrite_gamma(1/s, s, None, oo) + (([1], []), ([], [0]), 1, 1, 1) + >>> _rewrite_gamma(1/s, s, 0, None) + (([1], []), ([], [0]), 1, 1, 1) + >>> _rewrite_gamma(1/s, s, -oo, 0) + (([], [1]), ([0], []), 1, 1, -1) + >>> _rewrite_gamma(1/s, s, None, 0) + (([], [1]), ([0], []), 1, 1, -1) + >>> _rewrite_gamma(1/s, s, -oo, None) + (([], [1]), ([0], []), 1, 1, -1) + + >>> _rewrite_gamma(2**(-s+3), s, -oo, oo) + (([], []), ([], []), 1/2, 1, 8) + """ + # Our strategy will be as follows: + # 1) Guess a constant c such that the inversion integral should be + # performed wrt s'=c*s (instead of plain s). Write s for s'. + # 2) Process all factors, rewrite them independently as gamma functions in + # argument s, or exponentials of s. + # 3) Try to transform all gamma functions s.t. they have argument + # a+s or a-s. + # 4) Check that the resulting G function parameters are valid. + # 5) Combine all the exponentials. + + a_, b_ = S([a, b]) + + def left(c, is_numer): + """ + Decide whether pole at c lies to the left of the fundamental strip. + """ + # heuristically, this is the best chance for us to solve the inequalities + c = expand(re(c)) + if a_ is None and b_ is S.Infinity: + return True + if a_ is None: + return c < b_ + if b_ is None: + return c <= a_ + if (c >= b_) == True: + return False + if (c <= a_) == True: + return True + if is_numer: + return None + if a_.free_symbols or b_.free_symbols or c.free_symbols: + return None # XXX + #raise IntegralTransformError('Inverse Mellin', f, + # 'Could not determine position of singularity %s' + # ' relative to fundamental strip' % c) + raise MellinTransformStripError('Pole inside critical strip?') + + # 1) + s_multipliers = [] + for g in f.atoms(gamma): + if not g.has(s): + continue + arg = g.args[0] + if arg.is_Add: + arg = arg.as_independent(s)[1] + coeff, _ = arg.as_coeff_mul(s) + s_multipliers += [coeff] + for g in f.atoms(sin, cos, tan, cot): + if not g.has(s): + continue + arg = g.args[0] + if arg.is_Add: + arg = arg.as_independent(s)[1] + coeff, _ = arg.as_coeff_mul(s) + s_multipliers += [coeff/pi] + s_multipliers = [Abs(x) if x.is_extended_real else x for x in s_multipliers] + common_coefficient = S.One + for x in s_multipliers: + if not x.is_Rational: + common_coefficient = x + break + s_multipliers = [x/common_coefficient for x in s_multipliers] + if not (all(x.is_Rational for x in s_multipliers) and + common_coefficient.is_extended_real): + raise IntegralTransformError("Gamma", None, "Nonrational multiplier") + s_multiplier = common_coefficient/reduce(ilcm, [S(x.q) + for x in s_multipliers], S.One) + if s_multiplier == common_coefficient: + if len(s_multipliers) == 0: + s_multiplier = common_coefficient + else: + s_multiplier = common_coefficient \ + *reduce(igcd, [S(x.p) for x in s_multipliers]) + + f = f.subs(s, s/s_multiplier) + fac = S.One/s_multiplier + exponent = S.One/s_multiplier + if a_ is not None: + a_ *= s_multiplier + if b_ is not None: + b_ *= s_multiplier + + # 2) + numer, denom = f.as_numer_denom() + numer = Mul.make_args(numer) + denom = Mul.make_args(denom) + args = list(zip(numer, repeat(True))) + list(zip(denom, repeat(False))) + + facs = [] + dfacs = [] + # *_gammas will contain pairs (a, c) representing Gamma(a*s + c) + numer_gammas = [] + denom_gammas = [] + # exponentials will contain bases for exponentials of s + exponentials = [] + + def exception(fact): + return IntegralTransformError("Inverse Mellin", f, "Unrecognised form '%s'." % fact) + while args: + fact, is_numer = args.pop() + if is_numer: + ugammas, lgammas = numer_gammas, denom_gammas + ufacs = facs + else: + ugammas, lgammas = denom_gammas, numer_gammas + ufacs = dfacs + + def linear_arg(arg): + """ Test if arg is of form a*s+b, raise exception if not. """ + if not arg.is_polynomial(s): + raise exception(fact) + p = Poly(arg, s) + if p.degree() != 1: + raise exception(fact) + return p.all_coeffs() + + # constants + if not fact.has(s): + ufacs += [fact] + # exponentials + elif fact.is_Pow or isinstance(fact, exp): + if fact.is_Pow: + base = fact.base + exp_ = fact.exp + else: + base = exp_polar(1) + exp_ = fact.exp + if exp_.is_Integer: + cond = is_numer + if exp_ < 0: + cond = not cond + args += [(base, cond)]*Abs(exp_) + continue + elif not base.has(s): + a, b = linear_arg(exp_) + if not is_numer: + base = 1/base + exponentials += [base**a] + facs += [base**b] + else: + raise exception(fact) + # linear factors + elif fact.is_polynomial(s): + p = Poly(fact, s) + if p.degree() != 1: + # We completely factor the poly. For this we need the roots. + # Now roots() only works in some cases (low degree), and CRootOf + # only works without parameters. So try both... + coeff = p.LT()[1] + rs = roots(p, s) + if len(rs) != p.degree(): + rs = CRootOf.all_roots(p) + ufacs += [coeff] + args += [(s - c, is_numer) for c in rs] + continue + a, c = p.all_coeffs() + ufacs += [a] + c /= -a + # Now need to convert s - c + if left(c, is_numer): + ugammas += [(S.One, -c + 1)] + lgammas += [(S.One, -c)] + else: + ufacs += [-1] + ugammas += [(S.NegativeOne, c + 1)] + lgammas += [(S.NegativeOne, c)] + elif isinstance(fact, gamma): + a, b = linear_arg(fact.args[0]) + if is_numer: + if (a > 0 and (left(-b/a, is_numer) == False)) or \ + (a < 0 and (left(-b/a, is_numer) == True)): + raise NotImplementedError( + 'Gammas partially over the strip.') + ugammas += [(a, b)] + elif isinstance(fact, sin): + # We try to re-write all trigs as gammas. This is not in + # general the best strategy, since sometimes this is impossible, + # but rewriting as exponentials would work. However trig functions + # in inverse mellin transforms usually all come from simplifying + # gamma terms, so this should work. + a = fact.args[0] + if is_numer: + # No problem with the poles. + gamma1, gamma2, fac_ = gamma(a/pi), gamma(1 - a/pi), pi + else: + gamma1, gamma2, fac_ = _rewrite_sin(linear_arg(a), s, a_, b_) + args += [(gamma1, not is_numer), (gamma2, not is_numer)] + ufacs += [fac_] + elif isinstance(fact, tan): + a = fact.args[0] + args += [(sin(a, evaluate=False), is_numer), + (sin(pi/2 - a, evaluate=False), not is_numer)] + elif isinstance(fact, cos): + a = fact.args[0] + args += [(sin(pi/2 - a, evaluate=False), is_numer)] + elif isinstance(fact, cot): + a = fact.args[0] + args += [(sin(pi/2 - a, evaluate=False), is_numer), + (sin(a, evaluate=False), not is_numer)] + else: + raise exception(fact) + + fac *= Mul(*facs)/Mul(*dfacs) + + # 3) + an, ap, bm, bq = [], [], [], [] + for gammas, plus, minus, is_numer in [(numer_gammas, an, bm, True), + (denom_gammas, bq, ap, False)]: + while gammas: + a, c = gammas.pop() + if a != -1 and a != +1: + # We use the gamma function multiplication theorem. + p = Abs(S(a)) + newa = a/p + newc = c/p + if not a.is_Integer: + raise TypeError("a is not an integer") + for k in range(p): + gammas += [(newa, newc + k/p)] + if is_numer: + fac *= (2*pi)**((1 - p)/2) * p**(c - S.Half) + exponentials += [p**a] + else: + fac /= (2*pi)**((1 - p)/2) * p**(c - S.Half) + exponentials += [p**(-a)] + continue + if a == +1: + plus.append(1 - c) + else: + minus.append(c) + + # 4) + # TODO + + # 5) + arg = Mul(*exponentials) + + # for testability, sort the arguments + an.sort(key=default_sort_key) + ap.sort(key=default_sort_key) + bm.sort(key=default_sort_key) + bq.sort(key=default_sort_key) + + return (an, ap), (bm, bq), arg, exponent, fac + + +@_noconds_(True) +def _inverse_mellin_transform(F, s, x_, strip, as_meijerg=False): + """ A helper for the real inverse_mellin_transform function, this one here + assumes x to be real and positive. """ + x = _dummy('t', 'inverse-mellin-transform', F, positive=True) + # Actually, we won't try integration at all. Instead we use the definition + # of the Meijer G function as a fairly general inverse mellin transform. + F = F.rewrite(gamma) + for g in [factor(F), expand_mul(F), expand(F)]: + if g.is_Add: + # do all terms separately + ress = [_inverse_mellin_transform(G, s, x, strip, as_meijerg, + noconds=False) + for G in g.args] + conds = [p[1] for p in ress] + ress = [p[0] for p in ress] + res = Add(*ress) + if not as_meijerg: + res = factor(res, gens=res.atoms(Heaviside)) + return res.subs(x, x_), And(*conds) + + try: + a, b, C, e, fac = _rewrite_gamma(g, s, strip[0], strip[1]) + except IntegralTransformError: + continue + try: + G = meijerg(a, b, C/x**e) + except ValueError: + continue + if as_meijerg: + h = G + else: + try: + from sympy.simplify import hyperexpand + h = hyperexpand(G) + except NotImplementedError: + raise IntegralTransformError( + 'Inverse Mellin', F, 'Could not calculate integral') + + if h.is_Piecewise and len(h.args) == 3: + # XXX we break modularity here! + h = Heaviside(x - Abs(C))*h.args[0].args[0] \ + + Heaviside(Abs(C) - x)*h.args[1].args[0] + # We must ensure that the integral along the line we want converges, + # and return that value. + # See [L], 5.2 + cond = [Abs(arg(G.argument)) < G.delta*pi] + # Note: we allow ">=" here, this corresponds to convergence if we let + # limits go to oo symmetrically. ">" corresponds to absolute convergence. + cond += [And(Or(len(G.ap) != len(G.bq), 0 >= re(G.nu) + 1), + Abs(arg(G.argument)) == G.delta*pi)] + cond = Or(*cond) + if cond == False: + raise IntegralTransformError( + 'Inverse Mellin', F, 'does not converge') + return (h*fac).subs(x, x_), cond + + raise IntegralTransformError('Inverse Mellin', F, '') + +_allowed = None + + +class InverseMellinTransform(IntegralTransform): + """ + Class representing unevaluated inverse Mellin transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Mellin transforms, see the + :func:`inverse_mellin_transform` docstring. + """ + + _name = 'Inverse Mellin' + _none_sentinel = Dummy('None') + _c = Dummy('c') + + def __new__(cls, F, s, x, a, b, **opts): + if a is None: + a = InverseMellinTransform._none_sentinel + if b is None: + b = InverseMellinTransform._none_sentinel + return IntegralTransform.__new__(cls, F, s, x, a, b, **opts) + + @property + def fundamental_strip(self): + a, b = self.args[3], self.args[4] + if a is InverseMellinTransform._none_sentinel: + a = None + if b is InverseMellinTransform._none_sentinel: + b = None + return a, b + + def _compute_transform(self, F, s, x, **hints): + # IntegralTransform's doit will cause this hint to exist, but + # InverseMellinTransform should ignore it + hints.pop('simplify', True) + global _allowed + if _allowed is None: + _allowed = { + exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, + factorial, rf} + for f in postorder_traversal(F): + if f.is_Function and f.has(s) and f.func not in _allowed: + raise IntegralTransformError('Inverse Mellin', F, + 'Component %s not recognised.' % f) + strip = self.fundamental_strip + return _inverse_mellin_transform(F, s, x, strip, **hints) + + def _as_integral(self, F, s, x): + c = self.__class__._c + return Integral(F*x**(-s), (s, c - S.ImaginaryUnit*S.Infinity, c + + S.ImaginaryUnit*S.Infinity))/(2*S.Pi*S.ImaginaryUnit) + + +def inverse_mellin_transform(F, s, x, strip, **hints): + r""" + Compute the inverse Mellin transform of `F(s)` over the fundamental + strip given by ``strip=(a, b)``. + + Explanation + =========== + + This can be defined as + + .. math:: f(x) = \frac{1}{2\pi i} \int_{c - i\infty}^{c + i\infty} x^{-s} F(s) \mathrm{d}s, + + for any `c` in the fundamental strip. Under certain regularity + conditions on `F` and/or `f`, + this recovers `f` from its Mellin transform `F` + (and vice versa), for positive real `x`. + + One of `a` or `b` may be passed as ``None``; a suitable `c` will be + inferred. + + If the integral cannot be computed in closed form, this function returns + an unevaluated :class:`InverseMellinTransform` object. + + Note that this function will assume x to be positive and real, regardless + of the SymPy assumptions! + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + + Examples + ======== + + >>> from sympy import inverse_mellin_transform, oo, gamma + >>> from sympy.abc import x, s + >>> inverse_mellin_transform(gamma(s), s, x, (0, oo)) + exp(-x) + + The fundamental strip matters: + + >>> f = 1/(s**2 - 1) + >>> inverse_mellin_transform(f, s, x, (-oo, -1)) + x*(1 - 1/x**2)*Heaviside(x - 1)/2 + >>> inverse_mellin_transform(f, s, x, (-1, 1)) + -x*Heaviside(1 - x)/2 - Heaviside(x - 1)/(2*x) + >>> inverse_mellin_transform(f, s, x, (1, oo)) + (1/2 - x**2/2)*Heaviside(1 - x)/x + + See Also + ======== + + mellin_transform + hankel_transform, inverse_hankel_transform + """ + return InverseMellinTransform(F, s, x, strip[0], strip[1]).doit(**hints) + + +########################################################################## +# Fourier Transform +########################################################################## + +@_noconds_(True) +def _fourier_transform(f, x, k, a, b, name, simplify=True): + r""" + Compute a general Fourier-type transform + + .. math:: + + F(k) = a \int_{-\infty}^{\infty} e^{bixk} f(x)\, dx. + + For suitable choice of *a* and *b*, this reduces to the standard Fourier + and inverse Fourier transforms. + """ + F = integrate(a*f*exp(b*S.ImaginaryUnit*x*k), (x, S.NegativeInfinity, S.Infinity)) + + if not F.has(Integral): + return _simplify(F, simplify), S.true + + integral_f = integrate(f, (x, S.NegativeInfinity, S.Infinity)) + if integral_f in (S.NegativeInfinity, S.Infinity, S.NaN) or integral_f.has(Integral): + raise IntegralTransformError(name, f, 'function not integrable on real axis') + + if not F.is_Piecewise: + raise IntegralTransformError(name, f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError(name, f, 'integral in unexpected form') + + return _simplify(F, simplify), cond + + +class FourierTypeTransform(IntegralTransform): + """ Base class for Fourier transforms.""" + + def a(self): + raise NotImplementedError( + "Class %s must implement a(self) but does not" % self.__class__) + + def b(self): + raise NotImplementedError( + "Class %s must implement b(self) but does not" % self.__class__) + + def _compute_transform(self, f, x, k, **hints): + return _fourier_transform(f, x, k, + self.a(), self.b(), + self.__class__._name, **hints) + + def _as_integral(self, f, x, k): + a = self.a() + b = self.b() + return Integral(a*f*exp(b*S.ImaginaryUnit*x*k), (x, S.NegativeInfinity, S.Infinity)) + + +class FourierTransform(FourierTypeTransform): + """ + Class representing unevaluated Fourier transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Fourier transforms, see the :func:`fourier_transform` + docstring. + """ + + _name = 'Fourier' + + def a(self): + return 1 + + def b(self): + return -2*S.Pi + + +def fourier_transform(f, x, k, **hints): + r""" + Compute the unitary, ordinary-frequency Fourier transform of ``f``, defined + as + + .. math:: F(k) = \int_{-\infty}^\infty f(x) e^{-2\pi i x k} \mathrm{d} x. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`FourierTransform` object. + + For other Fourier transform conventions, see the function + :func:`sympy.integrals.transforms._fourier_transform`. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import fourier_transform, exp + >>> from sympy.abc import x, k + >>> fourier_transform(exp(-x**2), x, k) + sqrt(pi)*exp(-pi**2*k**2) + >>> fourier_transform(exp(-x**2), x, k, noconds=False) + (sqrt(pi)*exp(-pi**2*k**2), True) + + See Also + ======== + + inverse_fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return FourierTransform(f, x, k).doit(**hints) + + +class InverseFourierTransform(FourierTypeTransform): + """ + Class representing unevaluated inverse Fourier transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Fourier transforms, see the + :func:`inverse_fourier_transform` docstring. + """ + + _name = 'Inverse Fourier' + + def a(self): + return 1 + + def b(self): + return 2*S.Pi + + +def inverse_fourier_transform(F, k, x, **hints): + r""" + Compute the unitary, ordinary-frequency inverse Fourier transform of `F`, + defined as + + .. math:: f(x) = \int_{-\infty}^\infty F(k) e^{2\pi i x k} \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseFourierTransform` object. + + For other Fourier transform conventions, see the function + :func:`sympy.integrals.transforms._fourier_transform`. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import inverse_fourier_transform, exp, sqrt, pi + >>> from sympy.abc import x, k + >>> inverse_fourier_transform(sqrt(pi)*exp(-(pi*k)**2), k, x) + exp(-x**2) + >>> inverse_fourier_transform(sqrt(pi)*exp(-(pi*k)**2), k, x, noconds=False) + (exp(-x**2), True) + + See Also + ======== + + fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return InverseFourierTransform(F, k, x).doit(**hints) + + +########################################################################## +# Fourier Sine and Cosine Transform +########################################################################## + +@_noconds_(True) +def _sine_cosine_transform(f, x, k, a, b, K, name, simplify=True): + """ + Compute a general sine or cosine-type transform + F(k) = a int_0^oo b*sin(x*k) f(x) dx. + F(k) = a int_0^oo b*cos(x*k) f(x) dx. + + For suitable choice of a and b, this reduces to the standard sine/cosine + and inverse sine/cosine transforms. + """ + F = integrate(a*f*K(b*x*k), (x, S.Zero, S.Infinity)) + + if not F.has(Integral): + return _simplify(F, simplify), S.true + + if not F.is_Piecewise: + raise IntegralTransformError(name, f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError(name, f, 'integral in unexpected form') + + return _simplify(F, simplify), cond + + +class SineCosineTypeTransform(IntegralTransform): + """ + Base class for sine and cosine transforms. + Specify cls._kern. + """ + + def a(self): + raise NotImplementedError( + "Class %s must implement a(self) but does not" % self.__class__) + + def b(self): + raise NotImplementedError( + "Class %s must implement b(self) but does not" % self.__class__) + + + def _compute_transform(self, f, x, k, **hints): + return _sine_cosine_transform(f, x, k, + self.a(), self.b(), + self.__class__._kern, + self.__class__._name, **hints) + + def _as_integral(self, f, x, k): + a = self.a() + b = self.b() + K = self.__class__._kern + return Integral(a*f*K(b*x*k), (x, S.Zero, S.Infinity)) + + +class SineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated sine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute sine transforms, see the :func:`sine_transform` + docstring. + """ + + _name = 'Sine' + _kern = sin + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def sine_transform(f, x, k, **hints): + r""" + Compute the unitary, ordinary-frequency sine transform of `f`, defined + as + + .. math:: F(k) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty f(x) \sin(2\pi x k) \mathrm{d} x. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`SineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import sine_transform, exp + >>> from sympy.abc import x, k, a + >>> sine_transform(x*exp(-a*x**2), x, k) + sqrt(2)*k*exp(-k**2/(4*a))/(4*a**(3/2)) + >>> sine_transform(x**(-a), x, k) + 2**(1/2 - a)*k**(a - 1)*gamma(1 - a/2)/gamma(a/2 + 1/2) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return SineTransform(f, x, k).doit(**hints) + + +class InverseSineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated inverse sine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse sine transforms, see the + :func:`inverse_sine_transform` docstring. + """ + + _name = 'Inverse Sine' + _kern = sin + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def inverse_sine_transform(F, k, x, **hints): + r""" + Compute the unitary, ordinary-frequency inverse sine transform of `F`, + defined as + + .. math:: f(x) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty F(k) \sin(2\pi x k) \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseSineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import inverse_sine_transform, exp, sqrt, gamma + >>> from sympy.abc import x, k, a + >>> inverse_sine_transform(2**((1-2*a)/2)*k**(a - 1)* + ... gamma(-a/2 + 1)/gamma((a+1)/2), k, x) + x**(-a) + >>> inverse_sine_transform(sqrt(2)*k*exp(-k**2/(4*a))/(4*sqrt(a)**3), k, x) + x*exp(-a*x**2) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return InverseSineTransform(F, k, x).doit(**hints) + + +class CosineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated cosine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute cosine transforms, see the :func:`cosine_transform` + docstring. + """ + + _name = 'Cosine' + _kern = cos + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def cosine_transform(f, x, k, **hints): + r""" + Compute the unitary, ordinary-frequency cosine transform of `f`, defined + as + + .. math:: F(k) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty f(x) \cos(2\pi x k) \mathrm{d} x. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`CosineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import cosine_transform, exp, sqrt, cos + >>> from sympy.abc import x, k, a + >>> cosine_transform(exp(-a*x), x, k) + sqrt(2)*a/(sqrt(pi)*(a**2 + k**2)) + >>> cosine_transform(exp(-a*sqrt(x))*cos(a*sqrt(x)), x, k) + a*exp(-a**2/(2*k))/(2*k**(3/2)) + + See Also + ======== + + fourier_transform, inverse_fourier_transform, + sine_transform, inverse_sine_transform + inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return CosineTransform(f, x, k).doit(**hints) + + +class InverseCosineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated inverse cosine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse cosine transforms, see the + :func:`inverse_cosine_transform` docstring. + """ + + _name = 'Inverse Cosine' + _kern = cos + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def inverse_cosine_transform(F, k, x, **hints): + r""" + Compute the unitary, ordinary-frequency inverse cosine transform of `F`, + defined as + + .. math:: f(x) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty F(k) \cos(2\pi x k) \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseCosineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import inverse_cosine_transform, sqrt, pi + >>> from sympy.abc import x, k, a + >>> inverse_cosine_transform(sqrt(2)*a/(sqrt(pi)*(a**2 + k**2)), k, x) + exp(-a*x) + >>> inverse_cosine_transform(1/sqrt(k), k, x) + 1/sqrt(x) + + See Also + ======== + + fourier_transform, inverse_fourier_transform, + sine_transform, inverse_sine_transform + cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return InverseCosineTransform(F, k, x).doit(**hints) + + +########################################################################## +# Hankel Transform +########################################################################## + +@_noconds_(True) +def _hankel_transform(f, r, k, nu, name, simplify=True): + r""" + Compute a general Hankel transform + + .. math:: F_\nu(k) = \int_{0}^\infty f(r) J_\nu(k r) r \mathrm{d} r. + """ + F = integrate(f*besselj(nu, k*r)*r, (r, S.Zero, S.Infinity)) + + if not F.has(Integral): + return _simplify(F, simplify), S.true + + if not F.is_Piecewise: + raise IntegralTransformError(name, f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError(name, f, 'integral in unexpected form') + + return _simplify(F, simplify), cond + + +class HankelTypeTransform(IntegralTransform): + """ + Base class for Hankel transforms. + """ + + def doit(self, **hints): + return self._compute_transform(self.function, + self.function_variable, + self.transform_variable, + self.args[3], + **hints) + + def _compute_transform(self, f, r, k, nu, **hints): + return _hankel_transform(f, r, k, nu, self._name, **hints) + + def _as_integral(self, f, r, k, nu): + return Integral(f*besselj(nu, k*r)*r, (r, S.Zero, S.Infinity)) + + @property + def as_integral(self): + return self._as_integral(self.function, + self.function_variable, + self.transform_variable, + self.args[3]) + + +class HankelTransform(HankelTypeTransform): + """ + Class representing unevaluated Hankel transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Hankel transforms, see the :func:`hankel_transform` + docstring. + """ + + _name = 'Hankel' + + +def hankel_transform(f, r, k, nu, **hints): + r""" + Compute the Hankel transform of `f`, defined as + + .. math:: F_\nu(k) = \int_{0}^\infty f(r) J_\nu(k r) r \mathrm{d} r. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`HankelTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import hankel_transform, inverse_hankel_transform + >>> from sympy import exp + >>> from sympy.abc import r, k, m, nu, a + + >>> ht = hankel_transform(1/r**m, r, k, nu) + >>> ht + 2*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/(2**m*gamma(m/2 + nu/2)) + + >>> inverse_hankel_transform(ht, k, r, nu) + r**(-m) + + >>> ht = hankel_transform(exp(-a*r), r, k, 0) + >>> ht + a/(k**3*(a**2/k**2 + 1)**(3/2)) + + >>> inverse_hankel_transform(ht, k, r, 0) + exp(-a*r) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + inverse_hankel_transform + mellin_transform, laplace_transform + """ + return HankelTransform(f, r, k, nu).doit(**hints) + + +class InverseHankelTransform(HankelTypeTransform): + """ + Class representing unevaluated inverse Hankel transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Hankel transforms, see the + :func:`inverse_hankel_transform` docstring. + """ + + _name = 'Inverse Hankel' + + +def inverse_hankel_transform(F, k, r, nu, **hints): + r""" + Compute the inverse Hankel transform of `F` defined as + + .. math:: f(r) = \int_{0}^\infty F_\nu(k) J_\nu(k r) k \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseHankelTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import hankel_transform, inverse_hankel_transform + >>> from sympy import exp + >>> from sympy.abc import r, k, m, nu, a + + >>> ht = hankel_transform(1/r**m, r, k, nu) + >>> ht + 2*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/(2**m*gamma(m/2 + nu/2)) + + >>> inverse_hankel_transform(ht, k, r, nu) + r**(-m) + + >>> ht = hankel_transform(exp(-a*r), r, k, 0) + >>> ht + a/(k**3*(a**2/k**2 + 1)**(3/2)) + + >>> inverse_hankel_transform(ht, k, r, 0) + exp(-a*r) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform + mellin_transform, laplace_transform + """ + return InverseHankelTransform(F, k, r, nu).doit(**hints) + + +########################################################################## +# Laplace Transform +########################################################################## + +# Stub classes and functions that used to be here +import sympy.integrals.laplace as _laplace + +LaplaceTransform = _laplace.LaplaceTransform +laplace_transform = _laplace.laplace_transform +laplace_correspondence = _laplace.laplace_correspondence +laplace_initial_conds = _laplace.laplace_initial_conds +InverseLaplaceTransform = _laplace.InverseLaplaceTransform +inverse_laplace_transform = _laplace.inverse_laplace_transform diff --git a/phivenv/Lib/site-packages/sympy/integrals/trigonometry.py b/phivenv/Lib/site-packages/sympy/integrals/trigonometry.py new file mode 100644 index 0000000000000000000000000000000000000000..dd6389bcc79f28ed6c255546685da1a0e061c327 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/integrals/trigonometry.py @@ -0,0 +1,335 @@ +from sympy.core import cacheit, Dummy, Ne, Integer, Rational, S, Wild +from sympy.functions import binomial, sin, cos, Piecewise, Abs +from .integrals import integrate + +# TODO sin(a*x)*cos(b*x) -> sin((a+b)x) + sin((a-b)x) ? + +# creating, each time, Wild's and sin/cos/Mul is expensive. Also, our match & +# subs are very slow when not cached, and if we create Wild each time, we +# effectively block caching. +# +# so we cache the pattern + +# need to use a function instead of lamda since hash of lambda changes on +# each call to _pat_sincos +def _integer_instance(n): + return isinstance(n, Integer) + +@cacheit +def _pat_sincos(x): + a = Wild('a', exclude=[x]) + n, m = [Wild(s, exclude=[x], properties=[_integer_instance]) + for s in 'nm'] + pat = sin(a*x)**n * cos(a*x)**m + return pat, a, n, m + +_u = Dummy('u') + + +def trigintegrate(f, x, conds='piecewise'): + """ + Integrate f = Mul(trig) over x. + + Examples + ======== + + >>> from sympy import sin, cos, tan, sec + >>> from sympy.integrals.trigonometry import trigintegrate + >>> from sympy.abc import x + + >>> trigintegrate(sin(x)*cos(x), x) + sin(x)**2/2 + + >>> trigintegrate(sin(x)**2, x) + x/2 - sin(x)*cos(x)/2 + + >>> trigintegrate(tan(x)*sec(x), x) + 1/cos(x) + + >>> trigintegrate(sin(x)*tan(x), x) + -log(sin(x) - 1)/2 + log(sin(x) + 1)/2 - sin(x) + + References + ========== + + .. [1] https://en.wikibooks.org/wiki/Calculus/Integration_techniques + + See Also + ======== + + sympy.integrals.integrals.Integral.doit + sympy.integrals.integrals.Integral + """ + pat, a, n, m = _pat_sincos(x) + + f = f.rewrite('sincos') + M = f.match(pat) + + if M is None: + return + + n, m = M[n], M[m] + if n.is_zero and m.is_zero: + return x + zz = x if n.is_zero else S.Zero + + a = M[a] + + if n.is_odd or m.is_odd: + u = _u + n_, m_ = n.is_odd, m.is_odd + + # take smallest n or m -- to choose simplest substitution + if n_ and m_: + + # Make sure to choose the positive one + # otherwise an incorrect integral can occur. + if n < 0 and m > 0: + m_ = True + n_ = False + elif m < 0 and n > 0: + n_ = True + m_ = False + # Both are negative so choose the smallest n or m + # in absolute value for simplest substitution. + elif (n < 0 and m < 0): + n_ = n > m + m_ = not (n > m) + + # Both n and m are odd and positive + else: + n_ = (n < m) # NB: careful here, one of the + m_ = not (n < m) # conditions *must* be true + + # n m u=C (n-1)/2 m + # S(x) * C(x) dx --> -(1-u^2) * u du + if n_: + ff = -(1 - u**2)**((n - 1)/2) * u**m + uu = cos(a*x) + + # n m u=S n (m-1)/2 + # S(x) * C(x) dx --> u * (1-u^2) du + elif m_: + ff = u**n * (1 - u**2)**((m - 1)/2) + uu = sin(a*x) + + fi = integrate(ff, u) # XXX cyclic deps + fx = fi.subs(u, uu) + if conds == 'piecewise': + return Piecewise((fx / a, Ne(a, 0)), (zz, True)) + return fx / a + + # n & m are both even + # + # 2k 2m 2l 2l + # we transform S (x) * C (x) into terms with only S (x) or C (x) + # + # example: + # 100 4 100 2 2 100 4 2 + # S (x) * C (x) = S (x) * (1-S (x)) = S (x) * (1 + S (x) - 2*S (x)) + # + # 104 102 100 + # = S (x) - 2*S (x) + S (x) + # 2k + # then S is integrated with recursive formula + + # take largest n or m -- to choose simplest substitution + n_ = (Abs(n) > Abs(m)) + m_ = (Abs(m) > Abs(n)) + res = S.Zero + + if n_: + # 2k 2 k i 2i + # C = (1 - S ) = sum(i, (-) * B(k, i) * S ) + if m > 0: + for i in range(0, m//2 + 1): + res += (S.NegativeOne**i * binomial(m//2, i) * + _sin_pow_integrate(n + 2*i, x)) + + elif m == 0: + res = _sin_pow_integrate(n, x) + else: + + # m < 0 , |n| > |m| + # / + # | + # | m n + # | cos (x) sin (x) dx = + # | + # | + #/ + # / + # | + # -1 m+1 n-1 n - 1 | m+2 n-2 + # ________ cos (x) sin (x) + _______ | cos (x) sin (x) dx + # | + # m + 1 m + 1 | + # / + + res = (Rational(-1, m + 1) * cos(x)**(m + 1) * sin(x)**(n - 1) + + Rational(n - 1, m + 1) * + trigintegrate(cos(x)**(m + 2)*sin(x)**(n - 2), x)) + + elif m_: + # 2k 2 k i 2i + # S = (1 - C ) = sum(i, (-) * B(k, i) * C ) + if n > 0: + + # / / + # | | + # | m n | -m n + # | cos (x)*sin (x) dx or | cos (x) * sin (x) dx + # | | + # / / + # + # |m| > |n| ; m, n >0 ; m, n belong to Z - {0} + # n 2 + # sin (x) term is expanded here in terms of cos (x), + # and then integrated. + # + + for i in range(0, n//2 + 1): + res += (S.NegativeOne**i * binomial(n//2, i) * + _cos_pow_integrate(m + 2*i, x)) + + elif n == 0: + + # / + # | + # | 1 + # | _ _ _ + # | m + # | cos (x) + # / + # + + res = _cos_pow_integrate(m, x) + else: + + # n < 0 , |m| > |n| + # / + # | + # | m n + # | cos (x) sin (x) dx = + # | + # | + #/ + # / + # | + # 1 m-1 n+1 m - 1 | m-2 n+2 + # _______ cos (x) sin (x) + _______ | cos (x) sin (x) dx + # | + # n + 1 n + 1 | + # / + + res = (Rational(1, n + 1) * cos(x)**(m - 1)*sin(x)**(n + 1) + + Rational(m - 1, n + 1) * + trigintegrate(cos(x)**(m - 2)*sin(x)**(n + 2), x)) + + else: + if m == n: + ##Substitute sin(2x)/2 for sin(x)cos(x) and then Integrate. + res = integrate((sin(2*x)*S.Half)**m, x) + elif (m == -n): + if n < 0: + # Same as the scheme described above. + # the function argument to integrate in the end will + # be 1, this cannot be integrated by trigintegrate. + # Hence use sympy.integrals.integrate. + res = (Rational(1, n + 1) * cos(x)**(m - 1) * sin(x)**(n + 1) + + Rational(m - 1, n + 1) * + integrate(cos(x)**(m - 2) * sin(x)**(n + 2), x)) + else: + res = (Rational(-1, m + 1) * cos(x)**(m + 1) * sin(x)**(n - 1) + + Rational(n - 1, m + 1) * + integrate(cos(x)**(m + 2)*sin(x)**(n - 2), x)) + if conds == 'piecewise': + return Piecewise((res.subs(x, a*x) / a, Ne(a, 0)), (zz, True)) + return res.subs(x, a*x) / a + + +def _sin_pow_integrate(n, x): + if n > 0: + if n == 1: + #Recursion break + return -cos(x) + + # n > 0 + # / / + # | | + # | n -1 n-1 n - 1 | n-2 + # | sin (x) dx = ______ cos (x) sin (x) + _______ | sin (x) dx + # | | + # | n n | + #/ / + # + # + + return (Rational(-1, n) * cos(x) * sin(x)**(n - 1) + + Rational(n - 1, n) * _sin_pow_integrate(n - 2, x)) + + if n < 0: + if n == -1: + ##Make sure this does not come back here again. + ##Recursion breaks here or at n==0. + return trigintegrate(1/sin(x), x) + + # n < 0 + # / / + # | | + # | n 1 n+1 n + 2 | n+2 + # | sin (x) dx = _______ cos (x) sin (x) + _______ | sin (x) dx + # | | + # | n + 1 n + 1 | + #/ / + # + + return (Rational(1, n + 1) * cos(x) * sin(x)**(n + 1) + + Rational(n + 2, n + 1) * _sin_pow_integrate(n + 2, x)) + + else: + #n == 0 + #Recursion break. + return x + + +def _cos_pow_integrate(n, x): + if n > 0: + if n == 1: + #Recursion break. + return sin(x) + + # n > 0 + # / / + # | | + # | n 1 n-1 n - 1 | n-2 + # | sin (x) dx = ______ sin (x) cos (x) + _______ | cos (x) dx + # | | + # | n n | + #/ / + # + + return (Rational(1, n) * sin(x) * cos(x)**(n - 1) + + Rational(n - 1, n) * _cos_pow_integrate(n - 2, x)) + + if n < 0: + if n == -1: + ##Recursion break + return trigintegrate(1/cos(x), x) + + # n < 0 + # / / + # | | + # | n -1 n+1 n + 2 | n+2 + # | cos (x) dx = _______ sin (x) cos (x) + _______ | cos (x) dx + # | | + # | n + 1 n + 1 | + #/ / + # + + return (Rational(-1, n + 1) * sin(x) * cos(x)**(n + 1) + + Rational(n + 2, n + 1) * _cos_pow_integrate(n + 2, x)) + else: + # n == 0 + #Recursion Break. + return x diff --git a/phivenv/Lib/site-packages/sympy/interactive/__init__.py b/phivenv/Lib/site-packages/sympy/interactive/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1b3f043ada6222d79dd52fd28b035e2ea45c5683 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/interactive/__init__.py @@ -0,0 +1,8 @@ +"""Helper module for setting up interactive SymPy sessions. """ + +from .printing import init_printing +from .session import init_session +from .traversal import interactive_traversal + + +__all__ = ['init_printing', 'init_session', 'interactive_traversal'] diff --git a/phivenv/Lib/site-packages/sympy/interactive/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c63a0b01738623a25eff3a096118d0ba1a14258b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/__pycache__/printing.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/printing.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2eda3ca7fe5e170ea14f3f435da7dbde955d0dc8 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/printing.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/__pycache__/session.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/session.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..844a3286cb785519f6b79b0882edab18696af7ea Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/session.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/__pycache__/traversal.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/traversal.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..599f49ea37e000f2949fa36b099f7fa6297b2738 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/__pycache__/traversal.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/printing.py b/phivenv/Lib/site-packages/sympy/interactive/printing.py new file mode 100644 index 0000000000000000000000000000000000000000..2fcc73e3e96a5b7e25f7fc7ebf54a5781c3b15b9 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/interactive/printing.py @@ -0,0 +1,532 @@ +"""Tools for setting up printing in interactive sessions. """ + +from io import BytesIO + +from sympy.printing.latex import latex as default_latex +from sympy.printing.preview import preview +from sympy.utilities.misc import debug +from sympy.printing.defaults import Printable +from sympy.external import import_module + + +def _init_python_printing(stringify_func, **settings): + """Setup printing in Python interactive session. """ + import sys + import builtins + + def _displayhook(arg): + """Python's pretty-printer display hook. + + This function was adapted from: + + https://www.python.org/dev/peps/pep-0217/ + + """ + if arg is not None: + builtins._ = None + print(stringify_func(arg, **settings)) + builtins._ = arg + + sys.displayhook = _displayhook + + +def _init_ipython_printing(ip, stringify_func, use_latex, euler, forecolor, + backcolor, fontsize, latex_mode, print_builtin, + latex_printer, scale, **settings): + """Setup printing in IPython interactive session. """ + IPython = import_module("IPython", min_module_version="1.0") + try: + from IPython.lib.latextools import latex_to_png + except ImportError: + pass + + # Guess best font color if none was given based on the ip.colors string. + # From the IPython documentation: + # It has four case-insensitive values: 'nocolor', 'neutral', 'linux', + # 'lightbg'. The default is neutral, which should be legible on either + # dark or light terminal backgrounds. linux is optimised for dark + # backgrounds and lightbg for light ones. + if forecolor is None: + color = ip.colors.lower() + if color == 'lightbg': + forecolor = 'Black' + elif color == 'linux': + forecolor = 'White' + else: + # No idea, go with gray. + forecolor = 'Gray' + debug("init_printing: Automatic foreground color:", forecolor) + + if use_latex == "svg": + extra_preamble = "\n\\special{color %s}" % forecolor + else: + extra_preamble = "" + + imagesize = 'tight' + offset = "0cm,0cm" + resolution = round(150*scale) + dvi = r"-T %s -D %d -bg %s -fg %s -O %s" % ( + imagesize, resolution, backcolor, forecolor, offset) + dvioptions = dvi.split() + + svg_scale = 150/72*scale + dvioptions_svg = ["--no-fonts", "--scale={}".format(svg_scale)] + + debug("init_printing: DVIOPTIONS:", dvioptions) + debug("init_printing: DVIOPTIONS_SVG:", dvioptions_svg) + + latex = latex_printer or default_latex + + def _print_plain(arg, p, cycle): + """caller for pretty, for use in IPython 0.11""" + if _can_print(arg): + p.text(stringify_func(arg)) + else: + p.text(IPython.lib.pretty.pretty(arg)) + + def _preview_wrapper(o): + exprbuffer = BytesIO() + try: + preview(o, output='png', viewer='BytesIO', euler=euler, + outputbuffer=exprbuffer, extra_preamble=extra_preamble, + dvioptions=dvioptions, fontsize=fontsize) + except Exception as e: + # IPython swallows exceptions + debug("png printing:", "_preview_wrapper exception raised:", + repr(e)) + raise + return exprbuffer.getvalue() + + def _svg_wrapper(o): + exprbuffer = BytesIO() + try: + preview(o, output='svg', viewer='BytesIO', euler=euler, + outputbuffer=exprbuffer, extra_preamble=extra_preamble, + dvioptions=dvioptions_svg, fontsize=fontsize) + except Exception as e: + # IPython swallows exceptions + debug("svg printing:", "_preview_wrapper exception raised:", + repr(e)) + raise + return exprbuffer.getvalue().decode('utf-8') + + def _matplotlib_wrapper(o): + # mathtext can't render some LaTeX commands. For example, it can't + # render any LaTeX environments such as array or matrix. So here we + # ensure that if mathtext fails to render, we return None. + try: + try: + return latex_to_png(o, color=forecolor, scale=scale) + except TypeError: # Old IPython version without color and scale + return latex_to_png(o) + except ValueError as e: + debug('matplotlib exception caught:', repr(e)) + return None + + + # Hook methods for builtin SymPy printers + printing_hooks = ('_latex', '_sympystr', '_pretty', '_sympyrepr') + + + def _can_print(o): + """Return True if type o can be printed with one of the SymPy printers. + + If o is a container type, this is True if and only if every element of + o can be printed in this way. + """ + + try: + # If you're adding another type, make sure you add it to printable_types + # later in this file as well + + builtin_types = (list, tuple, set, frozenset) + if isinstance(o, builtin_types): + # If the object is a custom subclass with a custom str or + # repr, use that instead. + if (type(o).__str__ not in (i.__str__ for i in builtin_types) or + type(o).__repr__ not in (i.__repr__ for i in builtin_types)): + return False + return all(_can_print(i) for i in o) + elif isinstance(o, dict): + return all(_can_print(i) and _can_print(o[i]) for i in o) + elif isinstance(o, bool): + return False + elif isinstance(o, Printable): + # types known to SymPy + return True + elif any(hasattr(o, hook) for hook in printing_hooks): + # types which add support themselves + return True + elif isinstance(o, (float, int)) and print_builtin: + return True + return False + except RuntimeError: + return False + # This is in case maximum recursion depth is reached. + # Since RecursionError is for versions of Python 3.5+ + # so this is to guard against RecursionError for older versions. + + def _print_latex_png(o): + """ + A function that returns a png rendered by an external latex + distribution, falling back to matplotlib rendering + """ + if _can_print(o): + s = latex(o, mode=latex_mode, **settings) + if latex_mode == 'plain': + s = '$\\displaystyle %s$' % s + try: + return _preview_wrapper(s) + except RuntimeError as e: + debug('preview failed with:', repr(e), + ' Falling back to matplotlib backend') + if latex_mode != 'inline': + s = latex(o, mode='inline', **settings) + return _matplotlib_wrapper(s) + + def _print_latex_svg(o): + """ + A function that returns a svg rendered by an external latex + distribution, no fallback available. + """ + if _can_print(o): + s = latex(o, mode=latex_mode, **settings) + if latex_mode == 'plain': + s = '$\\displaystyle %s$' % s + try: + return _svg_wrapper(s) + except RuntimeError as e: + debug('preview failed with:', repr(e), + ' No fallback available.') + + def _print_latex_matplotlib(o): + """ + A function that returns a png rendered by mathtext + """ + if _can_print(o): + s = latex(o, mode='inline', **settings) + return _matplotlib_wrapper(s) + + def _print_latex_text(o): + """ + A function to generate the latex representation of SymPy expressions. + """ + if _can_print(o): + s = latex(o, mode=latex_mode, **settings) + if latex_mode == 'plain': + return '$\\displaystyle %s$' % s + return s + + # Printable is our own type, so we handle it with methods instead of + # the approach required by builtin types. This allows downstream + # packages to override the methods in their own subclasses of Printable, + # which avoids the effects of gh-16002. + printable_types = [float, tuple, list, set, frozenset, dict, int] + + plaintext_formatter = ip.display_formatter.formatters['text/plain'] + + # Exception to the rule above: IPython has better dispatching rules + # for plaintext printing (xref ipython/ipython#8938), and we can't + # use `_repr_pretty_` without hitting a recursion error in _print_plain. + for cls in printable_types + [Printable]: + plaintext_formatter.for_type(cls, _print_plain) + + svg_formatter = ip.display_formatter.formatters['image/svg+xml'] + if use_latex in ('svg', ): + debug("init_printing: using svg formatter") + for cls in printable_types: + svg_formatter.for_type(cls, _print_latex_svg) + Printable._repr_svg_ = _print_latex_svg + else: + debug("init_printing: not using any svg formatter") + for cls in printable_types: + # Better way to set this, but currently does not work in IPython + #png_formatter.for_type(cls, None) + if cls in svg_formatter.type_printers: + svg_formatter.type_printers.pop(cls) + Printable._repr_svg_ = Printable._repr_disabled + + png_formatter = ip.display_formatter.formatters['image/png'] + if use_latex in (True, 'png'): + debug("init_printing: using png formatter") + for cls in printable_types: + png_formatter.for_type(cls, _print_latex_png) + Printable._repr_png_ = _print_latex_png + elif use_latex == 'matplotlib': + debug("init_printing: using matplotlib formatter") + for cls in printable_types: + png_formatter.for_type(cls, _print_latex_matplotlib) + Printable._repr_png_ = _print_latex_matplotlib + else: + debug("init_printing: not using any png formatter") + for cls in printable_types: + # Better way to set this, but currently does not work in IPython + #png_formatter.for_type(cls, None) + if cls in png_formatter.type_printers: + png_formatter.type_printers.pop(cls) + Printable._repr_png_ = Printable._repr_disabled + + latex_formatter = ip.display_formatter.formatters['text/latex'] + if use_latex in (True, 'mathjax'): + debug("init_printing: using mathjax formatter") + for cls in printable_types: + latex_formatter.for_type(cls, _print_latex_text) + Printable._repr_latex_ = _print_latex_text + else: + debug("init_printing: not using text/latex formatter") + for cls in printable_types: + # Better way to set this, but currently does not work in IPython + #latex_formatter.for_type(cls, None) + if cls in latex_formatter.type_printers: + latex_formatter.type_printers.pop(cls) + Printable._repr_latex_ = Printable._repr_disabled + +def _is_ipython(shell): + """Is a shell instance an IPython shell?""" + # shortcut, so we don't import IPython if we don't have to + from sys import modules + if 'IPython' not in modules: + return False + try: + from IPython.core.interactiveshell import InteractiveShell + except ImportError: + # IPython < 0.11 + try: + from IPython.iplib import InteractiveShell + except ImportError: + # Reaching this points means IPython has changed in a backward-incompatible way + # that we don't know about. Warn? + return False + return isinstance(shell, InteractiveShell) + +# Used by the doctester to override the default for no_global +NO_GLOBAL = False + +def init_printing(pretty_print=True, order=None, use_unicode=None, + use_latex=None, wrap_line=None, num_columns=None, + no_global=False, ip=None, euler=False, forecolor=None, + backcolor='Transparent', fontsize='10pt', + latex_mode='plain', print_builtin=True, + str_printer=None, pretty_printer=None, + latex_printer=None, scale=1.0, **settings): + r""" + Initializes pretty-printer depending on the environment. + + Parameters + ========== + + pretty_print : bool, default=True + If ``True``, use :func:`~.pretty_print` to stringify or the provided pretty + printer; if ``False``, use :func:`~.sstrrepr` to stringify or the provided string + printer. + order : string or None, default='lex' + There are a few different settings for this parameter: + ``'lex'`` (default), which is lexographic order; + ``'grlex'``, which is graded lexographic order; + ``'grevlex'``, which is reversed graded lexographic order; + ``'old'``, which is used for compatibility reasons and for long expressions; + ``None``, which sets it to lex. + use_unicode : bool or None, default=None + If ``True``, use unicode characters; + if ``False``, do not use unicode characters; + if ``None``, make a guess based on the environment. + use_latex : string, bool, or None, default=None + If ``True``, use default LaTeX rendering in GUI interfaces (png and + mathjax); + if ``False``, do not use LaTeX rendering; + if ``None``, make a guess based on the environment; + if ``'png'``, enable LaTeX rendering with an external LaTeX compiler, + falling back to matplotlib if external compilation fails; + if ``'matplotlib'``, enable LaTeX rendering with matplotlib; + if ``'mathjax'``, enable LaTeX text generation, for example MathJax + rendering in IPython notebook or text rendering in LaTeX documents; + if ``'svg'``, enable LaTeX rendering with an external latex compiler, + no fallback + wrap_line : bool + If True, lines will wrap at the end; if False, they will not wrap + but continue as one line. This is only relevant if ``pretty_print`` is + True. + num_columns : int or None, default=None + If ``int``, number of columns before wrapping is set to num_columns; if + ``None``, number of columns before wrapping is set to terminal width. + This is only relevant if ``pretty_print`` is ``True``. + no_global : bool, default=False + If ``True``, the settings become system wide; + if ``False``, use just for this console/session. + ip : An interactive console + This can either be an instance of IPython, + or a class that derives from code.InteractiveConsole. + euler : bool, optional, default=False + Loads the euler package in the LaTeX preamble for handwritten style + fonts (https://www.ctan.org/pkg/euler). + forecolor : string or None, optional, default=None + DVI setting for foreground color. ``None`` means that either ``'Black'``, + ``'White'``, or ``'Gray'`` will be selected based on a guess of the IPython + terminal color setting. See notes. + backcolor : string, optional, default='Transparent' + DVI setting for background color. See notes. + fontsize : string or int, optional, default='10pt' + A font size to pass to the LaTeX documentclass function in the + preamble. Note that the options are limited by the documentclass. + Consider using scale instead. + latex_mode : string, optional, default='plain' + The mode used in the LaTeX printer. Can be one of: + ``{'inline'|'plain'|'equation'|'equation*'}``. + print_builtin : boolean, optional, default=True + If ``True`` then floats and integers will be printed. If ``False`` the + printer will only print SymPy types. + str_printer : function, optional, default=None + A custom string printer function. This should mimic + :func:`~.sstrrepr`. + pretty_printer : function, optional, default=None + A custom pretty printer. This should mimic :func:`~.pretty`. + latex_printer : function, optional, default=None + A custom LaTeX printer. This should mimic :func:`~.latex`. + scale : float, optional, default=1.0 + Scale the LaTeX output when using the ``'png'`` or ``'svg'`` backends. + Useful for high dpi screens. + settings : + Any additional settings for the ``latex`` and ``pretty`` commands can + be used to fine-tune the output. + + Examples + ======== + + >>> from sympy.interactive import init_printing + >>> from sympy import Symbol, sqrt + >>> from sympy.abc import x, y + >>> sqrt(5) + sqrt(5) + >>> init_printing(pretty_print=True) # doctest: +SKIP + >>> sqrt(5) # doctest: +SKIP + ___ + \/ 5 + >>> theta = Symbol('theta') # doctest: +SKIP + >>> init_printing(use_unicode=True) # doctest: +SKIP + >>> theta # doctest: +SKIP + \u03b8 + >>> init_printing(use_unicode=False) # doctest: +SKIP + >>> theta # doctest: +SKIP + theta + >>> init_printing(order='lex') # doctest: +SKIP + >>> str(y + x + y**2 + x**2) # doctest: +SKIP + x**2 + x + y**2 + y + >>> init_printing(order='grlex') # doctest: +SKIP + >>> str(y + x + y**2 + x**2) # doctest: +SKIP + x**2 + x + y**2 + y + >>> init_printing(order='grevlex') # doctest: +SKIP + >>> str(y * x**2 + x * y**2) # doctest: +SKIP + x**2*y + x*y**2 + >>> init_printing(order='old') # doctest: +SKIP + >>> str(x**2 + y**2 + x + y) # doctest: +SKIP + x**2 + x + y**2 + y + >>> init_printing(num_columns=10) # doctest: +SKIP + >>> x**2 + x + y**2 + y # doctest: +SKIP + x + y + + x**2 + y**2 + + Notes + ===== + + The foreground and background colors can be selected when using ``'png'`` or + ``'svg'`` LaTeX rendering. Note that before the ``init_printing`` command is + executed, the LaTeX rendering is handled by the IPython console and not SymPy. + + The colors can be selected among the 68 standard colors known to ``dvips``, + for a list see [1]_. In addition, the background color can be + set to ``'Transparent'`` (which is the default value). + + When using the ``'Auto'`` foreground color, the guess is based on the + ``colors`` variable in the IPython console, see [2]_. Hence, if + that variable is set correctly in your IPython console, there is a high + chance that the output will be readable, although manual settings may be + needed. + + + References + ========== + + .. [1] https://en.wikibooks.org/wiki/LaTeX/Colors#The_68_standard_colors_known_to_dvips + + .. [2] https://ipython.readthedocs.io/en/stable/config/details.html#terminal-colors + + See Also + ======== + + sympy.printing.latex + sympy.printing.pretty + + """ + import sys + from sympy.printing.printer import Printer + + if pretty_print: + if pretty_printer is not None: + stringify_func = pretty_printer + else: + from sympy.printing import pretty as stringify_func + else: + if str_printer is not None: + stringify_func = str_printer + else: + from sympy.printing import sstrrepr as stringify_func + + # Even if ip is not passed, double check that not in IPython shell + in_ipython = False + if ip is None: + try: + ip = get_ipython() + except NameError: + pass + else: + in_ipython = (ip is not None) + + if ip and not in_ipython: + in_ipython = _is_ipython(ip) + + if in_ipython and pretty_print: + try: + from IPython.terminal.interactiveshell import TerminalInteractiveShell + from code import InteractiveConsole + except ImportError: + pass + else: + # This will be True if we are in the qtconsole or notebook + if not isinstance(ip, (InteractiveConsole, TerminalInteractiveShell)) \ + and 'ipython-console' not in ''.join(sys.argv): + if use_unicode is None: + debug("init_printing: Setting use_unicode to True") + use_unicode = True + if use_latex is None: + debug("init_printing: Setting use_latex to True") + use_latex = True + + if not NO_GLOBAL and not no_global: + Printer.set_global_settings(order=order, use_unicode=use_unicode, + wrap_line=wrap_line, num_columns=num_columns) + else: + _stringify_func = stringify_func + + if pretty_print: + stringify_func = lambda expr, **settings: \ + _stringify_func(expr, order=order, + use_unicode=use_unicode, + wrap_line=wrap_line, + num_columns=num_columns, + **settings) + else: + stringify_func = \ + lambda expr, **settings: _stringify_func( + expr, order=order, **settings) + + if in_ipython: + mode_in_settings = settings.pop("mode", None) + if mode_in_settings: + debug("init_printing: Mode is not able to be set due to internals" + "of IPython printing") + _init_ipython_printing(ip, stringify_func, use_latex, euler, + forecolor, backcolor, fontsize, latex_mode, + print_builtin, latex_printer, scale, + **settings) + else: + _init_python_printing(stringify_func, **settings) diff --git a/phivenv/Lib/site-packages/sympy/interactive/session.py b/phivenv/Lib/site-packages/sympy/interactive/session.py new file mode 100644 index 0000000000000000000000000000000000000000..348b0938d69e5e7ffa9510f7d9ac759eb6683b8f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/interactive/session.py @@ -0,0 +1,463 @@ +"""Tools for setting up interactive sessions. """ + +from sympy.external.gmpy import GROUND_TYPES +from sympy.external.importtools import version_tuple + +from sympy.interactive.printing import init_printing + +from sympy.utilities.misc import ARCH + +preexec_source = """\ +from sympy import * +x, y, z, t = symbols('x y z t') +k, m, n = symbols('k m n', integer=True) +f, g, h = symbols('f g h', cls=Function) +init_printing() +""" + +verbose_message = """\ +These commands were executed: +%(source)s +Documentation can be found at https://docs.sympy.org/%(version)s +""" + +no_ipython = """\ +Could not locate IPython. Having IPython installed is greatly recommended. +See http://ipython.scipy.org for more details. If you use Debian/Ubuntu, +just install the 'ipython' package and start isympy again. +""" + + +def _make_message(ipython=True, quiet=False, source=None): + """Create a banner for an interactive session. """ + from sympy import __version__ as sympy_version + from sympy import SYMPY_DEBUG + + import sys + import os + + if quiet: + return "" + + python_version = "%d.%d.%d" % sys.version_info[:3] + + if ipython: + shell_name = "IPython" + else: + shell_name = "Python" + + info = ['ground types: %s' % GROUND_TYPES] + + cache = os.getenv('SYMPY_USE_CACHE') + + if cache is not None and cache.lower() == 'no': + info.append('cache: off') + + if SYMPY_DEBUG: + info.append('debugging: on') + + args = shell_name, sympy_version, python_version, ARCH, ', '.join(info) + message = "%s console for SymPy %s (Python %s-%s) (%s)\n" % args + + if source is None: + source = preexec_source + + _source = "" + + for line in source.split('\n')[:-1]: + if not line: + _source += '\n' + else: + _source += '>>> ' + line + '\n' + + doc_version = sympy_version + if 'dev' in doc_version: + doc_version = "dev" + else: + doc_version = "%s/" % doc_version + + message += '\n' + verbose_message % {'source': _source, + 'version': doc_version} + + return message + + +def int_to_Integer(s): + """ + Wrap integer literals with Integer. + + This is based on the decistmt example from + https://docs.python.org/3/library/tokenize.html. + + Only integer literals are converted. Float literals are left alone. + + Examples + ======== + + >>> from sympy import Integer # noqa: F401 + >>> from sympy.interactive.session import int_to_Integer + >>> s = '1.2 + 1/2 - 0x12 + a1' + >>> int_to_Integer(s) + '1.2 +Integer (1 )/Integer (2 )-Integer (0x12 )+a1 ' + >>> s = 'print (1/2)' + >>> int_to_Integer(s) + 'print (Integer (1 )/Integer (2 ))' + >>> exec(s) + 0.5 + >>> exec(int_to_Integer(s)) + 1/2 + """ + from tokenize import generate_tokens, untokenize, NUMBER, NAME, OP + from io import StringIO + + def _is_int(num): + """ + Returns true if string value num (with token NUMBER) represents an integer. + """ + # XXX: Is there something in the standard library that will do this? + if '.' in num or 'j' in num.lower() or 'e' in num.lower(): + return False + return True + + result = [] + g = generate_tokens(StringIO(s).readline) # tokenize the string + for toknum, tokval, _, _, _ in g: + if toknum == NUMBER and _is_int(tokval): # replace NUMBER tokens + result.extend([ + (NAME, 'Integer'), + (OP, '('), + (NUMBER, tokval), + (OP, ')') + ]) + else: + result.append((toknum, tokval)) + return untokenize(result) + + +def enable_automatic_int_sympification(shell): + """ + Allow IPython to automatically convert integer literals to Integer. + """ + import ast + old_run_cell = shell.run_cell + + def my_run_cell(cell, *args, **kwargs): + try: + # Check the cell for syntax errors. This way, the syntax error + # will show the original input, not the transformed input. The + # downside here is that IPython magic like %timeit will not work + # with transformed input (but on the other hand, IPython magic + # that doesn't expect transformed input will continue to work). + ast.parse(cell) + except SyntaxError: + pass + else: + cell = int_to_Integer(cell) + return old_run_cell(cell, *args, **kwargs) + + shell.run_cell = my_run_cell + + +def enable_automatic_symbols(shell): + """Allow IPython to automatically create symbols (``isympy -a``). """ + # XXX: This should perhaps use tokenize, like int_to_Integer() above. + # This would avoid re-executing the code, which can lead to subtle + # issues. For example: + # + # In [1]: a = 1 + # + # In [2]: for i in range(10): + # ...: a += 1 + # ...: + # + # In [3]: a + # Out[3]: 11 + # + # In [4]: a = 1 + # + # In [5]: for i in range(10): + # ...: a += 1 + # ...: print b + # ...: + # b + # b + # b + # b + # b + # b + # b + # b + # b + # b + # + # In [6]: a + # Out[6]: 12 + # + # Note how the for loop is executed again because `b` was not defined, but `a` + # was already incremented once, so the result is that it is incremented + # multiple times. + + import re + re_nameerror = re.compile( + "name '(?P[A-Za-z_][A-Za-z0-9_]*)' is not defined") + + def _handler(self, etype, value, tb, tb_offset=None): + """Handle :exc:`NameError` exception and allow injection of missing symbols. """ + if etype is NameError and tb.tb_next and not tb.tb_next.tb_next: + match = re_nameerror.match(str(value)) + + if match is not None: + # XXX: Make sure Symbol is in scope. Otherwise you'll get infinite recursion. + self.run_cell("%(symbol)s = Symbol('%(symbol)s')" % + {'symbol': match.group("symbol")}, store_history=False) + + try: + code = self.user_ns['In'][-1] + except (KeyError, IndexError): + pass + else: + self.run_cell(code, store_history=False) + return None + finally: + self.run_cell("del %s" % match.group("symbol"), + store_history=False) + + stb = self.InteractiveTB.structured_traceback( + etype, value, tb, tb_offset=tb_offset) + self._showtraceback(etype, value, stb) + + shell.set_custom_exc((NameError,), _handler) + + +def init_ipython_session(shell=None, argv=[], auto_symbols=False, auto_int_to_Integer=False): + """Construct new IPython session. """ + import IPython + + if version_tuple(IPython.__version__) >= version_tuple('0.11'): + if not shell: + # use an app to parse the command line, and init config + # IPython 1.0 deprecates the frontend module, so we import directly + # from the terminal module to prevent a deprecation message from being + # shown. + if version_tuple(IPython.__version__) >= version_tuple('1.0'): + from IPython.terminal import ipapp + else: + from IPython.frontend.terminal import ipapp + app = ipapp.TerminalIPythonApp() + + # don't draw IPython banner during initialization: + app.display_banner = False + app.initialize(argv) + + shell = app.shell + + if auto_symbols: + enable_automatic_symbols(shell) + if auto_int_to_Integer: + enable_automatic_int_sympification(shell) + + return shell + else: + from IPython.Shell import make_IPython + return make_IPython(argv) + + +def init_python_session(): + """Construct new Python session. """ + from code import InteractiveConsole + + class SymPyConsole(InteractiveConsole): + """An interactive console with readline support. """ + + def __init__(self): + ns_locals = {} + InteractiveConsole.__init__(self, locals=ns_locals) + try: + import rlcompleter + import readline + except ImportError: + pass + else: + import os + import atexit + + readline.set_completer(rlcompleter.Completer(ns_locals).complete) + readline.parse_and_bind('tab: complete') + + if hasattr(readline, 'read_history_file'): + history = os.path.expanduser('~/.sympy-history') + + try: + readline.read_history_file(history) + except OSError: + pass + + atexit.register(readline.write_history_file, history) + + return SymPyConsole() + + +def init_session(ipython=None, pretty_print=True, order=None, + use_unicode=None, use_latex=None, quiet=False, auto_symbols=False, + auto_int_to_Integer=False, str_printer=None, pretty_printer=None, + latex_printer=None, argv=[]): + """ + Initialize an embedded IPython or Python session. The IPython session is + initiated with the --pylab option, without the numpy imports, so that + matplotlib plotting can be interactive. + + Parameters + ========== + + pretty_print: boolean + If True, use pretty_print to stringify; + if False, use sstrrepr to stringify. + order: string or None + There are a few different settings for this parameter: + lex (default), which is lexographic order; + grlex, which is graded lexographic order; + grevlex, which is reversed graded lexographic order; + old, which is used for compatibility reasons and for long expressions; + None, which sets it to lex. + use_unicode: boolean or None + If True, use unicode characters; + if False, do not use unicode characters. + use_latex: boolean or None + If True, use latex rendering if IPython GUI's; + if False, do not use latex rendering. + quiet: boolean + If True, init_session will not print messages regarding its status; + if False, init_session will print messages regarding its status. + auto_symbols: boolean + If True, IPython will automatically create symbols for you. + If False, it will not. + The default is False. + auto_int_to_Integer: boolean + If True, IPython will automatically wrap int literals with Integer, so + that things like 1/2 give Rational(1, 2). + If False, it will not. + The default is False. + ipython: boolean or None + If True, printing will initialize for an IPython console; + if False, printing will initialize for a normal console; + The default is None, which automatically determines whether we are in + an ipython instance or not. + str_printer: function, optional, default=None + A custom string printer function. This should mimic + sympy.printing.sstrrepr(). + pretty_printer: function, optional, default=None + A custom pretty printer. This should mimic sympy.printing.pretty(). + latex_printer: function, optional, default=None + A custom LaTeX printer. This should mimic sympy.printing.latex() + This should mimic sympy.printing.latex(). + argv: list of arguments for IPython + See sympy.bin.isympy for options that can be used to initialize IPython. + + See Also + ======== + + sympy.interactive.printing.init_printing: for examples and the rest of the parameters. + + + Examples + ======== + + >>> from sympy import init_session, Symbol, sin, sqrt + >>> sin(x) #doctest: +SKIP + NameError: name 'x' is not defined + >>> init_session() #doctest: +SKIP + >>> sin(x) #doctest: +SKIP + sin(x) + >>> sqrt(5) #doctest: +SKIP + ___ + \\/ 5 + >>> init_session(pretty_print=False) #doctest: +SKIP + >>> sqrt(5) #doctest: +SKIP + sqrt(5) + >>> y + x + y**2 + x**2 #doctest: +SKIP + x**2 + x + y**2 + y + >>> init_session(order='grlex') #doctest: +SKIP + >>> y + x + y**2 + x**2 #doctest: +SKIP + x**2 + y**2 + x + y + >>> init_session(order='grevlex') #doctest: +SKIP + >>> y * x**2 + x * y**2 #doctest: +SKIP + x**2*y + x*y**2 + >>> init_session(order='old') #doctest: +SKIP + >>> x**2 + y**2 + x + y #doctest: +SKIP + x + y + x**2 + y**2 + >>> theta = Symbol('theta') #doctest: +SKIP + >>> theta #doctest: +SKIP + theta + >>> init_session(use_unicode=True) #doctest: +SKIP + >>> theta # doctest: +SKIP + \u03b8 + """ + import sys + + in_ipython = False + + if ipython is not False: + try: + import IPython + except ImportError: + if ipython is True: + raise RuntimeError("IPython is not available on this system") + ip = None + else: + try: + from IPython import get_ipython + ip = get_ipython() + except ImportError: + ip = None + in_ipython = bool(ip) + if ipython is None: + ipython = in_ipython + + if ipython is False: + ip = init_python_session() + mainloop = ip.interact + else: + ip = init_ipython_session(ip, argv=argv, auto_symbols=auto_symbols, + auto_int_to_Integer=auto_int_to_Integer) + + if version_tuple(IPython.__version__) >= version_tuple('0.11'): + # runsource is gone, use run_cell instead, which doesn't + # take a symbol arg. The second arg is `store_history`, + # and False means don't add the line to IPython's history. + ip.runsource = lambda src, symbol='exec': ip.run_cell(src, False) + + # Enable interactive plotting using pylab. + try: + ip.enable_pylab(import_all=False) + except Exception: + # Causes an import error if matplotlib is not installed. + # Causes other errors (depending on the backend) if there + # is no display, or if there is some problem in the + # backend, so we have a bare "except Exception" here + pass + if not in_ipython: + mainloop = ip.mainloop + + if auto_symbols and (not ipython or version_tuple(IPython.__version__) < version_tuple('0.11')): + raise RuntimeError("automatic construction of symbols is possible only in IPython 0.11 or above") + if auto_int_to_Integer and (not ipython or version_tuple(IPython.__version__) < version_tuple('0.11')): + raise RuntimeError("automatic int to Integer transformation is possible only in IPython 0.11 or above") + + _preexec_source = preexec_source + + ip.runsource(_preexec_source, symbol='exec') + init_printing(pretty_print=pretty_print, order=order, + use_unicode=use_unicode, use_latex=use_latex, ip=ip, + str_printer=str_printer, pretty_printer=pretty_printer, + latex_printer=latex_printer) + + message = _make_message(ipython, quiet, _preexec_source) + + if not in_ipython: + print(message) + mainloop() + sys.exit('Exiting ...') + else: + print(message) + import atexit + atexit.register(lambda: print("Exiting ...\n")) diff --git a/phivenv/Lib/site-packages/sympy/interactive/tests/__init__.py b/phivenv/Lib/site-packages/sympy/interactive/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..304bdcc629f704215f3f0e8c035883b4d0eb0169 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/test_interactive.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/test_interactive.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89a6c2f3c1b08e7de880afdff4d84065f3c297e4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/test_interactive.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/test_ipython.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/test_ipython.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..185d435c62ff0690447bceaf748d3e3ae405352c Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/interactive/tests/__pycache__/test_ipython.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/interactive/tests/test_interactive.py b/phivenv/Lib/site-packages/sympy/interactive/tests/test_interactive.py new file mode 100644 index 0000000000000000000000000000000000000000..3e088c42fd872c13849e593b04734158f5d1e5bc --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/interactive/tests/test_interactive.py @@ -0,0 +1,10 @@ +from sympy.interactive.session import int_to_Integer + + +def test_int_to_Integer(): + assert int_to_Integer("1 + 2.2 + 0x3 + 40") == \ + 'Integer (1 )+2.2 +Integer (0x3 )+Integer (40 )' + assert int_to_Integer("0b101") == 'Integer (0b101 )' + assert int_to_Integer("ab1 + 1 + '1 + 2'") == "ab1 +Integer (1 )+'1 + 2'" + assert int_to_Integer("(2 + \n3)") == '(Integer (2 )+\nInteger (3 ))' + assert int_to_Integer("2 + 2.0 + 2j + 2e-10") == 'Integer (2 )+2.0 +2j +2e-10 ' diff --git a/phivenv/Lib/site-packages/sympy/interactive/tests/test_ipython.py b/phivenv/Lib/site-packages/sympy/interactive/tests/test_ipython.py new file mode 100644 index 0000000000000000000000000000000000000000..ac4734406d2f1197732a9dcbdd94b2b34e9fe170 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/interactive/tests/test_ipython.py @@ -0,0 +1,278 @@ +"""Tests of tools for setting up interactive IPython sessions. """ + +from sympy.interactive.session import (init_ipython_session, + enable_automatic_symbols, enable_automatic_int_sympification) + +from sympy.core import Symbol, Rational, Integer +from sympy.external import import_module +from sympy.testing.pytest import raises + +# TODO: The code below could be made more granular with something like: +# +# @requires('IPython', version=">=1.0") +# def test_automatic_symbols(ipython): + +ipython = import_module("IPython", min_module_version="1.0") + +if not ipython: + #bin/test will not execute any tests now + disabled = True + +# WARNING: These tests will modify the existing IPython environment. IPython +# uses a single instance for its interpreter, so there is no way to isolate +# the test from another IPython session. It also means that if this test is +# run twice in the same Python session it will fail. This isn't usually a +# problem because the test suite is run in a subprocess by default, but if the +# tests are run with subprocess=False it can pollute the current IPython +# session. See the discussion in issue #15149. + +def test_automatic_symbols(): + # NOTE: Because of the way the hook works, you have to use run_cell(code, + # True). This means that the code must have no Out, or it will be printed + # during the tests. + app = init_ipython_session() + app.run_cell("from sympy import *") + + enable_automatic_symbols(app) + + symbol = "verylongsymbolname" + assert symbol not in app.user_ns + app.run_cell("a = %s" % symbol, True) + assert symbol not in app.user_ns + app.run_cell("a = type(%s)" % symbol, True) + assert app.user_ns['a'] == Symbol + app.run_cell("%s = Symbol('%s')" % (symbol, symbol), True) + assert symbol in app.user_ns + + # Check that built-in names aren't overridden + app.run_cell("a = all == __builtin__.all", True) + assert "all" not in app.user_ns + assert app.user_ns['a'] is True + + # Check that SymPy names aren't overridden + app.run_cell("import sympy") + app.run_cell("a = factorial == sympy.factorial", True) + assert app.user_ns['a'] is True + + +def test_int_to_Integer(): + # XXX: Warning, don't test with == here. 0.5 == Rational(1, 2) is True! + app = init_ipython_session() + app.run_cell("from sympy import Integer") + app.run_cell("a = 1") + assert isinstance(app.user_ns['a'], int) + + enable_automatic_int_sympification(app) + app.run_cell("a = 1/2") + assert isinstance(app.user_ns['a'], Rational) + app.run_cell("a = 1") + assert isinstance(app.user_ns['a'], Integer) + app.run_cell("a = int(1)") + assert isinstance(app.user_ns['a'], int) + app.run_cell("a = (1/\n2)") + assert app.user_ns['a'] == Rational(1, 2) + # TODO: How can we test that the output of a SyntaxError is the original + # input, not the transformed input? + + +def test_ipythonprinting(): + # Initialize and setup IPython session + app = init_ipython_session() + app.run_cell("ip = get_ipython()") + app.run_cell("inst = ip.instance()") + app.run_cell("format = inst.display_formatter.format") + app.run_cell("from sympy import Symbol") + + # Printing without printing extension + app.run_cell("a = format(Symbol('pi'))") + app.run_cell("a2 = format(Symbol('pi')**2)") + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + assert app.user_ns['a']['text/plain'] == "pi" + assert app.user_ns['a2']['text/plain'] == "pi**2" + else: + assert app.user_ns['a'][0]['text/plain'] == "pi" + assert app.user_ns['a2'][0]['text/plain'] == "pi**2" + + # Load printing extension + app.run_cell("from sympy import init_printing") + app.run_cell("init_printing()") + # Printing with printing extension + app.run_cell("a = format(Symbol('pi'))") + app.run_cell("a2 = format(Symbol('pi')**2)") + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + assert app.user_ns['a']['text/plain'] in ('\N{GREEK SMALL LETTER PI}', 'pi') + assert app.user_ns['a2']['text/plain'] in (' 2\n\N{GREEK SMALL LETTER PI} ', ' 2\npi ') + else: + assert app.user_ns['a'][0]['text/plain'] in ('\N{GREEK SMALL LETTER PI}', 'pi') + assert app.user_ns['a2'][0]['text/plain'] in (' 2\n\N{GREEK SMALL LETTER PI} ', ' 2\npi ') + + +def test_print_builtin_option(): + # Initialize and setup IPython session + app = init_ipython_session() + app.run_cell("ip = get_ipython()") + app.run_cell("inst = ip.instance()") + app.run_cell("format = inst.display_formatter.format") + app.run_cell("from sympy import Symbol") + app.run_cell("from sympy import init_printing") + + app.run_cell("a = format({Symbol('pi'): 3.14, Symbol('n_i'): 3})") + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + text = app.user_ns['a']['text/plain'] + raises(KeyError, lambda: app.user_ns['a']['text/latex']) + else: + text = app.user_ns['a'][0]['text/plain'] + raises(KeyError, lambda: app.user_ns['a'][0]['text/latex']) + # XXX: How can we make this ignore the terminal width? This test fails if + # the terminal is too narrow. + assert text in ("{pi: 3.14, n_i: 3}", + '{n\N{LATIN SUBSCRIPT SMALL LETTER I}: 3, \N{GREEK SMALL LETTER PI}: 3.14}', + "{n_i: 3, pi: 3.14}", + '{\N{GREEK SMALL LETTER PI}: 3.14, n\N{LATIN SUBSCRIPT SMALL LETTER I}: 3}') + + # If we enable the default printing, then the dictionary's should render + # as a LaTeX version of the whole dict: ${\pi: 3.14, n_i: 3}$ + app.run_cell("inst.display_formatter.formatters['text/latex'].enabled = True") + app.run_cell("init_printing(use_latex=True)") + app.run_cell("a = format({Symbol('pi'): 3.14, Symbol('n_i'): 3})") + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + text = app.user_ns['a']['text/plain'] + latex = app.user_ns['a']['text/latex'] + else: + text = app.user_ns['a'][0]['text/plain'] + latex = app.user_ns['a'][0]['text/latex'] + assert text in ("{pi: 3.14, n_i: 3}", + '{n\N{LATIN SUBSCRIPT SMALL LETTER I}: 3, \N{GREEK SMALL LETTER PI}: 3.14}', + "{n_i: 3, pi: 3.14}", + '{\N{GREEK SMALL LETTER PI}: 3.14, n\N{LATIN SUBSCRIPT SMALL LETTER I}: 3}') + assert latex == r'$\displaystyle \left\{ n_{i} : 3, \ \pi : 3.14\right\}$' + + # Objects with an _latex overload should also be handled by our tuple + # printer. + app.run_cell("""\ + class WithOverload: + def _latex(self, printer): + return r"\\LaTeX" + """) + app.run_cell("a = format((WithOverload(),))") + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + latex = app.user_ns['a']['text/latex'] + else: + latex = app.user_ns['a'][0]['text/latex'] + assert latex == r'$\displaystyle \left( \LaTeX,\right)$' + + app.run_cell("inst.display_formatter.formatters['text/latex'].enabled = True") + app.run_cell("init_printing(use_latex=True, print_builtin=False)") + app.run_cell("a = format({Symbol('pi'): 3.14, Symbol('n_i'): 3})") + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + text = app.user_ns['a']['text/plain'] + raises(KeyError, lambda: app.user_ns['a']['text/latex']) + else: + text = app.user_ns['a'][0]['text/plain'] + raises(KeyError, lambda: app.user_ns['a'][0]['text/latex']) + # Note : In Python 3 we have one text type: str which holds Unicode data + # and two byte types bytes and bytearray. + # Python 3.3.3 + IPython 0.13.2 gives: '{n_i: 3, pi: 3.14}' + # Python 3.3.3 + IPython 1.1.0 gives: '{n_i: 3, pi: 3.14}' + assert text in ("{pi: 3.14, n_i: 3}", "{n_i: 3, pi: 3.14}") + + +def test_builtin_containers(): + # Initialize and setup IPython session + app = init_ipython_session() + app.run_cell("ip = get_ipython()") + app.run_cell("inst = ip.instance()") + app.run_cell("format = inst.display_formatter.format") + app.run_cell("inst.display_formatter.formatters['text/latex'].enabled = True") + app.run_cell("from sympy import init_printing, Matrix") + app.run_cell('init_printing(use_latex=True, use_unicode=False)') + + # Make sure containers that shouldn't pretty print don't. + app.run_cell('a = format((True, False))') + app.run_cell('import sys') + app.run_cell('b = format(sys.flags)') + app.run_cell('c = format((Matrix([1, 2]),))') + # Deal with API change starting at IPython 1.0 + if int(ipython.__version__.split(".")[0]) < 1: + assert app.user_ns['a']['text/plain'] == '(True, False)' + assert 'text/latex' not in app.user_ns['a'] + assert app.user_ns['b']['text/plain'][:10] == 'sys.flags(' + assert 'text/latex' not in app.user_ns['b'] + assert app.user_ns['c']['text/plain'] == \ +"""\ + [1] \n\ +([ ],) + [2] \ +""" + assert app.user_ns['c']['text/latex'] == '$\\displaystyle \\left( \\left[\\begin{matrix}1\\\\2\\end{matrix}\\right],\\right)$' + else: + assert app.user_ns['a'][0]['text/plain'] == '(True, False)' + assert 'text/latex' not in app.user_ns['a'][0] + assert app.user_ns['b'][0]['text/plain'][:10] == 'sys.flags(' + assert 'text/latex' not in app.user_ns['b'][0] + assert app.user_ns['c'][0]['text/plain'] == \ +"""\ + [1] \n\ +([ ],) + [2] \ +""" + assert app.user_ns['c'][0]['text/latex'] == '$\\displaystyle \\left( \\left[\\begin{matrix}1\\\\2\\end{matrix}\\right],\\right)$' + +def test_matplotlib_bad_latex(): + # Initialize and setup IPython session + app = init_ipython_session() + app.run_cell("import IPython") + app.run_cell("ip = get_ipython()") + app.run_cell("inst = ip.instance()") + app.run_cell("format = inst.display_formatter.format") + app.run_cell("from sympy import init_printing, Matrix") + app.run_cell("init_printing(use_latex='matplotlib')") + + # The png formatter is not enabled by default in this context + app.run_cell("inst.display_formatter.formatters['image/png'].enabled = True") + + # Make sure no warnings are raised by IPython + app.run_cell("import warnings") + # IPython.core.formatters.FormatterWarning was introduced in IPython 2.0 + if int(ipython.__version__.split(".")[0]) < 2: + app.run_cell("warnings.simplefilter('error')") + else: + app.run_cell("warnings.simplefilter('error', IPython.core.formatters.FormatterWarning)") + + # This should not raise an exception + app.run_cell("a = format(Matrix([1, 2, 3]))") + + # issue 9799 + app.run_cell("from sympy import Piecewise, Symbol, Eq") + app.run_cell("x = Symbol('x'); pw = format(Piecewise((1, Eq(x, 0)), (0, True)))") + + +def test_override_repr_latex(): + # Initialize and setup IPython session + app = init_ipython_session() + app.run_cell("import IPython") + app.run_cell("ip = get_ipython()") + app.run_cell("inst = ip.instance()") + app.run_cell("format = inst.display_formatter.format") + app.run_cell("inst.display_formatter.formatters['text/latex'].enabled = True") + app.run_cell("from sympy import init_printing") + app.run_cell("from sympy import Symbol") + app.run_cell("init_printing(use_latex=True)") + app.run_cell("""\ + class SymbolWithOverload(Symbol): + def _repr_latex_(self): + return r"Hello " + super()._repr_latex_() + " world" + """) + app.run_cell("a = format(SymbolWithOverload('s'))") + + if int(ipython.__version__.split(".")[0]) < 1: + latex = app.user_ns['a']['text/latex'] + else: + latex = app.user_ns['a'][0]['text/latex'] + assert latex == r'Hello $\displaystyle s$ world' diff --git a/phivenv/Lib/site-packages/sympy/interactive/traversal.py b/phivenv/Lib/site-packages/sympy/interactive/traversal.py new file mode 100644 index 0000000000000000000000000000000000000000..1315ec4ef7868b666bb6b978b3d8b20442d100b0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/interactive/traversal.py @@ -0,0 +1,95 @@ +from sympy.core.basic import Basic +from sympy.printing import pprint + +import random + +def interactive_traversal(expr): + """Traverse a tree asking a user which branch to choose. """ + + RED, BRED = '\033[0;31m', '\033[1;31m' + GREEN, BGREEN = '\033[0;32m', '\033[1;32m' + YELLOW, BYELLOW = '\033[0;33m', '\033[1;33m' # noqa + BLUE, BBLUE = '\033[0;34m', '\033[1;34m' # noqa + MAGENTA, BMAGENTA = '\033[0;35m', '\033[1;35m'# noqa + CYAN, BCYAN = '\033[0;36m', '\033[1;36m' # noqa + END = '\033[0m' + + def cprint(*args): + print("".join(map(str, args)) + END) + + def _interactive_traversal(expr, stage): + if stage > 0: + print() + + cprint("Current expression (stage ", BYELLOW, stage, END, "):") + print(BCYAN) + pprint(expr) + print(END) + + if isinstance(expr, Basic): + if expr.is_Add: + args = expr.as_ordered_terms() + elif expr.is_Mul: + args = expr.as_ordered_factors() + else: + args = expr.args + elif hasattr(expr, "__iter__"): + args = list(expr) + else: + return expr + + n_args = len(args) + + if not n_args: + return expr + + for i, arg in enumerate(args): + cprint(GREEN, "[", BGREEN, i, GREEN, "] ", BLUE, type(arg), END) + pprint(arg) + print() + + if n_args == 1: + choices = '0' + else: + choices = '0-%d' % (n_args - 1) + + try: + choice = input("Your choice [%s,f,l,r,d,?]: " % choices) + except EOFError: + result = expr + print() + else: + if choice == '?': + cprint(RED, "%s - select subexpression with the given index" % + choices) + cprint(RED, "f - select the first subexpression") + cprint(RED, "l - select the last subexpression") + cprint(RED, "r - select a random subexpression") + cprint(RED, "d - done\n") + + result = _interactive_traversal(expr, stage) + elif choice in ('d', ''): + result = expr + elif choice == 'f': + result = _interactive_traversal(args[0], stage + 1) + elif choice == 'l': + result = _interactive_traversal(args[-1], stage + 1) + elif choice == 'r': + result = _interactive_traversal(random.choice(args), stage + 1) + else: + try: + choice = int(choice) + except ValueError: + cprint(BRED, + "Choice must be a number in %s range\n" % choices) + result = _interactive_traversal(expr, stage) + else: + if choice < 0 or choice >= n_args: + cprint(BRED, "Choice must be in %s range\n" % choices) + result = _interactive_traversal(expr, stage) + else: + result = _interactive_traversal(args[choice], stage + 1) + + return result + + return _interactive_traversal(expr, 0) diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__init__.py b/phivenv/Lib/site-packages/sympy/liealgebras/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d023d86f2c6f0c64d7ac460c50eedc355e78b21f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/__init__.py @@ -0,0 +1,3 @@ +from sympy.liealgebras.cartan_type import CartanType + +__all__ = ['CartanType'] diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f91599777ea1111713dc63cb229ea71aa3d5eef Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/cartan_matrix.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/cartan_matrix.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45409c0eaf14fa896f8077938f69e7f3885d5e8d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/cartan_matrix.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/cartan_type.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/cartan_type.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d545ec4ee395c12356eda635a8fe4ad5aaf2f33d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/cartan_type.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/dynkin_diagram.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/dynkin_diagram.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c56e08b91337760d0771327fc414c95c236a26a9 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/dynkin_diagram.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/root_system.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/root_system.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74f337f1bd718bc97bb26bfbf8c922f5fc098696 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/root_system.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_a.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_a.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e226d63d39fb773204c018b14017ea10462e107d Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_a.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_b.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_b.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b33c2641e7450fdb0d00cbfdfb2d616fe606fd4 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_b.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_c.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_c.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b13208e642f3aafd29a63c5181be3c14cadf545 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_c.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_d.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_d.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dc68e662c8d52fd646526a1835baf3ddf5f55fc Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_d.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_e.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_e.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9de9e3fb800374283960d755d08df330928faccb Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_e.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_f.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_f.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40ecaf769ac325f0f6788f966fc42549a1985cd1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_f.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_g.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_g.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b42cf0e8eb70002b8f11eaa462459432bf6b7f28 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/type_g.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/weyl_group.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/weyl_group.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91623f43c1fa579385bcb9475ac5a406cf1dfeba Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/__pycache__/weyl_group.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/cartan_matrix.py b/phivenv/Lib/site-packages/sympy/liealgebras/cartan_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..2d29b37bc9a1a26790ee88b5902951afe4fc4560 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/cartan_matrix.py @@ -0,0 +1,25 @@ +from .cartan_type import CartanType + +def CartanMatrix(ct): + """Access the Cartan matrix of a specific Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_matrix import CartanMatrix + >>> CartanMatrix("A2") + Matrix([ + [ 2, -1], + [-1, 2]]) + + >>> CartanMatrix(['C', 3]) + Matrix([ + [ 2, -1, 0], + [-1, 2, -1], + [ 0, -2, 2]]) + + This method works by returning the Cartan matrix + which corresponds to Cartan type t. + """ + + return CartanType(ct).cartan_matrix() diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/cartan_type.py b/phivenv/Lib/site-packages/sympy/liealgebras/cartan_type.py new file mode 100644 index 0000000000000000000000000000000000000000..16bb152469238ea912a30c2d0f8210d6f729bdb1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/cartan_type.py @@ -0,0 +1,73 @@ +from sympy.core import Atom, Basic + + +class CartanType_generator(): + """ + Constructor for actually creating things + """ + def __call__(self, *args): + c = args[0] + if isinstance(c, list): + letter, n = c[0], int(c[1]) + elif isinstance(c, str): + letter, n = c[0], int(c[1:]) + else: + raise TypeError("Argument must be a string (e.g. 'A3') or a list (e.g. ['A', 3])") + + if n < 0: + raise ValueError("Lie algebra rank cannot be negative") + if letter == "A": + from . import type_a + return type_a.TypeA(n) + if letter == "B": + from . import type_b + return type_b.TypeB(n) + + if letter == "C": + from . import type_c + return type_c.TypeC(n) + + if letter == "D": + from . import type_d + return type_d.TypeD(n) + + if letter == "E": + if n >= 6 and n <= 8: + from . import type_e + return type_e.TypeE(n) + + if letter == "F": + if n == 4: + from . import type_f + return type_f.TypeF(n) + + if letter == "G": + if n == 2: + from . import type_g + return type_g.TypeG(n) + +CartanType = CartanType_generator() + + +class Standard_Cartan(Atom): + """ + Concrete base class for Cartan types such as A4, etc + """ + + def __new__(cls, series, n): + obj = Basic.__new__(cls) + obj.n = n + obj.series = series + return obj + + def rank(self): + """ + Returns the rank of the Lie algebra + """ + return self.n + + def series(self): + """ + Returns the type of the Lie algebra + """ + return self.series diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/dynkin_diagram.py b/phivenv/Lib/site-packages/sympy/liealgebras/dynkin_diagram.py new file mode 100644 index 0000000000000000000000000000000000000000..cc9e2dac4d54490b803eeaf9637cb9b66b01f058 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/dynkin_diagram.py @@ -0,0 +1,24 @@ +from .cartan_type import CartanType + + +def DynkinDiagram(t): + """Display the Dynkin diagram of a given Lie algebra + + Works by generating the CartanType for the input, t, and then returning the + Dynkin diagram method from the individual classes. + + Examples + ======== + + >>> from sympy.liealgebras.dynkin_diagram import DynkinDiagram + >>> print(DynkinDiagram("A3")) + 0---0---0 + 1 2 3 + + >>> print(DynkinDiagram("B4")) + 0---0---0=>=0 + 1 2 3 4 + + """ + + return CartanType(t).dynkin_diagram() diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/root_system.py b/phivenv/Lib/site-packages/sympy/liealgebras/root_system.py new file mode 100644 index 0000000000000000000000000000000000000000..36eb24605e78bbdc669736910d89be5606df1389 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/root_system.py @@ -0,0 +1,196 @@ +from .cartan_type import CartanType +from sympy.core.basic import Atom + +class RootSystem(Atom): + """Represent the root system of a simple Lie algebra + + Every simple Lie algebra has a unique root system. To find the root + system, we first consider the Cartan subalgebra of g, which is the maximal + abelian subalgebra, and consider the adjoint action of g on this + subalgebra. There is a root system associated with this action. Now, a + root system over a vector space V is a set of finite vectors Phi (called + roots), which satisfy: + + 1. The roots span V + 2. The only scalar multiples of x in Phi are x and -x + 3. For every x in Phi, the set Phi is closed under reflection + through the hyperplane perpendicular to x. + 4. If x and y are roots in Phi, then the projection of y onto + the line through x is a half-integral multiple of x. + + Now, there is a subset of Phi, which we will call Delta, such that: + 1. Delta is a basis of V + 2. Each root x in Phi can be written x = sum k_y y for y in Delta + + The elements of Delta are called the simple roots. + Therefore, we see that the simple roots span the root space of a given + simple Lie algebra. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Root_system + .. [2] Lie Algebras and Representation Theory - Humphreys + + """ + + def __new__(cls, cartantype): + """Create a new RootSystem object + + This method assigns an attribute called cartan_type to each instance of + a RootSystem object. When an instance of RootSystem is called, it + needs an argument, which should be an instance of a simple Lie algebra. + We then take the CartanType of this argument and set it as the + cartan_type attribute of the RootSystem instance. + + """ + obj = Atom.__new__(cls) + obj.cartan_type = CartanType(cartantype) + return obj + + def simple_roots(self): + """Generate the simple roots of the Lie algebra + + The rank of the Lie algebra determines the number of simple roots that + it has. This method obtains the rank of the Lie algebra, and then uses + the simple_root method from the Lie algebra classes to generate all the + simple roots. + + Examples + ======== + + >>> from sympy.liealgebras.root_system import RootSystem + >>> c = RootSystem("A3") + >>> roots = c.simple_roots() + >>> roots + {1: [1, -1, 0, 0], 2: [0, 1, -1, 0], 3: [0, 0, 1, -1]} + + """ + n = self.cartan_type.rank() + roots = {i: self.cartan_type.simple_root(i) for i in range(1, n+1)} + return roots + + + def all_roots(self): + """Generate all the roots of a given root system + + The result is a dictionary where the keys are integer numbers. It + generates the roots by getting the dictionary of all positive roots + from the bases classes, and then taking each root, and multiplying it + by -1 and adding it to the dictionary. In this way all the negative + roots are generated. + + """ + alpha = self.cartan_type.positive_roots() + keys = list(alpha.keys()) + k = max(keys) + for val in keys: + k += 1 + root = alpha[val] + newroot = [-x for x in root] + alpha[k] = newroot + return alpha + + def root_space(self): + """Return the span of the simple roots + + The root space is the vector space spanned by the simple roots, i.e. it + is a vector space with a distinguished basis, the simple roots. This + method returns a string that represents the root space as the span of + the simple roots, alpha[1],...., alpha[n]. + + Examples + ======== + + >>> from sympy.liealgebras.root_system import RootSystem + >>> c = RootSystem("A3") + >>> c.root_space() + 'alpha[1] + alpha[2] + alpha[3]' + + """ + n = self.cartan_type.rank() + rs = " + ".join("alpha["+str(i) +"]" for i in range(1, n+1)) + return rs + + def add_simple_roots(self, root1, root2): + """Add two simple roots together + + The function takes as input two integers, root1 and root2. It then + uses these integers as keys in the dictionary of simple roots, and gets + the corresponding simple roots, and then adds them together. + + Examples + ======== + + >>> from sympy.liealgebras.root_system import RootSystem + >>> c = RootSystem("A3") + >>> newroot = c.add_simple_roots(1, 2) + >>> newroot + [1, 0, -1, 0] + + """ + + alpha = self.simple_roots() + if root1 > len(alpha) or root2 > len(alpha): + raise ValueError("You've used a root that doesn't exist!") + a1 = alpha[root1] + a2 = alpha[root2] + newroot = [_a1 + _a2 for _a1, _a2 in zip(a1, a2)] + return newroot + + def add_as_roots(self, root1, root2): + """Add two roots together if and only if their sum is also a root + + It takes as input two vectors which should be roots. It then computes + their sum and checks if it is in the list of all possible roots. If it + is, it returns the sum. Otherwise it returns a string saying that the + sum is not a root. + + Examples + ======== + + >>> from sympy.liealgebras.root_system import RootSystem + >>> c = RootSystem("A3") + >>> c.add_as_roots([1, 0, -1, 0], [0, 0, 1, -1]) + [1, 0, 0, -1] + >>> c.add_as_roots([1, -1, 0, 0], [0, 0, -1, 1]) + 'The sum of these two roots is not a root' + + """ + alpha = self.all_roots() + newroot = [r1 + r2 for r1, r2 in zip(root1, root2)] + if newroot in alpha.values(): + return newroot + else: + return "The sum of these two roots is not a root" + + + def cartan_matrix(self): + """Cartan matrix of Lie algebra associated with this root system + + Examples + ======== + + >>> from sympy.liealgebras.root_system import RootSystem + >>> c = RootSystem("A3") + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0], + [-1, 2, -1], + [ 0, -1, 2]]) + """ + return self.cartan_type.cartan_matrix() + + def dynkin_diagram(self): + """Dynkin diagram of the Lie algebra associated with this root system + + Examples + ======== + + >>> from sympy.liealgebras.root_system import RootSystem + >>> c = RootSystem("A3") + >>> print(c.dynkin_diagram()) + 0---0---0 + 1 2 3 + """ + return self.cartan_type.dynkin_diagram() diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__init__.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/__init__.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f981d545123e5dc158018e7bc3c74eee2a100cd0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/__init__.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_cartan_matrix.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_cartan_matrix.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..240a8825ee202feb683bcde12f8c174f2804bd2a Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_cartan_matrix.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_cartan_type.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_cartan_type.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d57903cdbe0ada537fe16df48981f46f04b2ef0b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_cartan_type.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_dynkin_diagram.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_dynkin_diagram.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64fdd4e94ac89145074b156eb28f5e570dda71a5 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_dynkin_diagram.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_root_system.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_root_system.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..658befb56cc8eda930a2495ac9d62be1a38cc812 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_root_system.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_A.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_A.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2eb16ced460a050c68b7175444b154042af29818 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_A.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_B.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_B.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7accdf7635802e5b7851cee6fdff425cbbc1872b Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_B.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_C.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_C.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f81dc182297232507fd01d03de02f3c7e38c2642 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_C.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_D.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_D.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2211459b2642e825fd9bc55c644588ecd80c2ad6 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_D.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_E.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_E.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89d20804b10ec7ea0c748665b0399e3e1cbab7ee Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_E.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_F.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_F.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3fd487b43f9b7dd0beda2ae4322092d283120f0 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_F.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_G.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_G.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8651e433e926118a5c10644dcd21b64894e95750 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_type_G.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_weyl_group.cpython-39.pyc b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_weyl_group.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f786c589d03a1e4298d0714819d3f3c786259dd1 Binary files /dev/null and b/phivenv/Lib/site-packages/sympy/liealgebras/tests/__pycache__/test_weyl_group.cpython-39.pyc differ diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_cartan_matrix.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_cartan_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..98b1793dee63e0e87c610768554a8388dfd641a1 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_cartan_matrix.py @@ -0,0 +1,10 @@ +from sympy.liealgebras.cartan_matrix import CartanMatrix +from sympy.matrices import Matrix + +def test_CartanMatrix(): + c = CartanMatrix("A3") + m = Matrix(3, 3, [2, -1, 0, -1, 2, -1, 0, -1, 2]) + assert c == m + a = CartanMatrix(["G",2]) + mt = Matrix(2, 2, [2, -1, -3, 2]) + assert a == mt diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_cartan_type.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_cartan_type.py new file mode 100644 index 0000000000000000000000000000000000000000..257eeca41d0f5f2eb240cc270f76d452848ed405 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_cartan_type.py @@ -0,0 +1,12 @@ +from sympy.liealgebras.cartan_type import CartanType, Standard_Cartan + +def test_Standard_Cartan(): + c = CartanType("A4") + assert c.rank() == 4 + assert c.series == "A" + m = Standard_Cartan("A", 2) + assert m.rank() == 2 + assert m.series == "A" + b = CartanType("B12") + assert b.rank() == 12 + assert b.series == "B" diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_dynkin_diagram.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_dynkin_diagram.py new file mode 100644 index 0000000000000000000000000000000000000000..ad2ee4c162945c437ecf83d75c7fef9455c9464a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_dynkin_diagram.py @@ -0,0 +1,9 @@ +from sympy.liealgebras.dynkin_diagram import DynkinDiagram + +def test_DynkinDiagram(): + c = DynkinDiagram("A3") + diag = "0---0---0\n1 2 3" + assert c == diag + ct = DynkinDiagram(["B", 3]) + diag2 = "0---0=>=0\n1 2 3" + assert ct == diag2 diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_root_system.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_root_system.py new file mode 100644 index 0000000000000000000000000000000000000000..42110da5a1c59a7e6b2e537ee13746bfce361579 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_root_system.py @@ -0,0 +1,18 @@ +from sympy.liealgebras.root_system import RootSystem +from sympy.liealgebras.type_a import TypeA +from sympy.matrices import Matrix + +def test_root_system(): + c = RootSystem("A3") + assert c.cartan_type == TypeA(3) + assert c.simple_roots() == {1: [1, -1, 0, 0], 2: [0, 1, -1, 0], 3: [0, 0, 1, -1]} + assert c.root_space() == "alpha[1] + alpha[2] + alpha[3]" + assert c.cartan_matrix() == Matrix([[ 2, -1, 0], [-1, 2, -1], [ 0, -1, 2]]) + assert c.dynkin_diagram() == "0---0---0\n1 2 3" + assert c.add_simple_roots(1, 2) == [1, 0, -1, 0] + assert c.all_roots() == {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], + 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], 5: [0, 1, 0, -1], + 6: [0, 0, 1, -1], 7: [-1, 1, 0, 0], 8: [-1, 0, 1, 0], + 9: [-1, 0, 0, 1], 10: [0, -1, 1, 0], + 11: [0, -1, 0, 1], 12: [0, 0, -1, 1]} + assert c.add_as_roots([1, 0, -1, 0], [0, 0, 1, -1]) == [1, 0, 0, -1] diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_A.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_A.py new file mode 100644 index 0000000000000000000000000000000000000000..85d6f451ee167cf6db17ab20e59efab86ac0b691 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_A.py @@ -0,0 +1,17 @@ +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix + +def test_type_A(): + c = CartanType("A3") + m = Matrix(3, 3, [2, -1, 0, -1, 2, -1, 0, -1, 2]) + assert m == c.cartan_matrix() + assert c.basis() == 8 + assert c.roots() == 12 + assert c.dimension() == 4 + assert c.simple_root(1) == [1, -1, 0, 0] + assert c.highest_root() == [1, 0, 0, -1] + assert c.lie_algebra() == "su(4)" + diag = "0---0---0\n1 2 3" + assert c.dynkin_diagram() == diag + assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], + 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_B.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_B.py new file mode 100644 index 0000000000000000000000000000000000000000..8f2a9011f96bc647e48d39e16cf10703a99d86b3 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_B.py @@ -0,0 +1,17 @@ +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix + +def test_type_B(): + c = CartanType("B3") + m = Matrix(3, 3, [2, -1, 0, -1, 2, -2, 0, -1, 2]) + assert m == c.cartan_matrix() + assert c.dimension() == 3 + assert c.roots() == 18 + assert c.simple_root(3) == [0, 0, 1] + assert c.basis() == 3 + assert c.lie_algebra() == "so(6)" + diag = "0---0=>=0\n1 2 3" + assert c.dynkin_diagram() == diag + assert c.positive_roots() == {1: [1, -1, 0], 2: [1, 1, 0], 3: [1, 0, -1], + 4: [1, 0, 1], 5: [0, 1, -1], 6: [0, 1, 1], 7: [1, 0, 0], + 8: [0, 1, 0], 9: [0, 0, 1]} diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_C.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_C.py new file mode 100644 index 0000000000000000000000000000000000000000..8154c201e6c50adb7c74458b240ed98b9a0dd123 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_C.py @@ -0,0 +1,22 @@ +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix + +def test_type_C(): + c = CartanType("C4") + m = Matrix(4, 4, [2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -2, 2]) + assert c.cartan_matrix() == m + assert c.dimension() == 4 + assert c.simple_root(4) == [0, 0, 0, 2] + assert c.roots() == 32 + assert c.basis() == 36 + assert c.lie_algebra() == "sp(8)" + t = CartanType(['C', 3]) + assert t.dimension() == 3 + diag = "0---0---0=<=0\n1 2 3 4" + assert c.dynkin_diagram() == diag + assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 1, 0, 0], + 3: [1, 0, -1, 0], 4: [1, 0, 1, 0], 5: [1, 0, 0, -1], + 6: [1, 0, 0, 1], 7: [0, 1, -1, 0], 8: [0, 1, 1, 0], + 9: [0, 1, 0, -1], 10: [0, 1, 0, 1], 11: [0, 0, 1, -1], + 12: [0, 0, 1, 1], 13: [2, 0, 0, 0], 14: [0, 2, 0, 0], 15: [0, 0, 2, 0], + 16: [0, 0, 0, 2]} diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_D.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_D.py new file mode 100644 index 0000000000000000000000000000000000000000..ddf6a34cb5be475cc30042e95bf8eae2376a2223 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_D.py @@ -0,0 +1,19 @@ +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix + + + +def test_type_D(): + c = CartanType("D4") + m = Matrix(4, 4, [2, -1, 0, 0, -1, 2, -1, -1, 0, -1, 2, 0, 0, -1, 0, 2]) + assert c.cartan_matrix() == m + assert c.basis() == 6 + assert c.lie_algebra() == "so(8)" + assert c.roots() == 24 + assert c.simple_root(3) == [0, 0, 1, -1] + diag = " 3\n 0\n |\n |\n0---0---0\n1 2 4" + assert diag == c.dynkin_diagram() + assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 1, 0, 0], + 3: [1, 0, -1, 0], 4: [1, 0, 1, 0], 5: [1, 0, 0, -1], 6: [1, 0, 0, 1], + 7: [0, 1, -1, 0], 8: [0, 1, 1, 0], 9: [0, 1, 0, -1], 10: [0, 1, 0, 1], + 11: [0, 0, 1, -1], 12: [0, 0, 1, 1]} diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_E.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_E.py new file mode 100644 index 0000000000000000000000000000000000000000..bdb08342f41ede3390f34e9b297864eda16bedc7 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_E.py @@ -0,0 +1,22 @@ +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix +from sympy.core.backend import Rational + +def test_type_E(): + c = CartanType("E6") + m = Matrix(6, 6, [2, 0, -1, 0, 0, 0, 0, 2, 0, -1, 0, 0, + -1, 0, 2, -1, 0, 0, 0, -1, -1, 2, -1, 0, 0, 0, 0, + -1, 2, -1, 0, 0, 0, 0, -1, 2]) + assert c.cartan_matrix() == m + assert c.dimension() == 8 + assert c.simple_root(6) == [0, 0, 0, -1, 1, 0, 0, 0] + assert c.roots() == 72 + assert c.basis() == 78 + diag = " "*8 + "2\n" + " "*8 + "0\n" + " "*8 + "|\n" + " "*8 + "|\n" + diag += "---".join("0" for i in range(1, 6))+"\n" + diag += "1 " + " ".join(str(i) for i in range(3, 7)) + assert c.dynkin_diagram() == diag + posroots = c.positive_roots() + assert posroots[8] == [1, 0, 0, 0, 1, 0, 0, 0] + assert posroots[21] == [Rational(1,2),Rational(1,2),Rational(1,2),Rational(1,2), + Rational(1,2),Rational(-1,2),Rational(-1,2),Rational(1,2)] diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_F.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_F.py new file mode 100644 index 0000000000000000000000000000000000000000..fbb58223d0b5886e6044108c9c5cc3bbf371dd14 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_F.py @@ -0,0 +1,24 @@ +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix +from sympy.core.backend import S + +def test_type_F(): + c = CartanType("F4") + m = Matrix(4, 4, [2, -1, 0, 0, -1, 2, -2, 0, 0, -1, 2, -1, 0, 0, -1, 2]) + assert c.cartan_matrix() == m + assert c.dimension() == 4 + assert c.simple_root(1) == [1, -1, 0, 0] + assert c.simple_root(2) == [0, 1, -1, 0] + assert c.simple_root(3) == [0, 0, 0, 1] + assert c.simple_root(4) == [-S.Half, -S.Half, -S.Half, -S.Half] + assert c.roots() == 48 + assert c.basis() == 52 + diag = "0---0=>=0---0\n" + " ".join(str(i) for i in range(1, 5)) + assert c.dynkin_diagram() == diag + assert c.positive_roots() == {1: [1, -1, 0, 0], 2: [1, 1, 0, 0], 3: [1, 0, -1, 0], + 4: [1, 0, 1, 0], 5: [1, 0, 0, -1], 6: [1, 0, 0, 1], 7: [0, 1, -1, 0], + 8: [0, 1, 1, 0], 9: [0, 1, 0, -1], 10: [0, 1, 0, 1], 11: [0, 0, 1, -1], + 12: [0, 0, 1, 1], 13: [1, 0, 0, 0], 14: [0, 1, 0, 0], 15: [0, 0, 1, 0], + 16: [0, 0, 0, 1], 17: [S.Half, S.Half, S.Half, S.Half], 18: [S.Half, -S.Half, S.Half, S.Half], + 19: [S.Half, S.Half, -S.Half, S.Half], 20: [S.Half, S.Half, S.Half, -S.Half], 21: [S.Half, S.Half, -S.Half, -S.Half], + 22: [S.Half, -S.Half, S.Half, -S.Half], 23: [S.Half, -S.Half, -S.Half, S.Half], 24: [S.Half, -S.Half, -S.Half, -S.Half]} diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_G.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_G.py new file mode 100644 index 0000000000000000000000000000000000000000..c427eeb85bad8fc77d17a1563a7b796d4e0f217f --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_type_G.py @@ -0,0 +1,16 @@ +# coding=utf-8 +from sympy.liealgebras.cartan_type import CartanType +from sympy.matrices import Matrix + +def test_type_G(): + c = CartanType("G2") + m = Matrix(2, 2, [2, -1, -3, 2]) + assert c.cartan_matrix() == m + assert c.simple_root(2) == [1, -2, 1] + assert c.basis() == 14 + assert c.roots() == 12 + assert c.dimension() == 3 + diag = "0≡<≡0\n1 2" + assert diag == c.dynkin_diagram() + assert c.positive_roots() == {1: [0, 1, -1], 2: [1, -2, 1], 3: [1, -1, 0], + 4: [1, 0, 1], 5: [1, 1, -2], 6: [2, -1, -1]} diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_weyl_group.py b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_weyl_group.py new file mode 100644 index 0000000000000000000000000000000000000000..e4e57246fdcb5a431d8bbd65f1f60e0254a9cdf0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/tests/test_weyl_group.py @@ -0,0 +1,35 @@ +from sympy.liealgebras.weyl_group import WeylGroup +from sympy.matrices import Matrix + +def test_weyl_group(): + c = WeylGroup("A3") + assert c.matrix_form('r1*r2') == Matrix([[0, 0, 1, 0], [1, 0, 0, 0], + [0, 1, 0, 0], [0, 0, 0, 1]]) + assert c.generators() == ['r1', 'r2', 'r3'] + assert c.group_order() == 24.0 + assert c.group_name() == "S4: the symmetric group acting on 4 elements." + assert c.coxeter_diagram() == "0---0---0\n1 2 3" + assert c.element_order('r1*r2*r3') == 4 + assert c.element_order('r1*r3*r2*r3') == 3 + d = WeylGroup("B5") + assert d.group_order() == 3840 + assert d.element_order('r1*r2*r4*r5') == 12 + assert d.matrix_form('r2*r3') == Matrix([[0, 0, 1, 0, 0], [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]]) + assert d.element_order('r1*r2*r1*r3*r5') == 6 + e = WeylGroup("D5") + assert e.element_order('r2*r3*r5') == 4 + assert e.matrix_form('r2*r3*r5') == Matrix([[1, 0, 0, 0, 0], [0, 0, 0, 0, -1], + [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, -1, 0]]) + f = WeylGroup("G2") + assert f.element_order('r1*r2*r1*r2') == 3 + assert f.element_order('r2*r1*r1*r2') == 1 + + assert f.matrix_form('r1*r2*r1*r2') == Matrix([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + g = WeylGroup("F4") + assert g.matrix_form('r2*r3') == Matrix([[1, 0, 0, 0], [0, 1, 0, 0], + [0, 0, 0, -1], [0, 0, 1, 0]]) + + assert g.element_order('r2*r3') == 4 + h = WeylGroup("E6") + assert h.group_order() == 51840 diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_a.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_a.py new file mode 100644 index 0000000000000000000000000000000000000000..96dc615366ae20d668d651620ac088f15751c50e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_a.py @@ -0,0 +1,164 @@ +from sympy.liealgebras.cartan_type import Standard_Cartan +from sympy.core.backend import eye + + +class TypeA(Standard_Cartan): + """ + This class contains the information about + the A series of simple Lie algebras. + ==== + """ + + def __new__(cls, n): + if n < 1: + raise ValueError("n cannot be less than 1") + return Standard_Cartan.__new__(cls, "A", n) + + + def dimension(self): + """Dimension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A4") + >>> c.dimension() + 5 + """ + return self.n+1 + + + def basic_root(self, i, j): + """ + This is a method just to generate roots + with a 1 iin the ith position and a -1 + in the jth position. + + """ + + n = self.n + root = [0]*(n+1) + root[i] = 1 + root[j] = -1 + return root + + def simple_root(self, i): + """ + Every lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + In A_n the ith simple root is the root which has a 1 + in the ith position, a -1 in the (i+1)th position, + and zeroes elsewhere. + + This method returns the ith simple root for the A series. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A4") + >>> c.simple_root(1) + [1, -1, 0, 0, 0] + + """ + + return self.basic_root(i-1, i) + + def positive_roots(self): + """ + This method generates all the positive roots of + A_n. This is half of all of the roots of A_n; + by multiplying all the positive roots by -1 we + get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + """ + + n = self.n + posroots = {} + k = 0 + for i in range(0, n): + for j in range(i+1, n+1): + k += 1 + posroots[k] = self.basic_root(i, j) + return posroots + + def highest_root(self): + """ + Returns the highest weight root for A_n + """ + + return self.basic_root(0, self.n) + + def roots(self): + """ + Returns the total number of roots for A_n + """ + n = self.n + return n*(n+1) + + def cartan_matrix(self): + """ + Returns the Cartan matrix for A_n. + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType('A4') + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0, 0], + [-1, 2, -1, 0], + [ 0, -1, 2, -1], + [ 0, 0, -1, 2]]) + + """ + + n = self.n + m = 2 * eye(n) + for i in range(1, n - 1): + m[i, i+1] = -1 + m[i, i-1] = -1 + m[0,1] = -1 + m[n-1, n-2] = -1 + return m + + def basis(self): + """ + Returns the number of independent generators of A_n + """ + n = self.n + return n**2 - 1 + + def lie_algebra(self): + """ + Returns the Lie algebra associated with A_n + """ + n = self.n + return "su(" + str(n + 1) + ")" + + def dynkin_diagram(self): + n = self.n + diag = "---".join("0" for i in range(1, n+1)) + "\n" + diag += " ".join(str(i) for i in range(1, n+1)) + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_b.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_b.py new file mode 100644 index 0000000000000000000000000000000000000000..c6ee85502261f4702769067c64021521a2bc1725 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_b.py @@ -0,0 +1,170 @@ +from .cartan_type import Standard_Cartan +from sympy.core.backend import eye + +class TypeB(Standard_Cartan): + + def __new__(cls, n): + if n < 2: + raise ValueError("n cannot be less than 2") + return Standard_Cartan.__new__(cls, "B", n) + + def dimension(self): + """Dimension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("B3") + >>> c.dimension() + 3 + """ + + return self.n + + def basic_root(self, i, j): + """ + This is a method just to generate roots + with a 1 iin the ith position and a -1 + in the jth position. + + """ + root = [0]*self.n + root[i] = 1 + root[j] = -1 + return root + + def simple_root(self, i): + """ + Every lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + In B_n the first n-1 simple roots are the same as the + roots in A_(n-1) (a 1 in the ith position, a -1 in + the (i+1)th position, and zeroes elsewhere). The n-th + simple root is the root with a 1 in the nth position + and zeroes elsewhere. + + This method returns the ith simple root for the B series. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("B3") + >>> c.simple_root(2) + [0, 1, -1] + + """ + n = self.n + if i < n: + return self.basic_root(i-1, i) + else: + root = [0]*self.n + root[n-1] = 1 + return root + + def positive_roots(self): + """ + This method generates all the positive roots of + A_n. This is half of all of the roots of B_n; + by multiplying all the positive roots by -1 we + get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + """ + + n = self.n + posroots = {} + k = 0 + for i in range(0, n-1): + for j in range(i+1, n): + k += 1 + posroots[k] = self.basic_root(i, j) + k += 1 + root = self.basic_root(i, j) + root[j] = 1 + posroots[k] = root + + for i in range(0, n): + k += 1 + root = [0]*n + root[i] = 1 + posroots[k] = root + + return posroots + + def roots(self): + """ + Returns the total number of roots for B_n" + """ + + n = self.n + return 2*(n**2) + + def cartan_matrix(self): + """ + Returns the Cartan matrix for B_n. + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType('B4') + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0, 0], + [-1, 2, -1, 0], + [ 0, -1, 2, -2], + [ 0, 0, -1, 2]]) + + """ + + n = self.n + m = 2* eye(n) + for i in range(1, n - 1): + m[i, i+1] = -1 + m[i, i-1] = -1 + m[0, 1] = -1 + m[n-2, n-1] = -2 + m[n-1, n-2] = -1 + return m + + def basis(self): + """ + Returns the number of independent generators of B_n + """ + + n = self.n + return (n**2 - n)/2 + + def lie_algebra(self): + """ + Returns the Lie algebra associated with B_n + """ + + n = self.n + return "so(" + str(2*n) + ")" + + def dynkin_diagram(self): + n = self.n + diag = "---".join("0" for i in range(1, n)) + "=>=0\n" + diag += " ".join(str(i) for i in range(1, n+1)) + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_c.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_c.py new file mode 100644 index 0000000000000000000000000000000000000000..615bb900b5ba9613fd02e43f476d34eef0d5d35c --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_c.py @@ -0,0 +1,169 @@ +from .cartan_type import Standard_Cartan +from sympy.core.backend import eye + +class TypeC(Standard_Cartan): + + def __new__(cls, n): + if n < 3: + raise ValueError("n cannot be less than 3") + return Standard_Cartan.__new__(cls, "C", n) + + + def dimension(self): + """Dimension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("C3") + >>> c.dimension() + 3 + """ + n = self.n + return n + + def basic_root(self, i, j): + """Generate roots with 1 in ith position and a -1 in jth position + """ + n = self.n + root = [0]*n + root[i] = 1 + root[j] = -1 + return root + + def simple_root(self, i): + """The ith simple root for the C series + + Every lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + In C_n, the first n-1 simple roots are the same as + the roots in A_(n-1) (a 1 in the ith position, a -1 + in the (i+1)th position, and zeroes elsewhere). The + nth simple root is the root in which there is a 2 in + the nth position and zeroes elsewhere. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("C3") + >>> c.simple_root(2) + [0, 1, -1] + + """ + + n = self.n + if i < n: + return self.basic_root(i-1,i) + else: + root = [0]*self.n + root[n-1] = 2 + return root + + + def positive_roots(self): + """Generates all the positive roots of A_n + + This is half of all of the roots of C_n; by multiplying all the + positive roots by -1 we get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + + """ + + n = self.n + posroots = {} + k = 0 + for i in range(0, n-1): + for j in range(i+1, n): + k += 1 + posroots[k] = self.basic_root(i, j) + k += 1 + root = self.basic_root(i, j) + root[j] = 1 + posroots[k] = root + + for i in range(0, n): + k += 1 + root = [0]*n + root[i] = 2 + posroots[k] = root + + return posroots + + def roots(self): + """ + Returns the total number of roots for C_n" + """ + + n = self.n + return 2*(n**2) + + def cartan_matrix(self): + """The Cartan matrix for C_n + + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType('C4') + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0, 0], + [-1, 2, -1, 0], + [ 0, -1, 2, -1], + [ 0, 0, -2, 2]]) + + """ + + n = self.n + m = 2 * eye(n) + for i in range(1, n - 1): + m[i, i+1] = -1 + m[i, i-1] = -1 + m[0,1] = -1 + m[n-1, n-2] = -2 + return m + + + def basis(self): + """ + Returns the number of independent generators of C_n + """ + + n = self.n + return n*(2*n + 1) + + def lie_algebra(self): + """ + Returns the Lie algebra associated with C_n" + """ + + n = self.n + return "sp(" + str(2*n) + ")" + + def dynkin_diagram(self): + n = self.n + diag = "---".join("0" for i in range(1, n)) + "=<=0\n" + diag += " ".join(str(i) for i in range(1, n+1)) + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_d.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_d.py new file mode 100644 index 0000000000000000000000000000000000000000..9450d76e906c79e23db0ce223ed0de03d71c1199 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_d.py @@ -0,0 +1,173 @@ +from .cartan_type import Standard_Cartan +from sympy.core.backend import eye + +class TypeD(Standard_Cartan): + + def __new__(cls, n): + if n < 3: + raise ValueError("n cannot be less than 3") + return Standard_Cartan.__new__(cls, "D", n) + + + def dimension(self): + """Dmension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("D4") + >>> c.dimension() + 4 + """ + + return self.n + + def basic_root(self, i, j): + """ + This is a method just to generate roots + with a 1 iin the ith position and a -1 + in the jth position. + + """ + + n = self.n + root = [0]*n + root[i] = 1 + root[j] = -1 + return root + + def simple_root(self, i): + """ + Every lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + In D_n, the first n-1 simple roots are the same as + the roots in A_(n-1) (a 1 in the ith position, a -1 + in the (i+1)th position, and zeroes elsewhere). + The nth simple root is the root in which there 1s in + the nth and (n-1)th positions, and zeroes elsewhere. + + This method returns the ith simple root for the D series. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("D4") + >>> c.simple_root(2) + [0, 1, -1, 0] + + """ + + n = self.n + if i < n: + return self.basic_root(i-1, i) + else: + root = [0]*n + root[n-2] = 1 + root[n-1] = 1 + return root + + + def positive_roots(self): + """ + This method generates all the positive roots of + A_n. This is half of all of the roots of D_n + by multiplying all the positive roots by -1 we + get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + """ + + n = self.n + posroots = {} + k = 0 + for i in range(0, n-1): + for j in range(i+1, n): + k += 1 + posroots[k] = self.basic_root(i, j) + k += 1 + root = self.basic_root(i, j) + root[j] = 1 + posroots[k] = root + return posroots + + def roots(self): + """ + Returns the total number of roots for D_n" + """ + + n = self.n + return 2*n*(n-1) + + def cartan_matrix(self): + """ + Returns the Cartan matrix for D_n. + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType('D4') + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0, 0], + [-1, 2, -1, -1], + [ 0, -1, 2, 0], + [ 0, -1, 0, 2]]) + + """ + + n = self.n + m = 2*eye(n) + for i in range(1, n - 2): + m[i,i+1] = -1 + m[i,i-1] = -1 + m[n-2, n-3] = -1 + m[n-3, n-1] = -1 + m[n-1, n-3] = -1 + m[0, 1] = -1 + return m + + def basis(self): + """ + Returns the number of independent generators of D_n + """ + n = self.n + return n*(n-1)/2 + + def lie_algebra(self): + """ + Returns the Lie algebra associated with D_n" + """ + + n = self.n + return "so(" + str(2*n) + ")" + + def dynkin_diagram(self): + n = self.n + diag = " "*4*(n-3) + str(n-1) + "\n" + diag += " "*4*(n-3) + "0\n" + diag += " "*4*(n-3) +"|\n" + diag += " "*4*(n-3) + "|\n" + diag += "---".join("0" for i in range(1,n)) + "\n" + diag += " ".join(str(i) for i in range(1, n-1)) + " "+str(n) + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_e.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_e.py new file mode 100644 index 0000000000000000000000000000000000000000..3db9a820d31bff31acc58ba1592a1b10f8be53db --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_e.py @@ -0,0 +1,275 @@ +import itertools + +from .cartan_type import Standard_Cartan +from sympy.core.backend import eye, Rational +from sympy.core.singleton import S + +class TypeE(Standard_Cartan): + + def __new__(cls, n): + if n < 6 or n > 8: + raise ValueError("Invalid value of n") + return Standard_Cartan.__new__(cls, "E", n) + + def dimension(self): + """Dimension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("E6") + >>> c.dimension() + 8 + """ + + return 8 + + def basic_root(self, i, j): + """ + This is a method just to generate roots + with a -1 in the ith position and a 1 + in the jth position. + + """ + + root = [0]*8 + root[i] = -1 + root[j] = 1 + return root + + def simple_root(self, i): + """ + Every Lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + This method returns the ith simple root for E_n. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("E6") + >>> c.simple_root(2) + [1, 1, 0, 0, 0, 0, 0, 0] + """ + n = self.n + if i == 1: + root = [-0.5]*8 + root[0] = 0.5 + root[7] = 0.5 + return root + elif i == 2: + root = [0]*8 + root[1] = 1 + root[0] = 1 + return root + else: + if i in (7, 8) and n == 6: + raise ValueError("E6 only has six simple roots!") + if i == 8 and n == 7: + raise ValueError("E7 only has seven simple roots!") + + return self.basic_root(i - 3, i - 2) + + def positive_roots(self): + """ + This method generates all the positive roots of + A_n. This is half of all of the roots of E_n; + by multiplying all the positive roots by -1 we + get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + """ + n = self.n + neghalf = Rational(-1, 2) + poshalf = S.Half + if n == 6: + posroots = {} + k = 0 + for i in range(n-1): + for j in range(i+1, n-1): + k += 1 + root = self.basic_root(i, j) + posroots[k] = root + k += 1 + root = self.basic_root(i, j) + root[i] = 1 + posroots[k] = root + + root = [poshalf, poshalf, poshalf, poshalf, poshalf, + neghalf, neghalf, poshalf] + for a, b, c, d, e in itertools.product( + range(2), range(2), range(2), range(2), range(2)): + if (a + b + c + d + e)%2 == 0: + k += 1 + if a == 1: + root[0] = neghalf + if b == 1: + root[1] = neghalf + if c == 1: + root[2] = neghalf + if d == 1: + root[3] = neghalf + if e == 1: + root[4] = neghalf + posroots[k] = root[:] + return posroots + if n == 7: + posroots = {} + k = 0 + for i in range(n-1): + for j in range(i+1, n-1): + k += 1 + root = self.basic_root(i, j) + posroots[k] = root + k += 1 + root = self.basic_root(i, j) + root[i] = 1 + posroots[k] = root + + k += 1 + posroots[k] = [0, 0, 0, 0, 0, 1, 1, 0] + root = [poshalf, poshalf, poshalf, poshalf, poshalf, + neghalf, neghalf, poshalf] + for a, b, c, d, e, f in itertools.product( + range(2), range(2), range(2), range(2), range(2), range(2)): + if (a + b + c + d + e + f)%2 == 0: + k += 1 + if a == 1: + root[0] = neghalf + if b == 1: + root[1] = neghalf + if c == 1: + root[2] = neghalf + if d == 1: + root[3] = neghalf + if e == 1: + root[4] = neghalf + if f == 1: + root[5] = poshalf + posroots[k] = root[:] + return posroots + if n == 8: + posroots = {} + k = 0 + for i in range(n): + for j in range(i+1, n): + k += 1 + root = self.basic_root(i, j) + posroots[k] = root + k += 1 + root = self.basic_root(i, j) + root[i] = 1 + posroots[k] = root + + root = [poshalf, poshalf, poshalf, poshalf, poshalf, + neghalf, neghalf, poshalf] + for a, b, c, d, e, f, g in itertools.product( + range(2), range(2), range(2), range(2), range(2), + range(2), range(2)): + if (a + b + c + d + e + f + g)%2 == 0: + k += 1 + if a == 1: + root[0] = neghalf + if b == 1: + root[1] = neghalf + if c == 1: + root[2] = neghalf + if d == 1: + root[3] = neghalf + if e == 1: + root[4] = neghalf + if f == 1: + root[5] = poshalf + if g == 1: + root[6] = poshalf + posroots[k] = root[:] + return posroots + + + + def roots(self): + """ + Returns the total number of roots of E_n + """ + + n = self.n + if n == 6: + return 72 + if n == 7: + return 126 + if n == 8: + return 240 + + + def cartan_matrix(self): + """ + Returns the Cartan matrix for G_2 + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType('A4') + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0, 0], + [-1, 2, -1, 0], + [ 0, -1, 2, -1], + [ 0, 0, -1, 2]]) + + + """ + + n = self.n + m = 2*eye(n) + for i in range(3, n - 1): + m[i, i+1] = -1 + m[i, i-1] = -1 + m[0, 2] = m[2, 0] = -1 + m[1, 3] = m[3, 1] = -1 + m[2, 3] = -1 + m[n-1, n-2] = -1 + return m + + + def basis(self): + """ + Returns the number of independent generators of E_n + """ + + n = self.n + if n == 6: + return 78 + if n == 7: + return 133 + if n == 8: + return 248 + + def dynkin_diagram(self): + n = self.n + diag = " "*8 + str(2) + "\n" + diag += " "*8 + "0\n" + diag += " "*8 + "|\n" + diag += " "*8 + "|\n" + diag += "---".join("0" for i in range(1, n)) + "\n" + diag += "1 " + " ".join(str(i) for i in range(3, n+1)) + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_f.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_f.py new file mode 100644 index 0000000000000000000000000000000000000000..f04da557870f2cd21818cf69c454ef598e2ab65a --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_f.py @@ -0,0 +1,162 @@ +from .cartan_type import Standard_Cartan +from sympy.core.backend import Matrix, Rational + + +class TypeF(Standard_Cartan): + + def __new__(cls, n): + if n != 4: + raise ValueError("n should be 4") + return Standard_Cartan.__new__(cls, "F", 4) + + def dimension(self): + """Dimension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("F4") + >>> c.dimension() + 4 + """ + + return 4 + + + def basic_root(self, i, j): + """Generate roots with 1 in ith position and -1 in jth position + + """ + + n = self.n + root = [0]*n + root[i] = 1 + root[j] = -1 + return root + + def simple_root(self, i): + """The ith simple root of F_4 + + Every lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("F4") + >>> c.simple_root(3) + [0, 0, 0, 1] + + """ + + if i < 3: + return self.basic_root(i-1, i) + if i == 3: + root = [0]*4 + root[3] = 1 + return root + if i == 4: + root = [Rational(-1, 2)]*4 + return root + + def positive_roots(self): + """Generate all the positive roots of A_n + + This is half of all of the roots of F_4; by multiplying all the + positive roots by -1 we get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + + """ + + n = self.n + posroots = {} + k = 0 + for i in range(0, n-1): + for j in range(i+1, n): + k += 1 + posroots[k] = self.basic_root(i, j) + k += 1 + root = self.basic_root(i, j) + root[j] = 1 + posroots[k] = root + + for i in range(0, n): + k += 1 + root = [0]*n + root[i] = 1 + posroots[k] = root + + k += 1 + root = [Rational(1, 2)]*n + posroots[k] = root + for i in range(1, 4): + k += 1 + root = [Rational(1, 2)]*n + root[i] = Rational(-1, 2) + posroots[k] = root + + posroots[k+1] = [Rational(1, 2), Rational(1, 2), Rational(-1, 2), Rational(-1, 2)] + posroots[k+2] = [Rational(1, 2), Rational(-1, 2), Rational(1, 2), Rational(-1, 2)] + posroots[k+3] = [Rational(1, 2), Rational(-1, 2), Rational(-1, 2), Rational(1, 2)] + posroots[k+4] = [Rational(1, 2), Rational(-1, 2), Rational(-1, 2), Rational(-1, 2)] + + return posroots + + + def roots(self): + """ + Returns the total number of roots for F_4 + """ + return 48 + + def cartan_matrix(self): + """The Cartan matrix for F_4 + + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType('A4') + >>> c.cartan_matrix() + Matrix([ + [ 2, -1, 0, 0], + [-1, 2, -1, 0], + [ 0, -1, 2, -1], + [ 0, 0, -1, 2]]) + """ + + m = Matrix( 4, 4, [2, -1, 0, 0, -1, 2, -2, 0, 0, + -1, 2, -1, 0, 0, -1, 2]) + return m + + def basis(self): + """ + Returns the number of independent generators of F_4 + """ + return 52 + + def dynkin_diagram(self): + diag = "0---0=>=0---0\n" + diag += " ".join(str(i) for i in range(1, 5)) + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/type_g.py b/phivenv/Lib/site-packages/sympy/liealgebras/type_g.py new file mode 100644 index 0000000000000000000000000000000000000000..014409cf5ed966b53c596b14e0073e89ceee05b6 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/type_g.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +from .cartan_type import Standard_Cartan +from sympy.core.backend import Matrix + +class TypeG(Standard_Cartan): + + def __new__(cls, n): + if n != 2: + raise ValueError("n should be 2") + return Standard_Cartan.__new__(cls, "G", 2) + + + def dimension(self): + """Dimension of the vector space V underlying the Lie algebra + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("G2") + >>> c.dimension() + 3 + """ + return 3 + + def simple_root(self, i): + """The ith simple root of G_2 + + Every lie algebra has a unique root system. + Given a root system Q, there is a subset of the + roots such that an element of Q is called a + simple root if it cannot be written as the sum + of two elements in Q. If we let D denote the + set of simple roots, then it is clear that every + element of Q can be written as a linear combination + of elements of D with all coefficients non-negative. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("G2") + >>> c.simple_root(1) + [0, 1, -1] + + """ + if i == 1: + return [0, 1, -1] + else: + return [1, -2, 1] + + def positive_roots(self): + """Generate all the positive roots of A_n + + This is half of all of the roots of A_n; by multiplying all the + positive roots by -1 we get the negative roots. + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("A3") + >>> c.positive_roots() + {1: [1, -1, 0, 0], 2: [1, 0, -1, 0], 3: [1, 0, 0, -1], 4: [0, 1, -1, 0], + 5: [0, 1, 0, -1], 6: [0, 0, 1, -1]} + + """ + + roots = {1: [0, 1, -1], 2: [1, -2, 1], 3: [1, -1, 0], 4: [1, 0, 1], + 5: [1, 1, -2], 6: [2, -1, -1]} + return roots + + def roots(self): + """ + Returns the total number of roots of G_2" + """ + return 12 + + def cartan_matrix(self): + """The Cartan matrix for G_2 + + The Cartan matrix matrix for a Lie algebra is + generated by assigning an ordering to the simple + roots, (alpha[1], ...., alpha[l]). Then the ijth + entry of the Cartan matrix is (). + + Examples + ======== + + >>> from sympy.liealgebras.cartan_type import CartanType + >>> c = CartanType("G2") + >>> c.cartan_matrix() + Matrix([ + [ 2, -1], + [-3, 2]]) + + """ + + m = Matrix( 2, 2, [2, -1, -3, 2]) + return m + + def basis(self): + """ + Returns the number of independent generators of G_2 + """ + return 14 + + def dynkin_diagram(self): + diag = "0≡<≡0\n1 2" + return diag diff --git a/phivenv/Lib/site-packages/sympy/liealgebras/weyl_group.py b/phivenv/Lib/site-packages/sympy/liealgebras/weyl_group.py new file mode 100644 index 0000000000000000000000000000000000000000..15ff70b6f1fc4649268a38ee13e1f717a1c9f5fa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/liealgebras/weyl_group.py @@ -0,0 +1,403 @@ +# -*- coding: utf-8 -*- + +from .cartan_type import CartanType +from mpmath import fac +from sympy.core.backend import Matrix, eye, Rational, igcd +from sympy.core.basic import Atom + +class WeylGroup(Atom): + + """ + For each semisimple Lie group, we have a Weyl group. It is a subgroup of + the isometry group of the root system. Specifically, it's the subgroup + that is generated by reflections through the hyperplanes orthogonal to + the roots. Therefore, Weyl groups are reflection groups, and so a Weyl + group is a finite Coxeter group. + + """ + + def __new__(cls, cartantype): + obj = Atom.__new__(cls) + obj.cartan_type = CartanType(cartantype) + return obj + + def generators(self): + """ + This method creates the generating reflections of the Weyl group for + a given Lie algebra. For a Lie algebra of rank n, there are n + different generating reflections. This function returns them as + a list. + + Examples + ======== + + >>> from sympy.liealgebras.weyl_group import WeylGroup + >>> c = WeylGroup("F4") + >>> c.generators() + ['r1', 'r2', 'r3', 'r4'] + """ + n = self.cartan_type.rank() + generators = [] + for i in range(1, n+1): + reflection = "r"+str(i) + generators.append(reflection) + return generators + + def group_order(self): + """ + This method returns the order of the Weyl group. + For types A, B, C, D, and E the order depends on + the rank of the Lie algebra. For types F and G, + the order is fixed. + + Examples + ======== + + >>> from sympy.liealgebras.weyl_group import WeylGroup + >>> c = WeylGroup("D4") + >>> c.group_order() + 192.0 + """ + n = self.cartan_type.rank() + if self.cartan_type.series == "A": + return fac(n+1) + + if self.cartan_type.series in ("B", "C"): + return fac(n)*(2**n) + + if self.cartan_type.series == "D": + return fac(n)*(2**(n-1)) + + if self.cartan_type.series == "E": + if n == 6: + return 51840 + if n == 7: + return 2903040 + if n == 8: + return 696729600 + if self.cartan_type.series == "F": + return 1152 + + if self.cartan_type.series == "G": + return 12 + + def group_name(self): + """ + This method returns some general information about the Weyl group for + a given Lie algebra. It returns the name of the group and the elements + it acts on, if relevant. + """ + n = self.cartan_type.rank() + if self.cartan_type.series == "A": + return "S"+str(n+1) + ": the symmetric group acting on " + str(n+1) + " elements." + + if self.cartan_type.series in ("B", "C"): + return "The hyperoctahedral group acting on " + str(2*n) + " elements." + + if self.cartan_type.series == "D": + return "The symmetry group of the " + str(n) + "-dimensional demihypercube." + + if self.cartan_type.series == "E": + if n == 6: + return "The symmetry group of the 6-polytope." + + if n == 7: + return "The symmetry group of the 7-polytope." + + if n == 8: + return "The symmetry group of the 8-polytope." + + if self.cartan_type.series == "F": + return "The symmetry group of the 24-cell, or icositetrachoron." + + if self.cartan_type.series == "G": + return "D6, the dihedral group of order 12, and symmetry group of the hexagon." + + def element_order(self, weylelt): + """ + This method returns the order of a given Weyl group element, which should + be specified by the user in the form of products of the generating + reflections, i.e. of the form r1*r2 etc. + + For types A-F, this method current works by taking the matrix form of + the specified element, and then finding what power of the matrix is the + identity. It then returns this power. + + Examples + ======== + + >>> from sympy.liealgebras.weyl_group import WeylGroup + >>> b = WeylGroup("B4") + >>> b.element_order('r1*r4*r2') + 4 + """ + n = self.cartan_type.rank() + if self.cartan_type.series == "A": + a = self.matrix_form(weylelt) + order = 1 + while a != eye(n+1): + a *= self.matrix_form(weylelt) + order += 1 + return order + + if self.cartan_type.series == "D": + a = self.matrix_form(weylelt) + order = 1 + while a != eye(n): + a *= self.matrix_form(weylelt) + order += 1 + return order + + if self.cartan_type.series == "E": + a = self.matrix_form(weylelt) + order = 1 + while a != eye(8): + a *= self.matrix_form(weylelt) + order += 1 + return order + + if self.cartan_type.series == "G": + elts = list(weylelt) + reflections = elts[1::3] + m = self.delete_doubles(reflections) + while self.delete_doubles(m) != m: + m = self.delete_doubles(m) + reflections = m + if len(reflections) % 2 == 1: + return 2 + + elif len(reflections) == 0: + return 1 + + else: + if len(reflections) == 1: + return 2 + else: + m = len(reflections) // 2 + lcm = (6 * m)/ igcd(m, 6) + order = lcm / m + return order + + + if self.cartan_type.series == 'F': + a = self.matrix_form(weylelt) + order = 1 + while a != eye(4): + a *= self.matrix_form(weylelt) + order += 1 + return order + + + if self.cartan_type.series in ("B", "C"): + a = self.matrix_form(weylelt) + order = 1 + while a != eye(n): + a *= self.matrix_form(weylelt) + order += 1 + return order + + def delete_doubles(self, reflections): + """ + This is a helper method for determining the order of an element in the + Weyl group of G2. It takes a Weyl element and if repeated simple reflections + in it, it deletes them. + """ + counter = 0 + copy = list(reflections) + for elt in copy: + if counter < len(copy)-1: + if copy[counter + 1] == elt: + del copy[counter] + del copy[counter] + counter += 1 + + + return copy + + + def matrix_form(self, weylelt): + """ + This method takes input from the user in the form of products of the + generating reflections, and returns the matrix corresponding to the + element of the Weyl group. Since each element of the Weyl group is + a reflection of some type, there is a corresponding matrix representation. + This method uses the standard representation for all the generating + reflections. + + Examples + ======== + + >>> from sympy.liealgebras.weyl_group import WeylGroup + >>> f = WeylGroup("F4") + >>> f.matrix_form('r2*r3') + Matrix([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, -1], + [0, 0, 1, 0]]) + + """ + elts = list(weylelt) + reflections = elts[1::3] + n = self.cartan_type.rank() + if self.cartan_type.series == 'A': + matrixform = eye(n+1) + for elt in reflections: + a = int(elt) + mat = eye(n+1) + mat[a-1, a-1] = 0 + mat[a-1, a] = 1 + mat[a, a-1] = 1 + mat[a, a] = 0 + matrixform *= mat + return matrixform + + if self.cartan_type.series == 'D': + matrixform = eye(n) + for elt in reflections: + a = int(elt) + mat = eye(n) + if a < n: + mat[a-1, a-1] = 0 + mat[a-1, a] = 1 + mat[a, a-1] = 1 + mat[a, a] = 0 + matrixform *= mat + else: + mat[n-2, n-1] = -1 + mat[n-2, n-2] = 0 + mat[n-1, n-2] = -1 + mat[n-1, n-1] = 0 + matrixform *= mat + return matrixform + + if self.cartan_type.series == 'G': + matrixform = eye(3) + for elt in reflections: + a = int(elt) + if a == 1: + gen1 = Matrix([[1, 0, 0], [0, 0, 1], [0, 1, 0]]) + matrixform *= gen1 + else: + gen2 = Matrix([[Rational(2, 3), Rational(2, 3), Rational(-1, 3)], + [Rational(2, 3), Rational(-1, 3), Rational(2, 3)], + [Rational(-1, 3), Rational(2, 3), Rational(2, 3)]]) + matrixform *= gen2 + return matrixform + + if self.cartan_type.series == 'F': + matrixform = eye(4) + for elt in reflections: + a = int(elt) + if a == 1: + mat = Matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) + matrixform *= mat + elif a == 2: + mat = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) + matrixform *= mat + elif a == 3: + mat = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]]) + matrixform *= mat + else: + + mat = Matrix([[Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2)], + [Rational(1, 2), Rational(1, 2), Rational(-1, 2), Rational(-1, 2)], + [Rational(1, 2), Rational(-1, 2), Rational(1, 2), Rational(-1, 2)], + [Rational(1, 2), Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]]) + matrixform *= mat + return matrixform + + if self.cartan_type.series == 'E': + matrixform = eye(8) + for elt in reflections: + a = int(elt) + if a == 1: + mat = Matrix([[Rational(3, 4), Rational(1, 4), Rational(1, 4), Rational(1, 4), + Rational(1, 4), Rational(1, 4), Rational(1, 4), Rational(-1, 4)], + [Rational(1, 4), Rational(3, 4), Rational(-1, 4), Rational(-1, 4), + Rational(-1, 4), Rational(-1, 4), Rational(1, 4), Rational(-1, 4)], + [Rational(1, 4), Rational(-1, 4), Rational(3, 4), Rational(-1, 4), + Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), Rational(1, 4)], + [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(3, 4), + Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), Rational(1, 4)], + [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), + Rational(3, 4), Rational(-1, 4), Rational(-1, 4), Rational(1, 4)], + [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), + Rational(-1, 4), Rational(3, 4), Rational(-1, 4), Rational(1, 4)], + [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), + Rational(-1, 4), Rational(-1, 4), Rational(-3, 4), Rational(1, 4)], + [Rational(1, 4), Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), + Rational(-1, 4), Rational(-1, 4), Rational(-1, 4), Rational(3, 4)]]) + matrixform *= mat + elif a == 2: + mat = eye(8) + mat[0, 0] = 0 + mat[0, 1] = -1 + mat[1, 0] = -1 + mat[1, 1] = 0 + matrixform *= mat + else: + mat = eye(8) + mat[a-3, a-3] = 0 + mat[a-3, a-2] = 1 + mat[a-2, a-3] = 1 + mat[a-2, a-2] = 0 + matrixform *= mat + return matrixform + + + if self.cartan_type.series in ("B", "C"): + matrixform = eye(n) + for elt in reflections: + a = int(elt) + mat = eye(n) + if a == 1: + mat[0, 0] = -1 + matrixform *= mat + else: + mat[a - 2, a - 2] = 0 + mat[a-2, a-1] = 1 + mat[a - 1, a - 2] = 1 + mat[a -1, a - 1] = 0 + matrixform *= mat + return matrixform + + + + def coxeter_diagram(self): + """ + This method returns the Coxeter diagram corresponding to a Weyl group. + The Coxeter diagram can be obtained from a Lie algebra's Dynkin diagram + by deleting all arrows; the Coxeter diagram is the undirected graph. + The vertices of the Coxeter diagram represent the generating reflections + of the Weyl group, $s_i$. An edge is drawn between $s_i$ and $s_j$ if the order + $m(i, j)$ of $s_is_j$ is greater than two. If there is one edge, the order + $m(i, j)$ is 3. If there are two edges, the order $m(i, j)$ is 4, and if there + are three edges, the order $m(i, j)$ is 6. + + Examples + ======== + + >>> from sympy.liealgebras.weyl_group import WeylGroup + >>> c = WeylGroup("B3") + >>> print(c.coxeter_diagram()) + 0---0===0 + 1 2 3 + """ + n = self.cartan_type.rank() + if self.cartan_type.series in ("A", "D", "E"): + return self.cartan_type.dynkin_diagram() + + if self.cartan_type.series in ("B", "C"): + diag = "---".join("0" for i in range(1, n)) + "===0\n" + diag += " ".join(str(i) for i in range(1, n+1)) + return diag + + if self.cartan_type.series == "F": + diag = "0---0===0---0\n" + diag += " ".join(str(i) for i in range(1, 5)) + return diag + + if self.cartan_type.series == "G": + diag = "0≡≡≡0\n1 2" + return diag diff --git a/phivenv/Lib/site-packages/sympy/logic/__init__.py b/phivenv/Lib/site-packages/sympy/logic/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cb26903a384e9df3a0f02a92c488c5442cee1486 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/logic/__init__.py @@ -0,0 +1,12 @@ +from .boolalg import (to_cnf, to_dnf, to_nnf, And, Or, Not, Xor, Nand, Nor, Implies, + Equivalent, ITE, POSform, SOPform, simplify_logic, bool_map, true, false, + gateinputcount) +from .inference import satisfiable + +__all__ = [ + 'to_cnf', 'to_dnf', 'to_nnf', 'And', 'Or', 'Not', 'Xor', 'Nand', 'Nor', + 'Implies', 'Equivalent', 'ITE', 'POSform', 'SOPform', 'simplify_logic', + 'bool_map', 'true', 'false', 'gateinputcount', + + 'satisfiable', +] diff --git a/phivenv/Lib/site-packages/sympy/logic/algorithms/dpll.py b/phivenv/Lib/site-packages/sympy/logic/algorithms/dpll.py new file mode 100644 index 0000000000000000000000000000000000000000..40e6802f7626c982a9a6cd7146baea3ac6b8b6e0 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/logic/algorithms/dpll.py @@ -0,0 +1,308 @@ +"""Implementation of DPLL algorithm + +Further improvements: eliminate calls to pl_true, implement branching rules, +efficient unit propagation. + +References: + - https://en.wikipedia.org/wiki/DPLL_algorithm + - https://www.researchgate.net/publication/242384772_Implementations_of_the_DPLL_Algorithm +""" + +from sympy.core.sorting import default_sort_key +from sympy.logic.boolalg import Or, Not, conjuncts, disjuncts, to_cnf, \ + to_int_repr, _find_predicates +from sympy.assumptions.cnf import CNF +from sympy.logic.inference import pl_true, literal_symbol + + +def dpll_satisfiable(expr): + """ + Check satisfiability of a propositional sentence. + It returns a model rather than True when it succeeds + + >>> from sympy.abc import A, B + >>> from sympy.logic.algorithms.dpll import dpll_satisfiable + >>> dpll_satisfiable(A & ~B) + {A: True, B: False} + >>> dpll_satisfiable(A & ~A) + False + + """ + if not isinstance(expr, CNF): + clauses = conjuncts(to_cnf(expr)) + else: + clauses = expr.clauses + if False in clauses: + return False + symbols = sorted(_find_predicates(expr), key=default_sort_key) + symbols_int_repr = set(range(1, len(symbols) + 1)) + clauses_int_repr = to_int_repr(clauses, symbols) + result = dpll_int_repr(clauses_int_repr, symbols_int_repr, {}) + if not result: + return result + output = {} + for key in result: + output.update({symbols[key - 1]: result[key]}) + return output + + +def dpll(clauses, symbols, model): + """ + Compute satisfiability in a partial model. + Clauses is an array of conjuncts. + + >>> from sympy.abc import A, B, D + >>> from sympy.logic.algorithms.dpll import dpll + >>> dpll([A, B, D], [A, B], {D: False}) + False + + """ + # compute DP kernel + P, value = find_unit_clause(clauses, model) + while P: + model.update({P: value}) + symbols.remove(P) + if not value: + P = ~P + clauses = unit_propagate(clauses, P) + P, value = find_unit_clause(clauses, model) + P, value = find_pure_symbol(symbols, clauses) + while P: + model.update({P: value}) + symbols.remove(P) + if not value: + P = ~P + clauses = unit_propagate(clauses, P) + P, value = find_pure_symbol(symbols, clauses) + # end DP kernel + unknown_clauses = [] + for c in clauses: + val = pl_true(c, model) + if val is False: + return False + if val is not True: + unknown_clauses.append(c) + if not unknown_clauses: + return model + if not clauses: + return model + P = symbols.pop() + model_copy = model.copy() + model.update({P: True}) + model_copy.update({P: False}) + symbols_copy = symbols[:] + return (dpll(unit_propagate(unknown_clauses, P), symbols, model) or + dpll(unit_propagate(unknown_clauses, Not(P)), symbols_copy, model_copy)) + + +def dpll_int_repr(clauses, symbols, model): + """ + Compute satisfiability in a partial model. + Arguments are expected to be in integer representation + + >>> from sympy.logic.algorithms.dpll import dpll_int_repr + >>> dpll_int_repr([{1}, {2}, {3}], {1, 2}, {3: False}) + False + + """ + # compute DP kernel + P, value = find_unit_clause_int_repr(clauses, model) + while P: + model.update({P: value}) + symbols.remove(P) + if not value: + P = -P + clauses = unit_propagate_int_repr(clauses, P) + P, value = find_unit_clause_int_repr(clauses, model) + P, value = find_pure_symbol_int_repr(symbols, clauses) + while P: + model.update({P: value}) + symbols.remove(P) + if not value: + P = -P + clauses = unit_propagate_int_repr(clauses, P) + P, value = find_pure_symbol_int_repr(symbols, clauses) + # end DP kernel + unknown_clauses = [] + for c in clauses: + val = pl_true_int_repr(c, model) + if val is False: + return False + if val is not True: + unknown_clauses.append(c) + if not unknown_clauses: + return model + P = symbols.pop() + model_copy = model.copy() + model.update({P: True}) + model_copy.update({P: False}) + symbols_copy = symbols.copy() + return (dpll_int_repr(unit_propagate_int_repr(unknown_clauses, P), symbols, model) or + dpll_int_repr(unit_propagate_int_repr(unknown_clauses, -P), symbols_copy, model_copy)) + +### helper methods for DPLL + + +def pl_true_int_repr(clause, model={}): + """ + Lightweight version of pl_true. + Argument clause represents the set of args of an Or clause. This is used + inside dpll_int_repr, it is not meant to be used directly. + + >>> from sympy.logic.algorithms.dpll import pl_true_int_repr + >>> pl_true_int_repr({1, 2}, {1: False}) + >>> pl_true_int_repr({1, 2}, {1: False, 2: False}) + False + + """ + result = False + for lit in clause: + if lit < 0: + p = model.get(-lit) + if p is not None: + p = not p + else: + p = model.get(lit) + if p is True: + return True + elif p is None: + result = None + return result + + +def unit_propagate(clauses, symbol): + """ + Returns an equivalent set of clauses + If a set of clauses contains the unit clause l, the other clauses are + simplified by the application of the two following rules: + + 1. every clause containing l is removed + 2. in every clause that contains ~l this literal is deleted + + Arguments are expected to be in CNF. + + >>> from sympy.abc import A, B, D + >>> from sympy.logic.algorithms.dpll import unit_propagate + >>> unit_propagate([A | B, D | ~B, B], B) + [D, B] + + """ + output = [] + for c in clauses: + if c.func != Or: + output.append(c) + continue + for arg in c.args: + if arg == ~symbol: + output.append(Or(*[x for x in c.args if x != ~symbol])) + break + if arg == symbol: + break + else: + output.append(c) + return output + + +def unit_propagate_int_repr(clauses, s): + """ + Same as unit_propagate, but arguments are expected to be in integer + representation + + >>> from sympy.logic.algorithms.dpll import unit_propagate_int_repr + >>> unit_propagate_int_repr([{1, 2}, {3, -2}, {2}], 2) + [{3}] + + """ + negated = {-s} + return [clause - negated for clause in clauses if s not in clause] + + +def find_pure_symbol(symbols, unknown_clauses): + """ + Find a symbol and its value if it appears only as a positive literal + (or only as a negative) in clauses. + + >>> from sympy.abc import A, B, D + >>> from sympy.logic.algorithms.dpll import find_pure_symbol + >>> find_pure_symbol([A, B, D], [A|~B,~B|~D,D|A]) + (A, True) + + """ + for sym in symbols: + found_pos, found_neg = False, False + for c in unknown_clauses: + if not found_pos and sym in disjuncts(c): + found_pos = True + if not found_neg and Not(sym) in disjuncts(c): + found_neg = True + if found_pos != found_neg: + return sym, found_pos + return None, None + + +def find_pure_symbol_int_repr(symbols, unknown_clauses): + """ + Same as find_pure_symbol, but arguments are expected + to be in integer representation + + >>> from sympy.logic.algorithms.dpll import find_pure_symbol_int_repr + >>> find_pure_symbol_int_repr({1,2,3}, + ... [{1, -2}, {-2, -3}, {3, 1}]) + (1, True) + + """ + all_symbols = set().union(*unknown_clauses) + found_pos = all_symbols.intersection(symbols) + found_neg = all_symbols.intersection([-s for s in symbols]) + for p in found_pos: + if -p not in found_neg: + return p, True + for p in found_neg: + if -p not in found_pos: + return -p, False + return None, None + + +def find_unit_clause(clauses, model): + """ + A unit clause has only 1 variable that is not bound in the model. + + >>> from sympy.abc import A, B, D + >>> from sympy.logic.algorithms.dpll import find_unit_clause + >>> find_unit_clause([A | B | D, B | ~D, A | ~B], {A:True}) + (B, False) + + """ + for clause in clauses: + num_not_in_model = 0 + for literal in disjuncts(clause): + sym = literal_symbol(literal) + if sym not in model: + num_not_in_model += 1 + P, value = sym, not isinstance(literal, Not) + if num_not_in_model == 1: + return P, value + return None, None + + +def find_unit_clause_int_repr(clauses, model): + """ + Same as find_unit_clause, but arguments are expected to be in + integer representation. + + >>> from sympy.logic.algorithms.dpll import find_unit_clause_int_repr + >>> find_unit_clause_int_repr([{1, 2, 3}, + ... {2, -3}, {1, -2}], {1: True}) + (2, False) + + """ + bound = set(model) | {-sym for sym in model} + for clause in clauses: + unbound = clause - bound + if len(unbound) == 1: + p = unbound.pop() + if p < 0: + return -p, False + else: + return p, True + return None, None diff --git a/phivenv/Lib/site-packages/sympy/logic/boolalg.py b/phivenv/Lib/site-packages/sympy/logic/boolalg.py new file mode 100644 index 0000000000000000000000000000000000000000..8e11a9b6361ac5d7e355d5d4fb176d8df443e07e --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/logic/boolalg.py @@ -0,0 +1,3587 @@ +""" +Boolean algebra module for SymPy +""" + +from __future__ import annotations +from typing import TYPE_CHECKING, overload, Any +from collections.abc import Iterable, Mapping + +from collections import defaultdict +from itertools import chain, combinations, product, permutations +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.containers import Tuple +from sympy.core.decorators import sympify_method_args, sympify_return +from sympy.core.function import Application, Derivative +from sympy.core.kind import BooleanKind, NumberKind +from sympy.core.numbers import Number +from sympy.core.operations import LatticeOp +from sympy.core.singleton import Singleton, S +from sympy.core.sorting import ordered +from sympy.core.sympify import _sympy_converter, _sympify, sympify +from sympy.utilities.iterables import sift, ibin +from sympy.utilities.misc import filldedent + + +def as_Boolean(e): + """Like ``bool``, return the Boolean value of an expression, e, + which can be any instance of :py:class:`~.Boolean` or ``bool``. + + Examples + ======== + + >>> from sympy import true, false, nan + >>> from sympy.logic.boolalg import as_Boolean + >>> from sympy.abc import x + >>> as_Boolean(0) is false + True + >>> as_Boolean(1) is true + True + >>> as_Boolean(x) + x + >>> as_Boolean(2) + Traceback (most recent call last): + ... + TypeError: expecting bool or Boolean, not `2`. + >>> as_Boolean(nan) + Traceback (most recent call last): + ... + TypeError: expecting bool or Boolean, not `nan`. + + """ + from sympy.core.symbol import Symbol + if e == True: + return true + if e == False: + return false + if isinstance(e, Symbol): + z = e.is_zero + if z is None: + return e + return false if z else true + if isinstance(e, Boolean): + return e + raise TypeError('expecting bool or Boolean, not `%s`.' % e) + + +@sympify_method_args +class Boolean(Basic): + """A Boolean object is an object for which logic operations make sense.""" + + __slots__ = () + + kind = BooleanKind + + if TYPE_CHECKING: + + def __new__(cls, *args: Basic | complex) -> Boolean: + ... + + @overload # type: ignore + def subs(self, arg1: Mapping[Basic | complex, Boolean | complex], arg2: None=None) -> Boolean: ... + @overload + def subs(self, arg1: Iterable[tuple[Basic | complex, Boolean | complex]], arg2: None=None, **kwargs: Any) -> Boolean: ... + @overload + def subs(self, arg1: Boolean | complex, arg2: Boolean | complex) -> Boolean: ... + @overload + def subs(self, arg1: Mapping[Basic | complex, Basic | complex], arg2: None=None, **kwargs: Any) -> Basic: ... + @overload + def subs(self, arg1: Iterable[tuple[Basic | complex, Basic | complex]], arg2: None=None, **kwargs: Any) -> Basic: ... + @overload + def subs(self, arg1: Basic | complex, arg2: Basic | complex, **kwargs: Any) -> Basic: ... + + def subs(self, arg1: Mapping[Basic | complex, Basic | complex] | Basic | complex, # type: ignore + arg2: Basic | complex | None = None, **kwargs: Any) -> Basic: + ... + + def simplify(self, **kwargs) -> Boolean: + ... + + @sympify_return([('other', 'Boolean')], NotImplemented) + def __and__(self, other): + return And(self, other) + + __rand__ = __and__ + + @sympify_return([('other', 'Boolean')], NotImplemented) + def __or__(self, other): + return Or(self, other) + + __ror__ = __or__ + + def __invert__(self): + """Overloading for ~""" + return Not(self) + + @sympify_return([('other', 'Boolean')], NotImplemented) + def __rshift__(self, other): + return Implies(self, other) + + @sympify_return([('other', 'Boolean')], NotImplemented) + def __lshift__(self, other): + return Implies(other, self) + + __rrshift__ = __lshift__ + __rlshift__ = __rshift__ + + @sympify_return([('other', 'Boolean')], NotImplemented) + def __xor__(self, other): + return Xor(self, other) + + __rxor__ = __xor__ + + def equals(self, other): + """ + Returns ``True`` if the given formulas have the same truth table. + For two formulas to be equal they must have the same literals. + + Examples + ======== + + >>> from sympy.abc import A, B, C + >>> from sympy import And, Or, Not + >>> (A >> B).equals(~B >> ~A) + True + >>> Not(And(A, B, C)).equals(And(Not(A), Not(B), Not(C))) + False + >>> Not(And(A, Not(A))).equals(Or(B, Not(B))) + False + + """ + from sympy.logic.inference import satisfiable + from sympy.core.relational import Relational + + if self.has(Relational) or other.has(Relational): + raise NotImplementedError('handling of relationals') + return self.atoms() == other.atoms() and \ + not satisfiable(Not(Equivalent(self, other))) + + def to_nnf(self, simplify=True): + # override where necessary + return self + + def as_set(self): + """ + Rewrites Boolean expression in terms of real sets. + + Examples + ======== + + >>> from sympy import Symbol, Eq, Or, And + >>> x = Symbol('x', real=True) + >>> Eq(x, 0).as_set() + {0} + >>> (x > 0).as_set() + Interval.open(0, oo) + >>> And(-2 < x, x < 2).as_set() + Interval.open(-2, 2) + >>> Or(x < -2, 2 < x).as_set() + Union(Interval.open(-oo, -2), Interval.open(2, oo)) + + """ + from sympy.calculus.util import periodicity + from sympy.core.relational import Relational + + free = self.free_symbols + if len(free) == 1: + x = free.pop() + if x.kind is NumberKind: + reps = {} + for r in self.atoms(Relational): + if periodicity(r, x) not in (0, None): + s = r._eval_as_set() + if s in (S.EmptySet, S.UniversalSet, S.Reals): + reps[r] = s.as_relational(x) + continue + raise NotImplementedError(filldedent(''' + as_set is not implemented for relationals + with periodic solutions + ''')) + new = self.subs(reps) + if new.func != self.func: + return new.as_set() # restart with new obj + else: + return new._eval_as_set() + + return self._eval_as_set() + else: + raise NotImplementedError("Sorry, as_set has not yet been" + " implemented for multivariate" + " expressions") + + @property + def binary_symbols(self): + from sympy.core.relational import Eq, Ne + return set().union(*[i.binary_symbols for i in self.args + if i.is_Boolean or i.is_Symbol + or isinstance(i, (Eq, Ne))]) + + def _eval_refine(self, assumptions): + from sympy.assumptions import ask + ret = ask(self, assumptions) + if ret is True: + return true + elif ret is False: + return false + return None + + +class BooleanAtom(Boolean): + """ + Base class of :py:class:`~.BooleanTrue` and :py:class:`~.BooleanFalse`. + """ + is_Boolean = True + is_Atom = True + _op_priority = 11 # higher than Expr + + def simplify(self, *a, **kw): + return self + + def expand(self, *a, **kw): + return self + + @property + def canonical(self): + return self + + def _noop(self, other=None): + raise TypeError('BooleanAtom not allowed in this context.') + + __add__ = _noop + __radd__ = _noop + __sub__ = _noop + __rsub__ = _noop + __mul__ = _noop + __rmul__ = _noop + __pow__ = _noop + __rpow__ = _noop + __truediv__ = _noop + __rtruediv__ = _noop + __mod__ = _noop + __rmod__ = _noop + _eval_power = _noop + + def __lt__(self, other): + raise TypeError(filldedent(''' + A Boolean argument can only be used in + Eq and Ne; all other relationals expect + real expressions. + ''')) + + __le__ = __lt__ + __gt__ = __lt__ + __ge__ = __lt__ + # \\\ + + def _eval_simplify(self, **kwargs): + return self + + +class BooleanTrue(BooleanAtom, metaclass=Singleton): + """ + SymPy version of ``True``, a singleton that can be accessed via ``S.true``. + + This is the SymPy version of ``True``, for use in the logic module. The + primary advantage of using ``true`` instead of ``True`` is that shorthand Boolean + operations like ``~`` and ``>>`` will work as expected on this class, whereas with + True they act bitwise on 1. Functions in the logic module will return this + class when they evaluate to true. + + Notes + ===== + + There is liable to be some confusion as to when ``True`` should + be used and when ``S.true`` should be used in various contexts + throughout SymPy. An important thing to remember is that + ``sympify(True)`` returns ``S.true``. This means that for the most + part, you can just use ``True`` and it will automatically be converted + to ``S.true`` when necessary, similar to how you can generally use 1 + instead of ``S.One``. + + The rule of thumb is: + + "If the boolean in question can be replaced by an arbitrary symbolic + ``Boolean``, like ``Or(x, y)`` or ``x > 1``, use ``S.true``. + Otherwise, use ``True``" + + In other words, use ``S.true`` only on those contexts where the + boolean is being used as a symbolic representation of truth. + For example, if the object ends up in the ``.args`` of any expression, + then it must necessarily be ``S.true`` instead of ``True``, as + elements of ``.args`` must be ``Basic``. On the other hand, + ``==`` is not a symbolic operation in SymPy, since it always returns + ``True`` or ``False``, and does so in terms of structural equality + rather than mathematical, so it should return ``True``. The assumptions + system should use ``True`` and ``False``. Aside from not satisfying + the above rule of thumb, the assumptions system uses a three-valued logic + (``True``, ``False``, ``None``), whereas ``S.true`` and ``S.false`` + represent a two-valued logic. When in doubt, use ``True``. + + "``S.true == True is True``." + + While "``S.true is True``" is ``False``, "``S.true == True``" + is ``True``, so if there is any doubt over whether a function or + expression will return ``S.true`` or ``True``, just use ``==`` + instead of ``is`` to do the comparison, and it will work in either + case. Finally, for boolean flags, it's better to just use ``if x`` + instead of ``if x is True``. To quote PEP 8: + + Do not compare boolean values to ``True`` or ``False`` + using ``==``. + + * Yes: ``if greeting:`` + * No: ``if greeting == True:`` + * Worse: ``if greeting is True:`` + + Examples + ======== + + >>> from sympy import sympify, true, false, Or + >>> sympify(True) + True + >>> _ is True, _ is true + (False, True) + + >>> Or(true, false) + True + >>> _ is true + True + + Python operators give a boolean result for true but a + bitwise result for True + + >>> ~true, ~True # doctest: +SKIP + (False, -2) + >>> true >> true, True >> True + (True, 0) + + See Also + ======== + + sympy.logic.boolalg.BooleanFalse + + """ + def __bool__(self): + return True + + def __hash__(self): + return hash(True) + + def __eq__(self, other): + if other is True: + return True + if other is False: + return False + return super().__eq__(other) + + @property + def negated(self): + return false + + def as_set(self): + """ + Rewrite logic operators and relationals in terms of real sets. + + Examples + ======== + + >>> from sympy import true + >>> true.as_set() + UniversalSet + + """ + return S.UniversalSet + + +class BooleanFalse(BooleanAtom, metaclass=Singleton): + """ + SymPy version of ``False``, a singleton that can be accessed via ``S.false``. + + This is the SymPy version of ``False``, for use in the logic module. The + primary advantage of using ``false`` instead of ``False`` is that shorthand + Boolean operations like ``~`` and ``>>`` will work as expected on this class, + whereas with ``False`` they act bitwise on 0. Functions in the logic module + will return this class when they evaluate to false. + + Notes + ====== + + See the notes section in :py:class:`sympy.logic.boolalg.BooleanTrue` + + Examples + ======== + + >>> from sympy import sympify, true, false, Or + >>> sympify(False) + False + >>> _ is False, _ is false + (False, True) + + >>> Or(true, false) + True + >>> _ is true + True + + Python operators give a boolean result for false but a + bitwise result for False + + >>> ~false, ~False # doctest: +SKIP + (True, -1) + >>> false >> false, False >> False + (True, 0) + + See Also + ======== + + sympy.logic.boolalg.BooleanTrue + + """ + def __bool__(self): + return False + + def __hash__(self): + return hash(False) + + def __eq__(self, other): + if other is True: + return False + if other is False: + return True + return super().__eq__(other) + + @property + def negated(self): + return true + + def as_set(self): + """ + Rewrite logic operators and relationals in terms of real sets. + + Examples + ======== + + >>> from sympy import false + >>> false.as_set() + EmptySet + """ + return S.EmptySet + + +true = BooleanTrue() +false = BooleanFalse() +# We want S.true and S.false to work, rather than S.BooleanTrue and +# S.BooleanFalse, but making the class and instance names the same causes some +# major issues (like the inability to import the class directly from this +# file). +S.true = true +S.false = false + +_sympy_converter[bool] = lambda x: true if x else false + + +class BooleanFunction(Application, Boolean): + """Boolean function is a function that lives in a boolean space + It is used as base class for :py:class:`~.And`, :py:class:`~.Or`, + :py:class:`~.Not`, etc. + """ + is_Boolean = True + + def _eval_simplify(self, **kwargs): + rv = simplify_univariate(self) + if not isinstance(rv, BooleanFunction): + return rv.simplify(**kwargs) + rv = rv.func(*[a.simplify(**kwargs) for a in rv.args]) + return simplify_logic(rv) + + def simplify(self, **kwargs): + from sympy.simplify.simplify import simplify + return simplify(self, **kwargs) + + def __lt__(self, other): + raise TypeError(filldedent(''' + A Boolean argument can only be used in + Eq and Ne; all other relationals expect + real expressions. + ''')) + __le__ = __lt__ + __ge__ = __lt__ + __gt__ = __lt__ + + @classmethod + def binary_check_and_simplify(self, *args): + return [as_Boolean(i) for i in args] + + def to_nnf(self, simplify=True): + return self._to_nnf(*self.args, simplify=simplify) + + def to_anf(self, deep=True): + return self._to_anf(*self.args, deep=deep) + + @classmethod + def _to_nnf(cls, *args, **kwargs): + simplify = kwargs.get('simplify', True) + argset = set() + for arg in args: + if not is_literal(arg): + arg = arg.to_nnf(simplify) + if simplify: + if isinstance(arg, cls): + arg = arg.args + else: + arg = (arg,) + for a in arg: + if Not(a) in argset: + return cls.zero + argset.add(a) + else: + argset.add(arg) + return cls(*argset) + + @classmethod + def _to_anf(cls, *args, **kwargs): + deep = kwargs.get('deep', True) + new_args = [] + for arg in args: + if deep: + if not is_literal(arg) or isinstance(arg, Not): + arg = arg.to_anf(deep=deep) + new_args.append(arg) + return cls(*new_args, remove_true=False) + + # the diff method below is copied from Expr class + def diff(self, *symbols, **assumptions): + assumptions.setdefault("evaluate", True) + return Derivative(self, *symbols, **assumptions) + + def _eval_derivative(self, x): + if x in self.binary_symbols: + from sympy.core.relational import Eq + from sympy.functions.elementary.piecewise import Piecewise + return Piecewise( + (0, Eq(self.subs(x, 0), self.subs(x, 1))), + (1, True)) + elif x in self.free_symbols: + # not implemented, see https://www.encyclopediaofmath.org/ + # index.php/Boolean_differential_calculus + pass + else: + return S.Zero + + +class And(LatticeOp, BooleanFunction): + """ + Logical AND function. + + It evaluates its arguments in order, returning false immediately + when an argument is false and true if they are all true. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import And + >>> x & y + x & y + + Notes + ===== + + The ``&`` operator is provided as a convenience, but note that its use + here is different from its normal use in Python, which is bitwise + and. Hence, ``And(a, b)`` and ``a & b`` will produce different results if + ``a`` and ``b`` are integers. + + >>> And(x, y).subs(x, 1) + y + + """ + zero = false + identity = true + + nargs = None + + if TYPE_CHECKING: + + def __new__(cls, *args: Boolean | bool) -> Boolean: # type: ignore + ... + + @property + def args(self) -> tuple[Boolean, ...]: + ... + + @classmethod + def _new_args_filter(cls, args): + args = BooleanFunction.binary_check_and_simplify(*args) + args = LatticeOp._new_args_filter(args, And) + newargs = [] + rel = set() + for x in ordered(args): + if x.is_Relational: + c = x.canonical + if c in rel: + continue + elif c.negated.canonical in rel: + return [false] + else: + rel.add(c) + newargs.append(x) + return newargs + + def _eval_subs(self, old, new): + args = [] + bad = None + for i in self.args: + try: + i = i.subs(old, new) + except TypeError: + # store TypeError + if bad is None: + bad = i + continue + if i == False: + return false + elif i != True: + args.append(i) + if bad is not None: + # let it raise + bad.subs(old, new) + # If old is And, replace the parts of the arguments with new if all + # are there + if isinstance(old, And): + old_set = set(old.args) + if old_set.issubset(args): + args = set(args) - old_set + args.add(new) + + return self.func(*args) + + def _eval_simplify(self, **kwargs): + from sympy.core.relational import Equality, Relational + from sympy.solvers.solveset import linear_coeffs + # standard simplify + rv = super()._eval_simplify(**kwargs) + if not isinstance(rv, And): + return rv + + # simplify args that are equalities involving + # symbols so x == 0 & x == y -> x==0 & y == 0 + Rel, nonRel = sift(rv.args, lambda i: isinstance(i, Relational), + binary=True) + if not Rel: + return rv + eqs, other = sift(Rel, lambda i: isinstance(i, Equality), binary=True) + + measure = kwargs['measure'] + if eqs: + ratio = kwargs['ratio'] + reps = {} + sifted = {} + # group by length of free symbols + sifted = sift(ordered([ + (i.free_symbols, i) for i in eqs]), + lambda x: len(x[0])) + eqs = [] + nonlineqs = [] + while 1 in sifted: + for free, e in sifted.pop(1): + x = free.pop() + if (e.lhs != x or x in e.rhs.free_symbols) and x not in reps: + try: + m, b = linear_coeffs( + Add(e.lhs, -e.rhs, evaluate=False), x) + enew = e.func(x, -b/m) + if measure(enew) <= ratio*measure(e): + e = enew + else: + eqs.append(e) + continue + except ValueError: + pass + if x in reps: + eqs.append(e.subs(x, reps[x])) + elif e.lhs == x and x not in e.rhs.free_symbols: + reps[x] = e.rhs + eqs.append(e) + else: + # x is not yet identified, but may be later + nonlineqs.append(e) + resifted = defaultdict(list) + for k in sifted: + for f, e in sifted[k]: + e = e.xreplace(reps) + f = e.free_symbols + resifted[len(f)].append((f, e)) + sifted = resifted + for k in sifted: + eqs.extend([e for f, e in sifted[k]]) + nonlineqs = [ei.subs(reps) for ei in nonlineqs] + other = [ei.subs(reps) for ei in other] + rv = rv.func(*([i.canonical for i in (eqs + nonlineqs + other)] + nonRel)) + patterns = _simplify_patterns_and() + threeterm_patterns = _simplify_patterns_and3() + return _apply_patternbased_simplification(rv, patterns, + measure, false, + threeterm_patterns=threeterm_patterns) + + def _eval_as_set(self): + from sympy.sets.sets import Intersection + return Intersection(*[arg.as_set() for arg in self.args]) + + def _eval_rewrite_as_Nor(self, *args, **kwargs): + return Nor(*[Not(arg) for arg in self.args]) + + def to_anf(self, deep=True): + if deep: + result = And._to_anf(*self.args, deep=deep) + return distribute_xor_over_and(result) + return self + + +class Or(LatticeOp, BooleanFunction): + """ + Logical OR function + + It evaluates its arguments in order, returning true immediately + when an argument is true, and false if they are all false. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import Or + >>> x | y + x | y + + Notes + ===== + + The ``|`` operator is provided as a convenience, but note that its use + here is different from its normal use in Python, which is bitwise + or. Hence, ``Or(a, b)`` and ``a | b`` will return different things if + ``a`` and ``b`` are integers. + + >>> Or(x, y).subs(x, 0) + y + + """ + zero = true + identity = false + + if TYPE_CHECKING: + + def __new__(cls, *args: Boolean | bool) -> Boolean: # type: ignore + ... + + @property + def args(self) -> tuple[Boolean, ...]: + ... + + @classmethod + def _new_args_filter(cls, args): + newargs = [] + rel = [] + args = BooleanFunction.binary_check_and_simplify(*args) + for x in args: + if x.is_Relational: + c = x.canonical + if c in rel: + continue + nc = c.negated.canonical + if any(r == nc for r in rel): + return [true] + rel.append(c) + newargs.append(x) + return LatticeOp._new_args_filter(newargs, Or) + + def _eval_subs(self, old, new): + args = [] + bad = None + for i in self.args: + try: + i = i.subs(old, new) + except TypeError: + # store TypeError + if bad is None: + bad = i + continue + if i == True: + return true + elif i != False: + args.append(i) + if bad is not None: + # let it raise + bad.subs(old, new) + # If old is Or, replace the parts of the arguments with new if all + # are there + if isinstance(old, Or): + old_set = set(old.args) + if old_set.issubset(args): + args = set(args) - old_set + args.add(new) + + return self.func(*args) + + def _eval_as_set(self): + from sympy.sets.sets import Union + return Union(*[arg.as_set() for arg in self.args]) + + def _eval_rewrite_as_Nand(self, *args, **kwargs): + return Nand(*[Not(arg) for arg in self.args]) + + def _eval_simplify(self, **kwargs): + from sympy.core.relational import Le, Ge, Eq + lege = self.atoms(Le, Ge) + if lege: + reps = {i: self.func( + Eq(i.lhs, i.rhs), i.strict) for i in lege} + return self.xreplace(reps)._eval_simplify(**kwargs) + # standard simplify + rv = super()._eval_simplify(**kwargs) + if not isinstance(rv, Or): + return rv + patterns = _simplify_patterns_or() + return _apply_patternbased_simplification(rv, patterns, + kwargs['measure'], true) + + def to_anf(self, deep=True): + args = range(1, len(self.args) + 1) + args = (combinations(self.args, j) for j in args) + args = chain.from_iterable(args) # powerset + args = (And(*arg) for arg in args) + args = (to_anf(x, deep=deep) if deep else x for x in args) + return Xor(*list(args), remove_true=False) + + +class Not(BooleanFunction): + """ + Logical Not function (negation) + + + Returns ``true`` if the statement is ``false`` or ``False``. + Returns ``false`` if the statement is ``true`` or ``True``. + + Examples + ======== + + >>> from sympy import Not, And, Or + >>> from sympy.abc import x, A, B + >>> Not(True) + False + >>> Not(False) + True + >>> Not(And(True, False)) + True + >>> Not(Or(True, False)) + False + >>> Not(And(And(True, x), Or(x, False))) + ~x + >>> ~x + ~x + >>> Not(And(Or(A, B), Or(~A, ~B))) + ~((A | B) & (~A | ~B)) + + Notes + ===== + + - The ``~`` operator is provided as a convenience, but note that its use + here is different from its normal use in Python, which is bitwise + not. In particular, ``~a`` and ``Not(a)`` will be different if ``a`` is + an integer. Furthermore, since bools in Python subclass from ``int``, + ``~True`` is the same as ``~1`` which is ``-2``, which has a boolean + value of True. To avoid this issue, use the SymPy boolean types + ``true`` and ``false``. + + - As of Python 3.12, the bitwise not operator ``~`` used on a + Python ``bool`` is deprecated and will emit a warning. + + >>> from sympy import true + >>> ~True # doctest: +SKIP + -2 + >>> ~true + False + + """ + + is_Not = True + + @classmethod + def eval(cls, arg): + if isinstance(arg, Number) or arg in (True, False): + return false if arg else true + if arg.is_Not: + return arg.args[0] + # Simplify Relational objects. + if arg.is_Relational: + return arg.negated + + def _eval_as_set(self): + """ + Rewrite logic operators and relationals in terms of real sets. + + Examples + ======== + + >>> from sympy import Not, Symbol + >>> x = Symbol('x') + >>> Not(x > 0).as_set() + Interval(-oo, 0) + """ + return self.args[0].as_set().complement(S.Reals) + + def to_nnf(self, simplify=True): + if is_literal(self): + return self + + expr = self.args[0] + + func, args = expr.func, expr.args + + if func == And: + return Or._to_nnf(*[Not(arg) for arg in args], simplify=simplify) + + if func == Or: + return And._to_nnf(*[Not(arg) for arg in args], simplify=simplify) + + if func == Implies: + a, b = args + return And._to_nnf(a, Not(b), simplify=simplify) + + if func == Equivalent: + return And._to_nnf(Or(*args), Or(*[Not(arg) for arg in args]), + simplify=simplify) + + if func == Xor: + result = [] + for i in range(1, len(args)+1, 2): + for neg in combinations(args, i): + clause = [Not(s) if s in neg else s for s in args] + result.append(Or(*clause)) + return And._to_nnf(*result, simplify=simplify) + + if func == ITE: + a, b, c = args + return And._to_nnf(Or(a, Not(c)), Or(Not(a), Not(b)), simplify=simplify) + + raise ValueError("Illegal operator %s in expression" % func) + + def to_anf(self, deep=True): + return Xor._to_anf(true, self.args[0], deep=deep) + + +class Xor(BooleanFunction): + """ + Logical XOR (exclusive OR) function. + + + Returns True if an odd number of the arguments are True and the rest are + False. + + Returns False if an even number of the arguments are True and the rest are + False. + + Examples + ======== + + >>> from sympy.logic.boolalg import Xor + >>> from sympy import symbols + >>> x, y = symbols('x y') + >>> Xor(True, False) + True + >>> Xor(True, True) + False + >>> Xor(True, False, True, True, False) + True + >>> Xor(True, False, True, False) + False + >>> x ^ y + x ^ y + + Notes + ===== + + The ``^`` operator is provided as a convenience, but note that its use + here is different from its normal use in Python, which is bitwise xor. In + particular, ``a ^ b`` and ``Xor(a, b)`` will be different if ``a`` and + ``b`` are integers. + + >>> Xor(x, y).subs(y, 0) + x + + """ + def __new__(cls, *args, remove_true=True, **kwargs): + argset = set() + obj = super().__new__(cls, *args, **kwargs) + for arg in obj._args: + if isinstance(arg, Number) or arg in (True, False): + if arg: + arg = true + else: + continue + if isinstance(arg, Xor): + for a in arg.args: + argset.remove(a) if a in argset else argset.add(a) + elif arg in argset: + argset.remove(arg) + else: + argset.add(arg) + rel = [(r, r.canonical, r.negated.canonical) + for r in argset if r.is_Relational] + odd = False # is number of complimentary pairs odd? start 0 -> False + remove = [] + for i, (r, c, nc) in enumerate(rel): + for j in range(i + 1, len(rel)): + rj, cj = rel[j][:2] + if cj == nc: + odd = not odd + break + elif cj == c: + break + else: + continue + remove.append((r, rj)) + if odd: + argset.remove(true) if true in argset else argset.add(true) + for a, b in remove: + argset.remove(a) + argset.remove(b) + if len(argset) == 0: + return false + elif len(argset) == 1: + return argset.pop() + elif True in argset and remove_true: + argset.remove(True) + return Not(Xor(*argset)) + else: + obj._args = tuple(ordered(argset)) + obj._argset = frozenset(argset) + return obj + + # XXX: This should be cached on the object rather than using cacheit + # Maybe it can be computed in __new__? + @property # type: ignore + @cacheit + def args(self): + return tuple(ordered(self._argset)) + + def to_nnf(self, simplify=True): + args = [] + for i in range(0, len(self.args)+1, 2): + for neg in combinations(self.args, i): + clause = [Not(s) if s in neg else s for s in self.args] + args.append(Or(*clause)) + return And._to_nnf(*args, simplify=simplify) + + def _eval_rewrite_as_Or(self, *args, **kwargs): + a = self.args + return Or(*[_convert_to_varsSOP(x, self.args) + for x in _get_odd_parity_terms(len(a))]) + + def _eval_rewrite_as_And(self, *args, **kwargs): + a = self.args + return And(*[_convert_to_varsPOS(x, self.args) + for x in _get_even_parity_terms(len(a))]) + + def _eval_simplify(self, **kwargs): + # as standard simplify uses simplify_logic which writes things as + # And and Or, we only simplify the partial expressions before using + # patterns + rv = self.func(*[a.simplify(**kwargs) for a in self.args]) + rv = rv.to_anf() + if not isinstance(rv, Xor): # This shouldn't really happen here + return rv + patterns = _simplify_patterns_xor() + return _apply_patternbased_simplification(rv, patterns, + kwargs['measure'], None) + + def _eval_subs(self, old, new): + # If old is Xor, replace the parts of the arguments with new if all + # are there + if isinstance(old, Xor): + old_set = set(old.args) + if old_set.issubset(self.args): + args = set(self.args) - old_set + args.add(new) + return self.func(*args) + + +class Nand(BooleanFunction): + """ + Logical NAND function. + + It evaluates its arguments in order, giving True immediately if any + of them are False, and False if they are all True. + + Returns True if any of the arguments are False + Returns False if all arguments are True + + Examples + ======== + + >>> from sympy.logic.boolalg import Nand + >>> from sympy import symbols + >>> x, y = symbols('x y') + >>> Nand(False, True) + True + >>> Nand(True, True) + False + >>> Nand(x, y) + ~(x & y) + + """ + @classmethod + def eval(cls, *args): + return Not(And(*args)) + + +class Nor(BooleanFunction): + """ + Logical NOR function. + + It evaluates its arguments in order, giving False immediately if any + of them are True, and True if they are all False. + + Returns False if any argument is True + Returns True if all arguments are False + + Examples + ======== + + >>> from sympy.logic.boolalg import Nor + >>> from sympy import symbols + >>> x, y = symbols('x y') + + >>> Nor(True, False) + False + >>> Nor(True, True) + False + >>> Nor(False, True) + False + >>> Nor(False, False) + True + >>> Nor(x, y) + ~(x | y) + + """ + @classmethod + def eval(cls, *args): + return Not(Or(*args)) + + +class Xnor(BooleanFunction): + """ + Logical XNOR function. + + Returns False if an odd number of the arguments are True and the rest are + False. + + Returns True if an even number of the arguments are True and the rest are + False. + + Examples + ======== + + >>> from sympy.logic.boolalg import Xnor + >>> from sympy import symbols + >>> x, y = symbols('x y') + >>> Xnor(True, False) + False + >>> Xnor(True, True) + True + >>> Xnor(True, False, True, True, False) + False + >>> Xnor(True, False, True, False) + True + + """ + @classmethod + def eval(cls, *args): + return Not(Xor(*args)) + + +class Implies(BooleanFunction): + r""" + Logical implication. + + A implies B is equivalent to if A then B. Mathematically, it is written + as `A \Rightarrow B` and is equivalent to `\neg A \vee B` or ``~A | B``. + + Accepts two Boolean arguments; A and B. + Returns False if A is True and B is False + Returns True otherwise. + + Examples + ======== + + >>> from sympy.logic.boolalg import Implies + >>> from sympy import symbols + >>> x, y = symbols('x y') + + >>> Implies(True, False) + False + >>> Implies(False, False) + True + >>> Implies(True, True) + True + >>> Implies(False, True) + True + >>> x >> y + Implies(x, y) + >>> y << x + Implies(x, y) + + Notes + ===== + + The ``>>`` and ``<<`` operators are provided as a convenience, but note + that their use here is different from their normal use in Python, which is + bit shifts. Hence, ``Implies(a, b)`` and ``a >> b`` will return different + things if ``a`` and ``b`` are integers. In particular, since Python + considers ``True`` and ``False`` to be integers, ``True >> True`` will be + the same as ``1 >> 1``, i.e., 0, which has a truth value of False. To + avoid this issue, use the SymPy objects ``true`` and ``false``. + + >>> from sympy import true, false + >>> True >> False + 1 + >>> true >> false + False + + """ + @classmethod + def eval(cls, *args): + try: + newargs = [] + for x in args: + if isinstance(x, Number) or x in (0, 1): + newargs.append(bool(x)) + else: + newargs.append(x) + A, B = newargs + except ValueError: + raise ValueError( + "%d operand(s) used for an Implies " + "(pairs are required): %s" % (len(args), str(args))) + if A in (True, False) or B in (True, False): + return Or(Not(A), B) + elif A == B: + return true + elif A.is_Relational and B.is_Relational: + if A.canonical == B.canonical: + return true + if A.negated.canonical == B.canonical: + return B + else: + return Basic.__new__(cls, *args) + + def to_nnf(self, simplify=True): + a, b = self.args + return Or._to_nnf(Not(a), b, simplify=simplify) + + def to_anf(self, deep=True): + a, b = self.args + return Xor._to_anf(true, a, And(a, b), deep=deep) + + +class Equivalent(BooleanFunction): + """ + Equivalence relation. + + ``Equivalent(A, B)`` is True iff A and B are both True or both False. + + Returns True if all of the arguments are logically equivalent. + Returns False otherwise. + + For two arguments, this is equivalent to :py:class:`~.Xnor`. + + Examples + ======== + + >>> from sympy.logic.boolalg import Equivalent, And + >>> from sympy.abc import x + >>> Equivalent(False, False, False) + True + >>> Equivalent(True, False, False) + False + >>> Equivalent(x, And(x, True)) + True + + """ + def __new__(cls, *args, **options): + from sympy.core.relational import Relational + args = [_sympify(arg) for arg in args] + + argset = set(args) + for x in args: + if isinstance(x, Number) or x in [True, False]: # Includes 0, 1 + argset.discard(x) + argset.add(bool(x)) + rel = [] + for r in argset: + if isinstance(r, Relational): + rel.append((r, r.canonical, r.negated.canonical)) + remove = [] + for i, (r, c, nc) in enumerate(rel): + for j in range(i + 1, len(rel)): + rj, cj = rel[j][:2] + if cj == nc: + return false + elif cj == c: + remove.append((r, rj)) + break + for a, b in remove: + argset.remove(a) + argset.remove(b) + argset.add(True) + if len(argset) <= 1: + return true + if True in argset: + argset.discard(True) + return And(*argset) + if False in argset: + argset.discard(False) + return And(*[Not(arg) for arg in argset]) + _args = frozenset(argset) + obj = super().__new__(cls, _args) + obj._argset = _args + return obj + + # XXX: This should be cached on the object rather than using cacheit + # Maybe it can be computed in __new__? + @property # type: ignore + @cacheit + def args(self): + return tuple(ordered(self._argset)) + + def to_nnf(self, simplify=True): + args = [] + for a, b in zip(self.args, self.args[1:]): + args.append(Or(Not(a), b)) + args.append(Or(Not(self.args[-1]), self.args[0])) + return And._to_nnf(*args, simplify=simplify) + + def to_anf(self, deep=True): + a = And(*self.args) + b = And(*[to_anf(Not(arg), deep=False) for arg in self.args]) + b = distribute_xor_over_and(b) + return Xor._to_anf(a, b, deep=deep) + + +class ITE(BooleanFunction): + """ + If-then-else clause. + + ``ITE(A, B, C)`` evaluates and returns the result of B if A is true + else it returns the result of C. All args must be Booleans. + + From a logic gate perspective, ITE corresponds to a 2-to-1 multiplexer, + where A is the select signal. + + Examples + ======== + + >>> from sympy.logic.boolalg import ITE, And, Xor, Or + >>> from sympy.abc import x, y, z + >>> ITE(True, False, True) + False + >>> ITE(Or(True, False), And(True, True), Xor(True, True)) + True + >>> ITE(x, y, z) + ITE(x, y, z) + >>> ITE(True, x, y) + x + >>> ITE(False, x, y) + y + >>> ITE(x, y, y) + y + + Trying to use non-Boolean args will generate a TypeError: + + >>> ITE(True, [], ()) + Traceback (most recent call last): + ... + TypeError: expecting bool, Boolean or ITE, not `[]` + + """ + def __new__(cls, *args, **kwargs): + from sympy.core.relational import Eq, Ne + if len(args) != 3: + raise ValueError('expecting exactly 3 args') + a, b, c = args + # check use of binary symbols + if isinstance(a, (Eq, Ne)): + # in this context, we can evaluate the Eq/Ne + # if one arg is a binary symbol and the other + # is true/false + b, c = map(as_Boolean, (b, c)) + bin_syms = set().union(*[i.binary_symbols for i in (b, c)]) + if len(set(a.args) - bin_syms) == 1: + # one arg is a binary_symbols + _a = a + if a.lhs is true: + a = a.rhs + elif a.rhs is true: + a = a.lhs + elif a.lhs is false: + a = Not(a.rhs) + elif a.rhs is false: + a = Not(a.lhs) + else: + # binary can only equal True or False + a = false + if isinstance(_a, Ne): + a = Not(a) + else: + a, b, c = BooleanFunction.binary_check_and_simplify( + a, b, c) + rv = None + if kwargs.get('evaluate', True): + rv = cls.eval(a, b, c) + if rv is None: + rv = BooleanFunction.__new__(cls, a, b, c, evaluate=False) + return rv + + @classmethod + def eval(cls, *args): + from sympy.core.relational import Eq, Ne + # do the args give a singular result? + a, b, c = args + if isinstance(a, (Ne, Eq)): + _a = a + if true in a.args: + a = a.lhs if a.rhs is true else a.rhs + elif false in a.args: + a = Not(a.lhs) if a.rhs is false else Not(a.rhs) + else: + _a = None + if _a is not None and isinstance(_a, Ne): + a = Not(a) + if a is true: + return b + if a is false: + return c + if b == c: + return b + else: + # or maybe the results allow the answer to be expressed + # in terms of the condition + if b is true and c is false: + return a + if b is false and c is true: + return Not(a) + if [a, b, c] != args: + return cls(a, b, c, evaluate=False) + + def to_nnf(self, simplify=True): + a, b, c = self.args + return And._to_nnf(Or(Not(a), b), Or(a, c), simplify=simplify) + + def _eval_as_set(self): + return self.to_nnf().as_set() + + def _eval_rewrite_as_Piecewise(self, *args, **kwargs): + from sympy.functions.elementary.piecewise import Piecewise + return Piecewise((args[1], args[0]), (args[2], True)) + + +class Exclusive(BooleanFunction): + """ + True if only one or no argument is true. + + ``Exclusive(A, B, C)`` is equivalent to ``~(A & B) & ~(A & C) & ~(B & C)``. + + For two arguments, this is equivalent to :py:class:`~.Xor`. + + Examples + ======== + + >>> from sympy.logic.boolalg import Exclusive + >>> Exclusive(False, False, False) + True + >>> Exclusive(False, True, False) + True + >>> Exclusive(False, True, True) + False + + """ + @classmethod + def eval(cls, *args): + and_args = [] + for a, b in combinations(args, 2): + and_args.append(Not(And(a, b))) + return And(*and_args) + + +# end class definitions. Some useful methods + + +def conjuncts(expr): + """Return a list of the conjuncts in ``expr``. + + Examples + ======== + + >>> from sympy.logic.boolalg import conjuncts + >>> from sympy.abc import A, B + >>> conjuncts(A & B) + frozenset({A, B}) + >>> conjuncts(A | B) + frozenset({A | B}) + + """ + return And.make_args(expr) + + +def disjuncts(expr): + """Return a list of the disjuncts in ``expr``. + + Examples + ======== + + >>> from sympy.logic.boolalg import disjuncts + >>> from sympy.abc import A, B + >>> disjuncts(A | B) + frozenset({A, B}) + >>> disjuncts(A & B) + frozenset({A & B}) + + """ + return Or.make_args(expr) + + +def distribute_and_over_or(expr): + """ + Given a sentence ``expr`` consisting of conjunctions and disjunctions + of literals, return an equivalent sentence in CNF. + + Examples + ======== + + >>> from sympy.logic.boolalg import distribute_and_over_or, And, Or, Not + >>> from sympy.abc import A, B, C + >>> distribute_and_over_or(Or(A, And(Not(B), Not(C)))) + (A | ~B) & (A | ~C) + + """ + return _distribute((expr, And, Or)) + + +def distribute_or_over_and(expr): + """ + Given a sentence ``expr`` consisting of conjunctions and disjunctions + of literals, return an equivalent sentence in DNF. + + Note that the output is NOT simplified. + + Examples + ======== + + >>> from sympy.logic.boolalg import distribute_or_over_and, And, Or, Not + >>> from sympy.abc import A, B, C + >>> distribute_or_over_and(And(Or(Not(A), B), C)) + (B & C) | (C & ~A) + + """ + return _distribute((expr, Or, And)) + + +def distribute_xor_over_and(expr): + """ + Given a sentence ``expr`` consisting of conjunction and + exclusive disjunctions of literals, return an + equivalent exclusive disjunction. + + Note that the output is NOT simplified. + + Examples + ======== + + >>> from sympy.logic.boolalg import distribute_xor_over_and, And, Xor, Not + >>> from sympy.abc import A, B, C + >>> distribute_xor_over_and(And(Xor(Not(A), B), C)) + (B & C) ^ (C & ~A) + """ + return _distribute((expr, Xor, And)) + + +def _distribute(info): + """ + Distributes ``info[1]`` over ``info[2]`` with respect to ``info[0]``. + """ + if isinstance(info[0], info[2]): + for arg in info[0].args: + if isinstance(arg, info[1]): + conj = arg + break + else: + return info[0] + rest = info[2](*[a for a in info[0].args if a is not conj]) + return info[1](*list(map(_distribute, + [(info[2](c, rest), info[1], info[2]) + for c in conj.args])), remove_true=False) + elif isinstance(info[0], info[1]): + return info[1](*list(map(_distribute, + [(x, info[1], info[2]) + for x in info[0].args])), + remove_true=False) + else: + return info[0] + + +def to_anf(expr, deep=True): + r""" + Converts expr to Algebraic Normal Form (ANF). + + ANF is a canonical normal form, which means that two + equivalent formulas will convert to the same ANF. + + A logical expression is in ANF if it has the form + + .. math:: 1 \oplus a \oplus b \oplus ab \oplus abc + + i.e. it can be: + - purely true, + - purely false, + - conjunction of variables, + - exclusive disjunction. + + The exclusive disjunction can only contain true, variables + or conjunction of variables. No negations are permitted. + + If ``deep`` is ``False``, arguments of the boolean + expression are considered variables, i.e. only the + top-level expression is converted to ANF. + + Examples + ======== + >>> from sympy.logic.boolalg import And, Or, Not, Implies, Equivalent + >>> from sympy.logic.boolalg import to_anf + >>> from sympy.abc import A, B, C + >>> to_anf(Not(A)) + A ^ True + >>> to_anf(And(Or(A, B), Not(C))) + A ^ B ^ (A & B) ^ (A & C) ^ (B & C) ^ (A & B & C) + >>> to_anf(Implies(Not(A), Equivalent(B, C)), deep=False) + True ^ ~A ^ (~A & (Equivalent(B, C))) + + """ + expr = sympify(expr) + + if is_anf(expr): + return expr + return expr.to_anf(deep=deep) + + +def to_nnf(expr, simplify=True): + """ + Converts ``expr`` to Negation Normal Form (NNF). + + A logical expression is in NNF if it + contains only :py:class:`~.And`, :py:class:`~.Or` and :py:class:`~.Not`, + and :py:class:`~.Not` is applied only to literals. + If ``simplify`` is ``True``, the result contains no redundant clauses. + + Examples + ======== + + >>> from sympy.abc import A, B, C, D + >>> from sympy.logic.boolalg import Not, Equivalent, to_nnf + >>> to_nnf(Not((~A & ~B) | (C & D))) + (A | B) & (~C | ~D) + >>> to_nnf(Equivalent(A >> B, B >> A)) + (A | ~B | (A & ~B)) & (B | ~A | (B & ~A)) + + """ + if is_nnf(expr, simplify): + return expr + return expr.to_nnf(simplify) + + +def to_cnf(expr, simplify=False, force=False): + """ + Convert a propositional logical sentence ``expr`` to conjunctive normal + form: ``((A | ~B | ...) & (B | C | ...) & ...)``. + If ``simplify`` is ``True``, ``expr`` is evaluated to its simplest CNF + form using the Quine-McCluskey algorithm; this may take a long + time. If there are more than 8 variables the ``force`` flag must be set + to ``True`` to simplify (default is ``False``). + + Examples + ======== + + >>> from sympy.logic.boolalg import to_cnf + >>> from sympy.abc import A, B, D + >>> to_cnf(~(A | B) | D) + (D | ~A) & (D | ~B) + >>> to_cnf((A | B) & (A | ~A), True) + A | B + + """ + expr = sympify(expr) + if not isinstance(expr, BooleanFunction): + return expr + + if simplify: + if not force and len(_find_predicates(expr)) > 8: + raise ValueError(filldedent(''' + To simplify a logical expression with more + than 8 variables may take a long time and requires + the use of `force=True`.''')) + return simplify_logic(expr, 'cnf', True, force=force) + + # Don't convert unless we have to + if is_cnf(expr): + return expr + + expr = eliminate_implications(expr) + res = distribute_and_over_or(expr) + + return res + + +def to_dnf(expr, simplify=False, force=False): + """ + Convert a propositional logical sentence ``expr`` to disjunctive normal + form: ``((A & ~B & ...) | (B & C & ...) | ...)``. + If ``simplify`` is ``True``, ``expr`` is evaluated to its simplest DNF form using + the Quine-McCluskey algorithm; this may take a long + time. If there are more than 8 variables, the ``force`` flag must be set to + ``True`` to simplify (default is ``False``). + + Examples + ======== + + >>> from sympy.logic.boolalg import to_dnf + >>> from sympy.abc import A, B, C + >>> to_dnf(B & (A | C)) + (A & B) | (B & C) + >>> to_dnf((A & B) | (A & ~B) | (B & C) | (~B & C), True) + A | C + + """ + expr = sympify(expr) + if not isinstance(expr, BooleanFunction): + return expr + + if simplify: + if not force and len(_find_predicates(expr)) > 8: + raise ValueError(filldedent(''' + To simplify a logical expression with more + than 8 variables may take a long time and requires + the use of `force=True`.''')) + return simplify_logic(expr, 'dnf', True, force=force) + + # Don't convert unless we have to + if is_dnf(expr): + return expr + + expr = eliminate_implications(expr) + return distribute_or_over_and(expr) + + +def is_anf(expr): + r""" + Checks if ``expr`` is in Algebraic Normal Form (ANF). + + A logical expression is in ANF if it has the form + + .. math:: 1 \oplus a \oplus b \oplus ab \oplus abc + + i.e. it is purely true, purely false, conjunction of + variables or exclusive disjunction. The exclusive + disjunction can only contain true, variables or + conjunction of variables. No negations are permitted. + + Examples + ======== + + >>> from sympy.logic.boolalg import And, Not, Xor, true, is_anf + >>> from sympy.abc import A, B, C + >>> is_anf(true) + True + >>> is_anf(A) + True + >>> is_anf(And(A, B, C)) + True + >>> is_anf(Xor(A, Not(B))) + False + + """ + expr = sympify(expr) + + if is_literal(expr) and not isinstance(expr, Not): + return True + + if isinstance(expr, And): + for arg in expr.args: + if not arg.is_Symbol: + return False + return True + + elif isinstance(expr, Xor): + for arg in expr.args: + if isinstance(arg, And): + for a in arg.args: + if not a.is_Symbol: + return False + elif is_literal(arg): + if isinstance(arg, Not): + return False + else: + return False + return True + + else: + return False + + +def is_nnf(expr, simplified=True): + """ + Checks if ``expr`` is in Negation Normal Form (NNF). + + A logical expression is in NNF if it + contains only :py:class:`~.And`, :py:class:`~.Or` and :py:class:`~.Not`, + and :py:class:`~.Not` is applied only to literals. + If ``simplified`` is ``True``, checks if result contains no redundant clauses. + + Examples + ======== + + >>> from sympy.abc import A, B, C + >>> from sympy.logic.boolalg import Not, is_nnf + >>> is_nnf(A & B | ~C) + True + >>> is_nnf((A | ~A) & (B | C)) + False + >>> is_nnf((A | ~A) & (B | C), False) + True + >>> is_nnf(Not(A & B) | C) + False + >>> is_nnf((A >> B) & (B >> A)) + False + + """ + + expr = sympify(expr) + if is_literal(expr): + return True + + stack = [expr] + + while stack: + expr = stack.pop() + if expr.func in (And, Or): + if simplified: + args = expr.args + for arg in args: + if Not(arg) in args: + return False + stack.extend(expr.args) + + elif not is_literal(expr): + return False + + return True + + +def is_cnf(expr): + """ + Test whether or not an expression is in conjunctive normal form. + + Examples + ======== + + >>> from sympy.logic.boolalg import is_cnf + >>> from sympy.abc import A, B, C + >>> is_cnf(A | B | C) + True + >>> is_cnf(A & B & C) + True + >>> is_cnf((A & B) | C) + False + + """ + return _is_form(expr, And, Or) + + +def is_dnf(expr): + """ + Test whether or not an expression is in disjunctive normal form. + + Examples + ======== + + >>> from sympy.logic.boolalg import is_dnf + >>> from sympy.abc import A, B, C + >>> is_dnf(A | B | C) + True + >>> is_dnf(A & B & C) + True + >>> is_dnf((A & B) | C) + True + >>> is_dnf(A & (B | C)) + False + + """ + return _is_form(expr, Or, And) + + +def _is_form(expr, function1, function2): + """ + Test whether or not an expression is of the required form. + + """ + expr = sympify(expr) + + vals = function1.make_args(expr) if isinstance(expr, function1) else [expr] + for lit in vals: + if isinstance(lit, function2): + vals2 = function2.make_args(lit) if isinstance(lit, function2) else [lit] + for l in vals2: + if is_literal(l) is False: + return False + elif is_literal(lit) is False: + return False + + return True + + +def eliminate_implications(expr): + """ + Change :py:class:`~.Implies` and :py:class:`~.Equivalent` into + :py:class:`~.And`, :py:class:`~.Or`, and :py:class:`~.Not`. + That is, return an expression that is equivalent to ``expr``, but has only + ``&``, ``|``, and ``~`` as logical + operators. + + Examples + ======== + + >>> from sympy.logic.boolalg import Implies, Equivalent, \ + eliminate_implications + >>> from sympy.abc import A, B, C + >>> eliminate_implications(Implies(A, B)) + B | ~A + >>> eliminate_implications(Equivalent(A, B)) + (A | ~B) & (B | ~A) + >>> eliminate_implications(Equivalent(A, B, C)) + (A | ~C) & (B | ~A) & (C | ~B) + + """ + return to_nnf(expr, simplify=False) + + +def is_literal(expr): + """ + Returns True if expr is a literal, else False. + + Examples + ======== + + >>> from sympy import Or, Q + >>> from sympy.abc import A, B + >>> from sympy.logic.boolalg import is_literal + >>> is_literal(A) + True + >>> is_literal(~A) + True + >>> is_literal(Q.zero(A)) + True + >>> is_literal(A + B) + True + >>> is_literal(Or(A, B)) + False + + """ + from sympy.assumptions import AppliedPredicate + + if isinstance(expr, Not): + return is_literal(expr.args[0]) + elif expr in (True, False) or isinstance(expr, AppliedPredicate) or expr.is_Atom: + return True + elif not isinstance(expr, BooleanFunction) and all( + (isinstance(expr, AppliedPredicate) or a.is_Atom) for a in expr.args): + return True + return False + + +def to_int_repr(clauses, symbols): + """ + Takes clauses in CNF format and puts them into an integer representation. + + Examples + ======== + + >>> from sympy.logic.boolalg import to_int_repr + >>> from sympy.abc import x, y + >>> to_int_repr([x | y, y], [x, y]) == [{1, 2}, {2}] + True + + """ + + # Convert the symbol list into a dict + symbols = dict(zip(symbols, range(1, len(symbols) + 1))) + + def append_symbol(arg, symbols): + if isinstance(arg, Not): + return -symbols[arg.args[0]] + else: + return symbols[arg] + + return [{append_symbol(arg, symbols) for arg in Or.make_args(c)} + for c in clauses] + + +def term_to_integer(term): + """ + Return an integer corresponding to the base-2 digits given by *term*. + + Parameters + ========== + + term : a string or list of ones and zeros + + Examples + ======== + + >>> from sympy.logic.boolalg import term_to_integer + >>> term_to_integer([1, 0, 0]) + 4 + >>> term_to_integer('100') + 4 + + """ + + return int(''.join(list(map(str, list(term)))), 2) + + +integer_to_term = ibin # XXX could delete? + + +def truth_table(expr, variables, input=True): + """ + Return a generator of all possible configurations of the input variables, + and the result of the boolean expression for those values. + + Parameters + ========== + + expr : Boolean expression + + variables : list of variables + + input : bool (default ``True``) + Indicates whether to return the input combinations. + + Examples + ======== + + >>> from sympy.logic.boolalg import truth_table + >>> from sympy.abc import x,y + >>> table = truth_table(x >> y, [x, y]) + >>> for t in table: + ... print('{0} -> {1}'.format(*t)) + [0, 0] -> True + [0, 1] -> True + [1, 0] -> False + [1, 1] -> True + + >>> table = truth_table(x | y, [x, y]) + >>> list(table) + [([0, 0], False), ([0, 1], True), ([1, 0], True), ([1, 1], True)] + + If ``input`` is ``False``, ``truth_table`` returns only a list of truth values. + In this case, the corresponding input values of variables can be + deduced from the index of a given output. + + >>> from sympy.utilities.iterables import ibin + >>> vars = [y, x] + >>> values = truth_table(x >> y, vars, input=False) + >>> values = list(values) + >>> values + [True, False, True, True] + + >>> for i, value in enumerate(values): + ... print('{0} -> {1}'.format(list(zip( + ... vars, ibin(i, len(vars)))), value)) + [(y, 0), (x, 0)] -> True + [(y, 0), (x, 1)] -> False + [(y, 1), (x, 0)] -> True + [(y, 1), (x, 1)] -> True + + """ + variables = [sympify(v) for v in variables] + + expr = sympify(expr) + if not isinstance(expr, BooleanFunction) and not is_literal(expr): + return + + table = product((0, 1), repeat=len(variables)) + for term in table: + value = expr.xreplace(dict(zip(variables, term))) + + if input: + yield list(term), value + else: + yield value + + +def _check_pair(minterm1, minterm2): + """ + Checks if a pair of minterms differs by only one bit. If yes, returns + index, else returns `-1`. + """ + # Early termination seems to be faster than list comprehension, + # at least for large examples. + index = -1 + for x, i in enumerate(minterm1): # zip(minterm1, minterm2) is slower + if i != minterm2[x]: + if index == -1: + index = x + else: + return -1 + return index + + +def _convert_to_varsSOP(minterm, variables): + """ + Converts a term in the expansion of a function from binary to its + variable form (for SOP). + """ + temp = [variables[n] if val == 1 else Not(variables[n]) + for n, val in enumerate(minterm) if val != 3] + return And(*temp) + + +def _convert_to_varsPOS(maxterm, variables): + """ + Converts a term in the expansion of a function from binary to its + variable form (for POS). + """ + temp = [variables[n] if val == 0 else Not(variables[n]) + for n, val in enumerate(maxterm) if val != 3] + return Or(*temp) + + +def _convert_to_varsANF(term, variables): + """ + Converts a term in the expansion of a function from binary to its + variable form (for ANF). + + Parameters + ========== + + term : list of 1's and 0's (complementation pattern) + variables : list of variables + + """ + temp = [variables[n] for n, t in enumerate(term) if t == 1] + + if not temp: + return true + + return And(*temp) + + +def _get_odd_parity_terms(n): + """ + Returns a list of lists, with all possible combinations of n zeros and ones + with an odd number of ones. + """ + return [e for e in [ibin(i, n) for i in range(2**n)] if sum(e) % 2 == 1] + + +def _get_even_parity_terms(n): + """ + Returns a list of lists, with all possible combinations of n zeros and ones + with an even number of ones. + """ + return [e for e in [ibin(i, n) for i in range(2**n)] if sum(e) % 2 == 0] + + +def _simplified_pairs(terms): + """ + Reduces a set of minterms, if possible, to a simplified set of minterms + with one less variable in the terms using QM method. + """ + if not terms: + return [] + + simplified_terms = [] + todo = list(range(len(terms))) + + # Count number of ones as _check_pair can only potentially match if there + # is at most a difference of a single one + termdict = defaultdict(list) + for n, term in enumerate(terms): + ones = sum(1 for t in term if t == 1) + termdict[ones].append(n) + + variables = len(terms[0]) + for k in range(variables): + for i in termdict[k]: + for j in termdict[k+1]: + index = _check_pair(terms[i], terms[j]) + if index != -1: + # Mark terms handled + todo[i] = todo[j] = None + # Copy old term + newterm = terms[i][:] + # Set differing position to don't care + newterm[index] = 3 + # Add if not already there + if newterm not in simplified_terms: + simplified_terms.append(newterm) + + if simplified_terms: + # Further simplifications only among the new terms + simplified_terms = _simplified_pairs(simplified_terms) + + # Add remaining, non-simplified, terms + simplified_terms.extend([terms[i] for i in todo if i is not None]) + return simplified_terms + + +def _rem_redundancy(l1, terms): + """ + After the truth table has been sufficiently simplified, use the prime + implicant table method to recognize and eliminate redundant pairs, + and return the essential arguments. + """ + + if not terms: + return [] + + nterms = len(terms) + nl1 = len(l1) + + # Create dominating matrix + dommatrix = [[0]*nl1 for n in range(nterms)] + colcount = [0]*nl1 + rowcount = [0]*nterms + for primei, prime in enumerate(l1): + for termi, term in enumerate(terms): + # Check prime implicant covering term + if all(t == 3 or t == mt for t, mt in zip(prime, term)): + dommatrix[termi][primei] = 1 + colcount[primei] += 1 + rowcount[termi] += 1 + + # Keep track if anything changed + anythingchanged = True + # Then, go again + while anythingchanged: + anythingchanged = False + + for rowi in range(nterms): + # Still non-dominated? + if rowcount[rowi]: + row = dommatrix[rowi] + for row2i in range(nterms): + # Still non-dominated? + if rowi != row2i and rowcount[rowi] and (rowcount[rowi] <= rowcount[row2i]): + row2 = dommatrix[row2i] + if all(row2[n] >= row[n] for n in range(nl1)): + # row2 dominating row, remove row2 + rowcount[row2i] = 0 + anythingchanged = True + for primei, prime in enumerate(row2): + if prime: + # Make corresponding entry 0 + dommatrix[row2i][primei] = 0 + colcount[primei] -= 1 + + colcache = {} + + for coli in range(nl1): + # Still non-dominated? + if colcount[coli]: + if coli in colcache: + col = colcache[coli] + else: + col = [dommatrix[i][coli] for i in range(nterms)] + colcache[coli] = col + for col2i in range(nl1): + # Still non-dominated? + if coli != col2i and colcount[col2i] and (colcount[coli] >= colcount[col2i]): + if col2i in colcache: + col2 = colcache[col2i] + else: + col2 = [dommatrix[i][col2i] for i in range(nterms)] + colcache[col2i] = col2 + if all(col[n] >= col2[n] for n in range(nterms)): + # col dominating col2, remove col2 + colcount[col2i] = 0 + anythingchanged = True + for termi, term in enumerate(col2): + if term and dommatrix[termi][col2i]: + # Make corresponding entry 0 + dommatrix[termi][col2i] = 0 + rowcount[termi] -= 1 + + if not anythingchanged: + # Heuristically select the prime implicant covering most terms + maxterms = 0 + bestcolidx = -1 + for coli in range(nl1): + s = colcount[coli] + if s > maxterms: + bestcolidx = coli + maxterms = s + + # In case we found a prime implicant covering at least two terms + if bestcolidx != -1 and maxterms > 1: + for primei, prime in enumerate(l1): + if primei != bestcolidx: + for termi, term in enumerate(colcache[bestcolidx]): + if term and dommatrix[termi][primei]: + # Make corresponding entry 0 + dommatrix[termi][primei] = 0 + anythingchanged = True + rowcount[termi] -= 1 + colcount[primei] -= 1 + + return [l1[i] for i in range(nl1) if colcount[i]] + + +def _input_to_binlist(inputlist, variables): + binlist = [] + bits = len(variables) + for val in inputlist: + if isinstance(val, int): + binlist.append(ibin(val, bits)) + elif isinstance(val, dict): + nonspecvars = list(variables) + for key in val.keys(): + nonspecvars.remove(key) + for t in product((0, 1), repeat=len(nonspecvars)): + d = dict(zip(nonspecvars, t)) + d.update(val) + binlist.append([d[v] for v in variables]) + elif isinstance(val, (list, tuple)): + if len(val) != bits: + raise ValueError("Each term must contain {bits} bits as there are" + "\n{bits} variables (or be an integer)." + "".format(bits=bits)) + binlist.append(list(val)) + else: + raise TypeError("A term list can only contain lists," + " ints or dicts.") + return binlist + + +def SOPform(variables, minterms, dontcares=None): + """ + The SOPform function uses simplified_pairs and a redundant group- + eliminating algorithm to convert the list of all input combos that + generate '1' (the minterms) into the smallest sum-of-products form. + + The variables must be given as the first argument. + + Return a logical :py:class:`~.Or` function (i.e., the "sum of products" or + "SOP" form) that gives the desired outcome. If there are inputs that can + be ignored, pass them as a list, too. + + The result will be one of the (perhaps many) functions that satisfy + the conditions. + + Examples + ======== + + >>> from sympy.logic import SOPform + >>> from sympy import symbols + >>> w, x, y, z = symbols('w x y z') + >>> minterms = [[0, 0, 0, 1], [0, 0, 1, 1], + ... [0, 1, 1, 1], [1, 0, 1, 1], [1, 1, 1, 1]] + >>> dontcares = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 1]] + >>> SOPform([w, x, y, z], minterms, dontcares) + (y & z) | (~w & ~x) + + The terms can also be represented as integers: + + >>> minterms = [1, 3, 7, 11, 15] + >>> dontcares = [0, 2, 5] + >>> SOPform([w, x, y, z], minterms, dontcares) + (y & z) | (~w & ~x) + + They can also be specified using dicts, which does not have to be fully + specified: + + >>> minterms = [{w: 0, x: 1}, {y: 1, z: 1, x: 0}] + >>> SOPform([w, x, y, z], minterms) + (x & ~w) | (y & z & ~x) + + Or a combination: + + >>> minterms = [4, 7, 11, [1, 1, 1, 1]] + >>> dontcares = [{w : 0, x : 0, y: 0}, 5] + >>> SOPform([w, x, y, z], minterms, dontcares) + (w & y & z) | (~w & ~y) | (x & z & ~w) + + See also + ======== + + POSform + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm + .. [2] https://en.wikipedia.org/wiki/Don%27t-care_term + + """ + if not minterms: + return false + + variables = tuple(map(sympify, variables)) + + + minterms = _input_to_binlist(minterms, variables) + dontcares = _input_to_binlist((dontcares or []), variables) + for d in dontcares: + if d in minterms: + raise ValueError('%s in minterms is also in dontcares' % d) + + return _sop_form(variables, minterms, dontcares) + + +def _sop_form(variables, minterms, dontcares): + new = _simplified_pairs(minterms + dontcares) + essential = _rem_redundancy(new, minterms) + return Or(*[_convert_to_varsSOP(x, variables) for x in essential]) + + +def POSform(variables, minterms, dontcares=None): + """ + The POSform function uses simplified_pairs and a redundant-group + eliminating algorithm to convert the list of all input combinations + that generate '1' (the minterms) into the smallest product-of-sums form. + + The variables must be given as the first argument. + + Return a logical :py:class:`~.And` function (i.e., the "product of sums" + or "POS" form) that gives the desired outcome. If there are inputs that can + be ignored, pass them as a list, too. + + The result will be one of the (perhaps many) functions that satisfy + the conditions. + + Examples + ======== + + >>> from sympy.logic import POSform + >>> from sympy import symbols + >>> w, x, y, z = symbols('w x y z') + >>> minterms = [[0, 0, 0, 1], [0, 0, 1, 1], [0, 1, 1, 1], + ... [1, 0, 1, 1], [1, 1, 1, 1]] + >>> dontcares = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 1]] + >>> POSform([w, x, y, z], minterms, dontcares) + z & (y | ~w) + + The terms can also be represented as integers: + + >>> minterms = [1, 3, 7, 11, 15] + >>> dontcares = [0, 2, 5] + >>> POSform([w, x, y, z], minterms, dontcares) + z & (y | ~w) + + They can also be specified using dicts, which does not have to be fully + specified: + + >>> minterms = [{w: 0, x: 1}, {y: 1, z: 1, x: 0}] + >>> POSform([w, x, y, z], minterms) + (x | y) & (x | z) & (~w | ~x) + + Or a combination: + + >>> minterms = [4, 7, 11, [1, 1, 1, 1]] + >>> dontcares = [{w : 0, x : 0, y: 0}, 5] + >>> POSform([w, x, y, z], minterms, dontcares) + (w | x) & (y | ~w) & (z | ~y) + + See also + ======== + + SOPform + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Quine-McCluskey_algorithm + .. [2] https://en.wikipedia.org/wiki/Don%27t-care_term + + """ + if not minterms: + return false + + variables = tuple(map(sympify, variables)) + minterms = _input_to_binlist(minterms, variables) + dontcares = _input_to_binlist((dontcares or []), variables) + for d in dontcares: + if d in minterms: + raise ValueError('%s in minterms is also in dontcares' % d) + + maxterms = [] + for t in product((0, 1), repeat=len(variables)): + t = list(t) + if (t not in minterms) and (t not in dontcares): + maxterms.append(t) + + new = _simplified_pairs(maxterms + dontcares) + essential = _rem_redundancy(new, maxterms) + return And(*[_convert_to_varsPOS(x, variables) for x in essential]) + + +def ANFform(variables, truthvalues): + """ + The ANFform function converts the list of truth values to + Algebraic Normal Form (ANF). + + The variables must be given as the first argument. + + Return True, False, logical :py:class:`~.And` function (i.e., the + "Zhegalkin monomial") or logical :py:class:`~.Xor` function (i.e., + the "Zhegalkin polynomial"). When True and False + are represented by 1 and 0, respectively, then + :py:class:`~.And` is multiplication and :py:class:`~.Xor` is addition. + + Formally a "Zhegalkin monomial" is the product (logical + And) of a finite set of distinct variables, including + the empty set whose product is denoted 1 (True). + A "Zhegalkin polynomial" is the sum (logical Xor) of a + set of Zhegalkin monomials, with the empty set denoted + by 0 (False). + + Parameters + ========== + + variables : list of variables + truthvalues : list of 1's and 0's (result column of truth table) + + Examples + ======== + >>> from sympy.logic.boolalg import ANFform + >>> from sympy.abc import x, y + >>> ANFform([x], [1, 0]) + x ^ True + >>> ANFform([x, y], [0, 1, 1, 1]) + x ^ y ^ (x & y) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Zhegalkin_polynomial + + """ + + n_vars = len(variables) + n_values = len(truthvalues) + + if n_values != 2 ** n_vars: + raise ValueError("The number of truth values must be equal to 2^%d, " + "got %d" % (n_vars, n_values)) + + variables = tuple(map(sympify, variables)) + + coeffs = anf_coeffs(truthvalues) + terms = [] + + for i, t in enumerate(product((0, 1), repeat=n_vars)): + if coeffs[i] == 1: + terms.append(t) + + return Xor(*[_convert_to_varsANF(x, variables) for x in terms], + remove_true=False) + + +def anf_coeffs(truthvalues): + """ + Convert a list of truth values of some boolean expression + to the list of coefficients of the polynomial mod 2 (exclusive + disjunction) representing the boolean expression in ANF + (i.e., the "Zhegalkin polynomial"). + + There are `2^n` possible Zhegalkin monomials in `n` variables, since + each monomial is fully specified by the presence or absence of + each variable. + + We can enumerate all the monomials. For example, boolean + function with four variables ``(a, b, c, d)`` can contain + up to `2^4 = 16` monomials. The 13-th monomial is the + product ``a & b & d``, because 13 in binary is 1, 1, 0, 1. + + A given monomial's presence or absence in a polynomial corresponds + to that monomial's coefficient being 1 or 0 respectively. + + Examples + ======== + >>> from sympy.logic.boolalg import anf_coeffs, bool_monomial, Xor + >>> from sympy.abc import a, b, c + >>> truthvalues = [0, 1, 1, 0, 0, 1, 0, 1] + >>> coeffs = anf_coeffs(truthvalues) + >>> coeffs + [0, 1, 1, 0, 0, 0, 1, 0] + >>> polynomial = Xor(*[ + ... bool_monomial(k, [a, b, c]) + ... for k, coeff in enumerate(coeffs) if coeff == 1 + ... ]) + >>> polynomial + b ^ c ^ (a & b) + + """ + + s = '{:b}'.format(len(truthvalues)) + n = len(s) - 1 + + if len(truthvalues) != 2**n: + raise ValueError("The number of truth values must be a power of two, " + "got %d" % len(truthvalues)) + + coeffs = [[v] for v in truthvalues] + + for i in range(n): + tmp = [] + for j in range(2 ** (n-i-1)): + tmp.append(coeffs[2*j] + + list(map(lambda x, y: x^y, coeffs[2*j], coeffs[2*j+1]))) + coeffs = tmp + + return coeffs[0] + + +def bool_minterm(k, variables): + """ + Return the k-th minterm. + + Minterms are numbered by a binary encoding of the complementation + pattern of the variables. This convention assigns the value 1 to + the direct form and 0 to the complemented form. + + Parameters + ========== + + k : int or list of 1's and 0's (complementation pattern) + variables : list of variables + + Examples + ======== + + >>> from sympy.logic.boolalg import bool_minterm + >>> from sympy.abc import x, y, z + >>> bool_minterm([1, 0, 1], [x, y, z]) + x & z & ~y + >>> bool_minterm(6, [x, y, z]) + x & y & ~z + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Canonical_normal_form#Indexing_minterms + + """ + if isinstance(k, int): + k = ibin(k, len(variables)) + variables = tuple(map(sympify, variables)) + return _convert_to_varsSOP(k, variables) + + +def bool_maxterm(k, variables): + """ + Return the k-th maxterm. + + Each maxterm is assigned an index based on the opposite + conventional binary encoding used for minterms. The maxterm + convention assigns the value 0 to the direct form and 1 to + the complemented form. + + Parameters + ========== + + k : int or list of 1's and 0's (complementation pattern) + variables : list of variables + + Examples + ======== + >>> from sympy.logic.boolalg import bool_maxterm + >>> from sympy.abc import x, y, z + >>> bool_maxterm([1, 0, 1], [x, y, z]) + y | ~x | ~z + >>> bool_maxterm(6, [x, y, z]) + z | ~x | ~y + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Canonical_normal_form#Indexing_maxterms + + """ + if isinstance(k, int): + k = ibin(k, len(variables)) + variables = tuple(map(sympify, variables)) + return _convert_to_varsPOS(k, variables) + + +def bool_monomial(k, variables): + """ + Return the k-th monomial. + + Monomials are numbered by a binary encoding of the presence and + absences of the variables. This convention assigns the value + 1 to the presence of variable and 0 to the absence of variable. + + Each boolean function can be uniquely represented by a + Zhegalkin Polynomial (Algebraic Normal Form). The Zhegalkin + Polynomial of the boolean function with `n` variables can contain + up to `2^n` monomials. We can enumerate all the monomials. + Each monomial is fully specified by the presence or absence + of each variable. + + For example, boolean function with four variables ``(a, b, c, d)`` + can contain up to `2^4 = 16` monomials. The 13-th monomial is the + product ``a & b & d``, because 13 in binary is 1, 1, 0, 1. + + Parameters + ========== + + k : int or list of 1's and 0's + variables : list of variables + + Examples + ======== + >>> from sympy.logic.boolalg import bool_monomial + >>> from sympy.abc import x, y, z + >>> bool_monomial([1, 0, 1], [x, y, z]) + x & z + >>> bool_monomial(6, [x, y, z]) + x & y + + """ + if isinstance(k, int): + k = ibin(k, len(variables)) + variables = tuple(map(sympify, variables)) + return _convert_to_varsANF(k, variables) + + +def _find_predicates(expr): + """Helper to find logical predicates in BooleanFunctions. + + A logical predicate is defined here as anything within a BooleanFunction + that is not a BooleanFunction itself. + + """ + if not isinstance(expr, BooleanFunction): + return {expr} + return set().union(*(map(_find_predicates, expr.args))) + + +def simplify_logic(expr, form=None, deep=True, force=False, dontcare=None): + """ + This function simplifies a boolean function to its simplified version + in SOP or POS form. The return type is an :py:class:`~.Or` or + :py:class:`~.And` object in SymPy. + + Parameters + ========== + + expr : Boolean + + form : string (``'cnf'`` or ``'dnf'``) or ``None`` (default). + If ``'cnf'`` or ``'dnf'``, the simplest expression in the corresponding + normal form is returned; if ``None``, the answer is returned + according to the form with fewest args (in CNF by default). + + deep : bool (default ``True``) + Indicates whether to recursively simplify any + non-boolean functions contained within the input. + + force : bool (default ``False``) + As the simplifications require exponential time in the number + of variables, there is by default a limit on expressions with + 8 variables. When the expression has more than 8 variables + only symbolical simplification (controlled by ``deep``) is + made. By setting ``force`` to ``True``, this limit is removed. Be + aware that this can lead to very long simplification times. + + dontcare : Boolean + Optimize expression under the assumption that inputs where this + expression is true are don't care. This is useful in e.g. Piecewise + conditions, where later conditions do not need to consider inputs that + are converted by previous conditions. For example, if a previous + condition is ``And(A, B)``, the simplification of expr can be made + with don't cares for ``And(A, B)``. + + Examples + ======== + + >>> from sympy.logic import simplify_logic + >>> from sympy.abc import x, y, z + >>> b = (~x & ~y & ~z) | ( ~x & ~y & z) + >>> simplify_logic(b) + ~x & ~y + >>> simplify_logic(x | y, dontcare=y) + x + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Don%27t-care_term + + """ + + if form not in (None, 'cnf', 'dnf'): + raise ValueError("form can be cnf or dnf only") + expr = sympify(expr) + # check for quick exit if form is given: right form and all args are + # literal and do not involve Not + if form: + form_ok = False + if form == 'cnf': + form_ok = is_cnf(expr) + elif form == 'dnf': + form_ok = is_dnf(expr) + + if form_ok and all(is_literal(a) + for a in expr.args): + return expr + from sympy.core.relational import Relational + if deep: + variables = expr.atoms(Relational) + from sympy.simplify.simplify import simplify + s = tuple(map(simplify, variables)) + expr = expr.xreplace(dict(zip(variables, s))) + if not isinstance(expr, BooleanFunction): + return expr + # Replace Relationals with Dummys to possibly + # reduce the number of variables + repl = {} + undo = {} + from sympy.core.symbol import Dummy + variables = expr.atoms(Relational) + if dontcare is not None: + dontcare = sympify(dontcare) + variables.update(dontcare.atoms(Relational)) + while variables: + var = variables.pop() + if var.is_Relational: + d = Dummy() + undo[d] = var + repl[var] = d + nvar = var.negated + if nvar in variables: + repl[nvar] = Not(d) + variables.remove(nvar) + + expr = expr.xreplace(repl) + + if dontcare is not None: + dontcare = dontcare.xreplace(repl) + + # Get new variables after replacing + variables = _find_predicates(expr) + if not force and len(variables) > 8: + return expr.xreplace(undo) + if dontcare is not None: + # Add variables from dontcare + dcvariables = _find_predicates(dontcare) + variables.update(dcvariables) + # if too many restore to variables only + if not force and len(variables) > 8: + variables = _find_predicates(expr) + dontcare = None + # group into constants and variable values + c, v = sift(ordered(variables), lambda x: x in (True, False), binary=True) + variables = c + v + # standardize constants to be 1 or 0 in keeping with truthtable + c = [1 if i == True else 0 for i in c] + truthtable = _get_truthtable(v, expr, c) + if dontcare is not None: + dctruthtable = _get_truthtable(v, dontcare, c) + truthtable = [t for t in truthtable if t not in dctruthtable] + else: + dctruthtable = [] + big = len(truthtable) >= (2 ** (len(variables) - 1)) + if form == 'dnf' or form is None and big: + return _sop_form(variables, truthtable, dctruthtable).xreplace(undo) + return POSform(variables, truthtable, dctruthtable).xreplace(undo) + + +def _get_truthtable(variables, expr, const): + """ Return a list of all combinations leading to a True result for ``expr``. + """ + _variables = variables.copy() + def _get_tt(inputs): + if _variables: + v = _variables.pop() + tab = [[i[0].xreplace({v: false}), [0] + i[1]] for i in inputs if i[0] is not false] + tab.extend([[i[0].xreplace({v: true}), [1] + i[1]] for i in inputs if i[0] is not false]) + return _get_tt(tab) + return inputs + res = [const + k[1] for k in _get_tt([[expr, []]]) if k[0]] + if res == [[]]: + return [] + else: + return res + + +def _finger(eq): + """ + Assign a 5-item fingerprint to each symbol in the equation: + [ + # of times it appeared as a Symbol; + # of times it appeared as a Not(symbol); + # of times it appeared as a Symbol in an And or Or; + # of times it appeared as a Not(Symbol) in an And or Or; + a sorted tuple of tuples, (i, j, k), where i is the number of arguments + in an And or Or with which it appeared as a Symbol, and j is + the number of arguments that were Not(Symbol); k is the number + of times that (i, j) was seen. + ] + + Examples + ======== + + >>> from sympy.logic.boolalg import _finger as finger + >>> from sympy import And, Or, Not, Xor, to_cnf, symbols + >>> from sympy.abc import a, b, x, y + >>> eq = Or(And(Not(y), a), And(Not(y), b), And(x, y)) + >>> dict(finger(eq)) + {(0, 0, 1, 0, ((2, 0, 1),)): [x], + (0, 0, 1, 0, ((2, 1, 1),)): [a, b], + (0, 0, 1, 2, ((2, 0, 1),)): [y]} + >>> dict(finger(x & ~y)) + {(0, 1, 0, 0, ()): [y], (1, 0, 0, 0, ()): [x]} + + In the following, the (5, 2, 6) means that there were 6 Or + functions in which a symbol appeared as itself amongst 5 arguments in + which there were also 2 negated symbols, e.g. ``(a0 | a1 | a2 | ~a3 | ~a4)`` + is counted once for a0, a1 and a2. + + >>> dict(finger(to_cnf(Xor(*symbols('a:5'))))) + {(0, 0, 8, 8, ((5, 0, 1), (5, 2, 6), (5, 4, 1))): [a0, a1, a2, a3, a4]} + + The equation must not have more than one level of nesting: + + >>> dict(finger(And(Or(x, y), y))) + {(0, 0, 1, 0, ((2, 0, 1),)): [x], (1, 0, 1, 0, ((2, 0, 1),)): [y]} + >>> dict(finger(And(Or(x, And(a, x)), y))) + Traceback (most recent call last): + ... + NotImplementedError: unexpected level of nesting + + So y and x have unique fingerprints, but a and b do not. + """ + f = eq.free_symbols + d = dict(list(zip(f, [[0]*4 + [defaultdict(int)] for fi in f]))) + for a in eq.args: + if a.is_Symbol: + d[a][0] += 1 + elif a.is_Not: + d[a.args[0]][1] += 1 + else: + o = len(a.args), sum(isinstance(ai, Not) for ai in a.args) + for ai in a.args: + if ai.is_Symbol: + d[ai][2] += 1 + d[ai][-1][o] += 1 + elif ai.is_Not: + d[ai.args[0]][3] += 1 + else: + raise NotImplementedError('unexpected level of nesting') + inv = defaultdict(list) + for k, v in ordered(iter(d.items())): + v[-1] = tuple(sorted([i + (j,) for i, j in v[-1].items()])) + inv[tuple(v)].append(k) + return inv + + +def bool_map(bool1, bool2): + """ + Return the simplified version of *bool1*, and the mapping of variables + that makes the two expressions *bool1* and *bool2* represent the same + logical behaviour for some correspondence between the variables + of each. + If more than one mappings of this sort exist, one of them + is returned. + + For example, ``And(x, y)`` is logically equivalent to ``And(a, b)`` for + the mapping ``{x: a, y: b}`` or ``{x: b, y: a}``. + If no such mapping exists, return ``False``. + + Examples + ======== + + >>> from sympy import SOPform, bool_map, Or, And, Not, Xor + >>> from sympy.abc import w, x, y, z, a, b, c, d + >>> function1 = SOPform([x, z, y],[[1, 0, 1], [0, 0, 1]]) + >>> function2 = SOPform([a, b, c],[[1, 0, 1], [1, 0, 0]]) + >>> bool_map(function1, function2) + (y & ~z, {y: a, z: b}) + + The results are not necessarily unique, but they are canonical. Here, + ``(w, z)`` could be ``(a, d)`` or ``(d, a)``: + + >>> eq = Or(And(Not(y), w), And(Not(y), z), And(x, y)) + >>> eq2 = Or(And(Not(c), a), And(Not(c), d), And(b, c)) + >>> bool_map(eq, eq2) + ((x & y) | (w & ~y) | (z & ~y), {w: a, x: b, y: c, z: d}) + >>> eq = And(Xor(a, b), c, And(c,d)) + >>> bool_map(eq, eq.subs(c, x)) + (c & d & (a | b) & (~a | ~b), {a: a, b: b, c: d, d: x}) + + """ + + def match(function1, function2): + """Return the mapping that equates variables between two + simplified boolean expressions if possible. + + By "simplified" we mean that a function has been denested + and is either an And (or an Or) whose arguments are either + symbols (x), negated symbols (Not(x)), or Or (or an And) whose + arguments are only symbols or negated symbols. For example, + ``And(x, Not(y), Or(w, Not(z)))``. + + Basic.match is not robust enough (see issue 4835) so this is + a workaround that is valid for simplified boolean expressions + """ + + # do some quick checks + if function1.__class__ != function2.__class__: + return None # maybe simplification makes them the same? + if len(function1.args) != len(function2.args): + return None # maybe simplification makes them the same? + if function1.is_Symbol: + return {function1: function2} + + # get the fingerprint dictionaries + f1 = _finger(function1) + f2 = _finger(function2) + + # more quick checks + if len(f1) != len(f2): + return False + + # assemble the match dictionary if possible + matchdict = {} + for k in f1.keys(): + if k not in f2: + return False + if len(f1[k]) != len(f2[k]): + return False + for i, x in enumerate(f1[k]): + matchdict[x] = f2[k][i] + return matchdict + + a = simplify_logic(bool1) + b = simplify_logic(bool2) + m = match(a, b) + if m: + return a, m + return m + + +def _apply_patternbased_simplification(rv, patterns, measure, + dominatingvalue, + replacementvalue=None, + threeterm_patterns=None): + """ + Replace patterns of Relational + + Parameters + ========== + + rv : Expr + Boolean expression + + patterns : tuple + Tuple of tuples, with (pattern to simplify, simplified pattern) with + two terms. + + measure : function + Simplification measure. + + dominatingvalue : Boolean or ``None`` + The dominating value for the function of consideration. + For example, for :py:class:`~.And` ``S.false`` is dominating. + As soon as one expression is ``S.false`` in :py:class:`~.And`, + the whole expression is ``S.false``. + + replacementvalue : Boolean or ``None``, optional + The resulting value for the whole expression if one argument + evaluates to ``dominatingvalue``. + For example, for :py:class:`~.Nand` ``S.false`` is dominating, but + in this case the resulting value is ``S.true``. Default is ``None``. + If ``replacementvalue`` is ``None`` and ``dominatingvalue`` is not + ``None``, ``replacementvalue = dominatingvalue``. + + threeterm_patterns : tuple, optional + Tuple of tuples, with (pattern to simplify, simplified pattern) with + three terms. + + """ + from sympy.core.relational import Relational, _canonical + + if replacementvalue is None and dominatingvalue is not None: + replacementvalue = dominatingvalue + # Use replacement patterns for Relationals + Rel, nonRel = sift(rv.args, lambda i: isinstance(i, Relational), + binary=True) + if len(Rel) <= 1: + return rv + Rel, nonRealRel = sift(Rel, lambda i: not any(s.is_real is False + for s in i.free_symbols), + binary=True) + Rel = [i.canonical for i in Rel] + + if threeterm_patterns and len(Rel) >= 3: + Rel = _apply_patternbased_threeterm_simplification(Rel, + threeterm_patterns, rv.func, dominatingvalue, + replacementvalue, measure) + + Rel = _apply_patternbased_twoterm_simplification(Rel, patterns, + rv.func, dominatingvalue, replacementvalue, measure) + + rv = rv.func(*([_canonical(i) for i in ordered(Rel)] + + nonRel + nonRealRel)) + return rv + + +def _apply_patternbased_twoterm_simplification(Rel, patterns, func, + dominatingvalue, + replacementvalue, + measure): + """ Apply pattern-based two-term simplification.""" + from sympy.functions.elementary.miscellaneous import Min, Max + from sympy.core.relational import Ge, Gt, _Inequality + changed = True + while changed and len(Rel) >= 2: + changed = False + # Use only < or <= + Rel = [r.reversed if isinstance(r, (Ge, Gt)) else r for r in Rel] + # Sort based on ordered + Rel = list(ordered(Rel)) + # Eq and Ne must be tested reversed as well + rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel] + # Create a list of possible replacements + results = [] + # Try all combinations of possibly reversed relational + for ((i, pi), (j, pj)) in combinations(enumerate(rtmp), 2): + for pattern, simp in patterns: + res = [] + for p1, p2 in product(pi, pj): + # use SymPy matching + oldexpr = Tuple(p1, p2) + tmpres = oldexpr.match(pattern) + if tmpres: + res.append((tmpres, oldexpr)) + + if res: + for tmpres, oldexpr in res: + # we have a matching, compute replacement + np = simp.xreplace(tmpres) + if np == dominatingvalue: + # if dominatingvalue, the whole expression + # will be replacementvalue + return [replacementvalue] + # add replacement + if not isinstance(np, ITE) and not np.has(Min, Max): + # We only want to use ITE and Min/Max replacements if + # they simplify to a relational + costsaving = measure(func(*oldexpr.args)) - measure(np) + if costsaving > 0: + results.append((costsaving, ([i, j], np))) + if results: + # Sort results based on complexity + results = sorted(results, + key=lambda pair: pair[0], reverse=True) + # Replace the one providing most simplification + replacement = results[0][1] + idx, newrel = replacement + idx.sort() + # Remove the old relationals + for index in reversed(idx): + del Rel[index] + if dominatingvalue is None or newrel != Not(dominatingvalue): + # Insert the new one (no need to insert a value that will + # not affect the result) + if newrel.func == func: + for a in newrel.args: + Rel.append(a) + else: + Rel.append(newrel) + # We did change something so try again + changed = True + return Rel + + +def _apply_patternbased_threeterm_simplification(Rel, patterns, func, + dominatingvalue, + replacementvalue, + measure): + """ Apply pattern-based three-term simplification.""" + from sympy.functions.elementary.miscellaneous import Min, Max + from sympy.core.relational import Le, Lt, _Inequality + changed = True + while changed and len(Rel) >= 3: + changed = False + # Use only > or >= + Rel = [r.reversed if isinstance(r, (Le, Lt)) else r for r in Rel] + # Sort based on ordered + Rel = list(ordered(Rel)) + # Create a list of possible replacements + results = [] + # Eq and Ne must be tested reversed as well + rtmp = [(r, ) if isinstance(r, _Inequality) else (r, r.reversed) for r in Rel] + # Try all combinations of possibly reversed relational + for ((i, pi), (j, pj), (k, pk)) in permutations(enumerate(rtmp), 3): + for pattern, simp in patterns: + res = [] + for p1, p2, p3 in product(pi, pj, pk): + # use SymPy matching + oldexpr = Tuple(p1, p2, p3) + tmpres = oldexpr.match(pattern) + if tmpres: + res.append((tmpres, oldexpr)) + + if res: + for tmpres, oldexpr in res: + # we have a matching, compute replacement + np = simp.xreplace(tmpres) + if np == dominatingvalue: + # if dominatingvalue, the whole expression + # will be replacementvalue + return [replacementvalue] + # add replacement + if not isinstance(np, ITE) and not np.has(Min, Max): + # We only want to use ITE and Min/Max replacements if + # they simplify to a relational + costsaving = measure(func(*oldexpr.args)) - measure(np) + if costsaving > 0: + results.append((costsaving, ([i, j, k], np))) + if results: + # Sort results based on complexity + results = sorted(results, + key=lambda pair: pair[0], reverse=True) + # Replace the one providing most simplification + replacement = results[0][1] + idx, newrel = replacement + idx.sort() + # Remove the old relationals + for index in reversed(idx): + del Rel[index] + if dominatingvalue is None or newrel != Not(dominatingvalue): + # Insert the new one (no need to insert a value that will + # not affect the result) + if newrel.func == func: + for a in newrel.args: + Rel.append(a) + else: + Rel.append(newrel) + # We did change something so try again + changed = True + return Rel + + +@cacheit +def _simplify_patterns_and(): + """ Two-term patterns for And.""" + + from sympy.core import Wild + from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt + from sympy.functions.elementary.complexes import Abs + from sympy.functions.elementary.miscellaneous import Min, Max + a = Wild('a') + b = Wild('b') + c = Wild('c') + # Relationals patterns should be in alphabetical order + # (pattern1, pattern2, simplified) + # Do not use Ge, Gt + _matchers_and = ((Tuple(Eq(a, b), Lt(a, b)), false), + #(Tuple(Eq(a, b), Lt(b, a)), S.false), + #(Tuple(Le(b, a), Lt(a, b)), S.false), + #(Tuple(Lt(b, a), Le(a, b)), S.false), + (Tuple(Lt(b, a), Lt(a, b)), false), + (Tuple(Eq(a, b), Le(b, a)), Eq(a, b)), + #(Tuple(Eq(a, b), Le(a, b)), Eq(a, b)), + #(Tuple(Le(b, a), Lt(b, a)), Gt(a, b)), + (Tuple(Le(b, a), Le(a, b)), Eq(a, b)), + #(Tuple(Le(b, a), Ne(a, b)), Gt(a, b)), + #(Tuple(Lt(b, a), Ne(a, b)), Gt(a, b)), + (Tuple(Le(a, b), Lt(a, b)), Lt(a, b)), + (Tuple(Le(a, b), Ne(a, b)), Lt(a, b)), + (Tuple(Lt(a, b), Ne(a, b)), Lt(a, b)), + # Sign + (Tuple(Eq(a, b), Eq(a, -b)), And(Eq(a, S.Zero), Eq(b, S.Zero))), + # Min/Max/ITE + (Tuple(Le(b, a), Le(c, a)), Ge(a, Max(b, c))), + (Tuple(Le(b, a), Lt(c, a)), ITE(b > c, Ge(a, b), Gt(a, c))), + (Tuple(Lt(b, a), Lt(c, a)), Gt(a, Max(b, c))), + (Tuple(Le(a, b), Le(a, c)), Le(a, Min(b, c))), + (Tuple(Le(a, b), Lt(a, c)), ITE(b < c, Le(a, b), Lt(a, c))), + (Tuple(Lt(a, b), Lt(a, c)), Lt(a, Min(b, c))), + (Tuple(Le(a, b), Le(c, a)), ITE(Eq(b, c), Eq(a, b), ITE(b < c, false, And(Le(a, b), Ge(a, c))))), + (Tuple(Le(c, a), Le(a, b)), ITE(Eq(b, c), Eq(a, b), ITE(b < c, false, And(Le(a, b), Ge(a, c))))), + (Tuple(Lt(a, b), Lt(c, a)), ITE(b < c, false, And(Lt(a, b), Gt(a, c)))), + (Tuple(Lt(c, a), Lt(a, b)), ITE(b < c, false, And(Lt(a, b), Gt(a, c)))), + (Tuple(Le(a, b), Lt(c, a)), ITE(b <= c, false, And(Le(a, b), Gt(a, c)))), + (Tuple(Le(c, a), Lt(a, b)), ITE(b <= c, false, And(Lt(a, b), Ge(a, c)))), + (Tuple(Eq(a, b), Eq(a, c)), ITE(Eq(b, c), Eq(a, b), false)), + (Tuple(Lt(a, b), Lt(-b, a)), ITE(b > 0, Lt(Abs(a), b), false)), + (Tuple(Le(a, b), Le(-b, a)), ITE(b >= 0, Le(Abs(a), b), false)), + ) + return _matchers_and + + +@cacheit +def _simplify_patterns_and3(): + """ Three-term patterns for And.""" + + from sympy.core import Wild + from sympy.core.relational import Eq, Ge, Gt + + a = Wild('a') + b = Wild('b') + c = Wild('c') + # Relationals patterns should be in alphabetical order + # (pattern1, pattern2, pattern3, simplified) + # Do not use Le, Lt + _matchers_and = ((Tuple(Ge(a, b), Ge(b, c), Gt(c, a)), false), + (Tuple(Ge(a, b), Gt(b, c), Gt(c, a)), false), + (Tuple(Gt(a, b), Gt(b, c), Gt(c, a)), false), + # (Tuple(Ge(c, a), Gt(a, b), Gt(b, c)), S.false), + # Lower bound relations + # Commented out combinations that does not simplify + (Tuple(Ge(a, b), Ge(a, c), Ge(b, c)), And(Ge(a, b), Ge(b, c))), + (Tuple(Ge(a, b), Ge(a, c), Gt(b, c)), And(Ge(a, b), Gt(b, c))), + # (Tuple(Ge(a, b), Gt(a, c), Ge(b, c)), And(Ge(a, b), Ge(b, c))), + (Tuple(Ge(a, b), Gt(a, c), Gt(b, c)), And(Ge(a, b), Gt(b, c))), + # (Tuple(Gt(a, b), Ge(a, c), Ge(b, c)), And(Gt(a, b), Ge(b, c))), + (Tuple(Ge(a, c), Gt(a, b), Gt(b, c)), And(Gt(a, b), Gt(b, c))), + (Tuple(Ge(b, c), Gt(a, b), Gt(a, c)), And(Gt(a, b), Ge(b, c))), + (Tuple(Gt(a, b), Gt(a, c), Gt(b, c)), And(Gt(a, b), Gt(b, c))), + # Upper bound relations + # Commented out combinations that does not simplify + (Tuple(Ge(b, a), Ge(c, a), Ge(b, c)), And(Ge(c, a), Ge(b, c))), + (Tuple(Ge(b, a), Ge(c, a), Gt(b, c)), And(Ge(c, a), Gt(b, c))), + # (Tuple(Ge(b, a), Gt(c, a), Ge(b, c)), And(Gt(c, a), Ge(b, c))), + (Tuple(Ge(b, a), Gt(c, a), Gt(b, c)), And(Gt(c, a), Gt(b, c))), + # (Tuple(Gt(b, a), Ge(c, a), Ge(b, c)), And(Ge(c, a), Ge(b, c))), + (Tuple(Ge(c, a), Gt(b, a), Gt(b, c)), And(Ge(c, a), Gt(b, c))), + (Tuple(Ge(b, c), Gt(b, a), Gt(c, a)), And(Gt(c, a), Ge(b, c))), + (Tuple(Gt(b, a), Gt(c, a), Gt(b, c)), And(Gt(c, a), Gt(b, c))), + # Circular relation + (Tuple(Ge(a, b), Ge(b, c), Ge(c, a)), And(Eq(a, b), Eq(b, c))), + ) + return _matchers_and + + +@cacheit +def _simplify_patterns_or(): + """ Two-term patterns for Or.""" + + from sympy.core import Wild + from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt + from sympy.functions.elementary.complexes import Abs + from sympy.functions.elementary.miscellaneous import Min, Max + a = Wild('a') + b = Wild('b') + c = Wild('c') + # Relationals patterns should be in alphabetical order + # (pattern1, pattern2, simplified) + # Do not use Ge, Gt + _matchers_or = ((Tuple(Le(b, a), Le(a, b)), true), + #(Tuple(Le(b, a), Lt(a, b)), true), + (Tuple(Le(b, a), Ne(a, b)), true), + #(Tuple(Le(a, b), Lt(b, a)), true), + #(Tuple(Le(a, b), Ne(a, b)), true), + #(Tuple(Eq(a, b), Le(b, a)), Ge(a, b)), + #(Tuple(Eq(a, b), Lt(b, a)), Ge(a, b)), + (Tuple(Eq(a, b), Le(a, b)), Le(a, b)), + (Tuple(Eq(a, b), Lt(a, b)), Le(a, b)), + #(Tuple(Le(b, a), Lt(b, a)), Ge(a, b)), + (Tuple(Lt(b, a), Lt(a, b)), Ne(a, b)), + (Tuple(Lt(b, a), Ne(a, b)), Ne(a, b)), + (Tuple(Le(a, b), Lt(a, b)), Le(a, b)), + #(Tuple(Lt(a, b), Ne(a, b)), Ne(a, b)), + (Tuple(Eq(a, b), Ne(a, c)), ITE(Eq(b, c), true, Ne(a, c))), + (Tuple(Ne(a, b), Ne(a, c)), ITE(Eq(b, c), Ne(a, b), true)), + # Min/Max/ITE + (Tuple(Le(b, a), Le(c, a)), Ge(a, Min(b, c))), + #(Tuple(Ge(b, a), Ge(c, a)), Ge(Min(b, c), a)), + (Tuple(Le(b, a), Lt(c, a)), ITE(b > c, Lt(c, a), Le(b, a))), + (Tuple(Lt(b, a), Lt(c, a)), Gt(a, Min(b, c))), + #(Tuple(Gt(b, a), Gt(c, a)), Gt(Min(b, c), a)), + (Tuple(Le(a, b), Le(a, c)), Le(a, Max(b, c))), + #(Tuple(Le(b, a), Le(c, a)), Le(Max(b, c), a)), + (Tuple(Le(a, b), Lt(a, c)), ITE(b >= c, Le(a, b), Lt(a, c))), + (Tuple(Lt(a, b), Lt(a, c)), Lt(a, Max(b, c))), + #(Tuple(Lt(b, a), Lt(c, a)), Lt(Max(b, c), a)), + (Tuple(Le(a, b), Le(c, a)), ITE(b >= c, true, Or(Le(a, b), Ge(a, c)))), + (Tuple(Le(c, a), Le(a, b)), ITE(b >= c, true, Or(Le(a, b), Ge(a, c)))), + (Tuple(Lt(a, b), Lt(c, a)), ITE(b > c, true, Or(Lt(a, b), Gt(a, c)))), + (Tuple(Lt(c, a), Lt(a, b)), ITE(b > c, true, Or(Lt(a, b), Gt(a, c)))), + (Tuple(Le(a, b), Lt(c, a)), ITE(b >= c, true, Or(Le(a, b), Gt(a, c)))), + (Tuple(Le(c, a), Lt(a, b)), ITE(b >= c, true, Or(Lt(a, b), Ge(a, c)))), + (Tuple(Lt(b, a), Lt(a, -b)), ITE(b >= 0, Gt(Abs(a), b), true)), + (Tuple(Le(b, a), Le(a, -b)), ITE(b > 0, Ge(Abs(a), b), true)), + ) + return _matchers_or + + +@cacheit +def _simplify_patterns_xor(): + """ Two-term patterns for Xor.""" + + from sympy.functions.elementary.miscellaneous import Min, Max + from sympy.core import Wild + from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt + a = Wild('a') + b = Wild('b') + c = Wild('c') + # Relationals patterns should be in alphabetical order + # (pattern1, pattern2, simplified) + # Do not use Ge, Gt + _matchers_xor = (#(Tuple(Le(b, a), Lt(a, b)), true), + #(Tuple(Lt(b, a), Le(a, b)), true), + #(Tuple(Eq(a, b), Le(b, a)), Gt(a, b)), + #(Tuple(Eq(a, b), Lt(b, a)), Ge(a, b)), + (Tuple(Eq(a, b), Le(a, b)), Lt(a, b)), + (Tuple(Eq(a, b), Lt(a, b)), Le(a, b)), + (Tuple(Le(a, b), Lt(a, b)), Eq(a, b)), + (Tuple(Le(a, b), Le(b, a)), Ne(a, b)), + (Tuple(Le(b, a), Ne(a, b)), Le(a, b)), + # (Tuple(Lt(b, a), Lt(a, b)), Ne(a, b)), + (Tuple(Lt(b, a), Ne(a, b)), Lt(a, b)), + # (Tuple(Le(a, b), Lt(a, b)), Eq(a, b)), + # (Tuple(Le(a, b), Ne(a, b)), Ge(a, b)), + # (Tuple(Lt(a, b), Ne(a, b)), Gt(a, b)), + # Min/Max/ITE + (Tuple(Le(b, a), Le(c, a)), + And(Ge(a, Min(b, c)), Lt(a, Max(b, c)))), + (Tuple(Le(b, a), Lt(c, a)), + ITE(b > c, And(Gt(a, c), Lt(a, b)), + And(Ge(a, b), Le(a, c)))), + (Tuple(Lt(b, a), Lt(c, a)), + And(Gt(a, Min(b, c)), Le(a, Max(b, c)))), + (Tuple(Le(a, b), Le(a, c)), + And(Le(a, Max(b, c)), Gt(a, Min(b, c)))), + (Tuple(Le(a, b), Lt(a, c)), + ITE(b < c, And(Lt(a, c), Gt(a, b)), + And(Le(a, b), Ge(a, c)))), + (Tuple(Lt(a, b), Lt(a, c)), + And(Lt(a, Max(b, c)), Ge(a, Min(b, c)))), + ) + return _matchers_xor + + +def simplify_univariate(expr): + """return a simplified version of univariate boolean expression, else ``expr``""" + from sympy.functions.elementary.piecewise import Piecewise + from sympy.core.relational import Eq, Ne + if not isinstance(expr, BooleanFunction): + return expr + if expr.atoms(Eq, Ne): + return expr + c = expr + free = c.free_symbols + if len(free) != 1: + return c + x = free.pop() + ok, i = Piecewise((0, c), evaluate=False + )._intervals(x, err_on_Eq=True) + if not ok: + return c + if not i: + return false + args = [] + for a, b, _, _ in i: + if a is S.NegativeInfinity: + if b is S.Infinity: + c = true + else: + if c.subs(x, b) == True: + c = (x <= b) + else: + c = (x < b) + else: + incl_a = (c.subs(x, a) == True) + incl_b = (c.subs(x, b) == True) + if incl_a and incl_b: + if b.is_infinite: + c = (x >= a) + else: + c = And(a <= x, x <= b) + elif incl_a: + c = And(a <= x, x < b) + elif incl_b: + if b.is_infinite: + c = (x > a) + else: + c = And(a < x, x <= b) + else: + c = And(a < x, x < b) + args.append(c) + return Or(*args) + + +# Classes corresponding to logic gates +# Used in gateinputcount method +BooleanGates = (And, Or, Xor, Nand, Nor, Not, Xnor, ITE) + +def gateinputcount(expr): + """ + Return the total number of inputs for the logic gates realizing the + Boolean expression. + + Returns + ======= + + int + Number of gate inputs + + Note + ==== + + Not all Boolean functions count as gate here, only those that are + considered to be standard gates. These are: :py:class:`~.And`, + :py:class:`~.Or`, :py:class:`~.Xor`, :py:class:`~.Not`, and + :py:class:`~.ITE` (multiplexer). :py:class:`~.Nand`, :py:class:`~.Nor`, + and :py:class:`~.Xnor` will be evaluated to ``Not(And())`` etc. + + Examples + ======== + + >>> from sympy.logic import And, Or, Nand, Not, gateinputcount + >>> from sympy.abc import x, y, z + >>> expr = And(x, y) + >>> gateinputcount(expr) + 2 + >>> gateinputcount(Or(expr, z)) + 4 + + Note that ``Nand`` is automatically evaluated to ``Not(And())`` so + + >>> gateinputcount(Nand(x, y, z)) + 4 + >>> gateinputcount(Not(And(x, y, z))) + 4 + + Although this can be avoided by using ``evaluate=False`` + + >>> gateinputcount(Nand(x, y, z, evaluate=False)) + 3 + + Also note that a comparison will count as a Boolean variable: + + >>> gateinputcount(And(x > z, y >= 2)) + 2 + + As will a symbol: + >>> gateinputcount(x) + 0 + + """ + if not isinstance(expr, Boolean): + raise TypeError("Expression must be Boolean") + if isinstance(expr, BooleanGates): + return len(expr.args) + sum(gateinputcount(x) for x in expr.args) + return 0 diff --git a/phivenv/Lib/site-packages/sympy/logic/inference.py b/phivenv/Lib/site-packages/sympy/logic/inference.py new file mode 100644 index 0000000000000000000000000000000000000000..c3798231c09ae351ea7e7026d622b834fea3e3fa --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/logic/inference.py @@ -0,0 +1,340 @@ +"""Inference in propositional logic""" + +from sympy.logic.boolalg import And, Not, conjuncts, to_cnf, BooleanFunction +from sympy.core.sorting import ordered +from sympy.core.sympify import sympify +from sympy.external.importtools import import_module + + +def literal_symbol(literal): + """ + The symbol in this literal (without the negation). + + Examples + ======== + + >>> from sympy.abc import A + >>> from sympy.logic.inference import literal_symbol + >>> literal_symbol(A) + A + >>> literal_symbol(~A) + A + + """ + + if literal is True or literal is False: + return literal + elif literal.is_Symbol: + return literal + elif literal.is_Not: + return literal_symbol(literal.args[0]) + else: + raise ValueError("Argument must be a boolean literal.") + + +def satisfiable(expr, algorithm=None, all_models=False, minimal=False, use_lra_theory=False): + """ + Check satisfiability of a propositional sentence. + Returns a model when it succeeds. + Returns {true: true} for trivially true expressions. + + On setting all_models to True, if given expr is satisfiable then + returns a generator of models. However, if expr is unsatisfiable + then returns a generator containing the single element False. + + Examples + ======== + + >>> from sympy.abc import A, B + >>> from sympy.logic.inference import satisfiable + >>> satisfiable(A & ~B) + {A: True, B: False} + >>> satisfiable(A & ~A) + False + >>> satisfiable(True) + {True: True} + >>> next(satisfiable(A & ~A, all_models=True)) + False + >>> models = satisfiable((A >> B) & B, all_models=True) + >>> next(models) + {A: False, B: True} + >>> next(models) + {A: True, B: True} + >>> def use_models(models): + ... for model in models: + ... if model: + ... # Do something with the model. + ... print(model) + ... else: + ... # Given expr is unsatisfiable. + ... print("UNSAT") + >>> use_models(satisfiable(A >> ~A, all_models=True)) + {A: False} + >>> use_models(satisfiable(A ^ A, all_models=True)) + UNSAT + + """ + if use_lra_theory: + if algorithm is not None and algorithm != "dpll2": + raise ValueError(f"Currently only dpll2 can handle using lra theory. {algorithm} is not handled.") + algorithm = "dpll2" + + if algorithm is None or algorithm == "pycosat": + pycosat = import_module('pycosat') + if pycosat is not None: + algorithm = "pycosat" + else: + if algorithm == "pycosat": + raise ImportError("pycosat module is not present") + # Silently fall back to dpll2 if pycosat + # is not installed + algorithm = "dpll2" + + if algorithm=="minisat22": + pysat = import_module('pysat') + if pysat is None: + algorithm = "dpll2" + + if algorithm=="z3": + z3 = import_module('z3') + if z3 is None: + algorithm = "dpll2" + + if algorithm == "dpll": + from sympy.logic.algorithms.dpll import dpll_satisfiable + return dpll_satisfiable(expr) + elif algorithm == "dpll2": + from sympy.logic.algorithms.dpll2 import dpll_satisfiable + return dpll_satisfiable(expr, all_models, use_lra_theory=use_lra_theory) + elif algorithm == "pycosat": + from sympy.logic.algorithms.pycosat_wrapper import pycosat_satisfiable + return pycosat_satisfiable(expr, all_models) + elif algorithm == "minisat22": + from sympy.logic.algorithms.minisat22_wrapper import minisat22_satisfiable + return minisat22_satisfiable(expr, all_models, minimal) + elif algorithm == "z3": + from sympy.logic.algorithms.z3_wrapper import z3_satisfiable + return z3_satisfiable(expr, all_models) + + raise NotImplementedError + + +def valid(expr): + """ + Check validity of a propositional sentence. + A valid propositional sentence is True under every assignment. + + Examples + ======== + + >>> from sympy.abc import A, B + >>> from sympy.logic.inference import valid + >>> valid(A | ~A) + True + >>> valid(A | B) + False + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Validity + + """ + return not satisfiable(Not(expr)) + + +def pl_true(expr, model=None, deep=False): + """ + Returns whether the given assignment is a model or not. + + If the assignment does not specify the value for every proposition, + this may return None to indicate 'not obvious'. + + Parameters + ========== + + model : dict, optional, default: {} + Mapping of symbols to boolean values to indicate assignment. + deep: boolean, optional, default: False + Gives the value of the expression under partial assignments + correctly. May still return None to indicate 'not obvious'. + + + Examples + ======== + + >>> from sympy.abc import A, B + >>> from sympy.logic.inference import pl_true + >>> pl_true( A & B, {A: True, B: True}) + True + >>> pl_true(A & B, {A: False}) + False + >>> pl_true(A & B, {A: True}) + >>> pl_true(A & B, {A: True}, deep=True) + >>> pl_true(A >> (B >> A)) + >>> pl_true(A >> (B >> A), deep=True) + True + >>> pl_true(A & ~A) + >>> pl_true(A & ~A, deep=True) + False + >>> pl_true(A & B & (~A | ~B), {A: True}) + >>> pl_true(A & B & (~A | ~B), {A: True}, deep=True) + False + + """ + + from sympy.core.symbol import Symbol + + boolean = (True, False) + + def _validate(expr): + if isinstance(expr, Symbol) or expr in boolean: + return True + if not isinstance(expr, BooleanFunction): + return False + return all(_validate(arg) for arg in expr.args) + + if expr in boolean: + return expr + expr = sympify(expr) + if not _validate(expr): + raise ValueError("%s is not a valid boolean expression" % expr) + if not model: + model = {} + model = {k: v for k, v in model.items() if v in boolean} + result = expr.subs(model) + if result in boolean: + return bool(result) + if deep: + model = dict.fromkeys(result.atoms(), True) + if pl_true(result, model): + if valid(result): + return True + else: + if not satisfiable(result): + return False + return None + + +def entails(expr, formula_set=None): + """ + Check whether the given expr_set entail an expr. + If formula_set is empty then it returns the validity of expr. + + Examples + ======== + + >>> from sympy.abc import A, B, C + >>> from sympy.logic.inference import entails + >>> entails(A, [A >> B, B >> C]) + False + >>> entails(C, [A >> B, B >> C, A]) + True + >>> entails(A >> B) + False + >>> entails(A >> (B >> A)) + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Logical_consequence + + """ + if formula_set: + formula_set = list(formula_set) + else: + formula_set = [] + formula_set.append(Not(expr)) + return not satisfiable(And(*formula_set)) + + +class KB: + """Base class for all knowledge bases""" + def __init__(self, sentence=None): + self.clauses_ = set() + if sentence: + self.tell(sentence) + + def tell(self, sentence): + raise NotImplementedError + + def ask(self, query): + raise NotImplementedError + + def retract(self, sentence): + raise NotImplementedError + + @property + def clauses(self): + return list(ordered(self.clauses_)) + + +class PropKB(KB): + """A KB for Propositional Logic. Inefficient, with no indexing.""" + + def tell(self, sentence): + """Add the sentence's clauses to the KB + + Examples + ======== + + >>> from sympy.logic.inference import PropKB + >>> from sympy.abc import x, y + >>> l = PropKB() + >>> l.clauses + [] + + >>> l.tell(x | y) + >>> l.clauses + [x | y] + + >>> l.tell(y) + >>> l.clauses + [y, x | y] + + """ + for c in conjuncts(to_cnf(sentence)): + self.clauses_.add(c) + + def ask(self, query): + """Checks if the query is true given the set of clauses. + + Examples + ======== + + >>> from sympy.logic.inference import PropKB + >>> from sympy.abc import x, y + >>> l = PropKB() + >>> l.tell(x & ~y) + >>> l.ask(x) + True + >>> l.ask(y) + False + + """ + return entails(query, self.clauses_) + + def retract(self, sentence): + """Remove the sentence's clauses from the KB + + Examples + ======== + + >>> from sympy.logic.inference import PropKB + >>> from sympy.abc import x, y + >>> l = PropKB() + >>> l.clauses + [] + + >>> l.tell(x | y) + >>> l.clauses + [x | y] + + >>> l.retract(x | y) + >>> l.clauses + [] + + """ + for c in conjuncts(to_cnf(sentence)): + self.clauses_.discard(c) diff --git a/phivenv/Lib/site-packages/sympy/release.py b/phivenv/Lib/site-packages/sympy/release.py new file mode 100644 index 0000000000000000000000000000000000000000..b9f68edb2f571e7bfaad9472d36a2d357b5574b2 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/release.py @@ -0,0 +1 @@ +__version__ = "1.14.0" diff --git a/phivenv/Lib/site-packages/sympy/this.py b/phivenv/Lib/site-packages/sympy/this.py new file mode 100644 index 0000000000000000000000000000000000000000..c2a5504ac42c7d57790d65524b066a6df86ab539 --- /dev/null +++ b/phivenv/Lib/site-packages/sympy/this.py @@ -0,0 +1,21 @@ +""" +The Zen of SymPy. +""" + +s = """The Zen of SymPy + +Unevaluated is better than evaluated. +The user interface matters. +Printing matters. +Pure Python can be fast enough. +If it's too slow, it's (probably) your fault. +Documentation matters. +Correctness is more important than speed. +Push it in now and improve upon it later. +Coverage by testing matters. +Smart tests are better than random tests. +But random tests sometimes find what your smartest test missed. +The Python way is probably the right way. +Community is more important than code.""" + +print(s)